CFG flattening — flattening¶
Replaces the original control-flow graph with a dispatcher / state machine. Every original block
becomes a case of a central switch; control flow is encoded as updates to a state variable. The
natural block-to-block structure — the thing a decompiler reconstructs into if/for/while — is
gone.
Options¶
| Key | Default | Range | Meaning |
|---|---|---|---|
minBlocks |
3 | 2–100000 | Minimum original blocks required to flatten. |
maxBlocks |
200 | 2–200000 | Maximum blocks considered (must be ≥ minBlocks). |
allowIndirect / indirect |
0 | 0/1 | Allow indirect dispatch forms (higher risk, more confusion). |
hybrid |
1 | 0/1 | Enable hybrid/structured flattening strategies. |
opaqueState |
1 | 0/1 | Store state updates as opaque expressions instead of constants. |
fakeTransitions / fake |
0 | 0/1 | Inject hard-false transitions + optional fake cases. |
fakeCases |
0 | 0–64 | Extra fake cases per dispatcher (requires fakeTransitions=1). |
perDispatcherDomain / domain |
1 | 0/1 | Per-dispatcher switch domain encoding. |
obfuscateStatePtr / ptr |
1 | 0/1 | Pointer games around state storage. |
opaqueAliasStatePtr / alias |
1 | 0/1 | Hard-false alias pointers to confuse alias analysis. |
Example¶
__attribute__((annotate(
"obf: flattening(minBlocks=3,maxBlocks=160,fakeTransitions=1,fakeCases=2)")))
int f(int x) { if (x > 0) return x*2; else return -x; }
Conflicts with vm
flattening and vm both restructure the entire CFG — they cannot run on
the same function. The pipeline rejects the combination. Pick one:
flattening— a visible dispatcher in native code, lower overhead.vm— the body is gone entirely behind an interpreter, higher overhead, stronger.
Harden the state variable
Keep opaqueState=1 and fakeTransitions=1 on: a plain flattener with constant state values is
reconstructable by symbolic recovery of the state machine. Opaque state + fake cases raise that
cost substantially.