what is _IO_FILE & _IO_FILE_plus

항상 헷갈려했던 FILE에 대한 정의, 이번에 한번 재대로 해보고 가자는 의미해서 정리해보았다.

우리가 항상 보는 stdout, stdin , stderr. 얘가 뭐고 어디에 존재하냐고 묻는다면

바로 대답이 나와야한다. 얘내는 libc의 존재하는 전역변수들이며, _IO_FILE_plus 구조체 라는 것을

물론 잘 모를 수도 있다. 그럼 지금 부터 시작해보자

C
pwndbg> p stdout
$8 = (struct _IO_FILE *) 0x7ffff7dce760 <_IO_2_1_stdout_>
pwndbg> vmmap 0x7ffff7dce760
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
             Start                End Perm     Size Offset File
    0x7ffff7dcd000     0x7ffff7dcf000 rw-p     2000 1eb000 /home/iofile_aaw/libc.so.6 +0x1760
pwndbg> 

우리가 gdb 에서 stdout 을 출력하면 struct _IO_FILE * 형태의 glibc의 존재하는 부분이 나온다.

이는 glibc에서 _IO_FILE 구조체로 존재하는 _IO_2_1_stdout_ 라는 전역변수가 우리가 아는 stdout 의 원래이름이기 때문이다.

더 자세히 알아보자면

C
pwndbg> p *(struct _IO_FILE_plus *) 0x7ffff7dce760
$10 = {
  file = {
    _flags = -72537977, 
    _IO_read_ptr = 0x7ffff7dce7e3 <_IO_2_1_stdout_+131> "", 
    _IO_read_end = 0x7ffff7dce7e3 <_IO_2_1_stdout_+131> "", 
    _IO_read_base = 0x7ffff7dce7e3 <_IO_2_1_stdout_+131> "", 
    _IO_write_base = 0x7ffff7dce7e3 <_IO_2_1_stdout_+131> "", 
    _IO_write_ptr = 0x7ffff7dce7e3 <_IO_2_1_stdout_+131> "", 
    _IO_write_end = 0x7ffff7dce7e3 <_IO_2_1_stdout_+131> "", 
    _IO_buf_base = 0x7ffff7dce7e3 <_IO_2_1_stdout_+131> "", 
    _IO_buf_end = 0x7ffff7dce7e4 <_IO_2_1_stdout_+132> "", 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x7ffff7dcda00 <_IO_2_1_stdin_>, 
    _fileno = 1, 
    _flags2 = 0, 
    _old_offset = -1, 
    _cur_column = 0, 
    _vtable_offset = 0 '\000', 
    _shortbuf = "", 
    _lock = 0x7ffff7dcf8c0 <_IO_stdfile_1_lock>, 
    _offset = -1, 
    _codecvt = 0x0, 
    _wide_data = 0x7ffff7dcd8c0 <_IO_wide_data_1>, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0, 
    _mode = -1, 
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7ffff7dca2a0 <_IO_file_jumps>
}

_IO_FILE_plus 구조체로 되어 있다. _IO_FILE + 아래에 vtable이 추가된.

C
struct _IO_FILE_plus {
    struct _IO_FILE file;
    const struct _IO_jump_t *vtable;  // 여기가 바로 vtable
};

이런 형태이다. 즉, 우리가 아는 모든 file들은 _IO_FILE_plus 구조체로 되어 있다. 그중에서도 이미 저명한 stdout, stdin , stderr 얘내들은 이미 유명한 glibc에서 사용하는 얘들이다.

즉, 우리가

C
	FILE *fp;
	fp = fopen("/etc/passwd", "r");

이런식으로 파일을 열면 내부에서는 _IO_FILE_plus 구조체가 열린다. 하지만, 우리가

p stdout 했을때 _IO_FILE 이 나오는 이유는 _IO_FILE_plus 구조체의 시작이 _IO_FILE 구조체의 시작과 같기 때문에 gdb 상 그렇게 보일뿐 모든 file 형태는 _IO_FILE_plus 구조체로 되어 있다.

C
__fopen_internal (const char *filename, const char *mode, int is32)
{
  struct locked_FILE
  {
    struct _IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO

...

실제 glibc의 fopen 코드에서도 struct _IO_FILE_plus fp; 을 사용하는 것을 볼 수 있다.

그럼 vtable 이 뭐냐?