SSDT—Hook和MDL
SSDT HOOK
首先要明白SSDTHOOK就是自己再写一份函数,替换了SSDT表中的地址替换掉
如何访问系统服务表呢?
SSDT 的全称是 System Services Descriptor Table,系统服务描述符表
下面这个是个全局变量
kd> dd KeServiceDescriptorTable(SSDT)
这个导出的 声明一下就可以使用了
kd> dd KeServiceDescriptorTableShadow(SSDT Shadow)
这个未导出 需要用其他的方式来查找
1 2 3 4 5 6 7 8 9 |
kd> dd KeServiceDescriptorTable 83fac9c0 83ec0d9c 00000000 00000191 83ec13e4 83fac9d0 00000000 00000000 00000000 00000000 83fac9e0 83f1f6af 00000000 025355a9 000000bb 83fac9f0 00000011 00000100 5385d2ba d717548f 83faca00 83ec0d9c 00000000 00000191 83ec13e4 83faca10 956d6000 00000000 00000339 956d702c 83faca20 00000000 00000000 83faca24 00000340 83faca30 00000340 865fab00 00000007 00000000 |
其中83ec0d9c是函数地址,00000191是函数个数,83ec13e4是函数参数个数,单位是一个字节,除以4就是函数地址表上对应函数的个数,第一个函数对第一个个数。
下面就是函数地址表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
kd> dd 83ec0d9c 83ec0d9c 840bcc28 83f0340d 8404cb68 83e6788a 83ec0dac 840be4ff 83f403fa 8412eb05 8412eb4e 83ec0dbc 840413bd 84148368 841495c1 84037b95 83ec0dcc 840c8b35 84121963 84074a56 840446cc 83ec0ddc 83fda928 84113898 8402b14e 8406da62 83ec0dec 840b9df1 8401b238 840b91fe 84038c0c 83ec0dfc 840ca5bc 8403b28f 840ca39c 840c2afc 83ec0e0c 8404d0f0 8410e657 840bfec9 840ca7ee 根据这个定义一个结构体 //下面这个是系统服务表的结构体 typedef struct _KSYSTEM_SERVICE_TABLE { PULONG ServiceTableBase; // 服务函数地址表基址 PULONG ServiceCounterTableBase; ULONG NumberOfService; // 服务函数的个数 PULONG ParamTableBase; // 服务函数参数表基址 } KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE; |
下面就是函数参数个数表除四就是真正个数
1 2 3 4 5 6 7 8 9 10 |
kd> db 83ec13e4 83ec13e4 18 20 2c 2c 40 2c 40 44-0c 08 08 18 18 08 04 04 . ,,@,@D........ 83ec13f4 0c 0c 10 18 24 0c 2c 0c-18 10 0c 0c 0c 0c 0c 0c ....$.,......... 83ec1404 08 0c 18 18 14 18 0c 20-10 08 08 08 0c 08 0c 0c ....... ........ 83ec1414 08 04 04 0c 08 08 08 08-0c 04 04 20 08 10 0c 20 ........... ... 83ec1424 14 0c 2c 10 0c 0c 1c 10-20 20 10 38 10 14 10 20 ..,..... .8... 83ec1434 24 24 28 1c 1c 14 10 20-2c 10 34 28 18 2c 14 28 $$(.... ,.4(.,.( 83ec1444 08 0c 08 04 04 04 04 04-0c 04 08 28 00 04 04 1c ...........(.... 83ec1454 18 00 08 08 18 0c 14 18-08 18 0c 08 08 0c 04 00 ................ |
如何得到函数表地址
1 2 3 4 5 6 7 8 9 10 11 |
//这个就是SSDT的结构体,即系统服务描述表,里面就是多个系统服务表 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; |
//导出由 ntoskrnl(10-10-12)所导出的 SSDT
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
//这个是导出的,要到内核文件找,所以名字不能瞎起
下面的代码是查看到SSDT表地址的代码,可以打印显示
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 |
#include <ntddk.h> #include<ntstatus.h> typedef struct _KSYSTEM_SERVICE_TABLE { PULONG ServiceTableBase; // 服务函数地址表基址 PULONG ServiceCounterTableBase; ULONG NumberOfService; // 服务函数的个数 PULONG ParamTableBase; // 服务函数参数表基址 } 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; //导出由 ntoskrnl所导出的 SSDT extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;//这个是导出的,要到内核文件找,所以名字不能瞎起 VOID DriverUnload(PDRIVER_OBJECT pDriver) { UNREFERENCED_PARAMETER(pDriver); KdPrint(("My Dirver is unloading...")); } NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath) { UNREFERENCED_PARAMETER(pPath); KdPrint(("->%x \n", KeServiceDescriptorTable)); pDriver->DriverUnload = DriverUnload; return STATUS_SUCCESS; } |
通过页表基址修改页属性
SSDT所在的物理页是只读的,如果要修改,先要修改页属性为可写:
1、第一种办法,用我们学过的知识,通过页表基址直接修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
if(RCR4 & 0x00000020) {//说明是2-9-9-12分页 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 {//说明是10-10-12分页 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寄存器(WP位置改成0)
比如我们要SSDTHOOKNtOpenProcess,就要先通过IDA找到kernelBase的OpenProcess,在经过一系列查看最后在ntdll里找到ZWOpenProcess,所在位置看到了NtOpenProcess
1 2 3 4 |
.text:77F05D88 mov eax, 0BEh ; NtOpenProcess .text:77F05D8D mov edx, 7FFE0300h .text:77F05D92 call dword ptr [edx] .text:77F05D94 retn 10h |
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 |
kd> dd KeServiceDescriptorTable 83f789c0 83e8cd9c 00000000 00000191 83e8d3e4 83f789d0 00000000 00000000 00000000 00000000 83f789e0 83eeb6af 00000000 025355a9 000000bb 83f789f0 00000011 00000100 5385d2ba d717548f 83f78a00 83e8cd9c 00000000 00000191 83e8d3e4 83f78a10 95d46000 00000000 00000339 95d4702c 83f78a20 00000000 00000000 83f78a24 00000340 83f78a30 00000340 865fab00 00000007 00000000 kd> dd 83e8cd9c+4*BE 83e8d094 840219dc 84073fff 84061b37 83f8d0c7 83e8d0a4 84079674 83ff50c6 84096977 8405db6f 83e8d0b4 8406dd87 840882e4 84061c4e 84119e0f 83e8d0c4 841026f1 84103989 83ff3506 84050970 83e8d0d4 841022a2 84101fc2 8410235a 8410207a 83e8d0e4 8400693f 83fd5f60 83ff0a51 841040e4 83e8d0f4 841041aa 84052403 840a35a7 840679a1 83e8d104 84114a3e 84114e83 83ed2d34 84086b8c kd> u 840219dc nt!NtOpenProcess: 840219dc 8bff mov edi,edi 840219de 55 push ebp 840219df 8bec mov ebp,esp 840219e1 51 push ecx 840219e2 51 push ecx 840219e3 64a124010000 mov eax,dword ptr fs:[00000124h] 840219e9 8a803a010000 mov al,byte ptr [eax+13Ah] 840219ef 8b4d14 mov ecx,dword ptr [ebp+14h] |
下面说一下通过MDL修改页属性。
就是 Memory Descriptor List,简称 MDL。有的同学可能会问了,MDL究竟是个什么东西呢?从字面意思看,不难理解,内存描述符列表。MDL包含了内存区域的起始、拥有者proc、字节数、标记等。OK,我们需要先定义一个MDL的指针。
1 2 |
PMDL MDLSystemCall; |
定义了MDL的指针以后,我们要通过MAPPED系列的参数来使内存拥有可写性,然后锁定内存中的MDL,那么我们就要定义一个PVOID的指针,来供MmMap操作。
1 |
PVOID *MappedSCT; |
1 2 3 4 5 6 7 |
MDLSystemCall = MmCreateMdl( NULL, //内存描述符列表 KeServiceDescriptorTable.ServiceTableBase,// 内存地址 KeServiceDescriptorTable.NumberOfServices*4);//内存长度 if(!MDLSystemCall) return STATUS_UNSUCCESSFUL; |
建立了MDL,填充一下页数组
1 2 3 |
MmBuildMdlForNonPagedPool(MDLSystemCall);//建立内存页的MDL描述 MDLSystemCall->MdlFlags = MDLSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA; //设置MDL标记为可写 MappedSCT = MmMapLockedPages(MDLSystemCall, KernelMode); |
然后做自己的操作,比如这里的Hook
然后另外还有释放MDL的代码
1 2 3 4 5 |
if(MDSystemCall) { MmUnmapLockedPages(MappedSCT, MDSystemCall); IoFreeMdl(MDSystemCall); } |
下面是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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
#include <ntddk.h> #include<ntstatus.h> //1.找到系统服务表的函数地址表 //定义一个全局变量用来存放之前的NtOpenProcess地址 ULONG uOldNtOpenProcess; //有了地址还需要一个函数NtOpenProcess指针,用于调用原来的NtOpenProcess //定义修复和恢复页属性的函数 PMDL MDSystemCall; PVOID *MappedSCT; typedef NTSTATUS(*NTOPENPROCESS)( __out PHANDLE ProcessHandle, __in ACCESS_MASK DesiredAccess, __in POBJECT_ATTRIBUTES ObjectAttributes, __in_opt PCLIENT_ID ClientId ); typedef struct _KSYSTEM_SERVICE_TABLE { PULONG ServiceTableBase; // 服务函数地址表基址 PULONG ServiceCounterTableBase; ULONG NumberOfService; // 服务函数的个数 PULONG ParamTableBase; // 服务函数参数表基址 } 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; //导出由 ntoskrnl所导出的 SSDT extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;//这个是导出的,要到内核文件找,所以名字不能瞎起 //准备用于替换的函数 NTSTATUS NTAPI MyNtOpenProcess(__out PHANDLE ProcessHandle, __in ACCESS_MASK DesiredAccess, __in POBJECT_ATTRIBUTES ObjectAttributes, __in_opt PCLIENT_ID ClientId ) { NTSTATUS Status; Status = STATUS_SUCCESS; //这里填自己的业务。。。各种过滤,修改返回结构等 KdPrint(("MyNtOpenProcess %x %x %x %x \n", ProcessHandle, DesiredAccess, ObjectAttributes, ClientId)); //后面这里填的是打开原来的函数,因为这个函数也要实现原来的功能,不然就乱套了,除非你自己在自己业务里实现了 return ((NTOPENPROCESS)uOldNtOpenProcess)(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId); } void PageProtectOff() { //MDSystemCall = MmCreateMdl(NULL, KeServiceDescriptorTable->ntoskrnl.ServiceTableBase, KeServiceDescriptorTable->ntoskrnl.NumberOfService * 4); //if (!MDSystemCall) // //return STATUS_UNSUCCESSFUL; // return; //MmBuildMdlForNonPagedPool(MDSystemCall); //MDSystemCall->MdlFlags = MDSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA; //MappedSCT = MmMapLockedPages(MDSystemCall, KernelMode); __asm { //关闭内存保护 push eax; mov eax, cr0; and eax, ~0x10000; mov cr0, eax; pop eax; } } void PageProtectOn() { ////解锁、释放MDL //if (MDSystemCall) //{ // MmUnmapLockedPages(MappedSCT, MDSystemCall); // IoFreeMdl(MDSystemCall); //} __asm { //恢复内存保护 push eax; mov eax, cr0; or eax, 0x10000; mov cr0, eax; pop eax; } } //3.修改函数地址,准备个函数用来修改函数地址 void HookNtOpenProcess() { NTSTATUS Status; Status = STATUS_SUCCESS; PageProtectOff(); uOldNtOpenProcess = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0xBE]; KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0xBE] = (ULONG)MyNtOpenProcess; PageProtectOn(); } //4.恢复 void UnHookNtOpenProcess() { PageProtectOff(); KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0xBE] = (ULONG)uOldNtOpenProcess; PageProtectOn(); } VOID DriverUnload(PDRIVER_OBJECT pDriver) { UNREFERENCED_PARAMETER(pDriver); UnHookNtOpenProcess(); KdPrint(("My Dirver is unloading...")); } NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath) { UNREFERENCED_PARAMETER(pPath); KdPrint(("->%x \n", KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0xBE]));//得到函数地址表 HookNtOpenProcess(); pDriver->DriverUnload = DriverUnload; return STATUS_SUCCESS; } |
然后操作时可以用PChunt观察HOOk前后这个函数地址变化