浅析stdout

写这篇文章完全是0ctf虐了之后决定努力一波而整理的,无意间看到了一个日本人的EasiestPrintf的wp而研究的

看了之后又提高了一波姿势水品,给了自己不少心里安慰(

首先来看一下printf的源码

#undef printf  

/* Write formatted output to stdout from the format string FORMAT.  */  
/* VARARGS1 */  
int  
printf (const char *format, ...)  
{  
  va_list arg;  
  int done;  

  va_start (arg, format);  
  done = vfprintf (stdout, format, arg);//主要是这个函数  
  va_end (arg);  

  return done;  
}  

#undef _IO_printf  
/* This is for libg++.  */  
strong_alias (printf, _IO_printf);  

可以看到printf的核心其实是调用了这个vfprintf,而这个函数的原型是:

extern int vfprintf (FILE *__restrict __s, __const char *__restrict __format, 
                     <span style="white-space:pre">      </span>     _G_va_list __arg);  

第一个参数是文件指针,说明printf函数是通过vfprintf将format输出到stdout文件中,而stdout是(FILE *)类型。

在stdio.h中,是这么定义的:

/* Standard streams.  */  
extern struct _IO_FILE *stdin;      /* Standard input stream.  */  
extern struct _IO_FILE *stdout;     /* Standard output stream.  */  
extern struct _IO_FILE *stderr;     /* Standard error output stream.  */  
/* C89/C99 say they're macros.  Make them happy.  */  
#define stdin stdin  
#define stdout stdout  
#define stderr stderr  

在stdio.c中可以看到

_IO_FILE *stdin = (FILE *) &_IO_2_1_stdin_;  
_IO_FILE *stdout = (FILE *) &_IO_2_1_stdout_;  
_IO_FILE *stderr = (FILE *) &_IO_2_1_stderr_;  

其实这些都不重要,需要了解的是stdout的这个_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;//这个就是linux内核中文件描述符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  
};  
struct _IO_FILE_plus  
{  
  _IO_FILE file;  
  const struct _IO_jump_t *vtable;//IO函数跳转表  
};  

最下面有一个不起眼的东西叫 vtable,这东西不仅名字,连功能也和c++类里面的vtable很像,里面的记录是一些函数指针,便于操作 _IO_FILE 结构,那么我们来看看这结构体有哪些东西吧:

const struct _IO_jump_t _IO_file_jumps =  
{  
  JUMP_INIT_DUMMY,  
  JUMP_INIT(finish, INTUSE(_IO_file_finish)),  
  JUMP_INIT(overflow, INTUSE(_IO_file_overflow)),  
  JUMP_INIT(underflow, INTUSE(_IO_file_underflow)),  
  JUMP_INIT(uflow, INTUSE(_IO_default_uflow)),  
  JUMP_INIT(pbackfail, INTUSE(_IO_default_pbackfail)),  
  JUMP_INIT(xsputn, INTUSE(_IO_file_xsputn)),  //
  JUMP_INIT(xsgetn, INTUSE(_IO_file_xsgetn)),  
  JUMP_INIT(seekoff, _IO_new_file_seekoff),  
  JUMP_INIT(seekpos, _IO_default_seekpos),  
  JUMP_INIT(setbuf, _IO_new_file_setbuf),  //
  JUMP_INIT(sync, _IO_new_file_sync),  
  JUMP_INIT(doallocate, INTUSE(_IO_file_doallocate)),  
  JUMP_INIT(read, INTUSE(_IO_file_read)),  
  JUMP_INIT(write, _IO_new_file_write),  
  JUMP_INIT(seek, INTUSE(_IO_file_seek)),  
  JUMP_INIT(close, INTUSE(_IO_file_close)),  
  JUMP_INIT(stat, INTUSE(_IO_file_stat)),  
  JUMP_INIT(showmanyc, _IO_default_showmanyc),  
  JUMP_INIT(imbue, _IO_default_imbue)  
};  

而这个表示可以写的,所以就可以通过覆盖这个表来获取shell,不仅如此,甚至在一些情况下可以自己伪造这样一张表,然后覆盖原来的vtable,以此来控制程序的流程。

说实话,我几乎认不到几个函数,但是还是可以通过覆盖几个一定会用到的函数来控制跳转。

这篇文章总结得比较全面,过段时间我有空的时候也会总结一下。

发表评论

电子邮件地址不会被公开。 必填项已用*标注