npuctf_2020_bad_guy
前言:House Of Roman + _IO_2_1_stdout 泄露:解题方法只是在第一步利用了House of Roman 拿到stdout之后就是泄漏libc和fastbin_attack,最后就可以 getshell了。可以把 Roman 当做一种爆破的思路,但是不推荐整道题都通过爆破去攻击,毕竟爆破次数是 16 的指数级增长。
本题目由于找不到合适的 libc,虽然远程打通了,不过本地还是由于 libc 版本问题打不通。
1 | int __fastcall __noreturn main(int argc, const char **argv, const char **envp) |
add
1 | ssize_t __fastcall add(__int64 __) |
简单的 add 函数,负责 chunk 的创建以及 read 读取 content
edit
1 | int __fastcall edit(__int64 __) |
通过 index 查找修改函数内容
delete
1 | int __fastcall delete(__int64 __) |
删除并且将指针置零,不存在 UAF
EXP 思路:
封装函数
1 | ru = lambda s : p.recvuntil(s) |
Overlapping
1 | alloc(0, 0x10, b'a') # 分配 chunk 0,实际大小 0x20 |
通过编辑 chunk0 导致堆溢出修改 chunk1 的 prev_size,然后 freechunk1 让它进入 UnSortedbin
而此时,UnSortedbin(chunk1)与 Fastbin(chunk2)就造成了堆重叠
overwrite
1 | alloc(1, 0x10, b'a') # 从 Unsorted Bin (0x90) 中切出 0x20 作为 chunk 1。 |
看一下断点之后的堆情况
这次可以再次利用堆溢出继续修改 chunk2 的头部和指针:
1 | # 写入 \xdd\x85 覆盖 fd 的低两字节,使其指向 _IO_2_1_stdout_ 附近的伪造块 (需要 1/16 爆破) |
在 glibc 2.23 中,我们要通过篡改 _IO_FILE 结构体来泄露地址。
- 目标地址:IO_2_1_stdout。
- 绕过检查:Fastbin 分配时会检查 size 字段。我们不能直接申请到 stdout 的开头,而是在它的上方寻找一个包含 0x7f 字节的位置,把它伪装成一个 0x70 大小的 Fastbin 块。
- 经典位置:在 IO_2_1_stdout 结构体上方偏移约 0x43 字节的地方,内存中通常存在类似 0x000000000000007f 的数据(这是由地址位错位产生的)。这个伪造块的地址通常以 0x5ed 或 0x5dd 结尾。
我们首先要找到 IO_2_1_stdout 的地址去伪造
由于 0x70 大小的 Fastbin 在分配时会检查 chunk->size 是否在 0x70 到 0x7f 之间,而 64 位系统的 libc 地址高位通常是 0x7f,我们可以利用字节错位来找到这个伪造的头部:
1 | # 使用 find 命令搜索 0x7f 字节 |
1 | pwndbg> p &(_IO_2_1_stdout_) |
改 Fastbin 头为 stdout
1 | alloc(2, 0x60, b'a') # 把原本的 chunk 2 从 Fastbin 中拿出来。 |
- 构造 stdout 的 Payload
- 填充 0x33 字节对齐到 _flags 字段
- 0xfbad1800 覆盖 _flags,绕过安全检查
- \x58 覆盖 _IO_write_base 的最低字节,强制扩大输出范围
再次 Overlap
1 | # 重新分配一批 chunk,准备第二次利用 |
我们再次获得了两个可以互相读写的重叠块chunk 2 和 chunk 3
劫持 __malloc_hook 获取 shell
1 | delete(3) # 将 chunk 3 释放进 0x70 大小的 Fastbin |
总 EXP:
1 | from pwn import * |
更新: 2026-04-04 17:52:53
原文: https://www.yuque.com/idcm/wnemg9/nnbx7fb2ffceirhb