之前的文章 【逆向】MinHook框架代码解读 简述了在用户模式下最简单的hook原理和一种框架。但是在内核当中使用hook遵循一样的原理,但是需要在部分细节上进行修改。
TL,DR:讲了内核中使用inline hook和ssdt表这两种比较基础的方式
windows rookit防护-Kernel Hook 1
基础 inline Hook
这里再次简述:
找到函数的地址
保存前n条合法的汇编指令
覆盖前n跳指令为一段跳到我们hook_func的跳板代码
在保存旧有n条指令的区域加上返回到后续地址的跳板代码,确保原始函数能被调用
所以在内核当中我们尝试 依旧这样做(这里我开了测试模式,关闭了内核隔离,而且调试器开着的,暂时不需要担心patchguard。在现代windows上肯定是不行的 )
那么在内核中如何找到要调用的函数呢?
在内核变成中可以使用MmGetSystemRoutineAddress获得内核函数的地址
#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寄存器
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)”。
unsigned __int64 syscall_entry = __readmsr(0xC0000082 );
这样就能得到SYSCALL 指令进入内核后的 RIP
#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的内存地址,其结构体如下
typedef struct _SYSTEM_SERVICE_TABLE { PVOID tableBase; PVOID serviceCountBase; ULONG64 numberOfServices; PVOID unkown; }SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
我们顺着之前的结果手动查找一下
开始第0号函数的地址计算:
offset = SSDT->tableBase[0] >> 4 = 0x27fe004>>4 = 0x27fe000 funcAddr = SSDT->tableBase+offset = SSDT->tableBase[DWORD(offset/4)] = 0xfffff805162c79f0+0x27fe000 = 0xfffff805165477f0
可以编写程序
#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,
失败版:
#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测试代码
#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; }