Skip to content
Anti-Analysisintermediate

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

c
#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:

c
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:

asm
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_detected

Detection & 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 GetThreadContext or RtlCaptureContext followed by four or more comparisons against DR-register offsets.
  • Taint analysis: track the CONTEXT buffer from a GetThreadContext call 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.
Votes

Comments(0)