上篇文章Patch Intel int 3断点指令的功能中谈到利用int 3反调试方法,今天想更深入的谈谈关于int 3反调试的方法。
在上篇文章中的方法过于简单直接就返回了,这样容易被发现和恢复。我需要的是更加不容易被发现的方法。
我先把内核当中int 3的中断处理例程贴出来:
public _KiTrap03 _KiTrap03 proc push 0 ; push dummy error code ENTER_TRAP kit3_a, kit3_t cmp ds:_PoHiberInProgress, 0 jnz short kit03_01 lock inc ds:_KiHardwareTrigger ; trip hardware analyzer kit03_01: mov eax, BREAKPOINT_BREAK KiTrap03DebugService: ; ; If caller is user mode, we want interrupts back on. ; . all relevent state has already been saved ; . user mode code always runs with ints on ; ; If caller is kernel mode, we want them off! ; . some state still in registers, must prevent races ; . kernel mode code can run with ints off ; ; ; Arguments: ; eax - ServiceClass - which call is to be performed ; ecx - Arg1 - generic first argument ; edx - Arg2 - generic second argument test dword ptr [ebp]+TsEFlags,EFLAGS_V86_MASK jnz kit03_30 ; fault occured in V86 mode => Usermode test word ptr [ebp]+TsSegCs,MODE_MASK jz kit03_10 cmp word ptr [ebp]+TsSegCs,KGDT_R3_CODE OR RPL_MASK jne kit03_30 kit03_05: sti kit03_10: ; ; Set up exception record and arguments for raising breakpoint exception ; mov esi, ecx ; ExceptionInfo 2 mov edi, edx ; ExceptionInfo 3 mov edx, eax ; ExceptionInfo 1 mov ebx, [ebp]+TsEip dec ebx ; (ebx)-> int3 instruction mov ecx, 3 mov eax, STATUS_BREAKPOINT call CommonDispatchException ; Never return kit03_30: ; Check to see if this process is a vdm mov ebx,PCR[PcPrcbData+PbCurrentThread] mov ebx,[ebx]+ThApcState+AsProcess test byte ptr [ebx]+PrVdmFlag,0fh ; is this a vdm process? jz kit03_05 stdCall _Ki386VdmReflectException_A, <03h> test ax,0FFFFh jz Kit03_10 jmp _KiExceptionExit _KiTrap03 endp
看了上面的代码,我发现把dec ebx这行nop掉也能起到很好的效果,为什么呢?
因为当CPU执行完int 3指令之后,对于原先没有下断点的汇编代码而言,这时的EIP是指向了这个汇编指令第二个字节,这样一来,一条完整的汇编代码被打破,因此要通过dec 减1的方法,向上移动一个字节,这样做可以让调试器停在被下断点的地方,而不是下断点的位置加1的地方。如果没有减1,那么被下断点的地方的指令被打破了,如果不是单字节的指令,下次运行一定会Crash。
不过,我用了更狠的方法让程序直接跑飞掉
---------------------------------------------------------------------
;小汇编之前的正常的代码
804dfb3c 4b dec ebx
804dfb3d b903000000 mov ecx,3
804dfb42 b803000080 mov eax,80000003h
804dfb47 e85af8ffff call nt!CommonDispatchException (804df3a6)
804dfb4c 648b1d24010000 mov ebx,dword ptr fs:[124h]
804dfb53 8b5b44 mov ebx,dword ptr [ebx+44h]
804dfb56 83bb5801000000 cmp dword ptr [ebx+158h],0
---------------------------------------------------------------------
;开始小汇编,我这儿把eip直接加10h,这样调试器就指飞了。当然也可以是其它的代码,反正就是让调试器跑飞。
kd> a 804dfb3c
804dfb3c add ebx, 10
804dfb3f mov ecx,3
804dfb44 mov eax, 80000003
804dfb49 call 804df3a6
kd> u 804dfb3c
nt!KiTrap03+0x9e:
804dfb3c 83c310 add ebx,10h; 加10h了 当然也可以是其它的代码
804dfb3f b903000000 mov ecx,3
804dfb44 b803000080 mov eax,80000003h
804dfb49 e858f8ffff call nt!CommonDispatchException (804df3a6)
---------------------------------------------------------------------
还有一个最直接的方法就是替换掉IDT中断表中的int 3的中断处理例程,指向我们自已的中断处理例程。但是这个方法太明显了。而且还涉及到多核CPU的问题,CPU当中的每个核都会有独立的IDT表,需要全部替换掉。
如果上int 3反调试的最终的效果是:
1. ring3调试器
如果是在ring3层下软件断点,并执行到了,直接跑飞了或程序崩溃了
2. ring0调试器
如果int 3直接返回了,那么在内核调试器就不能break了,有时也会蓝。