|
ring0下的Inline hook
首发于先知社区:
https://xz.aliyun.com/t/10913
前言
Inline hook是直接在以前的函数替里面修改指令,用一个跳转或者其他指令来达到挂钩的目的。这是相对普通的hook来说,因为普通的hook只是修改函数的调用地址,而不是在原来的函数体里面做修改。一般来说,普通的hook比较稳定使用。inline hook 更加高级一点,一般也跟难以被发现。ring3的Inline hook在之前已经实现过了,再看看ring0的Inline hook该如何实现。
探究及实现
这里本来调用链应该是OpenFile -> ZwOpenFile,这里在od里面应该可以看到,这里我就用windbg直接找到这个ring0函数。
首先在windbg里面定位到ZwOpenFile函数,可以看到它的偏移为0x74
 
通过SSDT表找到所有的内核函数地址,再通过0x74偏移定位到ZwOpenFile函数
- kd> dd KeServiceDescriptorTable
- kd> dd 80505450 + 74 * 4
- kd> u 8057b182
复制代码
 
我这里因为windbg的原因没有显示函数名称,到pchunter里面确认一下,地址确实是一样的
 
那么我们要实现Inline hook,无论是使用E8 call,还是E9 jmp,都需要至少5个硬编码才能实现,所以这里我们找5个硬编码进行填入我们代码的操作,这里注意不能够找全局变量和重定位的地址,否则在进行还原的过程中可能地址已经发生改变导致程序不能够正常运行
- mov ebp,esp
- xor eax,eax
- push eax
复制代码
 
首先我们写一个FilterNtOpenFile函数用来打印我们Inline hook后的一些信息,这里用到PsGetCurrentProcess获取进程的EPROCESS结构,在0x174偏移存放着进程名,我们通过打印进程名来查看一下哪些进程调用了NtOpenFile这个函数
 
- char* p = "r0 InlineHook";
- void FilterNtOpenFile(char* p)
- {
- KdPrint(("%s\r\n", p));
- KdPrint(("name:%s\r\n", (char*)PsGetCurrentProcess() + 0x174));
- }
复制代码
然后提供ServiceDescriptorEntry这个结构体并定义KeServiceDescriptorTable为 ntoskrnl.exe 所导出的全局变量- typedef struct ServiceDescriptorEntry {
- unsigned int* ServiceTableBase;
- unsigned int* ServiceCounterTableBase;
- unsigned int NumberOfServices;
- unsigned char* ParamTableBase;
- } ServiceDescriptorTableEntry_t, * PServiceDescriptorTableEntry_t;
- __declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
复制代码
这里我们再利用汇编来执行我们的汇编代码之后再jmp到原覆盖地址+5的地方,先用pushad跟pushfd保存寄存器
- void _declspec(naked) NewNtOpenFile()
- {
- __asm
- {
- pushad
- pushfd
- push p
- call FilterNtOpenFile
- popfd
- popad
- mov ebp, esp
- xor eax, eax
- push eax
- jmp ReAddress
- }
- }
复制代码
首先定义一个数组,用来存放E9jmp跳转的代码
然后因为我们在8057b185这个地址开始覆盖,函数的起始地址为8057b182,所以偏移为3
然后通过KeServiceDescriptorTable的ServiceTableBase属性定位到NtOpenFile的起始地址,这里在PCHunter里面可以看到NtOpenFile的索引号为116
 
- StartAddr = KeServiceDescriptorTable.ServiceTableBase[116];
复制代码
定义返回地址,用函数的开始地址+偏移+5即可得到返回地址- ReAddress = StartAddr + ChangeAddr + 5;
复制代码
通过E9 jmp的计算公式还需要计算我们自己定义的函数newNtOpenKey相对于NtOpenFile的偏移量- ULONG jmpAddr = (ULONG)NewNtOpenFile - StartAddr - ChangeAddr - 5;
复制代码
将跳转代码写入数组- jmp_code[0] = 0xE9;
- *(ULONG*)&jmp_code[1] = jmpAddr;
复制代码
这里就需要写入内存了,这里需要关闭页的只读保护,定义一个ShutPageProtect函数将CR0寄存器的WP位置0- //关闭页只读保护
- __asm
- {
- push eax;
- mov eax, cr0;
- and eax, ~0x10000; // 与0x10000想与后取反
- mov cr0, eax;
- pop eax;
- ret;
- }
复制代码
关闭页保护之后首先将之前的硬编码保存,再进行覆盖- ShutPageProtect();
- RtlCopyMemory(Old_code, (PVOID)(StartAddr + ChangeAddr), 5);
- RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), jmp_code, 5);
复制代码
写入内存完毕之后再定义一个OpenPageProtect函数将CR0寄存器的WP恢复为1- void _declspec(naked) OpenPageProtect()
- {
- __asm
- {
- push eax;
- mov eax, cr0;
- or eax, 0x10000;
- mov cr0, eax;
- pop eax;
- ret;
- }
- }
复制代码
那么到这里我们的hook代码就已经完成,因为我们已经将原来的硬编码存入了Old_code这个数组,这里我们编写UnHookNtOpenFile时利用RtlCopyMemory写会到原内存即可- void UnHookNtOpenFile()
- {
- ULONG ChangeAddr = 3;
- ShutPageProtect();
- RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), Old_code, 5);
- OpenPageProtect();
- }
复制代码
再就是加载驱动和卸载驱动,在加载驱动中调用HookNtOpenFile,在卸载驱动中调用UnHookNtOpenFile即可- //卸载驱动
- void DriverUnload(DRIVER_OBJECT* obj)
- {
- //卸载钩子
- UnHookNtOpenFile();
- KdPrint(("驱动卸载成功!\n"));
- }
- /***驱动入口主函数***/
- NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* path)
- {
- KdPrint(("驱动启动成功!\n"));
- //安装钩子
- HookNtOpenFile();
- driver->DriverUnload = DriverUnload;
- return STATUS_SUCCESS;
- }
复制代码
完整代码如下- #include <ntddk.h>
- typedef struct ServiceDescriptorEntry {
- unsigned int* ServiceTableBase;
- unsigned int* ServiceCounterTableBase;
- unsigned int NumberOfServices;
- unsigned char* ParamTableBase;
- } ServiceDescriptorTableEntry_t, * PServiceDescriptorTableEntry_t;
- __declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
- // 关闭页只读保护
- void ShutPageProtect();
- // 开启页只读保护
- void OpenPageProtect();
- // 测试函数
- void FilterNtOpenFile(char* p);
- // 新NtOpenFile
- void NewNtOpenFile();
- // hook NtOpenFile
- void HookNtOpenFile();
- // unhook NtOpenFile
- void UnHookNtOpenFile();
- //关闭页只读保护
- void _declspec(naked) ShutPageProtect()
- {
- __asm
- {
- push eax;
- mov eax, cr0;
- and eax, ~0x10000;
- mov cr0, eax;
- pop eax;
- ret;
- }
- }
- //开启页只读保护
- void _declspec(naked) OpenPageProtect()
- {
- __asm
- {
- push eax;
- mov eax, cr0;
- or eax, 0x10000;
- mov cr0, eax;
- pop eax;
- ret;
- }
- }
- ULONG StartAddr;
- ULONG ReAddress;
- UCHAR Old_code[5];
- char* p = "r0 InlineHook";
- void FilterNtOpenFile(char* p)
- {
- KdPrint(("%s\r\n", p));
- KdPrint(("name:%s\r\n", (char*)PsGetCurrentProcess() + 0x174));
- }
- void _declspec(naked) NewNtOpenFile()
- {
- __asm
- {
- pushad
- pushfd
- push p
- call FilterNtOpenFile
- popfd
- popad
- mov ebp, esp
- xor eax, eax
- push eax
- jmp ReAddress
- }
- }
- void HookNtOpenFile()
- {
- // 存放跳转指令的数组
- UCHAR jmp_code[5] = "";
- // 在入口0x3处hook
- ULONG ChangeAddr = 3;
- // NtOpenFile函数的开始地址
- StartAddr = KeServiceDescriptorTable.ServiceTableBase[116];
- // 返回地址
- ReAddress = StartAddr + ChangeAddr + 5;
- // newNtOpenKey相对于NtOpenKey的偏移量
- ULONG jmpAddr = (ULONG)NewNtOpenFile - StartAddr - ChangeAddr - 5;
- // 使用jmp指令跳转,jmp = 0xE9
- jmp_code[0] = 0xE9;
- // 填入偏移地址
- *(ULONG*)&jmp_code[1] = jmpAddr;
-
- ShutPageProtect();
- RtlCopyMemory(Old_code, (PVOID)(StartAddr + ChangeAddr), 5);
- RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), jmp_code, 5);
- OpenPageProtect();
- }
- void UnHookNtOpenFile()
- {
- ULONG ChangeAddr = 3;
- ShutPageProtect();
- RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), Old_code, 5);
- OpenPageProtect();
- }
- //卸载驱动
- void DriverUnload(DRIVER_OBJECT* obj)
- {
- //卸载钩子
- UnHookNtOpenFile();
- KdPrint(("驱动卸载成功!\n"));
- }
- /***驱动入口主函数***/
- NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* path)
- {
- KdPrint(("驱动启动成功!\n"));
- //安装钩子
- HookNtOpenFile();
- driver->DriverUnload = DriverUnload;
- return STATUS_SUCCESS;
- }
复制代码
实现效果如下
 
|
|