Windows 编译 OLLVM NDK 使用

编译 OLLVM

环境准备

Android Gradle plugin:gradle-8.2
NDK:25.1.8937393
Python: 3.12.1
Cmake: 3.26.4
LLVM: 14.x

Android Gradle plugin 的 gradle-8.2 版本默认使用的 NDK 版本为25.1.8937393,对应的 LLVM 为14.ximage 需要根据实际情况选择 NDK 对应的 LLVM 版本,编译 OLLVM,LLVM 版本号可以通过:

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

文件看到。如 SDK Manager 中的 25.1.8937393 版本为:

1
2
3
14.0.6
based on r450784d
for additional information on LLVM revision and cherry-picks, see clang_source_info.md

可以在 module 的 build.gradle 中指定 NDK 版本

1
2
3
android {
    ndkVersion "25.1.8937393"
}

下载 OLLVM 源码

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

OLLVM14 可以根据 README.md 找到下载方法: image

然后进行以下操作:

1
2
3
4
5
git clone --depth=1 -b release/14.x [email protected]:llvm/llvm-project.git
cd llvm-project
wget https://heroims.github.io/obfuscator/NewPass/ollvm14.patch
git apply ollvm14.patch
git apply --reject --ignore-whitespace ollvm14.patch

当你运行 git apply ollvm14.patch 时,Git 将尝试将名为 ollvm14.patch 的补丁文件应用到当前的代码库中。

而当你运行 git apply --reject --ignore-whitespace ollvm14.patch 时,Git 将以不同的方式处理。在这种情况下:

  • --reject: 这个选项告诉 Git,在遇到无法直接应用的更改时创建拒绝文件(.rej 文件),其中包含未能应用的部分内容。这样做可以让你手动查看和解决无法自动合并的部分。
  • --ignore-whitespace: 这个选项告诉 Git 在比较和应用补丁时忽略空格变化。有时候由于空格差异导致补丁无法直接匹配,使用此选项可以跳过空格差异而继续尝试应用补丁。

开始编译

在源码的目录执行以下命令构建 cmake 配置

1
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. 则配置成功。 image

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

1
cmake --build build -j16

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

配置 Studio

准备

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

  • clang.exe
  • clang++.exe
  • clang-format.exe

注意这里的 [VERSION] 是你的 LLVM 版本。

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

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

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

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

编译后的文件大小为 137MB,对比 NDK 目录下的 clang.exe 仅有 88.6MB。 编译后的文件可以通过 strip clang.exe 命令剥离可执行文件减小到 113MB。

把前面 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

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


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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
> Task :app:configureCMakeDebug[armeabi-v7a] FAILED
C/C++: CMake Error at C:/Users/xiaoqi/AppData/Local/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeTestCCompiler.cmake:69 (message):
C/C++:   The C compiler
C/C++:     "C:/Users/xiaoqi/AppData/Local/Android/Sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/windows-x86_64/bin/clang.exe"
C/C++:   is not able to compile a simple test program.
C/C++:   It fails with the following output:
C/C++:     Change Dir: C:/Users/xiaoqi/AndroidStudioProjects/NDK/app/.cxx/Debug/6y611i46/armeabi-v7a/CMakeFiles/CMakeTmp
C/C++:   
C/C++:     Run Build Command(s):C:\Users\xiaoqi\AppData\Local\Android\Sdk\cmake\3.22.1\bin\ninja.exe cmTC_cd6c3 && [1/2] Building C object CMakeFiles/cmTC_cd6c3.dir/testCCompiler.c.o
C/C++:     [2/2] Linking C executable cmTC_cd6c3
C/C++:     FAILED: cmTC_cd6c3 
C/C++:     cmd.exe /C "cd . && C:\Users\xiaoqi\AppData\Local\Android\Sdk\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin\clang.exe --target=armv7-none-linux-androideabi24 --sysroot=C:/Users/xiaoqi/AppData/Local/Android/Sdk/ndk/25.1.8937393/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 -march=armv7-a -mthumb -Wformat -Werror=format-security -static-libstdc++ -Wl,--build-id=sha1 -Wl,--no-rosegment -Wl,--fatal-warnings -Wl,--gc-sections -Wl,--no-undefined -Qunused-arguments -Wl,--gc-sections CMakeFiles/cmTC_cd6c3.dir/testCCompiler.c.o -o cmTC_cd6c3  -latomic -lm && cd ."
C/C++:     ld: error: unable to find library -latomic
C/C++:     ld: error: cannot open C:/Users/xiaoqi/AppData/Local/Android/Sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/14.0.6/lib/linux/libclang_rt.builtins-arm-android.a: No such file or directory
C/C++:     ld: error: unable to find library -l:libunwind.a
C/C++:     ld: error: cannot open C:/Users/xiaoqi/AppData/Local/Android/Sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/14.0.6/lib/linux/libclang_rt.builtins-arm-android.a: No such file or directory
C/C++:     ld: error: unable to find library -l:libunwind.a
C/C++:     clang: error: linker command failed with exit code 1 (use -v to see invocation)
C/C++:     ninja: build stopped: subcommand failed.
C/C++:   
C/C++:   
C/C++:   
C/C++:   CMake will not be able to correctly generate this project.
C/C++: Call Stack (most recent call first):
C/C++:   CMakeLists.txt:12 (project)

解决方法就是将:

SDK_DIR/ndk/25.1.8937393/toolchains/llvm/prebuilt/windows-x86_64/lib64/

目录下的 calng 目录,复制到 /lib 目录中,

并把 clang/14.0.3 修改为 14.0.6


这里的 14.0.6 是根据错误日志中出现的路径提取出来的,在控制台中输入 clang -v 查看当前 clang 的版本,按照日志中出现或当前使用的 clang 版本调整。 image

接着把 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 文件也必须复制过去。

至此,NDK 中 集成 OLLVM 已经完成了。接下来是配置和使用 OLLVM。

Studio 配置 ndk

这部分网上参考的文档很多,这里也只是简单介绍一下参数

参数 说明
-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 中进行配置,如:

1
2
3
4
5
6
7
8
9
android {
    defaultConfig {
        extrnalNativeBuild {
            cmake {
                cppFlags '-mllvm -fla'
            }
        }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
android {
    compileSdk 32
    ndkVersion "21.4.7075529"

    defaultConfig {
        applicationId "com.cat.stalker"
        externalNativeBuild {
            cmake {
                abiFilters 'arm64-v8a'
                cppFlags "-std=c++14 -fexceptions -fvisibility=hidden"
                cppFlags "-mllvm -fla -mllvm -split -mllvm -split_num=3 -mllvm -sub -mllvm -sub_loop=3 -mllvm -sobf"
            }
        }
        ndk {
            abiFilters "arm64-v8a"
        }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
android {
    ndkVersion "25.1.8937393"
  
    namespace 'com.wwxiaoqi.ndk'
    compileSdk 34

    defaultConfig {

        externalNativeBuild {
            cmake {
                cppFlags '-std=c++17 -fexceptions -fvisibility=hidden'
                cppFlags "-mllvm -fla -mllvm -split -mllvm -split_num=3 -mllvm -sub -mllvm -sub_loop=3 -mllvm -sobf"
            }
        }
    }

    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.22.1'
        }
    }
}

也可以在 CMakeLists 中进行配置,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 设置 llvm 混淆编译
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")

# 设置 llvm debug 模式混淆编译
SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -mllvm -fla -mllvm -sobf")
SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mllvm -fla -mllvm -sobf")
  
# 设置 llvm release 模式混淆编译
SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mllvm -fla -mllvm -sub -mllvm -bcf -mllvm -sobf")
SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mllvm -fla -mllvm -sub -mllvm -bcf -mllvm -sobf")

# 深度混淆
SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mllvm -bcf -mllvm -bcf_loop=4 -mllvm -bcf_prob=100 -mllvm -sub -mllvm -sub_loop=2 -mllvm -fla -mllvm -sobf -mllvm -split")

如果需要单个函数混淆的情况:

1
2
3
4
5
6
7
8
__attribute((__annotate__("bcf")))
__attribute((__annotate__("fla")))
__attribute((__annotate__("sub")))
__attribute((__annotate__("split")))
__attribute((__annotate__("sobf")))
void func(){
	xxx
}