在32位Windows中我们有很多定位SSDT的方法,最直接的就是利用导出符号来找到SSDT。再有就是通过在nt!KeAddSystemServiceTable函数中进行反汇编搜索。可是在64位WINDOWS中这两种方法都行不通。在64位Windows中不在导出SSDT了。这样要HOOK SSDT表就出现了第一个问题如何找到它?我想了三种思路。
思路1:
对每种版本的系统确定一个ntoskrnl到SSDT的硬编码偏移。呵呵,这种方法可能比较傻但最简单直接有效。就是维护起来比较非常麻烦,要对每一个版本进单独处理,如果不同补丁版本有变化也要处理。
思路2:
64位WINDOWS中存放着每个服务例程的入口偏移(这个偏移是相对于SSDT的开始地址的)。我取一个一般不被HOOK的服务例程入口偏移,然后从ntoskrnl的开始地址一直搜索到结束来查找这四个字节,然后根据服务例程的索引定位到SSDT的开始位置。可能对不同版本的操作系统其偏移和索引是不同的需要分别进行处理,但是相比第一种思路要通用一些。
思路3:
还是利用反汇编的方法。后来我想能不能映像搜索到SSDT的地址。
kd> dq nt!KeServiceDescriptorTable
fffff800`03eab840 fffff800`03c75b00 00000000`00000000
fffff800`03eab850 00000000`00000191 fffff800`03c7678c
fffff800`03eab860 00000000`00000000 00000000`00000000
fffff800`03eab870 00000000`00000000 00000000`00000000
fffff800`03eab880 fffff800`03c75b00 00000000`00000000
fffff800`03eab890 00000000`00000191 fffff800`03c7678c
fffff800`03eab8a0 fffff960`00111c00 00000000`00000000
fffff800`03eab8b0 00000000`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 4c8d1547782300 lea r10,[nt!KeServiceDescriptorTable (fffff800`03eab840)]
fffff800`03c73ff9 4c8d1d80782300 lea r11,[nt!KeServiceDescriptorTableShadow (fffff800`03eab880)]
fffff800`03c74000 f7830001000080000000 test dword ptr [rbx+100h],80h
fffff800`03c7400a 4d0f45d3 cmovne r10,r11
fffff800`03c7400e 423b441710 cmp eax,dword ptr [rdi+r10+10h]
fffff800`03c74013 0f83e9020000 jae nt!KiSystemServiceExit+0x1a7 (fffff800`03c74302)
fffff800`03c74019 4e8b1417 mov r10,qword ptr [rdi+r10]
fffff800`03c7401d 4d631c82 movsxd r11,dword ptr [r10+rax*4]
但是一个新的问题也同时产生了,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 488d059d300000 lea rax,[nt!KiServiceLinkage (fffff800`03c706f0)]
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 4881ec58010000 sub rsp,158h
fffff800`03c73e4c 488dac2480000000 lea rbp,[rsp+80h]
fffff800`03c73e54 48899dc0000000 mov qword ptr [rbp+0C0h],rbx
fffff800`03c73e5b 4889bdc8000000 mov qword ptr [rbp+0C8h],rdi
fffff800`03c73e62 4889b5d0000000 mov qword ptr [rbp+0D0h],rsi
fffff800`03c73e69 fb sti
fffff800`03c73e6a 65488b1c2588010000 mov rbx,qword ptr gs:[188h]
fffff800`03c73e73 0f0d8bd8010000 prefetchw [rbx+1D8h]
fffff800`03c73e7a 0fb6bbf6010000 movzx edi,byte ptr [rbx+1F6h]
fffff800`03c73e81 40887da8 mov byte ptr [rbp-58h],dil
fffff800`03c73e85 c683f601000000 mov byte ptr [rbx+1F6h],0
fffff800`03c73e8c 4c8b93d8010000 mov r10,qword ptr [rbx+1D8h]
fffff800`03c73e93 4c8995b8000000 mov qword ptr [rbp+0B8h],r10
fffff800`03c73e9a 4c8d1d3d010000 lea r11,[nt!KiSystemServiceStart (fffff800`03c73fde)]
fffff800`03c73ea1 41ffe3 jmp r11
fffff800`03c73ea4 666666666666660f1f840000000000 nop word ptr [rax+rax]
fffff800`03c73eb3 66666666660f1f840000000000 nop word ptr [rax+rax]
在这个函数中又跳转到了nt!KiSystemServiceStart,这里4c8d1d3d010000 这条指令需要参考一下x64汇编中有关Rex.w前缀及Mod r/m寻址方面的知识来解释这个偏移。在这里就是fffff800`03c73ea1+013d就是nt!KiSystemServiceStart入口地址。接下来再从这个地址开始反汇编。
nt!KiSystemServiceStart:
fffff800`03c73fde 4889a3d8010000 mov qword ptr [rbx+1D8h],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 4c8d1547782300 lea r10,[nt!KeServiceDescriptorTable (fffff800`03eab840)]
fffff800`03c73ff9 4c8d1d80782300 lea r11,[nt!KeServiceDescriptorTableShadow (fffff800`03eab880)]
fffff800`03c74000 f7830001000080000000 test dword ptr [rbx+100h],80h
fffff800`03c7400a 4d0f45d3 cmovne r10,r11
fffff800`03c7400e 423b441710 cmp eax,dword ptr [rdi+r10+10h]
fffff800`03c74013 0f83e9020000 jae nt!KiSystemServiceExit+0x1a7 (fffff800`03c74302)
fffff800`03c74019 4e8b1417 mov r10,qword ptr [rdi+r10]
fffff800`03c7401d 4d631c82 movsxd r11,dword ptr [r10+rax*4]
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 4c8b9bb8000000 mov r11,qword ptr [rbx+0B8h]
终于找到我们要找到函数了,4c8d1547782300,4c8d1d80782300根据这两条指令我就可以定位到SSDT的位置了。终于拨云见日了。这两条指令都加了REX.W前缀的根据具体字段的意义解释它就可找到SSDT,4c8d1547782300这条指令的64立即数寻址应该是fffff800`03c73ff9+237847这样来扩展,就是RIP+偏移。相类似4c8d1d80782300指令中的立即数应该被扩展成fffff800`03c74000+237880,通过DQ命令查看就是SSDT的内容。
kd> dq fffff800`03c74000+237880
fffff800`03eab880 fffff800`03c75b00 00000000`00000000
fffff800`03eab890 00000000`00000191 fffff800`03c7678c
fffff800`03eab8a0 fffff960`00111c00 00000000`00000000
fffff800`03eab8b0 00000000`0000033b fffff960`0011391c
fffff800`03eab8c0 00000000`7771fdd6 00000000`00000000
fffff800`03eab8d0 fffff800`00a01400 fffff800`00a013b0
fffff800`03eab8e0 00000000`00000002 00000000`00005bdb
fffff800`03eab8f0 00000000`00023f05 00000000`00000000
kd> dq nt!KeServiceDescriptorTableShadow
fffff800`03eab880 fffff800`03c75b00 00000000`00000000
fffff800`03eab890 00000000`00000191 fffff800`03c7678c
fffff800`03eab8a0 fffff960`00111c00 00000000`00000000
fffff800`03eab8b0 00000000`0000033b fffff960`0011391c
fffff800`03eab8c0 00000000`7771fdd6 00000000`00000000
fffff800`03eab8d0 fffff800`00a01400 fffff800`00a013b0
fffff800`03eab8e0 00000000`00000002 00000000`00005bdb
fffff800`03eab8f0 00000000`00023f05 00000000`00000000
两种方法找到位置一样。我这里的输出都针对WIN7 64位系统的调用,通过对2003进行调试发现这样的方法也适用。因为没有暂时没有其它版本操作系统。在其它操作系统上的适用性怎么样就无从知道了。个人推测XP,VISTA也应该差不多但需要事实证明。不过Win7与2003中SSDT表项内容略有不同,虽然都是四个节最后四位是例程的参数个数,但在2003上偏移的计算是SSDT基址+表项值&0xFFFFFFF0,而win7上则变成了SSDT基址+表项值>>4。如果HOOK表项还是要做一个分别对待。上面阐述如有错误请个位大侠多多指教,小弟不胜感激。