首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
16
13
[原创] 不改 NDK 也能用新版 LLVM Pass:Windows 下基于插件的 NDK r29 集成方法
发表于: 2025-11-22 06:11
13907
[原创] 不改 NDK 也能用新版 LLVM Pass:Windows 下基于插件的 NDK r29 集成方法
霜降白羽
2025-11-22 06:11
13907
新人第一次发帖,请各位前辈们多多指教qwq > 最近总算在 Windows 上用插件的方式把 LLVM Pass 集成进了 NDK,中间踩了不少坑。发现全网几乎没人把完整流程写出来,那就由我来补上这块空白吧~ ## 1. 前人的智慧 在查阅一些资料后,笔者发现,想要让我们的NDK也能吃到LLVM Pass一般有两种办法: - **方案一:在与 NDK 对齐的 llvm-project 上内建集成 Pass,重新编译整套 LLVM/Clang 工具链,并替换 NDK 自带的 clang(内建 Pass 方案)** - **方案二:把 Pass 编译成 LLVM 插件(shared library),让 NDK 自带的 clang 以 `fpass-plugin=` 的方式动态加载(插件方案)** 显然方法2可插拔性较强,所以我们接下来的讨论致力于方法2的实现 ## 2. Prelude > 若无特殊说明,以下内容均站在NDK 29.0.14206865的角度分析 > > 笔者在编译成功后偶然看到 <a href="elink@7a9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6^5P5W2)9J5k6h3q4D9K9i4W2#2L8W2)9J5k6h3y4G2L8g2)9J5c8X3&6W2N6%4y4Q4x3V1j5$3x3U0b7%4">这篇文章</a> ,虽然与本文的思路很相似,但有趣的是他也未给出Windows下的实现。~~不要慌,我接下来介绍的就是在Windows上的食用方法~~~ > 由于LLVM pass 插件使用 CPP ABI,所以我们必须严格保证插件的CPP ABI和Android NDK对齐。 但(从某个版本之后?)预编译NDK的头文件、**静态库(注意:NDK的预构建版本中,Windows和[Linux与Mac,下统称Unix平台] 的思路不同,Linux和Mac上有llvm的动态链接库,但是Windows上llvm是静态链接到clang的)**、以及缺失了LLVM CMake文件,编译LLVM的libc++静态库等 … 综上,我们在Windows上就真的需要编译一个和NDK版本完全对齐且可以编译我们pass插件的clang了! ## 3. 环境准备 - 操作系统:Linux(因为要用到git-repo,我不太确定他能否work on Windows)以及最终编译插件的Windows - 网络:能流畅链接AOSP仓库的(也可以使用清华源等?未测试,我直接用的默认源),预计消耗20G左右网络流量 - 磁盘:100G-150G - 软件环境:Linux: `sudo apt-get update && sudo apt-get install cmake clang llvm ninja-build build-essential wget curl btop flex bison git rsync -y `; Windows: msys2 ## 4. 编译和NDK版本对其的LLVM > 本部分极大程度上参考了同论坛的另外一篇帖子: [[原创]一种将LLVM Pass集成到NDK中的通用方法](https://bbs.kanxue.com/thread-271271.htm),感谢原帖作者的努力! > ### 4.1 拉取和NDK版本对齐的仓库 请直接参考上面的[看雪原帖](https://bbs.kanxue.com/thread-271271.htm)的 **0x01.1 - 0x01.3 部分即可** > 2026.01.18: 注意到以下几个revision和上面的不一样,表现在分支名和最终 29.0.14206865\toolchains\llvm\prebuilt\windows-x86_64 内的 `manifest_{}.xml`。 >  > 因此我们需要手动修正下 `manifest_{}.xml` 。 > 替换remote >  > 替换内部remote分支名 >  ### 4.2 Patch 以下Patch启用了LLVM的插件功能,并避免裁剪静态链接库和最终的可执行文件(因为我们要完整的对齐版本号的LLVM!) ```diff project toolchain/llvm_android/ diff --git a/do_build.py b/do_build.py index 36e7500..ec818e7 100755 --- a/do_build.py +++ b/do_build.py @@ -543,10 +544,11 @@ def package_toolchain(toolchain_builder: LLVMBuilder, necessary_lib_files = set() if with_runtimes: if not (host.is_windows and win_sdk.is_enabled()): - necessary_lib_files |= { - 'libc++.a', - 'libc++abi.a', - } + # necessary_lib_files |= { + # 'libc++.a', + # 'libc++abi.a', + # } + pass if host.is_linux: necessary_lib_files |= { 'libbolt_rt_instr.a', diff --git a/src/llvm_android/base_builders.py b/src/llvm_android/base_builders.py index ac11750..77b5e27 100644 --- a/src/llvm_android/base_builders.py +++ b/src/llvm_android/base_builders.py @@ -520,7 +520,7 @@ class LLVMBaseBuilder(CMakeBuilder): # pylint: disable=abstract-method # https://github.com/android-ndk/ndk/issues/574 - Don't depend on libtinfo. defines['LLVM_ENABLE_TERMINFO'] = 'OFF' - defines['LLVM_ENABLE_PLUGINS'] = 'OFF' + defines['LLVM_ENABLE_PLUGINS'] = 'ON' if patch_level := android_version.get_patch_level(): defines['LLVM_VERSION_PATCH'] = patch_level defines['LLVM_VERSION_SUFFIX'] = "" ``` ### 4.3 编译和NDK版本对齐的LLVM 以下编译命令供参考 ```bash python3 toolchain/llvm_android/build.py --build-name=win-clang-release --create-tar --no-build=linux --lto --bootstrap-use-prebuilt --builders-package ``` ### 4.4 解压并配置上一步编译好的LLVM  然后我们需要对一些文件重新建立符号链接 ```bash cd clang-win-clang/bin mklink wasm-ld.exe lld.exe mklink llvm-windres.exe llvm-rc.exe mklink clang++.exe clang.exe mklink ld64.lld.exe lld.exe ``` ## 5. 使用编译好的LLVM来编译我们的LLVM Pass 以<a href="elink@57cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1j5H3P5r3I4S2L8X3g2Q4x3V1k6G2L8r3I4$3L8g2)9J5k6s2u0#2M7%4c8Q4x3V1k6@1M7X3g2W2i4K6u0r3y4h3x3K6x3h3t1#2k6h3x3H3j5U0t1K6y4U0l9I4z5e0f1$3y4o6x3K6z5o6p5%4x3e0W2X3y4K6V1%4k6U0j5#2z5h3f1K6k6o6p5J5k6W2)9J5c8X3!0D9L8s2k6E0i4K6u0V1M7r3q4K6M7H3`.`.">该LLVM Pass仓库</a>为例,进行一个抛砖引玉( > 注意:我们需要在这里显式加入上面编译的 LLVM 的 libc++ 头文件与运行时库(libc++.a / libc++abi.a 等)进行编译链接,**从而确保 LLVM Pass 用与 NDK 一致的 C++ ABI 构建**。 > 将以下的patch apply,即可编译出我们所需的 `libLLVMObfuscationx.dll` ```diff Index: ollvm-pass/CMakeLists.txt <+>UTF-8 =================================================================== diff --git a/ollvm-pass/CMakeLists.txt b/ollvm-pass/CMakeLists.txt --- a/ollvm-pass/CMakeLists.txt (revision 5c31b5ec0b2360195643381719f797f659e3d12f) +++ b/ollvm-pass/CMakeLists.txt (date 1763698027522) @@ -5,7 +5,7 @@ # 1. LOAD LLVM CONFIGURATION #=============================================================================== # Set this to a valid LLVM installation dir -set(LT_LLVM_INSTALL_DIR "" CACHE PATH "D:\\dev\\rust_ollvm\\llvm-build\\llvm_x64") +set(LT_LLVM_INSTALL_DIR "D:\\another-clang-ndk\\clang-win-clang" CACHE PATH "Path to NDK LLVM installation") # Add the location of LLVMConfig.cmake to CMake search paths (so that # find_package can locate it) @@ -22,6 +22,7 @@ separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) add_definitions(${LLVM_DEFINITIONS_LIST}) include_directories(${LLVM_INCLUDE_DIRS}) +include_directories(SYSTEM "${LT_LLVM_INSTALL_DIR}/include/c++/v1") #=============================================================================== # 2. LLVM-TUTOR BUILD CONFIGURATION Index: ollvm-pass/obfuscation/CMakeLists.txt <+>UTF-8 =================================================================== diff --git a/ollvm-pass/obfuscation/CMakeLists.txt b/ollvm-pass/obfuscation/CMakeLists.txt --- a/ollvm-pass/obfuscation/CMakeLists.txt (revision 5c31b5ec0b2360195643381719f797f659e3d12f) +++ b/ollvm-pass/obfuscation/CMakeLists.txt (date 1763697579344) @@ -19,8 +19,15 @@ llvm_map_components_to_libnames(llvm_libs support core irreader linker) target_link_libraries(LLVMObfuscationx PRIVATE ${llvm_libs}) -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set_target_properties(LLVMObfuscationx PROPERTIES - LINK_FLAGS "-static -static-libgcc -Wl,-Bstatic,--whole-archive -lwinpthread -lstdc++ -Wl,--no-whole-archive -Wl,-Bdynamic" - ) -endif() +target_link_libraries(LLVMObfuscationx PRIVATE + ${llvm_libs} + "${LT_LLVM_INSTALL_DIR}/lib/x86_64-w64-windows-gnu/libc++.a" + "${LT_LLVM_INSTALL_DIR}/lib/x86_64-w64-windows-gnu/libc++abi.a" + "${LT_LLVM_INSTALL_DIR}/lib/libwinpthread-1.dll" + "C:/msys64/mingw64/lib/libucrtbase.a" + "C:/msys64/mingw64/lib/libvcruntime140.a" +) +target_compile_options(LLVMObfuscationx PRIVATE + -fno-rtti + -nostdinc++ +) ``` 在Clion内配置下基本的工具链:   然后应用补丁,插件可以成功编译   ## 6. 将 LLVM Pass Plugin 与 NDK 项目集成 > 以下的Android项目基于 Android Studio Otter | 2025.2.1 Patch 1 的 Native C++ 模板 > 前面的铺垫都做完了: - 有一套和 NDK 版本完全对齐的 LLVM/Clang; - 有一个能在 Windows 上正常加载的 LLVM Pass 插件; 接下来就只剩下最后一步:**如何把这个 Pass 串进实际的 NDK 项目构建流程里**。 在这一步上,大致有两种思路: 1. 在 CMake 管线上显式插一段 `IR → opt → codegen` 的流水线(**显式管线方案**) 2. 保持 CMake 完全不动,在 NDK 的 Clang 外面套一层 launcher 做“透明注入”(**透明注入方案**) ### 6.1. 在 CMake 管线中显式插入 IR → opt → codegen 这一种思路的核心就是:**自己当一回“小型编译器驱动”**。也就是说,不再让 `add_library()` 直接去编 .cpp,而是: 1. 用 `add_custom_command()` 调 `CMAKE_CXX_COMPILER` 把 `lib.cpp` 编成 `lib.bc`(`emit-llvm -c`); 2. 再用另一个 `add_custom_command()` 调你刚才编好的 `opt` + 插件,把 `lib.bc` 跑一遍 Pass,生成 `lib_obf.bc`; 3. 最后把 `lib_obf.bc` 当成一个“外部对象文件”,交给 `add_library()` 去链接成动态链接库。 本方案较为繁琐,需要实际修改CMakeFiles.txt,故不在这里详述,感兴趣的作者可以自行探索( --- ### 6.2. 通过 ANDROID_CCACHE/launcher 对 Clang 做“透明注入” > 由于NDK27的Clang 18前端可以使用 `-fpass-plugin` 加载Pass插件,**但不能主动接收PASS中的 `cl::opt` 参数**(具体原因可以去翻Clang的源码,社区也有关于这个问题的issue),**故以下部分均针对于 “如何让Clang吃到 `cl::opt` 参数” 这一问题展开**( > Android Gradle Plugin + NDK 的构建流程里,真正调 CMake 的那一层会自动传: - `DCMAKE_TOOLCHAIN_FILE=<NDK>/build/cmake/android.toolchain.cmake` - `DANDROID_ABI=...` / `DANDROID_PLATFORM=...` / `DCMAKE_CXX_COMPILER_TARGET=...` 等等 在这个 toolchain 里,`CMAKE_C_COMPILER` / `CMAKE_CXX_COMPILER` 最终都会被强行设成 NDK 自带的 `clang/clang++`。简单粗暴地在 Gradle 里塞: ```kotlin arguments += listOf( "-DCMAKE_C_COMPILER=C:/path/to/clang.bat", "-DCMAKE_CXX_COMPILER=C:/path/to/clang++.bat" ) ``` 是**不会生效的**,它在 toolchain 文件里会被改回去,NDK 真正留给我们的扩展点是:`ANDROID_CCACHE` 在 `android.toolchain.cmake` 里,大致会干这件事: ``` if(ANDROID_CCACHE) set(CMAKE_C_COMPILER_LAUNCHER "${ANDROID_CCACHE}") set(CMAKE_CXX_COMPILER_LAUNCHER "${ANDROID_CCACHE}") endif() ``` 也就是说,只要在 CMake 配置阶段传入 `-DANDROID_CCACHE=/path/to/wrapper.bat`,所有的编译命令都会变成 `wrapper.bat <真实的clang或clang++路径> <原始编译参数...>` 于是我们就可以写一个 **launcher 脚本**(Windows 上就是 `wrapper.bat`),逻辑是: 1. **第一个参数**永远是 NDK 自带的 `clang(.exe)` 或 `clang++(.exe)`,记成 `REAL_CLANG`; 2. 去掉第一个参数之后的 `%*`,就是原始的编译参数(`-target ... --sysroot ... -c ... -o xxx.o ...`),我们只做非常轻量的解析: - 有没有 `c`(只截获纯编译,不动链接); - `o` 后面那个 token 是真正的输出文件名(`CMakeFiles/xxx.dir/native-lib.cpp.o`); - `-target` / `-sysroot` 后面的各一个 token 分别是 triple 和 sysroot 路径; 3. 如果发现这是一个正常的 `c` 编译命令,又拿到了 `OUT_FILE` / `TARGET_TRIPLE` / `SYSROOT_PATH`,而且当前目录不是 `CMakeTmp`(CMake 自己用来探测编译器的临时目录),就走完整 OLLVM 流水线: - 调 `REAL_CLANG` + 原始参数 + `emit-llvm -o OUT_FILE.bc` 生成 bitcode; - 调 `opt.exe -load-pass-plugin=... -passes=... OUT_FILE.bc -o OUT_FILE.obf.bc` 跑插件; - 调 `REAL_CLANG --target=... --sysroot=... -c OUT_FILE.obf.bc -o OUT_FILE` 把混淆后的 IR 再编回 `.o`; 4. 如果不满足条件(比如不是 `c`,或者在 `CMakeTmp` 目录里),就原封不动把这条命令转发给 `REAL_CLANG`。 精简后的脚本大概长这样: ```bash @echo off setlocal ENABLEDELAYEDEXPANSION set "OPT_EXE=D:\\another-clang-ndk\\clang-win-clang\\bin\\opt.exe" set "OBF_PLUGIN=C:\\...\\obfuscation\\libLLVMObfuscationx.dll" set "OBF_PASSES=irobf(irobf,irobf-indgv,irobf-cse,irobf-cff)" set "REAL_CLANG=%~1" shift if "%~1"=="" ( "%REAL_CLANG%" goto :eof ) set "ORIG_ARGS=%*" echo %CD% | findstr /I "CMakeTmp" >nul if not errorlevel 1 goto passthrough set "COMPILE_ONLY=0" set "OUT_FILE=" set "PREV=" set "TARGET_TRIPLE=" set "SYSROOT_PATH=" set "NEXT_IS_TARGET=0" set "NEXT_IS_SYSROOT=0" for %%A in (%*) do ( if /I "%%A"=="-c" set "COMPILE_ONLY=1" if /I "!PREV!"=="-o" set "OUT_FILE=%%A" if /I "%%A"=="--target" ( set "NEXT_IS_TARGET=1" ) else if "!NEXT_IS_TARGET!"=="1" ( set "TARGET_TRIPLE=%%A" set "NEXT_IS_TARGET=0" ) if /I "%%A"=="--sysroot" ( set "NEXT_IS_SYSROOT=1" ) else if "!NEXT_IS_SYSROOT!"=="1" ( set "SYSROOT_PATH=%%A" set "NEXT_IS_SYSROOT=0" ) set "PREV=%%A" ) if "%COMPILE_ONLY%"=="0" goto passthrough if "%OUT_FILE%"=="" goto passthrough if "%OUT_FILE:~0,1%"=="-" goto passthrough if "%TARGET_TRIPLE%"=="" goto passthrough if "%SYSROOT_PATH%"=="" goto passthrough set "BC_FILE=%OUT_FILE%.bc" set "OBF_BC_FILE=%OUT_FILE%.obf.bc" echo [clang-launcher] OUT_FILE="%OUT_FILE%" echo [clang-launcher] compile to LLVM IR: "%BC_FILE%" "%REAL_CLANG%" %ORIG_ARGS% -emit-llvm -o "%BC_FILE%" if errorlevel 1 goto fallback echo [clang-launcher] run opt with plugin: "%OBF_PLUGIN%" "%OPT_EXE%" -load-pass-plugin="%OBF_PLUGIN%" -passes="%OBF_PASSES%" "%BC_FILE%" -o "%OBF_BC_FILE%" if errorlevel 1 goto fallback echo [clang-launcher] codegen obfuscated IR to object: "%OUT_FILE%" "%REAL_CLANG%" --target=%TARGET_TRIPLE% --sysroot=%SYSROOT_PATH% -fPIC -fno-limit-debug-info -c "%OBF_BC_FILE%" -o "%OUT_FILE%" goto :eof :fallback echo [clang-launcher] pipeline failed, falling back to plain clang >&2 :passthrough "%REAL_CLANG%" %ORIG_ARGS% goto :eof ``` 然后在 `app/build.gradle.kts` 里给 CMake 加上 `ANDROID_CCACHE` 即可: ```kotlin android { defaultConfig { externalNativeBuild { cmake { cppFlags += "-std=c++17" val launcher = file("wrapper.bat").absolutePath arguments += listOf( "-DANDROID_CCACHE=$launcher" ) } } } externalNativeBuild { cmake { path = file("src/main/cpp/CMakeLists.txt") version = "3.22.1" } } } ``` 而`CMakeLists.txt`保持不变 ```cmake cmake_minimum_required(VERSION 3.22.1) project("ollvmtest") add_library(${CMAKE_PROJECT_NAME} SHARED # List C/C++ source files with relative paths to this CMakeLists.txt. native-lib.cpp) target_link_libraries(${CMAKE_PROJECT_NAME} # List libraries link to the target library android log) ``` 就这样,在**不改任何 CMakeLists / 不改 NDK 本身**的情况下,所有 NDK 的 C/C++ 编译都会经过这层 launcher,能吃到你自己的 LLVM Pass。 **这个方案的优点:** - 对现有工程非常友好:CMakeLists 基本不用动,只要改一下 Gradle; - 全局生效,不止某一个库,所有通过 NDK Clang 编译的目标都可以被“透明”地跑一遍插件; - 和官方 NDK toolchain 对齐,不需要魔改 `<NDK>/build/cmake/android.toolchain.cmake`。 编译输出(记得在 `gradle.properties` 设置 `android.native.buildOutput=verbose`)  App运行效果:  原生库混淆前/后对比: > 使用的混淆参数:`irobf(irobf,irobf-indgv,irobf-cse,irobf-cff)` > 原函数:  混淆前:  混淆后:   可见字符串混淆生效,无法直接检索到字符串 ,证明我们成功加载并使用了OLLVM Pass插件~
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于
2026-1-18 14:04 被霜降白羽编辑 ,原因: upd
#软件保护
收藏
・
16
点赞
・
13
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
git_47499test-look
感谢你分享这么好的资源!
2026-1-19 02:18
binsys
感谢你的贡献,论坛因你而更加精彩!
2025-11-29 20:49
SnowFox
+1
谢谢你的细致分析,受益匪浅!
2025-11-27 10:57
东方玻璃
你的帖子非常有用,感谢分享!
2025-11-25 18:40
AEVE
你的帖子非常有用,感谢分享!
2025-11-25 11:54
wx_嗨
感谢你分享这么好的资源!
2025-11-25 11:31
灬哈密瓜
谢谢你的细致分析,受益匪浅!
2025-11-25 11:10
bananaships
+1
你的帖子非常有用,感谢分享!
2025-11-25 09:29
HOWMP
非常支持你的观点!
2025-11-24 10:46
ONewTach
感谢你分享这么好的资源!
2025-11-24 09:14
我的小拇指啊
你的帖子非常有用,感谢分享!
2025-11-23 01:34
tuosen
为你点赞!
2025-11-22 09:48
git_56009war9
感谢你的贡献,论坛因你而更加精彩!
2025-11-22 09:46
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
3
)
墨穹呢
雪 币:
4697
活跃值:
(8117)
能力值:
( LV3,RANK:20 )
在线值:
发帖
2
回帖
224
粉丝
18
关注
私信
墨穹呢
2
楼
感谢分享
2025-11-23 19:43
0
wx_Huber Barrientos
雪 币:
0
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
206
粉丝
1
关注
私信
wx_Huber Barrientos
3
楼
为你点赞!
2025-11-24 13:07
0
MsScotch
雪 币:
4163
活跃值:
(4876)
能力值:
( LV2,RANK:10 )
在线值:
发帖
3
回帖
394
粉丝
5
关注
私信
MsScotch
4
楼
mark
2025-11-26 23:07
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
霜降白羽
1
发帖
0
回帖
10
RANK
关注
私信
他的文章
[原创] 不改 NDK 也能用新版 LLVM Pass:Windows 下基于插件的 NDK r29 集成方法
13907
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部