Software Breakpoint Scanning
Malware scans or checksums its own code section for 0xCC INT3 bytes left behind by a debugger's software breakpoints, detecting analysis without any API call.
Software breakpoints work by overwriting the first byte of the target instruction with 0xCC (the INT3 opcode); the debugger restores the original byte when it stops. Malware exploits this by reading its own .text section and looking for stray 0xCC bytes, or by computing a checksum/CRC over the code and comparing it against a precomputed "clean" value baked into the binary.
Because the check reads memory directly, it requires no API import and is invisible to API-level hooks.
How it works
The simplest variant scans a known code range for the INT3 opcode. A more robust variant CRCs the whole region so it also defeats single-byte tampering and inline patches.
#include <windows.h>
// Scan [start, end) of our own code for stray software breakpoints.
BOOL HasSoftwareBreakpoint(BYTE *start, BYTE *end)
{
for (BYTE *p = start; p < end; p++)
if (*p == 0xCC) // INT3 left by a debugger
return TRUE;
return FALSE;
}
// Integrity variant: compare a checksum against the known-good value.
BOOL CodeWasModified(BYTE *start, SIZE_T len, DWORD expected)
{
DWORD sum = 0;
for (SIZE_T i = 0; i < len; i++)
sum = (sum << 1 | sum >> 31) ^ start[i]; // cheap rolling hash
return sum != expected;
}In assembly the scan loop is unmistakable: a cmp al, 0CCh (or scasb against 0xCC) inside a tight loop over the function's own address range.
Detection & bypass
- Static — Grep in IDA/Ghidra for the immediate
0xCCused in a comparison (cmp ..., 0CCh),scasb/repne scasbloops, and references toGetModuleHandle/section base used to derive the scan range. Code-integrity variants reference a hard-coded checksum constant compared after a loop over the image; look for that magic value as an anchor. - Dynamic — Avoid software breakpoints entirely: use hardware breakpoints (DR0–DR3) which never modify code bytes. In x64dbg set them via the "Hardware, on execution" breakpoint type. To find the check, breakpoint on memory reads of your own
.text(a memory-access hardware breakpoint over the code range) and watch which routine walks it. - Patch / bypass — Let the debugger restore original bytes before the scan runs, or patch the comparison: NOP the
cmp/je, or force the integrity function toreturn 0/xor eax,eax; ret. For the CRC variant, recompute and overwrite the expected constant, or short-circuit the conditional jump. - Tools — ScyllaHide and hardware-breakpoint-only workflows; x64dbg (use hardware breakpoints), pe-sieve to confirm in-memory code integrity, and TitanHide for kernel-side stealth.