什么是OLLVM?

首先了解什么是llvm,[原创]LLVM了解篇剧场版

以上这篇文章简要地介绍了LLVM

LLVM:Low Level Virtual Machine(最初)

官方定义:LLVM是一个模块化和可重用的编译器和工具链技术的集合

LLVM最初是在2000年由伊利诺伊大学香槟分校(UUIC)的学生Chris Lattner及其硕士顾问Vikram Adve创建的研究项目,并在2003年发布第一个正式版本,目的是提供一种基于SSA的现代编译策略,这种策略能够支持任何编程语言的静态和动态编译。

有意思的是,他的作者原本想要写一个低层虚拟机的,然而他并没有被当成虚拟机来用,逐渐地偏离了他原本的方向。现在llvm不是任何单词的缩写,而是一种体系,包含了一系列的项目

对于LLVM核心(LLVM Core)来说,它是一个编译器基础设施框架,它包含了为我们编写编译器一系列的库(如程序分析、代码优化、机器代码生成等),并且提供了调用这些库的相关工具,如llvm-opt可以调用LLVM优化相关的库,llvm-mc可以调用LLVM机器代码生成相关的库。并为此提供了一个非常方便简单、具备类型的、平台无关的统一中间代码语言,称之为LLVM IR.

Obfuscator-LLVM

Obfuscator-LLVM是由瑞士伊夫尔东莱班的应用科学与艺术大学信息安全小组(HEIG-VD)于2010年6月发起的一个项目。

该项目的目的是提供LLVM编译套件的开源分支,该套件能够通过代码混淆和防篡改来提高软件安全性。由于我们目前大部分工作在中间表示(IR)级别,因此我们的工具与所有编程语言(C,C ++,Objective-C,Ada和Fortran)和目标平台(x86,x86-64,PowerPC,PowerPC-64)兼容。

LLVM当前支持的ARM,Thumb,SPARC,Alpha,CellSPU,MIPS,MSP430,SystemZ和XCore)。

Obfuscator-LLVM的目的主要是在编译时增强软件就对抗向工程和编译时修改(动态调试?)

总之一句话,就是混淆llvm编译的可执行文件,让逆向分析更困难

环境编译

移植ollvm或者写自定义的pass等等,都需要先编译环境,网上有很多ollvm版本。

由于最开始就是llvm4.0版本,但现在迭代太快,不可能用这么老的版本。

这里演示挑选:ollvm9.0版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜ clang --version
Homebrew clang version 14.0.6
Target: x86_64-apple-darwin21.2.0
Thread model: posix
InstalledDir: /usr/local/opt/llvm/bin

➜ gcc -v
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX12.1.sdk/usr/include/c++/4.2.1
Apple clang version 13.0.0 (clang-1300.0.27.3)
Target: x86_64-apple-darwin21.2.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

➜ cmake --version
cmake version 3.21.3

孤挺花https://github.com/GoSSIP-SJTU/Armariris中编译的是llvm4.0版本。

许多功能没有,hakari的字符串没有孤挺花的好用。

但是可以移植,这里找了一份可移植性比较高的版本。https://github.com/kunnan/KNHikari-20180526

将全部源码下载,mac编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
git clone https://github.com/kunnan/KNHikari-20180526
cd KNHikari-20180526
mkdir build && cd build/
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON -DLLVM_ENABLE_PROJECTS=clang -G "Unix Makefiles" ../KNHikari-20180526
make -j#这个是默认开启全部cpu编译,直接起飞。

[100%] Linking CXX executable ../../../../bin/c-arcmt-test
[100%] Linking CXX executable ../../../../bin/c-index-test
ld: warning: directory not found for option '-L/opt/homebrew/opt/llvm/lib'
ld: warning: directory not found for option '-L/opt/homebrew/opt/llvm/lib'
[100%] Built target c-arcmt-test
[100%] Built target llvm-c-test
[100%] Built target clang-check
[100%] Built target llvm-dwp
[100%] Built target clang
[100%] Built target llvm-isel-fuzzer
[100%] Built target llvm-opt-fuzzer
[100%] Built target llvm-lto2
[100%] Built target c-index-test
[100%] Built target opt

编译完成会生成build/bin目录。

但是一般不出意外都会编译错误。

这里有很多编译问题:https://magic-king.net/posts/ollvm-learning/

https://www.jianshu.com/p/ab3694b12cfc 这位大佬已经帮我们编译好了一个ollvm版本,亲测可用。

接下来讲讲怎么把孤挺花里面的stringpass移植进去。

移植stringpass

一般的pass文件都是在lib/Transforms/Obfuscation

头文件 include/llvm/Transforms/Obfuscation

目标是将StringObfuscation.cpp移植到goron中,

先看看cpp中需要那些h文件。

1
#include "Transforms/Obfuscation/StringObfuscation.h"

拷贝到goron同级目录中。

看下我的目录结构

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
goron/llvm/lib/Transforms/Obfuscation on  llvm-9.0.0 [!?] 
➜ tree
.
├── CMakeLists.txt
├── CryptoUtils.cpp
├── EncodeFuncName.cpp
├── Flattening.cpp
├── IPObfuscationContext.cpp
├── IndirectBranch.cpp
├── IndirectCall.cpp
├── IndirectGlobalVariable.cpp
├── LICENSE-OBFUSCATOR.txt
├── LLVMBuild.txt
├── LegacyLowerSwitch.cpp
├── ObfuscationOptions.cpp
├── ObfuscationPassManager.cpp
├── StringEncryption.cpp
├── StringObfuscation.cpp
└── Utils.cpp

goron/llvm/include/llvm/Transforms/Obfuscation on  llvm-9.0.0 [!?]
➜ tree
.
├── CryptoUtils.h
├── EncodeFuncName.h
├── Flattening.h
├── IPObfuscationContext.h
├── IndirectBranch.h
├── IndirectCall.h
├── IndirectGlobalVariable.h
├── LegacyLowerSwitch.h
├── ObfuscationOptions.h
├── ObfuscationPassManager.h
├── StringEncryption.h
├── StringObfuscation.h
└── Utils.h

0 directories, 13 files

移植了要注意以下几点:

  1. cmakelist要声明StringObfuscation.cpp

  2. ObfuscationPassManager.h 添加\#include "llvm/Transforms/Obfuscation/StringObfuscation.h"

  3. ObfuscationOptions.h 添加bool EnableSOBF;

  4. StringObfuscation.h 改为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #ifndef OBFUSCATION_STRING_OBFUSCATION_H
    #define OBFUSCATION_STRING_OBFUSCATION_H

    namespace llvm {
    class ModulePass;
    class PassRegistry;
    class IPObfuscationContext;
    struct ObfuscationOptions;

    ModulePass* createStringObfuscationPass();
    ModulePass* createStringObfuscationPass(bool flag, IPObfuscationContext *IPO, ObfuscationOptions *Options);
    void initializeStringEncryptionPass(PassRegistry &Registry);

    }

    #endif
  5. StringObfuscation.cpp 将最后几行修改一下,为了匹配头文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    char StringObfuscationPass::ID = 0;
    static RegisterPass<StringObfuscationPass> X("GVDiv", "Global variable (i.e., const char*) diversification pass", false, true);

    // Pass * llvm::createStringObfuscationPass(bool flag) {
    // return new StringObfuscationPass(flag);
    // }

    ModulePass *llvm::createStringObfuscationPass() { return new StringObfuscationPass(true); }
    ModulePass *llvm::createStringObfuscationPass(bool flag,
    IPObfuscationContext *IPO,
    ObfuscationOptions *Options) {
    return new StringObfuscationPass(flag);
    }
  6. ObfuscationPassManager.cpp 中也要添加一个注册方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    static cl::opt<bool>
    EnableIRStringObfuscation("irobf-sobf", cl::init(false), cl::NotHidden,
    cl::desc("Enable IR Constant String Obfuscation."));

    if (EnableIRStringEncryption || Options->EnableCSE) {
    add(llvm::createStringEncryptionPass(true, IPO, Options.get()));
    // add(llvm::createStringObfuscationPass(true, IPO, Options.get()));
    } else if (EnableIRStringObfuscation || Options->EnableSOBF)
    {
    add(llvm::createStringObfuscationPass(true, IPO, Options.get()));
    }

可以开始编译了,不出意外就不会出意外,编译完成之后。

移植到NDK中

建议21.3/21.4版本的NDK

将ollvm中的clang, clang++, clang-format三个文件复制到ndk/21.1.6352462/toolchains/llvm/prebuilt/darwin-x86_64/bin中,使用替换方式。

配置NDK

app\build.gradle中增加下面配置:

1
2
3
4
5
6
7
android {
// ......
// 声明使用NDK的版本
ndkVersion "21.1.6352462"

// ......
}

编译APK

出现下面错误

1
/sysroot/usr/include/jni.h:27:10: fatal error: 'stdarg.h' file not found

复制头文件

上面的提示缺少stdarg.h文件,此文件在ollvm编译的文件夹lib/clang/9.0.0/include/中可以找到,然后复制到/ndk/21.1.6352462/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/include的目录下。

以此类推的复制头文件,我这里一共复制了下面这些文件:

1
2
3
4
stdarg.h
stddef.h
__stddef_max_align_t.h
float.h

开启混淆

上面的构建是一个常规的c++项目,并没有开启ollvm的混淆,把混淆开启一下,goron支持的配置如下:

说明 配置
间接跳转 -mllvm -irobf-indbr
间接函数调用 -mllvm -irobf-icall
间接全局变量引用 -mllvm -irobf-indgv
字符串(c string)加密功能 -mllvm -irobf-cse
过程相关控制流平坦混淆 -mllvm -irobf-cff

过程相关控制流平坦混淆 -mllvm -irobf-sobf 孤挺花的字符串加密。

1
2
3
4
5
externalNativeBuild {
cmake {
cppFlags "-mllvm -irobf-indbr -mllvm -irobf-icall -mllvm -irobf-indgv -mllvm -irobf-cse"
}
}

下载

知道你们懒,这是我编译好的goron,直接替换到你们的ndk就可以了,下载去吧:腾讯云

考虑时间的流逝,替换的时请注意您的ndk里的llvm的版本,我当前的版本:21.1.6352462