IO FILE 之恣意读写 | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

IO FILE 之恣意读写

申博_新闻事件 申博 95次浏览 已收录 0个评论

上篇文章形貌了vtable check以及绕过vtalbe check的要领之一,应用vtable段中的_IO_str_jumps来举行FSOP。本篇则重要形貌运用缓冲区指针来举行恣意内存读写。

从前面fread以及fwrite的剖析中,我们晓得了FILE组织体中的缓冲区指针是用来举行输入输出的,很轻易的就想到了假如能过捏造这些缓冲区指针,在肯定的前提下应当能够完成恣意地点的读写。

本文包括两部份:

  • 运用stdin规范输入缓冲区举行恣意地点写。
  • 运用stdout规范输出缓冲区举行恣意地点读写。

接下来形貌这两部份的道理以及给出响应的问题实践,道理引见部份是基于已具有能够捏造IO FILE组织体的缓冲区指针破绽的基础上举行的。在后续历程假定我们目标写的地点是write_start,写终了地点为write_end;读的目标地点为read_start,读的终了地点为read_end

前几篇传送门:

  • IO FILE之fopen详解
  • IO FILE之fread详解
  • IO FILE之fwrite详解
  • IO FILE之fclose详解
  • IO FILE之挟制vtable及FSOP
  • IO FILE 之vtable挟制以及绕过

stdin规范输入缓冲区举行恣意地点写

这一部份重要论述的是运用stdin规范输入缓冲区指针举行恣意地点写的功用。

道理剖析

先经由过程fread回忆下经由过程输入缓冲区举行输入的流程:

  1. 推断fp->_IO_buf_base输入缓冲区是不是为空,假如为空则挪用的_IO_doallocbuf去初始化输入缓冲区。
  2. 在分派完输入缓冲区或输入缓冲区不为空的情况下,推断输入缓冲区是不是存在数据。
  3. 假如输入缓冲区有数据则直接拷贝至用户缓冲区,假如没有或不够则挪用__underflow函数实行体系挪用读取数据到输入缓冲区,再拷贝到用户缓冲区。
    IO FILE 之恣意读写

假定我们能过掌握输入缓冲区指针,使得输入缓冲区指向想要写的地点,那末在第三步挪用体系挪用读取数据到输入缓冲区的时刻,也就会挪用体系挪用读取数据到我们想要写的地点,从而完成恣意地点写的目标。

依据fread的源码,我们再看下要想完成往write_start写长度为write_end - write_start的数据详细阅历了些什么。

_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
 ...
  if (fp->_IO_buf_base == NULL)
    {
      ...
      //输入缓冲区为空则初始化输入缓冲区
    }

  while (want > 0)
    {

      have = fp->_IO_read_end - fp->_IO_read_ptr;

      if (have > 0) 
        {
          ...
          //memcpy

        }

      if (fp->_IO_buf_base
          && want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
        {
          if (__underflow (fp) == EOF)  ## 挪用__underflow读入数据
          ...
        }
      ...
  return n - want;
}

上面贴出了一些症结代码,起首是_IO_file_xsgetn函数,函数先推断输入缓冲区_IO_buf_base是不是为空,假如为空的话则挪用_IO_doallocbuf初始化缓冲区,因而需组织_IO_buf_base不为空。

接着函数中当输入缓冲区有盈余时即_IO_read_end -_IO_read_ptr >0,会将缓冲区中的数据拷贝至目标中,因而想要应用输入缓冲区完成读写,最好使_IO_read_end -_IO_read_ptr =0_IO_read_end ==_IO_read_ptr

同时还请求读入的数据size要小于缓冲区数据的大小,否则为进步效力会挪用read直接读。

_IO_file_xsgetn函数中当缓冲区不能满足需求时会挪用__underflow去读取数据,检察__underflow

int
_IO_new_file_underflow (_IO_FILE *fp)
{
  _IO_ssize_t count;
  ...
  ## 假如存在_IO_NO_READS标志,则直接返回
  if (fp->_flags & _IO_NO_READS)
    {
      fp->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  ## 假如输入缓冲区里存在数据,则直接返回
  if (fp->_IO_read_ptr < fp->_IO_read_end)
    return *(unsigned char *) fp->_IO_read_ptr;
  ...

  ##挪用_IO_SYSREAD函数终究实行体系挪用读取数据
  count = _IO_SYSREAD (fp, fp->_IO_buf_base,
               fp->_IO_buf_end - fp->_IO_buf_base);
  ...

}
libc_hidden_ver (_IO_new_file_underflow, _IO_file_underflow)

_IO_new_file_underflow函数中先推断fp->_IO_read_ptr < fp->_IO_read_end是不是建立,建立则直接返回,因而再次请求捏造的组织体_IO_read_end ==_IO_read_ptr,绕过该前提搜检。

接着函数会搜检_flags是不是包括_IO_NO_READS标志,包括则直接返回。标志的定义是#define _IO_NO_READS 4,因而_flags不能包括4

终究体系挪用_IO_SYSREAD (fp, fp->_IO_buf_base,fp->_IO_buf_end - fp->_IO_buf_base)读取数据,因而要想应用stdin输入缓冲区需设置FILE组织体中_IO_buf_basewrite_start_IO_buf_endwrite_end。同时也需将组织体中的fp->_fileno设置为0,终究挪用read (fp->_fileno, buf, size))读取数据。

将上述前提综合表述为:

  1. 设置_IO_read_end即是_IO_read_ptr
  2. 设置_flag &~ _IO_NO_READS_flag &~ 0x4
  3. 设置_fileno为0。
  4. 设置_IO_buf_basewrite_start_IO_buf_endwrite_end;且使得_IO_buf_end-_IO_buf_base大于fread要读的数据。

实践

实践的问题是whctf2017的stackoverflow,这一年也是这一种应用体式格局的鼓起之年,这一题是很典范的一题。

问题起首是输入name,并把name输出出来,由于name未举行初始化设置且读取数据后未到场\x00,能够由此泄显露libc地点。

接着进入主功用函数,破绽在先运用temp变量保留了输入的size,然则后续末了写\x00的时刻运用的是temp,而不是size,因而存在一个溢出写\x00的破绽。

在之前的文章中,我们晓得了当请求堆块大小很大时(0x200000),请求出来的堆块会紧挨着libc,因而我们能够应用这个溢出写\x00的破绽往libc的内存中写入一个\x00字节。

往那里写一个\x00字节,后续转变全部内存组织而拿到shell?答案时stdin组织体中的\x00,我们先看下输入之前的stdin组织体中的数据:
IO FILE 之恣意读写
IO FILE 之恣意读写

能够看到在glibc 2.24中,stdin组织体中存储_IO_buf_end指针内存地点的末端恰好为\x00,若应用破绽我们将_IO_buf_base末端写\x00,则会使得_IO_buf_base指向stdin组织体中存储_IO_buf_end指针内存地点,即可应用输入缓冲区掩盖_IO_buf_end

我们可将_IO_buf_end掩盖为__malloc_hook+0x8,则输入时末了掌握写的数据为stdin中的_IO_buf_end指针位置到__malloc_hook+0x8,以完成掌握__malloc_hook

道理就是云云,须要多提两点。

一是IO_getc函数的作用是革新_IO_read_ptr,每次会从输入缓冲区读一个字节数据行将_IO_read_ptr加一,当_IO_read_ptr即是_IO_read_end的时刻便会挪用read读数据到_IO_buf_base地点中。

二是往malloc_hook写什么,由于one gadget用不了,因而在栈中找到了一个gadget,地点为0x400a23,能够读取数据构成栈溢出,从而举行ROP,拿到shell。

.text:0000000000400A23                 lea     rax, [rbp+name]
.text:0000000000400A27                 mov     esi, 50h        ; count
.text:0000000000400A2C                 mov     rdi, rax        ; input
.text:0000000000400A2F                 call    input_data

stdout规范输入缓冲区举行恣意地点读写

上半部份运用了stdin举行恣意地点写,这部份重要论述stdout来举行恣意地点读写。stdin只能输入数据到缓冲区,因而只能举行写。而stdout会将数据拷贝至输出缓冲区,并将输出缓冲区中的数据输出出来,所以假如可控stdout组织体,经由过程组织可完成应用其举行恣意地点读以及恣意地点写。

Linux Kernel Exploit 内核漏洞学习(3)-Bypass-Smep

简介 smep的全称是Supervisor Mode Execution Protection,它是内核的一种保护机制,作用是当CPU处于ring0模式的时候,如果执行了用户空间的代码就会触发页错误,很明现这个保护机制就是为了防止ret2usr攻击的…. 这里为了演示如何绕过这个保护机制,我仍然使用的是CISCN2017 babydriver,这道题基本分析和利用UAF的方法原理我已经在kernel pwn–UAF这篇文章中做了解释,在这里就不再阐述了,环境也是放在github上面的,需要的可以自行下载学习…. 前置知识 ptmx

恣意写

恣意写的重要道理为:组织好输出缓冲区将其改成想要恣意写的地点,当输出数据可控时,会将数据拷贝至输出缓冲区,即完成了将可控数据拷贝至我们想要写的地点。

想要完成上述功用,检察fwrite源码中怎样才完成该功用:

_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{ 
...
    ## 推断输出缓冲区另有若干空间
    else if (f->_IO_write_end > f->_IO_write_ptr)
    count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */

  ## 假如输出缓冲区有空间,则先把数据拷贝至输出缓冲区
  if (count > 0)
    {
    ...
      memcpy (f->_IO_write_ptr, s, count);

恣意写功用的完成在于IO缓冲区没有满时,会先将要输出的数据复制到缓冲区中,可经由过程这一点来完成恣意地点写的功用。能够看到恣意写彷佛很简单,只需将_IO_write_ptr指向write_start_IO_write_end指向write_end即可。

恣意读

应用stdout举行恣意地点读的道理为:掌握输出缓冲区指针指向我们输入的地点,组织好前提,使得输出缓冲区为已满的状况,再次挪用输出函数时,顺序会革新输出缓冲区即会输出我们想要的数据,完成恣意读。

仍然是检察fwrite源码中怎样才完成该功用:

_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{ 

    _IO_size_t count = 0;
...
    ## 推断输出缓冲区另有若干空间
    else if (f->_IO_write_end > f->_IO_write_ptr)
    count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */

  ## 假如输出缓冲区有空间,则先把数据拷贝至输出缓冲区
  if (count > 0)
    {    
    ...
    //memcpy
    }
    if (to_do + must_flush > 0)
    {
      if (_IO_OVERFLOW (f, EOF) == EOF)

f->_IO_write_end > f->_IO_write_ptr时,会挪用memcpy拷贝数据,因而最好组织前提f->_IO_write_end即是f->_IO_write_ptr

接着进入_IO_OVERFLOW函数,去革新输出缓冲区,跟进去:

int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
  ## 推断标志位是不是包括_IO_NO_WRITES
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      f->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }

  ## 推断输出缓冲区是不是为空
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
    {
      ...
    }

  ## 输出输出缓冲区 
  if (ch == EOF)
    return _IO_do_write (f, f->_IO_write_base,
             f->_IO_write_ptr - f->_IO_write_base);
  return (unsigned char) ch;
}
libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)

能够看到_IO_new_file_overflow,起首推断_flags是不是包括_IO_NO_WRITES,假如包括则直接返回,因而需组织_flags不包括_IO_NO_WRITES,其定义为#define _IO_NO_WRITES 8

接着推断缓冲区是不是为空以及是不是包括_IO_CURRENTLY_PUTTING标志位,假如包括的话则做一些过剩的操纵,能够不可控,因而最好定义_flags不包括_IO_CURRENTLY_PUTTING,其定义为#define _IO_CURRENTLY_PUTTING 0x800

接着挪用_IO_do_write去输出输出缓冲区,其传入的参数是f->_IO_write_base,大小为f->_IO_write_ptr - f->_IO_write_base。因而若想完成恣意地点读,应组织_IO_write_baseread_start,组织_IO_write_ptrread_end

跟进去_IO_do_write,看该函数的症结代码:

static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  ...
  _IO_size_t count;
  if (fp->_flags & _IO_IS_APPENDING)
    fp->_offset = _IO_pos_BAD;
  else if (fp->_IO_read_end != fp->_IO_write_base)
    {
      _IO_off64_t new_pos
    = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
      if (new_pos == _IO_pos_BAD)
    return 0;
      fp->_offset = new_pos;
    }
  ## 挪用函数输出输出缓冲区
  count = _IO_SYSWRITE (fp, data, to_do);
  ...

  return count;
}

看到在挪用_IO_SYSWRITE之前还推断了fp->_IO_read_end != fp->_IO_write_base,因而须要组织组织体使得_IO_read_end即是_IO_write_base

也能够组织_flags包括_IO_IS_APPENDING_IO_IS_APPENDING的定义为#define _IO_IS_APPENDING 0x1000,如许就不会走背面的这个推断而直接实行到_IO_SYSWRITE了,平常我都是设置_IO_read_end即是_IO_write_base

末了_IO_SYSWRITE挪用write (f->_fileno, data, to_do)输出数据,因而还需组织_fileno为规范输出形貌符1。

将上述前提综合形貌为:

  1. 设置_flag &~ _IO_NO_WRITES_flag &~ 0x8
  2. 设置_flag & _IO_CURRENTLY_PUTTING_flag | 0x800
  3. 设置_fileno为1。
  4. 设置_IO_write_base指向想要泄漏的处所;_IO_write_ptr指向泄漏终了的地点。
  5. 设置_IO_read_end即是_IO_write_base或设置_flag & _IO_IS_APPENDING_flag | 0x1000
  6. 设置_IO_write_end即是_IO_write_ptr(非必需)。

    满足上述五个前提,可完成恣意读。

实践

运用stdout举行恣意读写比较典范的一题应当是hctf2018的babyprintf_ver2了,下面来举行应用形貌。

问题直接给出了顺序基址。

然后存在显著的溢出,能够掩盖stdout,然则没法掩盖stdout的vtable,由于它会修改。

详细该怎样应用呢,起首运用stdout恣意读来泄漏libc地点。组织的FILE组织体以下(运用pwn_debug的IO_FILE_plus模块):

io_stdout_struct=IO_FILE_plus()
flag=0
flag&=~8
flag|=0x800
flag|=0x8000
io_stdout_struct._flags=flag
io_stdout_struct._IO_write_base=pro_base+elf.got['read']
io_stdout_struct._IO_read_end=io_stdout_struct._IO_write_base
io_stdout_struct._IO_write_ptr=pro_base+elf.got['read']+8
io_stdout_struct._fileno=1

以此来泄漏read的地点。

接着运用stdout的恣意地点写来写__malloc_hook,组织的FILE组织体以下:

io_stdout_struct=IO_FILE_plus()
flag=0
flag&=~8
flag|=0x8000
io_stdout_write=IO_FILE_plus()
io_stdout_write._flags=flag
io_stdout_write._IO_write_ptr=malloc_hook
io_stdout_write._IO_write_end=malloc_hook+8

终究将one gaget 写入malloc_hook。怎样触发malloc呢,能够运用输出较大的字符打印来触发malloc函数或是%n来触发,个中%n可触发malloc的原因是在于__readonly_area会经由过程fopen翻开maps文件来读取内容来推断地点段是不是可写,而fopen会挪用malloc函数请求空间,因而触发。

能够会有人关于以为flag|=0x8000这行组织代码以为比较新鲜,须要诠释下,在printf函数中会挪用_IO_acquire_lock_clear_flags2 (stdout)来猎取lock从而继承顺序,假如没有_IO_USER_LOCK标志的话,顺序会一直在轮回,而_IO_USER_LOCK定义为#define _IO_USER_LOCK 0x8000,因而须要设置flag|=0x8000才够使exp顺利举行。_IO_acquire_lock_clear_flags2 (stdout)的汇编代码以下:

0x7f0bcf15d850 <__printf_chk+96>     mov    rbp, qword ptr [rip + 0x2a16f9]
0x7f0bcf15d857 <__printf_chk+103>    mov    rbx, qword ptr [rbp]
0x7f0bcf15d85b <__printf_chk+107>    mov    eax, dword ptr [rbx]
0x7f0bcf15d85d <__printf_chk+109>    and    eax, 0x8000
0x7f0bcf15d862 <__printf_chk+114>    jne    __printf_chk+202 <0x7f0bcf15d8ba>

小结

运用IO FILE来举行恣意内存读写真的是个很壮大的功用,组织起来也比较轻易。然则关于FILE组织体的捏造,个人觉得能够最轻易出问题的处所照样_flags字段的组织,能够某个处所不注意就致使顺序走偏了,因而觉得能够照样把默许的stdoutstdin直接拷贝出来用会比较好一些,同时pwn_debugIO_FILE_plus模块供应了apiarbitrary_write_check以及arbitrary_read_check来举行响应检测,看响应字段是不是设置准确。

至此IO FILE系列形貌终了,前四篇对IO函数fopen、fread、fwrite以及fclose的源码剖析;背面三篇引见了针对IO FILE的相干应用,包括挟制vtable、vtable引入的check机制以及响应的后续应用体式格局。在全部历程当中为轻易组织IO 组织体还在pwn_debug中到场了IO_FILE_plus模块。

末了一句,浏览源码关于进修是一件很有协助的事变。

参考链接

  1. HCTF 2018 部份 PWN writeup–babyprinf_ver2
  2. 浅析IO_FILE组织及应用
  3. 锻练!那基础不是IO!——从printf源码看libc的IO


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明IO FILE 之恣意读写
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址