Skip to content

Annotation model

xollvm has no global "obfuscate everything" switch. You mark the functions you care about, and only those are transformed. Marking is done with LLVM's standard annotate attribute, which lands in llvm.global.annotations and is parsed once per module.

Grammar

In C/C++ the common pattern is:

__attribute__((annotate("obf: <spec>")))

where <spec> is a comma-separated list of pass specifications:

<spec>     := <passSpec> ( "," <passSpec> )*
<passSpec> := <passName> [ "(" <params> ")" ]
<params>   := <kv> ( "," <kv> )*
<kv>       := <key> "=" <value>

Example:

__attribute__((annotate(
  "obf: mba(prob=70,maxDepth=3), bcf(prob=40,loop=1), flattening(minBlocks=3,maxBlocks=120)")))

In C++ the attribute syntax also works:

[[clang::annotate("obf: mba(prob=60), bcf(prob=25)")]]

Use a macro

A one-line macro keeps annotations readable and greppable:

#define OBF(spec) __attribute__((annotate("obf: " spec)))
OBF("mba(prob=70), bcf(prob=30)")
int f(int x){ return x; }

Parameter parsing rules

  • Keys are [A-Za-z0-9_]no dashes.
  • Values can be unquoted (maxSites=200) or quoted (tag="hello world").
  • Whitespace around tokens is ignored.
  • Booleans usually take 0 / 1; some passes also accept true/false/yes/no/on/off.
  • All parameters are optional — unspecified keys use the pass default.

The complete key list per pass lives in the Passes reference; the full grammar in one place is on the Annotation grammar page.

Pass IDs and aliases

Canonical IDs (used in reports and manifests) plus accepted aliases:

Category Canonical ID Aliases
Expression / data-flow constenc cenc, numenc
Expression / data-flow mba
Expression / data-flow substitution sub
Expression / data-flow sdiff
Call hardening vcall
CFG split
CFG bcf
CFG flattening fla
Post-hardening shield antiopt, anti-opt
Post-hardening adec anti-decompiler, antidecompiler
Virtualisation vm virtualize, virt
Module-only fmerge funcmerge, merge
Module-only strenc

Aliases are case-insensitive; reports always use the canonical form.

The internal aes_stub pass

An internal aes_stub module pass exists but is not user-callable via annotations. It is auto-linked when strenc or vm (encBytecode=1) is enabled, embedding the shared __obf_aes_ctr_decrypt runtime. You never mention it in obf: specs.

Merging multiple annotations

If the same function carries multiple obf: annotations, configs are merged:

  • Pass enablement is additive — every mentioned pass runs.
  • Parameters are overridden by later annotations (last wins).
  • Ordering is resolved after merging, so stacking annotations is always safe.
// mba from one annotation, bcf from another — both run.
__attribute__((annotate("obf: mba(prob=60)")))
__attribute__((annotate("obf: bcf(prob=30)")))
int fn(int x) { return x; }

Verify what parsed

Never guess whether your grammar is valid — dump it:

opt -passes=obf-dump-config -S app.ll -o /dev/null -obf-verbose -obf-seed=1

This prints the resolved per-function config and the derived seeds. See Diagnostics.