mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-25 11:41:16 +07:00
599 lines
18 KiB
Markdown
599 lines
18 KiB
Markdown
# 4.13 利用 _IO\_FILE 结构
|
||
|
||
- [FILE 结构](#file-结构)
|
||
- [FSOP](#fsop)
|
||
- [防御机制](#防御机制)
|
||
- [新的利用技术](#新的利用技术)
|
||
- [CTF 实例](#ctf-实例)
|
||
- [参考资料](#参考资料)
|
||
|
||
|
||
## FILE 结构
|
||
FILE 结构体的利用是一种通用的控制流劫持技术。攻击者可以覆盖堆上的 FILE 指针使其指向一个伪造的结构,利用结构中一个叫做 `vtable` 的指针,来执行任意代码。
|
||
|
||
我们知道 FILE 结构被一系列流操作函数(`fopen()`、`fread()`、`fclose()`等)所使用,大多数的 FILE 结构体保存在堆上(stdin、stdout、stderr除外,位于libc数据段),其指针动态创建并由 `fopen()` 返回。在 glibc(2.23) 中,这个结构体是 `_IO_FILE_plout`,包含了一个 `_IO_FILE` 结构体和一个指向 `_IO_jump_t` 结构体的指针:
|
||
```c
|
||
// libio/libioP.h
|
||
|
||
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);
|
||
#if 0
|
||
get_column;
|
||
set_column;
|
||
#endif
|
||
};
|
||
|
||
/* We always allocate an extra word following an _IO_FILE.
|
||
This contains a pointer to the function jump table used.
|
||
This is for compatibility with C++ streambuf; the word can
|
||
be used to smash to a pointer to a virtual function table. */
|
||
|
||
struct _IO_FILE_plus
|
||
{
|
||
_IO_FILE file;
|
||
const struct _IO_jump_t *vtable;
|
||
};
|
||
|
||
extern struct _IO_FILE_plus *_IO_list_all;
|
||
```
|
||
`vtable` 指向的函数跳转表其实是一种兼容 C++ 虚函数的实现。当程序对某个流进行操作时,会调用该流对应的跳转表中的某个函数。
|
||
```c
|
||
// libio/libio.h
|
||
|
||
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
|
||
};
|
||
|
||
extern struct _IO_FILE_plus _IO_2_1_stdin_;
|
||
extern struct _IO_FILE_plus _IO_2_1_stdout_;
|
||
extern struct _IO_FILE_plus _IO_2_1_stderr_;
|
||
```
|
||
进程中的 FILE 结构会通过 `_chain` 域构成一个链表,链表头部用全局变量 `_IO_list_all` 表示。
|
||
|
||
下面我们来看几个函数的实现。
|
||
#### fopen
|
||
```c
|
||
// libio/iofopen.c
|
||
|
||
_IO_FILE *
|
||
__fopen_internal (const char *filename, const char *mode, int is32)
|
||
{
|
||
struct locked_FILE
|
||
{
|
||
struct _IO_FILE_plus fp;
|
||
#ifdef _IO_MTSAFE_IO
|
||
_IO_lock_t lock;
|
||
#endif
|
||
struct _IO_wide_data wd;
|
||
} *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE)); // 为 FILE 结构分配空间
|
||
|
||
if (new_f == NULL)
|
||
return NULL;
|
||
#ifdef _IO_MTSAFE_IO
|
||
new_f->fp.file._lock = &new_f->lock;
|
||
#endif
|
||
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|
||
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
|
||
#else
|
||
_IO_no_init (&new_f->fp.file, 1, 0, NULL, NULL);
|
||
#endif
|
||
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps; // 设置 vtable = &_IO_file_jumps
|
||
_IO_file_init (&new_f->fp); // 调用 _IO_file_init 函数进行初始化
|
||
#if !_IO_UNIFIED_JUMPTABLES
|
||
new_f->fp.vtable = NULL;
|
||
#endif
|
||
if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL) // 打开目标文件
|
||
return __fopen_maybe_mmap (&new_f->fp.file);
|
||
|
||
_IO_un_link (&new_f->fp);
|
||
free (new_f);
|
||
return NULL;
|
||
}
|
||
|
||
_IO_FILE *
|
||
_IO_new_fopen (const char *filename, const char *mode)
|
||
{
|
||
return __fopen_internal (filename, mode, 1);
|
||
}
|
||
```
|
||
```c
|
||
// libio/fileops.c
|
||
|
||
# define _IO_new_file_init _IO_file_init
|
||
|
||
void
|
||
_IO_new_file_init (struct _IO_FILE_plus *fp)
|
||
{
|
||
/* POSIX.1 allows another file handle to be used to change the position
|
||
of our file descriptor. Hence we actually don't know the actual
|
||
position before we do the first fseek (and until a following fflush). */
|
||
fp->file._offset = _IO_pos_BAD;
|
||
fp->file._IO_file_flags |= CLOSED_FILEBUF_FLAGS;
|
||
|
||
_IO_link_in (fp); // 调用 _IO_link_in 函数将 fp 放进链表
|
||
fp->file._fileno = -1;
|
||
}
|
||
```
|
||
```c
|
||
// libio/genops.c
|
||
|
||
void
|
||
_IO_link_in (struct _IO_FILE_plus *fp)
|
||
{
|
||
if ((fp->file._flags & _IO_LINKED) == 0)
|
||
{
|
||
fp->file._flags |= _IO_LINKED;
|
||
#ifdef _IO_MTSAFE_IO
|
||
_IO_cleanup_region_start_noarg (flush_cleanup);
|
||
_IO_lock_lock (list_all_lock);
|
||
run_fp = (_IO_FILE *) fp;
|
||
_IO_flockfile ((_IO_FILE *) fp);
|
||
#endif
|
||
fp->file._chain = (_IO_FILE *) _IO_list_all; // fp 放到链表头部
|
||
_IO_list_all = fp; // 链表头 _IO_list_all 指向 fp
|
||
++_IO_list_all_stamp;
|
||
#ifdef _IO_MTSAFE_IO
|
||
_IO_funlockfile ((_IO_FILE *) fp);
|
||
run_fp = NULL;
|
||
_IO_lock_unlock (list_all_lock);
|
||
_IO_cleanup_region_end (0);
|
||
#endif
|
||
}
|
||
}
|
||
```
|
||
|
||
#### fread
|
||
```c
|
||
// libio/iofread.c
|
||
|
||
_IO_size_t
|
||
_IO_fread (void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
|
||
{
|
||
_IO_size_t bytes_requested = size * count;
|
||
_IO_size_t bytes_read;
|
||
CHECK_FILE (fp, 0);
|
||
if (bytes_requested == 0)
|
||
return 0;
|
||
_IO_acquire_lock (fp);
|
||
bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested); // 调用 _IO_sgetn 函数
|
||
_IO_release_lock (fp);
|
||
return bytes_requested == bytes_read ? count : bytes_read / size;
|
||
}
|
||
```
|
||
```c
|
||
// libio/genops.c
|
||
|
||
_IO_size_t
|
||
_IO_sgetn (_IO_FILE *fp, void *data, _IO_size_t n)
|
||
{
|
||
/* FIXME handle putback buffer here! */
|
||
return _IO_XSGETN (fp, data, n); // 调用宏 _IO_XSGETN
|
||
}
|
||
```
|
||
```c
|
||
// libio/libioP.h
|
||
|
||
#define _IO_JUMPS_FILE_plus(THIS) \
|
||
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable)
|
||
|
||
#if _IO_JUMPS_OFFSET
|
||
# define _IO_JUMPS_FUNC(THIS) \
|
||
(*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \
|
||
+ (THIS)->_vtable_offset))
|
||
# define _IO_vtable_offset(THIS) (THIS)->_vtable_offset
|
||
#else
|
||
# define _IO_JUMPS_FUNC(THIS) _IO_JUMPS_FILE_plus (THIS)
|
||
# define _IO_vtable_offset(THIS) 0
|
||
#endif
|
||
|
||
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
|
||
|
||
#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)
|
||
```
|
||
所以 `_IO_XSGETN` 宏最终会调用 `vtable` 中的函数,即:
|
||
```c
|
||
// libio/fileops.c
|
||
|
||
_IO_size_t
|
||
_IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
|
||
{
|
||
```
|
||
|
||
#### fwrite
|
||
```c
|
||
// libio/iofwrite.c
|
||
|
||
_IO_size_t
|
||
_IO_fwrite (const void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
|
||
{
|
||
_IO_size_t request = size * count;
|
||
_IO_size_t written = 0;
|
||
CHECK_FILE (fp, 0);
|
||
if (request == 0)
|
||
return 0;
|
||
_IO_acquire_lock (fp);
|
||
if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
|
||
written = _IO_sputn (fp, (const char *) buf, request); // 调用 _IO_sputn 函数
|
||
_IO_release_lock (fp);
|
||
/* We have written all of the input in case the return value indicates
|
||
this or EOF is returned. The latter is a special case where we
|
||
simply did not manage to flush the buffer. But the data is in the
|
||
buffer and therefore written as far as fwrite is concerned. */
|
||
if (written == request || written == EOF)
|
||
return count;
|
||
else
|
||
return written / size;
|
||
}
|
||
```
|
||
```c
|
||
// libio/libioP.h
|
||
|
||
#define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N)
|
||
|
||
#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)
|
||
```
|
||
`_IO_XSPUTN` 最终将调用下面的函数:
|
||
```c
|
||
// libio/fileops.c
|
||
|
||
_IO_size_t
|
||
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
|
||
{
|
||
```
|
||
|
||
#### fclose
|
||
```c
|
||
// libio/iofclose.c
|
||
|
||
int
|
||
_IO_new_fclose (_IO_FILE *fp)
|
||
{
|
||
int status;
|
||
|
||
CHECK_FILE(fp, EOF);
|
||
|
||
#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
|
||
/* We desperately try to help programs which are using streams in a
|
||
strange way and mix old and new functions. Detect old streams
|
||
here. */
|
||
if (_IO_vtable_offset (fp) != 0)
|
||
return _IO_old_fclose (fp);
|
||
#endif
|
||
|
||
/* First unlink the stream. */
|
||
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
|
||
_IO_un_link ((struct _IO_FILE_plus *) fp); // 将 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);
|
||
if (fp->_mode > 0)
|
||
{
|
||
#if _LIBC
|
||
/* This stream has a wide orientation. This means we have to free
|
||
the conversion functions. */
|
||
struct _IO_codecvt *cc = fp->_codecvt;
|
||
|
||
__libc_lock_lock (__gconv_lock);
|
||
__gconv_release_step (cc->__cd_in.__cd.__steps);
|
||
__gconv_release_step (cc->__cd_out.__cd.__steps);
|
||
__libc_lock_unlock (__gconv_lock);
|
||
#endif
|
||
}
|
||
else
|
||
{
|
||
if (_IO_have_backup (fp))
|
||
_IO_free_backup_area (fp);
|
||
}
|
||
if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
|
||
{
|
||
fp->_IO_file_flags = 0;
|
||
free(fp); // 释放 FILE 结构体
|
||
}
|
||
|
||
return status;
|
||
}
|
||
```
|
||
|
||
|
||
## FSOP
|
||
FSOP(File Stream Oriented Programming)是一种劫持 `_IO_list_all`(libc.so中的全局变量) 来伪造链表的利用技术,通过调用 `_IO_flush_all_lockp()` 函数来触发,该函数会在下面三种情况下被调用:
|
||
- libc 检测到内存错误时
|
||
- 执行 exit 函数时
|
||
- main 函数返回时
|
||
|
||
当 glibc 检测到内存错误时,会依次调用这样的函数路径:`malloc_printerr -> _libc_message -> abort -> _IO_flush_all_lockp`。
|
||
```c
|
||
// libio/genops.c
|
||
|
||
int
|
||
_IO_flush_all_lockp (int do_lock)
|
||
{
|
||
int result = 0;
|
||
struct _IO_FILE *fp;
|
||
int last_stamp;
|
||
|
||
#ifdef _IO_MTSAFE_IO
|
||
__libc_cleanup_region_start (do_lock, flush_cleanup, NULL);
|
||
if (do_lock)
|
||
_IO_lock_lock (list_all_lock);
|
||
#endif
|
||
|
||
last_stamp = _IO_list_all_stamp;
|
||
fp = (_IO_FILE *) _IO_list_all; // 将其覆盖为伪造的链表
|
||
while (fp != NULL)
|
||
{
|
||
run_fp = fp;
|
||
if (do_lock)
|
||
_IO_flockfile (fp);
|
||
|
||
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) // 条件
|
||
#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) // 将其修改为 system 函数
|
||
result = EOF;
|
||
|
||
if (do_lock)
|
||
_IO_funlockfile (fp);
|
||
run_fp = NULL;
|
||
|
||
if (last_stamp != _IO_list_all_stamp)
|
||
{
|
||
/* Something was added to the list. Start all over again. */
|
||
fp = (_IO_FILE *) _IO_list_all;
|
||
last_stamp = _IO_list_all_stamp;
|
||
}
|
||
else
|
||
fp = fp->_chain; // 指向我们指定的区域
|
||
}
|
||
|
||
#ifdef _IO_MTSAFE_IO
|
||
if (do_lock)
|
||
_IO_lock_unlock (list_all_lock);
|
||
__libc_cleanup_region_end (0);
|
||
#endif
|
||
|
||
return result;
|
||
}
|
||
```
|
||
于是对 `_IO_OVERFLOW(fp, EOF)` 的调用会变成对 `system('/bin/sh')` 的调用。
|
||
|
||
|
||
## 防御机制
|
||
但是在 libc-2.24 中加入了对 vtable 指针的检查。这个 [commit](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=commitdiff;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51) 新增了两个函数:`IO_validate_vtable` 和 `_IO_vtable_check`。
|
||
```c
|
||
// libio/libioP.h
|
||
|
||
/* Perform vtable pointer validation. If validation fails, terminate
|
||
the process. */
|
||
static inline const struct _IO_jump_t *
|
||
IO_validate_vtable (const struct _IO_jump_t *vtable)
|
||
{
|
||
/* Fast path: The vtable pointer is within the __libc_IO_vtables
|
||
section. */
|
||
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
|
||
const char *ptr = (const char *) vtable;
|
||
uintptr_t offset = ptr - __start___libc_IO_vtables;
|
||
if (__glibc_unlikely (offset >= section_length))
|
||
/* The vtable pointer is not in the expected section. Use the
|
||
slow path, which will terminate the process if necessary. */
|
||
_IO_vtable_check ();
|
||
return vtable;
|
||
}
|
||
```
|
||
```c
|
||
// libio/vtables.c
|
||
|
||
void attribute_hidden
|
||
_IO_vtable_check (void)
|
||
{
|
||
#ifdef SHARED
|
||
/* Honor the compatibility flag. */
|
||
void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
|
||
#ifdef PTR_DEMANGLE
|
||
PTR_DEMANGLE (flag);
|
||
#endif
|
||
if (flag == &_IO_vtable_check)
|
||
return;
|
||
|
||
/* In case this libc copy is in a non-default namespace, we always
|
||
need to accept foreign vtables because there is always a
|
||
possibility that FILE * objects are passed across the linking
|
||
boundary. */
|
||
{
|
||
Dl_info di;
|
||
struct link_map *l;
|
||
if (_dl_open_hook != NULL
|
||
|| (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
|
||
&& l->l_ns != LM_ID_BASE))
|
||
return;
|
||
}
|
||
|
||
#else /* !SHARED */
|
||
/* We cannot perform vtable validation in the static dlopen case
|
||
because FILE * handles might be passed back and forth across the
|
||
boundary. Therefore, we disable checking in this case. */
|
||
if (__dlopen != NULL)
|
||
return;
|
||
#endif
|
||
|
||
__libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
|
||
}
|
||
```
|
||
所有的 libio vtables 被放进了专用的只读的 `__libc_IO_vtables` 段,以使它们在内存中连续。在任何间接跳转之前,vtable 指针将根据段边界进行检查,如果指针不在这个段,则调用函数 `_IO_vtable_check()` 做进一步的检查,并且在必要时终止进程。
|
||
|
||
|
||
## 新的利用技术
|
||
在防御机制下通过修改虚表的利用技术已经用不了了,但同时出现了新的利用技术。既然无法将 vtable 指针指向 `__libc_IO_vtables` 以外的地方,那么就在 `__libc_IO_vtables` 里面找些有用的东西。比如 `_IO_str_jumps`:
|
||
```c
|
||
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)
|
||
};
|
||
```
|
||
这个 vtable 中包含了一个叫做 `_IO_str_overflow` 的函数,该函数中存在相对地址的引用(可伪造):
|
||
```c
|
||
int
|
||
_IO_str_overflow (_IO_FILE *fp, int c)
|
||
{
|
||
int flush_only = c == EOF;
|
||
_IO_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 >= (_IO_size_t) (_IO_blen (fp) + flush_only))
|
||
{
|
||
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
|
||
return EOF;
|
||
else
|
||
{
|
||
char *new_buf;
|
||
char *old_buf = fp->_IO_buf_base;
|
||
size_t old_blen = _IO_blen (fp);
|
||
_IO_size_t new_size = 2 * old_blen + 100; // new_size 的计算方法
|
||
if (new_size < old_blen)
|
||
return EOF;
|
||
new_buf
|
||
= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size); // fp 上的相对地址
|
||
if (new_buf == NULL)
|
||
{
|
||
/* __ferror(fp) = 1; */
|
||
return EOF;
|
||
}
|
||
if (old_buf)
|
||
{
|
||
memcpy (new_buf, old_buf, old_blen);
|
||
(*((_IO_strfile *) fp)->_s._free_buffer) (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;
|
||
}
|
||
```
|
||
构造的条件如下:
|
||
- _flags = 0
|
||
- _IO_buf_base = 0
|
||
- _IO_buf_end = (bin_sh - 100) / 2
|
||
- _IO_write_ptr = 0x7fffffffffffffff
|
||
- _IO_write_base = 0
|
||
|
||
|
||
## CTF 实例
|
||
请查看章节 6.1.24、6.1.25 和 6.1.26。另外在章节 3.1.8 中也有相关内容。
|
||
|
||
|
||
## 参考资料
|
||
- [abusing the FILE structure](https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/)
|
||
- [Play with FILE Structure - Yet Another Binary Exploit Technique](https://www.slideshare.net/AngelBoy1/play-with-file-structure-yet-another-binary-exploit-technique)
|