初探win10 x64 SSDT(驱动学习笔记五)

初探win10 x64 SSDT(驱动学习笔记五)

初探win10 x64 SSDT

0x0 windbg中查看SSDT背景介绍查看SSDT

0x1 代码获取SSDT表中的函数获取SSDT的地址获取SSDT中函数的地址

0x0 windbg中查看SSDT

背景介绍

学习驱动的过程中,由于涉及到SSDT HOOK相关的知识点,开始学习SSDT,关于SSDT的基本概念,这里省去,中文名是系统服务描述表,具体的理解可自行百度,由于是初步探讨,本篇只介绍方法。

查看SSDT

x64系统下,在windbg中,我们想要找到SSDT还是比较简单(x86更简单)的,如下:

首先,我们直接可以直接查看到KeServiceDescriptorTable的值是KiServiceTable的指针

0: kd> dqs nt!KeServiceDescriptorTable

fffff804`3fd72880 fffff804`3fc150e0 nt!KiServiceTable

fffff804`3fd72888 00000000`00000000

...

通过KiServiceTable我们可以看到

0: kd> dds KiServiceTable

fffff804`3fc150e0 fc292704

fffff804`3fc150e4 fc336600

fffff804`3fc150e8 01c80302

fffff804`3fc150ec 0454cc00

fffff804`3fc150f0 02245d00

fffff804`3fc150f4 fdb97a00

fffff804`3fc150f8 01f70f05

fffff804`3fc150fc 01992206

....

3.这里,借用看雪上的前辈的脚本,我们可以看到SSDT中的函数的具体情况,具体脚本请进原帖查看 Win10 X64下SSDT表中的函数地址计算公式

KeServiceDescriptorTable->KiServiceTable: fffff8043fc150e0

KeServiceDescriptorTable->Count: 463

Index Address Func

--------------------------------

[ 0] fffff8043f83e350 (nt!NtAccessCheck (fffff804`3f83e350))

[ 1] fffff8043f848740 (nt!NtWorkerFactoryWorkerReady (fffff804`3f848740))

[ 2] fffff8043fddd110 (nt!NtAcceptConnectPort (fffff804`3fddd110))

[ 3] fffff80440069da0 (nt!NtMapUserPhysicalPagesScatter (fffff804`40069da0))

[ 4] fffff8043fe396b0 (nt!NtWaitForSingleObject (fffff804`3fe396b0))

[ 5] fffff8043f9ce880 (nt!NtCallbackReturn (fffff804`3f9ce880))

[ 6] fffff8043fe0c1d0 (nt!NtReadFile (fffff804`3fe0c1d0))

[ 7] fffff8043fdae300 (nt!NtDeviceIoControlFile (fffff804`3fdae300))

[ 8] fffff8043fe0b440 (nt!NtWriteFile (fffff804`3fe0b440))

[ 9] fffff8043fdadda0 (nt!NtRemoveIoCompletion (fffff804`3fdadda0))

[ 10] fffff8043fe17410 (nt!NtReleaseSemaphore (fffff804`3fe17410))

[ 11] fffff8043fe5aaf0 (nt!NtReplyWaitReceivePort (fffff804`3fe5aaf0))

...

用工具查看下结果是一致的 至此,我们顺利得到了SSDT表中函数的具体情况。

0x1 代码获取SSDT表中的函数

获取SSDT的地址

我们使用代码获取SSDT中函数地址的思路是: SYSCALL->SSDT->SSDTFunc 为此在获取SSDT的地址前,我们需要做一点知识铺垫。 在知乎的一篇名为深入分析微软新引入的内核虚拟地址影子(KVAS)特性的文章里我们看到下面的内容:

IA32_STAR(0xC0000081):Ring 0和Ring 3段基址,以及SYSCALL的EIP。在较低的32位中存储的是SYSCALL的EIP,在第32-47位存储内核段基址,在第48-63为存储用户段基址。 IA32_CSTAR(0xC0000083):兼容模式下,SYSCALL的内核RIP相对寻址。 IA32_LSTAR(0xC0000082):长模式(Long Mode,即64位)下,SYSCALL的内核RIP相对寻址。

其中,我们看到了放在MSR寄存器上的64系统的SYSCALL,通过该寄存器,我们可以定位SYSCALL的地址,通过该地址,我们可以缩小我们需要搜索的内存范围,然后,我们在内存中查找到SSDT的特征码,只要找到特征码,我们就可以计算出SSDT的地址了。 然而,与网上其他相关文章不同的是,通过C0000082 MSR寄存器,我所得到的地址并非是nt!KiSystemCall64,而是nt!KiSystemCall64Shadow。 因此,我的权宜之计是读取地址后取一个偏移量将开始进行特征码搜索的位置重新定位到合理的位置,再在其中进行搜索,当然,这显然不是一个有效的方式,待后续解决。 获取SSDT地址的代码如下:

ULONGLONG GetKeServiceDescriptorTable()

{

PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082)- 0x16A000;//0x16A000是取的一个偏移量

PUCHAR EndSearchAddress = StartSearchAddress + 0x500;//通常搜索范围在500字节就够了

PUCHAR i = NULL;

UCHAR a = 0, b = 0, c = 0;//a,b,c用来存储特征字节

ULONG Temp = 0;

ULONGLONG addr = 0;

for (i = StartSearchAddress; i < EndSearchAddress; i++)

{

//使用MmIsAddressValid()函数检查地址是否有页面错误,但是微软并不建议使用此函数

if(MmIsAddressValid(i) && MmIsAddressValid(i+1) && MmIsAddressValid(i+2))

{

a = *i;

b = *(i + 1);

c = *(i + 2);

//对比特征值

//fffff804`2f678184 4c8d15f5663900 lea r10,[nt!KeServiceDescriptorTable (fffff804`2fa0e880)]

//fffff804`2f67818b 4c8d1deee73700 lea r11, [nt!KeServiceDescriptorTableShadow(fffff804`2f9f6980)]

if (a == 0x4c && b == 0x8d && c == 0x15)

{

memcpy(&Temp, i + 3, 4);

addr = (ULONGLONG)Temp + (ULONGLONG)i + 7;

KdPrint(("addr为:%p", addr));

return addr;

}

}

}

return 0;

}

获取SSDT中函数的地址

我们找到由SSDT获取表中相关函数的关键汇编代码如下:

fffff802`627e0139 4d8b143a mov r10, qword ptr[r10 + rdi]

fffff802`627e013d 4d631c82 movsxd r11, dword ptr[r10 + rax * 4]

fffff802`627e0141 498bc3 mov rax, r11

fffff802`627e0144 49c1fb04 sar r11, 4

fffff802`627e0148 4d03d3 add r10, r11

fffff802`627e014b 83ff20 cmp edi, 20h

fffff802`627e014e 7550 jne nt!KiSystemServiceGdiTebAccess + 0x49 (fffff802`627e01a0)

fffff802`627e0150 4c8b9bf0000000 mov r11, qword ptr[rbx + 0F0h]

其中rax*4中的rax是索引号,将上述汇编代码翻译成C语言代码大致如下:

ServiceTableBase = KeServiceDescriptorTable->ServiceTableBase

dwoffset = ServiceTableBase[index]

if (dwoffset & 0x80000000) //判断最高位是否为1

{

//如果最高位为1,则右移后最高位需要补1,补1后最高位再次是1,再次右移后最高位依然需要补1。

//由于是右移四位,故需要补四次二进制的1,0y1111即为0XF,故最高位补F

dwoffset = (dwoffset >> 4) | 0xF0000000;

}

else

{

dwoffset = dwoffset >> 4;

}

FuncCurAddr = dwoffset + ServiceTableBase;

这里需要注意的,sar是算数右移,如果操作数最高位为1,右移后最高位也是需要补1的,最开始误把sar当成逻辑右移,从而导致计算的结果总比使用工具得到的结果多0x10000000,当时百思不得其解,后来看到sar后终于豁然开朗。 至此,我们用代码亦可以实现获取SSDT表中函数的地址,效果图如下: 由于学习SSDT相关知识才一两天的时间,故许多认识会多有不足之处,欢迎大家谈论指正,相信随着学习的深入,肯定会有更深的理解。 To be continue…

相关推荐

王者荣耀达摩梦境修炼有什么皮肤
365bet提现多久到账

王者荣耀达摩梦境修炼有什么皮肤

📅 08-25 👁️ 1370
蒙古族十大著名歌手榜中榜
365bet提现多久到账

蒙古族十大著名歌手榜中榜

📅 08-10 👁️ 3701
热血传奇云游商人刷新时间是什么 云游商人寻找攻略
365体育投注怎么玩

热血传奇云游商人刷新时间是什么 云游商人寻找攻略

📅 08-19 👁️ 939