드림핵 로드맵을 진행하면서 이해하는데 어려운부분이 있어 공부하며 정리하는 글로
아래 자료를 참고하여 정리하였습니다.
(공부중인 내용이므로 다르거나 틀린 내용이 있을 시 댓글로 알려주시기 바랍니다.)
https://dreamhack.io/lecture/courses/271
Background: _IO_FILE
이번 강의에서는 _IO_FILE 구조에 대해 소개합니다.
dreamhack.io
_IO_FILE
_IO_FILE이란 무엇인가?
기존 C언어를 공부했을때나 pwn(포너블) 공부를 위해 여러문제를 접했을때
아래 예시 코드와 같이 파일을 오픈해 읽거나 쓰는 fopen(), fwrite()함수를 많이 보았습니다.
// gcc -o file2 file2.c
#include <stdio.h>
int main()
{
char file_data[256];
int ret;
FILE *fp;
strcpy(file_data, "AAAA");
fp = fopen("testfile","r");
fread(file_data, 1, 256, fp);
printf("%s",file_data);
fclose(fp);
}
여기서 변수로 사용되는 *fp를 선언하는 FILE은 정확히 무엇인가에 관한 내용이
바로 _IO_FILE에 대한 답입니다.
현재 코드에서는 FILE 구조체는 fopen함수를 통해 파일스트림을 생성하고 변수에 저장하고 있습니다.
FILE이 무엇인지 모르니 FILE을 정의하는 코드를 보면 다음과 정의되어있습니다.
/* The opaque type of streams. This is the definition used elsewhere. */
typedef struct _IO_FILE FILE;
_IO_FILE이 FILE이라고 말입니다.
이는 리눅스 시스템에서 확인한 것이며 리눅스 시스템의 표준 라이브러리에서는
파일 스트림을 나타내기 위해 사용하는 구조체임을 알수 있습니다.
그러면 여기서 말하는 _IO_FILE의 구조체를 분석해본다면 무슨역할을 하는지와 어떤구조로 이루어져
파일을 열고 쓰는지 작업이 어떻게 이루어지는지 이해할 수 있을것입니다.
(해당 분석은 드림핵자료에 따라 Glibc 2.27버전을 이용해 분석하였습니다)
_IO_FILE 구조체
다음 코드는 _IO_FILE과 _IO_FILE_Plus 구조체 입니다.
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
struct _IO_FILE
{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */
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;
int _flags2;
__off_t _old_offset; /* This used to be _offset but it's too small. */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
구조체에서 선언되는 _IO_read_ptr 등 여러포인터가 선언되어 있는것과
해당 포인터의 역할이 간단하게 주석으로 정의되어 있습니다.
여기서 중요 멤버만 표로 정리해보면 다음과 같습니다.
멤버 변수 | 설명 |
_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 | 파일 관련 작업을 수행하는 가상 함수 테이블입니다. |
위 구조체를 통해 다음과 같은 멤버 변수의 역할로 파일 읽기, 쓰기등을 수행할수 있다고 합니다.
그런데 _flags에 읽기, 쓰기, 추가 권한이 있고 이를 하위2바이트 비트 플래그로 표현한다고 합니다.
_flags에 대해 설정된 비트를 glibc 버전별 정리 사이트에서 열어보면 다음과 같습니다.
해당 비트값을 정확히 이해하기 위해
예를 들어보면 __flags의 값이 0xFBAD2c84 라면
0xFBAD0000 으로 _IO_MAGIC 매직넘버가 포함되고
0x2000 으로 _IO_IS_FILEBUF 권한이,
0xc00 는 _IO_TIED_PUT_GET 과 IO_CURRENTLY_PUTTING를 더한 권한,
0x80 은 _IO_LINKED 권한,
0x4 는 _IO_NO_READS 권한을 가지게 되는 것입니다.
_IO_new_file_fopen()
_flags를 통해 FILE, 즉 _IO_FILE이 어떠한 권한을 가지고 어떠한 비트값으로 처리되는지 알수 있었습니다.
구조에 대해서는 알게되었으니 흐름을 통해 어떻게 해당 _IO_FILE에 값이 전달되는지 분석해보기 위해
FILE인 *fp에 fopen()함수나 fwrite()를 사용하는 경우를 보면
fp = fopen("testfile","r");
권한을 명시하고 해당파일을 명시합니다.
이를 디버깅해보면 fopen함수는 내부적으로 _fopen_internal 을
_fopen_internal은 _IO_new_file_fopen을 호출하는 것을 알수 있습니다.
( fopen() -> _fopen_internal() -> _IO_new_file_fopen() )
(_fopen_internal 은 _IO_FILE_Plus와 함께 다음에 정리하겠습니다)
처리되는 _IO_new_file_fopen() 코드를 보면 다음과 같습니다.
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_new_file_fopen는 인자값으로 FILE포인터와 파일이름에 대한 포인터, 권한을 명시하는 모드가 있고
코드를 보면 fopen에서 인자로 넘긴 r w a 를 switch로 확인해 해당하는 구문을 실행하는데
oflags와 omode를 설정해주고 있습니다.
실행되는 구문을 보면 O_RDONLY, _IO_NO_WRITES, _IO_NO_READS 를 지정하고 있습니다.
해당 값들은 위에서 봤던 _flags에 해당하는 값들이며
이러한 값들을 지정한뒤 open 시스템콜을 호출해 파일을 열고 인자로 전달되는 것을 확인 할 수 있습니다.
여기까지가 IO_FILE -> IO_new_file_fopen 이었습니다.
다음은 fopen으로 파일스트림을 생성하는 경우로 _IO_FILE_plus 반환하게 되는데
이는 pwn(포너블)분야의 _IO_FILE vtable Overwrite와 연결되어 있어
해당 공격기법을 정리하는 Pwnable_Study 항목 글에서 정리하도록 하겠습니다.
Reference
'Reference > Linux' 카테고리의 다른 글
Linux tmux 사용법 (0) | 2023.02.09 |
---|