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

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

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): 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. 测试

编译运行正常

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.kler.cn/a/453971.html

相关文章:

  • 【Python运维】自动化备份与恢复系统的实现:Python脚本实战
  • Vue开源项目Pure Admin二次开发:实现前后端柱状图
  • uniapp 文本转语音
  • VSCode 插件开发实战(十二):如何集成Git操作能力
  • 【VScode】第三方GPT编程工具-CodeMoss安装教程
  • 音视频入门基础:AAC专题(13)——FFmpeg源码中,获取ADTS格式的AAC裸流音频信息的实现
  • 【开源免费】基于SpringBoot+Vue.JS植物健康系统(JAVA毕业设计)
  • 1847. 最近的房间
  • 【OCR】数据集合集!
  • 操作002:HelloWorld
  • 使用EasyExcel来动态生成表头
  • 安全筑堤,效率破浪 | 统一运维管理平台下的免密登录应用解析
  • 【Go】-限流器的四种实现方法
  • stm32能跑人工智能么
  • 【源码】Sharding-JDBC源码分析之SQL中影子库ShadowSQLRouter路由的原理
  • SOME/IP 入门1
  • C# 2024/12/26 周四
  • 【大语言模型】ACL2024论文-36 利用NLI和ChatGPT及编码簿知识进行零样本政治关系分类
  • LabVIEW生物医学信号虚拟实验平台
  • 我用Cursor+DeepSeek做了个飞书文档一键同步插件,免费使用!
  • Java八股汇总【MySQL】
  • 代码随想录算法训练营第五十二天 | 101. 孤岛的总面积 102.沉没孤岛 103.水流问题 104.建造最大岛屿
  • 流批一体向量化计算引擎 Flex 在蚂蚁的探索和实践
  • Java爬虫实战:深度解析VIP商品详情获取技术
  • AWS IAM Roles Anywhere 使用 OpenSSL 自签 CA 过程
  • 黑马Java面试教程_P9_JVM虚拟机