Lightweight • Header-only • Hard-to-grep • Anti-static-analysis friendly
- Compile-time XOR obfuscation of string literals (
char[]&wchar_t[]) - Rolling XOR key stream — different key byte per position (16-byte cycle by default)
- Strong compile-time seed mixing (inspired by splitmix64 / wyhash)
- Lazy decryption (happens only when you first access the string)
- Automatic zeroization on scope exit (via RAII helper)
- Very simple & clean macro syntax
- Header-only — drop-in single file integration
- No external dependencies, no dynamic allocation
| Feature | Classic single XOR | This version (rolling) |
|---|---|---|
| Key per string | 1 byte | 16-byte cycling stream |
| Static analysis resistance | Low | Significantly higher |
| Easy to grep / pattern match | Very easy | Much harder |
| Key derivation | Weak shifts/mul | Strong mixer (wyhash-like) |
| Memory cleanup | Manual | Automatic RAII |
#include "obfuscator.h"
int main()
{
// Most common & safest way — decrypt + auto-zeroize when leaving scope
const char* msg = OBF_AUTO("kernel32.dll");
// One-time decrypt (stays decrypted until program ends or zeroized manually)
const char* api = OBF("CreateRemoteThread");
// Wide string versions
const wchar_t* wpath = OBF_W_AUTO(L"C:\\Windows\\Temp\\payload.bin");
wprintf(L"Path: %s\n", wpath);
printf("API name: %s\n", api);
// You can also use it inside functions, conditions, etc.
if (some_condition) {
MessageBoxA(NULL, OBF_AUTO("Critical error!"), OBF("Error"), MB_ICONERROR);
}
}| Macro | Behavior | Recommended? | Lifetime of decrypted data |
|---|---|---|---|
OBF("...") |
Lazy decrypt, stays decrypted in memory forever | Sometimes | Until program ends |
OBF_AUTO("...") |
Decrypt + auto zeroize when leaving scope | Yes | Only while in current scope |
OBF_W("L...") |
Wide version — lazy decrypt | Sometimes | Until program ends |
OBF_W_AUTO("L...") |
Wide version + auto zeroize on scope exit | Yes | Only while in current scope |
Recommendation: Use OBF_AUTO / OBF_W_AUTO in most cases — it's significantly safer as it minimizes the time sensitive strings remain in plaintext in memory.
All numbers are approximate, measured on modern hardware (Zen 4 / Intel 13th–14th gen, 2024–2025 compilers) with -O3 -march=native.
| Scenario | Plain literal (ns) | This lib (ns) | Vectorized xorstr-style (ns) | Overhead factor | Notes |
|---|---|---|---|---|---|
| Short string (~8–16 chars), cold | 0.5–2 | 10–25 | 4–12 | ~10–20× | First decrypt dominates |
| Short string, hot (cached) | 0.5–2 | ~1–3 | ~1–2 | ~1–3× | After decrypt(), near-zero cost |
| Medium (~32–64 chars), cold | 1–4 | 15–45 | 5–15 | ~5–15× | Modulo can limit auto-vectorization |
| Medium, hot | 1–4 | ~2–6 | ~1–3 | ~1–3× | |
| Long (~128+ chars), cold | 3–10 | 40–120 | 8–30 | ~5–15× | Vectorized wins big here |
| 10,000 calls/sec (typical logging) | negligible | <0.5% total | <0.2% total | — | Real apps: IO / crypto dominates |
| In tight loop (1M iter/sec) | — | 1–5% slowdown | <1% slowdown | — | Only if decrypt every iteration |