-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
split, bcf, and fla are now wired into Clang's new PM pipeline:
O0: pipeline-start extension pointO1+: optimizer-last extension point
From /Users/moloch/git/llvm-obfuscator:
cmake -S llvm-project/llvm -B build-llvm-project \
-DLLVM_ENABLE_PROJECTS=clang \
-DCMAKE_BUILD_TYPE=Release
cmake --build build-llvm-project --target clang opt -j8Compiler path used below:
CLANG=/Users/moloch/git/llvm-obfuscator/build-llvm-project/bin/clangIf 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.
- obfuscates constant C-string globals (
ConstantDataSequential::isCString()) - skips metadata and ObjC method-name sections
- uses per-byte rolling mask in decode
- emits decode startup via
@llvm.global_ctors
- 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
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