INT 2D Anti-Debugging
The INT 2D kernel-debugging interrupt skews the instruction pointer under a debugger, so a debugger that mishandles it is revealed.
INT 2D is the interrupt the Windows kernel uses for kernel-mode debugger services (DbgPrint/__debugbreak style signalling). When executed in user mode, its behaviour depends on whether a debugger is present, and crucially on how the kernel adjusts the instruction pointer afterwards.
The key quirk: when INT 2D is handled, the kernel treats the byte immediately following the INT 2D opcode as part of the interrupt servicing and advances EIP/RIP by one extra byte. Without a debugger, no exception surfaces to the process and execution simply continues. With a ring-3 debugger attached, an EXCEPTION_BREAKPOINT is raised — but because of the one-byte skew, a debugger that resumes naively lands in the middle of the next instruction, desynchronising the disassembly and often crashing or diverging the trace.
Malware places a one-byte "poison" instruction right after INT 2D. If that byte is silently swallowed (no debugger, or correct handling), the real code runs; if a debugger mis-handles the skew, control derails into a decoy or fault. This makes INT 2D both a debugger-presence check and an active disassembly-confusion trick.
How it works
; INT 2D with an EIP-skew trap
int 2dh
nop ; this byte is "eaten" by the EIP advance...
; ...so without a debugger, execution resumes HERE:
jmp real_code
; A debugger that does not account for the +1 skew resumes one
; byte early, decoding the wrong instruction stream:
trap:
db 0EBh ; partial opcode that derails a naive debuggerWrapped in SEH, the sample can turn the resulting exception state into a clean boolean:
BOOL DebuggerViaInt2D(void)
{
BOOL detected = TRUE;
__try {
__asm { int 0x2d } // kernel advances EIP past the next byte
__asm { nop } // swallowed when no debugger mishandles us
detected = FALSE; // reached only on a "clean" continuation
} __except (EXCEPTION_EXECUTE_HANDLER) {
detected = TRUE; // debugger surfaced/ mishandled the exception
}
return detected;
}The exact outcome varies across Windows builds and CPU vendors, so malware typically pairs INT 2D with INT 3 and INT 1 probes and treats any anomalous result as "being debugged".
Detection & analysis
Static analysis:
- Search for the opcode bytes
CD 2D(int 2dh), especially when followed by a single filler byte (nop, or a lone byte that the disassembler renders as a truncated instruction) and a jump. - The combination of
int 2dhinside a__try/SEH frame is a strong anti-debug indicator; it has no legitimate user-mode purpose.
Dynamic analysis:
- After the
INT 2Dexception fires in the debugger, manually correct the instruction pointer for the one-byte skew before resuming, then re-synchronise the disassembly on the true next instruction. - Alternatively, set a hardware breakpoint past the trap region and let the SEH path run, or patch the
CD 2Dbytes to90 90(nop nop) so the interrupt never fires.
Detection rule hint:
Flag CD 2D opcodes in user-mode code — particularly when wrapped in structured exception handling or immediately preceding a filler byte and a control-flow transfer — as an INT 2D debugger-evasion / disassembly-desync trap.