Pwn IO_FILE 学习笔记

IO_FILE结构体利用技巧

如果你是一个练习时长达到半年以上的Pwn手,你应该就会知道Pwn中很多进阶的题目都涉及到对于IO_FILE结构体的运用(当然你可能暂时还不了解这个技巧)。利用好IO_FILE结构体,学习相关技巧,你会不可思议地发现居然可以通过这些手段实现任意读写以及程序执行流的劫持。

就我个人感觉,我觉得在学习了IO_FILE结构体利用(以及ret2dlresolve这类高阶技巧)后,我对于Pwn的理解发生了质变。从之前死板地一招一式地学习漏洞利用技巧,慢慢进入了那个“天地万物为我所用”的境界。

不懂我的意思?你早晚会明白的(doge)

鉴于IO_FILE结构体利用技巧很多,本篇博客长期更新。

本篇博客参考了CTFwiki,以及很多大佬的博客,如CSDN上的hollk大佬的文章等,我会在必要的时候放上对应的链接供大家进一步学习。



1.FILE结构体组成。

FILE,在Linux系统的标准IO库中,是用于描述文件的结构称为文件流。FILE结构在程序执行fopen等函数时会进行创建,并分配在堆中。我们常定义一个指向FILE结构的指针来接收这个返回值。

这里以glibc 2.31 版本为例——实际上现在很多题目的glibc版本都已经比这个要高了。

struct _IO_FILE
{
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */
  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;
  int _flags2;
  __off_t _old_offset; /* This used to be _offset but it's too small.  */

  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

挺复杂的是不是,我也觉得很复杂,你也没必要(也不太可能)一次把它背下来,实际上这个结构在gdb里面也很好查到(一句 p _IO_2_1_stdout_,就可以查看stdout对应的FILE结构),所以背诵是没有必要的事情。至于具体字段的作用,我会在后面一一讲解。

首先,ELF文件在执行时,会默认创建三个文件流并且保存在libc.so的数据段中——没错,就是你之前在IDA里面经常在bss段最上面的看到的那三个:stdout、stdin、stderr,即标准输出,标准输入,标准错误输出。这三个在libc中有对应的符号:_IO_2_1_stdout_、_IO_2_1_stdin_、_IO_2_1_stderr_,同时libc中有一个_IO_list_all这个指针指向了stdin这个FILE结构体,并且FILE结构通过其chain域形成一个单向列表彼此相连。

你问我为啥第一个是stdin?你想想stdin的文件描述符,是0对吧......事实上,这个对应FILE的/_fileno字段。这里储存着FILE结构的文件描述符。

最开头的_flags,里面有很多的标志位,一般代表着FILE的状态,后续我们在利用这个结构体时常常需要覆写_flag来绕过一些检查以实现我们的目标。目前我们只需要记住这个_flags通常是 0x fbad xxxx 就行了,后四个十六进制位要根据需求调整。

这里放上和_flags标志位相关的宏定义,到时候大伙可以来这里去查。

/* Magic number and bits for the _flags field.  The magic number is
   mostly vestigial, but preserved for compatibility.  It occupies the
   high 16 bits of _flags; the low 16 bits are actual flag bits.  */

#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

不用背下来!不用背下来!

其实FILE(或者说_IO_FILE)并不是最外层的结构,它被封装于一个叫_IO_FILE_plus的结构体中。或者说,这个_IO_FILE_plus才是我们说的真正的FILE结构?

struct _IO_FILE_plus
{
  FILE file;    //这里的FILE和上面代码框里面的_IO_FILE是同一个东西,应该在某个地方有个typedef,暂时没找到......
  const struct _IO_jump_t *vtable;
};

可以看到,_IO_FILE_plus这个结构的主体,仍然是FILE结构。而多出来的那个指针指向了一个_IO_jump_t结构,这个结构里面存在了很多和输入输出有关的函数。如果我们劫持了这个指针并令其指向一段我们可控的内存附件,那么我们就能劫持程序执行流。

#define JUMP_FIELD(TYPE, NAME) TYPE NAME

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);
};

这个不用背下来,哪个常用以后会知道的。

除了stdout,stdin,stderr这三个文件流之外,之后再创建的FILE结构都是保存在堆段的。

另外,由于ELF文件的bss段开头存在着stdout等结构的指针,而堆题一般在bss段里面有一个heaplist数组来存放申请的堆块的地址,如果程序对于输入的下标idx没有检验,就很有可能通过stdout,stdin指针来修改FILE结构,进而实现某些目的。

未完待续......

posted @ 2022-10-13 20:10  Jmp·Cliff  阅读(500)  评论(0)    收藏  举报