Software Breakpoint Scanning (0xCC)
Malware scans its own executable code pages for stray 0xCC (INT3) bytes that a debugger writes when planting a software breakpoint, then bails out or corrupts itself if any are found.
When a debugger sets a software breakpoint it overwrites the target instruction's first byte with 0xCC, the opcode for INT3. The original byte is saved and restored when the breakpoint fires or is removed, so the substitution is invisible to a normal READ of memory through the debugger — but it is fully visible to the process itself reading its own code pages.
Malware exploits this by computing a checksum over its critical code, or by simply scanning the executable region for 0xCC bytes. If a breakpoint has been planted inside the scanned range, the byte count or checksum changes, and the sample diverges into a benign decoy path, crashes deliberately, or refuses to decrypt its payload.
How it works
The simplest variant walks a function's bytes looking for INT3:
// Scan the bytes of a target routine for 0xCC (INT3)
int HasSoftwareBreakpoint(unsigned char *start, size_t len)
{
for (size_t i = 0; i < len; i++) {
if (start[i] == 0xCC) // INT3 opcode planted by the debugger
return 1;
}
return 0;
}
void protected_routine(void)
{
extern unsigned char protected_routine[];
if (HasSoftwareBreakpoint((unsigned char *)protected_routine, 0x200))
ExitProcess(0); // or jump to a decoy / corrupt key material
// ... real logic ...
}A stealthier form folds the bytes into a rolling checksum so that a single planted 0xCC shifts the final value without an obvious literal 0xCC compare:
; sum the bytes of the protected region; result used as an XOR key
xor eax, eax
mov esi, offset protected_start
mov ecx, protected_len
.next:
movzx edx, byte ptr [esi]
add eax, edx
inc esi
loop .next
; EAX now feeds payload decryption — a stray 0xCC corrupts the keyBecause the checksum result is consumed as a decryption key, a breakpoint does not merely set a flag — it silently produces garbage, making the failure look like a bug rather than an evasion.
Detection & analysis
Static analysis:
- Look for routines that take a pointer to their own
.textrange and loop comparing bytes against0xCC, or that sum/CRC a code region. In IDA/Ghidra, cross-reference reads where the source operand resolves to an address inside an executable section. - The constant
0CChused in a comparison against bytes fetched from a code page is a strong indicator.
Dynamic analysis:
- Prefer hardware breakpoints (debug registers
DR0–DR3) over software breakpoints — they do not modify code bytes, so the scan finds nothing. - If you must use software breakpoints, place them outside the scanned/checksummed range, or step past the integrity check before arming them.
- Tools like
ScyllaHidecan shadow0xCCbytes, but a self-checksum (not a literal compare) defeats byte-hiding plugins; trace the computed value into its consumer instead.
Detection rule hint:
Flag any loop that reads bytes from an address range overlapping the module's own executable section and either compares them to 0xCC or accumulates them into a value later used as a decryption/XOR key — legitimate code rarely inspects its own opcodes byte-by-byte.