Tcache Poisoning
[LitCTF 2024] heap-2.31 Tcache Poisoning
1 | ┌──(pwn_env)─(root㉿kali)-[/home/kali/Desktop] |
封装函数
1 | from pwn import * |
泄露 libc 基地址,UnSortedbin
1 | add(0, 0x410) |
- add(0, 0x410):分配一个大的 chunk(chunk 0)。因为用户请求的大小是 0x410(实际分配的 chunk 大小为 0x420),它超出了 tcache 的大小限制,也不会进入 fastbins。
- add(1, 0x10) & add(2, 0x10):分配两个小 chunk。它们的作用是充当屏障,防止 chunk 0 被释放时与顶部的 Top Chunk 合并
- delete(0):释放 chunk 0。由于它的大小,它会被放入 Unsorted Bin 中。当一个 chunk 被放入空的 Unsorted Bin 时,glibc 会将 main_arena的地址写入该 chunk 的 fd 和 bk 指针中。
- show(0):得益于 UAF 漏洞,脚本可以在 chunk 0 被释放后依然读取其内容。这会打印出 main_arena 的地址,从而泄露 libc 在内存中的位置。
计算 libc 以及一系列关键地址
1 | libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 96 - 0x10 - libc.symbols['__malloc_hook'] |
在 glibc-2.31 中,Unsorted Bin 的双向链表头部位于 main_arena + 96,即偏移 0x60 的位置
- 而 main_arena 的起始位置通常位于 __malloc_hook + 0x10。
- 脚本通过减去 96+0x10 以及 __malloc_hook 在 libc 中的相对偏移量,精准地计算出了内存中绝对的 libc_base 基址
Tcache Poisoning
1 | delete(1) |
释放 chunk1 和 chunk2会放入 Tcachebin 当前链表: Tcache -> Chunk 2 -> Chunk 1
edit(2, p64(free_hook)):利用 UAF 的可写漏洞,脚本将已经释放的 chunk 2 的 fd 指针(即指向下一个 free chunk 的指针)强行覆盖为了 __free_hook 的地址。
此时的 Tcache 链表结构:Tcache -> chunk2 -> __free_hook
—> 第一次断点
—> 第二次断点
覆盖 __free_hook
1 | add(3, 0x10) |
- add(3, 0x10):请求分配一个小 chunk。内存分配器会从 tcache 顶部弹出一个块,将刚才的 Chunk 2 重新分配给我们。此时,tcache 的头部就被更新为指向下一个条目:__free_hook。
- add(4, 0x10):再次请求分配一个 chunk。分配器从 tcache 弹出下一个条目,这次直接在 __free_hook 指针所在的内存位置分配了空间(即 Chunk 4)。
1 | pwndbg> x/16gx 0x564134c906b0 |
- edit(4, p64(system)):将 system() 的地址写入 chunk 4。因为 chunk 4 指向的正是 __free_hook 的内存,这一步相当于把 system 函数的地址填入到了 __free_hook 中。现在,程序中只要有任何地方调用 free(ptr),实际上都会被劫持并转而执行 system(ptr)。
触发 shell;
1 | add(5, 0x30) |
- add(5, 0x30):分配一个普通的 chunk。
- edit(5, b’/bin/sh\x00’):向其中写入字符串 “/bin/sh\x00”。
- delete(5):调用 free(chunk 5)。因为我们之前已经劫持了 __free_hook,这个操作并不会真正释放内存,而是执行了 system(chunk 5 的内容),也就是执行了 system(“/bin/sh”)。
总 EXP
1 | from pwn import * |
更新: 2026-04-02 10:27:29
原文: https://www.yuque.com/idcm/wnemg9/xk2602mr1x839n0h