Obfuscation of Rust code with LLVM

LLVM and Obfuscation-LLVM (OLLVM)
Back in the days, in 2010, the University of Applied Sciences and Arts Western Switzerland of Yverdon-les-Bains published a fork of LLVM providing code obfuscation. Sadly support for this project stopped in 2016 on LLVM 4.0
Last year we looked to revitalize this project by making it a pass-plugin library able to be build out-of-tree and keeping up to date with LLVM. Currently this project is available on github at eshard/obfuscator-llvm
The goals of this project are:
- Obfuscate Rust code
- Follow LLVM updates
- Extend passes testing
- Cross-compile
- Make it easy to add new passes
One of the advantage of LLVM is its powerful intermediate representation (LLVM-IR) used for transformation and analysis during the compilation of source code (C, C++, Rust) to target binary (x86, ARM, RISC-V, Mips, …). Usually it’s used for optimizations and checking code correctness, but it also make it easy and language agnostic to implement obfuscation transformation.
As the Rust programming language is using LLVM we can leverage O-LLVM to provide code obfuscation with a shared library and few Cargo options.
In this article I will detail the steps to effectively obfuscate any Rust code.
Obfuscate Rust code
Recompile Rust’s LLVM fork
To compile our obfuscator we need LLVM headers compatible with the Rust version we are looking to use, in essence we will rebuild our own toolchain.
For this you need to setup Rust with rustup and the dependencies needed to rebuild LLVM.
For example on debian/ubuntu you need
sudo apt install python3 git build-essential curl cmake ninja-build pkg-config libssl-dev
Now choose a Rust version, currently the stable is on 1.79.0
rustc --version
# rustc 1.79.0 (129f3b996 2024-06-10)
# rustc 1.79.0 (129f3b996 2024-06-10)
Then clone the fork on the same tag.
mkdir ~/tmp && cd ~/tmp
git clone --depth 1 --branch 1.79.0 https://github.com/rust-lang/rust
cd rust
python3 x.py setup user # (this will setup the build env)
git clone --depth 1 --branch 1.79.0 https://github.com/rust-lang/rust
cd rust
python3 x.py setup user # (this will setup the build env)
And edit config.toml to add theses options:
[llvm]
clang = true
link-shared = true
download-ci-llvm = false
clang = true
link-shared = true
download-ci-llvm = false
You can check the other options available in config.example.toml (for example if you want build for multiple targets)
Now start the compilation of this new toolchain
python3 x.py build
Add custom toolchains to rustup
In the directory of the current build you will find stage1 and stage2 toolchains
ls build/x86_64-unknown-linux-gnu/
# llvm stage0 stage0-rustc stage0-std stage0-sysroot stage1 stage1-rustc stage1-std stage1-tools stage2 stage2-tools stage2-tools-bin
rustup toolchain link my-rust-stage1 $(readlink -f build/x86_64-unknown-linux-gnu/stage1)
rustup toolchain link my-rust-stage2 $(readlink -f build/x86_64-unknown-linux-gnu/stage2)
# llvm stage0 stage0-rustc stage0-std stage0-sysroot stage1 stage1-rustc stage1-std stage1-tools stage2 stage2-tools stage2-tools-bin
rustup toolchain link my-rust-stage1 $(readlink -f build/x86_64-unknown-linux-gnu/stage1)
rustup toolchain link my-rust-stage2 $(readlink -f build/x86_64-unknown-linux-gnu/stage2)
You can use either the stage1 or stage2 toolchain with respectively +my-rust-stage1 and +my-rust-stage2
We now need to build O-LLVM with the LLVM version we just rebuilt.
Build the obfuscation plugin
LLVM cmake files are located at build/x86_64-unknown-linux-gnu/llvm/lib/cmake
cd ~/tmp
git clone git@github.com:eshard/obfuscator-llvm.git
cd obfuscator-llvm
mkdir build && cd build
cmake -DLLVM_DIR=~/tmp/rust/build/x86_64-unknown-linux-gnu/llvm/lib/cmake ..
make -j8 && cp libLLVMObfuscator.so /tmp
git clone git@github.com:eshard/obfuscator-llvm.git
cd obfuscator-llvm
mkdir build && cd build
cmake -DLLVM_DIR=~/tmp/rust/build/x86_64-unknown-linux-gnu/llvm/lib/cmake ..
make -j8 && cp libLLVMObfuscator.so /tmp
Obfuscate Rust code
Within your project, you need to provide additional flags to use the newly built libLLVMObfuscator.so and select which passes you can to use.
Using Cargo, add the following rustflags inside .cargo/config
[target.x86_64-unknown-linux-gnu]
rustflags = [
"-Z", "llvm-plugins=/tmp/libLLVMObfuscator.so",
"-C", "passes=flattening bogus",
]
rustflags = [
"-Z", "llvm-plugins=/tmp/libLLVMObfuscator.so",
"-C", "passes=flattening bogus",
]
Lastly build your project with cargo
cargo +my-rust-stage1 build
Passes selection
When obfuscating code, you can insert passes (code modification) at different stages of the compilation to alter the generated LLVM-IR before it’s compiled to the target. To select which pass to apply at which stage ollvm is looking for environment variables,
- Peephole
usingLLVM_OBF_PEEPHOLE_PASSES - ScalarOptimizerLate
usingLLVM_OBF_SCALAROPTIMIZERLATE_PASSES - VectorizerStart
usingLLVM_OBF_VECTORIZERSTART_PASSES - PipelineStart
usingLLVM_OBF_PIPELINESTART_PASSES - PipelineEarlySimplification
usingLLVM_OBF_PIPELINEEARLYSIMPLIFICATION_PASSES - OptimizerLast
usingLLVM_OBF_OPTIMIZERLASTEP_PASSES
For example, to apply the passes “bogus” and “flattening” at the VectorizerStart stage do
export LLVM_OBF_VECTORIZERSTART_PASSES="bogus, flattening"
You can also provide multiples stages and the same pass multiples times
export LLVM_OBF_PEEPHOLE_PASSES="split-basic-blocks, split-basic-blocks, flattening"
export LLVM_OBF_SCALAROPTIMIZERLATE_PASSES="bogus"
export LLVM_OBF_SCALAROPTIMIZERLATE_PASSES="bogus"
Note: adding more passes will lead to more transformations and will lead to higher memory consumption during compilation and slower execution
Exemple on Alloy
Searching at random on github for a well tested rust project I stumbled upon alloy-rs
cd ~/tmp
git clone https://github.com/alloy-rs/core.git
cd core && mkdir .cargo
cat << EOF >> .cargo/config
[target.x86_64-unknown-linux-gnu]
rustflags = [
"-Z", "llvm-plugins=/tmp/libLLVMObfuscator.so",
]
EOF
export LLVM_OBF_PEEPHOLE_PASSES="split-basic-blocks, split-basic-blocks, flattening"
export LLVM_OBF_SCALAROPTIMIZERLATE_PASSES="bogus"
cargo +my-rust-stage1 test
git clone https://github.com/alloy-rs/core.git
cd core && mkdir .cargo
cat << EOF >> .cargo/config
[target.x86_64-unknown-linux-gnu]
rustflags = [
"-Z", "llvm-plugins=/tmp/libLLVMObfuscator.so",
]
EOF
export LLVM_OBF_PEEPHOLE_PASSES="split-basic-blocks, split-basic-blocks, flattening"
export LLVM_OBF_SCALAROPTIMIZERLATE_PASSES="bogus"
cargo +my-rust-stage1 test
