BruceFan's Blog

Stay hungry, stay foolish

0%

Mac编译OLLVM14 & 配置NDK

官方的OLLVM适配到LLVM-4.0就没有再更新了,不过网上有许多大佬开源的适配新版本LLVM的OLLVM可以参考。

环境及源码

Mac 14.6.1
Android Studio
NDK 25.1.8937393
cmake 3.31.5
ninja 1.12.1

下载OLLVM

NDK 25.1.8937393对应的LLVM版本为14.x,因此我从网上找了几个适配到14之后的OLLVM进行编译,最终使用的是https://github.com/heroims/obfuscator。

1
2
3
4
$ git clone --depth=1 -b release/14.x https://github.com/llvm/llvm-project.git
$ cd llvm-project
$ wget https://heroims.github.io/obfuscator/LegacyPass/ollvm14.patch
$ git apply --reject --ignore-whitespace ollvm14.patch

这里作者提供了两个版本的patch,我使用NewPass版本编译后,clang没有混淆的选项,可能是编译时没有配置正确,使用LegacyPass可以正常使用。

New Pass Manager是 LLVM 项目中对原有Legacy Pass Manager的重新设计和改进,从 LLVM 11 开始被引入,并在后续版本中逐步推广。新 Pass 管理器在性能、易用性和灵活性方面相较旧 Pass 管理器有许多优化,目前已经被推荐为默认的 Pass 管理方式。

  • reject选项,表示如果patch内容如果不能应用则生成.rej文件记录没有被应用的patch部分,可以后续手动修改,不过这里如果使用14.x版本,patch可以全部正常应用。
  • ignore-whitespace选项,表示比较和应用补丁时忽略空格变化。

编译OLLVM

1
$ cmake -S llvm -B build -G Ninja -DLLVM_ENABLE_PROJECTS="clang" -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_NEW_PASS_MANAGER=OFF
  • -G Ninja:使用 ninja 进行编译源码
  • -DLLVM_ENABLE_PROJECTS=”clang”:启用 clang,有多个选择 但我们只需要 clang,官方文档有说明
  • -DCMAKE_BUILD_TYPE=Release:构建 release 版本,比 debug 版本编译快很多
  • -DLLVM_INCLUDE_TESTS=OFF:关闭 llvm 的头文件测试,也是为了加快编译速度
  • -DLLVM_ENABLE_NEW_PASS_MANAGER=OFF:这个非常重要,llvm-12.x 开始默认使用 new pass manager 进行编译源码,导致 ollvm 不起作用

编译:

1
$ cmake --build build -j16

配置Android Studio

1.编译成功后,需要把build/bin目录中的clang clang++ clang-format clang-cl 4个文件复制到ndk对应目录中(最好先备份原文件):

1
~/Library/Android/sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/darwin-x86_64/bin

2.将build/lib/clang/14.0.6/include目录中的__stddef_max_align_t.h float.h stdarg.h stdbool.h stddef.h 5个文件也复制到ndk对应目录中:

1
~/Library/Android/sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/include

3.拷贝库文件

1
2
$ cd /Users/fanrong/Library/Android/sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/darwin-x86_64
$ cp -r lib64/clang lib

否则在编译arm架构时会报错缺少atomic或unwind等库文件,如果只编译arm64架构应该就不需要拷贝了。

NDK中使用OLLVM

参数 说明
-mllvm 传递给 LLVM 的内部选项
-fla 启用控制流平坦化(Control Flow Flattening)
-sub 启用指令替换(Instruction Substitution)
-sub_loop=3 一个函数应用3次sub,默认为1
-bcf 启用伪代码插入(Bogus Code Insertion)
-bcf_loop=3 一个函数应用3次bcf,默认为1
-bcf_prob=40 一个基本块会有40%的概率被混淆,默认为30%
-split 启用基本块分割,和fla一起用时增强fla
-split_num=3 一个基本块应用3次split,默认为1

Heroims 在移植 OLLVM 时,集成了 Armariris 的字符串混淆功能。(不过好像只对char *起作用了,对char []没生效)
| 参数 | 说明 |
| :—- | :—- |
| -sobf | 启用字符串加密 |

在 build.gradle 中进行配置,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
android {
namespace 'com.sankuai.reverse'
compileSdk 34

defaultConfig {
applicationId "com.sankuai.reverse"
minSdk 28
targetSdk 34
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-fvisibility=hidden"
cppFlags "-mllvm -fla -mllvm -sub -mllvm -sobf"
cFlags "-fvisibility=hidden"
cFlags "-mllvm -fla -mllvm -sub -mllvm -sobf"
}
}
ndk {
abiFilters "arm64-v8a"
}
}
...
ndkVersion '25.1.8937393'
}

如果需要单个函数混淆的情况:

1
2
3
4
5
6
7
8
__attribute((__annotate__("bcf")))
__attribute((__annotate__("fla")))
__attribute((__annotate__("sub")))
__attribute((__annotate__("split")))
__attribute((__annotate__("sobf")))
void func(){
xxx
}

从llvm-project源码看LLVM版本:
llvm-18:llvm-project/llvm/CMakeLists.txt
llvm-19:llvm-project/cmake/Modules/LLVMVersion.cmake

reference
Windows 编译 OLLVM NDK 使用
OLLVM代码混淆移植与使用(再续)
https://github.com/sr-tream/obfuscator/tree/master
移植 OLLVM 到 Android NDK,Android Studio 中使用 OLLVM
移植 OLLVM 到 LLVM 18,C&C++代码混淆
https://github.com/CYRUS-STUDIO/LLVM

https://llvm.org/docs/GettingStarted.html