houseoforange_hitcon_2016
main
1 | void __fastcall __noreturn main(const char *p_Invalid_choice, char **a2, char **a3) |
build
1 | int build() |
可以发现 v4 的定义是 DWORD,也就是说它是四个字节的数据,每一次填充都只能填充四个字节,它把 v3 拆分成两个四字节分别用来填充价格和颜色序号。
这里不需要考虑别的内容,颜色序号直接写 0xDDAA
1 | 全局变量 qword_203068 |
See
1 | int See() |
1 | printf("\x1B[01;38;5;214m%s\x1B[0m\n", *((const char **)&unk_203080 + v0 % 8)); |
整个 See 函数的作用就是选取 name 地址存放的内容进行打印,也就是打印出前面定义过的 name
Upgrade
1 | int Upgrade() |
整体就是一个更新名字,价格,颜色的的函数
exp 思路:
封装函数:
1 | from pwn import * |
堆溢出篡改 Top_chunk
1 | add(0x10, 'aaaa', 0xff, 1) # 1. 申请一个极小的 chunk (大小 0x10) |
1 | pwndbg> x/20gx 0x55e763de4000 |
这里强行缩小 top_chunk 的大小,但是需要注意页对齐 (Top_Chunk_Base + Size) & 0xfff == 0
触发 sysmalloc 进行堆扩展,造成 free_chunk
1 | # 4. 申请一个大于 0xfa1 的巨大 Chunk |
- 当我们请求 0x1000 时,Glibc 发现当前的 Top Chunk 只有 0xfa1,不够分配了。
- 于是 Glibc 调用 sysmalloc 重新映射内存。而旧的、被我们改小为 0xfa1 的 Top Chunk 会被自动释放,进入了 Unsorted Bin。至此,我们成功在无 free 的环境下获得了一个 Free Chunk.
1 | pwndbg> bins |
地址泄露 (Information Leak)
1 | # 5. 从刚才进入 Unsorted Bin 的 0xfa1 块中切割 0x400 出来 |
当我们申请 0x400 后,Unsorted Bin 中的块会被切割,剩下的部分称为 Remainder Chunk。
Remainder Chunk 的 fd 和 bk 指针会指向 Glibc 内部的 main_arena。由于我们之前申请的块和它是挨着的,利用 show() 可以越界读出这些指针,从而精准推算出 _IO_list_all 等关键结构的地址。
因此可以计算出 malloc_hook 的地址:
1 | malloc_hook = main_arena - 1640 - 0x10 |
泄露堆地址
1 | edit(0x10, b'b' * 0x10, 0xddaa, 0xddaa) |
- 填充缓冲区: 之前的操作中,由于 add(0x400…) 从 Unsorted Bin 中切分了内存,当前的堆块(Chunk)中残留了指向堆本身的指针 。
- **利用字符串截断:**edit 函数将堆块的前 0x10 字节填满字符 ‘b’。在 C 语言中,打印字符串(如 printf(“%s”))会一直打印直到遇到空字符(\x00)。
- 带出指针: 因为我们把原本用于截断的空字节用 ‘b’ 覆盖了,所以接下来的 show() 函数会顺着 ‘b’ 一直把后面紧跟的堆指针也打印出来 。
- **接收与转换:**r.recvuntil(‘b’ * 0x10) 跳过我们填充的垃圾字符,直接抓取后面跟着的 6 字节堆地址,并将其转换为整数 heap_addr
寻找 One_Gadgets 和 system
1 | one = [0x45226, 0x4527a, 0xf03a4, 0xf1247] |
FSOP 与 Unsorted Bin Attack
1 | # 7. 构造包含伪造 _IO_FILE 结构体和 Unsorted Bin 攻击的 Payload |
- **Unsorted Bin Attack:**bk 被修改为 io_list_all - 0x10。当这个块再次被 malloc 时,Glibc 会把 main_arena+88 写入到 bk + 0x10,也就是强行把 _IO_list_all 指向了堆上的 Unsorted Bin 头部!
- _IO_FILE 结构体重叠: 这段代码将一个堆块完美地伪造成了 _IO_FILE 结构。注意 _IO_write_ptr (0x1) 和 _IO_write_base (0) 的设置,这是触发后续 _IO_flush_all_lockp 刷新流的硬性校验条件。
- **vtable 劫持:**heap_addr+0x10 指向了我们放置 system_addr 的地方。
最后一次申请,触发崩溃与利用
1 | # 8. 最后一次申请,触发崩溃与利用 |
- 发送
1相当于触发了一次普通的malloc。 - Glibc 在遍历 Unsorted Bin 试图分配时,会触发我们布置的 Unsorted Bin Attack,将
_IO_list_all劫持。 - 随后,由于我们把那个块的大小伪造为了非法的
0x61,Glibc 在后续处理中会触发malloc_printerr报错崩溃。 - 崩溃前,程序会调用
_IO_flush_all_lockp刷新文件流,顺着被劫持的_IO_list_all找到了我们在堆上伪造的假文件块。 - 最终,程序本以为在调用
_IO_FILE的__overflow函数刷新缓冲区,实际上却调用了我们布置在 vtable 中的system,并将伪造的_flags("/bin/sh\x00") 作为参数传入。
总 EXP:
1 | from pwn import * |
更新: 2026-03-31 22:05:11
原文: https://www.yuque.com/idcm/wnemg9/pvhgcui6c4xeh08i