宇宙无限 发表于 2024-2-29 12:59:14

WINDOWS 64位SSDT定位思路

在32位Windows中我们有很多定位SSDT的方法,最直接的就是利用导出符号来找到SSDT。再有就是通过在nt!KeAddSystemServiceTable函数中进行反汇编搜索。可是在64位WINDOWS中这两种方法都行不通。在64位Windows中不在导出SSDT了,同时nt!KeAddSystemServiceTable中也不再出SSDT了(此处说明有误,应该为不出现直接的SSDT地址了,个人水平有限感觉计算太麻烦。)。
这样要HOOK SSDT表就出现了第一个问题如何找到它?我想了三种思路。

思路1:
对每种版本的系统确定一个ntoskrnl到SSDT的硬编码偏移。呵呵,这种方法可能比较傻但最简单直接有效。就是维护起来比较非常麻烦,要对每一个版本进单独处理,如果不同补丁版本有变化也要处理。

思路2:
64位WINDOWS中存放着每个服务例程的入口偏移(这个偏移是相对于SSDT的开始地址的)。我取一个一般不被HOOK的服务例程入口偏移,然后从ntoskrnl的开始地址一直搜索到结束来查找这四个字节,然后根据服务例程的索引定位到SSDT的开始位置。可能对不同版本的操作系统其偏移和索引是不同的需要分别进行处理,但是相比第一种思路要通用一些。

思路3:
还是利用反汇编的方法,但是nt!KeAddSystemServiceTable函数中已经找不到直接的SSDT地址了。后来我想能不能映像搜索到SSDT的地址。
kd> dq nt!KeServiceDescriptorTable
fffff800`03eab840fffff800`03c75b00 00000000`00000000
fffff800`03eab85000000000`00000191 fffff800`03c7678c
fffff800`03eab86000000000`00000000 00000000`00000000
fffff800`03eab87000000000`00000000 00000000`00000000
fffff800`03eab880fffff800`03c75b00 00000000`00000000
fffff800`03eab89000000000`00000191 fffff800`03c7678c
fffff800`03eab8a0fffff960`00111c00 00000000`00000000
fffff800`03eab8b000000000`0000033b fffff960`0011391c
kd> lm m nt
start             end               module name
fffff800`03c03000 fffff800`041e0000   nt         (pdb symbols)
kd> s -q fffff800`03c03000 l600000 fffff800`03eab840

很不幸我没有找到任何相关的信息。呵呵,但是我贼心不是死,相信肯定在某处会有引用的。于是我搜索了一下所有名称中含有Service单词的符号。

kd> x nt!Ki*Service*
fffff800`03c73e40 nt!KiServiceInternal = <no type information>
fffff800`03c7415b nt!KiSystemServiceExit = <no type information>
fffff800`03c73fde nt!KiSystemServiceStart = <no type information>
fffff800`03c76788 nt!KiServiceLimit = <no type information>
fffff800`03c73b00 nt!KiDebugServiceTrap = <no type information>
fffff800`03c74140 nt!KiSystemServiceCopyEnd = <no type information>
fffff800`03c75b00 nt!KiServiceTable = <no type information>
fffff800`03c74037 nt!KiSystemServiceGdiTebAccess = <no type information>
fffff800`03c73ff2 nt!KiSystemServiceRepeat = <no type information>
fffff800`03c740d0 nt!KiSystemServiceCopyStart = <no type information>
fffff800`03c706f0 nt!KiServiceLinkage = <no type information>
fffff800`03c73d40 nt!KiSystemServiceHandler = <no type information>

终于在nt!KiSystemServiceRepeat 中找到SSDT的信息。

nt!KiSystemServiceRepeat:
fffff800`03c73ff2 4c8d1547782300lea   r10,
fffff800`03c73ff9 4c8d1d80782300lea   r11,
fffff800`03c74000 f7830001000080000000 test dword ptr ,80h
fffff800`03c7400a 4d0f45d3      cmovner10,r11
fffff800`03c7400e 423b441710      cmp   eax,dword ptr
fffff800`03c74013 0f83e9020000    jae   nt!KiSystemServiceExit+0x1a7 (fffff800`03c74302)
fffff800`03c74019 4e8b1417      mov   r10,qword ptr
fffff800`03c7401d 4d631c82      movsxdr11,dword ptr

但是一个新的问题也同时产生了,nt!KiSystemServiceRepeat并不是系统的一个导出函数。那我怎么找到它呢?后来一想虽然它不导出但应该至少存在一条从外部到它的一个调用路径吧!那我就通过这条路径找到它不就行了。想到此就对它下了个断点看看都是谁会调用它。结果和我想的一样,不过发现我真是一个菜鸟,找到SSDT值太高兴竟没有仔细看它的汇编代码。nt!KiSystemServiceRepeat就是完成根据调用号从SSDT中获得服务例程入口并调用的。那就说每一个ZwXXX类的函数都会调用到它,而Zw类的函数是系统导出的这样我们可以通过任何一个Zw类的函数来找到nt!KiSystemServiceRepeat从而定位到SSDT。于是我手工尝试了一下。下面是尝试的过程。

kd> u nt!ZwClose l20
nt!ZwClose:
fffff800`03c6d640 488bc4          mov   rax,rsp
fffff800`03c6d643 fa            cli
fffff800`03c6d644 4883ec10      sub   rsp,10h
fffff800`03c6d648 50            push    rax
fffff800`03c6d649 9c            pushfq
fffff800`03c6d64a 6a10            push    10h
fffff800`03c6d64c 488d059d300000lea   rax,
fffff800`03c6d653 50            push    rax
fffff800`03c6d654 b80c000000      mov   eax,0Ch
fffff800`03c6d659 e9e2670000      jmp   nt!KiServiceInternal (fffff800`03c73e40)
fffff800`03c6d65e 6690            xchg    ax,ax

在ZwClose中(其它类似)跳转到了nt!KiServiceInternal 我们再跟踪nt!KiServiceInternal

nt!KiServiceInternal:
fffff800`03c73e40 4883ec08      sub   rsp,8
fffff800`03c73e44 55            push    rbp
fffff800`03c73e45 4881ec58010000sub   rsp,158h
fffff800`03c73e4c 488dac2480000000 lea   rbp,
fffff800`03c73e54 48899dc0000000mov   qword ptr ,rbx
fffff800`03c73e5b 4889bdc8000000mov   qword ptr ,rdi
fffff800`03c73e62 4889b5d0000000mov   qword ptr ,rsi
fffff800`03c73e69 fb            sti
fffff800`03c73e6a 65488b1c2588010000 mov   rbx,qword ptr gs:
fffff800`03c73e73 0f0d8bd8010000prefetchw
fffff800`03c73e7a 0fb6bbf6010000movzx   edi,byte ptr
fffff800`03c73e81 40887da8      mov   byte ptr ,dil
fffff800`03c73e85 c683f601000000mov   byte ptr ,0
fffff800`03c73e8c 4c8b93d8010000mov   r10,qword ptr
fffff800`03c73e93 4c8995b8000000mov   qword ptr ,r10
fffff800`03c73e9a 4c8d1d3d010000lea   r11,
fffff800`03c73ea1 41ffe3          jmp   r11
fffff800`03c73ea4 666666666666660f1f840000000000 nop word ptr
fffff800`03c73eb3 66666666660f1f840000000000 nop word ptr

在这个函数中又跳转到了nt!KiSystemServiceStart,这里4c8d1d3d010000 这条指令需要参考一下x64汇编中有关Rex.w前缀及Mod r/m寻址方面的知识来解释这个偏移。在这里就是fffff800`03c73ea1+013d就是nt!KiSystemServiceStart入口地址。接下来再从这个地址开始反汇编。

nt!KiSystemServiceStart:
fffff800`03c73fde 4889a3d8010000mov   qword ptr ,rsp
fffff800`03c73fe5 8bf8            mov   edi,eax
fffff800`03c73fe7 c1ef07          shr   edi,7
fffff800`03c73fea 83e720          and   edi,20h
fffff800`03c73fed 25ff0f0000      and   eax,0FFFh
nt!KiSystemServiceRepeat:
fffff800`03c73ff2 4c8d1547782300lea   r10,
fffff800`03c73ff9 4c8d1d80782300lea   r11,
fffff800`03c74000 f7830001000080000000 test dword ptr ,80h
fffff800`03c7400a 4d0f45d3      cmovner10,r11
fffff800`03c7400e 423b441710      cmp   eax,dword ptr
fffff800`03c74013 0f83e9020000    jae   nt!KiSystemServiceExit+0x1a7 (fffff800`03c74302)
fffff800`03c74019 4e8b1417      mov   r10,qword ptr
fffff800`03c7401d 4d631c82      movsxdr11,dword ptr
fffff800`03c74021 498bc3          mov   rax,r11
fffff800`03c74024 49c1fb04      sar   r11,4
fffff800`03c74028 4d03d3          add   r10,r11
fffff800`03c7402b 83ff20          cmp   edi,20h
fffff800`03c7402e 7550            jne   nt!KiSystemServiceGdiTebAccess+0x49 (fffff800`03c74080)
fffff800`03c74030 4c8b9bb8000000mov   r11,qword ptr

终于找到我们要找到函数了,4c8d1547782300,4c8d1d80782300根据这两条指令我就可以定位到SSDT的位置了。终于拨云见日了。这两条指令都加了REX.W前缀的根据具体字段的意义解释它就可找到SSDT,4c8d1547782300这条指令的64立即数寻址应该是fffff800`03c73ff9+237847这样来扩展,就是RIP+偏移。相类似4c8d1d80782300指令中的立即数应该被扩展成fffff800`03c74000+237880,通过DQ命令查看就是SSDT的内容。

kd> dq fffff800`03c74000+237880
fffff800`03eab880fffff800`03c75b00 00000000`00000000
fffff800`03eab89000000000`00000191 fffff800`03c7678c
fffff800`03eab8a0fffff960`00111c00 00000000`00000000
fffff800`03eab8b000000000`0000033b fffff960`0011391c
fffff800`03eab8c000000000`7771fdd6 00000000`00000000
fffff800`03eab8d0fffff800`00a01400 fffff800`00a013b0
fffff800`03eab8e000000000`00000002 00000000`00005bdb
fffff800`03eab8f000000000`00023f05 00000000`00000000

kd> dq nt!KeServiceDescriptorTableShadow
fffff800`03eab880fffff800`03c75b00 00000000`00000000
fffff800`03eab89000000000`00000191 fffff800`03c7678c
fffff800`03eab8a0fffff960`00111c00 00000000`00000000
fffff800`03eab8b000000000`0000033b fffff960`0011391c
fffff800`03eab8c000000000`7771fdd6 00000000`00000000
fffff800`03eab8d0fffff800`00a01400 fffff800`00a013b0
fffff800`03eab8e000000000`00000002 00000000`00005bdb
fffff800`03eab8f000000000`00023f05 00000000`00000000

两种方法找到位置一样。我这里的输出都针对WIN7 64位系统的调用,通过对2003进行调试发现这样的方法也适用。因为没有暂时没有其它版本操作系统。在其它操作系统上的适用性怎么样就无从知道了。个人推测XP,VISTA也应该差不多但需要事实证明。不过Win7与2003中SSDT表项内容略有不同,虽然都是四个节最后四位是例程的参数个数,但在2003上偏移的计算是SSDT基址+表项值&0xFFFFFFF0,而win7上则变成了SSDT基址+表项值>>4。如果HOOK表项还是要做一个分别对待。我是第一次发帖,大家轻拍啊。上面阐述如有错误请个位大侠多多指教,小弟不胜感激。
页: [1]
查看完整版本: WINDOWS 64位SSDT定位思路