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的三种形式

  1. .ll格式:人类可读的文本格式
  2. .bc格式:二进制格式,适合机器处理
  3. 内存表示: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的作用

  • 对代码进行优化
  • 对代码插桩(插入新代码)

处理流程

  1. 继承LLVM核心库提供的Pass类
  2. 实现关键方法
  3. 对传入的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文件

调试技巧

  1. 编译测试文件:
    clang-8 -emit-llvm -S exp.c -o exp.bc
    
  2. 调试opt:
    opt-8 -load ./VMPass.so -VMPass ./exp.bc
    
  3. 关键断点:
    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()

漏洞利用关键点

  1. 虚表定位:搜索vtable定位到虚表,最下面的函数通常是重写的虚函数runOnFunction
  2. 内存布局:使用vmmap查看加载的模块和偏移
  3. 断点设置:根据偏移将断点下在runOnFunction上

参考资源

  1. LLVM PASS类pwn题总结
  2. LLVM设计提示
  3. LLVM Pass PWN详解
  4. LLVM安全研究
LLVM Pass-PWN: 从理论到实践 环境搭建 推荐环境 Ubuntu 20.04 (推荐使用Docker搭建) 安装必要组件: 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代码: 对应的LLVM IR: LLVM Pass基础 Pass的作用 对代码进行优化 对代码插桩(插入新代码) 处理流程 继承LLVM核心库提供的Pass类 实现关键方法 对传入的LLVM IR进行遍历和操作 IR结构分析 LLVM IR由以下主要组件构成: 1. Module 包含函数和全局变量的链表 主要操作函数: 2. Function 包含基本块(BasicBlock)的双链表 主要操作函数: 3. BasicBlock 包含指令(Instruction)的链表 主要操作函数: 4. Instruction 继承自Value和User 主要操作函数: 常见指令类型及创建方法 1. AllocaInst (内存分配) 2. StoreInst (存储) 3. LoadInst (加载) 4. BinaryOperator (二元运算) 5. ICmpInst (整数比较) 6. BranchInst (分支) 7. ReturnInst (返回) 8. CallInst (函数调用) LLVM Pass-PWN实战 基本概念 LLVM PASS-PWN不是攻击.so文件,而是攻击加载.so文件的程序——opt CTF题目通常会提供特定版本的opt文件 调试技巧 编译测试文件: 调试opt: 关键断点: 调试脚本示例 漏洞利用关键点 虚表定位 :搜索vtable定位到虚表,最下面的函数通常是重写的虚函数runOnFunction 内存布局 :使用vmmap查看加载的模块和偏移 断点设置 :根据偏移将断点下在runOnFunction上 参考资源 LLVM PASS类pwn题总结 LLVM设计提示 LLVM Pass PWN详解 LLVM安全研究