🧠 Description

Anti-debugging techniques detect and evade debuggers and analysis environments. These methods prevent reverse engineers from stepping through code, setting breakpoints, or understanding program flow.

⏱️ Timing-Based Detection

RDTSC (Time Stamp Counter):

// Check for timing anomalies caused by debugging
unsigned long long start = __rdtsc();
// Single instruction
unsigned long long end = __rdtsc();
unsigned long long delta = end - start;

// If delta > 1000 cycles, likely being traced
if (delta > 1000) {
    // Exit or behave differently
    exit(0);
}

// Counter check via GetTickCount
DWORD start = GetTickCount();
Sleep(100);
DWORD elapsed = GetTickCount() - start;
if (elapsed > 200) exit(0);

QueryPerformanceCounter:

LARGE_INTEGER start, end, freq;
QueryPerformanceCounter(&start);
// code to test
QueryPerformanceCounter(&end);
QueryPerformanceFrequency(&freq);
double seconds = (end.QuadPart - start.QuadPart) / (double)freq.QuadPart;
if (seconds > 0.1) exit(0);

🎯 Software Breakpoint Detection

INT3 Detection:

// Check for 0xCC (INT3) in critical functions
unsigned char* p = (unsigned char*)&MyCriticalFunction;
for (int i = 0; i < 20; i++) {
    if (p[i] == 0xCC) {
        // debugger detected
        exit(0);
    }
}

// Check all memory pages for breakpoints
void check_breakpoints() {
    MEMORY_BASIC_INFORMATION info;
    for (int i = 0; i < 0x10000; i += 0x1000) {
        VirtualQuery((void*)i, &info, sizeof(info));
        if (info.Protect == PAGE_NOACCESS) {
            unsigned char byte = *(unsigned char*)i;
        }
    }
}

CPU Breakpoint Detection (DR Registers):

// Check if debug registers are set (context method)
BOOL IsDebuggable() {
    CONTEXT ctx = {0};
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    HANDLE hThread = GetCurrentThread();
    if (GetThreadContext(hThread, &ctx)) {
        if (ctx.Dr0 != 0 || ctx.Dr1 != 0 || ctx.Dr2 != 0 || ctx.Dr3 != 0)
            return TRUE;
        if (ctx.Dr7 != 0)
            return TRUE;
    }
    return FALSE;
}

🖥️ PEB-Based Detection

BeingDebugged Flag:

#include 
BOOL CheckPEB() {
    PEB* peb = (PEB*)__readgsqword(0x60);
    if (peb->BeingDebugged) {
        return TRUE; // Debugged
    }
    // Hide from process list
    peb->BeingDebugged = FALSE;
    return FALSE;
}

// Check ProcessHeap flags
BOOL CheckHeap() {
    PEB* peb = (PEB*)__readgsqword(0x60);
    DWORD heap = peb->ProcessHeaps;
    if (heap != 0) {
        DWORD flags = *(DWORD*)(heap + 0x0C);
        DWORD force_flags = *(DWORD*)(heap + 0x10);
        if (flags & 0x50000000 || force_flags & 0x50000000)
            return TRUE;
    }
    return FALSE;
}

// Check NtGlobalFlag
BOOL CheckNtGlobalFlag() {
    PEB* peb = (PEB*)__readgsqword(0x60);
    DWORD flag = *(DWORD*)((unsigned char*)peb + 0xBC);
    if (flag & 0x70) // FLG_HEAP_ENABLE_TAIL_CHECK, etc.
        return TRUE;
    return FALSE;
}

📡 API-Based Detection

CheckRemoteDebuggerPresent:

BOOL IsDebuggerPresent_API() {
    BOOL debugger_present = FALSE;
    CheckRemoteDebuggerPresent(GetCurrentProcess(), &debugger_present);
    return debugger_present;
}

// NtQueryInformationProcess
typedef NTSTATUS (WINAPI* pNtQueryInformationProcess)(
    HANDLE, UINT, PVOID, ULONG, PULONG);

BOOL CheckNtQuery() {
    pNtQueryInformationProcess NtQIP = (pNtQueryInformationProcess)
        GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryInformationProcess");
    
    DWORD isdebug = 0;
    NTSTATUS status = NtQIP(GetCurrentProcess(), 
        ProcessDebugPort, &isdebug, sizeof(isdebug), NULL);
    if (isdebug == 0xFFFFFFFF)
        return FALSE;
    return (isdebug != 0);
}

// Check ProcessDebugFlags
BOOL CheckProcessDebugFlags() {
    DWORD flags = 0;
    pNtQueryInformationProcess NtQIP = (pNtQueryInformationProcess)
        GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryInformationProcess");
    NtQIP(GetCurrentProcess(), ProcessDebugFlags, &flags, sizeof(flags), NULL);
    return (flags == 0); // 0 = being debugged
}

⚡ Interrupt Detection

INT2D Handler:

// INT2D - raises exception in debugger but not normally
__try {
    __asm {
        int 0x2D
        xor eax, eax // This executes if no debugger
    }
}
__except(EXCEPTION_EXECUTE_HANDLER) {
    // Debugger detected
    exit(0);
}

// INT1 (single step) trap
__try {
    __asm {
        int 0x01
        xor eax, eax
    }
}
__except(EXCEPTION_EXECUTE_HANDLER) {
    exit(0);
}
Back to Evasion