首先要理解双机调试的几个常识(有点类似初中上学时候,我们要做一道关于三角形的证明题,那么数学老师是不是要先交你几个定理:勾股定理,两点之间直线最短,同角或等角的补角相等 等等),那么现在你要解决双机调试,肯定也要知道双机调试的几个定理(常识):
我们获取知识的来源:
1:就是我告诉你们(别人告诉你们也好。)
2:从自己的实战经验总结(实战出真理。)
3:从书本上得到的知识。
1:KdReceivePacket和KdSendPacket的两个串口函数, 用于接收和发送串口数据
被调试的系统--》KdSendPacket发送数据到串口--》VM虚拟机串口转接到windbg
==》KdSendPacket发送数据到串口,然后被调试的系统KdReceivePacket接收数据
2:不能调用KdDisableDebugger禁止内核调式,否则windbg将无法调式。
NTSTATUS KdDisableDebugger(void)
{
int v0; // ecx@7
char v2; // [sp+1h] [bp-1h]@1
v2 = KfRaiseIrql(2);
KdpPortLock();
if ( !KdDisableCount )
{
if ( !(_BYTE)KdDebuggerEnabled || (KdPreviouslyEnabled = 1, KdPitchDebugger) )
KdPreviouslyEnabled = 0;
if ( (_BYTE)KdDebuggerEnabled )
{
KdpSuspendAllBreakpoints();
KiDebugRoutine = (int)KdpStub;
LOBYTE(KdDebuggerEnabled) = 0;
}
}
++KdDisableCount;
KdpPortUnlock();
LOBYTE(v0) = v2;
return KfLowerIrql(v0);
}
//禁止双机调试
KiDebugRoutine = (int)KdpStub;
LOBYTE(KdDebuggerEnabled) = 0;
3:双机调试的时候,那么 [KiDebugRoutine]必须指向KdpTrap(正常启动的系统KiDebugRoutine为KdpStub,在Boot.ini里加上/DEBUG启动的系统的KiDebugRoutine为KdpTrap),为了证明这点,我们现在来看虚拟机:
在正常系统下:
lkd> dd KiDebugRoutine //全局变量指向804f7c86 !KdpStub
80553f84 804f7c86 00000000 7c92e4a8 7c92e45c
80553f94 7c92e440 7c92e430 0002625a 00000000
80553fa4 00000000 00000000 00000000 00000000
80553fb4 00000000 00000000 00000000 00000000
80553fc4 00000000 00000000 00000000 00000000
80553fd4 00000000 00000000 00000000 80502b9c
80553fe4 00000000 0000011c 80503010 bf999e80
80553ff4 00000000 0000029b bf99ab90 00000000
lkd> u 804f7c86
nt!KdpStub:
804f7c86 8bff mov edi,edi
804f7c88 55 push ebp
804f7c89 8bec mov ebp,esp
804f7c8b 8b4510 mov eax,dword ptr [ebp+10h]
804f7c8e 813803000080 cmp dword ptr [eax],80000003h
804f7c94 7525 jne nt!KdpStub+0x35 (804f7cbb)
804f7c96 83781000 cmp dword ptr [eax+10h],0
804f7c9a 761f jbe nt!KdpStub+0x35 (804f7cbb)
在双机环境下:
kd> dd KiDebugRoutine
80553f84 80661a86 00000000 7c92e4a8 7c92e45c
80553f94 7c92e440 7c92e430 0002625a 00000000
80553fa4 00000000 00000000 00000000 00000000
80553fb4 00000000 00000000 00000000 00000000
80553fc4 00000000 00000000 00000000 00000000
80553fd4 00000000 00000000 00000000 80502b9c
80553fe4 00000000 0000011c 80503010 bf999e80
80553ff4 00000000 0000029b bf99ab90 00000000
kd> u 80661a86
nt!KdpTrap:
80661a86 8bff mov edi,edi
80661a88 55 push ebp
80661a89 8bec mov ebp,esp
80661a8b 51 push ecx
80661a8c 51 push ecx
80661a8d 8b4510 mov eax,dword ptr [ebp+10h]
80661a90 813803000080 cmp dword ptr [eax],80000003h
80661a96 56 push esi
内核调试器加载,则KiDebugRoutine(KdpTrap)可以处理int 0x3异常(看windbg断下之后是不是 int 0x3):
nt!RtlpBreakWithStatusInstruction:
80528bec cc int 3
异常处理到这里被正常返回。处理方法是将当前的处理器状态(比如各寄存器等重要信息)发送KdSendPacket(定理1要用到的函数) 给通过串口相连的主机上的内核调试器,并一直在等待内核调试器的回应,系统这时在KdpTrap里调用一个Kd函数KdpSendWaitContinue循环等待KdReceivePacket来自串口的数据(内核调试器和被调试系统通过串口联系),直到内核调试器下达继续执行的命令g,,让系统继续执行,系统可以正常从int 0x3后面一条指令执行。
我大概画了个图给大家理解:
通过图片我们大概知道流程,那么反双机调试只需要把[KiDebugRoutine]修改为KdpStub,又达到了反双机的目的。
推荐大家看两篇文章:http://www.xfocus.net/articles/200412/761.html
http://www.xfocus.net/articles/200412/765.html
一节课我们是无法阐述内核调试原理的,但是我们只要满足上面的三个条件,那么windbg就可以双机调试。那么我们来分析TP。大家都还记得前面我教大家怎么分析游戏保护对系统做的手脚,如果大家对那一节课还没吃透,建议再回去看看。
现在我们以现有的工具gmer来静态分析TP(至于动态分析,我们要到下一节课才会学到。)
为什么不用xuetr,因为人怕出名猪怕壮,xuetr已经进入了tp的黑名单。
1:两个串口发送函数已经被tp hook。
2:肯定会调用KdDisableDebugger来禁止内核调试
3:TP会强行的把KiDebugRoutine全局变量修改为KdpStub
TP反双机调试的原理分析。
1:已经处理定理1
2:我们在KdDisableDebugger函数写入ret
KiDebugRoutine = (int)KdpStub;
LOBYTE(KdDebuggerEnabled) = 0; //你就要想到,这里也是为0
如果tp的一条线程执行KdDisableDebugger,那么我们在HookKdDisableDebugger里面的[ebp+4]就是tp这条线程调用这个函数之后的下一个地址。
[ebp+4] 就是要执行的下一个地址,也就是函数返回地址。
无限调用
0 KdDisableDebugger() Call B1532008
0 KdDisableDebugger() Call B1532008
0 KdDisableDebugger() Call B1532008
0 KdDisableDebugger() Call B1532008
0 KdDisableDebugger() Call B1532008
0 KdDisableDebugger() Call B1532008
0 KdDisableDebugger() Call B1532008
0 KdDisableDebugger() Call B1532008
0 KdDisableDebugger() Call B1532008
kd> u B1532000 l 30
TesSafe+0x7000:
b1532000 b101 mov cl,1
b1532002 85ff test edi,edi
b1532004 7404 je TesSafe+0x700a (b153200a)
b1532006 ffd7 call edi //edi KdDisableDebugger
b1532008 eb24 jmp TesSafe+0x702e (b153202e) //[ebp+4]
b153200a 803d6ee253b100 cmp byte ptr [TesSafe+0x1326e (b153e26e)],0
b1532011 751b jne TesSafe+0x702e (b153202e)
b1532013 6812010000 push 112h
b1532018 68e64d6e43 push 436E4DE6h
b153201d 6873426e57 push 576E4273h
b1532022 e865caffff call TesSafe+0x3a8c (b152ea8c)
b1532027 c6056ee253b101 mov byte ptr [TesSafe+0x1326e (b153e26e)],1
b153202e 803e00 cmp byte ptr [esi],0 //这是一条判断语句
//LOBYTE(KdDebuggerEnabled) = 0; //你就要想到,这里也是为0
//他这里的 esi 是不是 KdDebuggerEnabled???
kd> bp b153202e
kd> g
Breakpoint 1 hit
TesSafe+0x702e:
b153202e 803e00 cmp byte ptr [esi],0
kd> r esi
esi=8054d541
kd> u 8054d541
nt!KdDebuggerEnabled:
8054d541 0100 add dword ptr [eax],eax
8054d543 0001 add byte ptr [ecx],al
8054d545 0000 add byte ptr [eax],al
8054d547 003f add byte ptr [edi],bh
8054d549 ff ???
8054d54a ff ???
8054d54b ff00 inc dword ptr [eax]
//果然证实了我们的猜想。
我们把b153202e 803e00 cmp byte ptr [esi],0
修改为 b153202e 803e01 cmp byte ptr [esi],1
之后,就等于pass了这条线程,那么又出现了下一条
b1532031 75b0 jne TesSafe+0x6fe3 (b1531fe3)
b1532033 5f pop edi
b1532034 5e pop esi
b1532035 c3 ret
0 KdDisableDebugger() Call B1532124
kd> u B1532124 l 30
TesSafe+0x7124:
Call edi //调用禁止内核调式的函数
b1532124 eb24 jmp TesSafe+0x714a (b153214a)
b1532126 803d72e253b100 cmp byte ptr [TesSafe+0x13272 (b153e272)],0
b153212d 751b jne TesSafe+0x714a (b153214a)
b153212f 6882010000 push 182h
b1532134 68e64d6e43 push 436E4DE6h
b1532139 6873426e57 push 576E4273h
b153213e e849c9ffff call TesSafe+0x3a8c (b152ea8c)
b1532143 c60572e253b101 mov byte ptr [TesSafe+0x13272 (b153e272)],1
b153214a 8b0d64e253b1 mov ecx,dword ptr [TesSafe+0x13264 (b153e264)]
b1532150 85c9 test ecx,ecx
b1532152 740f je TesSafe+0x7163 (b1532163)
b1532154 a168e253b1 mov eax,dword ptr [TesSafe+0x13268 (b153e268)]
b1532159 85c0 test eax,eax
b153215b 7406 je TesSafe+0x7163 (b1532163)
b153215d 3901 cmp dword ptr [ecx],eax
b153215f 7402 je TesSafe+0x7163 (b1532163)
b1532161 8901 mov dword ptr [ecx],eax
b1532163 c3 ret
b1532161 8901 mov dword ptr [ecx],eax //eax == KdpStub
是不是似曾相识?
KiDebugRoutine = (int)KdpStub;
Eax是从b1532154 a168e253b1 mov eax,dword ptr [TesSafe+0x13268 (b153e268)]
kd> dd b153e268
b153e268 804f7c86 00000100 00000000 00000000
b153e278 00000000 00000000 00000000 00000000
b153e288 00000000 00000000 00000000 00000000
kd> u 804f7c86
nt!KdpStub:
804f7c86 8bff mov edi,edi
804f7c88 55 push ebp
804f7c89 8bec mov ebp,esp
804f7c8b 8b4510 mov eax,dword ptr [ebp+10h]
804f7c8e 813803000080 cmp dword ptr [eax],80000003h
804f7c94 7525 jne nt!KdpStub+0x35 (804f7cbb)
804f7c96 83781000 cmp dword ptr [eax+10h],0
804f7c9a 761f jbe nt!KdpStub+0x35 (804f7cbb)
Ecx是从这里来的
b153214a 8b0d64e253b1 mov ecx,dword ptr [TesSafe+0x13264 (b153e264)]
b1532150 85c9 test ecx,ecx
kd> dd b153e264
b153e264 80553f84 804f7c86 00000100 00000000
b153e274 00000000 00000000 00000000 00000000
b153e284 00000000 00000000 00000000 00000000
b153e294 00000000 00000000 00000000 00000000
b153e2a4 00000000 00000000 00000000 00000000
b153e2b4 00000000 00000000 00000000 00000000
b153e2c4 00000000 89c34830 00000000 00000000
b153e2d4 00000000 00001c38 00001c38 00001c38
kd> u 80553f84
nt!KiDebugRoutine:
80553f84 861a xchg bl,byte ptr [edx]
80553f86 66800000 add byte ptr [eax],0
80553f8a 0000 add byte ptr [eax],al
正好验证了我们的猜想:
KiDebugRoutine = (int)KdpStub;
b1532161 8901 mov dword ptr [ecx],eax //eax == KdpStub
TP强行的把KiDebugRoutine 这个全局变量指向KdpStub
1:修改b153202e 803e00 cmp byte ptr [esi],0
2:b1532161 8901 mov dword ptr [ecx],eax //eax == KdpStub
我们可以inlinehook KdpStub 在头部直接跳到 KdpTrap 函数
或者我们可以通过搜索特征码,
b1532124 eb24 jmp TesSafe+0x714a (b153214a)
b1532126 803d72e253b100 cmp byte ptr [TesSafe+0x13272 (b153e272)],0
b153212d 751b jne TesSafe+0x714a (b153214a)
b153212f 6882010000 push 182h
b1532134 68e64d6e43 push 436E4DE6h
b1532139 6873426e57 push 576E4273h
以b1532124为开始地址,搜索特征码 ,那样我们就可以直接定位到TP的两个变量
b1532154 a168e253b1 mov eax,dword ptr [TesSafe+0x13268 (b153e268)]
ULONG_PTR ul_tp_check = 0xb153e268;
*(ULONG_PTR*)ul_tp_check = KdpTrap;
现在我们用windbg手工。
相信大家懂得原理之后,写代码只是一个苦力活了。
为了避免TX追杀,bin就不放了,你可以通过购买AGP视频教程:http://www.antigameprotect.com/thread-2860-1-1.html
来获得更多的内核安全,游戏安全,反游戏安全的驱动知识。