티스토리 뷰

Introduction

file struct 구조를 분석하여 File Stream Oriented Programming에 대해 공부한 내용을 정리하려 한다.

Data Structure in File

먼저, 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
};

libc에서, __IO_FILE_ 구조체들은 모두 단일 연결 리스트로 연결 되어있다.

포인터 *_chain은 리스트의 다음 __IO_FILE의 주소를 가지고 있다.

연결 리스트의 꼭대기는 __IO_list_all_에 저장된다.

일반적으로 링크된 layout은 다음과 같다.

0x7f0cc0434500 <_IO_list_all>:    0x00007f0cc0434520  0x0000000000000000
0x7f0cc0434510: 0x0000000000000000  0x0000000000000000
0x7f0cc0434520 <_IO_2_1_stderr_>: 0x00000000fbad2087  0x00007f0cc04345a3
0x7f0cc0434530 <_IO_2_1_stderr_+16>:  0x00007f0cc04345a3  0x00007f0cc04345a3
0x7f0cc0434540 <_IO_2_1_stderr_+32>:  0x00007f0cc04345a3  0x00007f0cc04345a3
0x7f0cc0434550 <_IO_2_1_stderr_+48>:  0x00007f0cc04345a3  0x00007f0cc04345a3
0x7f0cc0434560 <_IO_2_1_stderr_+64>:  0x00007f0cc04345a4  0x0000000000000000
0x7f0cc0434570 <_IO_2_1_stderr_+80>:  0x0000000000000000  0x0000000000000000
0x7f0cc0434580 <_IO_2_1_stderr_+96>:  0x0000000000000000  0x00007f0cc0434600
0x7f0cc0434590 <_IO_2_1_stderr_+112>: 0x0000000000000002  0xffffffffffffffff
0x7f0cc04345a0 <_IO_2_1_stderr_+128>: 0x0000000000000000  0x00007f0cc0435750
0x7f0cc04345b0 <_IO_2_1_stderr_+144>: 0xffffffffffffffff  0x0000000000000000
0x7f0cc04345c0 <_IO_2_1_stderr_+160>: 0x00007f0cc0433640  0x0000000000000000
0x7f0cc04345d0 <_IO_2_1_stderr_+176>: 0x0000000000000000  0x0000000000000000
0x7f0cc04345e0 <_IO_2_1_stderr_+192>: 0x0000000000000000  0x0000000000000000
0x7f0cc04345f0 <_IO_2_1_stderr_+208>: 0x0000000000000000  0x00007f0cc0430400
0x7f0cc0434600 <_IO_2_1_stdout_>: 0x00000000fbad2887  0x00007f0cc0434683

//And the data of _IO_2_1_stderr_ can be interpreted as:
_flags = 0xfbad2087, 
_IO_read_ptr = 0x7f0cc04345a3, 
_IO_read_end = 0x7f0cc04345a3, 
_IO_read_base = 0x7f0cc04345a3, 
_IO_write_base = 0x7f0cc04345a3, 
_IO_write_ptr = 0x7f0cc04345a3, 
_IO_write_end = 0x7f0cc04345a3, 
_IO_buf_base = 0x7f0cc04345a3, 
_IO_buf_end = 0x7f0cc04345a4, 
_IO_save_base = 0x0, 
_IO_backup_base = 0x0, 
_IO_save_end = 0x0, 
_markers = 0x0, 
_chain = 0x7f0cc0434600,  //point to _IO_2_1_stdout_
_fileno = 0x2, 
_flags2 = 0x0, 
_old_offset = 0xffffffffffffffff, 
_cur_column = 0x0, 
_vtable_offset = 0x0, 
_shortbuf = {0x0}, 
_lock = 0x7f0cc0435750, 
_offset = 0xffffffffffffffff, 
_codecvt = 0x0, 
_wide_data = 0x7f0cc0433640, 
_freeres_list = 0x0, 
_freeres_buf = 0x0, 
__pad5 = 0x0, 
_mode = 0x0, 
_unused2 = {0x0 <repeats 20 times>}

_IO_FILE의 또 다른 중요한 구조체는 __IO_FILE_plus_이다.

이것은 vtable(구조체 __IO_jump_t_와 같은) 을 유지시킨다.

struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *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);
    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
};

Exploitation techniques

기본적인 부분만 정리를 해보려고 한다.
(심화 된 내용은 아래 레퍼런스를 참조.)

fp를 이용한 fsop

File *fp; 의 값을 overwrite할 수 있다면, fake fp를 만들어 포인터 값을 옮기고 fake vtable으로 위치시켜 file stream 함수를 이용할때 프로그램의 실행 흐름을 control할 수 있다.

ba0bab➤  x/50gx 0x602010
0x602010:    0x00000000fbad2488    0x0000000000000000
0x602020:    0x0000000000000000    0x0000000000000000
0x602030:    0x0000000000000000    0x0000000000000000
0x602040:    0x0000000000000000    0x0000000000000000
0x602050:    0x0000000000000000    0x0000000000000000
0x602060:    0x0000000000000000    0x0000000000000000
0x602070:    0x0000000000000000    0x00007ffff7dd2540
0x602080:    0x0000000000000003    0x0000000000000000
0x602090:    0x0000000000000000    0x00000000006020f0
0x6020a0:    0xffffffffffffffff    0x0000000000000000
0x6020b0:    0x0000000000602100    0x0000000000000000
0x6020c0:    0x0000000000000000    0x0000000000000000
0x6020d0:    0x0000000000000000    0x0000000000000000
0x6020e0:    0x0000000000000000    0x00007ffff7dd06e0
0x6020f0:    0x0000000000000000    0x0000000000000000
0x602100:    0x0000000000000000    0x0000000000000000
0x602110:    0x0000000000000000    0x0000000000000000
0x602120:    0x0000000000000000    0x0000000000000000
0x602130:    0x0000000000000000    0x0000000000000000
0x602140:    0x0000000000000000    0x0000000000000000
0x602150:    0x0000000000000000    0x0000000000000000
0x602160:    0x0000000000000000    0x0000000000000000
0x602170:    0x0000000000000000    0x0000000000000000
0x602180:    0x0000000000000000    0x0000000000000000
0x602190:    0x0000000000000000    0x0000000000000000
ba0bab➤  

fake fp구조체에서 0x602098위치 (구조체에서 _cur_column)에 0을 가르키고 있는 주소로 세팅해주고

0x6020e8위치(_IO_file_jumps)에 fake vtable의 주소를 세팅해준다.

그리고 이제 fake vtable(== fake _IO_file_jumps )에서 원하는 부분을 원하는 걸로 바꿔주면 된다.

예를 들어서, 만약 _IO_file_jumps 구조체 에서 __GI__IO_file_close를 원샷이나 system으로 덮고 fclose(fp)를 콜하면 실행 될 것이다!

Reference

문서 좋은 것 같다! 더 공부해봐야겠다.

'pwnable > 정리' 카테고리의 다른 글

_IO_FILE Arbitrary Read & Write  (0) 2020.03.09
glibc exit.c analysis  (0) 2019.11.12
64bit fsb 정리  (0) 2019.11.06
libc_start_main leak  (0) 2018.09.13
kail tool pattern_create / offset  (0) 2018.09.06
Comments