Skip to content

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.