LLVM从小白到放弃(二)- LLVM Pass

LLVM Pass的基本概念

  • LLVM Pass框架是整个LLVM 提供给用户用来干预代码优化过程的框架,也是我们编写代码混淆工具的基础。
  • 编译后的LLVM Pass通过优化器opt进行加载,可以对LLVM IR中间代码进行分析和修改,生成新的中间代码。

llvm/inlcude/llvm 文件夹

  • llvm/include/llvm 文件夹存放了LLVM提供的一些公共头文件。
  • 即我们在开发过程中可以使用的头文件。

llvm/lib文件夹

  • llvm/lib文件夹存放了LLVM大部分源代码(.cpp文件)和一些不公开的头文件

llvm/lib/Transforms

  • llvm/lib/Transforms文件夹存放所有LLVM Pass的源代码
  • llvm/lib/Transforms文件夹也存放了一些LLVM自带的Pass

LLVM Pass的编写、编译以及加载

LLVM Pass的编写:Hello World
  • LLVM Pass支持三种编译方式:
    • 第一种是与整个LLVM一起重新编译,Pass代码需要存放在llvm/lib/Transforms文件夹中 (编译太耗时间)
    • 第二种方法是通过CMake对Pass进行单独编译 (好!)
    • 第三种方法是使用命令行对Pass进行单独编译 (项目越大越不好管理)
  • 在设计一个新的LLVM Pass时,你最先要决定的就是选择Pass的类型。
  • LLVM有多种类型的Pass可供选择,包括:ModulePass、FuncitonPass、CallGraphPass、LoopPass等等。
  • FunctionPass以函数为单位进行处理
  • FunctionPass的子类必须实现runOnFunction(Function &F)函数。
  • 在FunctionPass运行时,会对程序中的每个函数执行runOnFunction函数。
LLVM Pass的编写:步骤
  • 创建一个类(class),继承FunctionPass父类
  • 在创建的类中实现runOnFunction(Function &F)函数
  • 向 LLVM 注册我们的 Pass 类。
LLVM Pass的加载
  • 使用优化器 opt 将处理中间代码,生成新的中间代码:

opt -load ./LLVMObfuscator.so -hlw -S hello.ll -o hello_opt.ll

  • -load 加载编译好的 LLVM Pass(.so文件)进行优化

编写第一个LLVM Pass-实践部分

CMake创建

目录结构:

➜ OLLVM++-DEmo tree

.

├── Build

├── Test

│ └── TestProgram.cpp

├── test.sh

└── Transforms

​ ├── CMakeLists.txt

​ ├── include

​ └── src

​ └── HelloWorld.cpp

5 directories, 4 files

LLVM Pass的编写、编译以及加载

各自目录功能介绍

Build 文件夹:存放编译后 LLVM Pass

Test 文件夹:存放测试程序 TestProgram.cpp

Test/TestProgram.cpp:一个简单的 CTF 逆向题

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
// Test/TestProgram.cpp

#include <cstdio>

#include <cstring>



char input[100] = {0};

char enc[100] = "\x86\x8a\x7d\x87\x93\x8b\x4d\x81\x80\x8a\x43\x7f\x49\x49\x86\x71\x7f\x62\x53\x69\x28\x9d";



void encrypt(unsigned char *dest, char *src) {

int len = strlen(src);

for (int i = 0; i < len; i ++) {

dest[i] = (src[i] + (32 - i)) ^ i;

}

}



int main() {

printf("Please input your flag: ");

scanf("%s", input);

unsigned char dest[100] = {0};

encrypt(dest, input);

bool result = strlen(input) == 22 && !memcmp(dest, enc, 22);

if (result) {

printf("Congratulations~\n");

} else {

printf("Sorry try again.\n");

}

}

Transforms/include 文件夹:存放整个 LLVM Pass 项目的头文件,暂时还没有用到

Transforms/src 文件夹:存放整个 LLVM Pass 项目的源代码

Transforms/src/HelloWorld.cpp:HelloWorld Pass 的源代码,一般来说一个 Pass 使用一个 cpp 文件 实现即可。

Transforms/CMakeLists.txt:整个 CMake 项目的配置文件,内容如下

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
# 参考官方文档:https://llvm.org/docs/CMake.html#developing-llvm-passes-out-of-source 

project(OLLVM++)

cmake_minimum_required(VERSION 3.13.4)

find_package(LLVM REQUIRED CONFIG)



list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")

include(AddLLVM)

include_directories("./include") # 包含 ./include 文件夹中的头文件



separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS})

add_definitions(${LLVM_DEFINITIONS_LIST})

include_directories(${LLVM_INCLUDE_DIRS})



add_llvm_library(

LLVMObfuscator MODULE

src/HelloWorld.cpp

)

test.sh:编译 LLVM Pass 并对 Test 文件夹中的代码进行测试,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cd ./Build

cmake ../Transforms

make

cd ../Test

clang -S -emit-llvm TestProgram.cpp -o TestProgram.ll

opt -load ../Build/LLVMObfuscator.so -hlw -S TestProgram.ll -o TestProgram_hlw.ll

clang TestProgram_hlw.ll -o TestProgram_hlw



./TestProgram_hlw
LLVM Pass源代码模板
  • 创建一个类(class),继承FunctionPass父类
  • 在创建的类中实现runOnFunction(Function &F)函数
  • 向LLVM注册我们的Pass类

HelloWorld.cpp

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
#include "llvm/Pass.h"

#include "llvm/IR/Function.h"

#include "llvm/Support/raw_ostream.h"



using namespace llvm;



namespace{

class HelloWorld : public FunctionPass { // 继承Pass的FunctionPass

public:

static char ID;

HelloWorld() : FunctionPass(ID) {

// 构造函数

}



bool runOnFunction(Function &F); // runOnFunction实现

};

}



bool HelloWorld::runOnFunction(Function &F) {

// todo

outs() << "Hello, " << F.getName() << "\n";

}



char HelloWorld::ID = 0;

static RegisterPass<HelloWorld> X("hlw", "Pass 描述.");

效果: