File-Stream Oriented Programming

File-Stream Oriented Programming

文件描述符的结构

Linux中文件描述符的结构,定义在glibc/libio/bits/types/struct_FILE.h中和glibc/libio/libioP.h中

struct _IO_FILE
{
  int _flags;                /* High-order word is _IO_MAGIC; rest is flags. */
  /* The following pointers correspond to the C++ streambuf protocol. */
  char *_IO_read_ptr;        /* Current read pointer */
  char *_IO_read_end;        /* End of get area. */
  char *_IO_read_base;        /* Start of putback+get area. */
  char *_IO_write_base;        /* Start of put area. */
  char *_IO_write_ptr;        /* Current put pointer. */
  char *_IO_write_end;        /* End of put area. */
  char *_IO_buf_base;        /* Start of reserve area. */
  char *_IO_buf_end;        /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */
  struct _IO_marker *_markers;
  struct _IO_FILE *_chain;
  int _fileno;
  int _flags2;
  __off_t _old_offset; /* This used to be _offset but it's too small.  */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];
  _IO_lock_t *_lock;
};

struct _IO_FILE_plus
{
  FILE file;
  const struct _IO_jump_t *vtable;
};

对于进程打开的每一个文件描述符,libc会分配一个_IO_FILE_plus结构体,这个结构体位于用户进程的堆中。一个进程的文件描述符以单链表的形式存储,头指针是libc的_IO_list_all。

在一个进程开启时,会自动打开三个文件描述符,分别为stderr,stdout和stdin,这三个描述符结构体分配在libc中。当使用fopen等打开新的文件描述符时,新的描述符结构体加入到这个链表的头部。

对于一个文件描述符的操作,实际上是通过_IO_FILE_plus中vtable指向的一个_IO_jump_t类型的结构体实现的。这个结构体定义在glibc/libio/libioP.h中

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
};

对于文件描述符,其结构体中vtable字段通常指向libc中_IO_file_jumps结构体,这是一个_IO_jump_t的实例

const struct _IO_jump_t _IO_file_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_file_finish),
  JUMP_INIT(overflow, _IO_file_overflow),
  JUMP_INIT(underflow, _IO_file_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_default_pbackfail),
  JUMP_INIT(xsputn, _IO_file_xsputn),
  JUMP_INIT(xsgetn, _IO_file_xsgetn),
  JUMP_INIT(seekoff, _IO_new_file_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_new_file_setbuf),
  JUMP_INIT(sync, _IO_new_file_sync),
  JUMP_INIT(doallocate, _IO_file_doallocate),
  JUMP_INIT(read, _IO_file_read),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, _IO_file_seek),
  JUMP_INIT(close, _IO_file_close),
  JUMP_INIT(stat, _IO_file_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};

通过这个结构体一系列函数指针,可完成对文件的读写关闭等一系列操作。

文件描述符结构体的利用

使用fopen等打开的文件,其文件描述符会被分配在进程的堆上,通过堆上的漏洞可以对其中的字段进行改写等,达到控制PC的目的。

在libc-2.23以前,通用的利用方式为,改写文件描述符结构体的vtable字段,使之指向待执行的函数,通过控制偏移,使得对该文件描述符执行write,read或close等操作时实际上执行了攻击者需要的函数。

在libc-2.23以后,每次调用vtable指向的函数时,对vtable字段进行合法性检查,这个检查定义在glibc/libio/libioP.h中

/* Perform vtable pointer validation.  If validation fails, terminate the process.  */
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  /* Fast path: The vtable pointer is within the __libc_IO_vtables section.  */
  uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
  uintptr_t ptr = (uintptr_t) vtable;
  uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
  if (__glibc_unlikely (offset >= section_length))
    /* The vtable pointer is not in the expected section.  Use the slow path, which will terminate the process if necessary.  */
    _IO_vtable_check ();
  return vtable;
}

IO_validate_vtable()检查vtable字段是否指向libc中__libc_IO_vtables区域,如果检查不通过,则调用_IO_vtable_check()进行进一步检查。

1.改写vtable指针,指向_IO_str_jumps

适用于libc-2.27版本及以前
一种利用方式是,使vtable字段指向__libc_IO_vtables区域另一vtable,_IO_str_jumps,这也是一个_IO_jump_t类型的结构体,在内存中位于_IO_file_jumps附近

const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_str_finish),
  JUMP_INIT(overflow, _IO_str_overflow),
  JUMP_INIT(underflow, _IO_str_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_str_pbackfail),
  JUMP_INIT(xsputn, _IO_default_xsputn),
  JUMP_INIT(xsgetn, _IO_default_xsgetn),
  JUMP_INIT(seekoff, _IO_str_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),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};

在_IO_file_jumps中,对文件进行关闭操作的_IO_file_finish指针,在_IO_str_jumps的相同偏移处,为_IO_str_finish。若将文件描述符结构体的vtable字段指向了_IO_str_jumps,对这个文件进行关闭操作时,实际上会调用_IO_str_finish,这个函数定义如下

void
_IO_str_finish (_IO_FILE *fp, int dummy)
{
  if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
    (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
  fp->_IO_buf_base = NULL;
  _IO_default_finish (fp, 0);
}

可见,在这个函数中,首先会将文件描述符结构体cast为_IO_strfile类型,然后将其中_s._free_buffer字段的值当作函数,将_IO_buf_base字段的值当作参数进行一次调用。此处,_s._free_buffer字段相对于文件描述符结构体的偏移为0xe8

同时,可以注意到,进行上述调用前会对文件描述符结构体的flag字段进行一些检查,在执行flcose()到完成上述调用的过程中还会进行几次flag检查,需要对flag进行设置通过上述检查完成最终调用,一般设置flag为0x8000

我们可以通过堆上的漏洞,首先将一个文件描述符结构体的vtable字段改写为指向_IO_str_jumps,然后在结构体的0xe8偏移处写入system()函数的地址,在结构体的_IO_buf_base写入"/bin/sh"的地址,此时对这个文件描述符进行fclose操作,将会执行system("/bin/sh")从而得到shell

具体实例参考pwn2win2020-command

上述利用方法仅适用于libc-2.27及以前,从libc-2.28版本开始,_IO_str_finish的实现改为

void
_IO_str_finish (_IO_FILE *fp, int dummy)
{
  if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
    free (fp->_IO_buf_base);
  fp->_IO_buf_base = NULL;
  _IO_default_finish (fp, 0);
}

无法再使用上述利用方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值