看流星社区

 找回密码
 注册账号
查看: 2928|回复: 0

对双机调试的探索

[复制链接]

该用户从未签到

发表于 2017-6-1 17:26:09 | 显示全部楼层 |阅读模式
标 题:对双机调试的探索
作 者: 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函数的IATHOOK,且在HOOK的地方也做了检测,所以同样不能简单修改来达到目的
KdSendPacketKDCOM函数,发送数据包
KdReceivePacketKDCOM函数,接收数据包

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



第一块:
正常情况下ctrl+break中断到windbg,


代码:
1:kd>ddkdDebuggerEnabled
83f9d96c000000010000000000000000db1dbbbb
[/code]

发现此时kdDebuggerEnabled为1,

代码:
1:kd>edkdDebuggerEnabled0
1:kd>ddkdDebuggerEnabled
83f9d96c000000000000000000000000db1dbbbb
[/code]

修改后F5虚拟机跑起来,但是ctrl+break不能断下了
首先想到观察下ctrl+break的函数流程,看看这个变量对流程中的哪个地方造成了阻碍,
双机调试正常情况下ctrl+break中断到windbg栈回溯如下:

代码:
00807eaae083eb40cbnt!RtlpBreakWithStatusInstruction
01807eaae883eb409dnt!KdCheckForDebugBreak+0x22
02807eab9883eb937fnt!KeUpdateRunTime+0x164
03807eac2083eb0e0dnt!PoIdle+0x524
04807eac2400000000nt!KiIdleLoop+0xd
[/code]

或者

代码:
00807eaae083e950cbnt!RtlpBreakWithStatusInstruction
01807eaae883e9509dnt!KdCheckForDebugBreak+0x22
02807eab188422f430nt!KeUpdateRunTime+0x164
03807eab18937d05d6hal!HalpClockInterruptPn+0x158
...
[/code]

无论从哪里发起最后的几个函数流程都是一样KeUpdateRunTime->KdCheckForDebugBreak->RtlpBreakWithStatusInstruction
自然想看看这几个函数中的代码,看看是否和kdDebuggerEnabled有关,


代码:
1:kd>ufKeUpdateRunTime
后ctrl+F搜索kdDebuggerEnabled发现一处代码
83eb4082803d6cd9f98300cmpbyteptr[nt!KdDebuggerEnabled(83f9d96c)],0
83eb40897412jent!KeUpdateRunTime+0x164(83eb409d)
83eb408ba1740ff783moveax,dwordptr[nt!KiPollSlot(83f70f74)]
83eb40903b86cc030000cmpeax,dwordptr[esi+3CCh]
83eb40967505jnent!KeUpdateRunTime+0x164(83eb409d)
83eb4098e80c000000callnt!KdCheckForDebugBreak(83eb40a9)
83eb409d5fpopedi
[/code]

如果kdDebuggerEnabled为0则跳到83eb409d,这样就不会执行KdCheckForDebugBreak函数了,但是流程中KdCheckForDebugBreak函数必须要执行此函数后,ctrl+break才能断下,所以证明了kdDebuggerEnabled设置为0直接导致ctrl+break的失效,以此类推对余下的两个函数进行反汇编,发现如下代码:


代码:
1:kd>ufKdCheckForDebugBreak
83eb40a9803d271df68300cmpbyteptr[nt!KdPitchDebugger(83f61d27)],0
83eb40b07519jnent!KdCheckForDebugBreak+0x22(83eb40cb)
83eb40b2803d6cd9f98300cmpbyteptr[nt!KdDebuggerEnabled(83f9d96c)],0
83eb40b97410jent!KdCheckForDebugBreak+0x22(83eb40cb)
83eb40bbe81f000000callnt!KdPollBreakIn(83eb40df)
83eb40c084c0testal,al
83eb40c27407jent!KdCheckForDebugBreak+0x22(83eb40cb)
83eb40c46a01push1
83eb40c6e801000000callnt!DbgBreakPointWithStatus(83eb40cc)
83eb40cbc3ret
[/code]

可见此函数也用到了kdDebuggerEnabled变量,除此之外还可以发现2个问题:

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

2.流程中KdCheckForDebugBreak调用的是RtlpBreakWithStatusInstruction,但是代码中却没有发现他的调用,唯一调用的两个函数是KdPollBreakIn和DbgBreakPointWithStatus,抱着试一试的心态反汇编这两个函数发现:

代码:
1:kd>uDbgBreakPointWithStatus
nt!DbgBreakPointWithStatus:
83eb40cc8b442404moveax,dwordptr[esp+4]
nt!RtlpBreakWithStatusInstruction:
83eb40d0ccint3
83eb40d1c20400ret4
[/code]

发现RtlpBreakWithStatusInstruction其实是DbgBreakPointWithStatus的一个标签,所以流程中KdCheckForDebugBreak实际上是调用的DbgBreakPointWithStatus
由于KdCheckForDebugBreak中有对KdPollBreakIn的调用,所以顺道反汇编以下KdPollBreakIn函数,偶然也发现一处和kdDebuggerEnabled相关的代码:

代码:
83eb40fa381d6cd9f983cmpbyteptr[nt!KdDebuggerEnabled(83f9d96c)],bl
[/code]


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

包含KdPitchDebugger的函数有:
KdCheckForDebugBreak
KdPollBreakIn

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


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

//KdPitchDebugger变量存储
BOOLEANgKdPitchDebugger=FALSE;

//转移win7中的KdDebuggerEnabled和KdPitchDebugger变量
voidMoveVariable_Win7(INBOOLb***)
{
ULONGulAddr,ulAddr2;

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

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

//定位KdDebuggerEnabled
//特征码
//83ec9082803d6c29fb8300cmpbyteptr[nt!KdDebuggerEnabled(83fb296c)],0
//83ec90897412jent!KeUpdateRunTime+0x164(83ec909d)
//83ec908ba1745ff883moveax,dwordptr[nt!KiPollSlot(83f85f74)]
UCHARszSig1[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地址
//特征码
//83ec9098e80c000000callnt!KdCheckForDebugBreak(83ec90a9)
//83ec909d5fpopedi
UCHARszSig2[6]={0xe8,'?','?','?','?',0x5f};
ulAddr=SearchCode((PUCHAR)ulAddr,szSig2,6,0x100);
ulAddr=ulAddr+*(PULONG)((PUCHAR)ulAddr+1)+5;

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

//特征码定位KdDebuggerEnabled
//83ec90b2803d6c29fb8300cmpbyteptr[nt!KdDebuggerEnabled(83fb296c)],0
//83ec90b97410jent!KdCheckForDebugBreak+0x22(83ec90cb)
//83ec90bbe81f000000callnt!KdPollBreakIn(83ec90df)
UCHARszSig3[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
//83eb30a9803d270df68300cmpbyteptr[nt!KdPitchDebugger(83f60d27)],0
//83eb30b07519jnent!KdCheckForDebugBreak+0x22(83eb30cb)
ulAddr2+=2;

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


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

//得到KdPollBreakIn地址
//特征码
//83ebe0bbe81f000000callnt!KdPollBreakIn(83ebe0df)
//83ebe0c084c0testal,al
UCHARszSig4[7]={0xe8,'?','?','?','?',0x84,0xc0};
ulAddr=SearchCode((PUCHAR)ulAddr,szSig4,7,0x100);
ulAddr=ulAddr+*(PULONG)((PUCHAR)ulAddr+1)+5;

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

//特征码定位KdDebuggerEnabled
//83ebe0f7885dffmovbyteptr[ebp-1],bl
//83ebe0fa381d6c79fa83cmpbyteptr[nt!KdDebuggerEnabled(83fa796c)],bl
//83ebe1000f84c0000000jent!KdPollBreakIn+0xe7(83ebe1c6)
UCHARszSig5[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
//83eb30e633dbxorebx,ebx
//83eb30e8381d270df683cmpbyteptr[nt!KdPitchDebugger(83f60d27)],bl
//83eb30ee7407jent!KdPollBreakIn+0x18(83eb30f7)
UCHARszSig6[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);

}
[/code]

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



第二块:
正常情况下观察KiDebugRoutine

代码:
1:kd>ddKiDebugRoutine
83fa2b20841664f283f16683000000000311870a
83fa2b3000000bb8000000115385d2bad717548f
83fa2b4083eb7d5c000000000000019183eb83a4
83fa2b50986fa0000000000000000339986fb02c
83fa2b6000000100000000000000000083fa2b68
83fa2b7000000340000003400000000700000000
83fa2b8086cdb6b886cdb5f086ce383886ce39c8
83fa2b9086ce39000000000086d3704000000000
1:kd>u841664f2
nt!KdpTrap:
841664f28bffmovedi,edi
841664f455pushebp
841664f58becmovebp,esp
841664f751pushecx
841664f851pushecx
841664f98b4510moveax,dwordptr[ebp+10h]
841664fc33d2xoredx,edx
841664fe813803000080cmpdwordptr[eax],80000003h
[/code]

将其设置为KdpStub函数

代码:
1:kd>edKiDebugRoutineKdpStub
1:kd>ddKiDebugRoutine
83fa2b2083f1698383f16683000000000311870a
83fa2b3000000bb8000000115385d2bad717548f
83fa2b4083eb7d5c000000000000019183eb83a4
83fa2b50986fa0000000000000000339986fb02c
83fa2b6000000100000000000000000083fa2b68
83fa2b7000000340000003400000000700000000
83fa2b8086cdb6b886cdb5f086ce383886ce39c8
83fa2b9086ce39000000000086d3704000000000
1:kd>u83f16983
nt!KdpStub:
83f169838bffmovedi,edi
83f1698555pushebp
83f169868becmovebp,esp
83f1698853pushebx
83f1698956pushesi
83f1698a8b7510movesi,dwordptr[ebp+10h]
83f1698d33dbxorebx,ebx
83f1698f813e03000080cmpdwordptr[esi],80000003h
[/code]

WindbgF5运行起来,接着ctrl+break发现VM死机了,看来这个影响还蛮大的,重启后IDA打开内核搜索全部KiDebugRoutine,发现调用它的代码全部都在KiDispatchException中,反汇编KiDispatchException搜索KiDebugRoutine相关代码,过程和第一块一样,发现以下三个结果:

代码:
83f04027ff15208bfb83calldwordptr[nt!KiDebugRoutine(83fb8b20)]
83f0404fff15208bfb83calldwordptr[nt!KiDebugRoutine(83fb8b20)]
83f040c8ff15208bfb83calldwordptr[nt!KiDebugRoutine(83fb8b20)]
[/code]

看来先前修改了KiDebugRoutine,造成的死机就是在KiDispatchException中出现的问题,现在编程解决问题,思路和上一块一样,转移KiDebugRoutine变量,部分代码如下:


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

//转移win7中的KiDebugRoutine变量
voidMoveKiDebugRoutine_Win7(INBOOLb***)
{
//得到KdpTrap地址
ULONGulAddr=GetOriginalProcAddr(L"KdpTrap");

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

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

//---------------------------------------第一处KiDebugRoutine
//特征码定位KiDebugRoutine
//83eb5027ff15209bf683calldwordptr[nt!KiDebugRoutine(83f69b20)]
//83eb502d84c0testal,al
UCHARszSig[8]={0xff,0x15,'?','?','?','?',0x84,0xc0};
ulAddr=SearchCode((PUCHAR)ulAddr,szSig,8,0x200);
if(!ulAddr)
{
KdPrint(("\n[MoveKiDebugRoutine_Win7]:ThesignatureisnotfoundinKiDispatchException"));
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);
}
[/code]

这样一来第二块问题也基本解决了,这个和第一块原理一样,接下来就是第三块了



第三块:
反汇编KdSendPacket

代码:
1:kd>uKdSendPacket
nt!KdSendPacket:
83e60200ff25cc01e583jmpdwordptr[nt!_imp__KdSendPacket(83e501cc)]
83e6020690nop
83e6020790nop
83e6020890nop
83e6020990nop
83e6020a90nop
1:kd>dd83e501cc
83e501cc80bc757c80bc717080bc716080bc709c
83e501dc80bc7150000000008c8e3a048c8b5cb8
83e501ec8c8b35bc8c8e485c8c8b5db08c8e37e0
83e501fc8c8e7db28c8e4b4a8c8e7df28c8e7538
83e5020c8c8e4fd28c8c19d08c8b35868c8e2b44
83e5021c8c8e44688c8e4a408c8e3c968c8e2f5e
83e5022c8c8e28948c8c19d88c8e6e3e8c8bde0c
83e5023c8c8e810a8c8e416a8c8b5cee8c8e7fc0
1:kd>dd83e501c8
83e501c880bc730080bc757c80bc717080bc7160
1:kd>u80bc757c
80bc757c8bffmovedi,edi
80bc757e55pushebp
80bc757f8becmovebp,esp
80bc75818b4510moveax,dwordptr[ebp+10h]
80bc758483ec10subesp,10h
80bc758753pushebx
80bc758833dbxorebx,ebx
80bc758a56pushesi
[/code]

可以发现其他callKdSendPacket其实是通过jmp访问了83e501cc地址里边的80bc757c,这里的83e501cc也就是iat导入表中的地址,KdSendPacket函数属于kdcom.dll,内核文件是引入的这个dll,IATHOOK也就是把83e501cc地址里边的80bc757c修改成了自己的函数地址,80bc757c本来是真正的KdSendPacket函数地址,KdReceivePacket函数原理也是一样,解决的思路是把callKdSendPacket的代码修改为call80bc757c这样就不经过iat表了,当然此方法牵涉的函数量比较大,部分代码实现如下:


代码:
//转移KdSendPacket和KdReceivePacket相关的信息记录
typedefstruct_CODE_SIGN_KD
{
UCHARszFunSig[30];//函数特征码
ULONGulSigLen;//特征码长度
ULONGulOffset;//关键指令相对特征码的偏移
PCHARpFunName;//函数名称
}CODE_SIGN_KD;


//需要转移的KdSendPacket相关的函数信息
CODE_SIGN_KDgKdSendPacketInfo_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_KDgKdReceivePacketInfo_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}
};


//HOOKKdSendPacket相关函数
voidHookKdSendPacket(INBOOLb***)
{
//得到KdSendPacket函数地址存在于kdcom模块
ULONGulKdSendPacketAddr=GetOriginalProcAddr(L"KdSendPacket");

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

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

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

//HOOKKdReceivePacket相关函数
voidHookKdReceivePacket(INBOOLb***)
{
//得到KdReceivePacket函数地址存在于kdcom模块
ULONGulKdReceivePacketAddr=GetOriginalProcAddr(L"KdReceivePacket");

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


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

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

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

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

//设置CALL指令
ReplaceProcAddr(ulKdReceivePacketAddr,ulCode,FALSE);
}
}
[/code]

经过以上的替换基本就可以完成第三块的功能了,需要说明的是,上面的函数特征码是通过IDA的全部搜索功能完成的,后经尝试并不需要替换全部的函数,这个可以自行实验删减



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


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


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


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

第一点:
在双机调试win7时,在windbg中用ctrl+break中断到windbg后,栈回溯有以下结果:


代码:
0083f71a8083ec50cbnt!RtlpBreakWithStatusInstruction
0183f71a8883ec509dnt!KdCheckForDebugBreak+0x22
0283f71ab883ec4f2bnt!KeUpdateRunTime+0x164
0383f71b1483ec9c17nt!KeUpdateSystemTime+0x613
或者
00807eaae083e950cbnt!RtlpBreakWithStatusInstruction
01807eaae883e9509dnt!KdCheckForDebugBreak+0x22
02807eab188422f430nt!KeUpdateRunTime+0x164
03807eab18937d05d6hal!HalpClockInterruptPn+0x158
[/code]

可以发现各个流程都会经过KeUpdateRunTime,这里HOOK这个函数,让流程进入新内核

第二点:
调试的符号加载过程是通过KdpSendWaitContinue来发送消息的这个函数的调用来源于
KdpReportLoadSymbolsStateChange和KdpReportCommandStringStateChange栈回溯如下:


代码:
009051b21c8412f4cant!KdpReportLoadSymbolsStateChange+0x2
019051b2408412f570nt!KdpSymbol+0x53
029051b27083eb702dnt!KdpTrap+0x7e
039051b80c83e40e06nt!KiDispatchException+0x16d
049051b87483e416a8nt!CommonDispatchException+0x4a
059051b87483e1a579nt!KiTrap03+0xb8
069051b90083e1b3e3nt!DbgLoadImageSymbols+0x48
079051b91c83fcaa5dnt!DbgLoadImageSymbolsUnicode+0x23
089051b95883fc746dnt!MiDriverLoadSucceeded+0x183
099051b9dc84051b1ant!MmLoadSystemImage+0x720
0a9051bb2883e4021ant!NtSetSystemInformation+0x967
0b9051bb2883e3f2ddnt!KiFastCallEntry+0x12a
0c9051bbec840164dcnt!ZwSetSystemInformation+0x11
0d9051bc0083e7fa6bnt!IopProcessWorkItem+0x23
0e9051bc508400afdant!ExpWorkerThread+0x10d
0f9051bc9083eb31f9nt!PspSystemThreadStartup+0x9e
100000000000000000nt!KiThreadStartup+0x19
[/code]

这是一个新线程来执行的,所以必须在其中找一个函数来HOOK,让其进入新内核,这样才便于修改,
暂时定为HOOKKiDispatchException函数让其转入新内核

第三点:
KdpSendWaitContinue函数中有个switch的代码,新内核KdpSendWaitContinue中switch反汇编如下:


代码:
8907e9eaff2485b4a01384jmpdwordptrnt!KdpSendWaitContinue+0x770(841480b4)[eax*4]
[/code]

8907e9ea是指令地址,跳向的地方是841480b4,它也是switch的跳转表了内容如下:


代码:
841480b4841479f184147a4e84147b1284147b27
841480c484147b3b84147b778414805284147bb3
841480d484147c0884147c5284147c8f84147e6d
841480e48414805c84147aac84147abe84148011
841480f484147e7a84147ed584147ee084147eed
8414810484147cc884147d0c84147f0584147f66
8414811484147f81841480728414801184148011
8414812484148011841480118414801184148011
841481348414808784147f4684147d4384147d87
84148144841480118414801184147f9284147dcb
8414815484147e2a84147a8284147efa84147fa2
8414816484147fb29090909055ff8b905351ec8b
查看原内核地址:
startendmodulename
83e1b0008422d000nt(pdbsymbols)
[/code]

可以发现switch中的跳转是到原内核,如果执行了switch跳转指令,流程又会回到原内核,所以跳转表需要相应修改

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


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

具体代码见附件,代码只是代表思路,里边有一些自定义函数,除加载新内核外,其他功能比较单一,如有需要自行实现,加载新内核的代码可以搜索论坛得到,升级后的程序,运行后ctrl+break断到windbg中,查看栈回溯可以看到:

代码:
Breakinstructionexception-code80000003(firstchance)
885ee0d0ccint3
0:kd>kn
#ChildEBPRetAddr
WARNING:FrameIPnotinanyknownmodule.Followingframesmaybewrong.
0083f37ab883e8af2b0x885ee0d0
0183f37b1483e8fc17nt!KeUpdateSystemTime+0x613
0283f37b14936015d6nt!KeUpdateSystemTimeAssist+0x13
0383f37b9883e9037fintelppm!C1Halt+0x4
0483f37c2083e87e0dnt!PoIdle+0x524
0583f37c2400000000nt!KiIdleLoop+0xd
[/code]


的确是进入新内核了,再程序运行前后修改点的变化
运行前:


运行后:



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



后记:
从这个双机调试的探索可以看出来,其实这就是内核调试的一部分基本原理,在张老师的<软件调试>一书中全部都有,但是现在*P只是用了一部分来做修改,以后还有很大升级空间,所以跟着知识走才是王道,有空之余完全可以把<软件调试>上的内核调试知识,写入代码中,这样一来就不怕其他程序升级造成影响了,如果后期这两个函数被检测,可以根据栈回溯上下来换成其他函数,,目的只有一个进入新内核,可换的函数从数量上来说还是可观的,此方法整体看来比较繁杂,但是后面用起来比较方便,不受至于其他程序,自己按照调试流程来就行,一劳永逸.
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-3-19 10:42

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表