这个题目又是一个PLT
表损坏,ida
无法分析的题目。修复方法是将.got
中的偏移修改为extern
的位置,之后ida
就会自动识别相应的函数,此时我们将.got
中对应的DATA XREF
中函数(.plt.sec
)的命名更改之后,ida
就可以正常的分析函数了。具体的修复方法点这。
程序一共提供了四种功能add, delete, show, edit
。存储两个堆块指针,可分配的堆块的大小为0x80-0x400
。只能使用一次edit
,并且在该函数中存在一个off-by-one
的漏洞。
同时设置了sandbox
只能通过orw
进行,因此我们需要执行ropchain
。
off-by-one
我们可以进行overlap
,泄露出libc
基址。FSOP
的利用了。libc2.24
之后的版本中增加了对vtable
的检查,而libc2.29
相对libc2.27
之前的IO_strfile
来说,其分配和释放内存的函数都被修改为了标准的malloc,free
函数,因此没有办法直接getshell
直接覆写io_list_all
或者stderr->chain
。伪造io_file
链表,利用_io_str_overflow
函数中的malloc
和memcpy
函数实现任意写。看一下源码
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;
}
注意到这里会申请一个new_size=2 * old_blen + 100
大小的new_buf
,并执行memcpy (new_buf, old_buf, old_blen);
操作,这里的old_blen
的计算方式是#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
,也就是说如果我们控制了IO_FILE
结构体,并伪造_IO_buf_end
和_IO_buf_base
就可以申请任意大小的堆块,如果提前布置好堆布局,就可以分配到任意的内存地址,利用memcpy
执行任意地址写。需要绕过几个条件
1. ((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
2. (fp->_flags & _IO_NO_WRITES) == FALSE
// _IO_blen (fp): ((fp)->_IO_buf_end - (fp)->_IO_buf_base)`
3. (fp->_IO_write_ptr - fp->_IO_write_base) > (fp)->_IO_buf_end - (fp)->_IO_buf_base
4. fp->_IO_buf_base != NULL //当然这也是进行memcpy的地址