经过IRP那一章,发现如果文章篇幅过长(那篇我粘了很多结构定义的代码),网页端是无法显示出来博客内容的,只能分篇,看起来会比较麻烦,以后会尽量讲的精炼,也方便自己以后查看。虽然现在依然是4月19日,但是代码是4月17日写的,就算作是那天的博客吧。这一篇主要讲解一下SSDT Hook的手法,并实现一个简单的SSDT Hook
系统服务表&SSDT
关于系统服务表和SSDT的细节可以参考这里,本篇不再赘述,仅作简要介绍。
系统服务表(SystemServiceTable):之前在3环进0环时有讲过,因为3环调用0环函数时,需要提供一个系统服务号,就是在系统服务表里进行寻址用的。
SSDT(System Service Desciptor Table):系统服务描述符表,是一张已经导出的包含了4张系统服务表的表。其中只有第一张系统服务表可见。 通过如下语句声明,即可获取到SSDT表的首地址
1
| extern PKSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;
|
SSDT Shadow(System Service Desciptor Table Shadow):一张未导出的表,与SSDT类似,不同的在于,SSDT Shadow两张表均可见。由于未导出,需要通过其它方式进行查找,例如内存搜索。
SSDT Hook原理
Hook总的来说可以分为两类:表Hook(例如IAT Hook)和地址Hook(例如Inline Hook,下一篇会讲)
- 表Hook:主要是通过修改函数表中的函数,替换为自己的函数从而达到Hook的目的
- 地址Hook:通过修改函数内部的部分字节,从而跳转到指定地方,改变程序执行流程,从而达到Hook目的
本篇中介绍的SSDT Hook属于表Hook。SSDT Hook的原理在于,通过SSDT找对应的系统服务表,在系统服务表中找到指定的内核函数,将其替换为自己的函数,从而达到Hook的目的。
SSDT Hook思路
找到系统服务表
由于SSDT是导出的,声明后即可获取SSDT地址,但是内核文件并未提供SSDT及系统服务表的结构体,这里我们需要自己先定义SSDT和系统服务表的结构体,并声明SSDT,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| typedef struct _KSYSTEM_SERVICE_TABLE { PULONG ServiceTableBase; PULONG Count; ULONG ServiceLimit; PULONG ArgmentTableBase; }KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
typedef struct _KSERVICE_DESCRIPTOR_TABLE { KSYSTEM_SERVICE_TABLE ntoskrnl; KSYSTEM_SERVICE_TABLE win32k; KSYSTEM_SERVICE_TABLE notUsed1; KSYSTEM_SERVICE_TABLE notUsed2; }KSERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;
extern PKSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;
|
构建自己的函数
本次实验采用对NtOpenProcess进行Hook,所以我们需要构建一个自己的MyNtOpenProcess函数。构建自己的函数有如下注意事项:
- 参数必须与原函数一致,否则执行时会崩溃
- 实现自己的功能,例如打印参数等
- 调用原本的函数,若不调用原本的函数,该函数将无法执行下去实现原本的功能,也会造成程序崩溃。
所以需要先定义一个函数指针,并用一个全局变量保存原函数的地址(在系统服务表中找到NtOpenProcess,该函数的系统服务号是0x7A),在自己的MyNtOpenProcess中执行完自身的功能调用原函数完成整个程序的执行。代码如下:
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
| typedef NTSTATUS(*NTOPENPROCESS)( PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId );
ULONG OriginFunctionAddr = (KeServiceDescriptorTable->ServiceTableBase)[0x7A];
NTSTATUS MyNtOpenProcess( PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId ) { DbgPrint("ProcessHandle: %x, DesiredAccess: %x, ObjectAttributes: %x, ClientId: %x\n", ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
NTOPENPROCESS pOpenProcess = (NTOPENPROCESS)OriginFunctionAddr; pOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
return STATUS_SUCCESS; }
|
修改系统服务表地址
NtOpenProcess的系统服务号是0x7A,这一步看上去很简单,只需要修改系统服务表中,下标为0x7A那个函数的地址为自己MyNtOpenProcess函数的地址即可。但是有一点需要注意的是,系统服务表所对应的物理页是只读的,所以在修改前我们需要先修改物理页的属性。有如下两种方式:
通过页表基址修改物理页属性(注:这里的演示代码仅修改了PTE的属性,没有修改PDE的属性)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| if(RCR4 & 0x00000020) { KdPrint(("2-9-9-12分页 %p\n",RCR4)); KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8)))); *(DWORD64*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8)) |= 0x02; KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8)))); } else { KdPrint(("10-10-12分页\n")); KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC)))); *(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC)) |= 0x02; KdPrint(("PTE2 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC)))); }
|
通过修改CR0寄存器 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| VOID PageProtectOff() { __asm { cli mov eax, cr0 and eax, not 0x10000 mov cr0, eax } }
VOID PageProtectOn() { __asm { mov eax, cr0 or eax, 0x10000 mov cr0, eax sti } }
|
本次实验采用的是第二种方式,修改Cr0寄存器达成Hook的目的,代码如下:
1 2 3 4 5 6
| VOID HookNtOpenProcess() { PageProtectOff(); (KeServiceDescriptorTable->ServiceTableBase)[0x7A] = &MyNtOpenProcess; DbgPrint("Hook Starting...\n"); PageProtectOn(); }
|
设置Unhook函数
Hook完实现自身功能,达成目的之后,需要设置一个Unhook函数,把系统服务表再改回来,所以还需要设置一个Unhook函数,这个函数就比较简单了,只需要把原函数的地址再写回去即可,需要通过先前代码设置一个全局变量保存原函数的地址。代码如下:
1 2 3 4 5 6 7 8 9 10
| ULONG OriginFunctionAddr = (KeServiceDescriptorTable->ServiceTableBase)[0x7A];
VOID UnHookNtOpenProcess() { PageProtectOff(); (KeServiceDescriptorTable->ServiceTableBase)[0x7A] = OriginFunctionAddr; DbgPrint("UnHook Finished!\n"); PageProtectOn();· }
|
功能演示
在执行驱动前,查看PC Hunter,可以发现SSDT并没有函数被挂钩
执行后,在DebugView中可以看到,不断有NtOpenProcess的参数被写入,说明不断有新的进程被打开,我们打开一个记事本文件,同样会增加一些参数的打印
这时,我们再次查看PC Hunter,发现PC Hunter已经被检测到了SSDT表被挂钩子了,也说明这次实验SSDT 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
| #include "ntddk.h"
typedef NTSTATUS(*NTOPENPROCESS)( PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId );
typedef struct _KSYSTEM_SERVICE_TABLE { PULONG ServiceTableBase; PULONG Count; ULONG ServiceLimit; PULONG ArgmentTableBase; }KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
typedef struct _KSERVICE_DESCRIPTOR_TABLE { KSYSTEM_SERVICE_TABLE ntoskrnl; KSYSTEM_SERVICE_TABLE win32k; KSYSTEM_SERVICE_TABLE notUsed1; KSYSTEM_SERVICE_TABLE notUsed2; }KSERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;
extern PKSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;
NTSTATUS MyNtOpenProcess( PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId ); VOID HookNtOpenProcess(); VOID UnHookNtOpenProcess(); VOID PageProtectOff(); VOID PageProtectOn(); VOID Driver_Unload(PDRIVER_OBJECT pDriverObj);
ULONG OriginFunctionAddr;
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING RegistryPath) { DbgPrint("Driver is running!\n");
OriginFunctionAddr = (KeServiceDescriptorTable->ServiceTableBase)[0x7A];
HookNtOpenProcess();
pDriverObj->DriverUnload = Driver_Unload; return STATUS_SUCCESS; }
NTSTATUS MyNtOpenProcess( PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId ) { DbgPrint("ProcessHandle: %x, DesiredAccess: %x, ObjectAttributes: %x, ClientId: %x\n", ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
NTOPENPROCESS pOpenProcess = (NTOPENPROCESS)OriginFunctionAddr; pOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
return STATUS_SUCCESS; }
VOID HookNtOpenProcess() { PageProtectOff(); (KeServiceDescriptorTable->ServiceTableBase)[0x7A] = &MyNtOpenProcess; DbgPrint("Hook Starting...\n"); PageProtectOn(); }
VOID UnHookNtOpenProcess() { PageProtectOff(); (KeServiceDescriptorTable->ServiceTableBase)[0x7A] = OriginFunctionAddr; DbgPrint("UnHook Finished!\n"); PageProtectOn(); }
VOID PageProtectOff() { __asm { cli mov eax, cr0 and eax, not 0x10000 mov cr0, eax } }
VOID PageProtectOn() { __asm { mov eax, cr0 or eax, 0x10000 mov cr0, eax sti } }
VOID Driver_Unload(PDRIVER_OBJECT pDriverObj) { UnHookNtOpenProcess();
DbgPrint("Unload Success!\n"); }
|
总结
通过PC Hunter我们可以发现,SSDT Hook是很容易被检测出来的,也就容易有反制措施,下一篇会介绍另一种Hook的方式:Inline Hook来达成同样的功能或者目的。
参考资料
参考教程:https://www.bilibili.com/video/BV1NJ411M7aE?p=62
参考文章:https://blog.csdn.net/qq_41988448/article/details/103527830
参考笔记:馍馍的笔记