Mac(ARM)下编译OLLVM并集成NDK25

本文介绍在Mac ARM架构(M1/M2/M3芯片)下,完成OLLVM混淆编译并集成至NDK25的步骤。

环境准备

Mac M2
Python: 3.12.4
Cmake: 4.0.0
ninja
clang:
Apple clang version 17.0.0 (clang-1700.0.13.3)
Target: arm64-apple-darwin24.4.0

Android Gradle plugin:gradle-8.7
NDK:25.1.8937393
LLVM: 14.0.6

编译OLLVM所需的版本需与LLVM保持一致,而LLVM版本由NDK决定。

Android Gradle plugin 的 gradle-8.7 版本默认使用的 NDK 版本为25.1.8937393,对应的 LLVM 为14.0.6

可通过以下方式查询LLVM版本号:

Sdk_DIR\ndk\$version\toolchains\llvm\prebuilt\windows-x86_64\AndroidVersion.txt

Android开发中,若不希望使用Gradle插件默认的NDK版本,可以在module的 build.gradle 文件中手动指定所需的NDK版本。

>android {
>ndkVersion "25.1.8937393"
>}

获取源码

可以在 github/heroims/obfuscator 仓库的分支中找到对应的移植源码

LLVM14源码

git clone -b release/14.x git@github.com:llvm/llvm-project.git

OLLVM源码

cd llvm-project		#进入LLVM14源码目录
wget https://heroims.github.io/obfuscator/LegacyPass/ollvm14.patch #NewPass混淆失败
git apply ollvm14.patch

编译OLLVM

1、因为 LLVM 使用 CMake 构建系统,必须先通过 CMake 配置生成构建文件,才能执行编译。

#进入LLVM14源码目录,执行以下命令
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
参数 含义
-S llvm 指定源码路径为当前目录下的llvm文件夹
-B build 指定构建输出路径为当前目录下的build文件夹
-G Ninja 使用 ninja 作为构建工具(更快、更轻量)
-DLLVM_ENABLE_PROJECTS=”clang” 只构建 clang 项目(OLLVM 混淆通常只需 Clang)
-DCMAKE_BUILD_TYPE=Release 编译为发布版,开启优化,关闭调试信息
-DLLVM_INCLUDE_TESTS=OFF 不构建测试用例(节省时间)
-DLLVM_ENABLE_NEW_PASS_MANAGER=OFF 关闭NewPass管理器,启用LegacyPass(兼容OLLVM混淆)

-G Ninja

如果不指定Ninja则会根据系统的默认环境自动选择一个构建工具。

可通过下列命令查看默认构建工具

>cmake --help

查看输出中的Generators部分,带 * 的那一项就是当前默认,类似如下。

>The following generators are available on this platform (* marks default):
>* Unix Makefiles = Generates standard UNIX makefiles.
Ninja = Generates build.ninja files.

-DLLVM_ENABLE_PROJECTS=”clang”

除此以外还支持 clang-tools-extra, lldb, lld, polly, or cross-project-tests

-DLLVM_ENABLE_NEW_PASS_MANAGER=OFF

这个非常重要,llvm-12.x 开始默认使用 newPM 进行编译源码,导致 ollvm 不起作用!

因此,需要加上这个参数禁用掉 newPM (在每个编译时增加 flag -flegacy-pass-manager 让 llvm 不走 newPM 编译也可以,但没必要)

LLVM官方文档:https://llvm.org/docs/GettingStarted.html

执行以上命令后若提示 Configuration done. 则配置成功。

image-20250430141111865

2、接下来执行以下命令开始编译

cmake --build build -j8

其中 -j8 为指定的线程数,需要根据 CPU 调整。然后等待编译完成。

在 macOS 终端中通过以下命令查询 CPU 核心数:

sysctl -n hw.ncpu

执行完命令后进度条拉满及编译完成。

image-20250430155950539

NDK集成OLLVM

1、编译成功后需要把 build/bin 目录中的以下文件文件复制出来,方便后面操作:

  • clang
  • clang++
  • clang-[VERSION]
  • clang-format
  • clang-cl

注意这里的 [VERSION] 是你的 LLVM 主版本号,如LLVM14对应的则为clang-14。

同理,接着把 build/lib/clang/[VERSION]/include 目录中的也复制出来:

  • __stddef_max_align_t.h
  • float.h
  • stdarg.h
  • stddef.h
  • stdbool.h

这是需要复制的文件结构内容:

├── build
│ └── bin
│ ├── clang
│ ├── clang++
│ ├── clang-[VERSION]
│ └── clang-format
│ └── clang-cl
└── lib
└── clang
└── [VERSION] #14.0.6
└── include
├── __stddef_max_align_t.h
├── float.h
├── stdarg.h
├── stdbool.h
└── stddef.h

编译后可以使用 strip clang 命令来去除 Clang 可执行文件中的符号信息,从而减小体积,适用于部署阶段。

2、把前面 bin 目录的 4 个文件复制到:

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

# Windows
C:\Users\xiaoqi\AppData\Local\Android\Sdk\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin

覆盖之前文件(覆盖前请备份)根据自己电脑情况做修改。

接着把 include 目录下 5 个文件复制到:

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

# Windows
C:\Users\xiaoqi\AppData\Local\Android\Sdk\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\sysroot\usr\include

因为编译使用了 property 系统的头文件读取系统属性,会遇到 stdbool.h 缺失,所以 H 文件也必须复制过去。

3、如果此时编译会出现找不到 libunwind 等库的错误,错误信息显示目录文件不存在

则将SDK_DIR/ndk/25.1.8937393/toolchains/llvm/prebuilt/darwin-x86_64/lib64/目录下的calng 目录,复制到 SDK_DIR/ndk/25.1.8937393/toolchains/llvm/prebuilt/darwin-x86_64/lib 目录中

至此,NDK 中 集成 OLLVM 已经完成了。

SO编译配置

参数 说明
-mllbm -sub 激活指令替换
-mllvm -sub_loop=3 如果激活了传递,则在函数上应用3次。默认值:1
-mllvm -bcf 激活虚假控制流程
-mllvm -bcf_loop=3 如果激活了传递,则在函数上应用3次。默认值:1
-mllvm -bcf_prob=40 如果激活了传递,基本块将以40%的概率进行模糊处理。默认值:30
-mllvm -fla 激活控制流扁平化
-mllvm -split 激活基本块分割。在一起使用时改善展平
-mllvm -split_num=3 如果激活了传递,则在每个基本块上应用3次。默认值:1

Heroims 在移植 OLLVM 时,集成了 Armariris 的字符串混淆功能。

参数 说明
-mllvm -sobf 编译时候添加选项开启字符串加密
-mllvm -seed= 指定随机数生成器种子

build.gradle 中进行配置,如:

android {
defaultConfig {
extrnalNativeBuild {
cmake {
cppFlags '-mllvm -fla'
}
}
}
}

OLLVM验证

1、创建一个空的C文件

touch helloworld.c

2、查看 LLVM 支持哪些 -mllvm 参数

./clang -mllvm -help helloworld.c	

如果输出中含有 -sub-fla-bcf编译成功。

image-20250430155020532

3、混淆效果验证(以armeabi-v7a为例 )

可以通过IDA逆向分析相关so库

未混淆Graph Overview

image-20250430211423966

混淆后的Graph Overview

image-20250430211727733

结语

通过以上流程,可在Mac ARM环境下完成OLLVM混淆编译并成功集成至NDK25,增强Android项目安全性。