WINDOWS 环境下编译 OLLVM 替换到 NDK 环境
编译 OLLVM
环境准备
这里使用的是AGP 7.2.2
、NDK 25.2.9519653
、llvm 14.0.7
、cmake 3.22.1
、python39
git
用来下载源码
python
搞到这一步环境变量里应该已经有python了吧
NDK
AGP 的 7.2.2 版本默认使用的 NDK 版本为21.4.7075529
,对应的 LLVM 为9.0.9
。
需要根据实际情况选择 NDK 对应的 LLVM 版本,编译 OLLVM,LLVM 版本号可以通过Sdk_DIR\ndk\$version\toolchains\llvm\prebuilt\windows-x86_64\AndroidVersion.txt
文件看到,如SDK Manager 中的最新版本25.2.9519653
为:
14.0.7
based on r450784d1
for additional information on LLVM revision and cherry-picks, see clang_source_info.md
在 module 的build.gradle
中指定 NDK 版本
android {
ndkVersion "25.2.9519653"
}
OLLVM
可以在 github/heroims/obfuscator 仓库的分支中找到对应的移植源码,
13x
和14x
版本也可以在 github/yangyiyu08/ollvm-project 仓库的对应分支中获取。14x
版本还可以直接在 Releases 中下载作者编译好的文件,跳过编译的步骤。但这个文件在我的环境下会有NDK编译错误的情况,我自己编译出来的文件运行正常。
这里使用NDK 25.2.9519653
,需要下载 14x 的源码。
CMAKE
AGP 的 7.2.2 版本默认使用的 cmake 版本为3.18.1
,在指定 NDK 版本到 25.2.9519653 后,会在编译时提示需要升级到 3.19 版本以上的信息。所以这里在编译前就通过 SDK Manager 下载 3.22.1 版本,并把所在目录添加到环境变量。
同时在build.gradle
和CMakeLists
中修改工程的 cmake 版本。
开始编译
在源码的目录执行以下命令构建 cmake 配置
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
对应的参数:
-G Ninja: 使用 ninja 进行编译源码
-DLLVM_ENABLE_PROJECTS="clang": 启用clang,有多个选择 但我们只需要clang,官方文档有说明
-DCMAKE_BUILD_TYPE=Release: 构建 release 版本,比 debug 版本编译快很多
-DLLVM_INCLUDE_TESTS=OFF: 关闭 llvm 的头文件测试,也是为了加快编译速度
-DLLVM_ENABLE_NEW_PASS_MANAGER=OFF: 这个非常重要,llvm-12.x 开始默认使用 newPM进行编译源码,导致 ollvm 不起作用!因此,需要加上这个参数禁用掉 newPM。(在每个编译时增加flag -flegacy-pass-manager 让 llvm 不走 newPM 编译也可以,但没必要)
执行以上命令后若提示 Configuration done. 则配置成功。接下来执行以下命令开始编译:
cmake --build build -j16
其中-j16
为指定的线程数,需要根据 CPU 调整。然后等待编译完成
编译完成
编译完成后打开build/bin
目录,找到clang.exe
、clang++.exe
、clang-cl.exe
,可以看到三个文件的MD5是相同的。
编译后的文件大小为 137MB,对比 NDK 目录下的 clang.exe 仅有 88.6MB。编译后的文件可以通过strip clang.exe
命令剥离可执行文件减小到 113MB。
替换到 NDK 环境
备份与替换
首先打开当前 NDK 的 llvm 目录,将clang.exe
、clang++.exe
、clang-cl.exe
备份,然后把上一步编译后的文件复制到当前目录。
复制 lib 库
此时编译会出现找不到libunwind
等库的错误,错误信息显示目录文件不存在
CMake Error at SDK_DIR/cmake/3.22.1/share/cmake-3.22/Modules/CMakeTestCCompiler.cmake:69 (message):
The C compiler
"SDK_DIR/ndk/25.2.9519653/toolchains/llvm/prebuilt/windows-x86_64/bin/clang.exe"
is not able to compile a simple test program.
It fails with the following output:
Change Dir: APPLICATION_DIR/app/.cxx/RelWithDebInfo/703a16l3/arm64-v8a/CMakeFiles/CMakeTmp
Run Build Command(s):SDK_DIR\\cmake\3.22.1\bin\ninja.exe cmTC_c87d1 && [1/2] Building C object CMakeFiles/cmTC_c87d1.dir/testCCompiler.c.o
[2/2] Linking C executable cmTC_c87d1
FAILED: cmTC_c87d1
cmd.exe /C "cd . && SDK_DIR\\ndk\25.2.9519653\toolchains\llvm\prebuilt\windows-x86_64\bin\clang.exe --target=aarch64-none-linux-android26 --sysroot=SDK_DIR/ndk/25.2.9519653/toolchains/llvm/prebuilt/windows-x86_64/sysroot -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -static-libstdc++ -Wl,--build-id=sha1 -Wl,--no-rosegment -Wl,--fatal-warnings -Wl,--no-undefined -Qunused-arguments CMakeFiles/cmTC_c87d1.dir/testCCompiler.c.o -o cmTC_c87d1 -latomic -lm && cd ."
ld: error: unable to find library -latomic
ld: error: cannot open SDK_DIR/ndk/25.2.9519653/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/14.0.0/lib/linux/libclang_rt.builtins-aarch64-android.a: No such file or directory
ld: error: unable to find library -l:libunwind.a
ld: error: cannot open SDK_DIR/ndk/25.2.9519653/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/14.0.0/lib/linux/libclang_rt.builtins-aarch64-android.a: No such file or directory
ld: error: unable to find library -l:libunwind.a
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
CMake will not be able to correctly generate this project.
Call Stack (most recent call first):
CMakeLists.txt:10 (project)
需要把SDK_DIR/ndk/25.2.9519653/toolchains/llvm/prebuilt/windows-x86_64/lib64/
目录下的calng
目录,复制到/lib
目录中,并把clang/14.0.7
修改为14.0.0
。
这里的14.0.0
是根据错误日志中出现的路径提取出来的,在控制台中输入clang -v
查看当前 clang 的版本,按照日志中出现或当前使用的 clang 版本调整。
至此,NDK 中 集成 OLLVM 已经完成了。接下来是配置和使用 OLLVM。
配置 OLLVM
这部分网上参考的文档很多,这里也只是简单介绍一下参数
参数
参数 | 说明 |
---|---|
-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'
}
}
}
}
也可以在CMakeLists
中进行配置,如:
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mllvm -fla -mllvm -sub -mllvm -sobf")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mllvm -fla -mllvm -sub -mllvm -sobf")
输出比对
以配置了-fvisibility=hidden -ffunction-sections -fdata-sections
的获取 updatemark 为例,源码为:
static jstring getUpdate(JNIEnv *env, jobject clazz) {
struct stat sb{};
int updates = 0;
int updatens = 0;
if (stat("/data/data", &sb) == -1) {
//获取失败
} else {
updatens = (int) sb.st_atim.tv_nsec;
updates = (int) sb.st_atim.tv_sec;
}
std::string idRes = std::to_string(updates) + "." + std::to_string(updatens);
return env->NewStringUTF(idRes.c_str());
}
before
after
遇到的问题
虚假控制流程的问题
实际编译的过程中,增加了-mllvm -bcf
参数后,编译超过半个小时还是没有完成,移除后正常编译。
搜索了相关的问题后,发现可能是 ndk 的编译器优化 flag 在 release 时是 -O2 导致的。
This is specifically an issue with llvm / android-ndk when compiling with thumb mode. You'll either need to disable thumb compilation (annoying) or patch the llvm to not generate this type of instructions; it's not actually an obfuscator-llvm issue.
Potentially try upgrading your ndk as well, though I'm doubtful that will fix this issue. If I have extra time later I can try to find the patch I needed to create for llvm to specifically work around this issue.
但在我这边,debug 依然是没有响应,只能是去掉-bcf
。
CMAKELIST 的 Release 不生效问题
CMAKE_C_FLAGS
和CMAKE_C_FLAGS_DEBUG
配置的参数都可以正常生效,但CMAKE_C_FLAGS_RELEASE
配置无法在 release 时生效。只能配置在CMAKE_C_FLAGS
中,然后在 CMakeLists 中判断当前环境是否为 DEBUG。