一道比较综合的堆题,漏洞只有 off by one ,保护开满, libc 版本为2.23。
特点:
- 程序分析比较简单,难点在于漏洞利用。
- 泄漏地址的方法比较巧妙(没想到)
- 我还发现了多次使用 unsortedbin attack 的方法。
- 了解了如何伪造 chunk 并绕过
_int_free
中的一系列检查
基本情况
保护
只能打各种 hook 了。
采用排除的方式设置 seccomp,mprotect
没有被禁用,后面可能有用。
程序分析
menu
一应俱全。
init
init
中的设置让这道堆题异于常规。
- 这里的
mallopt
的效果为将global_max_fast
的值设为0x10,这意味着任何 chunk 都不会被free
进 fastbin。 - 记录堆块的数组存放在
mmap
申请到的区域内,且基地址随机,那我们不能通过 unlink 进行任意地址写。
add
add
用calloc
申请 chunk(第一次见),实际上在calloc
内部仍会调用__int_malloc
,因此攻击方式不会受到影响(但对于启用了 tcache 的 libc ,calloc
不会从 tcachebin 中取 chunk)。calloc
会将 chunk boby 部分内存全部清0,因此不能通过申请被free
过的 chunk泄漏地址- 申请 chunk 的大小不能超过 0x1000,则不能用 house of force 进行任意地址写。
edit
- 划红线处存在 off by null 漏洞,比较难发现。
- 不能 Patial write 了。
- 没有更多的堆溢出,索引边界检查正常。
delete
清理得干干净净
show
使用 pust
输出。考虑到前面的 edit
中的 off by null ,泄漏地址可能有些困难了。。。
漏洞利用
我了解到 off by null 漏洞的利用方法只有两个:chunk overlap 和 chunk shrink (说实话我压根不知道 chunk shrink 有什么用)。 chunk overlap 可以获得对 overlapped chunk 的控制权,为下面的攻击(unsortedbin attack, fastbin attack)做准备。由于本题的功能函数齐全,我们对 chunk 的掌控力很强,chunk overlap 的区域可以被我们反复利用。
我知道的堆利用的任意地址写任意值的基本方法有四种:fastbin attack(不算任意地址)、house of force、unlink +后续利用、house of lore(wiki上说算,但我觉得要提前布置 victim 是不是太困难了,能的话还需要用这个方法吗?),house of einherjar。(tcache的不算)。house of force 在程序流程中就被枪毙了,unlink 需要知道堆块数组的地址(在本题中 ptr 存在栈上,也就是说要知道程序加载基址),house of lore 要泄漏堆地址以及提前布置 victim 地址,house of einherjar 需要泄漏堆地址以及提前布置fake_chunk(绕过unlink检测)。看起来只有 fastbin attack 比较容易了。
由于 global_max_fast
在 init
中被设为 0x10 ,我们需要利用 unsortedbin attack 将其修改。下面是攻击流程:
攻击流程:
- 利用 off by one 漏洞进行chunk overlap,获得对 overlapped chunk 的控制权。
- 分割上一步中进入 unsorted bin 的 chunk(这样堆块数组中就会有两个指向 overlapped chunk 的指针),
free
掉 overlapped chunk,然后泄漏 libc 地址。 - 再次进行 chunk overlap,重新获得对 overlapped chunk 的控制权。
- 使用 unsortedbin attack 修改
global_max_fast
。 - 用奇妙的方法修复 unsortedbin 的 bk 指针(指向可控堆区域),再次进行 unsortedbin attack 从而在
__free_hook
上方布置 0x7f - 利用 fastbin 链泄漏堆地址
- 使用 fastbin attack 分配 chunk 到第五步中布置了 size==0x7f 的位置,修改
__free_hook
为奇妙的 gadget,以便后续 rop - 在堆上布置 rop 链,进行 orw。
exp及其分析
完整的exp。直接看的话可能比较难理解,接下来我会挑一些重要的步骤进行分析。
1 | from pwn import * |
chunk overlap
1 | # chunk overlap 注意不同版本的unlink中和合并时presize的检查机理不同 |
由于存在 off by one 漏洞,size 为0x101的 big chunk(这样称呼它吧) 的最低字节会被置零,这样 pre_inuse 位变为0。同时还需要修改 pre_size 为 0x40,之后申请时才会到目标地址。
如果在 libc-2.31的源代码 中搜索 “corrupted size vs. prev_size” 字符串,会有三处结果:
unlink中的检测。可以轻松绕过,因为伪造只需要 unlink 的 chunk 的 nextchunk 在可控区域内。
向后合并时的检测。这个检测是致命的,我不知道如何绕过。所幸 libc-2.23 没有这个检测。
malloc_consolidate
中的检测。与本题无关,但影响了 house of roman。
leak libc
1 | # split unsorted chunk to leak libc addr 没想出来。。。 |
本题中泄漏地址的限制很多:off by null、calloc 清0。只能用上面的方法。让指针 1(前一步中分配)、2 同时指向一个 chunk, free
2 后用 1 泄漏地址。我觉得乏善可陈。。。(不过一些泄漏地址困难的操作都是通过分割 unsorted chunk 完成的,有点神奇)。这里的 3 在后面会用来修复 unsorted bin。
unsortedbin attack
1 | # chunk overlap again |
free
fake_chunk 进 unsortedbin(注意绕过 free
中的一些检测),然后修改其 bk 指针,分配时 global_max_fast
被覆盖为 main_arena + 88
,非常大。0xf1 的 fake_chunk 被指针 3 指,后面修复 unsortedbin 会用到。
fix unsortedbin bk
1 | # 修复unsortedbin,第二次unsortedbin attack |
上次的 unsortedbin attack 后:
再看 unsortedbin 的插入:
unsortedbin 的插入只用到了 unsortedbin 的 fd 指针,看起来没什么大问题。
接下来是 unsortedbin 的移除:
由于 bck 已经被修改到了无法控制的区域(libc中),无法再进行 unsortedbin attack。如果我们能将 unsortedbin 的 bk 修改到可控区域,便可以再次进行 unsortedbin attack(不用管 fd)。
还记得我们已经把 global_max_fast
设为一个超级大的值了吗?这不仅说明着 fastbin 被启用,也意味着更大的(大于默认的最大值0x80)chunk free
时会进入 fastbin。而 fastbin 的定义如下:
fastbin 又位于 main_arena
中。如果我们 free
的 chunk 计算出的索引超过了 NFASTBINS
,我们可以溢出到 main_arena
中 fastbinsY
后的任意地址(当然还是有 global_max_fast
的限制),覆盖其为 free
的 chunk 的地址。
前面我们的指针 3 指向的 size==0xf1 的 fake_chunk 的意义就在于此。将其 free
后:
然后我们就可以再进行一次 unsortedbin attack。
fastbin attack
1 | # 泄漏heap地址,同时fastbin attack 指向free_hook前通过第二次unsortedbin attack布置的值 |
你可能想知道为什么要进行第二次 unsortedbin attack,因为在 __free_hook
的上方:
全是0,直到大约 -0x1C00 的地方才有其他的值(好像还是 main_arena
中的。。。),所以需要一次 unsortedbin attack 在 __free_hook
前放入一个 0x7f。
其他师傅用的方法跟我的好像很不一样,我应该去看一看。
overwrite __free_hook
1 | # 修改free_hook |
在我的前一篇文章中,提到了 libc-2.31 下在堆上进行 rop 的一些 gadget。在 libc-2.23 中,这些 gadget 依然存在,而且有些更好用,比如最关键的、修改 rsp 的 gadget :
用 rdi 就能给 rsp 寄存器赋值,对比 libc-2.31 的,同样在 setcontext
函数中:
还要先给 rdx 赋值(不过有用 rdi 给 rdx 赋值的 gadget),还多了一个奇怪的跳转(不知道条件是什么)。
总结
- 对 libc 的 chunk 管理方式更熟悉了,了解了一些与
free
有关的检测。 - 构造这东西挺巧妙的,有时候改来改去最后还要重新写。
- 我记得有句话是:“知识面决定攻击面,知识链决定攻击深度”,这道题就是前者的体现吧。(不过我还有一做题就蒙问题,还是要把逻辑理清楚)
参考
- 堆中global_max_fast相关利用。看了这篇文章的前半部分我才想到了修复 unsortedbin 的可能性。
- RCTF2019 pwn babyheap writeup。参考了里面泄漏 libc 地址的方法,我是真想不到。。。还有另一种思路:使用 house of storm 和
setcontext
(和 srop 类似)