WHCSRL 技术网

ios 中集成ollvm12.x_Denny_Chen

一. 前言

最近工作中遇到 ios 代码需要做混淆的需求,因此搜索及研究了下怎么将 ollvm 移植到 xcode 中使用,本篇文章记录整个的流程。在网上查到的 ios 集成 ollvm的方法大多是说:“将 ollvm4.0 编译结果制作成一个xcode插件”,但这种应该只适用较老的xcode版本,在xcode10以上的版本可能并不适用。本人需要移植的是 xcode12.4,对应的 clang 版本是12.0.0,因此那种方法并不适用,且经过尝试,确实没有成功(当然也不排除本人操作不当的可能)。

移植环境:

  1. xcode版本:12.4
  2. llvm版本:12.0.0
  3. Mac OS版本:10.15.7 Catalina

二. ollvm移植(mac、linux通用,window上应该也通用)

移植的方法网上比较多,主要是从官网上 ollvm4.0 移植对应的 Obfuscation pass 到高版本 llvm上。如果llvm版本相差太大,可能有的结构体或者方法会有些许改变,编译的时候就会报错,本人的解决方法是将LLVM工程导入到 CLion 中,针对报错的代码,对比高低版本llvm使用的改变(Find in file功能),然后将 Obfuscation pass 中报错代码修正。

1. 导入步骤:

  1. 用 CLion 打开 Obfuscate/llvm/CMakeLists.txt ,选择 open as project;

  2. 在 File | Settings | Build, Execution, Deployment | CMake 的 CMake options 中配置编译选项,Build Type选Release就快一些,编译选项如下:

    # 使用 ninja
    -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=Off
    # 默认使用 make
    -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=Off
    # 如果要移植到的 NDK 或者 xcode 的toolchain中包含 lib64,则还需添加 
    # -DLLVM_LIBDIR_SUFFIX=64
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  3. 在 File | Settings | Build, Execution, Deployment | Toolchains 中配置 C++编译器是使用 g++还是clang++,C编译器是使用 g 还是 clang,Make是选择 make 还是ninja。这一步其实默认就可以。

cmake 完成后,开始 Build-Build Project 构建工程,如果有报错会在下边栏中给出错误信息和错误代码位置,点过去就可,方便排查错误。另外用CLion的好处还有就是可以跟踪符号,直接点击便可跳转代码。

2. 具体的移植步骤

  1. llvm-project/llvm/include/llvm/Transforms/Obfuscation -----> llvm12_root/ ; (目录层级得一样,下同)
  2. llvm-project/llvm/lib/llvm/Transforms/Obfuscation -----> llvm12_root/ ;
  3. llvm-project/llvm/include/llvm/CryptoUtils.h -----> llvm12_root/ ;
  4. llvm-project/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp 中添加
// Flags for obfuscation
static cl::opt<bool> Flattening("fla", cl::init(false),
                                cl::desc("Enable the flattening pass"));

static cl::opt<bool> BogusControlFlow("bcf", cl::init(false),
                                      cl::desc("Enable bogus control flow"));

static cl::opt<bool> Substitution("sub", cl::init(false),
                                  cl::desc("Enable instruction substitutions"));

static cl::opt<std::string> AesSeed("aesSeed", cl::init(""),
                                    cl::desc("seed for the AES-CTR PRNG"));

static cl::opt<bool> Split("split", cl::init(false),
                           cl::desc("Enable basic block splitting"));

static cl::opt<bool> StringObf("sobf", cl::init(false),
                               cl::desc("Enable the string obfuscation"));

PassManagerBuilder::PassManagerBuilder() {
    OptLevel = 2;
   	...
    CallGraphProfile = true;

	//ollvm add begin
    // Initialization of the global cryptographically
    // secure pseudo-random generator
    if (!AesSeed.empty()) {
        if (!llvm::cryptoutils->prng_seed(AesSeed.c_str()))
            exit(1);
    }
    //ollvm add end
}

void PassManagerBuilder::addLTOOptimizationPasses(legacy::PassManagerBase &PM) {
    // Load sample profile before running the LTO optimization pipeline.
   	...
    // Infer attributes about declarations if possible.
    PM.add(createInferFunctionAttrsLegacyPass());
	
	//ollvm add begin
    PM.add(createSplitBasicBlock(Split));
    PM.add(createBogus(BogusControlFlow));
    if (Flattening) {
        // Lower switch
        PM.add(createLowerSwitchPass());
    }
    PM.add(createFlattening(Flattening));
    PM.add(createSubstitution(Substitution));
    PM.add(createStringObfuscation(StringObf));
	//ollvm add end
	
    if (OptLevel > 1) {
        ...
    }
    PM.add(createJumpThreadingPass(/*FreezeSelectCond*/ true));
}

  • 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
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  1. llvm-project/llvm/lib/Transforms/CMakeLists.txt 中添加:
add_subdirectory(Obfuscation)
  • 1
  1. llvm-project/llvm/lib/Transforms/IPO/CMakeLists.txt 中添加:

    add_llvm_component_library(
    ...
    Obfuscation
      )
    
    • 1
    • 2
    • 3
    • 4

    llvm12中已经弃用 LLVMBuild.txt,这一项本来是加在 llvm-project/llvm/lib/Transforms/IPO/LLVMBuild.txt 中,现在改到加在这里。
    移植的 ollvm12.x (兼 Armariris 字符串加密)已上传至:https://github.com/Chenyangming9/llvm-project/tree/release/ollvm12.x

三. ollvm 整合到工具链中使用

不管是哪个平台,最好先事先看下原来的llvm的版本 (命令:clang --version),然后将ollvm移植到对应的llvm版本中。

1. Android NDK

git clone ollvm代码地址
mkdir llvm_root/build_release # CLion中默认生成在 cmake-build-release 或 cmake-build-debug
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS='clang'  -DLLVM_INCLUDE_TESTS=OFF -DLLVM_LIBDIR_SUFFIX=64 ../llvm #大多的NDK中有 lib64,所以添加-DLLVM_LIBDIR_SUFFIX=64,xcode中没有,不用加。为什么加 -DLLVM_ENABLE_PROJECTS='clang',因为 Obfuscation pass会编译进 clang,所以要加该项
ninja -j7 
# 如果有命令找不到,则按照 官网 [https://releases.llvm.org/12.0.0/docs/GettingStarted.html](https://releases.llvm.org/12.0.0/docs/GettingStarted.html) 中说明配置
  • 1
  • 2
  • 3
  • 4
  • 5

编译完成后,将 llvm_root/build_release 中 bin 、include、lib、lib64 覆盖拷贝到 android-ndk/toolchains/llvm/prebuilt/linux-x86_64 中即可。

2. xcode 中集成

(1)编译

git clone ollvm代码地址
mkdir llvm_root/build_release # CLion中默认生成在 cmake-build-release 或 cmake-build-debug
cmake -DLLVM_CREATE_XCODE_TOOLCHAIN=ON -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS='clang' -DLLVM_INCLUDE_TESTS=OFF ../llvm #大多的NDK中有 lib64,所以添加-DLLVM_LIBDIR_SUFFIX=64,xcode中没有,不用加。为什么加 -DLLVM_ENABLE_PROJECTS='clang',因为 Obfuscation pass会编译进 clang,所以要加该项
make install-xcode-toolchain -j10
# 编译完成后生成的工具链位于 /usr/local/Toolchains,将其移动到 /Library/Developer/ 目录
sudo mv /usr/local/Toolchains /Library/Developer/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

DLLVM_CREATE_XCODE_TOOLCHAIN 选项参考:https://llvm.org/docs/CMake.html#llvm-related-variables

说明: 之前尝试使用 -G “Xcode”,则可以在 xcode 中打开工程,但报错如下:

CMake Error in utils/benchmark/CMakeLists.txt:
The custom command generating
/usr/local/Toolchains/LLVM12.0.0.xctoolchain
is attached to multiple targets:
install-xcode-toolchain
install-xcode-toolchain-stripped
but none of these is a common dependency of the other(s). This is not
allowed by the Xcode "new build system".
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

找不到解决方法,就弃用了。

(3)xcode 中使用

打开后 xcode 后,在 Xcode-Preferences-Components-Toolchains看到生成好的工具链,如下图:
在这里插入图片描述
或者:Xcode-Toolchains中查看到,如下图:
在这里插入图片描述
更换工具链,在以上两个位置都可以更换。使用新工具链编译 Xcode 项目,报错unknown argument: ‘-index-store-path’,需要到项目 Build Settings 关闭 Index-While-Building,应该是这个参数在开源的 LLVM 项目中没有实现。如下:
在这里插入图片描述
关闭该选项,如下图:在这里插入图片描述
添加 ollvm 编译选项,-mllvm -fla -mllvm -sub -mllvm -bcf -mllvm -sobf
在这里插入图片描述

(4)印证新的工具链中使用的clang是编译出来的clang

在main函数前,添加申明 “int add();”,并在main中添加调用 “add();”,编译时,会因为找不到函数实现而报错,从报错信息中可以看到使用的clang路径是我们自己编译的clang的路径,如下图:
在这里插入图片描述
如果使用的是系统默认的原生工具链或者我们编译ollvm时没有指定“-DLLVM_ENABLE_PROJECTS=‘clang’”时,使用的路径是xcode中默认的clang路径,如下图:
在这里插入图片描述

(5)xcode 中混淆后的效果

混淆前:
在这里插入图片描述
混淆后:
在这里插入图片描述

三. xcode集成ollvm后使用时遇到的问题

在运行一些小工程时,几乎没遇到什么问题。但要用来跑比较大的工程(编译出的可执行文件有60多M,并且工程中包含很多三方框架)时,遇到了一些问题。

1. 报 ld 找不到的问题解决

ld 是一个链接器,在原生工具链中有,但在编译出来的工具链中没有看到该工具。这边解决方法是,将原生工具链中工具全部拷贝到我们自己编译的工具链中,然后用我们自己编译的工具链内容去覆盖那个目录,如以下脚本内容:

# 先备份
sudo mkdir /Library/Developer/Toolchains/LLVM12.0.0.xctoolchain/tmp
sudo mv /Library/Developer/Toolchains/LLVM12.0.0.xctoolchain/usr/ /Library/Developer/Toolchains/LLVM12.0.0.xctoolchain/tmp/usr/
# 将原生工具链中工具全部拷贝到我们自己编译的工具链中
sudo cp -rf /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/ /Library/Developer/Toolchains/LLVM12.0.0.xctoolchain/usr/
# 覆盖拷贝,使用 ditto 命令,可以让文件夹及子文件夹 只覆盖相同名称的文件,而不会覆盖整个文件夹,导致有些文件丢失
sudo ditto /Library/Developer/Toolchains/LLVM12.0.0.xctoolchain/tmp/usr/ /Library/Developer/Toolchains/LLVM12.0.0.xctoolchain/usr/ 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2. 报错 linker error Assertion failed:(atom->fixupCount()==1)

经过多次排查,发现是字符串加密的pass导致的,去掉 -mllvm -sobf 就可以正常使用了。

3. ollvm 移植到 llvm13.x 时的一个问题

类似 ollvm12.x,将 ollvm 的代码移植到 llvm13.x 即可,但使用混淆功能时却没有效果。后来经过不断排查和尝试,在使用混淆时,额外再加编译选项:-fno-experimental-new-pass-manager ,例如:

clang-13 test.cpp -o test -mllvm -bcf -fno-experimental-new-pass-manager
#或者用生成 ll 代码的命令测试
clang -emit-llvm -S main.c -mllvm -bcf -mllvm -fla -mllvm -sub -mllvm -sobf -fno-experimental-new-pass-manager
  • 1
  • 2
  • 3

混淆生效后,ll 文件大小会大很多,效果图如下:
在这里插入图片描述
如果文章不错,也请多支持,如果有不对的地方,也请多指正~

四. 参考

  1. llvm 官网
  2. [iOS 逆向 13] 代码混淆
  3. 人工智能 Xcode集成OLLVM踩坑
推荐阅读