LLVM Pass-PWN:从理论到实践,从入门到精通
字数 1147 2025-08-20 18:17:58
LLVM Pass-PWN: 从理论到实践
环境搭建
推荐环境
- Ubuntu 20.04 (推荐使用Docker搭建)
- 安装必要组件:
sudo apt install clang-8 llvm-8 clang-10 llvm-10 clang-12 llvm-12
LLVM IR基础知识
LLVM IR的三种形式
- .ll格式:人类可读的文本格式
- .bc格式:二进制格式,适合机器处理
- 内存表示:LLVM运行时使用的内存数据结构
转换命令
| 转换类型 | 命令 |
|---|---|
| .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 |
IR示例分析
C代码:
int add(int a, int b) {
return a + b;
}
对应的LLVM IR:
; ModuleID = 'example.c'
source_filename = "example.c"
define i32 @add(i32 %a, i32 %b) {
entry:
%0 = add i32 %a, %b
ret i32 %0
}
LLVM Pass基础
Pass的作用
- 对代码进行优化
- 对代码插桩(插入新代码)
处理流程
- 继承LLVM核心库提供的Pass类
- 实现关键方法
- 对传入的LLVM IR进行遍历和操作
IR结构分析
LLVM IR由以下主要组件构成:
1. Module
- 包含函数和全局变量的链表
- 主要操作函数:
begin(), end(), size(), empty(), functions() getFunctionList(), getFunction()
2. Function
- 包含基本块(BasicBlock)的双链表
- 主要操作函数:
begin(), end(), size(), empty(), front(), back() arg_begin(), arg_end(), getArg(unsigned i), args()
3. BasicBlock
- 包含指令(Instruction)的链表
- 主要操作函数:
begin(), end(), rbegin(), rend(), size(), empty(), front(), back()
4. Instruction
- 继承自Value和User
- 主要操作函数:
getParent(), getOpcode(), clone() ReplaceInstWithInst() // 指令替换
常见指令类型及创建方法
1. AllocaInst (内存分配)
AllocaInst(Type *Ty, unsigned AddrSpace, Value *ArraySize,
Align Align, const Twine &Name, Instruction *InsertBefore);
2. StoreInst (存储)
StoreInst(Value *Val, Value *Ptr, bool isVolatile,
Align Align, Instruction *InsertBefore);
3. LoadInst (加载)
LoadInst(Type *Ty, Value *Ptr, const Twine &NameStr,
bool isVolatile, Align Align, BasicBlock *InsertAtEnd);
4. BinaryOperator (二元运算)
BinaryOperator::Create(BinaryOps Op, Value *S1, Value *S2,
const Twine &Name, BasicBlock *InsertAtEnd);
5. ICmpInst (整数比较)
ICmpInst(Instruction *InsertBefore, Predicate pred,
Value *LHS, Value *RHS, const Twine &NameStr);
6. BranchInst (分支)
BranchInst::Create(BasicBlock *IfTrue, BasicBlock *IfFalse,
Value *Cond, BasicBlock *InsertAtEnd);
7. ReturnInst (返回)
ReturnInst::Create(LLVMContext &C, Value *retVal, BasicBlock *InsertAtEnd);
8. CallInst (函数调用)
CallInst::Create(FunctionType *Ty, Value *Func,
ArrayRef<Value *> Args, const Twine &NameStr,
Instruction *InsertBefore);
LLVM Pass-PWN实战
基本概念
- LLVM PASS-PWN不是攻击.so文件,而是攻击加载.so文件的程序——opt
- CTF题目通常会提供特定版本的opt文件
调试技巧
- 编译测试文件:
clang-8 -emit-llvm -S exp.c -o exp.bc - 调试opt:
opt-8 -load ./VMPass.so -VMPass ./exp.bc - 关键断点:
llvm::Pass::preparePassManager
调试脚本示例
from pwn import *
import sys
import os
os.system("clang-8 -emit-llvm -S exp.c -o exp.bc")
p = gdb.debug(["./opt-8", '-load', './VMPass.so', '-VMPass', './exp.bc'],
"b llvm::Pass::preparePassManager\nc")
p.interactive()
漏洞利用关键点
- 虚表定位:搜索vtable定位到虚表,最下面的函数通常是重写的虚函数runOnFunction
- 内存布局:使用vmmap查看加载的模块和偏移
- 断点设置:根据偏移将断点下在runOnFunction上