这篇文章是在石墨上编写的,可能会存在格式上的一些问题,导致阅读不够流畅。
如果有需要格式较为完善的文件,可以发邮件找我要 PDF 格式的文件。
Largebin Attack 是我一直以来未能攻克的一道难题,不过终于在最近把他弄懂了,其中也少不了长时间的琢磨。
根据我自己浏览各个文章后得到的经验,我建议师傅在学习 Large Bin Attck 的内容时可以对其中的结构进行绘图,并且手动模拟一遍源码的流程以加深理解。
Large Bin Attack
利用范围
Large Bin Attack 是一种较为困难的攻击方式,他对攻击的条件要求较多,实现也较为复杂,通常和 Unsorted Bin 打配合来实现 house of storm,来达到提升影响力的作用。
glibc2.29 及以下版本,可以利用 Large Bin Attack 来写两个地址。
但是在 glibc2.30 及以上版本中,只能利用 Large Bin Attack 来写一个地址。
git commit:
利用原理
我们在 unsorted bin attack 的时候就已经了解过,当一个块将要 malloc 的时候,堆管理器会根据返回去不同的地方查找是否有空闲的 chunk,其中一个地方就是 unsorted bin,而在遍历 unsorted bin 堆块的时候会先将当前遍历的堆块进行脱链,脱链根据大小放入 small bin 或者 largebin 当中去。
而 largebin attack 就发生在堆块中 unsorted bin 放入 largebin 的过程当中去。
这个过程较为复杂,所以我们需要先来了解一下 largebin 是什么?有什么?
largebin 是什么?
堆块管理器中最慢的一种管理方式,largebin 的范围是 size > 0x400(x64)
large bin 采用双链表结构,里面的 chunk 从头结点的 fd 指针开始,按大小顺序进行排列。
largebin 有什么?
在 bins 中占据 64 到 126 这个范围的位置,
index
公差
64
[0x400,0x440) 0x40
65
[0x440,0x480) 0x40
…
0x40(共 32 个)
97
0x200
98
0x200
…
0x200(共 16 个)
112
0x1000
113
0x1000
…
0x1000(共 8 个)
120
0x8000
121
0x8000
122
0x8000
123
0x8000(共 4 个)
124
0x40000
125
0x40000(共 2 个)
126
> 0x40000
根据上表可以知道,就算在同一个 index 中,其大小也有差别,这和 fastbin 是不一样的。
所以堆管理器就利用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
This struct declaration is misleading (but accurate and necessary).
It declares a "view" into memory allowing access to necessary
fields at known offsets from a given base. See explanation below.
*/structmalloc_chunk{INTERNAL_SIZE_Tprev_size;/* Size of previous chunk (if free). */INTERNAL_SIZE_Tsize;/* Size in bytes, including overhead. */structmalloc_chunk*fd;/* double links -- used only if free. */structmalloc_chunk*bk;/* Only used for large blocks: pointer to next larger size. */structmalloc_chunk*fd_nextsize;/* double links -- used only if free. */structmalloc_chunk*bk_nextsize;};
if(in_smallbin_range(size)){victim_index=smallbin_index(size);//获取size对应的smallbin的index
bck=bin_at(av,victim_index);//bck指向size对应的smallbin的链表头
//fwd指向size对应的smallbin的链表中的新加入的chunk(small bin使用头插法)
fwd=bck->fd;}else//如果不再smallbin的范围,也就是说在large bin 的范围
{victim_index=largebin_index(size);//获取size对应的large bin的index
bck=bin_at(av,victim_index);//bck指向size对应的large bin的链表头
fwd=bck->fd;//fwd指向size对应的large bin的链表中的新加入的chunk
//如果large bin 非空,在largbin进行按顺序插入
if(fwd!=bck){/* Or with inuse bit to speed comparisons */size|=PREV_INUSE;assert((bck->bk->size&NON_MAIN_ARENA)==0);//默认不启用assert
/*
large bin中的chunk是按从大到小排列的,如果size < large bin
的最后一个chunk,说明size是这个large bin中的最小的,我们把它
加入到此large bin尾部。
*/if((unsignedlong)(size)<(unsignedlong)(bck->bk->size)){fwd=bck;bck=bck->bk;/*
large bin 中size最小的chunk的fd_nextsize会指向size最大的
那个chunk,也就是首部的chunk。同样,large bin 中size最大的
chunk的bk_nextsize会指向size最小的那个chunk。
victim的bk_nextsize指向large bin原来最小的chunk,它的
bk_nextsize指向最大的那个chunk。那么原来的最小的就成了第二小的了。
把它fd_nextsize和bk_nextsize都修正。
*/victim->fd_nextsize=fwd->fd;victim->bk_nextsize=fwd->fd->bk_nextsize;//最大size的chunk的bk_nextsize,和原来最小chunk的bk_nextsize都指向victim
fwd->fd->bk_nextsize=victim->bk_nextsize->fd_nextsize=victim;}else//如果victim不是large bin 中最小的chunk
{assert((fwd->size&NON_MAIN_ARENA)==0);//默认不启用assert
//从大到小(从头到尾)找到合适的位置
while((unsignedlong)size<fwd->size){fwd=fwd->fd_nextsize;assert((fwd->size&NON_MAIN_ARENA)==0);}//如果size刚好相等,就直接加入到其后面省的改fd_nextsize和bk_nextsize了
if((unsignedlong)size==(unsignedlong)fwd->size)fwd=fwd->fd;else{//size不相等,即size>fwd->size,把victim加入到纵向链表中
victim->fd_nextsize=fwd;victim->bk_nextsize=fwd->bk_nextsize;fwd->bk_nextsize=victim;victim->bk_nextsize->fd_nextsize=victim;}bck=fwd->bk;}}else//如果large bin 为空,将victim加入到纵向列表
victim->fd_nextsize=victim->bk_nextsize=victim;}//#define mark_bin(m, i) ((m)->binmap[idx2block (i)] |= idx2bit (i))
mark_bin(av,victim_index);//把victim加入到的bin的表示为非空
//把victim加入到large bin的链表中
victim->bk=bck;victim->fd=fwd;fwd->bk=victim;bck->fd=victim;
那么我们需要满足的情况下,这时候申请一块堆块,就会触发 unsorted bin 的脱链,让 X 插入到 largebin 中,那么如果满足 size(X) > size(Y) && index(size(X)) == index(size(Y)),那么 X 就会插入到 Y 的前面,从而触发这一串代码进行插入。
if((unsignedlong)(size)<(unsignedlong)chunksize_nomask(bck->bk)){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{assert(chunk_main_arena(fwd));while((unsignedlong)size<chunksize_nomask(fwd)){fwd=fwd->fd_nextsize;assert(chunk_main_arena(fwd));}if((unsignedlong)size==(unsignedlong)chunksize_nomask(fwd))/* Always insert in the second position. */fwd=fwd->fd;else{victim->fd_nextsize=fwd;victim->bk_nextsize=fwd->bk_nextsize;if(__glibc_unlikely(fwd->bk_nextsize->fd_nextsize!=fwd))malloc_printerr("malloc(): largebin double linked list corrupted (nextsize)");fwd->bk_nextsize=victim;victim->bk_nextsize->fd_nextsize=victim;}bck=fwd->bk;if(bck->fd!=fwd)malloc_printerr("malloc(): largebin double linked list corrupted (bk)");}