de1ctf_2019_weapon
依旧是一道 House Of Roman + _IO_2_1_stdout 的例题
函数分析
main
1 | void __fastcall __noreturn main(int a1, char **a2, char **a3) |
create
1 | __int64 create() |
delete
1 | unsigned __int64 delete() |
UAF
rename
1 | unsigned __int64 rename() |
修改内容
EXP 思路:
构造堆布局:
1 | add(0x58,0,b'a'*0x48+p64(0x61)) # 0 |
- 首先申请了 4 个 Chunk。注意 0x58 和 0x60 加上 chunk header 后,实际分配的大小(Size)分别为 0x60 和 0x70。
- 在申请 chunk 0 时,提前在偏移 0x48 的位置伪造了一个 size 字段 p64(0x61)。这是为了后续 Fastbin 攻击伪造的 Fake Chunk 做准备。
1 | pwndbg> x/16gx 0x5558e1333000 |
利用 UAF 制造 Fastbin Dup (Partial Overwrite)
1 | free(1) |
- 依次释放 1、3、0。此时 Fastbin 0x60 的链表为:chunk 0 -> chunk 3。
- 程序在 free 后没有将指针置空存在 UAF 漏洞。利用 edit(0),覆盖 chunk 0 的 fd 指针的最低一个字节,将其修改为 0x50。
- 这使得 chunk 0 的 fd 不再指向 chunk 3,而是指向了 chunk 0 内部地址往前偏移的位置。
1 | pwndbg> heap |
Overlapping 修改 chunk1_size
1 | add(0x58,4,b'a') |
- 再次申请两次 0x58 的 chunk。chunk 4 拿回了原来的 chunk 0。
- chunk 5 拿到了刚才伪造的 Fake Chunk。这个 Fake Chunk 的Overlap到了 chunk 1 的头部。
- 通过 add(…, 5) 写入数据,将 chunk 1 原本的 size(0x71)篡改为了 0x91。
1 | pwndbg> x/20gx 0x5624f4249000 |
UnSortedbin + Roman 爆破 找 IO_2_1_stdout
1 | free(1) |
1 | find /g ...-0x100, +0x100, 0x7f |
- 由于 chunk1 的 size 位被篡改为 0x90,free 时会被放入 UnSortedbin 当中
- 此时 chunk1 的 fd 和 bk 同时指向 main_arena + 88
- 找一下符合绕过 fastbin 的以 0x7f 开头的地址去泄露 IO_2_1_stdout,0xa 是爆破 ASLR 的 4 个 bit 位
修复 Size 并泄露 Libc 基址
1 | edit(5,b'a'*8+p64(0x71)) |
- 将 chunk 1 的 size 改回 0x71,修复 Fastbin 链表结构以防崩溃。
- 连续分配两次 0x60,第二次chunk 7就会分配到我们刚才指向的 IO_2_1_stdout 伪造区块。
- 写入特定的 Payload(0xfbad1887 和一串 0),篡改 IO_2_1_stdout 的标志位(flags)和写指针。这会欺骗 glibc,使其在下一次调用输出函数(如 puts/printf)时,直接把 libc 内存里的数据当成字符串打印出来。
- 接收打印出的数据,减去固定偏移 0x3c5600(glibc 2.23 中 stdout 相对 libc 基址的偏移),成功获取 libc_addr。
Hijack __malloc_hook 与 One Gadget 调栈
1 | free(1) |
- 有了 libc 地址,接下来就是拿 shell。再次释放 chunk 1 进入 Fastbin 0x70。
- 利用 UAF 改写 fd,指向 __malloc_hook - 0x23 处,此处内存天然存在一个 0x7f 的值,可作为伪造的 size 绕过安全检查。
- 分配拿到这个 Fake Chunk(chunk 8),进行数据覆写:
- 写入目标:__realloc_hook 写入 One Gadget
- 写入目标:__malloc_hook 写入 realloc+4 的地址。
- 为什么要写 realloc+4? 这个技巧叫做调栈。One Gadget 通常对栈环境(如 [rsp+0x30] == NULL)有苛刻要求,直接跳可能无法执行。先跳到 realloc+4,会执行一段 push 寄存器的指令,强行改变栈指针偏移,进而满足 One Gadget 的触发条件,然后再由 realloc 跳转到 __realloc_hook 执行 One Gadget。
循环爆破
1 | p.sendlineafter(b'choice >>',b'1') |
总 EXP:
1 | from pwn import * |
更新: 2026-04-15 19:50:25
原文: https://www.yuque.com/idcm/wnemg9/azw0egv8bflfugnx