在前面的章节《X86驱动:挂接SSDT内核钩子》
我们通过代码的方式直接读取 KeServiceDescriptorTable
这个被导出的表结构从而可以直接读取到SSDT表的基址,而在Win64系统中 KeServiceDescriptorTable
这个表并没有被导出,所以我们必须手动搜索到它的地址。
为了确保系统的安全性与稳定性,微软从 Windows Vista X64 开始对系统内核增加了一定的限制,其主要增加了两种保护措施,一是KPP (内核补丁保护),KPP是机制其利用了PG(PatchGuard)技术,PG技术在x64系统下加入了内核哨兵,用于检测系统内核是否被恶意篡改(打补丁),如果发现被打了补丁,则会导致关键结构损毁直接蓝屏,二是DSE (驱动强制签名),DSE技术则是拒绝加载不包含正确签名的驱动。
1.这里我们可以通过MSR(特别模块寄存器),读取C0000082寄存器,从而得到KiSystemCall64的地址,在内核调试模式下直接输入 rdmsr c0000082
即可读取到该地址,反汇编可看到 nt!KiSystemCall64
函数。
kd> rdmsr c0000082 msr[c0000082] = fffff800`03 c72ec0 kd> u fffff800`03 c72ec0 nt!KiSystemCall64: fffff800`03 c72ec0 0f 01f8 swapgs fffff800`03 c72ec3 654889242510000000 mov qword ptr gs:[10 h],rsp fffff800`03 c72ecc 65488b 2425a8010000 mov rsp,qword ptr gs:[1 A8h] fffff800`03 c72ed5 6 a2b push 2B h fffff800`03 c72ed7 65f f342510000000 push qword ptr gs:[10 h] fffff800`03 c72edf 4153 push r11 fffff800`03 c72ee1 6 a33 push 33 h fffff800`03 c72ee3 51 push rcx
2.接着我们从 KiSystemCall64
函数地址开始向下反汇编,可以看到最后 nt!KiSystemServiceRepeat
这个函数里面包含了 nt!KeServiceDescriptorTable (fffff80003eaa840)
,通过 03c72ff2 减去03c72ec0 即可得到SDT表结构与KiSystemCall64函数之间的偏移值 132 (306)
kd> uf KiSystemCall64 Flow analysis was incomplete, some code may be missing nt!KiSystemCall64: fffff800`03 c72ec0 0f 01f8 swapgs fffff800`03 c72ec3 654889242510000000 mov qword ptr gs:[10 h],rsp fffff800`03 c72ecc 65488b 2425a8010000 mov rsp,qword ptr gs:[1 A8h] fffff800`03 c72ed5 6 a2b push 2B h fffff800`03 c72ed7 65f f342510000000 push qword ptr gs:[10 h] fffff800`03 c72edf 4153 push r11 fffff800`03 c72ee1 6 a33 push 33 h fffff800`03 c72ee3 51 push rcx fffff800`03 c72ee4 498b ca mov rcx,r10 nt!KiSystemServiceRepeat: fffff800`03 c72ff2 4 c8d1547782300 lea r10,[nt!KeServiceDescriptorTable (fffff800`03 eaa840)] fffff800`03 c72ff9 4 c8d1d80782300 lea r11,[nt!KeServiceDescriptorTableShadow (fffff800`03 eaa880)] fffff800`03 c73000 f7830001000080000000 test dword ptr [rbx+100 h],80 h fffff800`03 c7300a 4 d0f45d3 cmovne r10,r11 fffff800`03 c7300e 423b 441710 cmp eax,dword ptr [rdi+r10+10 h] fffff800`03 c73013 0f 83e9020000 jae nt!KiSystemServiceExit+0x1a7 (fffff800`03 c73302) Branch
总结一下:我们通过读取C0000082寄存器,能够得到KiSystemCall64的地址,然后从KiSystemCall64的地址开始,往下搜索0x150字节左右(特征码4c8d15),就能得到KeServiceDescriptorTable的地址。
#include <ntddk.h> #include <windef.h> #include <intrin.h> #pragma intrinsic(__readmsr) VOID UnDriver (PDRIVER_OBJECT driver) { DbgPrint(("驱动程序卸载成功! \n" )); } ULONGLONG Get_SSTD_Base () { PUCHAR Base = (PUCHAR)__readmsr(0xC0000082 ); PUCHAR Address = Base + 0x150 ; PUCHAR i = NULL ; UCHAR b1 = 0 , b2 = 0 , b3 = 0 ; ULONG templong = 0 ; ULONGLONG addr = 0 ; for (i = Base; i<Address; i++) { if (MmIsAddressValid(i) && MmIsAddressValid(i + 1 ) && MmIsAddressValid(i + 2 )) { b1 = *i; b2 = *(i + 1 ); b3 = *(i + 2 ); if (b1 == 0x4c && b2 == 0x8d && b3 == 0x15 ) { memcpy (&templong, i + 3 , 4 ); addr = (ULONGLONG)templong + (ULONGLONG)i + 7 ; return addr; } } } return 0 ; } NTSTATUS DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { DbgPrint("SSTD Base= %11x" , Get_SSTD_Base()); DriverObject->DriverUnload = UnDriver; return STATUS_SUCCESS; }
3.接着我们则需要获取到SSDT中某个函数的序号,这里以OpenProcess
为例:
0 :000 > u ntdll!NtOpenProcessntdll!NtOpenProcess: 77820700 b826000000 mov eax,23 h77820705 bac04f8377 mov edx,offset ntdll!Wow64SystemServiceCall (77834f c0)7782070 a ffd2 call edx7782070 c c21000 ret 10 h7782070f 90 nop
4.读取代码如下.
#include <ntddk.h> #include <windef.h> #include <intrin.h> #pragma intrinsic(__readmsr) VOID UnDriver (PDRIVER_OBJECT driver) { DbgPrint(("驱动程序卸载成功! \n" )); } ULONGLONG Get_SSDT_Base () { PUCHAR Base = (PUCHAR)__readmsr(0xC0000082 ); PUCHAR Address = Base + 0x150 ; PUCHAR i = NULL ; UCHAR b1 = 0 , b2 = 0 , b3 = 0 ; ULONG templong = 0 ; ULONGLONG addr = 0 ; for (i = Base; i<Address; i++) { if (MmIsAddressValid(i) && MmIsAddressValid(i + 1 ) && MmIsAddressValid(i + 2 )) { b1 = *i; b2 = *(i + 1 ); b3 = *(i + 2 ); if (b1 == 0x4c && b2 == 0x8d && b3 == 0x15 ) { memcpy (&templong, i + 3 , 4 ); addr = (ULONGLONG)templong + (ULONGLONG)i + 7 ; return addr; } } } return 0 ; } typedef struct _SYSTEM_SERVICE_TABLE { PVOID ServiceTableBase; PVOID ServiceCounterTableBase; ULONGLONG NumberOfServices; PVOID ParamTableBase; } SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE; ULONGLONG GetSSDTFunction (ULONGLONG Index) { LONG dwTemp = 0 ; ULONGLONG qwTemp = 0 , stb = 0 , ret = 0 ; PSYSTEM_SERVICE_TABLE ssdt = (PSYSTEM_SERVICE_TABLE)Get_SSDT_Base(); stb = (ULONGLONG)(ssdt->ServiceTableBase); qwTemp = stb + 4 * Index; dwTemp = *(PLONG)qwTemp; dwTemp = dwTemp >> 4 ; ret = stb + (LONG64)dwTemp; return ret; } NTSTATUS DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { DbgPrint("OpenProcess=%llx" , GetSSDTFunction(0x23 )); DriverObject->DriverUnload = UnDriver; return STATUS_SUCCESS; }
在64位环境下想要任意Hook系统函数是不可能的,因为64位中每个驱动程序都不在同一个4GB空间里,而4字节的整数只能表示4GB的范围,所以无论你怎么改,都不可能跨越这个内存空间,而微软也不希望你挂钩内核的一些函数,如果非要使用的话,微软提供了一些回调函数可以实现相应的挂钩效果。