对双机调试的探索

标 题:  对双机调试的探索
作 者: farmtest
时 间: 2014-01-21,12:26:15
链 接: http://bbs.pediy.com/showthread.php?t=183925

最近学习游戏保护,发现VM+WINDBG双机调试不能使用,以游戏保护*P为例,经过论坛搜索,发现有以下几点动了手脚:
1.  kdDebuggerEnabled变量不停的清零,清零的代码还做了检测,所以意味着不能轻易修改*P的代码, 
kdDebuggerEnabled是windows全局变量,用来标识内核调试是否被启用
开启调试状态:*(PBYTE)KdDebuggerEnabled=0x01;
禁止调试状态:*(PBYTE)KdDebuggerEnabled=0x00;


2.  KiDebugRoutine变量不停设置为KdpStub函数, 其写入的代码同上做了检测,不能随便修改
KiDebugRoutine是函数指针,内核调试引擎的异常处理回调函数指针。当内核调试引擎活动时,它指向KdpTrap函数,否则指向KdpStub函数


3.  KdSendPacket和KdReceivePacket函数的IAT HOOK,且在HOOK的地方也做了检测,所以同样不能简单修改来达到目的
KdSendPacket KDCOM函数,发送数据包
KdReceivePacket KDCOM函数,接收数据包

以上就是*p的3块防护及其含义,下面一块一块来探索一番



第一块:
正常情况下ctrl+break中断到windbg,
代码:
1: kd> dd kdDebuggerEnabled
83f9d96c  00000001 00000000 00000000 db1dbbbb
发现此时kdDebuggerEnabled为1,
代码:
1: kd> ed kdDebuggerEnabled 0
1: kd> dd kdDebuggerEnabled
83f9d96c  00000000 00000000 00000000 db1dbbbb
修改后F5虚拟机跑起来,但是ctrl+break不能断下了
首先想到观察下ctrl+break的函数流程, 看看这个变量对流程中的哪个地方造成了阻碍,
双机调试正常情况下ctrl+break中断到windbg栈回溯如下:
代码:
00 807eaae0 83eb40cb nt!RtlpBreakWithStatusInstruction
01 807eaae8 83eb409d nt!KdCheckForDebugBreak+0x22
02 807eab98 83eb937f nt!KeUpdateRunTime+0x164
03 807eac20 83eb0e0d nt!PoIdle+0x524
04 807eac24 00000000 nt!KiIdleLoop+0xd
或者
代码:
00 807eaae0 83e950cb nt!RtlpBreakWithStatusInstruction
01 807eaae8 83e9509d nt!KdCheckForDebugBreak+0x22
02 807eab18 8422f430 nt!KeUpdateRunTime+0x164
03 807eab18 937d05d6 hal!HalpClockInterruptPn+0x158
...
无论从哪里发起最后的几个函数流程都是一样KeUpdateRunTime-> KdCheckForDebugBreak-> RtlpBreakWithStatusInstruction
自然想看看这几个函数中的代码,看看是否和kdDebuggerEnabled有关,

代码:
1: kd> uf KeUpdateRunTime
后ctrl+F搜索kdDebuggerEnabled发现一处代码
83eb4082 803d6cd9f98300  cmp     byte ptr [nt!KdDebuggerEnabled (83f9d96c)],0
83eb4089 7412            je      nt!KeUpdateRunTime+0x164 (83eb409d)
83eb408b a1740ff783      mov     eax,dword ptr [nt!KiPollSlot (83f70f74)]
83eb4090 3b86cc030000    cmp     eax,dword ptr [esi+3CCh]
83eb4096 7505            jne     nt!KeUpdateRunTime+0x164 (83eb409d)
83eb4098 e80c000000      call    nt!KdCheckForDebugBreak (83eb40a9)
83eb409d 5f              pop     edi
如果kdDebuggerEnabled为0则跳到83eb409d,这样就不会执行KdCheckForDebugBreak函数了,但是流程中KdCheckForDebugBreak函数必须要执行此函数后,ctrl+break才能断下,所以证明了kdDebuggerEnabled设置为0直接导致ctrl+break的失效,以此类推对余下的两个函数进行反汇编,发现如下代码:

代码:
1: kd> uf KdCheckForDebugBreak
83eb40a9 803d271df68300  cmp     byte ptr [nt!KdPitchDebugger (83f61d27)],0
83eb40b0 7519            jne     nt!KdCheckForDebugBreak+0x22 (83eb40cb)
83eb40b2 803d6cd9f98300  cmp     byte ptr [nt!KdDebuggerEnabled (83f9d96c)],0
83eb40b9 7410            je      nt!KdCheckForDebugBreak+0x22 (83eb40cb)
83eb40bb e81f000000      call    nt!KdPollBreakIn (83eb40df)
83eb40c0 84c0            test    al,al
83eb40c2 7407            je      nt!KdCheckForDebugBreak+0x22 (83eb40cb)
83eb40c4 6a01            push    1
83eb40c6 e801000000      call    nt!DbgBreakPointWithStatus (83eb40cc)
83eb40cb c3              ret
可见此函数也用到了kdDebuggerEnabled变量,除此之外还可以发现2个问题:

1.  除了kdDebuggerEnabled变量,貌似KdPitchDebugger变量也比较重要,百度以下发现: 如果KdPitchDebugger为TRUE,即在bcd中指定了nodebug,这样也就不支持调试了,所以也算一个内核调试是否启用的标志,

2.  流程中KdCheckForDebugBreak调用的是RtlpBreakWithStatusInstruction,但是代码中却没有发现他的调用,唯一调用的两个函数是KdPollBreakIn和DbgBreakPointWithStatus,抱着试一试的心态反汇编这两个函数发现:
代码:
1: kd> u DbgBreakPointWithStatus
nt!DbgBreakPointWithStatus:
83eb40cc 8b442404        mov     eax,dword ptr [esp+4]
nt!RtlpBreakWithStatusInstruction:
83eb40d0 cc              int     3
83eb40d1 c20400          ret     4
发现RtlpBreakWithStatusInstruction其实是DbgBreakPointWithStatus的一个标签,所以流程中KdCheckForDebugBreak实际上是调用的DbgBreakPointWithStatus
由于KdCheckForDebugBreak中有对KdPollBreakIn的调用,所以顺道反汇编以下KdPollBreakIn函数,偶然也发现一处和kdDebuggerEnabled相关的代码:
代码:
83eb40fa 381d6cd9f983    cmp     byte ptr [nt!KdDebuggerEnabled (83f9d96c)],bl

总结一下上面的发现: 当把kdDebuggerEnabled设置为0后,ctrl+break失效,跟踪流程发现kdDebuggerEnabled和KdPitchDebugger标志直接影响中断到windbg,反汇编流程中的函数发现:
包含KdDebuggerEnabled的函数有:
KeUpdateRunTime
KdCheckForDebugBreak
KdPollBreakIn


包含KdPitchDebugger的函数有:
KdCheckForDebugBreak
KdPollBreakIn


接下来就是编程解决问题,思路是转移变量,把这些函数中的KdDebuggerEnabled和KdPitchDebugger变量设置为自己驱动模块中的全局变量,部分代码如下:

代码:
//KdDebuggerEnabled变量存储
BOOLEAN gKdDebuggerEnabled=TRUE;

//KdPitchDebugger变量存储
BOOLEAN gKdPitchDebugger=FALSE;

//转移win7中的KdDebuggerEnabled和KdPitchDebugger变量
void MoveVariable_Win7(IN BOOL b***)
{
    ULONG ulAddr, ulAddr2;

    //----------------------------改写KeUpdateRunTime中的KdDebuggerEnabled 

    //得到原内核的KeUpdateRunTime地址
    ulAddr=GetOriginalProcAddr(L"KeUpdateRunTime");

    //定位KdDebuggerEnabled
    //特征码
    //83ec9082 803d6c29fb8300  cmp     byte ptr [nt!KdDebuggerEnabled (83fb296c)],0
    //83ec9089 7412            je      nt!KeUpdateRunTime+0x164 (83ec909d)
    //83ec908b a1745ff883      mov     eax,dword ptr [nt!KiPollSlot (83f85f74)]
    UCHAR szSig1[10] = {0x80, 0x3d, '?', '?', '?', '?', '?', 0x74, 0x12, 0xa1};

    //特征码定位KdDebuggerEnabled
    ulAddr=SearchCode((PUCHAR)ulAddr, szSig1, 10, 0x200);
    ulAddr+=2;

    //改写KdDebuggerEnabled变量
    PageProtect(FALSE);
    *(PULONG)ulAddr=(ULONG)&gKdDebuggerEnabled;
    PageProtect(TRUE);


    //----------------------------改写KdCheckForDebugBreak中的KdDebuggerEnabled

    //得到KdCheckForDebugBreak地址
    //特征码
    //83ec9098 e80c000000      call    nt!KdCheckForDebugBreak (83ec90a9)
    //83ec909d 5f              pop     edi
    UCHAR szSig2[6] = {0xe8, '?', '?', '?', '?', 0x5f};
    ulAddr=SearchCode((PUCHAR)ulAddr, szSig2, 6, 0x100);
    ulAddr=ulAddr+*(PULONG)((PUCHAR)ulAddr+1)+5;

    //记录KdCheckForDebugBreak函数地址 用于KdPitchDebugger变量的处理
    ulAddr2=ulAddr;

    //特征码定位KdDebuggerEnabled
    //83ec90b2 803d6c29fb8300  cmp     byte ptr [nt!KdDebuggerEnabled (83fb296c)],0
    //83ec90b9 7410            je      nt!KdCheckForDebugBreak+0x22 (83ec90cb)
    //83ec90bb e81f000000      call    nt!KdPollBreakIn (83ec90df)
    UCHAR szSig3[10] = {0x80, 0x3d, '?', '?', '?', '?', '?', 0x74, 0x10, 0xe8};
    ulAddr=SearchCode((PUCHAR)ulAddr, szSig3, 10, 0x100);
    ulAddr+=2;

    //改写KdDebuggerEnabled变量
    PageProtect(FALSE);
    *(PULONG)ulAddr=(ULONG)&gKdDebuggerEnabled;
    PageProtect(TRUE);


    //----------------------------改写KdCheckForDebugBreak中的KdPitchDebugger

    //定位KdPitchDebugger变量
    //由于KdPitchDebugger变量在函数开头偏移2处 所以直接偏移定位KdPitchDebugger
    //83eb30a9 803d270df68300  cmp     byte ptr [nt!KdPitchDebugger (83f60d27)],0
    //83eb30b0 7519            jne     nt!KdCheckForDebugBreak+0x22 (83eb30cb)
    ulAddr2+=2;

    //改写KdPitchDebugger变量
    PageProtect(FALSE);
    *(PULONG)ulAddr2=(ULONG)&gKdPitchDebugger;
    PageProtect(TRUE);


    //----------------------------改写KdPollBreakIn中的KdDebuggerEnabled

    //得到KdPollBreakIn地址
    //特征码
    //83ebe0bb e81f000000      call    nt!KdPollBreakIn (83ebe0df)
    //83ebe0c0 84c0            test    al,al
    UCHAR szSig4[7] = {0xe8, '?', '?', '?', '?', 0x84, 0xc0};
    ulAddr=SearchCode((PUCHAR)ulAddr, szSig4, 7, 0x100);
    ulAddr=ulAddr+*(PULONG)((PUCHAR)ulAddr+1)+5;

    //记录KdPollBreakIn函数地址 用于KdPitchDebugger变量的处理
    ulAddr2=ulAddr;

    //特征码定位KdDebuggerEnabled
    //83ebe0f7 885dff          mov     byte ptr [ebp-1],bl
    //83ebe0fa 381d6c79fa83    cmp     byte ptr [nt!KdDebuggerEnabled (83fa796c)],bl
    //83ebe100 0f84c0000000    je      nt!KdPollBreakIn+0xe7 (83ebe1c6)
    UCHAR szSig5[11] = {0x88, 0x5d, 0xff, 0x38, 0x1d, '?', '?', '?', '?', 0x0f, 0x84};
    ulAddr=SearchCode((PUCHAR)ulAddr, szSig5, 11, 0x100);
    ulAddr+=5;

    //改写KdDebuggerEnabled变量
    PageProtect(FALSE);
    *(PULONG)ulAddr=(ULONG)&gKdDebuggerEnabled;
    PageProtect(TRUE);


    //----------------------------改写KdPollBreakIn中的KdPitchDebugger

    //特征码定位KdPitchDebugger
    //83eb30e6 33db            xor     ebx,ebx
    //83eb30e8 381d270df683    cmp     byte ptr [nt!KdPitchDebugger (83f60d27)],bl
    //83eb30ee 7407            je      nt!KdPollBreakIn+0x18 (83eb30f7)
    UCHAR szSig6[10] = {0x33, 0xdb, 0x38, 0x1d, '?', '?', '?', '?', 0x74, 0x07};
    ulAddr2=SearchCode((PUCHAR)ulAddr2, szSig6, 10, 0x100);
    ulAddr2+=4;

    //改写KdPitchDebugger变量
    PageProtect(FALSE);
    *(PULONG)ulAddr2=(ULONG)&gKdPitchDebugger;
    PageProtect(TRUE);

}
经过以上转移以后 再次清零KdDebuggerEnabled变量后,也可以ctrl+break断点到windbg中,第一块问题就基本解决了



第二块:
正常情况下观察KiDebugRoutine
代码:
1: kd> dd KiDebugRoutine
83fa2b20  841664f2 83f16683 00000000 0311870a
83fa2b30  00000bb8 00000011 5385d2ba d717548f
83fa2b40  83eb7d5c 00000000 00000191 83eb83a4
83fa2b50  986fa000 00000000 00000339 986fb02c
83fa2b60  00000100 00000000 00000000 83fa2b68
83fa2b70  00000340 00000340 00000007 00000000
83fa2b80  86cdb6b8 86cdb5f0 86ce3838 86ce39c8
83fa2b90  86ce3900 00000000 86d37040 00000000
1: kd> u 841664f2 
nt!KdpTrap:
841664f2 8bff            mov     edi,edi
841664f4 55              push    ebp
841664f5 8bec            mov     ebp,esp
841664f7 51              push    ecx
841664f8 51              push    ecx
841664f9 8b4510          mov     eax,dword ptr [ebp+10h]
841664fc 33d2            xor     edx,edx
841664fe 813803000080    cmp     dword ptr [eax],80000003h
将其设置为KdpStub函数
代码:
1: kd> ed KiDebugRoutine KdpStub
1: kd> dd KiDebugRoutine
83fa2b20  83f16983 83f16683 00000000 0311870a
83fa2b30  00000bb8 00000011 5385d2ba d717548f
83fa2b40  83eb7d5c 00000000 00000191 83eb83a4
83fa2b50  986fa000 00000000 00000339 986fb02c
83fa2b60  00000100 00000000 00000000 83fa2b68
83fa2b70  00000340 00000340 00000007 00000000
83fa2b80  86cdb6b8 86cdb5f0 86ce3838 86ce39c8
83fa2b90  86ce3900 00000000 86d37040 00000000
1: kd> u 83f16983 
nt!KdpStub:
83f16983 8bff            mov     edi,edi
83f16985 55              push    ebp
83f16986 8bec            mov     ebp,esp
83f16988 53              push    ebx
83f16989 56              push    esi
83f1698a 8b7510          mov     esi,dword ptr [ebp+10h]
83f1698d 33db            xor     ebx,ebx
83f1698f 813e03000080    cmp     dword ptr [esi],80000003h
Windbg F5运行起来,接着ctrl+break发现VM死机了,看来这个影响还蛮大的,重启后IDA打开内核搜索全部KiDebugRoutine,发现调用它的代码全部都在KiDispatchException中,反汇编KiDispatchException搜索KiDebugRoutine相关代码,过程和第一块一样,发现以下三个结果:
代码:
83f04027 ff15208bfb83    call    dword ptr [nt!KiDebugRoutine (83fb8b20)]
83f0404f ff15208bfb83    call    dword ptr [nt!KiDebugRoutine (83fb8b20)]
83f040c8 ff15208bfb83    call    dword ptr [nt!KiDebugRoutine (83fb8b20)]
看来先前修改了KiDebugRoutine,造成的死机就是在KiDispatchException中出现的问题,现在编程解决问题,思路和上一块一样,转移KiDebugRoutine变量,部分代码如下:

代码:
//KiDebugRoutine变量存储
ULONG gKiDebugRoutine=0;

//转移win7中的KiDebugRoutine变量
void MoveKiDebugRoutine_Win7(IN BOOL b***)
{
    //得到KdpTrap地址
    ULONG ulAddr=GetOriginalProcAddr(L"KdpTrap");

    //设置自定义的KiDebugRoutine变量
    gKiDebugRoutine=ulAddr;

    //得到原内核KiDispatchException地址
    ulAddr=GetOriginalProcAddr(L"KiDispatchException");

    //---------------------------------------第一处KiDebugRoutine
    //特征码定位KiDebugRoutine
    //83eb5027 ff15209bf683    call    dword ptr [nt!KiDebugRoutine (83f69b20)]
    //83eb502d 84c0            test    al,al
    UCHAR szSig[8] = {0xff, 0x15, '?', '?', '?', '?', 0x84, 0xc0};
    ulAddr=SearchCode((PUCHAR)ulAddr, szSig, 8, 0x200);
    if(!ulAddr)
    {
      KdPrint(("\n[MoveKiDebugRoutine_Win7]: The signature is not found in KiDispatchException"));
      return;
    }
    ulAddr+=2; 

    //改写KiDebugRoutine变量
    PageProtect(FALSE);
    *(PULONG)ulAddr=(ULONG)&gKiDebugRoutine;
    PageProtect(TRUE);


    //---------------------------------------第二处KiDebugRoutine
    //特征码与前边一样
    ulAddr=SearchCode((PUCHAR)ulAddr, szSig, 8, 0x200);
    ulAddr+=2; 
    //改写KiDebugRoutine变量
    PageProtect(FALSE);
    *(PULONG)ulAddr=(ULONG)&gKiDebugRoutine;
    PageProtect(TRUE);


    //---------------------------------------第三处KiDebugRoutine
    //特征码与前边一样
    ulAddr=SearchCode((PUCHAR)ulAddr, szSig, 8, 0x200);
    ulAddr+=2; 

    //改写KiDebugRoutine变量
    PageProtect(FALSE);
    *(PULONG)ulAddr=(ULONG)&gKiDebugRoutine;
    PageProtect(TRUE);
}
这样一来第二块问题也基本解决了,这个和第一块原理一样,接下来就是第三块了



第三块:
反汇编KdSendPacket 
代码:
1: kd> u KdSendPacket
nt!KdSendPacket:
83e60200 ff25cc01e583    jmp     dword ptr [nt!_imp__KdSendPacket (83e501cc)]
83e60206 90              nop
83e60207 90              nop
83e60208 90              nop
83e60209 90              nop
83e6020a 90              nop
1: kd> dd 83e501cc
83e501cc  80bc757c 80bc7170 80bc7160 80bc709c
83e501dc  80bc7150 00000000 8c8e3a04 8c8b5cb8
83e501ec  8c8b35bc 8c8e485c 8c8b5db0 8c8e37e0
83e501fc  8c8e7db2 8c8e4b4a 8c8e7df2 8c8e7538
83e5020c  8c8e4fd2 8c8c19d0 8c8b3586 8c8e2b44
83e5021c  8c8e4468 8c8e4a40 8c8e3c96 8c8e2f5e
83e5022c  8c8e2894 8c8c19d8 8c8e6e3e 8c8bde0c
83e5023c  8c8e810a 8c8e416a 8c8b5cee 8c8e7fc0
1: kd> dd 83e501c8
83e501c8  80bc7300 80bc757c 80bc7170 80bc7160
1: kd> u 80bc757c 
80bc757c 8bff            mov     edi,edi
80bc757e 55              push    ebp
80bc757f 8bec            mov     ebp,esp
80bc7581 8b4510          mov     eax,dword ptr [ebp+10h]
80bc7584 83ec10          sub     esp,10h
80bc7587 53              push    ebx
80bc7588 33db            xor     ebx,ebx
80bc758a 56              push    esi
可以发现其他call KdSendPacket其实是通过jmp访问了83e501cc地址里边的80bc757c,这里的83e501cc也就是iat导入表中的地址,KdSendPacket函数属于kdcom.dll,内核文件是引入的这个dll,IAT HOOK也就是把83e501cc地址里边的80bc757c修改成了自己的函数地址,80bc757c本来是真正的KdSendPacket函数地址,KdReceivePacket函数原理也是一样,解决的思路是把call KdSendPacket的代码修改为call 80bc757c这样就不经过iat表了,当然此方法牵涉的函数量比较大,部分代码实现如下:

代码:
//转移KdSendPacket和KdReceivePacket相关的信息记录
typedef struct _CODE_SIGN_KD
{
  UCHAR szFunSig[30];      //函数特征码
  ULONG ulSigLen;        //特征码长度
  ULONG ulOffset;        //关键指令相对特征码的偏移
  PCHAR pFunName;        //函数名称
} CODE_SIGN_KD;


//需要转移的KdSendPacket相关的函数信息
CODE_SIGN_KD gKdSendPacketInfo_win7[]={
  {{0x6A, 0x02, 0xC7, 0x45, 0xC0, 0x46, 0x31, 0, 0, 0xE8, '?', '?', '?', '?', 0xE9, '?', '?', '?', '?', 0x6A, 0x38}, 21, 9, "1.KdpSendWaitContinue"},
  {{0x50, 0x56, 0xE8, '?', '?', '?', '?', 0xE9, '?', '?', '?', '?', 0x53}, 13, 2, "2.KdpSendWaitContinue"},
  {{0xFF, 0x75, 0x98, 0x6A, 0x07, 0xE8, '?', '?', '?', '?', 0x80, 0x3D, '?', '?', '?', '?', 0x00, 0x0F, 0x84, 0x66, 0xF9, 0xFF, 0xFF}, 23, 5, "3.KdpSendWaitContinue"},
  {{0x66, 0x89, 0x74, 0x24, 0x20, 0x89, 0x7C, 0x24, 0x24, 0xE8, '?', '?', '?', '?', 0xE8, '?', '?', '?', '?', 0x5F}, 20, 9, "4.KdpPrintString"},
  {{0x51, 0x8D, 0x45, 0xF8, 0x50, 0x6A, 0x02, 0xE8, '?', '?', '?', '?', 0x5F, 0x5E}, 14, 7, "5.KdGetInternalBreakpoint"},
  {{0x53, 0x8D, 0x45, 0xF4, 0x50, 0x6A, 0x02, 0xE8, '?', '?', '?', '?', 0x5F, 0x5E}, 14, 7, "6.KdpGetContext"},
  {{0x50, 0x6A, 0x02, 0xE8, '?', '?', '?', '?', 0x5B, 0xC9}, 10, 3, "7.KdpSetContext"},
  {{0x50, 0x6A, 0x02, 0xE8, '?', '?', '?', '?', 0xC9, 0xC3}, 10, 3, "8.KdpReadPhysicalMemory"},
  {{0x50, 0x6A, 0x02, 0xE8, '?', '?', '?', '?', 0x8B, 0x46, 0x14, 0xEB, 0x1B}, 13, 3, "9.KdpWriteBreakPointEx"},
  {{0x6A, 0x02, 0xC7, 0x46, 0x08, 0x01, 0x00, 0x00, 0xC0, 0xE8, '?', '?', '?', '?', 0x8B, 0x46, 0x08, 0x5F}, 18, 9, "10.KdpWriteBreakPointEx"},
  {{0x50, 0x6A, 0x02, 0xE8, '?', '?', '?', '?', 0x5F, 0x5B}, 10, 3, "11.KdpRestoreBreakPointEx"},
  {{0x6A, 0x02, 0x89, 0x7D, 0xD0, 0xE8, '?', '?', '?', '?', 0x5E, 0x5B}, 12, 5, "12.KdpSearchMemory"},
  {{0x89, 0x4E, 0x08, 0x89, 0x75, 0xDC, 0xE8, '?', '?', '?', '?', 0x5F, 0x5B}, 13, 6, "13.KdpFillMemory"},
  {{0x89, 0x4C, 0x24, 0x24, 0x89, 0x74, 0x24, 0x1C, 0xE8, '?', '?', '?', '?', 0x5E, 0x8B, 0xE5}, 16, 8, "14.KdpSendTraceData"},
  {{0x89, 0x4C, 0x24, 0x2C, 0x89, 0x74, 0x24, 0x24, 0xE8, '?', '?', '?', '?', 0x6A, 0x10, 0x58}, 16, 8, "15.KdpPromptString"},
  {{0x50, 0x6A, 0x0B, 0xE8, '?', '?', '?', '?', 0x80, 0x3D, '?', '?', '?', '?', 0x00, 0x0F, 0x84, 0x79, 0xFF, 0xFF, 0xFF}, 21, 3, "16.KdpCreateRemoteFile"},
  {{0x50, 0x6A, 0x0B, 0xE8, '?', '?', '?', '?', 0x38, 0x1D, '?', '?', '?', '?', 0x75, 0x49}, 16, 3, "17.KdpReadRemoteFile"},
  {{0x50, 0x6A, 0x0B, 0xE8, '?', '?', '?', '?', 0x38, 0x1D, '?', '?', '?', '?', 0x74, 0xAD}, 16, 3, "18.KdpCloseRemoteFile"},
  {0}
};  


//需要转移的KdReceivePacket相关的函数信息
CODE_SIGN_KD gKdReceivePacketInfo_win7[]={
  {{0x50, 0x6A, 0x08, 0xE8, '?', '?', '?', '?', 0x85, 0xC0, 0x75, 0x02, 0xB3, 0x01}, 14, 3, "1.KdpPollBreakInWithPortLock"},
  {{0x53, 0x6A, 0x08, 0xE8, '?', '?', '?', '?', 0x85, 0xC0, 0x75, 0x0B, 0xC6, 0x45, 0xFF, 0x01}, 16, 3, "2.KdPollBreakIn"},
  {{0x5E, 0x56, 0xE8, 0x05, 0xE8, '?', '?', '?', '?', 0x0F, 0x84, 0x5E, 0x06, 0x00, 0x00, 0x83, 0xF8, 0x01}, 18, 2, "3.KdpSendWaitContinue"},
  {{0x50, 0x6A, 0x03, 0xE8, '?', '?', '?', '?', 0x83, 0xF8, 0x02, 0x74, 0x3A, 0x85, 0xC0}, 15, 3, "4.KdpPromptString"},
  {{0x8D, 0x45, 0xE4, 0x50, 0x6A, 0x0B, 0xE8, '?', '?', '?', '?', 0x83, 0xF8, 0x01, 0x74, 0xE7, 0x85, 0xC0}, 18, 6, "5.KdpCreateRemoteFile"},
  {{0x50, 0x6A, 0x0B, 0xE8, '?', '?', '?', '?', 0x83, 0xF8, 0x01, 0x74, 0xE7, 0x3B, 0xC3, 0x75, 0x16}, 17, 3, "6.KdpReadRemoteFile"},
  {{0x50, 0x6A, 0x0B, 0xE8, '?', '?', '?', '?', 0x83, 0xF8, 0x01, 0x74, 0xE7, 0x3B, 0xC3, 0x74, 0x2D}, 17, 3, "7.KdpCloseRemoteFile"},
  {0}
};  


//HOOK KdSendPacket相关函数
void HookKdSendPacket(IN BOOL b***)
{
    //得到KdSendPacket函数地址 存在于kdcom模块
    ULONG ulKdSendPacketAddr = GetOriginalProcAddr(L"KdSendPacket");

    //得到内核中PAGEKD节区的内存基地址和大小
    ULONG ulBase, ulSize, ulCode;
    GetPeSectionInfo(GetVarKernelBase(), "PAGEKD", &ulBase, &ulSize);

    //循环特征码 转移KdSendPacket函数调用
    for(int i=0; gKdSendPacketInfo_win7[i].ulOffset; i++)
    {
      //得到特征码地址
      ulCode=SearchCode((PUCHAR)ulBase, gKdSendPacketInfo_win7[i].szFunSig, gKdSendPacketInfo_win7[i].ulSigLen, ulSize);
      if(ulCode==0)
      {
        KdPrint(("\n[HookKdSendPacket]: Error! FunName:%s, codeAddr:0x%x, i:%d\n ", gKdSendPacketInfo_win7[i].pFunName, ulCode, i));
        return;
      }

      //得到修改点地址
      ulCode+=gKdSendPacketInfo_win7[i].ulOffset;
      //设置CALL指令
      ReplaceProcAddr(ulKdSendPacketAddr, ulCode, FALSE);
    }
}

//HOOK KdReceivePacket相关函数
void HookKdReceivePacket(IN BOOL b***)
{
    //得到KdReceivePacket函数地址 存在于kdcom模块
    ULONG ulKdReceivePacketAddr = GetOriginalProcAddr(L"KdReceivePacket");

    //得到内核中PAGEKD节区和.text节区的内存基地址和大小
    ULONG ulBase, ulSize, ulBase2, ulSize2, ulCode;
    GetPeSectionInfo(GetVarKernelBase(), "PAGEKD", &ulBase, &ulSize);
    GetPeSectionInfo(GetVarKernelBase(), ".text", &ulBase2, &ulSize2);


    //循环特征码 转移KdReceivePacket函数调用
    for(int i=0; gKdReceivePacketInfo_win7[i].ulOffset; i++)
    {
      ASSERT(arraysize(gKdReceivePacketInfo_win7)>i);

      //得到特征码地址
      ulCode=SearchCode((PUCHAR)ulBase, gKdReceivePacketInfo_win7[i].szFunSig, gKdReceivePacketInfo_win7[i].ulSigLen, ulSize);
      if(ulCode==0)
      {
        ulCode=SearchCode((PUCHAR)ulBase2, gKdReceivePacketInfo_win7[i].szFunSig, gKdReceivePacketInfo_win7[i].ulSigLen, ulSize2);
        if(ulCode==0)
        {
          KdPrint(("\n[HookKdReceivePacket]: Error! FunName:%s, codeAddr:0x%x, i:%d\n ", gKdReceivePacketInfo_win7[i].pFunName, ulCode, i));
          return;
        }  
      }

      //得到修改点地址
      ulCode+=gKdReceivePacketInfo_win7[i].ulOffset;

      //保存修改点
      gKdReceivePacketSave[i].ulFixFunAddr=ulCode;
      //保存修改代码
      RtlCopyMemory((PUCHAR)gKdReceivePacketSave[i].szFunCode, (PUCHAR)ulCode, 5);

      //设置CALL指令
      ReplaceProcAddr(ulKdReceivePacketAddr, ulCode, FALSE);
    }
}
经过以上的替换基本就可以完成第三块的功能了,需要说明的是,上面的函数特征码是通过IDA的全部搜索功能完成的,后经尝试并不需要替换全部的函数,这个可以自行实验删减



继续升级:
上面三块经编译测试发现已经可以ctrl+break断在windbg中了,如图:

 
但是用PCHunter观察内核修改,发现修改的地方全部显示出来了,多且乱,这样一来很容易被封堵掉,具体如图:

 
这里显示的是47个修改点,再对比下正常情况下的内核修改

 
只有17个修改点,可见动的手脚太大了,所以需要进一步升级程序,目标是是尽可能减少原内核的修改,思路是加载一个新内核,把调试相关的流程引入到新内核中,然后在新内核中进行以上修改,这样一来基本满足先前的目标了.具体原理如下:

   第一点:
  在双机调试win7时,在windbg中用ctrl+break中断到windbg后,栈回溯有以下结果:
  
代码:
00 83f71a80 83ec50cb nt!RtlpBreakWithStatusInstruction
  01 83f71a88 83ec509d nt!KdCheckForDebugBreak+0x22
  02 83f71ab8 83ec4f2b nt!KeUpdateRunTime+0x164
  03 83f71b14 83ec9c17 nt!KeUpdateSystemTime+0x613
  或者
  00 807eaae0 83e950cb nt!RtlpBreakWithStatusInstruction
  01 807eaae8 83e9509d nt!KdCheckForDebugBreak+0x22
  02 807eab18 8422f430 nt!KeUpdateRunTime+0x164
  03 807eab18 937d05d6 hal!HalpClockInterruptPn+0x158
  可以发现各个流程都会经过KeUpdateRunTime,这里HOOK这个函数,让流程进入新内核

   第二点:
  调试的符号加载过程是通过KdpSendWaitContinue来发送消息的 这个函数的调用来源于 
  KdpReportLoadSymbolsStateChange和KdpReportCommandStringStateChange 栈回溯如下:
  
代码:
00 9051b21c 8412f4ca nt!KdpReportLoadSymbolsStateChange+0x2
  01 9051b240 8412f570 nt!KdpSymbol+0x53
  02 9051b270 83eb702d nt!KdpTrap+0x7e
  03 9051b80c 83e40e06 nt!KiDispatchException+0x16d
  04 9051b874 83e416a8 nt!CommonDispatchException+0x4a
  05 9051b874 83e1a579 nt!KiTrap03+0xb8
  06 9051b900 83e1b3e3 nt!DbgLoadImageSymbols+0x48
  07 9051b91c 83fcaa5d nt!DbgLoadImageSymbolsUnicode+0x23
  08 9051b958 83fc746d nt!MiDriverLoadSucceeded+0x183
  09 9051b9dc 84051b1a nt!MmLoadSystemImage+0x720
  0a 9051bb28 83e4021a nt!NtSetSystemInformation+0x967
  0b 9051bb28 83e3f2dd nt!KiFastCallEntry+0x12a
  0c 9051bbec 840164dc nt!ZwSetSystemInformation+0x11
  0d 9051bc00 83e7fa6b nt!IopProcessWorkItem+0x23
  0e 9051bc50 8400afda nt!ExpWorkerThread+0x10d
  0f 9051bc90 83eb31f9 nt!PspSystemThreadStartup+0x9e
  10 00000000 00000000 nt!KiThreadStartup+0x19
  这是一个新线程来执行的,所以必须在其中找一个函数来HOOK,让其进入新内核,这样才便于修改,
  暂时定为HOOK KiDispatchException函数让其转入新内核

   第三点:
  KdpSendWaitContinue函数中有个switch的代码,新内核KdpSendWaitContinue中switch反汇编如下:
  
代码:
8907e9ea ff2485b4a01384  jmp     dword ptr nt!KdpSendWaitContinue+0x770 (841480b4)[eax*4]
  8907e9ea是指令地址,跳向的地方是841480b4,它也是switch的跳转表了 内容如下:
  
代码:
841480b4  841479f1 84147a4e 84147b12 84147b27
  841480c4  84147b3b 84147b77 84148052 84147bb3
  841480d4  84147c08 84147c52 84147c8f 84147e6d
  841480e4  8414805c 84147aac 84147abe 84148011
  841480f4  84147e7a 84147ed5 84147ee0 84147eed
  84148104  84147cc8 84147d0c 84147f05 84147f66
  84148114  84147f81 84148072 84148011 84148011
  84148124  84148011 84148011 84148011 84148011
  84148134  84148087 84147f46 84147d43 84147d87
  84148144  84148011 84148011 84147f92 84147dcb
  84148154  84147e2a 84147a82 84147efa 84147fa2
  84148164  84147fb2 90909090 55ff8b90 5351ec8b
  查看原内核地址:
  start    end        module name
  83e1b000 8422d000   nt         (pdb symbols) 
  可以发现switch中的跳转是到原内核,如果执行了switch跳转指令,流程又会回到原内核,所以跳转表需要相应修改

   第四点:
  关于int3断点和调试信息打印KdPrint都是要经过KiDispatchException的,前边第二点修改以后这里也就OK了


   总结:
  调试有3个流程,
  一个基本的流程通过HOOK KeUpdateRunTime搞定
  一个加载符号的流程,通过HOOK KiDispatchException搞定
  一个int3断点和调试信息打印,也是通过HOOK KiDispatchException搞定
  当整个流程都在新内核中以后 就可以任意的修改新内核了 不容易被检测到 一劳永逸

具体代码见附件,代码只是代表思路,里边有一些自定义函数,除加载新内核外,其他功能比较单一,如有需要自行实现,加载新内核的代码可以搜索论坛得到,升级后的程序,运行后ctrl+break断到windbg中,查看栈回溯可以看到:
代码:
Break instruction exception - code 80000003 (first chance)
885ee0d0 cc              int     3
0: kd> kn
 # ChildEBP RetAddr  
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 83f37ab8 83e8af2b 0x885ee0d0
01 83f37b14 83e8fc17 nt!KeUpdateSystemTime+0x613
02 83f37b14 936015d6 nt!KeUpdateSystemTimeAssist+0x13
03 83f37b98 83e9037f intelppm!C1Halt+0x4
04 83f37c20 83e87e0d nt!PoIdle+0x524
05 83f37c24 00000000 nt!KiIdleLoop+0xd
 
的确是进入新内核了,再程序运行前后修改点的变化
运行前:

 
运行后:

 

这里增加了2个修改点,就正是为了实现进入新内核而HOOK的两个函数,至此就升级完毕了.



后记:
从这个双机调试的探索可以看出来,其实这就是内核调试的一部分基本原理,在张老师的<软件调试>一书中全部都有,但是现在*P只是用了一部分来做修改,以后还有很大升级空间,所以跟着知识走才是王道,有空之余完全可以把<软件调试>上的内核调试知识,写入代码中,这样一来就不怕其他程序升级造成影响了,如果后期这两个函数被检测,可以根据栈回溯上下来换成其他函数, ,目的只有一个进入新内核,可换的函数从数量上来说还是可观的,此方法整体看来比较繁杂,但是后面用起来比较方便,不受至于其他程序,自己按照调试流程来就行,一劳永逸.

你可能感兴趣的:(对双机调试的探索)