windows rookit防护-Kernel Hook 1
之前的文章 【逆向】MinHook框架代码解读 简述了在用户模式下最简单的hook原理和一种框架。但是在内核当中使用hook遵循一样的原理,但是需要在部分细节上进行修改。
TL,DR:讲了内核中使用inline hook和ssdt表这两种比较基础的方式
基础 inline Hook
这里再次简述:
找到函数的地址
保存前n条合法的汇编指令
覆盖前n跳指令为一段跳到我们hook_func的跳板代码
在保存旧有n条指令的区域加上返回到后续地址的跳板代码,确保原始函数能被调用
所以在内核当中我们尝试 依旧这样做(这里我开了测试模式,关闭了内核隔离,而且调试器开着的,暂时不需要担心patchguard。在现代windows上肯定是不行的 )
那么在内核中如何找到要调用的函数呢?
在内核变成中可以使用MmGetSystemRoutineAddress获得内核函数的地址
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 #include <ntifs.h> #include <windef.h> VOID DriverUnload (PDRIVER_OBJECT DriverObject) { DbgPrint("Driver Stopping -> %wZ\n" , &DriverObject->DriverName); } NTSTATUS DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); DbgPrint("Driver Running -> %wZ\n" , &DriverObject->DriverName); DriverObject->DriverUnload = DriverUnload; NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING funcName = { 0 }; RtlInitUnicodeString(&funcName, L"NtOpenFile" ); PVOID funcPtr = MmGetSystemRoutineAddress(&funcName); DbgPrint("[test driver] NtOpenFile at: 0x%p\n" , funcPtr); return status; }
而且加载两份驱动得到的地址是一样的,所以按照R3的思路就可以hook所有R0中的操作了,也就是说其他驱动调用了NtOpenFile也能被hook到
逆向一下NtOpenFile的代码,需要复制17个字节(jmp rax的跳转是12字节,所以要复制的汇编必须>12)
详细代码参见附录
有点不一样的是代码使用了cr0寄存器
1 2 3 4 5 6 7 8 9 10 11 VOID DisableWP () { __writecr0(__readcr0() & (~0x10000 )); _disable(); } VOID EnableWP () { __writecr0(__readcr0() | 0x10000 ); _enable(); }
修改 CR0 的目的是 临时关闭 CPU 的写保护机制(Write Protect) ,这样内核代码页(如 NtOpenFile 所在的 .text 段)才能被修改。
在寄存器 CR0 中设置 PE 标志,将使处理器切换至保护模式;进而启用段保护机制。一旦进入保护模式,便不再存在用于开启或关闭该保护机制的控制位。不过,即使处于保护模式下,仍可通过为所有段选择符和段描述符分配特权级 0(最高特权级),从而实质上禁用段保护机制中基于特权级的那部分功能。此操作将消除段与段之间的特权级保护屏障,但诸如界限检查和类型检查等其他保护检查仍将照常执行。 当启用分页功能(即在寄存器 CR0 中设置 PG 标志)时,页级保护功能将自动开启。同样地,一旦启用了分页功能,便不再存在用于关闭页级保护的模式位。然而,通过执行以下操作,仍可禁用页级保护:
清除控制寄存器 CR0 中的 WP 标志。
为每一个页目录项和页表项设置读/写(R/W)标志及用户/管理(U/S)标志。 此操作将使每一个页面均变为可写的用户页面,从而在实质上禁用了页级保护功能。
详细可见:《英特尔® 64 位和 IA-32 架构开发人员手册合订本》第三卷第6章
最后的效果:
安装hook
运行到了自己的NtOpenFile函数
打印出来的结果
SSDT Hook
SSDT是系统服务描述符表,里面存储了操作系统的一些底层实现,包括没有导出的一些函数(MmGetSystemRoutineAddress找不到)
这里需要用到MSR寄存器,MSR(Model-Specific Register)是 CPU 提供的一组 特殊寄存器 ,用于控制或查询处理器的特定功能。依旧在IA32手册中找到定义,位于第一卷3.2 OVERVIEW OF THE BASIC EXECUTION ENVIRONMENT(3-4 Vol. 1):
特定型号寄存器(MSRs)——处理器提供多种特定型号寄存器,用于控制和报告处理器的性能。几乎所有的 MSR 均负责处理系统相关功能,且应用程序无法直接访问。此规则的一个例外是时间戳计数器(Time-Stamp Counter)。有关 MSR 的详细描述,请参阅《Intel® 64 和 IA-32 架构软件开发人员手册》第 4 卷中的第 2 章“特定型号寄存器(MSRs)”。
1 unsigned __int64 syscall_entry = __readmsr(0xC0000082 );
这样就能得到SYSCALL 指令进入内核后的 RIP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <ntifs.h> VOID DriverUnload (PDRIVER_OBJECT DriverObject) { DbgPrint("Driver Unloaded\n" ); } NTSTATUS DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); DbgPrint("Driver Loaded\n" ); DriverObject->DriverUnload = DriverUnload; DWORD64 dmsr = __readmsr(0xC0000082 ); DbgPrint("KiSystemCall64 at %p\n" , dmsr); return STATUS_SUCCESS; }
解析SSDT
在开启系统隔离的情况下,得到的是KiSystemCall64Shadow,没有开启是KiSystemCall64。我这里没有开启内核隔离
然后继续向下查到KiSystemServiceRepeat,主要是为了拿到KeServiceDescriptorTable或者KeServiceDescriptorTableShadow的值,在这里就是
35 05 9F 00和AE B6 8E 00
通过这种偏移可以找到保存ssdt的内存地址,其结构体如下
1 2 3 4 5 6 7 8 typedef struct _SYSTEM_SERVICE_TABLE { PVOID tableBase; PVOID serviceCountBase; ULONG64 numberOfServices; PVOID unkown; }SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
我们顺着之前的结果手动查找一下
开始第0号函数的地址计算:
1 2 3 offset = SSDT->tableBase[0] >> 4 = 0x27fe004>>4 = 0x27fe000 funcAddr = SSDT->tableBase+offset = SSDT->tableBase[DWORD(offset/4)] = 0xfffff805162c79f0+0x27fe000 = 0xfffff805165477f0
可以编写程序
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 #include <ntifs.h> typedef struct _SYSTEM_SERVICE_TABLE { PVOID tableBase; PVOID serviceCountBase; ULONG64 numberOfServices; PVOID unkown; }SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE; LONG64 GetFuncAddr (size_t index, PSYSTEM_SERVICE_TABLE ssdt) { if (index > ssdt->numberOfServices) return 0 ; LONG offset = ((PLONG)(ssdt->tableBase))[index] >> 4 ; return (DWORDLONG)(ssdt->tableBase) + offset; } VOID DriverUnload (PDRIVER_OBJECT DriverObject) { DbgPrint("Driver Unloaded\n" ); } NTSTATUS DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); DbgPrint("Driver Loaded\n" ); DriverObject->DriverUnload = DriverUnload; DWORD64 dmsr = __readmsr(0xC0000082 ); DbgPrint("KiSystemCall64 at %p\n" , dmsr); PUCHAR tempptr = (PUCHAR)(dmsr); LONG offset = 0 ; PSYSTEM_SERVICE_TABLE table = NULL ; for (size_t i = 0 ; i < 0x1000 ; i++) { if (*(tempptr + i) == 0x4c && *(tempptr + i + 1 ) == 0x8d && *(tempptr + i + 2 ) == 0x15 ) { offset = *((PLONG)(tempptr + i + 3 )); table = (PSYSTEM_SERVICE_TABLE)(tempptr + i + 7 + offset); break ; } } if (offset == 0 ) { DbgPrint("Not found KeServiceDescriptorTable\n" ); return STATUS_NOT_FOUND; } DbgPrint("KeServiceDescriptorTable at: %p\n tableBase: %p\n" , table, table->tableBase); for (size_t i = 0 ; i < table->numberOfServices-1 ; i++) { DbgPrint("No.%d func at: %p\n" ,i, GetFuncAddr(i, table)); } return STATUS_SUCCESS; }
具体的index对应的函数,可以解析ntdll中的api实现的汇编中填入的值来确定(例如 mov eax, 0x33,然后进入到了KiSystemCall64)
尝试hook
找到ssdt后,替换table中的偏移值,从而实现hook,
失败版:
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 139 #include <ntifs.h> typedef NTSTATUS (*NTOPENFILE) ( PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, ULONG ShareAccess, ULONG OpenOptions ) ;typedef struct _SYSTEM_SERVICE_TABLE { PVOID tableBase; PVOID serviceCountBase; ULONG64 numberOfServices; PVOID unkown; }SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE; NTOPENFILE OriginalNtOpenFile = NULL ; ULONG NtOpenFileIndex = 51 ; PSYSTEM_SERVICE_TABLE table = NULL ; NTSTATUS HookNtOpenFile ( PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, ULONG ShareAccess, ULONG OpenOptions ) { if (ObjectAttributes && ObjectAttributes->ObjectName) { DbgPrint("HookNtOpenFile: %wZ\n" , ObjectAttributes->ObjectName); } return OriginalNtOpenFile( FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions ); } LONG64 GetFuncAddr (size_t index, PSYSTEM_SERVICE_TABLE ssdt) { if (index > ssdt->numberOfServices) return 0 ; LONG offset = ((PLONG)(ssdt->tableBase))[index] >> 4 ; return (DWORDLONG)(ssdt->tableBase) + offset; } VOID DisableWP () { ULONG64 cr0 = __readcr0(); cr0 &= 0xfffffffffffeffff ; __writecr0(cr0); _disable(); } VOID EnableWP () { ULONG64 cr0 = __readcr0(); cr0 |= 0x10000 ; __writecr0(cr0); _enable(); } VOID HookSSDT (PSYSTEM_SERVICE_TABLE ssdt) { PULONG table = ssdt->tableBase; ULONG entry = table[NtOpenFileIndex]; ULONG64 base = (ULONG64)table; ULONG64 original = base + (entry >> 4 ); OriginalNtOpenFile = (NTOPENFILE)original; ULONG param = entry & 0xF ; ULONG64 hookAddr = (ULONG64)HookNtOpenFile; ULONG newEntry = (ULONG)(((hookAddr - base) << 4 ) | param); DisableWP(); table[NtOpenFileIndex] = newEntry; EnableWP(); } VOID UnhookSSDT (PSYSTEM_SERVICE_TABLE ssdt) { PULONG table = ssdt->tableBase; ULONG entry = table[NtOpenFileIndex]; ULONG param = entry & 0xF ; ULONG64 base = (ULONG64)table; ULONG64 original = (ULONG64)OriginalNtOpenFile; ULONG newEntry = (ULONG)(((original - base) << 4 ) | param); DisableWP(); table[NtOpenFileIndex] = newEntry; EnableWP(); } VOID DriverUnload (PDRIVER_OBJECT DriverObject) { DbgPrint("Driver Unloaded\n" ); } NTSTATUS DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); DbgPrint("Driver Loaded\n" ); DriverObject->DriverUnload = DriverUnload; DWORD64 dmsr = __readmsr(0xC0000082 ); DbgPrint("KiSystemCall64 at %p\n" , dmsr); PUCHAR tempptr = (PUCHAR)(dmsr); LONG offset = 0 ; PSYSTEM_SERVICE_TABLE table = NULL ; for (size_t i = 0 ; i < 0x1000 ; i++) { if (*(tempptr + i) == 0x4c && *(tempptr + i + 1 ) == 0x8d && *(tempptr + i + 2 ) == 0x15 ) { offset = *((PLONG)(tempptr + i + 3 )); table = (PSYSTEM_SERVICE_TABLE)(tempptr + i + 7 + offset); break ; } } if (offset == 0 ) { DbgPrint("Not found KeServiceDescriptorTable\n" ); return STATUS_NOT_FOUND; } DbgPrint("KeServiceDescriptorTable at: %p\n tableBase: %p\n MyHook at: %p\n" , table, table->tableBase, HookNtOpenFile); DbgPrint("NtOpenFile at: %p\n" , GetFuncAddr(NtOpenFileIndex, table)); HookSSDT(table); DbgPrint("[hooked] NtOpenFile at: %p\n" , GetFuncAddr(NtOpenFileIndex, table)); UnhookSSDT(table); DbgPrint("[unhooked] NtOpenFile at: %p\n" , GetFuncAddr(NtOpenFileIndex, table)); return STATUS_SUCCESS; }
然后进行hook,但是最后调用的并不是我们的函数
因为table中存储大小是4字节的,所以寻址范围有限,这里就很像我们在ring3下使用inline hook编写Trampoline遇到的问题了,无法跳转到函数。有三种预期的解决方法
SSDT 附近的内核模块中放一个 trampoline,而且让他尽量靠近ssdt,这也是ring3下minhook框架中的方法
在 ntoskrnl 附近分配内存,然后作为trampoline。
在 ntoskrnl.exe 或 win32k.sys 找到未使用空间,覆盖他们为trampoline。win32k中的部分可能涉及到Shadow SSDT,该部分不会再本篇进行说明。
或者找到未导出函数的地址,然后inline hook即可
附录
在windows 10 上的inline hook测试代码
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 139 140 141 142 143 144 145 146 147 148 149 #include <ntddk.h> typedef NTSTATUS (*NTOPENFILE) ( PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, ULONG ShareAccess, ULONG OpenOptions ) ;NTOPENFILE OriginalNtOpenFile = NULL ; UCHAR OriginalBytes[17 ]; PVOID Trampoline = NULL ; PVOID TargetFunction = NULL ; VOID DisableWP () { ULONG64 cr0 = __readcr0(); cr0 &= 0xfffffffffffeffff ; __writecr0(cr0); _disable(); } VOID EnableWP () { ULONG64 cr0 = __readcr0(); cr0 |= 0x10000 ; __writecr0(cr0); _enable(); } NTSTATUS HookNtOpenFile ( PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, ULONG ShareAccess, ULONG OpenOptions ) { if (ObjectAttributes && ObjectAttributes->ObjectName) { DbgPrint("InlineHook NtOpenFile: %wZ\n" , ObjectAttributes->ObjectName); } return OriginalNtOpenFile( FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions ); } VOID BuildTrampoline () { Trampoline = ExAllocatePool2( POOL_FLAG_NON_PAGED_EXECUTE, 32 , 'HKTN' ); RtlCopyMemory(Trampoline, OriginalBytes, 17 ); UCHAR* p = (UCHAR*)Trampoline + 17 ; p[0 ] = 0x48 ; p[1 ] = 0xB8 ; *(PVOID*)(p + 2 ) = (PUCHAR)TargetFunction + 17 ; p[10 ] = 0xFF ; p[11 ] = 0xE0 ; OriginalNtOpenFile = (NTOPENFILE)Trampoline; } VOID InstallInlineHook () { UNICODE_STRING name; RtlInitUnicodeString(&name, L"NtOpenFile" ); TargetFunction = MmGetSystemRoutineAddress(&name); if (!TargetFunction) return ; RtlCopyMemory(OriginalBytes, TargetFunction, 17 ); BuildTrampoline(); DisableWP(); UCHAR patch[17 ]; patch[0 ] = 0x48 ; patch[1 ] = 0xB8 ; *(PVOID*)(patch + 2 ) = HookNtOpenFile; patch[10 ] = 0xFF ; patch[11 ] = 0xE0 ; patch[12 ] = 0x90 ; patch[13 ] = 0x90 ; patch[14 ] = 0x90 ; patch[15 ] = 0x90 ; patch[16 ] = 0x90 ; RtlCopyMemory(TargetFunction, patch, 17 ); EnableWP(); } VOID RemoveInlineHook () { if (!TargetFunction) return ; DisableWP(); RtlCopyMemory(TargetFunction, OriginalBytes, 17 ); EnableWP(); if (Trampoline) ExFreePool(Trampoline); } VOID DriverUnload (PDRIVER_OBJECT DriverObject) { RemoveInlineHook(); DbgPrint("Inline hook removed\n" ); } NTSTATUS DriverEntry ( PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath ) { UNREFERENCED_PARAMETER(RegistryPath); DriverObject->DriverUnload = DriverUnload; InstallInlineHook(); DbgPrint("Inline hook installed\n" ); return STATUS_SUCCESS; }