前言:
这两题都是关于_IO_FILE文件流攻击的题目,如果对_IO_FILE文件流不是很清楚,可以看ctfwiki中的_IO_FILE介绍——传送门。
echo_back——关于_IO_2_1_stdin结构的利用
文件保护全开,不能got覆写。
进入IDA,发现程序有两个功能,一个是setname,一个是echo back。
查看setname功能,发现此功能只能输入7个字节,不具备ROP和栈帧转移的能力。
我们只能看第二个功能echo back功能。
进入功能内,我们发现这个程序里边同样有输入功能,但是最大输入仍然只有7个字节,不具备ROP和栈帧转移的能力,我们只能另寻他法。继续往下看,我们发现底下有明显的格式化字符串漏洞,我可以通过他来泄露数据和改写地址。
我们打开gdb来看看栈上是否有可利用的地址:
我们发现在偏移19的地方有__libc_start_main的数据,我这边由于程序链接的是libc2.29的库,所以偏移为234个字节,但是远端肯定不是这样的。我们知道远端libc库位2.23,通过对libc2.23库的了解,我们知道栈上__libc_start_main地址偏移位240。所以我们泄露出来后要减去的是240才能打通远端。
由于程序开了PIE,拿到了libc的基址,我们还需要拿到elf文件的基地址:这里标红的地址选一个减去后三位就可以了。
由于程序开了Full RELRO,所以我们不能覆写got表的地址,而我们可输入字节只有7个,我们这里就只有攻击scanf函数来绕过7字节输入的限制了。
这里我们就要了解一些scanf内部的知识:
其中最重要的就属_IO_new_file_underflow函数,他揭示了scanf输入的内部结构:
int _IO_new_file_underflow (_IO_FILE *fp)
{
_IO_ssize_t count;
#if 0
/* SysV does not make this test; take it out for compatibility */
if (fp->_flags & _IO_EOF_SEEN)
return (EOF);
#endif
if (fp->_flags & _IO_NO_READS)
{
fp->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
/
if (fp->_IO_read_ptr < fp->_IO_read_end) //
return *(unsigned char *) fp->_IO_read_ptr; //
if (fp->_IO_buf_base == NULL)
{
/* Maybe we already have a push back pointer. */
if (fp->_IO_save_base != NULL)
{
free (fp->_IO_save_base);
fp->_flags &= ~_IO_IN_BACKUP;
}
_IO_doallocbuf (fp);
}
/* Flush all line buffered files before reading. */
/* FIXME This can/should be moved to genops ?? */
if (fp->_flags & (_IO_LINE_BUF|_IO_UNBUFFERED))
{
#if 0
_IO_flush_all_linebuffered ();
#else
/* We used to flush all line-buffered stream. This really isn't
required by any standard. My recollection is that
traditional Unix systems did this for stdout. stderr better
not be line buffered. So we do just that here
explicitly. --drepper */
_IO_acquire_lock (_IO_stdout);
if ((_IO_stdout->_flags & (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF))
== (_IO_LINKED | _IO_LINE_BUF))
_IO_OVERFLOW (_IO_stdout, EOF);
_IO_release_lock (_IO_stdout);
#en