1.异常-SEH
#include "stdio.h" #include "windows.h" #include "tchar.h" void AD_BreakPoint() { printf("SEH : BreakPoint\n"); __asm { // install SEH push handler push DWORD ptr fs:[0] mov DWORD ptr fs:[0], esp //INT 3 指令是CPU中断命令,在用户模式的调试器啥都不做(经过调试发现不会触发ntdll.dll中的KiUserExceptionDispatcher)。 // generating exception int 3 // 1) debugging // go to terminating code mov eax, 0xFFFFFFFF jmp eax // process terminating!!! // 2) not debugging // go to normal code handler: mov eax, dword ptr ss:[esp+0xc] mov ebx, normal_code mov dword ptr ds:[eax+0xb8], ebx xor eax, eax retn normal_code: // remove SEH pop dword ptr fs:[0] add esp, 4 } printf(" => Not debugging...\n\n"); } int _tmain(int argc, TCHAR* argv[]) { AD_BreakPoint(); return 0; }
注意:OD的异常选项 忽略int 3 中断必须不选,strongOD插件选项最好不用,才能进入指令
mov eax, 0xFFFFFFFF
jmp eax
OD不能在int 3下断(F2),否则会循环进入异常地址 int 3.
2.异常-SetUnhandledExceptionFilter
#include "stdio.h" #include "windows.h" #include "tchar.h" LPVOID g_pOrgFilter = 0; LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS pExcept) { SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)g_pOrgFilter); // 8900 MOV DWORD PTR DS:[EAX], EAX // FFE0 JMP EAX pExcept->ContextRecord->Eip += 4; return EXCEPTION_CONTINUE_EXECUTION; } void AD_SetUnhandledExceptionFilter() { printf("SEH : SetUnhandledExceptionFilter()\n"); //SetUnhandledExceptionFilter()来注册新的Top Level Exception Filter回调函数。 //触发异常时,系统在前面没有处理异常的情况下,会调用Kernel32.dll中的 //UnhandledExceptionFilter()函数。UnhandledExceptionFilter()会利用 //ntdll.dll中的NtQueryInformationProcess()来判断是否被调试, //若判断在被调试,异常给调试器(调试器无法处理异常,进程终止)。 //若判断未被调试,则调用Top Level Exception Filter回调函数。 g_pOrgFilter = (LPVOID)SetUnhandledExceptionFilter( (LPTOP_LEVEL_EXCEPTION_FILTER)ExceptionFilter); __asm { xor eax, eax; mov dword ptr [eax], eax jmp eax } printf(" => Not debugging...\n\n"); } int _tmain(int argc, TCHAR* argv[]) { AD_SetUnhandledExceptionFilter(); return 0; }
#include "stdio.h" #include "windows.h" #include "tchar.h" void DynAD_RDTSC() { //Timing Check 技术通过计算运行时间的差异来反调试,反模拟。 //1.Counter based method // RDTSC 汇编指令(RD==READ TSC == Time Stamp Counter) // x86 CPU中存在一个名为TSC(Time Stamp Counter,时间戳计数器)的64位寄存器。 // CPU对每个Clock Cycle(时钟周期)计数,然后保存到TSC.RDTSC是一条汇编指令, // 用来将TSC值读入EDX:EAX寄存器)。 // OD中的插件Olly Advanced->options 反调试2 Anti-RDSTC(基于驱动模式可以绕过),此功能有可能造成死机。 // kernel32!QueryPerformanceCounter()/ntdll!NtQueryPerformanceCounter() // kernel32!GetTickCount() //2.Time based method // timeGetTime() // _ftime() DWORD dwDelta = 0; printf("Timing Check (RDTSC method)"); __asm { pushad //0F31 rdtsc rdtsc push edx push eax //用于消耗时间的循环(实际代码相当复杂) xor eax, eax mov ecx, 0x3e8 _LOOP_START: inc eax loop _LOOP_START rdtsc pop esi // eax pop edi // edx // check high order bits cmp edx, edi ja _DEBUGGER_FOUND // check low order bits sub eax, esi mov dwDelta, eax cmp eax, 0xffffff jb _DEBUGGER_NOT_FOUND // debugger found -> crash!!! _DEBUGGER_FOUND: xor eax, eax mov [eax], eax // debugger not found _DEBUGGER_NOT_FOUND: popad } printf(" : delta = %X (ticks)\n", dwDelta); printf(" => Not debugging...\n\n"); } int _tmain(int argc, TCHAR* argv[]) { DynAD_RDTSC(); return 0; }
4.陷阱标志
#include "stdio.h" #include "windows.h" #include "tchar.h" void DynAD_SingleStep() { //陷阱标志指EFLAGS寄存器的第9个(Index8)比特位。 //TF值设置为1时,CPU将进入单步执行(Single Step)模式。单步直行模式中,CPU执行1条指令后即 //触发一个EXCEPTION_SINGLE_STEP(0x80000004)异常,异常地址为下一条指令。然后陷阱标志会 //自动清零。 printf("Trap Flag (Single Step)\n"); __asm { // install SEH push handler push DWORD ptr fs:[0] mov DWORD ptr fs:[0], esp //因无法修改EFLAGS,故通过栈修改 pushfd or dword ptr ss:[esp], 0x100 popfd //执行完nop指令后,才触发EXCEPTION_SINGLE_STEP异常,OD在nop指令使用F7,F8,F9都可以。 //即KiUserExceptionDispatcher的异常地址指向 mov eax,0xFFFFFFFF //1)若为正常运行,则运行前面注册过的SEH //2)若为调试运行,则继续执行以下指令,不管使用F7,F8,F9都断在mov eax,0xFFFFFFFF nop // 1) debugging // go to terminating code mov eax, 0xFFFFFFFF jmp eax // process terminating!!! // 2) not debugging // go to normal code handler: mov eax, dword ptr ss:[esp+0xc] mov ebx, normal_code mov dword ptr ds:[eax+0xb8], ebx xor eax, eax retn normal_code: // remove SEH pop dword ptr fs:[0] add esp, 4 } printf(" => Not debugging...\n\n"); } int _tmain(int argc, TCHAR* argv[]) { DynAD_SingleStep(); return 0; }
#include "stdio.h" #include "windows.h" #include "tchar.h" void DynAD_INT2D() { //INT 2D 原为内核模式中用来触发异常的指令,也可以在用户模式下触发异常。但程序 //调试运行时不会触发异常,只是忽略。 //INT 2D指令会造成两个有趣的现象 //1.在调试模式中执行INT2D指令后(F7,F8),下条指令的第一个字节将被忽略,后一个字节会被识别为新的指令继续执行。此特性,可用于代码混淆。 //2.在调试模式中执行INT2D指令后(F7,F8),程序不会停在其下条指令开始的地方,而是一直运行,直到遇到断点(原有的代码字节顺序被打乱,OD的BUG)。 BOOL bDebugging = FALSE; __asm { // install SEH push handler push DWORD ptr fs:[0] mov DWORD ptr fs:[0], esp //可以在执行到此条指令时,修改EFlags寄存器TF=1,然后就能进入SEH处理函数 int 0x2d nop mov bDebugging, 1 jmp normal_code handler: mov eax, dword ptr ss:[esp+0xc] mov dword ptr ds:[eax+0xb8], offset normal_code mov bDebugging, 0 xor eax, eax retn normal_code: // remove SEH pop dword ptr fs:[0] add esp, 4 } printf("Trap Flag (INT 2D)\n"); if( bDebugging ) printf(" => Debugging!!!\n\n"); else printf(" => Not debugging...\n\n"); } int _tmain(int argc, TCHAR* argv[]) { DynAD_INT2D(); return 0; }
#include "stdio.h" #include "windows.h" #include "tchar.h" DWORD g_dwOrgChecksum = 0xF5934986; int _tmain(int argc, TCHAR* argv[]); void DynAD_Checksum() { BOOL bDebugging = FALSE; DWORD dwSize = 0; printf("Checksum\n"); __asm { mov ecx, offset _tmain mov esi, offset DynAD_Checksum sub ecx, esi // ecx : loop count (buf size) xor eax, eax // eax : checksum xor ebx, ebx _CALC_CHECKSUM: movzx ebx, byte ptr ds:[esi] add eax, ebx rol eax, 1 inc esi loop _CALC_CHECKSUM cmp eax, g_dwOrgChecksum je _NOT_DEBUGGING mov bDebugging, 1 _NOT_DEBUGGING: } if( bDebugging ) printf(" => Debugging!!!\n\n"); else printf(" => Not debugging...\n\n"); } int _tmain(int argc, TCHAR* argv[]) { DynAD_Checksum(); return 0; }
总结:
1. CC int3 指令(注意两者之间没有空格)。
2.CD 2D int 2d 指令
3.
pushfd
or [esp],100
popfd
使EFlags寄存器TF==1.
此3种方法,都是利用了调试器特有的性质(1.忽略 2.一直运行,直到断点。 3.断在执行指令后的一条指令,都没有触发ntdll!KiUserExceptionDispatcher())。