移植 OLLVM 到 Android NDK,Android Studio 中使用 OLLVM
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
OLLVM、LLVM 与 Android NDK
在 Android NDK 中,LLVM/Clang 是默认的编译器。自 Android NDK r18 开始,Google 弃用了 GCC,全面转向使用 LLVM/Clang 作为 NDK 的编译工具链。
NDK 中 LLVM 所在路径:/toolchains/llvm/prebuilt//bin/
查看 clang 版本,这里版本是 18.0.2
(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 中版本相近的 LLVM,具体可以参考这篇文章【编译 LLVM 源码,使用 Clion 调试 clang】
OLLVM 是 LLVM 的一个分支,增加了代码混淆功能(如控制流平坦化、指令替换),主要用于保护二进制代码的安全性。
关于如何移植 OLLVM 到 LLVM 可以参考下面的文章:
-
移植 OLLVM 到 LLVM 18,C&C++代码混淆
-
移植 OLLVM 到 LLVM18,修复控制流平坦化报错
编译 LLVM
1. 构建环境设置
创建并进入构建目录
mkdir build && cd build
配置编译目标
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
2. 编译
编译目标设置完成后,执行 ninja 开始编译。
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,并编译完成后可以在构建目录下看到同样也有相关目录
复制并替换 bin、include、lib 目录到 ndk 中
Android Studio 中使用 OLLVM
1. 创建 native 工程
2. 配置 OLLVM NDK
编辑 local.properties 添加 ndk.dir 配置为 ollvm ndk 路径
ndk.dir=D\:\\App\\android\\sdk\\ndk\\27.1.12297006
3. 代码实现
创建 OLLVMActivity,定义并调用 native 方法
/**
* 移植 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 方法
#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
add_library( # 设置库的名称
ollvm-lib
# 设置库的类型
SHARED
# 设置源文件路径
ollvm-lib.cpp)
4. 全局混淆
编辑 CMakeLists.txt,添加如下配置启用 OLLVM 混淆
# 全局启用指令替换
add_definitions("-mllvm -sub")
通过 -mllvm 选项开启 OLLVM 的代码混淆功能:
-
-mllvm -bcf:启用基本块控制流混淆。
-
-mllvm -fla:启用控制流平坦化。
-
-mllvm -sub:启用指令替换。
5. 动态库混淆
编辑 CMakeLists.txt,只为 ollvm-lib 动态库启用虚假控制流
# 为 ollvm-lib 动态库启用虚假控制流
target_compile_options(
ollvm-lib
PRIVATE
-mllvm -bcf)
如果有多个编译项
target_compile_options(
ollvm-lib
PRIVATE
-mllvm -bcf # 启用 Bogus Control Flow 混淆
-mllvm -sub # 启用 Substitution 混淆
-mllvm -fla # 启用 Flattening 混淆
)
6. 函数混淆
通过注解为 fla 方法禁用虚假控制流和启用控制流平坦化
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());
}
7. 测试
编译运行正常
把 apk 中的 so 文件解压出来
使用 IDA 打开 libollvm-lib.so,可以看到 sub 函数反汇编视图如下(启用虚假控制流+指令替换)
bcf 函数反汇编视图(启用虚假控制流+指令替换)
fla 函数反汇编视图(禁用虚假控制流并启用控制流平坦化)
其他动态库中函数(未启用 OLLVM 混淆)
源码
-
OLLVM 源码:https://github.com/CYRUS-STUDIO/LLVM
-
Android OLLVM Demo 源码:https://github.com/CYRUS-STUDIO/AndroidExample