Defense Evasion | T1622
🔍 Anti-Debugging Techniques
🧠 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:
#includeBOOL 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);
}