动态反调试技术总结

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;
}

3.Timing Check

#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;
}



5.0xCC探测

#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())。


你可能感兴趣的:(反调试,Windows逆向)