-
FSOP - File Stream Oriented ProgrammingHeap exploitation 2020. 2. 15. 04:28
FSOP란 glibc의 FILE 구조체가 가지고있는 vtable을 변경하는 등 파일 스트림을 이용해 프로그램의 흐름을 변경하는 기법이다.
Structures
struct _IO_FILE
이 구조체는 glibc에서 사용하는 FILE 구조체이다. 파일을 선언하고 fopen으로 파일을 열게 되면 힙에 이 구조체가 생성된다.
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_lock_t이다. 멀티쓰레딩 환경에서 파일을 읽고 쓸 때 race condition을 방지하기 위해 이 포인터를 사용한다. 이 포인터는 write 권한이 있는 메모리 영역을 가리키고 있어야 한다. 일반적으로 자기 FILE 구조체를 가리키게 하거나 bss영역을 넣기도 한다.
struct _IO_FILE_plus
struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable; // + 216 }
이 구조체는 최신 glibc에서 사용하는 FILE 구조체로, _IO_FILE에 _IO_jump_t를 가리키는 포인터가 추가되었다.
struct _IO_jump_t
이 구조체는 _IO_FILE_plus에서 사용하는 vtable 구조체이다.
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); // + 136 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 };
결론적으로 보면 아래 그림과 같이 구조체가 생성된다.
_IO_FILE_plus Exploit Flow
일반적으로 FILE*를 변경하는 경우 위 두 구조체를 직접 다 만들어주어도 되고, vtable*만 덮을 경우 _IO_file_jumps를 만들어주면 된다.
아니면 libc에 있는 stdin의 vtable에 접근해 직접 vtable을 변조하는 등 여러가지 방법이 존재한다.