String encryption — strenc¶
Module-only. Finds string-literal globals whose length meets a threshold and encrypts them at
compile time; a .init_array constructor decrypts them at process load. The plaintext strings — the
easiest thing to grep in a binary — are gone from the static image.
Enabled when at least one annotated function includes strenc(...) anywhere in the module.
Ciphers¶
Selected via the cipher key. Selection is resolved module-wide: if any annotated function opts
into a stronger cipher (or keysplit), it applies to every encrypted string in the module.
| Cipher | Notes |
|---|---|
aes (default) |
AES-128-CTR. Uses the shared __obf_aes_ctr_decrypt runtime — the same stub the vm pass links when it encrypts bytecode. |
chacha |
ChaCha20 (tableless: 256-bit key + 96-bit nonce, no AES S-box tables in the binary). Takes precedence over aes when both are requested. |
xor |
Legacy XOR keystream. Weakest; a lightweight fallback. |
Options¶
| Key | Default | Range | Meaning |
|---|---|---|---|
minlen / minLength / min |
4 | 1–100 | Minimum string length to encrypt. |
cipher |
aes |
aes/chacha/xor |
Cipher selection. chacha wins over aes. |
aes |
1 | 0/1 | Shorthand toggle for the AES path. aes=0 falls back to XOR (unless cipher=chacha). |
keysplit |
1 | 0/1 | AES only — split the 176-byte AES round-key schedule across module segments so the full key never appears contiguously. |
Examples¶
// Default AES-128-CTR
__attribute__((annotate("obf: strenc(minlen=6)")))
void init(void) { puts("confidential string"); }
// ChaCha20 — no AES tables in the binary
__attribute__((annotate("obf: strenc(minlen=4,cipher=chacha)")))
void init2(void) { puts("another secret"); }
Pick ChaCha to shrink your fingerprint
AES S-box tables are themselves a recognisable signature. cipher=chacha is tableless — no AES
constants land in the binary — which both reduces size and removes that fingerprint. keysplit
(AES path) is the complementary defence: it stops the round-key schedule from appearing as one
contiguous, greppable blob.