版权归作者所有,如有转发,请注明文章出处:cyrus-studio.github.io/blog/
前言
在 Android 应用安全中,Native 层 so 库往往是最容易被逆向分析的目标 。无论是游戏的核心逻辑,还是 App 的关键算法,一旦 so 被反编译,核心代码就可能暴露无遗。
传统的 Java 层混淆工具(如 ProGuard、R8)对 C/C++ 代码无能为力,因此 NDK 层代码的保护 成了安全加固中的难点。解决思路:在编译阶段对 so 进行混淆处理 ,让逆向难度大幅提升。
LLVM 生态中有一个安全扩展 ------ OLLVM (Obfuscator-LLVM) ,它在编译流程里插入了混淆 Pass,能对 C/C++ 代码做 控制流平坦化、虚假控制流、指令替换 等处理,从而显著增加逆向门槛。
本文将带你实战:如何将 OLLVM 移植到 LLVM/NDK,并在 Android Studio 工程中使用它,为 Native 代码加上一层混淆保护。
OLLVM、LLVM 与 Android NDK
LLVM 是一个高度模块化的编译器框架,它能够将 C/C++ 等高级语言源码编译为中间表示(LLVM IR),再经过优化、生成目标机器码。它不仅仅是一个编译器,更是一个"编译基础设施"。
在 Android 平台上,自 NDK r18 开始,Google 就全面弃用了 GCC,转而采用 LLVM/Clang 作为官方工具链。也就是说,所有的 C/C++ 代码编译、优化、生成 so 库的过程,底层都是由 LLVM 驱动完成的。
OLLVM (Obfuscator-LLVM) 则是在 LLVM 基础上扩展的一个安全项目。它在 LLVM 编译流程中增加了混淆 Pass,可以对 C/C++ 代码进行 控制流平坦化、虚假控制流、指令替换 等混淆处理,从而有效提高逆向分析和反编译的难度,保护 Android 应用中 Native so 层的核心逻辑不被轻易破解。
最终实现编译流程大概如下:
java
┌──────────────────┐
│ C / C++ 源码 │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ LLVM (Clang) │ ← Android NDK 内置的官方编译器工具链
│ 前端:生成 IR │
└────────┬─────────┘
│ LLVM IR
▼
┌──────────────────┐
│ OLLVM Pass │ ← 基于 LLVM 的扩展:混淆(控制流平坦化、指令替换等)
│ (插入在中间) │
└────────┬─────────┘
│ 混淆后的 IR
▼
┌──────────────────┐
│ LLVM 后端优化 │
│ + 代码生成 │
└────────┬─────────┘
│ 汇编
▼
┌──────────────────┐
│ 链接生成 so │ ← 最终供 Android 应用调用的 native 库
└──────────────────┘
编译 LLVM
1. 下载源码
NDK 中 LLVM 所在路径:<android-ndk>/toolchains/llvm/prebuilt/<host-system>/bin/

查看 clang 版本,这里版本是 18.0.2
less
(base) PS D:\App\android\sdk\ndk\27.1.12297006\toolchains\llvm\prebuilt\windows-x86_64\bin> ./clang --version
Android (12285214, based on r522817b) clang version 18.0.2 (https://android.googlesource.com/toolchain/llvm-project d8003a456d14a3deb8054cdaa529ffbf02d9b262)
Target: x86_64-w64-windows-gnu
Thread model: posix
InstalledDir: D:/App/android/sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/windows-x86_64/bin
根据 NDK 中 clang 的版本,下载和编译版本相近的 LLVM。
关于 LLVM 源码下载和编译参考:LLVM 全面解析:NDK 为什么离不开它?如何亲手编译调试 clang
2. 构建环境设置
创建并进入构建目录
bash
mkdir build && cd build
配置编译目标
ini
cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="/utf-8" -DLLVM_ENABLE_RTTI=ON -DLLVM_ENABLE_EH=ON -DLLVM_ENABLE_PROJECTS="clang;lld" ../llvm
3. 编译
编译目标设置完成后,执行 ninja 开始编译。
ini
D:\Projects\llvm-project\build>ninja
[1651/2426] Building CXX object tools\lld\ELF\CMakeFiles\lldELF.dir\Arch\LoongArch.cpp.obj
D:\Projects\llvm-project\lld\ELF\Arch\LoongArch.cpp(705): warning C4334: "<<": 32 位移位的结果被隐式转换为 64 位(是否希望进行 64 位移位?)
[2426/2426] Linking CXX executaset PATH=%PATH%;D:\Projects\llvm-project\build\bin
移植 OLLVM 到 Android NDK
这是 Android NDK 中 toolchains\llvm\prebuilt\windows-x86_64 目录下的文件夹结构
其中主要几个文件夹:
-
bin:包含可执行文件,例如编译器(clang、clang++)、链接器(ld)等,主要用于 NDK 工具链的操作。
-
include:包含头文件,提供编译时所需的接口定义。例如,标准 C/C++ 库的头文件以及与 Android 平台相关的头文件。
-
lib:包含静态库和动态库,提供编译和链接时使用的库文件。例如,支持标准 C/C++ 函数的实现库。
这些文件共同组成了 Android NDK 的工具链,用于开发和调试 Android native 代码。
当我们成功把 OLLVM 移植到 LLVM,并编译完成后可以在构建目录下看到同样也有相关目录

关于 OLLVM 移植到 LLVM 过程参考:
复制并替换 bin、include、lib 目录到 ndk 中

Android Studio 中使用 OLLVM
1. 创建 native 工程

2. 配置 OLLVM NDK
编辑 local.properties 添加 ndk.dir 配置为 ollvm ndk 路径
ini
ndk.dir=D\:\\App\\android\\sdk\\ndk\\27.1.12297006

3. 代码实现
创建 OLLVMActivity,定义并调用 native 方法
kotlin
/**
* 移植 OLLVM 到 Android NDK
*/
class OLLVMActivity : AppCompatActivity() {
// 声明 native 方法
external fun sub(a: Int, b: Int): Int
external fun bcf(input: String?): String?
external fun fla(x: Int, y: Int): String?
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_ollvmactivity)
// 加载本地库
System.loadLibrary("ollvm-lib");
// 调用 native 方法并显示结果
val textView = findViewById<TextView>(R.id.textView)
val subResult = sub(10, 5)
val bcfResult = bcf("Hello OLLVM!")
val flaResult = fla(3, 2)
val resultText = """
sub(10, 5) = $subResult
bcf("Hello OLLVM!") = $bcfResult
fla(x, y) = $flaResult
""".trimIndent()
textView.text = resultText
}
}
创建 ollvm-lib.cpp 实现 native 方法
c
#include <jni.h>
#include <string>
// sub 方法:两个整数相减
extern "C" JNIEXPORT jint JNICALL
Java_com_cyrus_example_ollvm_OLLVMActivity_sub(JNIEnv* env, jobject, jint a, jint b) {
return a - b;
}
// bcf 方法:接收字符串并返回拼接后的字符串
extern "C" JNIEXPORT jstring JNICALL
Java_com_cyrus_example_ollvm_OLLVMActivity_bcf(JNIEnv* env, jobject, jstring input) {
const char* inputStr = env->GetStringUTFChars(input, nullptr);
std::string result = std::string("BCF: ") + inputStr;
env->ReleaseStringUTFChars(input, inputStr);
return env->NewStringUTF(result.c_str());
}
// fla 方法:两个int相加判断大小并返回结果字符串
extern "C" JNIEXPORT jstring JNICALL
Java_com_cyrus_example_ollvm_OLLVMActivity_fla(JNIEnv *env, jobject , jint x, jint y) {
int sum = x + y;
// 使用字符串流拼接结果
std::ostringstream result;
if (sum < 5) {
result << "x = " << x << ", y = " << y << ", x + y " << "小于 5";
} else if(sum == 5){
result << "x = " << x << ", y = " << y << ", x + y " << "等于 5";
} else{
result << "x = " << x << ", y = " << y << ", x + y " << "大于 5";
}
// 返回拼接好的字符串
return env->NewStringUTF(result.str().c_str());
}
编辑 CMakeLists.txt,添加动态库 ollvm-lib
bash
add_library( # 设置库的名称
ollvm-lib
# 设置库的类型
SHARED
# 设置源文件路径
ollvm-lib.cpp)
4. 全局混淆
编辑 CMakeLists.txt,添加如下配置启用 OLLVM 混淆
bash
# 全局启用指令替换
add_definitions("-mllvm -sub")
通过 -mllvm 选项开启 OLLVM 的代码混淆功能:
-
-mllvm -bcf:启用基本块控制流混淆。
-
-mllvm -fla:启用控制流平坦化。
-
-mllvm -sub:启用指令替换。
5. 动态库混淆
编辑 CMakeLists.txt,只为 ollvm-lib 动态库启用虚假控制流
vbnet
# 为 ollvm-lib 动态库启用虚假控制流
target_compile_options(
ollvm-lib
PRIVATE
-mllvm -bcf)
如果有多个编译项
bash
target_compile_options(
ollvm-lib
PRIVATE
-mllvm -bcf # 启用 Bogus Control Flow 混淆
-mllvm -sub # 启用 Substitution 混淆
-mllvm -fla # 启用 Flattening 混淆
)
6. 函数混淆
通过注解为 fla 方法禁用虚假控制流和启用控制流平坦化
c
extern "C" JNIEXPORT jstring JNICALL
__attribute__((annotate("nobcf,fla"))) Java_com_cyrus_example_ollvm_OLLVMActivity_fla(JNIEnv *env, jobject, jint x, jint y) {
int sum = x + y;
// 使用字符串流拼接结果
std::ostringstream result;
if (sum < 5) {
result << "x = " << x << ", y = " << y << ", x + y " << "小于 5";
} else if(sum == 5){
result << "x = " << x << ", y = " << y << ", x + y " << "等于 5";
} else{
result << "x = " << x << ", y = " << y << ", x + y " << "大于 5";
}
// 返回拼接好的字符串
return env->NewStringUTF(result.str().c_str());
}
测试与验证
编译运行正常

把 apk 中的 so 文件解压出来

使用 IDA 打开 libollvm-lib.so,可以看到 sub 函数反汇编视图如下(启用虚假控制流+指令替换)

bcf 函数反汇编视图(启用虚假控制流+指令替换)

fla 函数反汇编视图(禁用虚假控制流并启用控制流平坦化)

其他动态库中函数(未启用 OLLVM 混淆)

完整源码
-
OLLVM 开源地址:github.com/CYRUS-STUDI...
-
Android OLLVM Demo 开源地址:github.com/CYRUS-STUDI...