First fit

当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大可以放置所请求块的空闲块。分配器执行这种搜索的方式是由放置策略(placement policy)确定的。一些常见的策略是首次适配(first fit)、下一次适配(next fit)和最佳适配(best fit)。首次适配从头开始搜索空闲链表,选择第一个合适的空闲块….

《深入理解计算机系统 第二版(CSAPP)》P567

glibc使用的策略就是first fit,这种特性使我们可以很好地预测分配器将会使用空闲列表中的哪个块。一个常见情景是在存在堆指针UAF的情况下,我们可以控制其他块的内容。

first_fit.c

代码中我们先请求了两个块a和b,释放a后,a被放入空闲链表,此时请求一个比a小的块c,分配器会使用原来a使用的空间,此时如果a存在UAF,我们将有两个指向同一片内存区域的指针(a和c),这种情形往往是一次攻击的开端。

Fastbin dup

绕过fastbin double free检查的一种方式。

请求两个大小合适的块a和b,释放a进入fastbin,此时如果再次释放a,就会触发double free检查。但是malloc.c:3935这个判断只检查了bin的顶端和要释放的指针是否相同,所以如果我们按照a->b->a的顺序释放,检查就会被绕过。

image-20191031160126798

image-20191031160416744

fastbin_dup_into_stack

fastbin dup的进一步利用。

在fastbin dup构造出double free的基础上(假设0x60200在fastbin中存在两次),我们第一次请求到0x60200的地址时,把0x60200的fd位置写入某个地址x,因为0x60200仍在fastbin中,所以在再次请求到0x60200后,fastbin top变成了x,意味着我们下次的请求到的地址是x。选择合适的x值,我们就可能获得任意内存读写的机会。(这里有一个需要绕过的检查是需要让x+sizeof(long)处的值为这个fastbin的大小来“骗”过分配器x指向的是一个合法的块。)

fastbin_dup_into_stack.c中的x是&stack_var-8

image-20191031164225989

fastbin_dup_consolidate

绕过fastbin double free检查的另一种方式。

当请求一个大块时会触发malloc_consolidate,把fastbin中的空闲块都整理到unsorted bin中。存在这样的场景:我们请求一个块a并释放让他进入fastbin,此时请求一个大块触发malloc_consolidate让这个空闲块被转移到unsorted bin中,这样再次释放块a的时候,fastbin top不是块a,所以可以利绕过检查把a被放置到fastbin中,此时块a同时处于fastbin和unsorted bin中,double free达成。

image-20191101144309575

image-20191101144412511

unsafe_unlink

有两个相邻的堆块a和b,a存在溢出可以把b的in_use位覆盖成0,可以在a中伪造一个fake chunk,并改写b的pre_size让它认为它前面的空闲块就是这个fake chunk。此时释放块b,就会对fake chunk进行unlink。

image-20191103203645894

unlink宏在进行双向链表合法性的检查后把目标块从链表中取出,通过在fake chunk的fd和bk处填入合适的值,我们就可以绕过检查并且达成一个场景—自己写自己

假设有一个我们可以控制的指针p,让fd=p-0x18, bk=p-0x10,就可以满足fd->bk==p和bk->fd==p,之后的交换指针之后p=p-0x18,这意味着p指向了自己之前的区域,在合适的条件下,我们就得到了一个任意地址读写。

image-20191103204702541

image-20191103211327710

image-20191103211742192

house_of_spirit

在栈上伪造一个fake chunk,释放它进入fastbins,这样如果再次申请就会申请到这片可控区域🚧

image-20191103213031926

poison_null_byte

通过溢出相邻free chunk的chunk_size的低1位为0。制造fake chunk最后达成块重叠

after b1 = malloc(0x100)

​ after b2 = malloc(0x80)

​ after free(b1)

image-20191103223650584

after free(c),b1和c合并,b2被忽视😟

image-20191103223748949

after d=malloc(0x300),d和b2重叠

image-20191103224501945

house_of_lore

在栈上设计两个fake chunk,写small bin中块的bk指向fake chunk 1,通过两次malloc请求得到栈上的内存。

下面描述一下house_of_lore.c的过程

改掉victim->bk之后

image-20191103233258704

image-20191103233406717

after void *p3 = malloc(100);

image-20191103234115374

image-20191103234205313

after char *p4 = malloc(100); ,此时p4指向栈上的地址

image-20191103234342336

最后两次malloc都涉及了small bin的取用,注意small bins是顺着头结点按bk来“取件”🛴

image-20191104000153906

overlapping_chunks

通过修改unsorted bin中chunk的size,使得在获取新块的时候得到与已有chunk重叠的块。

修改p2 size之前

image-20191104091933720

修改p2 size为evil_chunk_size之后。unsortedbin中的空闲块大小为0x181,覆盖p3的区域

image-20191104092327588

p4=malloc(0x178)之后, p4使用了空闲块,此时p4包含p3

image-20191104092848744

overlapping_chunks_2

修改chunk的大小,使他错误地包含其他使用中的chunk,经过释放再重新获取得到的新块和老的chunk重叠

before change p2 size

image-20191104095001025

p2 size被改之后,此时p2和p4 “相邻”

image-20191104095447527

free(p2)之后,p2和p4合并成一个空闲块,大小为0xbd0,p3被包含在其中

image-20191104100430025

此时申请一个合适大小的块便会和p3重叠。

house_of_force

通过修改top_chunk为特别大的数使得可以是堆请求落到bss处

修改top chunk size之前

image-20191104101859688

修改top chunk size为-1(0xffffffffffffffff),此时即使申请一个特别大的块,也不会触发mmap,这里我们想让堆请求到bss区域,而在内存布局中bss地址比heap地址低,所以请求的大小是一个负值,无符号表现为0xfff….这样的形式。

image-20191104101940203

如果我们想让堆分配到bss上的dest处,malloc的size应该多大呢,下面是计算过程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
	/*
	 * The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
	 * new_top = old_top + nb
	 * nb = new_top - old_top
	 * req + 2sizeof(long) = new_top - old_top
	 * req = new_top - old_top - 2sizeof(long)
	 * req = dest - 2sizeof(long) - old_top - 2sizeof(long)
	 * req = dest - old_top - 4*sizeof(long)
	 */
	unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;

后面通过两次malloc,就可以达成目标。

第一次:malloc(evil_size),此时top chunk在bss_var-0x10处

image-20191104105719220

第二次:malloc(100),成功!

image-20191104105929084

unsorted_bin_into_stack

修改unsorted bin中chunk的size和bk指向一个fake chunk,请求一个大小在(size, fake_chunk size)之间的块,使得fake chunk被征用。

修改victim的size和bk之前

image-20191104111357125

修改victim的size和bk之后,减少victim的size为0x20并把victim的bk指向stack_buffer,在stack_buffer处伪造了一个大小为0x110的chunk

image-20191104111203297

现在请求一个0x100的,会在unsortedbin中顺着bk找可用的块,victim不满足,命中stack_buffer指向的fake chunk。注意stack_buffer[3]的位置需要提前写入一个可写的地址(这个原因没在源码中找到),不一定非得是stack_buffer的地址。

unsorted_bin_attack

修改unsorted bin中chunk的bk使其指向target-0x10,此chunk被取用时发生unlink,taget被赋值改写。

写p的bk使其指向stack_var-0x10处

image-20191104113309035

此时malloc(400),p指向的chunk被使用。unlink过程中发生victim->bk->fd = victim->fd,stack_var被赋值为0x00007ffff7dd1b78。

image-20191104114013030

这里改写的值是不可控制,一般为unsorted bin的链表头。在真实场景中,这招通常用来改写global_max_fast来为后面的攻击做铺垫。

large_bin_attack

通过改写large bin中chunk的bk和bk_nextsize使得在large bin中插入新节点的时候目标地址被改写。改写的值为堆地址。常被用来改写global_max_fast的值以进行fast bin attack

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
unsigned long *p1 = malloc(0x320);
malloc(0x20);
unsigned long *p2 = malloc(0x400);
malloc(0x20);
unsigned long *p3 = malloc(0x400);
malloc(0x20);
free(p1);
free(p2);
malloc(0x90);
free(p3);

image-20191104115546389

malloc(0x90)之后,p1被切割使用,p2被放入largebins

image-20191104120337879

free(p3)

image-20191104120857402

此时改写p2的size、bk、bk_nextsize。

1
2
3
4
5
p2[-1] = 0x3f1; //缩小p2的size,使得后面p3进入largebin的时候被插在头部(因为largebin降序排列)
p2[0] = 0;
p2[2] = 0;
p2[1] = (unsigned long)(&stack_var1 - 2);
p2[3] = (unsigned long)(&stack_var2 - 4);

image-20191104121015416

malloc(0x90)后,p1继续被分割,p3被整理到large bin中。p3被插入large bin的头部,发生

victim->bk->fd=p3victim->bk_nextsize->fd=p3,所以stack_var1和stack_var2都被改写为p3的地址

image-20191104121739626

image-20191104122108956

image-20191104122842499

house_of_einherjar

溢出相邻下一个chunk的size低位字节为\x00,并伪造pre_size,使得下一个chunk释放的时候发生合并,指针指向设计好的位置。

image-20191104142409949

溢出低位字节并写入伪造的pre_size,使得pre chunk指向fake_chunk,注意fake_chunk->size需要和b的pre size对应上

image-20191104142457570

image-20191104142603983

此时free(b),top_chunk指向fake_chunk

image-20191104142849363