本帖最后由 459121520 于 2022-11-20 23:59 编辑
一、INT 3指令INT3是用作软件断点的中断。在没有调试器的情况下,在到达INT3指令后,将生成异常EXCEPTION_BREAKPOINT (0x80000003),并将调用异常处理程序。如果存在调试器,则不会将控制权赋予异常处理程序。 bool IsDebugged(){ __try { __asm int 3; return true; } __except(EXCEPTION_EXECUTE_HANDLER) { return false; }}
除了 INT 3指令的简短形式0xCC之外,该指令还有一种长形式:CD 03。 当异常EXCEPTION_BREAKPOINT 发生时,Windows将EIP寄存器递减到0xCC操作码的假定位置,并将控制权传递给异常处理程序。对于INT3指令的长形式,EIP将指向指令的中间(0x03偏移字节处)。因此,如果我们想在INT3指令之后继续执行,就应该在异常处理程序中编辑EIP(否则我们很可能会得到EXCEPTION_ACCESS_VIOLATION 异常)。如果没有,我们可以忽略指令指针的修改。 bool g_bDebugged = false;int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep){ g_bDebugged = code != EXCEPTION_BREAKPOINT; return EXCEPTION_EXECUTE_HANDLER;}bool IsDebugged(){ __try { __asm __emit(0xCD); __asm __emit(0x03); } __except (filter(GetExceptionCode(), GetExceptionInformation())) { return g_bDebugged; }}
二、 INT 2D跟INT 3一样,该指令也会触发EXCEPTION_BREAKPOINT ,但对于INT2D,Windows使用EIP寄存器作为异常地址,然后递增EIP寄存器值。在执行INT2D时,Windows还检查EAX寄存器的值。如果为1、3或4,或在Vista+上为5,则异常地址将增加1。 对于一些调试器可能会崩溃,当EIP用作异常处理地址之后,INT 2D指令后的代码将会被跳过,可能跳入到一些非法指令处。 在本例中,我们将一个字节的NOP指令放在INT2D之后,以便在任何情况下跳过它都不影响。如果程序在没有调试器的情况下执行,则控制权将传递给异常处理程序。 bool IsDebugged(){ __try { __asm xor eax, eax; __asm int 0x2d; __asm nop; return true; } __except(EXCEPTION_EXECUTE_HANDLER) { return false; }}
三、 DebugBreak如DebugBreak文档中所述,“DebugBreak导致当前进程中发生断点异常。这允许调用线程向调试器发出信号以处理异常”。 如果程序在没有调试器的情况下执行,则控制权将传递给异常处理程序。否则,调试器将拦截执行。 bool IsDebugged(){ __try { DebugBreak(); } __except(EXCEPTION_BREAKPOINT) { return false; } return true;}
四、ICE“ICE”是英特尔的一条未经证明的指令。它的操作码是0xF1????。它可以用来检测程序是否被跟踪。 如果执行ICE指令,将引发EXCEPTION_SINGLE_STEP(0x8000004)异常。 但是,如果程序已经被跟踪调试,调试器会将此异常视为通过在Flags寄存器中设置SingleStep位来执行指令而生成的正常异常。因此,在调试器下,不会调用异常处理程序,ICE指令后将继续执行。 bool IsDebugged(){ __try { __asm __emit 0xF1; return true; } __except(EXCEPTION_EXECUTE_HANDLER) { return false; }}
五、堆栈寄存器跟踪以下汇编指令序列: push ss pop ss pushf
如果在调试器中单步经过此代码,????[color=var(--color-accent-fg)]Trap标志位 将会被设置,正常情况下,调试器在每一个调试事件传送完后清理 TRAP 标志,并且不可见。但是只要事前保存EFLAGS到堆栈,就可以检查 Tarp 是否更改。 bool IsDebugged(){ bool bTraced = false; __asm { push ss pop ss pushf test byte ptr [esp+1], 1 jz movss_not_being_debugged } bTraced = true;movss_not_being_debugged: __asm popf; return bTraced;}
六、指令计数滥用调试器EXCEPTION_SINGLE_STEP异常处理方式
这个技巧的思想是按照预定义的顺序为每条指令设置硬件断点。执行带有硬件断点的指令会引发EXCEPTION_SINGLE_STEP异常,并被异常处理链捕获。在异常处理程序中,我们设置一个寄存器,该寄存器充当指令计数器(在本例中为EAX)和指令指针EIP的角色,以将控制权传递给序列中的下一条指令。因此,每次将控制权传递到序列中的下一条指令时,????都会引发异常并递增计数器。当指定的序列执行完后,比较指令数和计数器,不一样就代表有调试器存在: 代码片段
七、POPFD 和 Trap 标志设置 Trap 标志时,将引发异常 SINGLE_STEP。但是,如果我们跟踪调试代码,Trap 标志将被调试器清除,因此我们不会看到异常。 bool IsDebugged(){ __try { __asm { pushfd mov dword ptr [esp], 0x100 popfd nop } return true; } __except(GetExceptionCode() == EXCEPTION_SINGLE_STEP ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_EXECUTION) { return false; }}
八、 指令前缀(OllyDbg v1.10)Instruction Prefixes
在"PUSHFD",前面加上REP指令前缀,od识别不了。 在下面的例子中,如果在od调试器,当步入到 0xF3 时,我们会直接跳到 try 的末尾。调试器直接跳过前缀,并把控制权交给 INT1 指令。 bool IsDebugged(){ __try { // 0xF3 0x64 PREFIX REP: __asm __emit 0xF3 __asm __emit 0x64 // One byte INT 1 __asm __emit 0xF1 return true; } __except(EXCEPTION_EXECUTE_HANDLER) { return false; }}
|