0x00 SSDT:
SSDT 的全称是 System Services Descriptor Table,系统服务描述符表。
内核中有两个系统服务描述符表,一个是KeServiceDescriptorTable,另一个是KeServieDescriptorTableShadow。
KeServiceDescriptorTable 主要是处理来自 Ring3 层得 Kernel32.dll 中的系统调用,而 KeServiceDescriptorTableShadow 则主要处理来自 User32.dll 和 GDI32.dll 中的系统调用,并且 KeServiceDescriptorTable 在 ntoskrnl.exe(Windows 操作系统内核文件,包括内核和执行体层)是导出的,而 KeServiceDescriptorTableShadow 则是没有被 Windows 操作系统所导出,而关于 SSDT 的全部内容则都是通过 KeServiceDescriptorTable 来完成的。
SSDT即系统服务描述符表,它的结构如下:
// KSYSTEM_SERVICE_TABLE 和 KSERVICE_TABLE_DESCRIPTOR // 定义 SSDT 结构 typedef struct _KSYSTEM_SERVICE_TABLE { PULONG ServiceTableBase; // SSDT (System Service Dispatch Table)的基地址 PULONG ServiceCounterTableBase; // 用于 checked builds, 包含 SSDT 中每个服务被调用的次数 ULONG NumberOfService; // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小 ULONG ParamTableBase; // SSPT(System Service Parameter Table)的基地址 } KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE; typedef struct _KSERVICE_TABLE_DESCRIPTOR { KSYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe 的服务函数 KSYSTEM_SERVICE_TABLE win32k; // win32k.sys 的服务函数(GDI32.dll/User32.dll 的内核支持) KSYSTEM_SERVICE_TABLE notUsed1; KSYSTEM_SERVICE_TABLE notUsed2; }KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
0x01 API执行流程:
Ntdll.dll 中的 API 是一个简单的包装函数,当 Kernel32.dll 中的 API 通过 Ntdll.dll 时,会完成参数的检查,再调用一个中断(int 2Eh 或者 SysEnter 指令),从而实现从 Ring3 进入 Ring0 层,并且将所要调用的服务号(即 SSDT 数组中的索引值)存放到寄存器 EAX 中,并且将参数地址放到指定的寄存器(EDX)中,再将参数复制到内核地址空间中,再根据存放在 EAX 中的索引值来在 SSDT 数组中调用指定的服务。
nt!ZwOpenProcess: 840736cc b8be000000 mov eax,0BEh 840736d1 8d542404 lea edx,[esp+4] 840736d5 9c pushfd 840736d6 6a08 push 8 840736d8 e8b1190000 call nt!KiSystemService (8407508e) 840736dd c21000 ret 10h
kd> !idt 2e
Dumping IDT:
2e: 8407508e nt!KiSystemService
在上面调试中可以看到在 Ring0 下的 ZwOpenProcess将 0BEh 放入了寄存器 eax 中,然后调用了系统服务分发函数 KiSystemService,而 KiSystemService 函数则是根据 eax 寄存器中的索引值,然后再 SSDT 数组中找到索引值为 eax 寄存器中存放的值得那个 SSDT 项,最后就是根据这个 SSDT 项中所存放的系统服务的地址来调用这个系统服务了,比如在这里就是调用 KeServiceDescriptorTable[0BEh] 处所保存的地址所对应的系统服务了 ,也就是调用 Ring0 下的 NtOpenProcess了。
0x01 SSDT HOOK原理
如图可通过 ZwOpenProcess 获取 NtOpenProcess 索引号 0BEh,通过 KeServiceDescriptorTable 可获得 SSDT 首地址 840b5d9c。可以根据 SSDT 系统服务地址= SSDT 首地址 + 4 * 索引号(Address = 840b5d9c + 4 * 0BEh = 840b6094;),然后 840b6094->84249aa0 这个就是 NtOpenProcess 系统服务函数所在。我们需要做的就是将 84249aa0 地址替换成我们的Hook函数地址。
SDT Hook前后,NtOpenProcess 的当前地址会发生变化。变化后的当前地址:9d31b050 为我们自定义的Hook函数(即 HookNtOpenProcess)的地址。之后每次执行 NtOpenProcess 的时候,都会根据执行“当前地址”所指向的函数了,由于SSDT所在的物理页是只读、可执行的,需要修改页属性为可写(1.通过页表基址直接修改;2.通过修改CR0寄存器修改;3.通过MDL修改;)。
0x3 SSDT HOOK 思路汇总
1.加载驱动,获取需要保护进程的PID;
2.保存系统的原NtOpenProcess函数地址 ;
3.修改内存保护属性使得SSDT表可读可写 ;
4.修改SSDT表中NtOpenProcess函数处的地址为我们自定义Hook函数的地址,使调用时执行自定义的函数;
5.函数中做判断,如果是我们要保护的进程就直接返回,不让其获得句柄,其他则执行原NtOpenProcess函数;
6.驱动卸载,还原被Hook的地址;
#include <ntddk.h> // ntdll!NtWriteFile: // 001b : 77656a68 b88c010000 mov eax, 18Ch // 001b : 77656a6d ba0003fe7f mov edx, offset SharedUserData!SystemCallStub(7ffe0300) // 001b : 77656a72 ff12 call dword ptr[edx] // 001b : 77656a74 c22400 ret 24h // 001b : 77656a77 90 nop // //call dword ptr[edx]: //ntdll!KiFastSystemCall: // 001b : 776570b0 8bd4 mov edx, esp // 001b : 776570b2 0f34 sysenter // SSDT HOOK 实现进程保护 // 定义修复和恢复页属性的函数 PMDL MDSystemCall; PVOID *MappedSCT; // SSDT表结构体 typedef struct _ServiceDesriptorEntry { ULONG *ServiceTableBase; // SSDT (System Service Dispatch Table)的基地址 ULONG *ServiceCounterTableBase; // 用于 checked builds, 包含 SSDT 中每个服务被调用的次数 ULONG NumberOfServices; // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小 UCHAR *ParamTableBase; // SSPT(System Service Parameter Table)的基地址 }SSDTEntry, *PSSDTEntry; // 导入SSDT NTSYSAPI SSDTEntry KeServiceDescriptorTable; // 被保护程序的PID ULONG g_uProtectPID = 0; // OpenProcess函数原型 typedef NTSTATUS(NTAPI *NTOPENPROCESS)(__out PHANDLE ProcessHandle, __in ACCESS_MASK DesiredAccess, __in POBJECT_ATTRIBUTES ObjectAttributes, __in_opt PCLIENT_ID ClientId ); // 原OpenProcess函数地址 NTOPENPROCESS g_OldOpenProcess = NULL; // 自定义HookNtProcess函数 NTSTATUS NTAPI HookNtOpenProcess(__out PHANDLE ProcessHandle, __in ACCESS_MASK DesiredAccess, __in POBJECT_ATTRIBUTES ObjectAttributes, __in_opt PCLIENT_ID ClientId) { // 判断是不是要针对的进程PID if ((ULONG)ClientId->UniqueProcess == g_uProtectPID) { return STATUS_ABANDONED; } // 过滤完成再次调用原来的NtOpenprocess return g_OldOpenProcess( ProcessHandle, DesiredAccess, ObjectAttributes, ClientId ); } void _declspec(naked)OffMemoryProtect() { // MDSystemCall = MmCreateMdl(NULL, KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices * 4); // if (!MDSystemCall) // return STATUS_UNSUCCESSFUL; // MmBuildMdlForNonPagedPool(MDSystemCall); // MDSystemCall->MdlFlags = MDSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA; // MappedSCT = MmMapLockedPages(MDSystemCall, KernelMode); // 关闭内存保护 __asm { push eax; mov eax, CR0; and eax, not 0x10000; mov CR0, eax; pop eax; ret; } } void _declspec(naked)OnMemoryProtect() { // if (MDSystemCall) // { // MmUnmapLockedPages(MappedSCT, MDSystemCall); // IoFreeMdl(MDSystemCall); // } // 恢复内存保护 __asm { push eax; mov eax, CR0; or eax, 0x10000; mov CR0, eax; pop eax; ret; } } // 开启Hook void OnHook() { // 保存环境,OpenProcess函数在SSDT表中的第0xBE项(190) g_OldOpenProcess = (NTOPENPROCESS)KeServiceDescriptorTable.ServiceTableBase[0xBE]; // 开始Hook前需要修改内存属性,修改完地址后恢复内存属性 OffMemoryProtect(); KeServiceDescriptorTable.ServiceTableBase[0xBE] = (ULONG)HookNtOpenProcess; OnMemoryProtect(); } // 关闭Hook void OffHook() { // 修改地址前,先修改内存属性,改完后还原内存属性 OffMemoryProtect(); KeServiceDescriptorTable.ServiceTableBase[0xBE] = (ULONG)g_OldOpenProcess; OnMemoryProtect(); } // 驱动卸载函数 VOID DriverUnload(PDRIVER_OBJECT pDriver) { OffHook(); DbgPrint("------驱动卸载------\r\n"); } // 驱动入口函数 NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath) { // DbgBreakPoint(); DbgPrint("------驱动加载------\r\n"); g_uProtectPID = 3632; // 开启Hook OnHook(); pDriver->DriverUnload = DriverUnload; return STATUS_SUCCESS; }