_IO_FILE
_IO_FILE이란, fopen 등의 함수를 통해서 반환하는 함수 포인터가 가리키는 구조체를 말한다.
예를 들어 우리는 파일을 열 때 아래와 같은 코드를 사용했다.FILE *fp=fopen("./test.txt",'r')
이때 fopen을 통해 반환되는 포인터가 _IO_FILE 구조체 포인터가 되는 것이다.
/* The opaque type of streams. This is the definition used elsewhere. */
typedef struct _IO_FILE FILE;
_IO_FILE 구조체가 FILE로 쓰이는 것을 알 수 있다.
_IO_FILE이란 구조체는 파일의 정보를 담고 있는 구조체이다. fopen을 통해서 파일을 여므로 이때 파일의 정보를 담고 있는 구조체가 생성되는 것이다. _IO_FILE 구조체는 heap 영역에 할당된다.
위를 보면 rax값이 heap에 할당된 것을 볼 수 있다.
그렇다면 해당 구조체가 어떤 정보를 가지고 있는지 살펴보자.
구조체 정의
아래는 _IO_FILE 구조체의 정의이다.
struct _IO_FILE
{
int _flags; /* (4bytes) High-order word is _IO_MAGIC; rest is flags. */
//align때문에 flag뒤에 4yte는 더미로 저장되는듯
/* The following pointers correspond to the C++ streambuf protocol. */
//8*8=64bytes
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. */
//8*5=40bytes
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; /* Pointer00 to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; //4bytes
int _flags2; //4bytes
__off_t _old_offset; /* (8bytes) This used to be _offset but it's too small. */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column; //2bytes
signed char _vtable_offset; //1byte
char _shortbuf[1]; //1byte
_IO_lock_t *_lock; //8bytes
//그리고 보통 dummy로 0x0000000000 * 9 있음
#ifdef _IO_USE_OLD_IO_FILE
};
- 맨 위에 있는 flag는 파일의 읽기, 쓰기, 추가 등의 권한을 나타낸다.
- 0xfbad0000가 매직 넘버로 쓰이고 나머지 비트가 플래그로 사용된다
- 밑에 있는 8개의 포인터 파일의 읽기 혹은 쓰기 버퍼 주소 등을 나타낸다.
- _IO_FILE은 _chain 필드를 이용해 singly linked list를 구성한다
- 해당 리스트의 해더는 라이브러리 전역변수 _IO_list_all에 저장된다.
- _fileno는 파일 디스크립터의 값을 나타낸다.
멤버 | 변수 설명 |
_flags | 파일에 대한 읽기/쓰기/추가 권한을 의미합니다. 0xfbad0000 값을 매직 값으로, 하위 2바이트는 비트 플래그로 사용됩니다. |
_IO_read_ptr | 파일 읽기 버퍼에 대한 포인터입니다. |
_IO_read_end | 파일 읽기 버퍼 주소의 끝을 가리키는 포인터입니다. |
_IO_read_base | 파일 읽기 버퍼 주소의 시작을 가리키는 포인터입니다. |
_IO_write_base | 파일 쓰기 버퍼 주소의 시작을 가리키는 포인터입니다. |
_IO_write_ptr | 쓰기 버퍼에 대한 포인터입니다. |
_IO_write_end | 파일 쓰기 버퍼 주소의 끝을 가리키는 포인터입니다. |
_chain | 프로세스의 _IO_FILE 구조체는 _chain 필드를 통해 링크드 리스트를 만듭니다. 링크드 리스트의 헤더는 라이브러리의 전역 변수인 _IO_list_all에 저장됩니다. |
_fileno | 파일 디스크립터의 값입니다. |
_IO_jump_ t *vtable | 파일 관련 작업을 수행하는 가상 함수 테이블입니다. //얘는 조금 더 뒤에 다루겠다. |
이러한 입력/출력 버퍼들은 domallocbuf라는 함수에 의해 초기화 및 할당이 되는 것 같다
그리고 _IO_setg등의 함수를 통해 write ptr이런애들을 초기화하는 것 같다. (아래 gpt 설명 참고)
그리고 fwrite 등을 하는 경우 일단 데이터에서 write buf로 복사한다음 write buf에서 file로 쓰는 것 같다(?)
_IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base); 얘는 뭘 하는거야
_IO_setg는 C 표준 라이브러리 내부에서 사용되는 함수로서, 파일 스트림(_IO_FILE 구조체)의 입력 버퍼를 설정하는 역할을 합니다.
입력 버퍼는 파일로부터 데이터를 읽어오는데 사용되는 버퍼로, 파일 스트림의 내부에서 관리됩니다. _IO_setg 함수는 해당 입력 버퍼를 설정하기 위해 사용되며, 파일 스트림의 gptr (get pointer), egptr (end get pointer), base (buffer base)를 초기화합니다.
다음은 _IO_setg 함수의 세 가지 인자와 역할에 대한 설명입니다:
- f: _IO_FILE 구조체를 가리키는 포인터입니다. 파일 스트림의 입력 버퍼와 관련된 정보를 설정하기 위해 사용됩니다.
- f->_IO_buf_base: 입력 버퍼의 시작 주소를 나타냅니다. gptr과 base가 같은 값을 가리키도록 합니다.
- f->_IO_buf_base: 입력 버퍼의 끝 주소를 나타냅니다. egptr가 base와 같은 값을 가리키도록 합니다.
- f->_IO_buf_base: 입력 버퍼의 현재 위치를 나타냅니다. gptr이 base와 같은 값을 가리키도록 합니다.
이렇게 _IO_setg 함수를 호출하여 파일 스트림의 입력 버퍼를 설정하면, 파일로부터 데이터를 읽어오는 작업에 사용할 수 있게 됩니다. 이 함수는 주로 C 표준 라이브러리의 내부 구현에 사용되며, 일반적으로 사용자가 직접 호출하거나 다루는 일은 없습니다. 사용자는 보통 fopen, fread, getc 등의 C 표준 라이브러리 함수를 사용하여 파일로부터 데이터를 읽어오는데 사용됩니다.
아래처럼 FILE의 구조체를 확인해 보았다.
flags
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 0x0001 /* Don't deallocate buffer on close. */
#define _IO_UNBUFFERED 0x0002
#define _IO_NO_READS 0x0004 /* Reading not allowed. */
#define _IO_NO_WRITES 0x0008 /* Writing not allowed. */
#define _IO_EOF_SEEN 0x0010
#define _IO_ERR_SEEN 0x0020
#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close. */
#define _IO_LINKED 0x0080 /* In the list of all open files. */
#define _IO_IN_BACKUP 0x0100
#define _IO_LINE_BUF 0x0200 //라인 버퍼링인지(개행문자 기준인지)
#define _IO_TIED_PUT_GET 0x0400 /* Put and get pointer move in unison. */
#define _IO_CURRENTLY_PUTTING 0x0800 //현재 쓰기 작업 중인지
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
/* 0x4000 No longer used, reserved for compat. */
#define _IO_USER_LOCK 0x8000
위를 보면 막 _IO_NO_READS, _IO_NO_WRITES 등의 값이 있는 것을 볼 수 있다.
이처럼 flag은 파일의 읽기, 쓰기, 수정 권한 등이 어떻게 설정되어있는지를 나타낸다.
0xfbad0000은 위에서 언급했듯 매직넘버이다. 따라서 뒤 두 바이트만 플래그로 설정되는 것이다.
예를 들어 flag가 0xfbad2c84라면
0xfbad2c84=0xfbad000+
+0x2000 (_IO_IS_FILEBUF)
+0x0800(_IO_USER_LOCK)+0x0400(_IO_TIRED_PUT_GET)
+0x80(_IO_LINKED)
+0x4(_IO_NO_READS)
이다.
즉, _IO_MAGIC, _IO_NO_READS, _IO_LINKED, _IO_TIED_PUT_GET, _IO_CURRENTLY_PUTTING, _IO_IS_FILEBUF 비트가 포함된 것을 알 수 있다.
이 플래그들은 fopen을 실행하는 과정에서 설정된다.
fopen 은 내부적으로 __fopen_internal을 호출하게되며 __fopen_internal에서 _IO_new_file_fopen을 호출하는 것을 볼 수 있다. _IO_new_file_fopen은 여기서 사용자가 입력한 모드('r', 'w' 등)에 따라 위에 나온 플래그 _IO_NO_WRITES 등을 설정해준다. (fopen -> __fopen_internal -> _IO_new_file_fopen 순으로 호출)
아래 코드를 보면 mode가 'r'인지 'w'인지 'a' 인지 등에 따라서 read_write 변수의 값을 위에 나온 플래그로 설정해주고 있다.
FILE *_IO_new_file_fopen(FILE *fp, const char *filename, const char *mode,
int is32not64) {
int oflags = 0, omode;
int read_write;
int oprot = 0666;
int i;
FILE *result;
const char *cs;
const char *last_recognized;
if (_IO_file_is_open(fp)) return 0;
switch (*mode) {
case 'r':
omode = O_RDONLY;
read_write = _IO_NO_WRITES;
break;
case 'w':
omode = O_WRONLY;
oflags = O_CREAT | O_TRUNC;
read_write = _IO_NO_READS;
break;
case 'a':
omode = O_WRONLY;
oflags = O_CREAT | O_APPEND;
read_write = _IO_NO_READS | _IO_IS_APPENDING;
break;
...
}
아래는 _IO_FILE 구조체의 예시인 stdin과 stdout의 메모리 구조이다.
예제
위에서 배운 내용을 복습하기 위해 간단한 코드를 작성하였다.
"./tmp.txt"의 내용을 buf에 저장한 후 화면에 출력하는 예제이다.
"./tmp.txt"에는 "hi my name is haeun" 이라는 텍스트가 저장되어 있다.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(){
FILE * fp=fopen("./tmp.txt","r");
char buf[256]={0};
fread(buf,1,sizeof(buf),fp);
fclose(fp);
printf("%s",buf);
return 0;
}
fopen 끝난 후에 fp의 메모리를 확인해 보았다.
flag가 0xfbad2488로 설정되어있고 아직 나머지 IO관련 포인터는 설정되지 않았다.
빨간 표시 전까지가 FILE 구조체이다.참고로 빨간 표시에 나타난 0x7ff~5e0은 vtable의 주소인데 후에 설명하겠다.
fread 후에 fp의 메모리를 다시 확인해 보았다.
0x555555559480이라는 값이 8번 적혀있다. 적힌 위치는 위에서 보았던 포인터들이다.
0x555555559480에는 무슨 값이 적혀있을까?
아직 아무 값도 안 적혀있다. 읽기 혹은 쓰기 작업을 하면 해당 위치에 데이터가 적힐 것이다.
_IO_FILE_plus
한편 fopen을 통해 생성되는 값은 사실 _IO_FILE이 아니라 _IO_FILE_plus이다.
엥 !?? 뜬금없을 수 있긴 한데 암튼 그렇다...
/* 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
{
FILE file;
const struct _IO_jump_t *vtable;
};
_IO_FILE_plus는 _IO_FILE에다가 vtable이라는 가상함수 테이블을 추가한 것이다.
fopen, fread 등 파일 관련 표준 함수를 호출하면 이 함수들은 vtable안에있는 함수들을 참조하여 호출한다.
이처럼 _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);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};
한편, vtable안의 값을 조작할 수 있다면 우리가 원하는 함수가 실행되게끔 할 수 있다. 이에 관련해서는 다른 포스트에서 다루도록 하겠다!
아래 __fopen_internal은 fopen의 internal implementation이다.
__fopen_internal 함수는 malloc으로 locked_FILE을 할당하고 거기에서 FILE을 반환한다.
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));
//malloc으로 locked_FILE을 할당하고 해당 주소를 new_f에 넣음
if (new_f == NULL)
return NULL;
#ifdef _IO_MTSAFE_IO
new_f->fp.file._lock = &new_f->lock;
#endif
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
_IO_new_file_init_internal (&new_f->fp);
if (_IO_file_fopen ((FILE *) new_f, filename, mode, is32) != NULL)
return __fopen_maybe_mmap (&new_f->fp.file);
//제대로 실행이되면 위처럼 new_f가 가리키는 fp(_IO_FILE_plus)의 file(_IO_FILE_plus.FILE)을 반환
_IO_un_link (&new_f->fp);
free (new_f);
return NULL;
}
(1) Allocate a locked_file. (locked_file 반환)
(2) Invoke function _IO_new_file_init_internal. In this function, the newly allocated fp will be inserted into the singly linked list. (_IO_new_file_init_internal에서 singly linked list에 fp 삽입)
(3) Invoke syscall fopen to get a file descriptor of the target file and assign the file descriptor number to the fp->fileno.
(fopen을 통해 target file의 file descriptor 가져오고 걔를 fp->fileno에 넣음)
참고로 FILE 구조체를 조작할 때 pwntools에서 이런것도 지원해준다고 한다.. 처음 알음 ㅠㅠ
https://docs.pwntools.com/en/stable/filepointer.html
pwnlib.filepointer — FILE* structure exploitation — pwntools 4.10.0 documentation
© Copyright 2016, Gallopsled et al. Revision a0c9da07.
docs.pwntools.com
References
https://learn.dreamhack.io/11#41
https://aidencom.tistory.com/187
https://skysquirrel.tistory.com/268
https://learn.dreamhack.io/271#5
https://dangokyo.me/2018/01/01/advanced-heap-exploitation-file-stream-oriented-programming/
'Linux Exploitation > FSOP' 카테고리의 다른 글
[FSOP] _IO_FILE vtable overwrite (~Ubuntu 16.04) (0) | 2023.08.05 |
---|---|
[드림핵(Dreamhack)] _IO_FILE Arbitrary Address Write (0) | 2023.08.03 |
[FSOP] fread 분석 (0) | 2023.08.03 |
[FSOP] _IO_FILE Arbitrary Address Read (0) | 2023.08.03 |
[FSOP] fwrite 분석 (0) | 2023.07.29 |