Guard Page Anti-Debug
Arming memory with PAGE_GUARD turns the first access into a one-shot STATUS_GUARD_PAGE_VIOLATION, exposing single-steppers and memory-scanning tools.
A guard page is memory marked with PAGE_GUARD. The first access — read, write,
or execute — raises a one-shot STATUS_GUARD_PAGE_VIOLATION (0x80000001) and
then automatically clears the guard bit. Malware abuses this as a tripwire: it
arms a page, accesses it through its own controlled path, and verifies the
exception arrives exactly when expected.
If a debugger has placed a software breakpoint on the page, or a memory-scanning tool reads it first, the guard fires out of order — or the analyst's exception handler swallows the violation — and the sample detects the interference.
How it works
The malware allocates or re-protects a page with PAGE_GUARD, installs a
vectored/SEH handler, then deliberately touches the page. On a clean machine the
handler runs exactly once; the handler is also where the real (often decrypted)
code lives, so naively skipping the fault breaks execution.
DWORD old;
BYTE *page = VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE);
VirtualProtect(page, 0x1000, PAGE_READWRITE | PAGE_GUARD, &old);
__try {
volatile BYTE x = page[0]; // triggers STATUS_GUARD_PAGE_VIOLATION
(void)x;
// If we reach here without the exception, something cleared the guard.
debugger_detected();
} __except (GetExceptionCode() == STATUS_GUARD_PAGE_VIOLATION
? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
// Expected on a clean run — continue real logic here.
}Single-stepping interacts badly with guard pages: combining PAGE_GUARD with an
instruction that the debugger steps over can desynchronise the expected
exception count, which the sample tallies.
Detection & analysis
Static analysis: Look for VirtualAlloc/VirtualProtect with the
PAGE_GUARD flag (0x100) ORed into the protection constant, paired with an
SEH/VEH handler that explicitly checks for STATUS_GUARD_PAGE_VIOLATION
(0x80000001). The deliberate self-access immediately after arming is the tell.
Dynamic analysis: Configure your debugger to pass STATUS_GUARD_PAGE_VIOLATION
straight to the application's handler rather than catching it. Avoid placing
software breakpoints inside guarded pages; use hardware breakpoints outside them.
Trace the exception-count logic so you do not perturb the sample's expected
sequence.
Detection rule hint: Flag re-protection of recently-written memory to
PAGE_READWRITE | PAGE_GUARD followed within a few instructions by a read of
that same region, plus a handler keyed on exception code 0x80000001.