Quickstart¶
Goal: take one annotated C function all the way to a hardened binary, and confirm what ran.
1. Annotate a function¶
#define OBF(spec) __attribute__((annotate("obf: " spec)))
// Expression + control-flow + post-hardening
OBF("mba(prob=70), bcf(prob=30), flattening(minBlocks=3), shield")
int check(int key, int data) {
return key ^ (data + 0xDEAD);
}
int main(void) { return check(0x1234, 42) & 1; }
Only annotated functions are transformed — main here is left untouched.
2. Emit LLVM IR¶
Use -O0 for the input: it keeps the IR close to the source so the obfuscator sees the structure
you annotated. Optimization comes after obfuscation (step 4).
3. Obfuscate¶
-obf-seed=1 -obf-deterministic pins the RNG so the output is reproducible (see
Determinism & seeds).
4. Compile to a binary¶
-O2 here optimizes around the obfuscation without unravelling it — the obfuscator inserts
opaque, volatile, and dispatcher constructs specifically so the optimizer can't fold them away.
5. See what happened¶
# resolved per-function config (what the annotations parsed to)
opt -passes=obf-dump-config app.ll -disable-output
# machine-readable metrics (JSONL): instruction/block deltas per pass
opt -passes=obf-metrics app.ll -disable-output
obf-dump-config is the fastest way to confirm your annotation grammar is valid and that the
passes you expect are enabled with the options you set.
The whole loop¶
flowchart LR
S["app.c<br/>(annotated)"] -->|"clang -O0 -emit-llvm"| I[app.ll]
I -->|"opt -passes=obfuscation"| O[app.obf.ll]
O -->|"clang -O2"| B[app]
I -.->|"obf-dump-config<br/>obf-metrics"| R[(diagnostics)]
Next steps¶
- Annotating code — the full annotation cheat-sheet.
- Choosing passes — match passes to a threat model.
- Passes reference — every pass and its options.
- Reproducible builds — seed manifests for auditable output.