写这篇文章完全是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,以此来控制程序的流程。
说实话,我几乎认不到几个函数,但是还是可以通过覆盖几个一定会用到的函数来控制跳转。
这篇文章总结得比较全面,过段时间我有空的时候也会总结一下。