前言
之前一直有2.31的题目出现在赛场上,趁着这个机会,整理一波2.31下的tcache机制和利用技巧,以学习的目的为主。
一、check机制和利用技巧说明
1、fastbin的double free
只要填满了7个tcache,下一步就是放入到fastbin中,接着如果存在uaf的话,可以直接121实现和2.23一样的double free效果
2、tcache的double free
我们先来看下2.31下的源码:关于tcache结构体和key结构体
1 | /* We overlay this structure on the user-data portion of a chunk when |
我们实际看一下效果:
这里tcache的结构体发生了变化,多了个key指针,用来防御double free的,再看free时是怎么检测的,look一下free的源码
1 | static void |
这个绕过相当简单,我们只需要能修改bk指针,即key的值即可,不进去if (__glibc_unlikely (e->key == tcache))这个条件判断,就是正常free,所以也可以实现double free
构造方法:
存在uaf漏洞,可以改写key的值,就可以直接实现tcache的double free
3、tcache在malloc时,堆块重复性检查
2.29不检查tcache中的堆块重复使用,意味着能使得2个指针指向同一个堆块
2.31检查tcache中的堆块重复使用,意味着不能使得2个指针指向同一个堆块,意味着overlap不可用了。
4、当fastbin中摘了一个chunk后,其他在fastbin中的堆块会被unlink到tcache中对应的大小去,符合tcache优先的原则,smallbin也一样。
1 | static void * |
5、tcache不检查size头
1 |
|
6、small bin的unlink依旧可以用(主要利用点)
1 | static void * |
利用的机制是当malloc走到smallbin的时候,如果smallbin的size和malloc的nb相同,会从smallbin链bk上解下来一个chunk,同时会将这条链上剩余的chunk放到对应size的tcache_entry中,终止条件是该smallbin为空或者对应的tcache_entry被装满。
通俗地讲解利用方式就2个:
1、任意地址写main_arena的值
构造方法:
先往tcache中填充6个堆块,然后放一个相同大小的堆块到small bin中,接着放第二个相同大小的堆到small bin中,修改第二个堆块的bk指针为fake_chunk地址,申请相同大小的chunk,会得到第一个堆块,然后第二个堆块会被放到tcache中,接着fake_chunk的fd指针会写入main_arena的地址。
2、任意地址写(申请出一个fake_chunk往里面写内容)
构造方法:
先往tcache中填充5个堆块,然后放一个相同大小的堆块到small bin中,接着放第二个相同大小的堆到small bin中,修改第二个堆块的bk指针为fake_chunk地址,然后满足fake_chunk的bk指针是个可写地址,申请相同大小的chunk,会得到第一个堆块,然后第二个堆块会和fake_chunk被放到tcache中,接着fake_chunk的bk指针所在的地址的fd指针会写入main_arena的地址,再次申请即可得到fake_chunk,然后往fake_chunk中写入内容。
但是前提是fd指针必须是有相应的堆地址才行,不然行不通的!所以需要泄露出堆地址
题目:高校战疫情twochunk(unlink++)
1 | # -*- coding: UTF-8 -*- |
7、unsorted bin的attack已经成为历史:
unlink实现overlap the chunk和任意地址写超大值都不行了
1 | for (;; ) |
8、largebin attack的利用
在2.23里面这个漏洞点就是任意地址写一个堆地址,这个和small bin的任意地址写一个main_arena地址效果是一样的,largebin也可以实现申请出一个fake_chunk,只要将指针构造好,在2.31下这个攻击依旧可用。
假设largebin中已经有了一个堆块,这里有2种方式可以让最新的chunk(比原本的chunk小),进入到相应的largebin位置:
1、先将chunk放到unsorted bin中,然后申请一个比chunk大的堆,使得不满足条件,从而触发堆整理机制,把unsorted bin中的chunk放到对应大小的largebin中去
2、先将chunk放到unsorted bin中,然后申请一个fastbin大小的堆块,这时会先遍历fastbin中所有满足的条件的chunk,发现都不行,于是触发堆整理机制,把chunk放到对应大小的largebin中,然后再次遍历搜索,找到largebin中满足条件的第一个堆块(从小到大找),进行切割操作,得到满足大小的chunk,剩下的lastremainder,就会被放到unsortedbin中,这样也是间接进入到了largebin中的一个方法,仅限largebin特有。
在2.29种,比原本的chunk大也可以写入到largebin中,但是2.31下加了新的check,所以只能放比原来的小的了:
1 | victim_index = largebin_index (size); |
通俗地讲就是任意地址写堆地址而已。
构造方法:
先放一个堆块到largebin,在放入第二个largebin时,先改写已经存在的largebin的bk_nextsize为fake_chunk,通过搞一个比它小的largebin放入,插到链表尾巴那里(还在同一链),实现往fake_chunk的fd_nextsize写入堆地址。
2.31下的house of storm有待研究。。。。。(house of banana)
9、house of botcake
这个其实就是tcache和unsorted bin的结合使用造成overlap的效果
构造条件:
存在uaf漏洞,先在tcache中填充7个unsorted bin的堆块,然后继续填充2个相同大小的堆块,就会放到unsorted bin中,因为相邻,会被unlink合并起来形成大堆快,然后我们申请一个堆块,使得tcache中只剩下6个堆块,接着再次free最后一次放入的堆块(尾巴),使得它放入到tcache中,那么我们通过申请到大堆快,就实现了overlap的效果了,可以往free状态的tcache中的堆块fd指针写入free_hook,然后申请出来,改成system,最后getshell。适用于有uaf没有edit时实现tcache的劫持操作。
10、offbynull
2.31下的offbynull比较复杂,因为加了挺多check机制,但是还是可以了利用的,我们来看一下具体是什么样的,上源码:
1 | /* consolidate backward *///向后合并堆块 |
1 | /* Take a chunk off a bin list. */ //unlink操作 |
来看一张构造过程的截图:
0x555555560020就是我们的fake_chunk,这里是在0x31的堆块里面构造的,这里fakechunk的size是0x521,fd和bk指针通过largebin的残留得到这个不难,然后我们控制fd的尾字节为\x40,就可以在0x555555560040的堆块处利用small bin的bk指针残留得到堆地址,接着我们改堆地址的地位为我们的fakechunk的地址0x555555560020,fakechunk的bk因为我们动不了,但是根据largebin的残留指针,这是指向它自己的,所以presize需要填写fakechunk的地址,这里我们利用fastbin的attack,得到fd指针残留,修改尾字节为\x20即可,这样一来,unlink就被我们绕过了,下面就是构造出presize,利用offbynull实现我们的overlap the chunk,有了overlap,接下来的操作就比较简单了。
以上便是源码阅读计划的全部内容。
offbynull做题:
一道简单的offbynull题,直接利用泄露出来的堆地址和真实地址,构造unlink,实现overlap,往tacahe放入足够数量的堆块,再free目标堆块,然后改fd为free_hook,改free_hook为system即可getshell
1 | #coding=utf8 |
再来道难题:
这题是balsCTF原题
保护机制全开
发现有沙箱规则:
只能劫持freehook来写入setcontext,从而orw打印flag了
1 | #coding=utf8 |
尝试自己出题
这里只给add和free2种操作,没有其他的操作,加了沙箱,只能orw出flag
源码:
1 |
|
看下malloc函数:
看下free函数:
只有malloc和free可以操作,先利用largebin的残留,结合fastbin和smallbin的操作,堆风水构造出unlink的条件,然后先利用offbynull构造出overlap the chunk,利用small bin的指针残留,得到堆块大小为0x31,含有0x7f地址的fd指针(即stdout),利用overlap,使得tache中得到stdout,放一个堆块到largbin中,从而实现泄露出真实地址和堆地址(堆在main_arena那里分布),最后就是常规套路,改free_hook为某个gadget,从而调用setcontext,最后orw的获取flag
1 | # -*- coding: utf-8 -*- |
参考链接:
https://github.com/StarCross-Tech/heap_exploit_2.31
https://elixir.bootlin.com/glibc/glibc-2.29/source/malloc/malloc.c#L3880