Pipeline & ordering¶
Annotation order does not decide execution order. The driver resolves a stable pipeline by topological sort with conflict enforcement, so stacking annotations any way you like produces the same, correct ordering.
Execution order¶
The function pipeline runs in this partial order (earlier = first):
flowchart LR
constenc --> mba --> substitution --> vcall --> split --> sdiff --> bcf --> flattening --> shield --> adec --> vm
Module-only passes run before the function pipeline:
flowchart LR
fmerge --> strenc --> FP[function pipeline]
Why this order:
constencfirst — encrypts constant operands so every later pass buries the materialization arithmetic further.- Expression passes (
mba,substitution,sdiff) before CFG passes (split,bcf,flattening) so the structural passes fold over already-obfuscated data flow. - Post-hardening (
shield,adec) near the end, so their volatile barriers and anti-decompiler gadgets protect the finished shape. vmlast — it virtualises the entire function, so anything that ran before it gets encoded into the bytecode. Pre-obfuscating then virtualising yields harder bytecode.
fmerge feeds the pipeline
fmerge collapses same-group= functions into one super-function first, so the merged body
is then obfuscated by the whole function pipeline. See Function merging.
The vm ↔ flattening conflict¶
vm and flattening both restructure the whole CFG. They cannot run on the same function — the
pipeline rejects the combination.
// ❌ rejected — pick one
__attribute__((annotate("obf: flattening, vm")))
// ✅ virtualise (encodes any earlier expression obfuscation)
__attribute__((annotate("obf: mba(prob=70), bcf(prob=30), vm(hardened=1)")))
Use flattening when you want a visible dispatcher in native code; use vm when you want the body
gone entirely behind an interpreter. See Choosing passes.
Overriding the order¶
Two global flags override the default topological sort (both still run conflict checks):
| Flag | Effect |
|---|---|
-obf-pipeline-ordering=<csv> |
Explicit order, e.g. mba,split,bcf,flattening. Listed passes run first in this order; remaining enabled passes are appended topologically. Unknown names are fatal. |
-obf-pipeline-ordering-ann |
Use the per-function annotation order verbatim. Ignored when -obf-pipeline-ordering is set. |
These are for experimentation and debugging. The default (topological) order is the tested, recommended one — reach for overrides only when you have a specific reason.