Hardware Breakpoints Detection
Malware reads the CPU debug registers DR0–DR3 via GetThreadContext to detect hardware breakpoints set by a debugger.
Intel x86/x64 processors provide eight debug registers. DR0–DR3 hold the linear addresses of up to four hardware breakpoints, while DR7 controls their enabled/disabled state and break conditions. Debuggers set hardware breakpoints by writing to these registers; software running in user mode cannot write them directly but can read them via the GetThreadContext / RtlCaptureContext APIs.
If any of DR0–DR3 contains a non-zero value, a hardware breakpoint is active, indicating a debugger.
How it works
#include <windows.h>
BOOL HardwareBreakpointsPresent(void)
{
CONTEXT ctx = {0};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
// Read debug registers of the current thread
if (!GetThreadContext(GetCurrentThread(), &ctx))
return FALSE;
return (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3);
}A stealthier variant uses RtlCaptureContext to avoid the GetThreadContext API appearing in the import table:
CONTEXT ctx;
RtlCaptureContext(&ctx);
if (ctx.Dr0 | ctx.Dr1 | ctx.Dr2 | ctx.Dr3)
goto evasion_path;In assembly (x86), the check can be performed inline after capturing context on the stack:
sub esp, 4Ch ; CONTEXT size (abbreviated)
lea eax, [esp]
push CONTEXT_DEBUG_REGISTERS ; 0x10010
push eax
call GetThreadContext
mov eax, [esp + OFFSET_DR0]
or eax, [esp + OFFSET_DR1]
or eax, [esp + OFFSET_DR2]
or eax, [esp + OFFSET_DR3]
jnz debugger_detectedDetection & analysis
During debugging:
- Clear DR0–DR3 before the check runs: in x64dbg, go to the context pane and zero all DR registers.
- Use software breakpoints (INT 3,
0xCC) rather than hardware breakpoints while analysing samples known to perform this check. - ScyllaHide "Clear DR registers" option resets debug registers on context captures automatically.
Static / automated detection:
- CAPA rule U0127 / B0001.005: flags calls to
GetThreadContextorRtlCaptureContextfollowed by four or more comparisons against DR-register offsets. - Taint analysis: track the
CONTEXTbuffer from aGetThreadContextcall and flag reads of bytes at DR0–DR3 offsets (0xB8–0xC8 on x64). - Python/Frida: hook
GetThreadContext, intercept the returned context, and zero the DR fields before returning to the caller.