标 题: 关于过某P的双机联调
作 者: upupc
时 间: 2013-03-05,18:20:57
链 接: http://bbs.pediy.com/showthread.php?t=163630
最近公司没啥事做,闲的蛋疼,于是来研究下怎么过某游戏驱动保护的双机联调。
调试环境: 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 [nt!_imp_KfRaiseIrql (804d86f4)]
804f77aa 8845ff mov byte ptr [ebp-1],al
804f77ad e81c010000 call nt!KdpPortLock (804f78ce)
804f77b2 833dc8d5548000 cmp dword ptr [nt!KdDisableCount (8054d5c8)],0
804f77b9 753a jne nt!KdDisableDebugger+0x59 (804f77f5)
nt!KdDisableDebugger+0x1f:
804f77bb a0c1d55480 mov al,byte ptr [nt!KdDebuggerEnabled (8054d5c1)]
804f77c0 84c0 test al,al
804f77c2 7410 je nt!KdDisableDebugger+0x38 (804f77d4)
nt!KdDisableDebugger+0x28:
804f77c4 803dfc6f548000 cmp byte ptr [nt!KdPitchDebugger (80546ffc)],0
804f77cb c605ccd5548001 mov byte ptr [nt!KdPreviouslyEnabled (8054d5cc)],1
804f77d2 7407 je nt!KdDisableDebugger+0x3f (804f77db)
nt!KdDisableDebugger+0x38:
804f77d4 c605ccd5548000 mov byte ptr [nt!KdPreviouslyEnabled (8054d5cc)],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 [nt!KiDebugRoutine (80554004)],offset nt!KdpStub (804f7c86)
804f77ee c605c1d5548000 mov byte ptr [nt!KdDebuggerEnabled (8054d5c1)],0
nt!KdDisableDebugger+0x59:
804f77f5 ff05c8d55480 inc dword ptr [nt!KdDisableCount (8054d5c8)]
804f77fb e8de000000 call nt!KdpPortUnlock (804f78de)
804f7800 8a4dff mov cl,byte ptr [ebp-1]
804f7803 ff151c874d80 call dword ptr [nt!_imp_KfLowerIrql (804d871c)]
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 [TesSafe+0x17b6c (ee1b6b6c)]
ee1a5fcf 8b0d686b1bee mov ecx,dword ptr [TesSafe+0x17b68 (ee1b6b68)]
ee1a5fd5 56 push esi
ee1a5fd6 8b7028 mov esi,dword ptr [eax+28h]
ee1a5fd9 57 push edi
ee1a5fda 8b782c mov edi,dword ptr [eax+2Ch]
ee1a5fdd 33f1 xor esi,ecx
ee1a5fdf 33f9 xor edi,ecx
ee1a5fe1 eb4b jmp TesSafe+0x702e (ee1a602e)
ee1a5fe3 803d6d221bee00 cmp byte ptr [TesSafe+0x1326d (ee1b226d)],0
ee1a5fea 7516 jne TesSafe+0x7002 (ee1a6002)
ee1a5fec 688a4d6e43 push 436E4D8Ah
ee1a5ff1 6876426e57 push 576E4276h
ee1a5ff6 e873caffff call TesSafe+0x3a6e (ee1a2a6e)
ee1a5ffb c6056d221bee01 mov byte ptr [TesSafe+0x1326d (ee1b226d)],1
ee1a6002 85ff test edi,edi
ee1a6004 7404 je TesSafe+0x700a (ee1a600a)
ee1a6006 ffd7 call edi
ee1a6008 eb24 jmp TesSafe+0x702e (ee1a602e)
ee1a600a 803d6e221bee00 cmp byte ptr [TesSafe+0x1326e (ee1b226e)],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 c6056e221bee01 mov byte ptr [TesSafe+0x1326e (ee1b226e)],1
ee1a602e 803e00 cmp byte ptr [esi],0
ee1a6031 75b0 jne TesSafe+0x6fe3 (ee1a5fe3)
ee1a6033 5f pop edi
ee1a6034 5e pop esi
ee1a6035 c3 ret
上图红色部分为当前CPU要执行的指令,继续往下执行,会发现ee1a6031处会直接跳转会该函数前面,分析它的跳转条件,cmp byte ptr [esi],0,执行到这里时,查看下esi的值,发现为8054d5c1,回想下KdDisableDebugger函数实现,里面有个变量KdDebuggerEnabled的地址也为8054d5c1,也就是说在调用了KdDisableDebugger,程序会判断KdDebuggerEnabled的值是否为1,为1的话就不停的调用KdDisableDebugger........, 所以只修改KdDisableDebugger直接返回你会发现你的cpu会一直处于满负荷状态,好吧,那我修改下这个地方的判断,jne改为je,现在好了,不会再死循环了。想一想,游戏保护应该还可能有其他的地方调用了KdDisableDebugger,所以先不取消KdDisableDebugger的断点,继续执行,果然,又断下来了,执行两步,回到调用函数,如下:
ee1a6112 a16c6b1bee mov eax,dword ptr [TesSafe+0x17b6c (ee1b6b6c)] ds:0023:ee1b6b6c=862204e8
ee1a6117 8b402c mov eax,dword ptr [eax+2Ch]
ee1a611a 3305686b1bee xor eax,dword ptr [TesSafe+0x17b68 (ee1b6b68)]
ee1a6120 7404 je TesSafe+0x7126 (ee1a6126)
ee1a6122 ffd0 call eax
ee1a6124 eb24 jmp TesSafe+0x714a (ee1a614a)
ee1a6126 803d72221bee00 cmp byte ptr [TesSafe+0x13272 (ee1b2272)],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 c60572221bee01 mov byte ptr [TesSafe+0x13272 (ee1b2272)],1
ee1a614a 8b0d64221bee mov ecx,dword ptr [TesSafe+0x13264 (ee1b2264)]
ee1a6150 85c9 test ecx,ecx
ee1a6152 740f je TesSafe+0x7163 (ee1a6163)
ee1a6154 a168221bee mov eax,dword ptr [TesSafe+0x13268 (ee1b2268)]
ee1a6159 85c0 test eax,eax
ee1a615b 7406 je TesSafe+0x7163 (ee1a6163)
ee1a615d 3901 cmp dword ptr [ecx],eax
ee1a615f 7402 je TesSafe+0x7163 (ee1a6163)
ee1a6161 8901 mov dword ptr [ecx],eax
ee1a6163 c3 ret
当前指令位于ee1a6124处,往下继续执行
在windbg的反汇编窗口,我们会看到出现一些符号提示,如下
ee1a614a 8b0d64221bee mov ecx,dword ptr [TesSafe+0x13264 (ee1b2264)] ds:0023:ee1b2264={nt!KiDebugRoutine (80554004)}
运行到ee1a614a ,出现了KiDebugRoutine,猜测这里应该是强行将KiDebugRoutine设为了KdpStub,往下执行,在ee1a6161处
ee1a6161 8901 mov dword ptr [ecx],eax ds: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
作 者: upupc
时 间: 2013-03-05,18:20:57
链 接: http://bbs.pediy.com/showthread.php?t=163630
最近公司没啥事做,闲的蛋疼,于是来研究下怎么过某游戏驱动保护的双机联调。
调试环境: 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 [nt!_imp_KfRaiseIrql (804d86f4)]
804f77aa 8845ff mov byte ptr [ebp-1],al
804f77ad e81c010000 call nt!KdpPortLock (804f78ce)
804f77b2 833dc8d5548000 cmp dword ptr [nt!KdDisableCount (8054d5c8)],0
804f77b9 753a jne nt!KdDisableDebugger+0x59 (804f77f5)
nt!KdDisableDebugger+0x1f:
804f77bb a0c1d55480 mov al,byte ptr [nt!KdDebuggerEnabled (8054d5c1)]
804f77c0 84c0 test al,al
804f77c2 7410 je nt!KdDisableDebugger+0x38 (804f77d4)
nt!KdDisableDebugger+0x28:
804f77c4 803dfc6f548000 cmp byte ptr [nt!KdPitchDebugger (80546ffc)],0
804f77cb c605ccd5548001 mov byte ptr [nt!KdPreviouslyEnabled (8054d5cc)],1
804f77d2 7407 je nt!KdDisableDebugger+0x3f (804f77db)
nt!KdDisableDebugger+0x38:
804f77d4 c605ccd5548000 mov byte ptr [nt!KdPreviouslyEnabled (8054d5cc)],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 [nt!KiDebugRoutine (80554004)],offset nt!KdpStub (804f7c86)
804f77ee c605c1d5548000 mov byte ptr [nt!KdDebuggerEnabled (8054d5c1)],0
nt!KdDisableDebugger+0x59:
804f77f5 ff05c8d55480 inc dword ptr [nt!KdDisableCount (8054d5c8)]
804f77fb e8de000000 call nt!KdpPortUnlock (804f78de)
804f7800 8a4dff mov cl,byte ptr [ebp-1]
804f7803 ff151c874d80 call dword ptr [nt!_imp_KfLowerIrql (804d871c)]
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 [TesSafe+0x17b6c (ee1b6b6c)]
ee1a5fcf 8b0d686b1bee mov ecx,dword ptr [TesSafe+0x17b68 (ee1b6b68)]
ee1a5fd5 56 push esi
ee1a5fd6 8b7028 mov esi,dword ptr [eax+28h]
ee1a5fd9 57 push edi
ee1a5fda 8b782c mov edi,dword ptr [eax+2Ch]
ee1a5fdd 33f1 xor esi,ecx
ee1a5fdf 33f9 xor edi,ecx
ee1a5fe1 eb4b jmp TesSafe+0x702e (ee1a602e)
ee1a5fe3 803d6d221bee00 cmp byte ptr [TesSafe+0x1326d (ee1b226d)],0
ee1a5fea 7516 jne TesSafe+0x7002 (ee1a6002)
ee1a5fec 688a4d6e43 push 436E4D8Ah
ee1a5ff1 6876426e57 push 576E4276h
ee1a5ff6 e873caffff call TesSafe+0x3a6e (ee1a2a6e)
ee1a5ffb c6056d221bee01 mov byte ptr [TesSafe+0x1326d (ee1b226d)],1
ee1a6002 85ff test edi,edi
ee1a6004 7404 je TesSafe+0x700a (ee1a600a)
ee1a6006 ffd7 call edi
ee1a6008 eb24 jmp TesSafe+0x702e (ee1a602e)
ee1a600a 803d6e221bee00 cmp byte ptr [TesSafe+0x1326e (ee1b226e)],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 c6056e221bee01 mov byte ptr [TesSafe+0x1326e (ee1b226e)],1
ee1a602e 803e00 cmp byte ptr [esi],0
ee1a6031 75b0 jne TesSafe+0x6fe3 (ee1a5fe3)
ee1a6033 5f pop edi
ee1a6034 5e pop esi
ee1a6035 c3 ret
上图红色部分为当前CPU要执行的指令,继续往下执行,会发现ee1a6031处会直接跳转会该函数前面,分析它的跳转条件,cmp byte ptr [esi],0,执行到这里时,查看下esi的值,发现为8054d5c1,回想下KdDisableDebugger函数实现,里面有个变量KdDebuggerEnabled的地址也为8054d5c1,也就是说在调用了KdDisableDebugger,程序会判断KdDebuggerEnabled的值是否为1,为1的话就不停的调用KdDisableDebugger........, 所以只修改KdDisableDebugger直接返回你会发现你的cpu会一直处于满负荷状态,好吧,那我修改下这个地方的判断,jne改为je,现在好了,不会再死循环了。想一想,游戏保护应该还可能有其他的地方调用了KdDisableDebugger,所以先不取消KdDisableDebugger的断点,继续执行,果然,又断下来了,执行两步,回到调用函数,如下:
ee1a6112 a16c6b1bee mov eax,dword ptr [TesSafe+0x17b6c (ee1b6b6c)] ds:0023:ee1b6b6c=862204e8
ee1a6117 8b402c mov eax,dword ptr [eax+2Ch]
ee1a611a 3305686b1bee xor eax,dword ptr [TesSafe+0x17b68 (ee1b6b68)]
ee1a6120 7404 je TesSafe+0x7126 (ee1a6126)
ee1a6122 ffd0 call eax
ee1a6124 eb24 jmp TesSafe+0x714a (ee1a614a)
ee1a6126 803d72221bee00 cmp byte ptr [TesSafe+0x13272 (ee1b2272)],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 c60572221bee01 mov byte ptr [TesSafe+0x13272 (ee1b2272)],1
ee1a614a 8b0d64221bee mov ecx,dword ptr [TesSafe+0x13264 (ee1b2264)]
ee1a6150 85c9 test ecx,ecx
ee1a6152 740f je TesSafe+0x7163 (ee1a6163)
ee1a6154 a168221bee mov eax,dword ptr [TesSafe+0x13268 (ee1b2268)]
ee1a6159 85c0 test eax,eax
ee1a615b 7406 je TesSafe+0x7163 (ee1a6163)
ee1a615d 3901 cmp dword ptr [ecx],eax
ee1a615f 7402 je TesSafe+0x7163 (ee1a6163)
ee1a6161 8901 mov dword ptr [ecx],eax
ee1a6163 c3 ret
当前指令位于ee1a6124处,往下继续执行
在windbg的反汇编窗口,我们会看到出现一些符号提示,如下
ee1a614a 8b0d64221bee mov ecx,dword ptr [TesSafe+0x13264 (ee1b2264)] ds:0023:ee1b2264={nt!KiDebugRoutine (80554004)}
运行到ee1a614a ,出现了KiDebugRoutine,猜测这里应该是强行将KiDebugRoutine设为了KdpStub,往下执行,在ee1a6161处
ee1a6161 8901 mov dword ptr [ecx],eax ds: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