找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1248

积分

0

好友

184

主题
发表于 3 天前 | 查看: 16| 回复: 0

最近在 Windows 平台上尝试通过插件方式将 LLVM Pass 集成进 NDK 时,遇到了不少挑战。鉴于目前网络上缺乏完整的 Windows 平台实现流程,本文将详细介绍如何填补这一技术空白,实现无需修改 NDK 源码即可集成新版 LLVM Pass。

方案选择与背景

在查阅相关资料后,实现 NDK 集成 LLVM Pass 主要有两种途径:

  1. 内建 Pass 方案:在与 NDK 版本对齐的 llvm-project 上内建集成 Pass,重新编译整套 LLVM/Clang 工具链,并替换 NDK 自带的 clang。
  2. 插件方案:将 Pass 编译成 LLVM 插件(shared library),让 NDK 自带的 clang 以 -fpass-plugin= 的方式动态加载。

显然,插件方案具有更强的可插拔性和灵活性,因此本文将重点讨论如何在 Windows 下实现该方案。

前置说明

若无特殊说明,以下内容均基于 NDK 27 进行分析。

由于 LLVM Pass 插件使用 C++ ABI,必须严格保证插件的 C++ ABI 与 Android/iOS NDK 中的定义完全对齐。

然而,NDK 的预构建版本中,Windows 平台与 Unix 平台(Linux/Mac)的构建思路不同:Linux 和 Mac 上有 LLVM 的动态链接库,而 Windows 上的 LLVM 是静态链接到 clang 的,且缺失了 LLVM CMake 文件。

因此,我们需要在 Windows 上编译一个与 NDK 版本完全对齐、且能够支持 Pass 插件编译的 Clang。

环境准备

  • 操作系统
    • Linux:用于运行 git-repo 拉取源码(推荐使用 Ubuntu 等发行版)。
    • Windows:最终编译插件和运行环境。
  • 网络:需能够流畅连接 AOSP 仓库,预计消耗 20G 左右流量。
  • 磁盘空间:建议预留 100G-150G。
  • 软件依赖
    • Linux: sudo apt-get install build-essential clang llvm flex bison cmake ninja-build wget curl
    • Windows: MSYS2 环境。

编译与 NDK 版本对齐的 LLVM

1. 拉取源码

首先需要拉取与 NDK 版本一致的 LLVM 源码。

2. 应用 Patch

以下 Patch 启用了 LLVM 的插件功能,并避免裁剪静态链接库和最终的可执行文件,以确保我们获得完整的、版本对齐的 LLVM。

Subject: [PATCH] Adapt on Windows
---
Index: ollvm-pass/obfuscation/CMakeLists.txt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP<+>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 1763760762124)
@@ -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++
+)
Index: ollvm-pass/CMakeLists.txt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP<+>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 1763760762133)
@@ -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

3. 执行编译

使用 Python 脚本调用构建工具,这对于熟悉 运维/DevOps 的开发者来说应该不陌生。参考编译命令如下:

python3 toolchain/llvm_android/build.py \
--build-name=win-clang \
--skip-tests \
--create-tar \
--no-build=linux

4. 解压配置

解压编译好的包,并对部分文件重新建立符号链接,以确保工具链的完整性。

LLVM文件结构

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

编译 LLVM Pass 插件

ollvm-rust 仓库为例,我们需要显式加入上面编译的 LLVM 的 libc++ 头文件与运行时库(libc++.a / libc++abi.a 等)进行编译链接,从而确保 LLVM Pass 使用与 NDK 一致的 C++ ABI 构建。

应用 Patch 后,即可编译出所需的 libLLVMObfuscationx.dll

在 CLion 中配置基本的工具链:

CLion配置1
CLion配置2

应用补丁后,插件编译成功:

编译成功
插件文件

将 LLVM Pass Plugin 与 NDK 项目集成

完成上述准备后,我们拥有了:

  1. 一套和 NDK 版本完全对齐的 LLVM/Clang。
  2. 一个能在 Windows 上正常加载的 LLVM Pass 插件。

接下来解决核心问题:如何将 Pass 串进 NDK 项目的构建流程

方案选择:透明注入

虽然可以通过修改 CMake 管线显式插入 IR → opt → codegen 流程,但这种方式较为繁琐。本文推荐使用 透明注入方案,即保持 CMake 不动,在 NDK 的 Clang 外层套用 Launcher。

由于 NDK 27 的 Clang 18 前端虽然支持 -fpass-plugin,但存在无法主动接收 Pass 中 cl::opt 参数的问题。因此,我们需要通过 Launcher 脚本来解决这一问题,这在 安全/渗透 领域的代码混淆实践中非常关键。

实现原理

Android Gradle Plugin + NDK 的构建流程中,android.toolchain.cmake 提供了 ANDROID_CCACHE 扩展点:

if(ANDROID_CCACHE)
    set(CMAKE_C_COMPILER_LAUNCHER   "${ANDROID_CCACHE}")
    set(CMAKE_CXX_COMPILER_LAUNCHER "${ANDROID_CCACHE}")
endif()

我们可以编写一个 Launcher 脚本(wrapper.bat),拦截编译命令,解析参数,并在满足条件时执行完整的 OLLVM 流水线:

  1. 调用真实 Clang 生成 Bitcode。
  2. 调用 opt 加载插件进行混淆。
  3. 调用真实 Clang 将混淆后的 IR 编译为目标文件。

Launcher 脚本 (wrapper.bat)

@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

Gradle 配置

app/build.gradle.kts 中配置 ANDROID_CCACHE

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 或 NDK 本身,所有 NDK 的 C/C++ 编译都会经过 Launcher,自动应用 LLVM Pass。

编译输出:
编译日志

App 运行效果:
运行效果

混淆前后对比:

  • 原函数
    原函数
  • 混淆前
    混淆前
  • 混淆后
    混淆后
    字符串混淆

可以看到字符串混淆已生效,证明我们成功在 Windows 环境下加载并使用了 OLLVM Pass 插件。

*本文为看雪论坛优秀文章,由 霜降白羽 原创,转载自看雪社区




上一篇:Salsa20文件加密器逆向实战:从反汇编到Python PIN码爆破
下一篇:Java后端安全深度实战:文件上传漏洞、信息泄露与依赖安全防御指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 17:29 , Processed in 0.119491 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表