Skip to content

About

LLVM Obfuscator

Resources

Code of conduct

Contributing

Security policy

Stars

7 stars

Watchers

0 watching

Forks

Latest commit

 

History

66 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LLVM Obfuscator (LLVM 23.1.3)

Based on upstream llvmorg-23.1.3 at commit 0d261d1ca552c95a8f007e061c787ac7132fbcbc.

Clang-exposed transforms

  • -mllvm -sobf: string obfuscation
  • -mllvm -sub: instruction substitution
  • -mllvm -sub_loop=<N>: substitution iteration count (default 1)
  • -mllvm -split: basic block splitting
  • -mllvm -split_num=<N>: splits per block (default 2, valid 2..10)
  • -mllvm -bcf: bogus control flow
  • -mllvm -bcf_prob=<N>: block obfuscation probability in % (default 30, valid 1..100)
  • -mllvm -bcf_loop=<N>: BCF loop count (default 1, valid >0)
  • -mllvm -fla: control-flow flattening
  • -mllvm -constenc -mllvm -constenc-values=i32:0x5a17: selected integer-constant encoding
  • -mllvm -gai -mllvm -gai-only-globals=state: selected private integer-global access indirection

All seven transforms also have named new-pass-manager passes in opt: obf-string, obf-sub, obf-split, obf-bcf, obf-fla, obf-const, and obf-global-access. Use -obf-only-functions for exact LLVM function names, -sobf-only-globals for selected string storage, and -obf-report-skips for eligibility diagnostics. Numeric controls reject invalid values.

The Clang build manifest launcher collects effects across parallel translation units and can enforce strict selection after the complete build. Compiler events establish pass activity; verify runtime behavior and final code/data separately.

Rust and Cargo

The fork includes a pinned Rust 1.99 compiler/sysroot and Cargo launcher. It supports package and target selection, Rust byte strings, ordinary regions inside unwind functions, and pre-link selection across local/cross-crate LTO. The custom sysroot includes the test harness and rustdoc. See build instructions, installation and delivery, and Cargo configuration. Stock rustc is not interchangeable with this toolchain. Consult the exact revision's native extracted-archive CI results for platform qualification.

split, bcf, and fla are now wired into Clang's new PM pipeline:

  • O0: pipeline-start extension point
  • O1+: optimizer-last extension point

Build

From the repository root:

cmake -S llvm -B build-llvm-project \
  -DLLVM_ENABLE_PROJECTS=clang \
  -DLLVM_ENABLE_ASSERTIONS=ON \
  -DCMAKE_BUILD_TYPE=Release

cmake --build build-llvm-project --target clang opt llvm-objdump ObfuscationTests -j8

Verify the transforms

The smoke test compiles each transform separately and together at -O0 and -O2. It checks emitted IR, verifies it with opt, and compares executable results with an independent oracle. It also checks function annotations.

python3 tests/obfuscation/run_portable.py \
  --clang build-llvm-project/bin/clang \
  --opt build-llvm-project/bin/opt \
  --objdump build-llvm-project/bin/llvm-objdump \
  --work-dir build-llvm-project/portable-smoke

The integration test builds HTTPS clients in C, C++, Objective-C, and Objective-C++. Each client follows a redirect, verifies a local TLS certificate, parses two different HTML pages, and reports the title, link count, and total path length. The test checks all five transforms separately and together at -O0 and -O2, verifies each emitted IR module, and runs every executable. It needs pkg-config, OpenSSL, libcurl development files, and an Objective-C runtime with development files.

python3 tests/obfuscation/test_http_programs.py \
  --clang build-llvm-project/bin/clang \
  --opt build-llvm-project/bin/opt \
  --work-dir build-llvm-project/http-integration

GitHub Actions runs the portable suite across the four native Rust hosts and the HTTPS suite on Linux. Tag releases require extracted LLVM/Clang and Rust archive gates before publishing both toolchain families. Rust releases retain checksums, per-host smoke reports, and benchmark evidence with their archives.

The CI suite also compiles Objective-C and Objective-C++ metadata probes for the Apple, GNUstep, and GCC runtimes at both optimization levels. This guards class and selector registration while string obfuscation is enabled.

Usage

Compiler path used below (from the repository root):

CLANG="$PWD/build-llvm-project/bin/clang"

macOS note

If standard headers are missing, pass SDK sysroot:

SDKROOT=$(xcrun --show-sdk-path)

and add -isysroot "$SDKROOT" to commands.

Example commands

# string obfuscation
$CLANG -isysroot "$SDKROOT" -O2 -mllvm -sobf hello.c -o hello.obf

# substitution
$CLANG -isysroot "$SDKROOT" -O2 -mllvm -sub -mllvm -sub_loop=2 hello.c -o hello.sub

# split / bcf / flattening
$CLANG -isysroot "$SDKROOT" -O2 -mllvm -split hello_complex.c -o hello_complex.split
$CLANG -isysroot "$SDKROOT" -O2 -mllvm -bcf hello_complex.c -o hello_complex.bcf
$CLANG -isysroot "$SDKROOT" -O2 -mllvm -fla hello_complex.c -o hello_complex.fla

# combined control-flow obfuscation
$CLANG -isysroot "$SDKROOT" -O2 \
  -mllvm -split -mllvm -bcf -mllvm -fla \
  hello_complex.c -o hello_complex.all

Function-Level Annotations

These passes use annotations from llvm.global.annotations:

__attribute__((annotate("sub")))   int f(int x) { ... }
__attribute__((annotate("nosub"))) int g(int x) { ... }

__attribute__((annotate("split")))   int h(int x) { ... }
__attribute__((annotate("nosplit"))) int i(int x) { ... }

__attribute__((annotate("bcf")))   int j(int x) { ... }
__attribute__((annotate("nobcf"))) int k(int x) { ... }

__attribute__((annotate("fla")))   int m(int x) { ... }
__attribute__((annotate("nofla"))) int n(int x) { ... }

Behavior:

  • If a pass flag is enabled (-mllvm -sub, -mllvm -split, -mllvm -bcf, -mllvm -fla), it applies broadly to eligible functions unless no<pass> is present.
  • If a flag is not enabled, a positive annotation (sub, split, bcf, fla) still enables that pass for the function.

Quick Verification

Runtime checks

./hello.obf
./hello_complex.split
./hello_complex.bcf
./hello_complex.fla
./hello_complex.all

Expected sample output:

  • hello.obf: Hello from ported LLVM obfuscation: 42
  • hello_complex.*: complex result: 56

IR checks

$CLANG -isysroot "$SDKROOT" -O2 -S -emit-llvm hello_complex.c -o hello_complex.base.ll
$CLANG -isysroot "$SDKROOT" -O2 -S -emit-llvm -mllvm -split hello_complex.c -o hello_complex.split.ll
$CLANG -isysroot "$SDKROOT" -O2 -S -emit-llvm -mllvm -bcf hello_complex.c -o hello_complex.bcf.ll
$CLANG -isysroot "$SDKROOT" -O2 -S -emit-llvm -mllvm -fla hello_complex.c -o hello_complex.fla.ll

What to look for:

  • split: noticeably more basic blocks / br instructions vs baseline.
  • bcf: inserted opaque-predicate globals like @x and @y.
  • fla: dispatcher-style flattened CFG with extra state/switch structure.

String obfuscation sanity check

strings ./hello.obf | grep "Hello from ported LLVM obfuscation" || true

Expected: no plaintext match.

Implementation Notes

String obfuscation

  • Clang's -sobf encodes constant C-string globals; the Rust pipeline also handles eligible non-NUL byte arrays, UTF-8, and embedded NUL
  • skips LLVM metadata, Objective-C runtime metadata, and constant-string backing storage used before startup constructors run
  • uses per-byte rolling mask in decode
  • emits decode startup via @llvm.global_ctors
  • preserves explicit sections and strings reachable from priority-zero constructors; observable decoder accesses prevent optimization from restoring short plaintext literals

Substitution

  • integer/int-vector substitutions for add/sub/and/or/xor/mul
  • skips add/sub/mul with nsw/nuw to avoid semantic drift

Control-flow transforms

  • split: random split points per block
  • bcf: inserts opaque predicates and altered blocks
  • fla: rewrites CFG into dispatcher loop/switch form
  • CFG passes preserve protected EH regions and report exclusions; BCF skips naked functions whose assembly controls register and stack use

Troubleshooting

  • fatal error: 'stdio.h' file not found:
    • use -isysroot "$(xcrun --show-sdk-path)" on macOS
  • very long compile times with heavy obfuscation:
    • reduce -bcf_prob, -bcf_loop, and/or -split_num

About

LLVM Obfuscator

Resources

Code of conduct

Contributing

Security policy

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages