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:
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:
Use a macro
A one-line macro keeps annotations readable and greppable:
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 accepttrue/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:
This prints the resolved per-function config and the derived seeds. See Diagnostics.