ytn2001 发表于 2018-3-4 10:46:41

过TP游戏驱动保护双机调试


调试环境: win7 + windbg + VirtualKD +vmware(虚拟机装的XP SP3)
xp内核:ntkrnlpa.exe
理论知识准备:windbg双机联调的配置和内核调式器原理,这方面谷歌上大把资料,自己找吧。

我们都知道,在虚拟机上运行有驱动保护的游戏后,windbg就会没办法下断了,准确点说是windbg不会再收到目标机的任何数据,所以这时候外在现象就是windbg虽然仍然显示连接中,但是所有的功能都用不了。

要解决这个问题,我们首先要换位思考,如果是你,你怎么实现这个功能,最简单的方法当然是调用内核现成的API来实现禁用调试这个功能,于是查找内核API,发现有个KdDisableDebugger函数,从名字看就知道是干啥的了。

先在windbg查看函数具体实现,如下:
804f779c 8bff            mov   edi,edi
804f779e 55            push    ebp
804f779f 8bec            mov   ebp,esp
804f77a1 51            push    ecx
804f77a2 b102            mov   cl,2
804f77a4 ff15f4864d80    call    dword ptr
804f77aa 8845ff          mov   byte ptr ,al
804f77ad e81c010000      call    nt!KdpPortLock (804f78ce)
804f77b2 833dc8d5548000cmp   dword ptr ,0
804f77b9 753a            jne   nt!KdDisableDebugger+0x59 (804f77f5)

nt!KdDisableDebugger+0x1f:
804f77bb a0c1d55480      mov   al,byte ptr
804f77c0 84c0            test    al,al
804f77c2 7410            je      nt!KdDisableDebugger+0x38 (804f77d4)

nt!KdDisableDebugger+0x28:
804f77c4 803dfc6f548000cmp   byte ptr ,0
804f77cb c605ccd5548001mov   byte ptr ,1
804f77d2 7407            je      nt!KdDisableDebugger+0x3f (804f77db)

nt!KdDisableDebugger+0x38:
804f77d4 c605ccd5548000mov   byte ptr ,0

nt!KdDisableDebugger+0x3f:
804f77db 84c0            test    al,al
804f77dd 7416            je      nt!KdDisableDebugger+0x59 (804f77f5)

nt!KdDisableDebugger+0x43:
804f77df e8baa31600      call    nt!KdpSuspendAllBreakpoints (80661b9e)
804f77e4 c70504405580867c4f80 mov dword ptr ,offset nt!KdpStub (804f7c86)
804f77ee c605c1d5548000mov   byte ptr ,0

nt!KdDisableDebugger+0x59:
804f77f5 ff05c8d55480    inc   dword ptr
804f77fb e8de000000      call    nt!KdpPortUnlock (804f78de)
804f7800 8a4dff          mov   cl,byte ptr
804f7803 ff151c874d80    call    dword ptr
804f7809 c9            leave
804f780a c3            ret

发现在nt!KdDisableDebugger+0x43处,是API真正起作用的地方
首先call    nt!KdpSuspendAllBreakpoints挂起所有的断点,执行完这个call,windbg会从断开状态变为运行状态
然后nt!KiDebugRoutine变量设为nt!KdpStub
最后nt!KdDebuggerEnabled 变量设为0,也就是false
这三条指令执行后,windbg也就无法再继续调试了

这里需要注意KiDebugRoutine,这是一个函数变量,内核调试绝大部分工作都是调用该变量所指向的函数来实现的,那么这里将它赋值为KdpStub是什么意思呢?

原来在内核中,有两个API实现了内核调试的功能,KdpStub和KdpTrap,这两个函数有什么区别呢?KdpStub是在非调试情况下使用的API,也就是说在非调式情况下KiDebugRoutine应该是指向KdpStub的,而KdpStub基本上没做什么调试的事情,而所有和调试相关的工作则都放到了KdpTrap里,所以,要想保持windbg可以使用,则要保证KiDebugRoutine是指向KdpTrap的。

分析完毕,那么要现在看游戏保护是否真的调用了KdDisableDebugger。
在KdDisableDebugger开头下断点,启动游戏。
windbg断下来了,看来是真的调用了这个函数,那么现在我们要让这个函数失去作用。
windbg输入ew KdDisableDebugger 0xc390, 意思是修改KdDisableDebugger头两个字节,变为:
kd> u KdDisableDebugger
nt!KdDisableDebugger:
804f779c 90            nop
804f779d c3            ret
现在KdDisableDebugger会直接返回,不在起作用。
当然,事情远没有这么简单,取消断点继续执行后,会发现windbg还是没办法用。
看来游戏保护在调用KdDisableDebugger后还做了其他的事情。

一切重来,重启虚拟机XP,修改KdDisableDebugger为直接返回,下断KdDisableDebugger。
运行游戏,断下来后,单步执行两次,返回到上一层函数,整个函数如下
ee1a5fca a16c6b1bee      mov   eax,dword ptr
ee1a5fcf 8b0d686b1bee    mov   ecx,dword ptr
ee1a5fd5 56            push    esi
ee1a5fd6 8b7028          mov   esi,dword ptr
ee1a5fd9 57            push    edi
ee1a5fda 8b782c          mov   edi,dword ptr
ee1a5fdd 33f1            xor   esi,ecx
ee1a5fdf 33f9            xor   edi,ecx
ee1a5fe1 eb4b            jmp   TesSafe+0x702e (ee1a602e)
ee1a5fe3 803d6d221bee00cmp   byte ptr ,0
ee1a5fea 7516            jne   TesSafe+0x7002 (ee1a6002)
ee1a5fec 688a4d6e43      push    436E4D8Ah
ee1a5ff1 6876426e57      push    576E4276h
ee1a5ff6 e873caffff      call    TesSafe+0x3a6e (ee1a2a6e)
ee1a5ffb c6056d221bee01mov   byte ptr ,1
ee1a6002 85ff            test    edi,edi
ee1a6004 7404            je      TesSafe+0x700a (ee1a600a)
ee1a6006 ffd7            call    edi
ee1a6008 eb24            jmp   TesSafe+0x702e (ee1a602e)
ee1a600a 803d6e221bee00cmp   byte ptr ,0
ee1a6011 751b            jne   TesSafe+0x702e (ee1a602e)
ee1a6013 6812010000      push    112h
ee1a6018 68e64d6e43      push    436E4DE6h
ee1a601d 6873426e57      push    576E4273h
ee1a6022 e865caffff      call    TesSafe+0x3a8c (ee1a2a8c)
ee1a6027 c6056e221bee01mov   byte ptr ,1
ee1a602e 803e00          cmp   byte ptr ,0
ee1a6031 75b0            jne   TesSafe+0x6fe3 (ee1a5fe3)
ee1a6033 5f            pop   edi
ee1a6034 5e            pop   esi
ee1a6035 c3            ret

上图红色部分为当前CPU要执行的指令,继续往下执行,会发现ee1a6031处会直接跳转会该函数前面,分析它的跳转条件,cmp   byte ptr ,0,执行到这里时,查看下esi的值,发现为8054d5c1,回想下KdDisableDebugger函数实现,里面有个变量KdDebuggerEnabled的地址也为8054d5c1,也就是说在调用了KdDisableDebugger,程序会判断KdDebuggerEnabled的值是否为1,为1的话就不停的调用KdDisableDebugger........, 所以只修改KdDisableDebugger直接返回你会发现你的cpu会一直处于满负荷状态,好吧,那我修改下这个地方的判断,jne改为je,现在好了,不会再死循环了。想一想,游戏保护应该还可能有其他的地方调用了KdDisableDebugger,所以先不取消KdDisableDebugger的断点,继续执行,果然,又断下来了,执行两步,回到调用函数,如下:
ee1a6112 a16c6b1bee      mov   eax,dword ptr ds:0023:ee1b6b6c=862204e8
ee1a6117 8b402c          mov   eax,dword ptr
ee1a611a 3305686b1bee    xor   eax,dword ptr
ee1a6120 7404            je      TesSafe+0x7126 (ee1a6126)
ee1a6122 ffd0            call    eax
ee1a6124 eb24            jmp   TesSafe+0x714a (ee1a614a)
ee1a6126 803d72221bee00cmp   byte ptr ,0
ee1a612d 751b            jne   TesSafe+0x714a (ee1a614a)
ee1a612f 6882010000      push    182h
ee1a6134 68e64d6e43      push    436E4DE6h
ee1a6139 6873426e57      push    576E4273h
ee1a613e e849c9ffff      call    TesSafe+0x3a8c (ee1a2a8c)
ee1a6143 c60572221bee01mov   byte ptr ,1
ee1a614a 8b0d64221bee    mov   ecx,dword ptr
ee1a6150 85c9            test    ecx,ecx
ee1a6152 740f            je      TesSafe+0x7163 (ee1a6163)
ee1a6154 a168221bee      mov   eax,dword ptr
ee1a6159 85c0            test    eax,eax
ee1a615b 7406            je      TesSafe+0x7163 (ee1a6163)
ee1a615d 3901            cmp   dword ptr ,eax
ee1a615f 7402            je      TesSafe+0x7163 (ee1a6163)
ee1a6161 8901            mov   dword ptr ,eax
ee1a6163 c3            ret

当前指令位于ee1a6124处,往下继续执行
在windbg的反汇编窗口,我们会看到出现一些符号提示,如下
ee1a614a 8b0d64221bee    mov   ecx,dword ptr ds:0023:ee1b2264={nt!KiDebugRoutine (80554004)}
运行到ee1a614a ,出现了KiDebugRoutine,猜测这里应该是强行将KiDebugRoutine设为了KdpStub,往下执行,在ee1a6161处
ee1a6161 8901            mov   dword ptr ,eaxds:0023:80554004={nt!KdpTrap (80662706)}
这里显示的符号为ecx当前所有,而这条指令是吧eax的值付给ecx地址处,
kd> r eax
eax=804f7c86
看到eax为804f7c86

kd> u KdpStub
nt!KdpStub:
804f7c86 8bff            mov   edi,edi
看到KdpStub的地址为804f7c86

得到结论,ee1a6161处将KdpStub赋值给了KiDebugRoutine,而KiDebugRoutine必须保持为KdpTrap ,那么简单了,nop掉这句(建议不要采用nop的方式,因为搞不好以后就会内存校验这里,这里另外一种比较稳妥的方法是更改kdpStub头部,让其直接jmp到KdpTrap ),继续执行。

又在KdDisableDebugger断下了,回到上层函数,发现还是之前那个函数,无视之,继续执行,
在连续断下N次后,每次都是这个函数,那么基本上也就确认只有两个地方调用了。去掉KdDisableDebugger的断点,继续执行。

游戏跑起来后,现在在break windbg,发现windbg又可以断下了。
搞定收工。
总结下步骤,
1.修改KdDisableDebugger为直接返回即在windbg执行 ew KdDisableDebugger 0xc390
2.修改驱动保护第一个调用KdDisableDebugger的函数,让其通过检查KdDebuggerEnabled,防止死循环,在windbg执行eb TesSafe+0x7031 74
3.修改驱动保护第二个调用KdDisableDebugger的函数,让其不要修改KiDebugRoutine的值,在windbg输入ew TesSafe+7161 9090

魔皇铭 发表于 2019-11-25 10:55:01

需要游戏调试驱动的+Q1084850218

支持楼主,支持看流星社区,以后我会经常来!
页: [1]
查看完整版本: 过TP游戏驱动保护双机调试