内容简介:把以前学过的东西再复习总结一下,温故而知新。由于相较其他heap,largebin 中的机制相对复杂,所以largebin attack 一直是CTF的堆漏洞利用常规而少见的一种,但是却一直活着人们的心中。我们以64位,libc 2.27为例,其实与2.23差不多,只不过2.27中,0×420以下堆块会进入tcache,但是2.23中0×400就能进入largebin了
把以前学过的东西再复习总结一下,温故而知新。由于相较其他heap,largebin 中的机制相对复杂,所以largebin attack 一直是CTF的堆漏洞利用常规而少见的一种,但是却一直活着人们的心中。
背景知识
我们以64位,libc 2.27为例,其实与2.23差不多,只不过2.27中,0×420以下堆块会进入tcache,但是2.23中0×400就能进入largebin了
开始介绍large bin的相关机制。在此之前,请学习堆的申请与释放等相关知识。
largebin 从哪里来
在堆的机制中,free掉的chunk会进入bin,如果此chunk的大小大于fastbin,free后就会先进unsorted bin,之后,再次申请更大堆块,unsorted bin无法满足,heap就会进入large bin,那么large bin的范围是多少呢?
源码告诉我们一切:
源码中可以看到,MIN_LARGE_SIZE是(64 – 0)*0×10 = 0×400,也就是最小的large bin。这里的MALLOC_ALIGNMENT其实等于SIZE_SZ,即系统位数乘以2,64位系统即 0×8乘以2=0×10,32位为0×4乘以2=0×8。
也就是说我们可以申请0×400的chunk,然后free掉进unsorted bin ,之后申请更大chunk,就可以将0×400的堆放入largebin。
注意:此时,我们使用的是libc 2.27,所以,其实0×400到不了largebin,而是tcache的范围,所以,在libc 2.27中,一般使用0×420,但是如果可以将0×400的tcache填满,也是可以申请到largebin的
那么large bin 的index如何确定呢,这里,系统采取了相对于fastbin和smallbin更加粗犷的方式:
large bin采取了分段的存储,比如第一个范围就是0×400到(48<<6),即0×400到0xc00,而其中,每个链表之间大小差为(1<<6)=0×40,结果如下表:
index | size范围 |
---|---|
64 | [0x400,0x440) 相差0x40 |
65 | [0x440,0x480)相差0x40 |
...... | ......相差0x40 |
96 | [0xc00,0xc40)相差0x40 |
97 | [0xc40,0xe00)相差0x1c0 |
98 | [0xe00,0x1000)相差0x200 |
...... | ......相差0x200 |
...... | ...... |
largebin 的内部
因为largebin,一个bin内部并不是一个size,所以需要fd_nextsize与bk_nextsize将其串起来。
那么largebin内部是如何布置的呢:
首先fd_nextsize指向比他小的最大heap,而bk_nextsize指向比他大的最小的heap,最后将两条链条首尾相连。而fd和bk和其原来的任务一样,都是指向和其大小相同的堆块。那么,画个草图:
那么如果bin里的大小都一样的话,那么第一个释放的堆块作为此bin链的链首(这个和fastbin和tcache都不一样),fd_nextsize与bk_nextsize都指向自己,其余的大小相同的堆块free的时候,fd_nextsize与bk_nextsize就都为0了。
而fd和bk与原本作用一样,指向上一个释放的堆块,但是,这里的链头始终为第一个释放的chunk。
做个小实验,申请4个堆块ABCD,AB大小为0x450,CD大小为0x460,如下图:
其堆块如下:
将其释放至unsorted bin,释放顺序为A B D C:
之后我们申请0x470堆块,此时unsorted bin无法满足,将ABCD放入large bin:
此时largebin内由fd和bk组成的链为:
此时的fd_nextsize与bk_nextsize:
最后的示意图即:
那么large bin 是如何申请到的呢?
源码中显示:
首先通过申请的size找到index,进而找到指定的bin链
在找到bin链后,从最小的heap开始通过bk_nextsize找到第一个大于等于size的heap
在找到heap后,利用fd判断下一个heap的size是否于当前heap的size,如果相等,将下一个heap返回,这样可以减少设置链头的操作
利用unlink取下得到的heap
判断heap的size减去申请的size是否大于MINSIZE,如果大于就将其放入unsorted bin,如果小于,那么直接分配给用户
这就是largebin的分配过程,那么largebin只有这一种被申请的方式吗?
如果在申请堆块的时候没有找到合适heap的时候,会从topchunk开始申请,在此之前,程序会遍历比当前index大的索引,如果在大于fastbin的范围内,存在heap,就会从中切割申请的大小,当前heap的size减去申请的size是否大于MINSIZE,如果大于就将其放入unsorted bin,如果小于,那么直接分配给用户。
即,如果在上述情况下申请0x60,就会从B中分配0x60,余下0x3e0归入unsorted bin
这就是largebin在ptmalloc中的机制了。
large bin attack
那么large bin attack又是怎么进行的呢?
现在有两种常见的利用方式:
在申请largebin的过程中,伪造largebin的bk_nextsize,实现非预期内存申请。
在largebin插入的过程中,伪造largebin的bk_nextsize以及bk,实现任意地址写堆地址。
申请时利用
在申请largebin的时候,从最小的heap开始,利用bk_nextsize寻找合适的heap,如果找到了合适的heap,就将其取出,那么如果我们将bk_nextsize指向其他地方,就可以申请到其他地方。看下源码:
if ((victim = first (bin)) != bin && (unsigned long) chunksize_nomask (victim) >= (unsigned long) (nb)) { victim = victim->bk_nextsize; while (((unsigned long) (size = chunksize (victim)) < (unsigned long) (nb))) victim = victim->bk_nextsize; //寻找堆块 ...... remainder_size = size - nb; unlink (av, victim, bck, fwd); //unlink
所以只需要在目标地方符合unlink,那么就可以通过检查了。举个例子:申请一个largebin,之后free,再申请一个大堆块使之前堆块进入large bin:
申请A和底下分割的0x20,之后freeA,申请B和C。
之后,我们开始利用,这里提供两种思路吧,但是其本质都是修改bk_nextsize。
直接将bk_nextsize指向B的头
将bk_nextsize指向B的内容
第一种和第二种本质上一样,但是第二种可以顺便利用unlink,向bss段上写一个bss段地址,这里假设bss段上存着堆地址,如果PIE开启了,就不好操作了,这里我们关掉PIE,演示第二种漏洞利用。修改A的bk_nextsize,假设此时有UAF:也就是将 bk_nextsize = B+0x10
,而B+0x10一般存在bss段上,所以,此时B上伪造以一个的 fd = bss - 0x18
, bk= bss - 0x10
的堆块,这都属于unlink常规操作。注意伪造堆块的fd_nextsize要等于0,因为unlink有检查。
之后注意对于伪造堆块检查,即检查下一个堆块的pre_size。最后得到:
此时申请large bin ,就会得到fake B的内存区域。
插入时利用
在申请大堆块后,unsorted bin不满足size要求,会将其放入large bin,此时,我们可以通过一些办法,达到一次任意地址写堆地址的目的,看代码:
victim_index = largebin_index (size); bck = bin_at (av, victim_index); //这个是main_arena的地址 fwd = bck->fd;//这是最大size的链首 /* maintain large bins in sorted order */ if (fwd != bck) { /* Or with inuse bit to speed comparisons */ size |= PREV_INUSE; /* if smaller than smallest, bypass loop below */ assert (chunk_main_arena (bck->bk)); if ((unsigned long) (size) //bck->bk是最小size的链首 < (unsigned long) chunksize_nomask (bck->bk)) //如果当前申请的size小于最小szie { fwd = bck; bck = bck->bk; victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//这里不好整 } else //如果当前申请的size不是最小的 { assert (chunk_main_arena (fwd)); while ((unsigned long) size < chunksize_nomask (fwd)) //从最大块开始寻找一个小于szie的链 { fwd = fwd->fd_nextsize; assert (chunk_main_arena (fwd)); } if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd)) // 如果找到的链和申请的size相同 /* Always insert in the second position. */ fwd = fwd->fd; else//如果不同,则说明应该插在这个链前面 { victim->fd_nextsize = fwd;//小的链在victim上 victim->bk_nextsize = fwd->bk_nextsize;//这里如果可以控制 fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim;//这里能写一个victim } bck = fwd->bk; } } else victim->fd_nextsize = victim->bk_nextsize = victim; } mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;//最后这里,还可以有一次写,如果fwd->bk可控
可以看到,这段代码中有两次可以写入victim的地方,由于其比较简单,没啥检查,所以可以直接搞起:
搞到一块large bin 然后搞到一块更大的large bin,且其index一样。在得到这个large bin之前,先改写原本就在large bin中的堆块的bk或bk_nextsize。
举个例子:
首先申请3个chunk,free A堆块,之后申请更大的chunk
此时A在large bin内,之后修改A的bk或bk_nextsize:
修改上述的bk和bk_nextsize,后续就可以达到在target1和target2中写堆B地址的目的。
之后,free chunk B,然后申请大于等于0x480的堆块,比如申请malloc(0x470),就可以达到漏洞利用目的了。
写在最后
如有错误欢迎指正: sofr@foxmail.com
参考
[1] https://ray-cp.github.io/archivers/ptmalloc_argebin_attack
、*本文作者:白里个白sofr,转载请注明来自FreeBuf.COM
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 技术讨论 | Ret2dl_resolve漏洞利用分析
- 技术讨论 | 自动化Web渗透Payload提取技术
- 技术讨论 | 如何编写一段内存蠕虫?
- 技术讨论 | Glibc中堆管理的变化
- 技术讨论 | 简谈渗透测试的些许基础
- 技术讨论 | 基于基站定位APP开发实录
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Django 1.0 Template Development
Scott Newman / Packt / 2008 / 24.99
Django is a high-level Python web application framework designed to support the rapid development of dynamic websites, web applications, and web services. Getting the most out of its template system a......一起来看看 《Django 1.0 Template Development》 这本书的介绍吧!