当前位置: 首页 > news >正文

移植 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/

word/media/image1.png

查看 clang 版本,这里版本是 18.0.2

(base) PS D:\App\android\sdk\ndk\27.1.12297006\toolchains\llvm\prebuilt\windows-x86_64\bin> ./clang --versionAndroid (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

word/media/image2.png
这是 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,并编译完成后可以在构建目录下看到同样也有相关目录

word/media/image3.png

复制并替换 bin、include、lib 目录到 ndk 中

word/media/image4.png

Android Studio 中使用 OLLVM

1. 创建 native 工程

word/media/image5.png

2. 配置 OLLVM NDK

编辑 local.properties 添加 ndk.dir 配置为 ollvm ndk 路径

ndk.dir=D\:\\App\\android\\sdk\\ndk\\27.1.12297006

word/media/image6.png

3. 代码实现

创建 OLLVMActivity,定义并调用 native 方法

/*** 移植 OLLVM 到 Android NDK*/
class OLLVMActivity : AppCompatActivity() {// 声明 native 方法external fun sub(a: Int, b: Int): Intexternal 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) = $subResultbcf("Hello OLLVM!") = $bcfResultfla(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-libPRIVATE-mllvm -bcf)

如果有多个编译项

target_compile_options(ollvm-libPRIVATE-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. 测试

编译运行正常

word/media/image7.png

把 apk 中的 so 文件解压出来

word/media/image8.png

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

word/media/image9.png

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

word/media/image10.png

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

word/media/image11.png

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

word/media/image12.png

源码

  • OLLVM 源码:https://github.com/CYRUS-STUDIO/LLVM

  • Android OLLVM Demo 源码:https://github.com/CYRUS-STUDIO/AndroidExample

http://www.lryc.cn/news/508305.html

相关文章:

  • DAY36|动态规划Part04|LeetCode:1049. 最后一块石头的重量 II、494. 目标和、474.一和零
  • Linux 下SVN新手操作手册
  • 障碍感知 | 基于KD树的障碍物快速处理(附案例分析与ROS C++仿真)
  • Electron -- Electron Fiddle(一)
  • 详解Redis的常用命令
  • elasticache备份
  • Tomcat负载均衡全解析
  • [LeetCode-Python版] 定长滑动窗口8——2461. 长度为 K 子数组中的最大和
  • springboot476基于vue篮球联盟管理系统(论文+源码)_kaic
  • 预约参观华为基地,见证行业巅峰
  • 【Flink-scala】DataSet编程模型介绍及数据源
  • Odrive源码分析(四) 位置爬坡算法
  • [Unity Shader][图形渲染] Shader数学基础11 - 复合变换详解
  • 使用Python实现智能家居控制系统:开启智慧生活的钥匙
  • 使用 HTML5 Canvas 实现动态蜈蚣动画
  • 计算机视觉目标检测——DETR(End-to-End Object Detection with Transformers)
  • uniapp .gitignore
  • JavaWeb Servlet的反射优化、Dispatcher优化、视图(重定向)优化、方法参数值获取优化
  • 备忘一个FDBatchMove数据转存的问题
  • CEF127 编译指南 MacOS 篇 - 编译 CEF(六)
  • 【更新】LLM Interview
  • Django 视图中使用 Redis 缓存优化查询性能
  • 正则表达式解析与功能说明
  • STUN服务器实现NAT穿透
  • 音视频入门基础:MPEG2-TS专题(19)——FFmpeg源码中,解析TS流中的PES流的实现
  • tomcat的安装以及配置(基于linuxOS)
  • 因子分解(递归)
  • 【Python】pandas库---数据分析
  • RabbitMQ 的7种工作模式
  • 负载均衡式在线OJ