本文讲 用inline hook的方式修改NtOpenKey函数的一个例子
hook 计算机里面一般是指 挂钩某函数,也可以是替换掉原来的函数。
inline hook 是直接在以前的函数替里面修改指令,用一个跳转或者其他指令来达到挂钩的目的。 这是相对普通的hook来说,因为普通的hook只是修改函数的调用地址,而不是在原来的函数体里面做修改。
一般来说 普通的hook比较稳定使用。 inline hook 更加高级一点,一般也跟难以被发现。所以很多人比如病毒制作者都比较推崇inline hook。SSDT的全称是System Services Descriptor Table,系统服务描述符表
一般来说此表与链接系统内核的API密切相关,对此有一项应用就是杀毒软件的主动防御,当然病毒也可以通过修改主动防御的SSDT来绕过杀软的主动防御。SSDT hook一般是用来隐藏进程运行程序的,前面已经讲了。
在函数头部inline hook太容易被发现, 在函数中间inline 不容易被发现, 下面demo实现在函数中间inline
hook NtOpenFile函数, 使用PCHunter查看, 发现NtOpenFile已经被QQ的QQFrmMgr.sys hook了取ssdt表NtOpenFile的当前地址(QFrmMgr.sys hook地址), 在函数头+48的地方替换5个字节的 机器码 来跳转, 在QFrmMgr.sys里面加了一个inline hook模块执行流程 ntkrnlpa.exe ---> QQFrmMgr.sys ---> myinline.sys先用PCHunter查看下NtOpenKey的序号
完整代码
1 #include "ntddk.h" 2 3 4 extern "C" 5 { 6 #pragma pack(1) 7 typedef struct ServiceDescriptorEntry { 8 unsigned int *ServiceTableBase; 9 unsigned int *ServiceCounterTableBase; //仅适用于checked build版本10 unsigned int NumberOfServices;11 unsigned char *ParamTableBase;12 } ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;13 #pragma pack()14 __declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;15 void PageProtectOn()//恢复内存保护 16 {17 __asm18 {19 mov eax,cr020 or eax,10000h21 mov cr0,eax22 sti23 }24 }25 26 void PageProtectOff()//去掉内存保护27 { 28 __asm{29 cli30 mov eax,cr031 and eax,not 10000h32 mov cr0,eax33 }34 }35 ULONG g_ntopenkey;36 ULONG g_jmp_orig_ntopenkey;37 UCHAR g_orig_funcode[5];38 39 char *pp="my de inline hook";40 void FilterNtOpenFile(char* p1)41 {42 KdPrint(("kk:%s",pp));43 KdPrint(("kk:%s",(char*)PsGetCurrentProcess()+0x16c));44 }45 46 //__declspec(naked) 告诉编译器函数代码的汇编语言为自己的所写,不需要编译器添加任何汇编代码47 __declspec(naked) void NewNtOpenFile()48 {49 __asm{50 PUSHAD//保存所有寄存器51 push pp52 call FilterNtOpenFile53 POPAD//还原所有寄存器54 xor ecx,ecx55 mov eax,esi56 inc ecx57 jmp g_jmp_orig_ntopenkey58 }59 }60 61 void HookNtOpenKey()62 {63 UCHAR jmp_code[5]="";//存放修改指令的数组64 ULONG changeva=48;//相对于函数入口偏移48的位置hook65 g_ntopenkey = KeServiceDescriptorTable.ServiceTableBase[179];//NtOpenKey的入口地址66 g_jmp_orig_ntopenkey = g_ntopenkey + changeva+ 5;//调回的地址, 29从入口地址偏移29的地方改写指令, 指令长度567 ULONG u_jmp_temp = (ULONG)NewNtOpenFile - g_ntopenkey - changeva - 5;//计算 newNtOpenKey相对于NtOpenKey的偏移量68 jmp_code[0] = 0xE9;//0xE9对应jmp指令。 (上一篇使用E8(call)指令跳转, 需要在pop eax, jmp不需要)69 *(ULONG*)&jmp_code[1] = u_jmp_temp;//生成完整的修改指令70 PageProtectOff();//解锁内存71 RtlCopyMemory(g_orig_funcode,(PVOID)(g_ntopenkey+changeva), 5);//备份 原始指令, 已便还原72 RtlCopyMemory((PVOID)(g_ntopenkey+changeva), jmp_code, 5);//修改指令73 PageProtectOn();//锁定内存74 }75 76 void UnHookOpenFile()77 {78 PageProtectOff();79 RtlCopyMemory((PVOID)(g_ntopenkey+48),g_orig_funcode,5);//还原指令, 卸载hook80 PageProtectOn();81 }82 83 VOID MyUnload(PDRIVER_OBJECT pDriverObject)84 {85 UnHookOpenFile();//卸载86 }87 88 NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING Reg_Path)89 {90 HookNtOpenKey();91 g_ntopenkey = KeServiceDescriptorTable.ServiceTableBase[179];//179==NtOpenFile 函数的 ssdt表的数组下标92 pDriverObject->DriverUnload = MyUnload;93 return STATUS_SUCCESS;94 }95 }