Skip to content

Quickstart

Goal: take one annotated C function all the way to a hardened binary, and confirm what ran.

1. Annotate a function

app.c
#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

clang -S -emit-llvm -O0 app.c -o app.ll

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

./install/bin/opt -passes=obfuscation app.ll -S -o app.obf.ll \
    -obf-seed=1 -obf-deterministic
opt-22 -load-pass-plugin=./build/Obfuscator.so \
    -passes=obfuscation app.ll -S -o app.obf.ll \
    -obf-seed=1 -obf-deterministic

-obf-seed=1 -obf-deterministic pins the RNG so the output is reproducible (see Determinism & seeds).

4. Compile to a binary

clang app.obf.ll -O2 -o app
./app; echo "exit=$?"

-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