放寒假了,于是我把ptmalloc2机制又重新学习了一遍,开始做点简单的堆利用题了!本题一半抄一半自己摸,也算是基本搞懂了,这里放一个笔记。
基础信息:
ubuntu16(glibc2.23),菜单题,64 位,保护全开。
提供 alloc、 free、dump、fill 功能,同时允许分配 16 个区块。
漏洞:
fill 功能可以向区块写入任意长度信息,也就是堆溢出。
由于保护全开,于是 pwn 的目标便是:
- 泄露 libc 地址
- 修改__malloc_hook 为 libc 中的 one gadget
泄露 libc 地址
ubuntu16 下没有 tcache 机制,因此只有 fast bins 和 3 个普通的 bins。其中,fast bins 以单链表形式维护,非循环链表,因此无法泄露 main_arena 的 malloc_struct 地址。而普通 bins 都是循环链表,large bins 比较复杂,但 unsorted bins 和 small bins 中的 chunk 都会有指向 arena 的指针。
我们需要泄露这个指针,就需要想办法构造 UAF 或者 overlap。如果构造 UAF 的话,就需要使两个指针同时指向一个区块,然后 free 其中一个,这可以通过 poisoning the fastbin 做到(修改 fastbin 链表为某个特定区块,然后就可以把这个区块分配出来)。
1 | # --- leak libc base --- |
为了给 fastbin 下毒,我们需要链表中有两个区块,然后利用堆溢出修改其 fd 指针到想要的区块。因此首先就分配 4 个区块,编号 0 的区块用来溢出区块 1 的信息,编号 3 的区块用来防止合并,编号 1 和 2 待会会被释放,且顺序为先 2 后 1,理由是这样 1 区块就会因为前插法位于链表的头部,这样就方便用编号 0 的区块来溢出它,但实际上由于溢出大小无限制,想怎么溢出都可以。
此外,我们还需要一个会被扔到 unsorted bin 的区块,fastbin 在 64 位下允许最大的大小是 0xb0(包括 chunk 头),所以我们这里就申请一块 0xb0 大小的空间(对应 chunk 大小 0xc0)。我们待会要释放它,为了防止它和 top chunk 合并触发 consolidation,我们再分配一个区块用来占位。
1 | free(2) |
然后我们将区块 1 和 2 释放,他们会被放到 fastbin 的一个链表中。
我们从区块 0 开始溢出区块 1 的 fd 指针,将其最低 bit 修改为 0x80。这里利用了虚拟页大小为 0x1000 的特性,区块的后 12bits 不变,因此 0x80 处就是区块 4。
我们还需要将区块 4 的大小改为 0x21,这是为了通过 fastbin 分配区块的检查。
1 | alloc(0x10) # 1 |
然后我们此时分配两个区块,程序会顺位编号(类似 fd 的分配),所以分配得到的区块会被编号为 1 和 2。此时,区块 2 就是区块 4!我们已经做到了让两个指针同时指向一个区块。
1 | # change 4's size to 0xc0 (free check) |
接下来我们 free 其中一个指针。为了通过 free 的检查,我们再用溢出将其大小改回其真实大小 0xc1。(具体来说,如果不改的话,该 chunk 属于 fastbin,free 会检查该 chunk 物理高位的下一个区块的大小是否正常,然后会惊喜地读到一个 0,并报错。)
在将其释放之后,它不属于 fastbin 且没有可以合并的区块,于是被放进了 unsorted bin。这时我们就可以 dump 区块 2 来查看 unsorted bin 的地址了(实际上是 bins 的地址,因为 unsorted bin 作为一个 malloc_chunk,其位置是 &bin[0]
,其 fd 字段位置才是 &bin[2]
)。
覆写 __malloc_hook
为了覆写__malloc_hook(地址已知),我们需要寻找其附近的现存 fake chunk。我觉得这应该有工具可以做到,我只找到了 pwndbg 提供的 find_fake_fast
指令,但我这次没有成功,它给我报错(呜呜呜)。
然后询问了学长,得知一般是 &__malloc_hook
减去 0x23 或 0x33 之类的位置,因为 0x7f 是一个合法的 size(我猜是因为有了 IS_MMAPED bit,别的 bit 都会作废)。使用 pwndbg 的 malloc_chunk
指令查看这两处,发现 size 字段确实是 0x7f。
那么接下来就很简单了,我们分配两个大小为 0x70 的 chunk,把它们释放进 fastbins,然后堆溢出把 fd 改成 fake chunk 的地址(和上面流程一样)。
最后用 fill 把 __malloc_hook 改了,再随便 alloc 一下,成功用 one gadget 拿到 shell!
完整 exp
1 | #!/usr/bin/python3 |