SSDT 的全称是 System Services Descriptor Table,系统服务描述符表。这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来。SSDT 并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。通过修改此表的函数地址可以对常用 Windows 函数及 API 进行 Hook,从而实现对一些关心的系统动作进行过滤、监控的目的。一些 HIPS、防毒软件、系统监控、注册表监控软件往往会采用此接口来实现自己的监控模块。
结构 ssdt是一张表,即系统服务描述符表
1 kd> dd KeServiceDescriptorTable
第一个参数指向的地址存储的是全部的内核函数
这个参数代表ssdt表里面有多少个内核函数
这个参数是一个指针指向一个地址,这里表示的是与上面的内核函数相对应的参数个数,例如第一个为18,参数个数就为18/4 = 6
这里找一下OpenProcess
在SSDT表的索引,首先bp OpenProcess
断在了kerner32.OpenProcess
,这里OpenProcess
会调用ntdll里面的ZwOpenProcess
进入ring0,在ring0ZwOpenProcess
又会调用NtOpenProcess
赶紧去可以发现mov eax,0x7A
,那么这里ZwOpenProcess
的索引就为0x7A
然后通过KeServiceDescriptorTable
找到所有的内核函数,通过内核函数+偏移找到OpenProcess
函数
在 NT 4.0 以上的 Windows 操作系统中,默认就存在两个系统服务描述表,这两个调度表对应了两类不同的系统服务,这两个调度表为:KeServiceDescriptorTable 和 KeServiceDescriptorTableShadow,其中 KeServiceDescriptorTable 主要是处理来自 Ring3 层 Kernel32.dll 中的系统调用,而 KeServiceDescriptorTableShadow 则主要处理来自 User32.dll 和 GDI32.dll 中的系统调用,并且KeServiceDescriptorTable 在ntoskrnl.exe(Windows 操作系统内核文件,包括内核和执行体层)是导出的,而 KeServiceDescriptorTableShadow 则是没有被 Windows 操作系统所导出。
关于 SSDT 的全部内容则都是通过KeServiceDescriptorTable 来完成的。
SSDT表的结构通过结构体表示为如下:
1 2 3 4 5 6 7 8 typedef struct _KSERVICE_TABLE_DESCRIPTOR { KSYSTEM_SERVICE_TABLE ntoskrnl; KSYSTEM_SERVICE_TABLE win32k; (GDI32.dll/User32.dll 的内核支持) KSYSTEM_SERVICE_TABLE notUsed1; KSYSTEM_SERVICE_TABLE notUsed2; } KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;
其中每一项又是一个结构体: KSYSTEM_SERVICE_TABLE 。通过结构体表示为如下:
1 2 3 4 5 6 7 typedef struct _KSYSTEM_SERVICE_TABLE { PULONG ServiceTableBase; PULONG ServiceCounterTableBase; ULONG NumberOfService; ULONG ParamTableBase; } KSYSTEM_SERVICE_TABLE, * PKSYSTEM_SERVICE_TABLE;
调用号 进入0环时调用号是eax传递的,但这个调用号并不只是一个普通的数字作为索引序号,系统会把他用32位数据表示,拆分成19:1:12的格式,如下:
分析一下0-11这低12位组成一个真正的索引号,第12位表示服务表号,13-31位没有使用。而进入内核后调用哪一张表,就由调用号中的第12位决定,为0则调用SSDT表,为1则调用ShadowSSDT表。
CR4寄存器 这里函数准备好以后,就要将该函数的指针覆盖原来NtOpenProcess的指针。但是需要注意的是:我们自己改自己的代码是不用管权限的,改别人的代码很有可能这块内存是只读的,并不可写。
那么本质上就是SSDT对应的物理页是只读的,这里有两种办法,我们都知道物理页的内存R/W位的属性是由PDE和PTE相与而来的,那么我们就可以改变SSDT对应的PDE和PTE的R/W属性,将物理页设置为可读可写的。通过CR4寄存器判断是2-9-9-12分页还是10-10-12分页。
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寄存器 使用PsGetCurrentThread()
函数可获取当前KTHREAD的首地址。
但是需要注意的是SSDT表所在的内存页属性是只读,没有写入的权限,所以需要把该地址设置为可写入,这样才能写入自己的函数,使用的是CR0寄存器关闭只读属性。
简单介绍下CR0寄存器:
可以看到这里使用32位寄存器,而在CR0寄存器中,我们重点关注的是3个标志位:
PE 是否启用保护模式,置1则启用。 PG 是否使用分页模式, 置1则开启分页模式, 此标志置1时, PE 标志也必须置1,否则CPU报异常。 WP WP为1时, 不能修改只读的内存页 , WP为0时, 可以修改只读的内存页。
所以在进行HOOK时,只要把CR0寄存器中的WP位置为0,就能对内存进行写入操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 __asm { push eax; mov eax, cr0; and eax, ~0x10000 ; mov cr0, eax; pop eax; ret; } __asm { push eax; mov eax, cr0; or eax, 0x10000 ; mov cr0, eax; pop eax; ret; }
实现代码
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 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 #include <ntddk.h> #include <ntstatus.h> ULONG uOldNtOpenProcess; typedef struct _KSYSTEM_SERVICE_TABLE { PULONG ServiceTableBase; PULONG ServiceCounterTableBase; ULONG NumberOfService; ULONG ParamTableBase; }KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE; typedef struct _KSERVICE_TABLE_DESCRIPTOR { KSYSTEM_SERVICE_TABLE ntoskrnl; KSYSTEM_SERVICE_TABLE win32k; KSYSTEM_SERVICE_TABLE notUsed1; KSYSTEM_SERVICE_TABLE notUsed2; }KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR; typedef NTSTATUS (NTAPI* FuZwOpenProcess) ( _Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PCLIENT_ID ClientId ) ;NTSTATUS NTAPI MyZwOpenProcess ( _Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PCLIENT_ID ClientId ) ;extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;FuZwOpenProcess g_OldZwOpenProcess; KSERVICE_TABLE_DESCRIPTOR* g_pServiceTable = NULL ; ULONG g_Pid = 1624 ; NTSTATUS HookNtOpenProcess () ;NTSTATUS UnHookNtOpenProcess () ;void ShutPageProtect () ;void OpenPageProtect () ;void DriverUnload (DRIVER_OBJECT* obj) ;NTSTATUS DriverEntry (DRIVER_OBJECT* driver, UNICODE_STRING* path) { KdPrint (("驱动启动成功!\n" )); HookNtOpenProcess (); driver->DriverUnload = DriverUnload; return STATUS_SUCCESS; } void DriverUnload (DRIVER_OBJECT* obj) { UnHookNtOpenProcess (); KdPrint (("驱动卸载成功!\n" )); } NTSTATUS HookNtOpenProcess () { NTSTATUS Status; Status = STATUS_SUCCESS; ShutPageProtect (); uOldNtOpenProcess = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a ]; KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a ] =(ULONG)MyZwOpenProcess; OpenPageProtect (); return Status; } NTSTATUS UnHookNtOpenProcess () { NTSTATUS status; status = STATUS_SUCCESS; ShutPageProtect (); KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a ] = uOldNtOpenProcess; OpenPageProtect (); return status; } 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; } } NTSTATUS NTAPI MyZwOpenProcess ( _Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PCLIENT_ID ClientId ) { if (ClientId->UniqueProcess == (HANDLE)g_Pid) { DesiredAccess = 0 ; } return NtOpenProcess (ProcessHandle,DesiredAccess,ObjectAttributes,ClientId); }
实现效果如下
参考 参考自hxd的r0下的进程保护