Inline hook是直接在以前的函数替里面修改指令,用一个跳转或者其他指令来达到挂钩的目的。 这是相对普通的hook来说,因为普通的hook只是修改函数的调用地址,而不是在原来的函数体里面做修改。一般来说,普通的hook比较稳定使用。 inline hook 更加高级一点,一般也跟难以被发现。ring3的Inline hook在之前已经实现过了,再看看ring0的Inline hook该如何实现。
这里本来调用链应该是OpenFile -> ZwOpenFile
,这里在od里面应该可以看到,这里我就用windbg直接找到这个ring0函数。
首先在windbg里面定位到ZwOpenFile
函数,可以看到它的偏移为0x74
通过SSDT表找到所有的内核函数地址,再通过0x74
偏移定位到ZwOpenFile
函数
1 2 3
| kd> dd KeServiceDescriptorTable kd> dd 80505450 + 74 * 4 kd> u 8057b182
|
我这里因为windbg的原因没有显示函数名称,到pchunter里面确认一下,地址确实是一样的
那么我们要实现Inline hook,无论是使用E8 call,还是E9 jmp,都需要至少5个硬编码才能实现,所以这里我们找5个硬编码进行填入我们代码的操作,这里注意不能够找全局变量和重定位的地址,否则在进行还原的过程中可能地址已经发生改变导致程序不能够正常运行
1 2 3
| mov ebp,esp xor eax,eax push eax
|
首先我们写一个FilterNtOpenFile
函数用来打印我们Inline hook后的一些信息,这里用到PsGetCurrentProcess
获取进程的EPROCESS结构,在0x174偏移存放着进程名,我们通过打印进程名来查看一下哪些进程调用了NtOpenFile
这个函数
1 2 3 4 5 6
| 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
所导出的全局变量
1 2 3 4 5 6 7 8
| 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
保存寄存器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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
1
| StartAddr = KeServiceDescriptorTable.ServiceTableBase[116];
|
定义返回地址,用函数的开始地址+偏移+5即可得到返回地址
1
| ReAddress = StartAddr + ChangeAddr + 5;
|
通过E9 jmp的计算公式还需要计算我们自己定义的函数newNtOpenKey
相对于NtOpenFile
的偏移量
1
| ULONG jmpAddr = (ULONG)NewNtOpenFile - StartAddr - ChangeAddr - 5;
|
将跳转代码写入数组
1 2 3
| jmp_code[0] = 0xE9;
*(ULONG*)&jmp_code[1] = jmpAddr;
|
这里就需要写入内存了,这里需要关闭页的只读保护,定义一个ShutPageProtect
函数将CR0寄存器的WP位置0
1 2 3 4 5 6 7 8 9 10
| __asm { push eax; mov eax, cr0; and eax, ~0x10000; mov cr0, eax; pop eax; ret; }
|
关闭页保护之后首先将之前的硬编码保存,再进行覆盖
1 2 3
| ShutPageProtect(); RtlCopyMemory(Old_code, (PVOID)(StartAddr + ChangeAddr), 5); RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), jmp_code, 5);
|
写入内存完毕之后再定义一个OpenPageProtect
函数将CR0寄存器的WP恢复为1
1 2 3 4 5 6 7 8 9 10 11 12
| void _declspec(naked) OpenPageProtect() { __asm { push eax; mov eax, cr0; or eax, 0x10000; mov cr0, eax; pop eax; ret; } }
|
那么到这里我们的hook代码就已经完成,因为我们已经将原来的硬编码存入了Old_code
这个数组,这里我们编写UnHookNtOpenFile
时利用RtlCopyMemory
写会到原内存即可
1 2 3 4 5 6 7 8 9
| void UnHookNtOpenFile() { ULONG ChangeAddr = 3;
ShutPageProtect(); RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), Old_code, 5); OpenPageProtect();
}
|
再就是加载驱动和卸载驱动,在加载驱动中调用HookNtOpenFile
,在卸载驱动中调用UnHookNtOpenFile
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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; }
|
完整代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
| #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);
void NewNtOpenFile();
void HookNtOpenFile();
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] = ""; ULONG ChangeAddr = 3; StartAddr = KeServiceDescriptorTable.ServiceTableBase[116]; ReAddress = StartAddr + ChangeAddr + 5; ULONG jmpAddr = (ULONG)NewNtOpenFile - StartAddr - ChangeAddr - 5;
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; }
|
实现效果如下