简介
关于 INLINEHOOK
的技术,既可以用于驱动,又可以用于应用层,其原理本质上是汇编指令替换。
因为应用层进程之间内存空间有隔离,所以要想HOOK函数,需要先注入模块到目标进程中。
INLINEHOOK的原理
操作系统中的函数的开头多数是固定格式,这里以 NtTerminateProcess
为例,查看汇编指令
1 2 3 4 5 6
| nt!NtTerminateProcess: 805d399e 8bff mov edi,edi 805d39a0 55 push ebp 805d39a1 8bec mov ebp,esp 805d39a3 83ec10 sub esp,10h 805d39a6 53 push ebx
|
可以看到前3条指令合起来占用5个字节的机器码,我们使用无条件跳转指令来替换它们,从而调用这个
函数时首先跳转到我们自定义的函数中,处理完毕后再跳转回来。
1 2
| _asm jmp MyNtTerminateProcess; 00e1143e e9e5fbffff jmp 00e11028
|
以上机器码 E9
表示 JMP
指令,后边4个字节为跳转的偏移 0xFFFFFBE5
,这里可以计算一下,
指令地址为 0x00E1143E
目标地址为 0x00E11028
,属于往前跳,所以跳转偏移是个负数,转换完并
求绝对值为 0x41B
,这里 0x00E1143E - 0x41B = 0x00E11023
并不是 0x00E11028
,是因为跳转
指令本身占了 5
个字节,CPU是从执行完指令之后的地址开始算的。
所以我们在替换 NtTerminateProcess
函数首 5
个字节时,可以使用如下公式计算跳转偏移
1
| 跳转偏移 = 目标地址(自定义函数地址) - 指令地址(原函数地址) - 5
|
注意在我们自定义的函数执行完毕后,如果还需要执行原函数,需要先运行替换的原5个字节指令,
再跳转回原函数中。获取 NtTerminateProcess
函数地址的方法这里不再专门说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ULONG NtTerminateProcessNextCodeAddr = 0;
__declspec(naked) void NakeNtTerminateProcess() { _asm { mov eax, [esp + 8]; push eax; mov eax, [esp + 8]; push eax; call ProxyNtTerminateProcess; add esp, 8; test eax, eax; je oricode; ret; oricode: nop; nop; nop; nop; nop; jmp NtTerminateProcessNextCodeAddr; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| NTSTATUS ProxyNtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus) { NTSTATUS Status = STATUS_SUCCESS; do { if (ProcessHandle == NULL) break; if (ProcessHandle == (HANDLE)-1) break; PsGetCurrentProcessId(); if (!NT_SUCCESS(Status)) return STATUS_ACCESS_DENIED; } while (0); return STATUS_SUCCESS; }
|
如下为HOOK操作函数,如果要卸载HOOK就是把替换的5个字节指令恢复回去,这里不再专门说明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 00D613D0 8B 44 24 08 mov eax,dword ptr [esp+8] 00D613D4 50 push eax 00D613D5 8B 44 24 08 mov eax,dword ptr [esp+8] 00D613D9 50 push eax 00D613DA E8 39 FD FF FF call ProxyNtTerminateProcess (0D61118h) 00D613DF 83 C4 08 add esp,8 00D613E2 85 C0 test eax,eax 00D613E4 74 01 je oricode (0D613E7h) 00D613E6 C3 ret oricode: 00D613E7 90 nop 00D613E8 90 nop 00D613E9 90 nop 00D613EA 90 nop 00D613EB 90 nop 00D613EC FF 25 30 81 D6 00 jmp dword ptr ds:[0D68130h]
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void InlineHook(IN PVOID FuncAddr, IN PVOID NakeAddr, OUT PULONG NextCodeAddr) { PULONG NopAddr = (PULONG)((ULONG)NakeAddr + 23); if (*NopAddr != 0x90909090) return; *NextCodeAddr = (ULONG)FuncAddr + 5; WPOff(); *(PUCHAR)NopAddr = *(PUCHAR)FuncAddr; *(PULONG)((PUCHAR)NopAddr + 1) = *(PULONG)((PUCHAR)FuncAddr + 1); *(PUCHAR)FuncAddr = 0xE9; *(PULONG)((PUCHAR)FuncAddr + 1) = (ULONG)NakeAddr - (ULONG)FuncAddr - 5; WPOn(); }
|
有些情况下,要HOOK的函数开头不是正好5个字节的指令,就需要我们内置一个反汇编引擎,反汇编
目标函数,至少找到5个字节的空间。比如7个字节的指令,仍然是相同的操作,先保存这7个字节的指令
到 Nake函数
预置的 0x90
中,然后保存下一条指令的地址,最后写入跳转指令,多余的空间写入 0x90
可以使用开源的minhook引擎:https://github.com/TsudaKageyu/minhook