本项目基于LLVM NewPass实现原版OLLVM和Hikari的Pass化,有以下目标:
- 验证可以用独立
Pass实现IR层混淆 - 用于辅助验证另一个项目
ida_mcp
本项目在MacOS15+LLVM15-19上测试,因为最大限度保留原始代码未改动因此不会有混淆功能方面的增强。对更强的混淆功能有兴趣的可以关注我的另一个项目SLLVM
使用独立Pass实现混淆的优缺点:
- 优点1:如果已经用
Homebrew(Mac)/apt(Debian)等安装过LLVM则无需编译LLVM,开发速度快 - 优点2:
Pass本身编译速度快,编译出的文件小 - 缺点1:搭配对应的
Clang一起使用,至少保证LLVM大版本匹配
在很多实际项目中,由于以下原因无法对整个项目完全混淆:
- 项目较大,依赖较多,或使用了很多header-only的库,混淆了很多不需要混淆的代码,导致编译出来的二进制过大
- 项目较大,依赖较多,使用了平坦化(或其他方式)混淆了很多不需要混淆的代码,导致编译时间过久甚至卡死
- 混淆了复杂算法,导致运行时耗时比正常大很多,一般使用平坦化后耗时会增加10%以上
- 混淆过多可能不允许上架AppStore/GooglePlay等
实际操作时, 常常需要根据模块/函数的重要性使用不同程度的混淆,因此需要配置策略来指定哪些模块/函数需要用哪种混淆,而开源的OLLVM常见设置策略的方式如下:
- 对需要混淆的模块单独指定命令行参数,如
-llvm -fla,这种方式兼容所有支持LLVM命令行参数的编译器前端 - 使用环境变量指定混淆参数
- 对需要混淆的函数指定注解,如
__attribute((__annotate__(("fla"))))(新式语法[[clang::annotate("fla")]]),这种方式仅支持C/C++,Objective-C和其他语言均不支持 - 对需要混淆的函数指定标记函数,如下所示,这种方式支持
Objective-C
extern void hikari_fla(void);
@implementation foo2:NSObject
+(void)foo{
hikari_fla();
NSLog(@"FOOOO2");
}
@end以上方式均有局限性,或对代码改动太大,或无法控制到函数粒度,或只支持特定语言。本项目使用配置文件来指定需要混淆的函数和模块,兼容大部分编译器前端及开发语言。策略文件为工作目录下名为policy.json的文件,需用户提供,字段如下:
| 字段类型 | 字段含义 | 必须 | |
|---|---|---|---|
| globals | 字典 | 全局选项 | 否 |
| policy_map | 字典 | 策略名 - 混淆选项集合的映射 |
是 |
| policies | 数组 | 所有策略 | 是 |
| policies.module | 字符串 | 正则匹配模块名 | 是 |
| policies.func | 字符串 | 正则匹配函数名 | 是 |
| policies.policy | 字符串 | 策略名,对应policies |
是 |
语法如下:
- 前向覆盖,如果
policies数组中,如果后面的项匹配的module/func是在其之前项匹配的子集,则覆盖前一项对应的策略 - 支持注释:非必须的子字段,都支持
#注释,如"#enable-strcry": true - 支持名称混淆:本人另一个项目
SLLVM支持c++/swift等语言名称混淆情况下的module/func匹配
{
"globals": {
"acd-use-initialize": true,
...
},
"policy_map": {
"test_pol": {
"enable-strcry": true,
"enable-splitobf": false,
"split_num": 2,
...
}
},
"policies": [
{
"module": ".*",
"func": ".*",
"policy": "test_pol"
}
]
}在每个子工程(原版OLLVM/Hikari/...)中的test目录可找到policy.json,其中包含了所有可配置字段
若能找到LLVM对应的编译目录位置(如已经用Homebrew(Mac)/apt(Debian)等安装过LLVM,可定位到./cmake/AddLLVM.cmake)则可跳过编译
# optional: -DLLVM_ENABLE_PROJECTS=clang -DCMAKE_BUILD_TYPE=Debug
cmake -S llvm -G Ninja -B llvm_build_dir
cmake --build llvm_build_dir以Mac系统+原版OLLVM为例
git clone https://github.com/lich4/llvm-pass-hikari
cd llvm-pass-hikari
export LLVM_DIR=/path/to/llvm_build_dir
cmake -S obfuscator -G Ninja -B obfuscator/build
cmake --build obfuscator/build以Mac系统+原版OLLVM为例,注意:
Pass要匹配对应的LLVM/Clang大版本- 如果测试
objc++时报头文件相关错误则需要开源clang版本和Xcode对应clang版本一致
cd test
# test c
/path/to/llvm_build_dir/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=../build/Hikari.dylib test.c -o test
# test cpp
/path/to/llvm_build_dir/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=../build/Hikari.dylib -std=c++11 -stdlib=libc++ -lc++ test.cpp -o test
# test objc
/path/to/llvm_build_dir/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=../build/Hikari.dylib -framework Foundation test.m -o test
# test objc++
/path/to/llvm_build_dir/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=../build/Hikari.dylib -framework Foundation -std=c++11 -stdlib=libc++ -lc++ test.mm -o test由于开源LLVM的Clang不同于Xcode的Clang,因此动态Pass不能直接用于Xcode,可以考虑以下方式:
- 在
Xcode中指定CC变量为开源clang(如brew install llvm@15),且指定Other C Flags的-fpass-plugin为对应Pass路径 - 在
Xcode中指定CC变量为编译脚本,脚本逻辑为"先用clang -emit-llvm参数生成bitcode,然后运行opt执行Pass,最后用clang -c生成原本要生成的obj文件"。此种方式可以直接使用Xcode自带的Apple clang,能比较好的兼容arm64e架构 - 直接针对
Xcode自带的Apple clang开发动态Pass,在Xcode中指定Other C Flags指定-fpass-plugin为Pass路径。此种方式复杂度较高,需要处理大量符号冲突,只适合精通LLVM的开发者。此种方式可以直接使用Xcode自带的Apple clang,能比较好的兼容arm64e架构