Nanomites
Branch instructions are replaced with INT3 traps that a debugger-parent resolves from a hidden table at runtime, erasing control flow from the binary.
Nanomites are a debugger-assisted control-flow obfuscation popularised by the
Armadillo (SoftwarePassport) protector. Selected branch instructions in the
protected code are stripped out and overwritten with single-byte INT3 (0xCC)
software breakpoints. The branches no longer exist in the binary at all — neither
the opcode, the condition, nor the destination — so a static disassembler sees a
breakpoint trap where a jmp/jcc used to be and cannot reconstruct the edge.
The trick is that the protected process is launched under a parent debugger
(the protector's own loader, running as a debug-attached supervisor). When the
child executes a planted 0xCC, the CPU raises a breakpoint exception that the
parent receives via the debug event loop. The parent looks up the faulting
address in an encrypted nanomite table that records, for each INT3, the
original branch type, condition, and target. It evaluates the condition against
the child's EFLAGS, then rewrites the child's EIP/RIP to the correct
destination and resumes. Detach the debugger-parent and the program simply
crashes on the first unhandled breakpoint.
How it works
A normal conditional branch is replaced by a one-byte trap; the surrounding bytes are left intact so the function still looks plausible to linear sweep:
; ---- Original (pre-protection) ----
00401050: 3B C3 cmp eax, ebx
00401052: 0F 84 A6 00 00 00 je 0x004010FE ; 6-byte near-jump, real edge
00401058: 8B 4D F8 mov ecx, [ebp-8]
; ---- After nanomite insertion (what the disassembler sees) ----
00401050: 3B C3 cmp eax, ebx
00401052: CC int3 ; <- branch erased, trap planted
00401053: A6 cmpsb ; \
00401054: 00 00 add [eax], al ; > leftover je operand bytes
00401056: 00 00 add [eax], al ; / now decode as garbage
00401058: 8B 4D F8 mov ecx, [ebp-8]The five operand bytes of the original je (A6 00 00 00 plus alignment) are
now orphaned and the sweep decodes them as a meaningless cmpsb / add
sequence, fragmenting the function. The disassembler has no way to know that
0x00401052 was ever a branch, let alone that its target was 0x004010FE. At
runtime the parent intercepts the INT3 at 0x00401052, reads the nanomite
record, checks ZF in the child's flags, and sets the child EIP to either
0x004010FE (taken) or 0x00401053 (fall-through) before continuing.
Detection & analysis
Static analysis:
- A function body peppered with stray
0xCCbytes inside live code (not as padding between functions) is the signature — especially when eachINT3sits immediately after acmp/test/subthat would normally feed ajcc. - The branch targets are absent from the binary, so the recovered CFG has dead-ending basic blocks with no outgoing edge. Cross-references collapse and the decompiler produces truncated functions.
- Locate the nanomite table by tracing the loader's debug-event handler
(
WaitForDebugEvent→EXCEPTION_BREAKPOINTdispatch) and the decryption routine that indexes it by faulting address.
Dynamic analysis:
- Run the sample intact under its own protector loader, attach a second
observer non-intrusively (a hypervisor-level tracer such as a Intel PT trace
or an emulator), and log every
EIPrewrite the parent performs — each one reveals a resolved branch and its target. - Dump the reconstructed branches and patch them back into the binary, or script the debugger to replay the parent's table lookups, rebuilding the CFG offline.
Detection rule hint:
Flag executables that spawn a child via CreateProcess with
DEBUG_PROCESS/DEBUG_ONLY_THIS_PROCESS and whose code sections contain
dense, non-aligned 0xCC bytes interleaved with arithmetic/compare
instructions. The combination of a self-debugging parent and breakpoint bytes
embedded mid-function is near-exclusive to nanomite protectors and effectively
never produced by legitimate compilers.