経緯
リバースエンジニアリングがむずい最強バイナリをつくりたいよね。
obfuscator-llvmが難読化手法としてあるがRustには対応していない。でもRustのコンパイルの仕組み的には途中にLLVM-IRを挟むので行けそう...?
ということでやってみた
パッチあてる作業がかなり面倒なのであてたものがこちらです。
手順
- LLVMにパッチをあてる
- config.tomlを編集しRustをソースコードからビルドする
今回こちらを参考に1.76.0のバージョンに対応させました。
リポジトリのクローン
Hikari-LLVM15にはLLVMバージョン17.0.6と18.0.8がある。記事作成時点では18.0.8を使っているRustバージョンがなかったので17.0.6を使うことに。
Rustが使用しているLLVMの情報の調べ方
例
$ ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustc --version --verbose
rustc 1.77.0 (aedd173a2 2024-03-17)
binary: rustc
commit-hash: aedd173a2c086e558c2b66d3743b344f977621a7
commit-date: 2024-03-17
host: x86_64-unknown-linux-gnu
release: 1.77.0
LLVM version: 17.0.6
今回使用するRustのバージョンを1.76.0にした。Rustのソースコード,Rust用LLVMのソースコード,差分を取るためにHikari-LLVMをクローンする
git clone --single-branch --branch 1.76.0 --depth 1 https://github.com/rust-lang/rust rust-1.76.0
git clone --single-branch --branch rustc-1.76.0 --depth 1 https://github.com/rust-lang/llvm-project llvm-17.6
git clone --single-branch --branch llvm-17.6.0rel --recursive --depth 1 https://github.com/61bcdefg/Hikari-LLVM15 ollvm-17.6
LLVMの修正・ビルド
差分を取る
patchをあてるために差分を取る。
git diff llvm-17.6/llvm ollvm-17.6/llvm/ > llvm.patch
権限変更に関する情報が多かったので魔法の呪文を唱えて再度差分をとった。
chmod 777 *
不要そうなtestなどの変更を削除しvimで%s/llvm-17.6///gと%s/ollvm-17.6///gでパスを修正しパッチをあてられるようにする。ここら辺のどれを削除したとかあまり記憶にない(うごいたからよし!状態)なのでこちらのファイルを参照してください。
パッチをあてる
cd llvm-17.6
git apply --reject --ignore-whitespace ../llvm.patch
ビルド
clangなどを用意する。バージョンは17。他にはcmakeとninjaが必要だったけどバージョンはそんなに重要ではない。
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 17
mkdir build && cd build
cmake -G "Ninja" ../llvm -DCMAKE_INSTALL_PREFIX="./llvm_x64" \
-DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;lld;" -DLLVM_TARGETS_TO_BUILD="X86" \
-DBUILD_SHARED_LIBS=ON -DLLVM_INSTALL_UTILS=ON -DLLVM_INCLUDE_TESTS=OFF -DLLVM_BUILD_TESTS=OFF \
-DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_BUILD_BENCHMARKS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF \
-DLLVM_ENABLE_BACKTRACES=OFF -DLLVM_BUILD_DOCS=OFF -DBUILD_SHARED_LIBS=OFF \
-DCMAKE_CXX_COMPILER=clang++-17 -DCMAKE_C_COMPILER=clang-17
問題が起きなければ引き続き...jに続く数字はお好みで
cmake --build . -j16
cmake --install .
Rustのビルド
cd rust-1.76.0
cp config.example.toml config.toml
config.example.tomlがあるのでコピーしてconfig.tomlを作成。
- [rust]セクション内 debug=false
- [rust]セクション内 channel = "nightly"
- [target.x86_64-unknown-linux-gnu]セクション内 llvm-config = "/home/user/.../llvm-17.6/build/bin/llvm-config" 先ほどコンパイルした際に生成されたllvm-configのパスを指定する。
設定が終わったら
python3 ./x.py build
python3 ./x.py build --stage 2 rustc
python3 ./x.py build cargo
python3 ./x.py build --stage 2 cargo
でコンパイルされることを祈りましょう。
$ ls rust-1.76.0/build/x86_64-unknown-linux-gnu
doc/ stage0-codegen/ stage0-sysroot/ stage1/ stage1-tools/ stage2-std/
md-doc/ stage0-rustc/ stage0-tools/ stage1-rustc/ stage1-tools-bin/ stage2-tools/
stage0/ stage0-std/ stage0-tools-bin/ stage1-std/ stage2/ stage2-tools-bin
stage0→stage1→stage2の順でコンパイルされていきます。数字が大きいほうがいいと思ってくれれば大丈夫です。コンパイラのビルドプロセスを参考にしてください。
ということでstage2にはrustcがstage2-tools-binにはcargoが入っていると思います。
実験
適当なプログラムをコンパイルしてみる
fn main() {
println!("Hello, world!");
}
-Cllvm-argsで難読化オプションを指定する。指定できるオプションはこちら
$ ./rust-1.76.0/build/x86_64-unknown-linux-gnu/stage2/bin/rustc -Cllvm-args=-enable-allobf hello.rs
解析
とりあえずstripせずに解析
main関数ですが不明なスタック変数が大量にあり更にジャンプ先がわかりません。アセンブリを見たところjmp raxとなっていました。println!におそらく使われるstd :: io :: stdio :: _printのxrefを見ましたがなにもありませんでした。