Android NDK r27 Clang 18 Hikari 混淆器 LLVM Pass 插件加载方案

要用到的项目及说明

  • Hikari-LLVM15-Core: Obfuscator source code (Working on LLVM 18, Don’t be confused by its project name)

  • Hikari-LLVM15-Headers: Obfuscator headers

  • Android Prebuilt Clang r522817: NDK r27 使用的 clang revision 为 r522817 (见 $ANDROID_SDK_ROOT/ndk/27.0.12077973/toolchains/llvm/prebuilt/darwin-x86_64/AndroidVersion.txt)

  • clang-r522817 tarball: 预编译 clang 工具链

  • Android llvm-project: NDK r27 的 revision 为 d8003a456d14a3deb8054cdaa529ffbf02d9b262 (见 $ANDROID_SDK_ROOT/ndk/27.0.12077973/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --version)

  • 仅在 arm64 的 macOS 14 上进行了测试, 在 macOS x86_64 及 Linux 上应该是通用的, Windows 上不可用.

混淆器 LLVM Pass 插件构建

要编译一个独立的 Hikari LLVM Pass 插件, 可参考如下项目结构:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
.
├── CMakeLists.txt
├── Hikari
│ ├── AntiClassDump.cpp
│ ├── AntiDebugging.cpp
│ ├── AntiHooking.cpp
│ ├── BogusControlFlow.cpp
│ ├── CMakeLists.txt
│ ├── ConstantEncryption.cpp
│ ├── CryptoUtils.cpp
│ ├── Flattening.cpp
│ ├── FunctionCallObfuscate.cpp
│ ├── FunctionWrapper.cpp
│ ├── IndirectBranch.cpp
│ ├── LICENSE
│ ├── Obfuscation.cpp
│ ├── PluginEntry.cpp
│ ├── README.md
│ ├── SplitBasicBlocks.cpp
│ ├── StringEncryption.cpp
│ ├── SubstituteImpl.cpp
│ ├── Substitution.cpp
│ ├── Utils.cpp
│ ├── include
│ │ └── llvm
│ │ └── Transforms
│ │ └── Obfuscation
│ │ ├── AntiClassDump.h
│ │ ├── AntiDebugging.h
│ │ ├── AntiHook.h
│ │ ├── BogusControlFlow.h
│ │ ├── ConstantEncryption.h
│ │ ├── CryptoUtils.h
│ │ ├── Flattening.h
│ │ ├── FunctionCallObfuscate.h
│ │ ├── FunctionWrapper.h
│ │ ├── IndirectBranch.h
│ │ ├── LICENSE
│ │ ├── Obfuscation.h
│ │ ├── README.md
│ │ ├── Split.h
│ │ ├── StringEncryption.h
│ │ ├── SubstituteImpl.h
│ │ ├── Substitution.h
│ │ ├── Utils.h
│ │ └── compat
│ │ └── CallSite.h
│ └── json.hpp
└── clang-r522817
└── ......

将 Hikari-LLVM15-Core 放在 ./Hikari 目录下, Hikari-LLVM15-Headers 放在 ./Hikari/include/llvm/Transforms/Obfuscation 下, 这样可以尽可能减少对原项目源码的修改.

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
26
27
28
29
30
31
# ./CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(Hikari-Obf-LLVM18-NDK27)

# set(CMAKE_OSX_ARCHITECTURES x86_64)
set(CMAKE_OSX_ARCHITECTURES arm64)

set(CMAKE_C_COMPILER ${CMAKE_CURRENT_LIST_DIR}/clang-r522817/bin/clang)
set(CMAKE_CXX_COMPILER ${CMAKE_CURRENT_LIST_DIR}/clang-r522817/bin/clang++)
set(ENV{LLVM_HOME} ${CMAKE_CURRENT_LIST_DIR}/clang-r522817)

if (NOT DEFINED ENV{LLVM_HOME})
message(FATAL_ERROR "$LLVM_HOME is not defined")
else ()
set(ENV{LLVM_DIR} $ENV{LLVM_HOME}/lib/cmake/llvm)
endif ()

find_package(LLVM REQUIRED CONFIG)
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})

set(CMAKE_CXX_STANDARD 17)

# Uncomment this for macOS arm64
add_compile_definitions(ENDIAN_LITTLE)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
set(CMAKE_SKIP_RPATH ON)

add_subdirectory(Hikari)
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# ./Hikari/CMakeLists.txt
add_library(Hikari MODULE
FunctionCallObfuscate.cpp
CryptoUtils.cpp
BogusControlFlow.cpp
SubstituteImpl.cpp
Substitution.cpp
Flattening.cpp
Utils.cpp
SplitBasicBlocks.cpp
AntiClassDump.cpp
AntiDebugging.cpp
AntiHooking.cpp
StringEncryption.cpp
IndirectBranch.cpp
FunctionWrapper.cpp
ConstantEncryption.cpp
Obfuscation.cpp
PluginEntry.cpp
)

target_include_directories(Hikari PUBLIC include)

# LLVM is (typically) built with no C++ RTTI. We need to match that;
# otherwise, we'll get linker errors about missing RTTI data.
set_target_properties(Hikari PROPERTIES
COMPILE_FLAGS "-fno-rtti"
)

# Get proper shared-library behavior (where symbols are not necessarily
# resolved when the shared library is linked) on OS X.
if (APPLE)
set_target_properties(Hikari PROPERTIES
LINK_FLAGS "-undefined dynamic_lookup"
)
endif (APPLE)

execute_process(
COMMAND git log -1 --format=%H
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
OUTPUT_VARIABLE HIKARI_GIT_COMMIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
target_compile_definitions(Hikari PRIVATE "-DGIT_COMMIT_HASH=\"${HIKARI_GIT_COMMIT_HASH}\"")

相比 Hikari-LLVM15-Core 原项目, LLVM pass plugin 还需要添加一个插件入口:

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
26
27
// ./Hikari/PluginEntry.cpp

#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Transforms/Obfuscation/Obfuscation.h"

#if LLVM_VERSION_MAJOR >= 13

namespace llvm {

PassPluginLibraryInfo getHikariPluginInfo() {
return {LLVM_PLUGIN_API_VERSION, "Hikari", LLVM_VERSION_STRING,
[](PassBuilder &PB) {
PB.registerPipelineStartEPCallback(
[](ModulePassManager &PM, OptimizationLevel) {
PM.addPass(ObfuscationPass());
});
}};
}

extern "C" LLVM_ATTRIBUTE_WEAK PassPluginLibraryInfo llvmGetPassPluginInfo() {
return getHikariPluginInfo();
}

} // namespace llvm

#endif

构建混淆插件

1
2
3
4
5
6
mkdir -p build
pushd build
cmake -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel ../
cmake --build .
popd
# 构建出的 libHikari.so 在 ./build/Hikari 目录下

构建时可能遇到的两种问题:

  1. CMake 报错:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    CMake Error at clang-r522817-darwin-x86_64/lib/cmake/llvm/LLVMExports.cmake:1238 (message):
    The imported target "LLVMDemangle" references the file

    "$(OBF_PROJECT_DIR)/clang-r522817/lib/libLLVMDemangle.a"

    but this file does not exist. Possible reasons include:

    * The file was deleted, renamed, or moved to another location.

    * An install or uninstall procedure did not complete successfully.

    * The installation package was faulty and contained

    在 $(OBF_PROJECT_DIR)/clang-r522817/lib/cmake/llvm/LLVMExports.cmake 中找到以下脚本, 注释即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # Loop over all imported files and verify that they actually exist
    foreach(target ${_IMPORT_CHECK_TARGETS} )
    foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} )
    if(NOT EXISTS "${file}" )
    message(FATAL_ERROR "The imported target \"${target}\" references the file
    \"${file}\"
    but this file does not exist. Possible reasons include:
    * The file was deleted, renamed, or moved to another location.
    * An install or uninstall procedure did not complete successfully.
    * The installation package was faulty and contained
    \"${CMAKE_CURRENT_LIST_FILE}\"
    but not all the files it references.
    ")
    endif()
    endforeach()
    unset(_IMPORT_CHECK_FILES_FOR_${target})
    endforeach()
    unset(_IMPORT_CHECK_TARGETS)
  2. (macOS) 构建时报类似于 clang-18 已损坏: sudo xattr -d com.apple.quarantine $(OBF_PROJECT_DIR)/clang-r522817/bin/*

NDK Clang 工具链的改动

如果直接用原版 NDK 加载 libHikari, 可能会遇到以下三种问题:

  1. (macOS) clang 和 libHikari.so 签名不一致导致被 Hardened Runtime 拒绝加载: error: unable to load plugin 'libHikari.so': 'dlopen(libHikari.so, 0x0009): tried: 'libHikari.so' (code signature in <xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx> 'libHikari.so' not valid for use in process: mapping process and mapped file (non-platform) have different Team IDs), '/System/Volumes/Preboot/Cryptexes/OSlibHikari.so' (no such file), 'libHikari.so' (code signature in <xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx> 'libHikari.so' not valid for use in process: mapping process and mapped file (non-platform) have different Team IDs)'
    需要将 llvm/bin 中的可执行文件与 libHikari.so 用同一苹果开发者 TeamID 签名:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    sign_macho() {
    # Directory to search for Mach-O executables
    local directory="$1"

    # Signing identity
    local signing_identity="$TeamID"

    # Find and sign all Mach-O executables
    find "$directory" -type f -perm +111 | while read -r file; do
    # Check if the file is a Mach-O executable
    if file "$file" | grep -q "Mach-O"; then
    echo "Signing $file"
    codesign -s "$signing_identity" -f "$file"
    fi
    done

    echo "Signing process completed."
    }
  2. 找不到符号: error: unable to load plugin '$(PROJECT_DIR)/app/obf_lib/darwin_arm64/libHikari.so': 'dlopen($(PROJECT_DIR)/app/obf_lib/darwin_arm64/libHikari.so, 0x0009): symbol not found in flat namespace '__ZTVN4llvm2cl6OptionE''. 这是由于官方 NDK 中自带的 clang 二进制符号不完整导致的. 需要自行构建 Android LLVM 来获取完整的 clang-18, clang, clang++ 替换掉 NDK 内自带的对应的三个文件.

构建 Android llvm-project

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
26
git clone https://android.googlesource.com/toolchain/llvm-project
cd llvm-project
# 确保与原版 NDK 相同的 git revision
git checkout d8003a456d14a3deb8054cdaa529ffbf02d9b262
cd ..
mkdir -p build
cd build

cmake -G "Ninja" ../llvm-project/llvm \
-DCMAKE_INSTALL_PREFIX="./llvm_x64" \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_BUILD_TYPE=MinSizeRel
-DLLVM_APPEND_VC_REV=on \
-DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;compiler-rt;lld;mlir;openmp;polly;" \
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" \
-DLLVM_TARGETS_TO_BUILD="X86;ARM;AArch64" \
-DLLVM_INSTALL_UTILS=ON \
-DLLVM_INCLUDE_TESTS=OFF \
-DLLVM_BUILD_TESTS=OFF \
-DLLVM_INCLUDE_BENCHMARKS=OFF \
-DLLVM_BUILD_BENCHMARKS=OFF \
-DLLVM_INCLUDE_EXAMPLES=OFF \
-DLLVM_ENABLE_BACKTRACES=OFF \
-DLLVM_BUILD_DOCS=OFF

cmake --build .

构建完成后, 可以将原有的 NDK 复制一份出来专门用于混淆:

1
2
3
4
5
6
cd $ANDROID_SDK_ROOT/ndk
cp -r 27.0.12077973 27.0.12077973-obf
cd 27.0.12077973-obf/toolchains/llvm/prebuilt/darwin-x86_64/bin
mv clang clang.bak
mv clang++ clang++.bak
mv clang-18 clang-18.bak

再将构建获得的 clang, clang++, clang-18 放到该目录下即可.

加载混淆器 LLVM Pass 插件, 传递混淆参数

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
26
27
28
// build.gradle.kts
android {
ndkVersion = "27.0.12077973"
ndkPath = "$ANDROID_SDK_ROOT/ndk/27.0.12077973-obf"

defaultConfig {
externalNativeBuild {
cmake {
// 混淆插件路径
val obfLibDir =
"${project.layout.projectDirectory.asFile.absolutePath}/obf_lib/darwin_arm64/libHikari.so"
val obfArgs = listOf(
"-fvisibility=hidden",
"-fpass-plugin=$obfLibDir",
"-Xclang",
"-load",
"-Xclang",
obfLibDir,
// 混淆参数, 请自行参考 Hikari-LLVM15-Core 原项目配置
"-mllvm",
"-enable-allobf"
)
cppFlags += obfArgs
cFlags += obfArgs
}
}
}
}

结果对比

so 大小

未混淆

混淆后

致谢