Windows驱动开发学习笔记(六)—— Inline HOOK
前言
一、学习自
滴水编程达人
中级班课程,官网:https://bcdaren.com
二、海东老师牛逼!
SSDT HOOK
缺点:
- 容易发现,容易绕过
- 只能HOOK系统服务表里的函数
Inline Hook
JMP的偏移 = 跳转地址 - 补丁地址 -5
挂钩
执行流程
脱钩
实验一:3环 Inline Hook
#include <stdio.h>
#include <windows.h>
BYTE* ProcAddr = 0; //原函数地址
DWORD OldProtect = 0; //保存旧的内存保护
BYTE OldCmd[20] = {0}; //存储原函数头部原始指令 在最后面构造返回到原函数的指令
BYTE* HookProcAddr = 0; //钩子函数的地址
//函数指针 将数据作为指令执行
typedef void (*POLDCMD)();
POLDCMD pOldCmd = (POLDCMD)(char*)OldCmd;
//printf的格式
char *format = "%x";
DWORD add(DWORD a, DWORD b)
{
return a + b;
}
void __declspec(naked) HookProc()
{
//保存现场
__asm
{
pushad
pushfd
}
//获得参数1
printf("a = ");
__asm
{
mov eax, esp
add eax, 40
push [eax]
push format
call printf
add esp, 8
}
//获得参数2
printf("\nb = ");
__asm
{
mov eax, esp
add eax, 44
push [eax]
push format
call printf
add esp, 8
}
printf("\n");
//还原现场
__asm
{
popfd
popad
//执行原函数头部指令 并返回到原函数下一行继续执行
jmp pOldCmd
}
}
void InlineHook()
{
//获得需要挂钩的函数地址
ProcAddr = (BYTE*)add + 1;
ProcAddr += *(DWORD*)ProcAddr + 4;
//修改内存保护
VirtualProtect(ProcAddr, 6, PAGE_EXECUTE_READWRITE, &OldProtect);
//将被覆盖的指令复制到数组中
strncpy((char*)OldCmd, (char*)ProcAddr, 6);
//数组末尾添上跳转指令 用于返回到被挂钩的函数
OldCmd[6] = 0xE9;
*(DWORD*)&OldCmd[7] = (DWORD)(ProcAddr+6) - (DWORD)&OldCmd[6] - 5;
//获得钩子函数的地址
HookProcAddr = (BYTE*)HookProc + 1;
HookProcAddr += *(DWORD*)HookProcAddr + 4;
//修改原函数的头部 跳转到钩子函数
ProcAddr[0] = 0xE9;
*(DWORD*)&ProcAddr[1] = HookProcAddr - ProcAddr - 5;
//跳转指令只占五个字节 原函数头部三行指令占六个字节 因此将多余字节填充为nop
ProcAddr[5] = 0x90;
}
void UnInlineHook()
{
//判断函数是否被挂钩
if(ProcAddr[0] == 0xE9)
{
strncpy((char*)ProcAddr, (char*)OldCmd, 6);
}
}
int main()
{
DWORD sum;
//挂钩
InlineHook();
sum = add(1, 2);
printf("sum = %x\n", sum);
//脱钩
UnInlineHook();
sum = add(3, 4);
printf("sum = %x\n", sum);
return 0;
}
执行结果:
实验二:0环 Inline Hook
#include <ntddk.h>
#include <ntstatus.h>
PUCHAR pNtOpenProcess;
UCHAR OldCmd[20] = {0};
//函数指针 用于将数据作为指令执行
typedef VOID (*POLDCMD)();
POLDCMD pOldCmd = (POLDCMD)(PUCHAR)OldCmd;
PCHAR format = "%x %x %x %x \r\n";
typedef NTSTATUS (*NTOPENPROCESS)(
PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId);
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; // 服务函数地址表基址
PULONG ServiceCounterTableBase; // SSDT函数被调用的次数
ULONG NumberOfService; // 服务函数的个数
PULONG ParamTableBase; // 服务函数参数表基址
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
KSYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe 的服务函数
KSYSTEM_SERVICE_TABLE win32k; // win32k.sys 的服务函数(GDI32.dll/User32.dll 的内核支持)
KSYSTEM_SERVICE_TABLE notUsed1;
KSYSTEM_SERVICE_TABLE notUsed2;
}KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
// KeServiceDescriptorTable 是 ntoskrnl.exe 所导出的全局变量 申明一下就可以直接使用了
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
VOID PageProtectOn()
{
__asm{//开启内存保护
mov eax,cr0
or eax,10000h
mov cr0,eax
sti
}
}
VOID PageProtectOff()
{
__asm{//关闭内存保护
cli
mov eax,cr0
and eax,not 10000h
mov cr0,eax
}
}
VOID __declspec(naked) HookProc(){
__asm
{
//保存现场
pushad
pushfd
mov eax, esp
add eax, 40 //eflags + 8个通用寄存器 + 返回地址
add eax, 12 //定位到最后一个参数
push [eax] //ClientId
push [eax-0x4] //ObjectAttributes
push [eax-0x8] //DesiredAccess
push [eax-0xC] //ProcessHandle
push format
call DbgPrint
add esp, 20
//还原现场
popfd
popad
//调用原本的指令,然后返回到原函数
jmp pOldCmd
}
}
NTSTATUS InlineHook()
{
NTSTATUS status;
status = STATUS_SUCCESS;
PageProtectOff();
//保存原来的指令
pNtOpenProcess = (PUCHAR)KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7A];
RtlMoveMemory(OldCmd, pNtOpenProcess, 5);
//构造回去的指令,用于返回到原函数
OldCmd[5] = 0xE9;
*(PULONG)&OldCmd[6] = ((ULONG)pNtOpenProcess+5) - (ULONG)&OldCmd[5] - 5;
//内核函数不存在中间人(call后通过jmp跳转),函数地址为实际地址
pNtOpenProcess[0] = 0xE9;
*(PULONG)&pNtOpenProcess[1] = (ULONG)HookProc - (ULONG)pNtOpenProcess - 5;
PageProtectOn();
return status;
}
NTSTATUS UnInlineHook()
{
NTSTATUS status;
status = STATUS_SUCCESS;
if(pNtOpenProcess[0] == 0xE9)
{
PageProtectOff();
RtlMoveMemory(pNtOpenProcess, OldCmd, 5);
PageProtectOn();
}
return status;
}
//卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{
//卸载驱动时脱钩
UnInlineHook();
DbgPrint("驱动程序已停止.\r\n");
}
//驱动程序入口函数,相当于控制台的main函数
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
DbgPrint("驱动程序已运行.\r\n");
//运行驱动时挂钩
InlineHook();
//设置一个卸载函数 便于退出
DriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
执行结果: