# LLVM Pass
# LLVM 基础 (随便写写)
Compilers 编译器总体结构
-
Lexical Analysis 词法分析
-
Parsing 语法分析
-
Semantic Analysis 语义分析 (主要)
---------- 前端 ----------
-
Optimization 优化 (主要)
---------- 后端 ----------
-
Code Generation 代码生成
LLVM 编译器结构如下图
优点:通用的中间表示(IR)来表示程序的结构和行为
# 环境配置
官方教程~~~~
https://llvm.org/docs/WritingAnLLVMPass.html
一开始我还编译 llvm-project… 中途还遇到了 git clone 文件过大的问题… 果断放弃
Ubantu apt
# 在 Ubuntu 上安装 LLVM 17
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-add-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main"
sudo apt-get update
sudo apt-get install -y llvm-17 llvm-17-dev llvm-17-tools clang-17
这将安装所有必需的头文件、库和工具路径为 /usr/lib/llvm-17/
。
查找安装路径
mou@mou-virtual-machine:~/桌面$ cd .. | |
mou@mou-virtual-machine:~$ cd /usr/include | |
mou@mou-virtual-machine:/usr/include$ ls llvm* | |
llvm-17: | |
llvm | |
llvm-c-17: | |
llvm-c | |
mou@mou-virtual-machine:/usr/include$ cd .. | |
mou@mou-virtual-machine:/usr$ cd lib | |
mou@mou-virtual-machine:/usr/lib$ ls llvm-17/ | |
bin build cmake include lib share | |
mou@mou-virtual-machine:/usr/lib$ |
CMake 编译相关
使用 cmake 的 find_library
cmake 学习之前有记录 这里就不过多赘述了
apt 安装的,你让他找得到 llvm 就行 ()
坑
之前我 apt 装好 llvm 也明白了安装路径就在网上随便找了 pass 代码编译 结果按照给的命令运行后没有任何回显 问了大佬才发现复制的代码是 leagct pass 不应该是 -fpass-plugin 载入插件… 于是引出了我对新版管理器的学习…
12.12 在加装一个 T 固态后先已经从 VMware 转战 WSL
# PASS
LLVM Pass 框架是 LLVM 提供给用户用来干预代码优化过程的框架,编译后的 LLVM Pass 通过优化器 opt 进行加载,可以对 LLVM IR 中间代码进行分析和修改,生成新的中间代码。 在实现上,LLVM 的核心库中会给你一些 Pass 类 去继承。你需要实现它的一些方法。 最后使用 LLVM 的编译器会把它翻译得到的 IR 传入 Pass 里,给你遍历和修改。
LLVM IR 实际上有三种表示:
- .ll 格式:人类可以阅读的文本。
- .bc 格式:适合机器存储的二进制文件。
- 内存表示
首先,.ll 格式和.bc 格式是如何生成并相互转换的呢?下面我列了个常用的简单指令清单:
- .c -> .ll:
clang -emit-llvm -S a.c -o a.ll
- .c -> .bc:
clang -emit-llvm -c a.c -o a.bc
- .ll -> .bc:
llvm-as a.ll -o a.bc
- .bc -> .ll:
llvm-dis a.bc -o a.ll
- .bc -> .s:
llc a.bc -o a.s
可以看到, clang
通过 -emit-llvm
参数, 使得原本要生成汇编以及机器码的指令生成了 LLVM IR 的 ll 格式和 bc 格式。 这可以理解为:对于 LLVM IR 来说,.ll 文件就相当于汇编,.bc 文件就相当于机器码。 这也是 llvm-as
和 llvm-dis
指令为什么叫 as
和 dis
的缘故。
LLVM 有多种类型的 Pass 可供选择,包括:ModulePass (基于模块)、FunctionPass (基于函数)、CallGraphPass (基于调用图)、LoopPass (基于循环) 等
参考学习的好👍项目:
https://github.com/banach-space/llvm-tutor llvm-17 新版管理器
clang-17 -O1 -emit-llvm input.c -S -o out.ll #得.ll 文件 |
/usr/lib/llvm-17/bin/opt -load-pass-plugin ./libHelloWorld.so -passes=hello-world -disable-output out.ll |
一个 hello world 的 FunctionPass,基于函数的 Pass 打印函数名称和参数数量
后续传 github
# LLVM IR 学习 (略)
LLVM IR 是一门低级编程语言 语法类似于汇编
# CSCD70
cscd70 作业可以做做
# 自己写 pass 实现 OLLVM 之指令替代
之前做 csapp 的 datalab 就发现算数运算存在一些恒等式,若能替换也算是一种混淆吧 后面了解 OLLVM 之后就想着自己实现一下
减法:a - b == (a + ~b) + 1
加法:a + b == (((a ^ b) + 2 * (a & b)) * 39 + 23) * 151 + 111 (仅限 byte)
异或:x^y==x-y-2(x|~y)-2*
1.(a|b)&(a|b)
2.(a&b)&(a&b)
3.(a&b)|(a&b)
# MBASub
混合布尔算术(Mixed Boolean-Arithmetic, MBA)是一种混淆算法,这种算法由算数运算(例如 ADD、SUB、MUL)和布尔运算(例如 AND、OR、NOT)的混合使用组成。具体参考 https://bbs.kanxue.com/thread-271574.htm (线性 MBA)
PASS 注册方式有两种,一是修改 PassRegistry.def 和 PassBuilder.cpp 文件,直接追加 Pass 定义进去。第二种是用插件接口进行注册,也是我使用的方式
解析一个能跑通的 pass 的组成部分
# llvmGetPassPluginInfo 获取相关信息
llvm 相关函数 google 一下有官网介绍
//----------------------------------------------------------------------------- | |
// New PM Registration 写 pass 套这个就行 | |
//----------------------------------------------------------------------------- | |
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo | |
llvmGetPassPluginInfo() { | |
return {LLVM_PLUGIN_API_VERSION, "mba-sub", LLVM_VERSION_STRING,//llvm 版本号环境取值,第二个是插件名字,第三个是插件版本号都是随便传 | |
[](PassBuilder &PB) { // 第四个参数比较重要,回调函数传递 PassBuilder &PB 对象 | |
PB.registerPipelineParsingCallback( | |
[](StringRef Name, FunctionPassManager &FPM, | |
ArrayRef<PassBuilder::PipelineElement>) { | |
if (Name == "mba-sub") { | |
FPM.addPass(MBASub()); | |
//FPM.addPass (MBASub ()) 这行代码向函数级别 Pass 管理器 FPM 中添加了一个 MBASub Pass 的实例。在这里,MBASub () 表示创建了 MBASub 结构体的一个实例 (默认构造函数),并将其添加到 Pass 管理器中 | |
return true; | |
} | |
return false; | |
}); | |
}}; | |
} |
# PassInfoMixin 相关
All LLVM passes inherit from the CRTP mix-in
PassInfoMixin<PassT>
. The pass should have arun()
method which returns aPreservedAnalyses
and takes in some unit of IR along with an analysis manager.
PassInfoMixin 模板类简化注册过程
继承了 PassInfoMixin 的类我们就可以视为是一个 Pass
//mixin 的作用是当多个组件都需要相同的数据或者某些功能的话就可以把这些内容提取出来写成一个 mixin, 虽然 C++ 没有这种特性,但通过特殊技巧实现了,暂且不管 | |
struct MBASub : public llvm::PassInfoMixin<MBASub> { | |
// 定义了一个结构体 MBASub,并通过 llvm::PassInfoMixin 类来继承一些 Pass 相关的信息 | |
//Pass 插件主入口点,为回调函数,会把源码的函数 IR 对象和和函数函数管理对象传递此函数 | |
llvm::PreservedAnalyses run(llvm::Function &F, | |
llvm::FunctionAnalysisManager &); | |
bool runOnBasicBlock(llvm::BasicBlock &B); | |
static bool isRequired() { return true; }//isRequired 是可选的,不实现则会是默认 false | |
}; |
补充 PassInfoMixin 的声明作参考
/// A CRTP mix-in to automatically provide informational APIs needed for | |
/// passes. | |
/// | |
/// This provides some boilerplate for types that are passes. | |
template <typename DerivedT> struct PassInfoMixin { | |
/// Gets the name of the pass we are mixed into. | |
static StringRef name() { | |
static_assert(std::is_base_of<PassInfoMixin, DerivedT>::value, | |
"Must pass the derived type as the template argument!"); | |
StringRef Name = getTypeName<DerivedT>(); | |
Name.consume_front("llvm::"); | |
return Name; | |
} | |
void printPipeline(raw_ostream &OS, | |
function_ref<StringRef(StringRef)> MapClassName2PassName) { | |
StringRef ClassName = DerivedT::name(); | |
auto PassName = MapClassName2PassName(ClassName); | |
OS << PassName; | |
} | |
}; |
# ReplaceInstWithInst 函数
void llvm::ReplaceInstWithInst (BasicBlock *BB, BasicBlock::iterator &BI, Instruction *I)
Replace the instruction specified by BI with the instruction specified by I. (将BI指定的指令替换为I指定的指令)
# run () method which returns a PreservedAnalyses 实现
//-------------- | |
PreservedAnalyses MBASub::run(llvm::Function &F, | |
llvm::FunctionAnalysisManager &) {//ModulePass 需要这里是 llvm::Module 和 ModuleAnalysisManager,FunctionPass 需要这里是 Function 和 FunctionAnalysisManager | |
bool Changed = false; | |
std::cout << "HAHAHAHAHAHAHAHAHA MyPass Loaded" << std::endl; | |
for (auto &BB : F) { | |
Changed |= runOnBasicBlock(BB); | |
} | |
return (Changed ? llvm::PreservedAnalyses::none() | |
: llvm::PreservedAnalyses::all());//run 函数返回一个 PreservedAnalyses 结构表示哪些 Analysis Pass 的结果有变化。比如没有任何改变,可以返回 PreservedAnalyses::all () 表示保留全部分析结果;如果变化了,可以返回 PreservedAnalyses::none () 表示分析结果全部失效,需要重新计算。 | |
} |
# MBASub 实现
//----------------------------------------------------------------------------- | |
// MBASub 实现 | |
//----------------------------------------------------------------------------- | |
bool MBASub::runOnBasicBlock(BasicBlock &BB) { | |
bool Changed = false; | |
// 在 LLVM 中,BasicBlock(基本块)是一个基本的控制流单元,它包含一系列按照程序的控制流路径顺序排列的指令。因此,在 LLVM 中,将基本块中的指令作为迭代器的元素是很自然的选择,因为基本块实际上就是一个指令序列 | |
// 循环块中的所有指令。 替换指令需要迭代器 | |
for (auto Inst = BB.begin(), IE = BB.end(); Inst != IE; ++Inst) {// 初始化了一个名为 Inst 的迭代器,指向基本块(BB)中的指令的起始位置 | |
//IE = BB.end (): 这里初始化了另一个名为 IE 的迭代器,指向基本块中指令的结束位置 | |
// 跳过非二元指令 | |
auto *BinOp = dyn_cast<BinaryOperator>(Inst);// 在 LLVM 中,BinaryOperator 是表示二元操作符的一种特殊指令类型。它用于表示诸如加法、减法、乘法等二元操作的指令。BinaryOperator 继承自 Instruction 类,因此它是指令的一种 | |
if (!BinOp) | |
continue; | |
/// 跳过不是减法,或者不是整数的指令 | |
unsigned Opcode = BinOp->getOpcode(); | |
if (Opcode != Instruction::Sub || !BinOp->getType()->isIntegerTy()) | |
continue; | |
IRBuilder<> Builder(BinOp);// 创建了一个 IRBuilder 对象,插入新的指令、操作数等到当前基本块所需的 | |
// Create (a + ~b) + 1 | |
Instruction *NewValue = BinaryOperator::CreateAdd( | |
Builder.CreateAdd(BinOp->getOperand(0), | |
Builder.CreateNot(BinOp->getOperand(1))), | |
ConstantInt::get(BinOp->getType(), 1)); | |
// 替换! | |
ReplaceInstWithInst(&BB, Inst, NewValue); | |
Changed = true; | |
} | |
return Changed; | |
} |
附混淆效果
# MBAAdd
(有点神了 这个指令替换 d810 解不了)
见 github
# XOR
[{c1: c5/2, c2: -c5/2, c3: -c5/2, c4: -c5}]
其中,c5 为任意非零常数
得:
x^y==x-y-2*(x|~y)-2
MBAXor 实现
//----------------------------------------------------------------------------- | |
// MBAXor 实现 | |
//----------------------------------------------------------------------------- | |
bool MBAXor::runOnBasicBlock(BasicBlock &BB) { | |
bool Changed = false; | |
for (auto Inst = BB.begin(), IE = BB.end(); Inst != IE; ++Inst) { | |
auto *BinOp = dyn_cast<BinaryOperator>(Inst); | |
if (!BinOp) | |
continue; | |
unsigned Opcode = BinOp->getOpcode(); | |
if (Opcode != Instruction::Xor || !BinOp->getType()->isIntegerTy()) | |
continue; | |
IRBuilder<> Builder(BinOp); | |
auto Val2 = ConstantInt::get(BinOp->getType(), 2); | |
// 逐步拆解 x-y-2*(x|~y)-2 可变成 x-(y+(2*(x|~y)+2)) | |
Instruction *NewValue = BinaryOperator::CreateSub(BinOp->getOperand(0), | |
Builder.CreateAdd(BinOp->getOperand(1), | |
Builder.CreateAdd( | |
Builder.CreateMul(Val2, | |
Builder.CreateOr( | |
BinOp->getOperand(0), | |
Builder.CreateNot( | |
BinOp->getOperand(1)))), | |
Val2))); | |
// 替换! | |
ReplaceInstWithInst(&BB, Inst, NewValue); | |
Changed = true; | |
} | |
return Changed; | |
} |
test.cpp
#include <stdio.h> | |
int main() { | |
int a = 78; | |
int b = 67; | |
int c = 34; | |
int d = 45; | |
int e = 99; | |
int result = a ^ b; | |
result ^= c; | |
result ^= d; | |
result ^= e; | |
result ^= e; | |
printf("The result of 5 XOR operations is: %d\n", result); | |
printf("a: %d\n", a); | |
printf("b: %d\n", b); | |
printf("c: %d\n", c); | |
printf("d: %d\n", d); | |
printf("e: %d\n", e); | |
return 0; | |
} |
/usr/lib/llvm-17/bin/opt -load-pass-plugin ./libMYXor.so -passes=“mba-xor” -S out.ll -o temp.ll
clang-17 temp.ll -o temp# 得二进制文件
# d810 对抗相关
像 mbaadd 这种针对特定 byte 数据范围的 d810 默认自带的反混淆规则没有的式子可以,但找到这种式子就考验数学能力了…