内容简介:这节课我们来讲SSDT。我估计SSDT这个词对很多底层爱好者都有特殊的含义,绝对不仅仅是“系统服务描述表”这么简单,相信不少人都是从玩SSDT HOOK开始玩WINDOWS内核的,至少我就是如此。好了,扯淡的话就不说了,说多了估计有读者会拿砖头拍我。言归正传,本文只解决两个问题。第一,如何在内核里动态获得SSDT的基址;第二,如何在内核里动态获得SSDT函数的地址。在WIN32下,第一个问题就根本不是问题,因为KeServiceDescriptorTable直接被导出了。但是WIN64下KeService
这节课我们来讲SSDT。我估计SSDT这个词对很多底层爱好者都有特殊的含义,绝对不仅仅是“系统服务描述表”这么简单,相信不少人都是从玩SSDT HOOK开始玩WINDOWS内核的,至少我就是如此。好了,扯淡的话就不说了,说多了估计有读者会拿砖头拍我。言归正传,本文只解决两个问题。第一,如何在内核里动态获得SSDT的基址;第二,如何在内核里动态获得SSDT函数的地址。
在WIN32下,第一个问题就根本不是问题,因为KeServiceDescriptorTable直接被导出了。但是WIN64下KeServiceDescriptorTable没有被导出。所以我们必须搜索得到它的地址。首先反汇编一下KiSystemCall64:
lkd> uf KiSystemCall64 Flow analysis was incomplete, some code may be missing nt!KiSystemCall64: fffff800`03cc7ec0 0f01f8 swapgs fffff800`03cc7ec3 654889242510000000 mov qword ptr gs:[10h],rsp fffff800`03cc7ecc 65488b2425a8010000 mov rsp,qword ptr gs:[1A8h] fffff800`03cc7ed5 6a2b push 2Bh fffff800`03cc7ed7 65ff342510000000 push qword ptr gs:[10h] fffff800`03cc7edf 4153 push r11 fffff800`03cc7ee1 6a33 push 33h fffff800`03cc7ee3 51 push rcx fffff800`03cc7ee4 498bca mov rcx,r10 fffff800`03cc7ee7 4883ec08 sub rsp,8 fffff800`03cc7eeb 55 push rbp fffff800`03cc7eec 4881ec58010000 sub rsp,158h fffff800`03cc7ef3 488dac2480000000 lea rbp,[rsp+80h] fffff800`03cc7efb 48899dc0000000 mov qword ptr [rbp+0C0h],rbx fffff800`03cc7f02 4889bdc8000000 mov qword ptr [rbp+0C8h],rdi fffff800`03cc7f09 4889b5d0000000 mov qword ptr [rbp+0D0h],rsi fffff800`03cc7f10 c645ab02 mov byte ptr [rbp-55h],2 fffff800`03cc7f14 65488b1c2588010000 mov rbx,qword ptr gs:[188h] fffff800`03cc7f1d 0f0d8bd8010000 prefetchw [rbx+1D8h] fffff800`03cc7f24 0fae5dac stmxcsr dword ptr [rbp-54h] fffff800`03cc7f28 650fae142580010000 ldmxcsr dword ptr gs:[180h] fffff800`03cc7f31 807b0300 cmp byte ptr [rbx+3],0 fffff800`03cc7f35 66c785800000000000 mov word ptr [rbp+80h],0 fffff800`03cc7f3e 0f848c000000 je nt!KiSystemCall64+0x110 (fffff800`03cc7fd0) 【省略大量无关代码】 nt!KiSystemCall64+0x110: fffff800`03cc7fd0 fb sti fffff800`03cc7fd1 48898be0010000 mov qword ptr [rbx+1E0h],rcx fffff800`03cc7fd8 8983f8010000 mov dword ptr [rbx+1F8h],eax fffff800`03cc7fde 4889a3d8010000 mov qword ptr [rbx+1D8h],rsp fffff800`03cc7fe5 8bf8 mov edi,eax fffff800`03cc7fe7 c1ef07 shr edi,7 fffff800`03cc7fea 83e720 and edi,20h fffff800`03cc7fed 25ff0f0000 and eax,0FFFh nt!KiSystemServiceRepeat: fffff800`03cc7ff2 4c8d1547782300 lea r10,[nt!KeServiceDescriptorTable (fffff800`03eff840)] fffff800`03cc7ff9 4c8d1d80782300 lea r11,[nt!KeServiceDescriptorTableShadow (fffff800`03eff880)] fffff800`03cc8000 f7830001000080000000 test dword ptr [rbx+100h],80h fffff800`03cc800a 4d0f45d3 cmovne r10,r11 fffff800`03cc800e 423b441710 cmp eax,dword ptr [rdi+r10+10h] fffff800`03cc8013 0f83e9020000 jae nt!KiSystemServiceExit+0x1a7 (fffff800`03cc8302) nt!KiSystemServiceRepeat+0x27: fffff800`03cc8019 4e8b1417 mov r10,qword ptr [rdi+r10] fffff800`03cc801d 4d631c82 movsxd r11,dword ptr [r10+rax*4] fffff800`03cc8021 498bc3 mov rax,r11 fffff800`03cc8024 49c1fb04 sar r11,4 fffff800`03cc8028 4d03d3 add r10,r11 fffff800`03cc802b 83ff20 cmp edi,20h fffff800`03cc802e 7550 jne nt!KiSystemServiceGdiTebAccess+0x49 (fffff800`03cc8080) 【省略大量无关代码】
最终,我们在KiSystemServiceRepeat里找到了KeServiceDescriptorTable的踪影。
可能会有人问,为什么不直接反汇编KiSystemServiceRepeat呢?原因很简单,因为你找
不到KiSystemServiceRepeat的地址。虽然KiSystemCall64和KiSystemServiceRepeat都没有由ntoskrnl.exe导出,但是我们能直接找到KiSystemCall64的地址。怎么找?直接读取指定的msr得出。很多人只听过通用寄存器和调试寄存器,其实还有很多其它的寄存器(你想想再古老的586 CPU的一级缓存都有32KB呢,而现在的AMD64 CPU的每个核心的一级缓存正好有64KB)。Msr的中文全称是就是“特别模块寄存器”(model specific register),它控制CPU的工作环境和标示CPU的工作状态等信息(例如倍频、最大TDP、危险警报温度),它能够读取,也能够写入,但是无论读取还是写入,都只能在Ring 0下进行。我们通过读取C0000082寄存器,能够得到KiSystemCall64的地址,然后从 KiSystemCall64的地址开始,往下搜索0x500字节左右(特征码是4c8d15),就能得到
KeServiceDescriptorTable的地址了。同理,我们换一下特征码(4c8d1d),就能获得 KeServiceDescriptorTableShadow的地址了。
先用WINDBG证明一下(输入rdmsr c0000082):
代码实现如下:
ULONGLONG MyGetKeServiceDescriptorTable64() { PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082); PUCHAR EndSearchAddress = StartSearchAddress + 0x500; PUCHAR i = NULL; UCHAR b1=0,b2=0,b3=0; ULONG templong=0; ULONGLONG addr=0; for(i=StartSearchAddress;i<EndSearchAddress;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 ) //4c8d15 { memcpy(&templong,i+3,4); addr = (ULONGLONG)templong + (ULONGLONG)i + 7; return addr; } } } return 0; }
计算地址的核心代码是4c8d15后面的那4个字节(正好算是一个long)加上当前指令的起始地址再加上7。为什么要加上7呢?因为[lea r10,XXXXXXXX]指令的长度是7个字节。另外我在外国的网站上看到了同样功能的另外一段代码,也贴出来给大家围观下:
ULONGLONG GetKeServiceDescriptorTable64() { char KiSystemServiceStart_pattern[13] = "\x8B\xF8\xC1\xEF\x07\x83\xE7\x20\x25\xFF\x0F\x00\x00"; ULONGLONG CodeScanStart = (ULONGLONG)&_strnicmp; ULONGLONG CodeScanEnd = (ULONGLONG)&KdDebuggerNotPresent; ULONGLONG i, tbl_address, b; for (i = 0; i < CodeScanEnd - CodeScanStart; i++) { if (!memcmp((char*)(ULONGLONG)CodeScanStart +i, (char*)KiSystemServiceStart_pattern,13)) { for (b = 0; b < 50; b++) { tbl_address = ((ULONGLONG)CodeScanStart+i+b); if (*(USHORT*) ((ULONGLONG)tbl_address ) == (USHORT)0x8d4c) return ((LONGLONG)tbl_address +7) + *(LONG*)(tbl_address +3); } } } return 0; }
接下来就是讲述SSDT函数地址了。在获得地址之前,需要知道SSDT函数的INDEX。
获得这个INDEX的方法很简单,直接在RING3读取NTDLL的内容即可。使用WINDBG的方法如下:随便创建一个进程,然后使用WINDBG附加,然后在命令栏里输入:
u ntdll!函数名
比如输入u ntdll!NtOpenProcess,出现以下结果:
0:004> u ntdll!ntopenprocess ntdll!ZwOpenProcess:
00000000`772b0110 4c8bd1 mov r10,rcx
00000000`772b0113 b823000000 mov eax,23h
00000000`772b0118 0f05 syscall
00000000`772b011a c3 ret
再输入u ntdll!NtTerminateProcess,出现以下结果:
0:004> u ntdll!NtTerminateProcess ntdll!ZwTerminateProcess:
00000000`772b0170 4c8bd1 mov r10,rcx
00000000`772b0173 b829000000 mov eax,29h
00000000`772b0178 0f05 syscall
00000000`772b017a c3 ret
可以看到两次反汇编的结果几乎完全相同,唯一不同的地方是第二句。XXh就是此函数的index。知道INDEX之后,就可以计算地址了。首先看看原版的反汇编代码是怎么实
现的(计算方法就藏在KiSystemServiceStart里)。
nt!KiSystemServiceStart:
fffff800`03cc7fde 4889a3d8010000 mov qword ptr [rbx+1D8h],rsp
;Native API Index
fffff800`03cc7fe5 8bf8 mov edi,eax
;操作 1
fffff800`03cc7fe7 c1ef07 shr edi,7
;操作 2
fffff800`03cc7fea 83e720 and edi,20h
;操作 3(和获得地址无关,和对比函数有效性有关)
fffff800`03cc7fed 25ff0f0000 and eax,0FFFh
nt!KiSystemServiceRepeat:
;取得 SSDT 地址
fffff800`03cc3ff2 4c8d1547782300 lea r10,[nt!KeServiceDescriptorTable
(fffff800`03efb840)]
;取得 SSSDT 地址
fffff800`03cc3ff9 4c8d1d80782300 lea r11,[nt!KeServiceDescriptorTableShadow
(fffff800`03efb880)]
;判断调用的是 ssdt 函数还是 sssdt 函数
fffff800`03cc4000 f7830001000080000000 test dword ptr [rbx+100h],80h
;根据上面的判断把 ssdt 或 sssdt 的基址放入 r10
fffff800`03cc400a 4d0f45d3 cmovne r10,r11
;判断函数是否有效
fffff800`03cc400e 423b441710 cmp eax,dword ptr [rdi+r10+10h]
;条件跳转
fffff800`03cc4013 0f83e9020000 jae nt!KiSystemServiceExit+0x1a7
(fffff800`03cc4302)
;计算步骤 1
fffff800`03cc4019 4e8b1417 mov r10,qword ptr [rdi+r10]
;计算步骤 2
fffff800`03cc401d 4d631c82 movsxd r11,dword ptr [r10+rax*4]
;计算步骤 3
fffff800`03cc4021 498bc3 mov rax,r11
;计算步骤 4
fffff800`03cc4024 49c1fb04 sar r11,4
;计算步骤 5
fffff800`03cc4028 4d03d3 add r10,r11
;edi 和 0x20 对比(和计算函数地址无关)
fffff800`03cc402b 83ff20 cmp edi,20h
;条件跳转
fffff800`03cc402e 7550 jne nt!KiSystemServiceGdiTebAccess+0x49
(fffff800`03cc4080)
【省略大量无关代码】
;调用 Native API
fffff800`03cc4150 41ffd2 call r10提取出来的代码就是:
mov rax, rcx ;rcx=Native API 的 index lea r10,[rdx] ;rdx=ssdt 基址 mov edi,eax shr edi,7 and edi,20h mov r10, qword ptr [r10+rdi] movsxd r11,dword ptr [r10+rax] mov rax,r11 sar r11,4 add r10,r11 mov rax,r10 ret
由于微软的 x64 编译器不能内联汇编,所以使用我只能使用 Shellcode 了
typedef UINT64 (__fastcall *SCFN)(UINT64,UINT64); SCFN scfn; VOID Initxxxx() { UCHAR strShellCode[36]="\x48\x8B\xC1\x4C\x8D\x12\x8B\xF8\xC1\xEF\x07\x83\xE7\x20\x4E\x8B\x14\x17\x 4D\x63\x1C\x82\x49\x8B\xC3\x49\xC1\xFB\x04\x4D\x03\xD3\x49\x8B\xC2\xC3"; scfn=ExAllocatePool(NonPagedPool,36); memcpy(scfn,strShellCode,36); } ULONGLONG GetSSDTFunctionAddress64(ULONGLONG NtApiIndex) { ULONGLONG ret=0; ULONGLONG ssdt=GetKeServiceDescriptorTable64(); if(scfn==NULL) Initxxxx(); ret=scfn(NtApiIndex, ssdt); return ret; }
把汇编代码转换为 C 语言的代码如下(注意以下结构体的第三个成员,通过它可以获得 SSDT 函数的数量):
typedef struct _SYSTEM_SERVICE_TABLE{ PVOID ServiceTableBase; PVOID ServiceCounterTableBase; ULONGLONG NumberOfServices; PVOID ParamTableBase; } SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE; ULONGLONG GetSSDTFunctionAddress64_2(ULONGLONG Index) { LONG dwTemp=0; ULONGLONG qwTemp=0,stb=0,ret=0; PSYSTEM_SERVICE_TABLE ssdt=(PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTable64(); stb=(ULONGLONG)(ssdt->ServiceTableBase); qwTemp = stb + 4 * Index; dwTemp = *(PLONG)qwTemp; dwTemp = dwTemp >> 4; ret = stb + (LONG64)dwTemp; return ret; }
测试代码和运行结果:
DbgPrint(“[method 1]SSDT: %llx”,MyGetKeServiceDescriptorTable64());
DbgPrint(“[method 2]SSDT: %llx”,GetKeServiceDescriptorTable64());
DbgPrint(“[method 1]NtOpenProcess: %llx”,GetSSDTFunctionAddress64(0x23));
DbgPrint(“[method 1]NtTerminateProcess: %llx”,GetSSDTFunctionAddress64(0x29));
DbgPrint(“[method 2]NtOpenProcess: %llx”,GetSSDTFunctionAddress64_2(0x23));
DbgPrint(“[method 2]NtTerminateProcess: %llx”,GetSSDTFunctionAddress64_2(0x29));
最后,总结一下 WIN32 和 WIN64 在 SSDT 方面的不同。大家可以把 SSDT (其实 SHADOW SSDT 同理)想像成一排保险柜,每个柜子都有编号(从 0 开始),柜子的长度为四字节,每个柜子里都放了一个 LONG 数据。但不同的是, WIN32 的 “ 柜子 ” 里放的数据是某个函数的绝对地址,而 WIN64 的 “ 柜子 ” 里放的数据是某个函数的偏移地址。这个偏移地址要经过一定的计算才能变成绝对地址。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTML5移动应用开发入门经典
凯瑞恩 / 林星 / 人民邮电出版社 / 2013-3 / 55.00元
《HTML5移动应用开发入门经典》总共分为24章,以示例的方式对如何使用HTML5及相关技术进行移动应用开发做了全面而细致的介绍。《HTML5移动应用开发入门经典》首先讲解了HTML5的起源以及它为什么适用于移动设备,然后讲解了HTML5的基本元素以及所做的改进、canvas(画布)、视音频、微格式、微数据、拖曳等新增特性,还讲解了WebSocket、WebWorkers、Web存储、离线Web应......一起来看看 《HTML5移动应用开发入门经典》 这本书的介绍吧!