Based on upstream llvmorg-23.1.3
at commit 0d261d1ca552c95a8f007e061c787ac7132fbcbc.
-mllvm -sobf: string obfuscation-mllvm -sub: instruction substitution-mllvm -sub_loop=<N>: substitution iteration count (default1)-mllvm -split: basic block splitting-mllvm -split_num=<N>: splits per block (default2, valid2..10)-mllvm -bcf: bogus control flow-mllvm -bcf_prob=<N>: block obfuscation probability in%(default30, valid1..100)-mllvm -bcf_loop=<N>: BCF loop count (default1, 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.
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 pointO1+: optimizer-last extension point
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 -j8The 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-smokeThe 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-integrationGitHub 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.
Compiler path used below (from the repository root):
CLANG="$PWD/build-llvm-project/bin/clang"If standard headers are missing, pass SDK sysroot:
SDKROOT=$(xcrun --show-sdk-path)and add -isysroot "$SDKROOT" to 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.allThese 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 unlessno<pass>is present. - If a flag is not enabled, a positive annotation (
sub,split,bcf,fla) still enables that pass for the function.
./hello.obf
./hello_complex.split
./hello_complex.bcf
./hello_complex.fla
./hello_complex.allExpected sample output:
hello.obf:Hello from ported LLVM obfuscation: 42hello_complex.*:complex result: 56
$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.llWhat to look for:
split: noticeably more basic blocks /brinstructions vs baseline.bcf: inserted opaque-predicate globals like@xand@y.fla: dispatcher-style flattened CFG with extra state/switch structure.
strings ./hello.obf | grep "Hello from ported LLVM obfuscation" || trueExpected: no plaintext match.
- Clang's
-sobfencodes 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
- integer/int-vector substitutions for add/sub/and/or/xor/mul
- skips add/sub/mul with
nsw/nuwto avoid semantic drift
split: random split points per blockbcf: inserts opaque predicates and altered blocksfla: 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
fatal error: 'stdio.h' file not found:- use
-isysroot "$(xcrun --show-sdk-path)"on macOS
- use
- very long compile times with heavy obfuscation:
- reduce
-bcf_prob,-bcf_loop, and/or-split_num
- reduce