Skip to content

MBA — mba

Mixed Boolean/Arithmetic obfuscation for integer expressions.

mba rewrites integer add/sub/and/or/xor and similar instructions as semantically equivalent MBA expressions built from linear zero-sum terms and optional nonlinear components. The value is unchanged; the algebraic form that computes it is deeply tangled.

Presets (start here)

preset bundles the knobs into one tier; any explicit key overrides it.

Preset What it sets Cost Resists
light basic rewriting, no inflation lowest weak
medium noise-slot inflation (nonlinear + layered) — historical default medium SMT (fragile under -O2)
high memory-free SLE zeros replacing the noise slot — recommended ~medium SMT + linear-MBA, survives -O2
max noise-slot inflation + SLE + input-derived zeros stacked highest SMT + linear-MBA, survives -O2
__attribute__((annotate("obf: mba(preset=high)")))          // recommended
__attribute__((annotate("obf: mba(preset=max, prob=60)")))  // max, override prob

Options

Key Default Range Meaning
preset light|medium|high|max Knob bundle applied before explicit keys.
prob 40 0–100 Probability (%) to transform a candidate site.
maxDepth 3 1–10 Maximum recursive depth for MBA expansion.
maxSites 120 1–5000 Cap on transformed sites per function.
linearTermsMin 6 1–64 Minimum linear "zero-sum" terms to inject.
linearTermsMax 10 1–96 Maximum linear terms (must be ≥ linearTermsMin).
enableNonLinear 1 0/1 Enable nonlinear zero-addends (mul/urem based).
nonLinearWeight / nonLinearProb 20 0–100 Weight/probability for nonlinear components per site.
enableLayered 1 0/1 Enable layered MBA windowing.
layeredWindow 48 0–256 Sliding window size for layered MBA.
layeredBudget 1 0–32 Max layered expansions per transformed site.
inputZero 0 0/1 Inject memory-free, input-derived nonlinear runtime zeros.
inputZeroReplace 0 0/1 With inputZero, drop the noise-slot inflation and use input-derived zeros only.
inputZeroCount 1 1–8 Input-derived zero forms emitted per site.
inputZeroWeight 40 0–100 Probability (%) to inject input-derived zeros per site.
sle 0 0/1 Inject SLE-pool nonlinear runtime zeros (per-form structural diversity).
sleReplace 0 0/1 With sle, drop the noise-slot inflation and use SLE zeros only.
sleCount 1 1–8 SLE pool forms emitted per site.
sleWeight 40 0–100 Probability (%) to inject SLE zeros per site.

Example

__attribute__((annotate("obf: mba(prob=70,maxDepth=3,maxSites=80)")))
int mix(int a, int b) { return a ^ (b + 3); }

By default, MBA inflation leans on a per-function volatile noise slot. That slot's zero folds away under an analyst's own -O2 (intra-block store-forwarding), and the plain linear terms fall to SMT and linear-MBA simplifiers. Two opt-in modes replace it with memory-free, nonlinear runtime zeros that survive -O2 and resist both a general SMT solver and a dedicated linear-MBA simplifier:

  • inputZero — a small fixed set of input-derived, nonlinear-lifted zeros.
  • sle — a large, swappable pool of synthesized forms, adding per-form structural diversity.

Recommended (strongest per cost):

__attribute__((annotate("obf: mba(sle=1,sleReplace=1,sleCount=1)")))

Raise sleCount / sleWeight, or combine inputZero and sle, for more layering at higher runtime cost.

Regenerating the SLE pool without rebuilding

The pool loads at runtime from the file named by $XOLLVM_SLE_POOL, falling back to a compiled-in default. Regenerate and point at it — no LLVM or obfuscator rebuild required:

python utils/mba_sle_gen.py --n 256 --out my_pool.txt   # mints + vets a fresh pool
export XOLLVM_SLE_POOL=$PWD/my_pool.txt                  # next opt/clang run uses it
utils/mba_sle_verify.py re-checks that a pool file is all exact runtime zeros.

Z3-strong ≠ linear-MBA-strong

Resisting a general SMT solver and resisting a dedicated linear-MBA simplifier are different properties. The high/max presets and the SLE pool are tuned against both — test with both if you change the knobs. See Resilience benchmarks.