Unsorted Bin attack 学习记录

警告
本文最后更新于 2021-01-26,文中内容可能已过时。

之前就接触过unsorted bin相关的很多攻击方法,但是一直没有深入的理解这部分内容。 趁着学习house of storm的机会,阅读了一下相关的源码,加深了对其的理解,也把最近遇到的结合unsorted bin的几种攻击方法包括了进去。 感谢cnitlrt师傅一直以来对我的帮助!cnitlrt师傅实在是太强啦!!ORZ

Unsorted Bin Attack

利用范围

unsorted bin attack是一种容易的攻击方式,他在glibc2.29之前都可以简单的利用,几乎没有检测,在glibc2.29后加入了检测(commit:https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c

检测代码如下:

1
2
if (__glibc_unlikely (bck->fd != victim))
    malloc_printerr ("malloc(): corrupted unsorted chunks 3");

这里检测的重点还是bck->fd != victim,也就是判断了bck(bck = victim->bk)的下一个是否是victim,这如果是在双向链表中一定是符合的,但是如果chunk被我们unsorted bin attack后,bk就被修改成target位置了,那么也就就达不到这个要求了。

利用原理

malloc的时候会根据大小选择的去fastbin、small bin中查看是否有合适的chunk,如果都没有找到合适的chunk,那么就会遍历unsorted bin进行查找,遍历代码如下。

while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))

其中av是main_arena的指针,通过unsorted_chunks这个宏来定位到储存unsorted bin的那块区域。

unsorted_chunks (av)的两个重要元素:

fd:bin[0](指向下一块数据,这里指向unsorted bin 链表的头部)

bk:bin[1](指向前一块数据,这里指向unsorted bin 链表的尾部)

unsorted bin从尾部依次往前遍历,由于遍历到一个chunk就会进行脱链,所以如果要访问到下一个chunk,只需要访问unsorted_chunks (av)->bk就可以了,而且当bk内容为**unsorted_chunks (av)**则可以说明全部数据已经被遍历,unsortbin无内容了。

而在遍历到一块unsorted bin的时候,无论大小如何,都会先进行脱链,也就是从unsorted bin上把这块chunk取出来,此时会根据大小的不同分为三种情况

1.unsorted bin size == malloc size 也就是 if (size == nb) 成立的情况下,那么就会直接分配后返回

2.unsorted bin size > malloc size 这种时候会先放入到small bin或者large bin中,然后再将多余的部分放入unsorted bin中。(如果小于MINSIZE则直接分配给用户)

3.unsorted bin size < malloc size 这种时候也是会先放入到small bin或者large bin中,但是在后续的判断中,发现还是没有合适的chunk,那么会去top chunk中分配。

而在上述过程前,都会进行脱链从而促成了unsortbin attack的利用,利用代码可以间接成以下两行

1
2
3
4
bck = victim->bk;
...
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

这里在做的就是把victim中链表中移除。 在这个过程中,就利用bck->fd = unsorted_chunks (av);

往上面写了一个unsorted_chunks (av)的地址,这是一个在libc上的地址,由0x7f开头,是一个很大的数字。

所以我们只要能够控制bck(victim->bk)的内容,那么就可以在脱链的过程中,往victim->bk->fd写入一个很大的数字。

利用方向

  • 我们通过修改循环的次数来使得程序可以执行多次循环。
  • 在申请大小受限的情况下或者程序使用mallopt来关闭fastbin的情况下,我们可以利用global_max_fast 来做一些操作,一般我会利用到以下两种操作。

  a.利用fastbin bin leak | attack IO_FILE

  我们先free一个unsortbin,在利用UAF等漏洞,partial overwrite bk的值,使得在没有libc_base的情况下,把bk覆盖成global_max_fast 的地址(1/16),接着malloc一次,来修改 heap 中的 global_max_fast,这时候很大的size都会被视为fastbin,如果size过大(超过了10),那么在插入到fastbinY的时候就会发生数组越界,从而把后面的bins当做fastbinY来用。所以,如果在free的时候,size过大从而导致数组越界,而且对应bins中存在内容,那么那个bins的内容就会被当做之前被free的chunk,从而赋值到当前被free的fake fastbin chunk的fd中,而我们知道bins中有很多数据是libc的地址。所以如果我们构造得当。那么就有机会让chunk的fd中存在一个libc的地址。

  接下来就需要分为两种情况:

  1.有show函数:

   有show函数可以直接leak出libc的地址,来进行下一步的利用

  2.无show函数:

   利用partial overwrite来部分覆盖fd,让fd的内容变成stdout的某个偏移。

   这里会遇到的一个问题是,一般来说打stdout用的都是stdout - 0x43处的0x7f这个size,但是0x7f这个size不会导致fastbinsY越界,那么就无法在fd上有一个libc地址了。

   这里有在不同情况下的两种处理方法:

   在未限制申请size的情况下(使用mallopt来关闭fastbin的情况下):

   我们可以利用多次覆盖size位来构造,也就是先修改size为0x7f,然后再free一次,然后再修改为可溢出的size,然后free一次,这时候fd上有了libc地址,最后再改回size为0x7f,进行下一步利用(老套路:打__malloc_hook - 0x23等)。

   在限制申请size的情况下(以可以申请 > 0xE8为例):

   这个方法就是在目标地址寻找一些其他可错位利用的size为,我们这里我们需要就找到了一个偏移,是stdout - 0x51,在这里有一个0xF?的size可以利用。由于未在hook函数附近找到可利用的位置,所以这个方法需要先利用stdout来leak libc再利用。

  以上两种处理方法的最终概率都是1/16,虽然在这之前也有一次爆破,但这两个都是基于libc偏移的爆破,所以概率直接不需要相乘。

  例题:ByteCTF 2019 note_five

  b.利用fastbin attack

   这里的情况与a的区别在于已经有了libc基址,只是需要一个任意写入,那么直接利用这个来打__malloc_hook等位置。

  • 错位写0x7f   由于在内存中0x7f是在最高地址的,所以我们可以错位写一个0x7f,与fastbin attack打配合。(因为fastbin attack会检测申请位置的size是否符合)
  • house of orange

  往_IO_list_all上写一个地址unsorted_chunks (av),使得对应_chain就是smallbin[4] size = 0x60

  • house of storm

  用于伪造一个链入到unsorted bin链表的chunk的fd指针,bk指针通过large bin来伪造。使得fd指针正好对应到unsorted_chunks (av)上,bk指针正好对应到unsortbin chunk上。从而达到了unsortbin的检测。

总结

不难看出,虽然unsortbin attack的操作有限,但是由于其不严格的检测(glibc < 2.29)使得这种攻击与其他攻击方法的搭配从而绕过检测的方法变的很流行。不过在这里提醒一下各位,如果想要深度的掌握和了解这种方法的攻击技巧和延伸,阅读linux源码也是非常重要的。(我就是因为没有看源码从而吃了大亏。)

参考链接:

[1] Unsorted Bin Attack - CTF Wiki https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/unsorted_bin_attack-zh/

0%