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 逆向题
// 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 项目的配置文件,内容如下
# 参考官方文档: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 文件夹中的代码进行测试,内容如下:
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
#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 描述.");
效果: