CIDY
[System_Hacking] AD: stage6__IO_FILE 본문
파일을 열 때는 접근 유형을 명시해야 함 -> 읽기모드, 쓰기모드 등... -> 파일 관련 함수들은 fopen이 반환한 파일 포인터를 토대로 파일 정보를 확인한다. -> 파일 작업 이해를 위해서는 파일 구조체 이해가 선행되어야 함.
*_IO_FILE
리눅스 시스템 표준 라이브러리에서 파일 스트림을 나타내기 위한 구조체이다. -> fopen함수 사용 시 힙 영역에 할당된다.
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
};
↑이렇게 생겼는데,
_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 -> 파일 관련 작업을 수행하는 가상 함수 테이블
-flag : 파일의 성질을 나타내는 필드이다. fopen함수로 파일 열 때 전달한 모드에 따라 값이 설정된다.
#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
위는 _flags변수를 구성하는 비트들의 집합이다.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void file_info(FILE *buf) {
printf("_flags: %x\n", buf->_flags);
printf("_fileno: %d", buf->_fileno);
}
int main() {
FILE *fp;
char buf[256];
strcpy(buf, "THIS IS TESTFILE!");
fp = fopen("testfile", "w");
fwrite(buf, 1, strlen(buf), fp);
file_info(fp);
fclose(fp);
return 0;
}
이 코드를 컴파일해서 실행시켜보면,
이 출력값은 _IO_MAGIC이라는 매직넘버를 포함한 각 권한을 나타낸다.
이 값은 _IO_MAGIC(0xfbad0000), _IO_IS_FILEBUF(0x2000), _IO_TIED_PUT_GET(0x400), _IO_CURRENTLY_PUTTING(0x800), _IO_LINKED(0x80), _IO_NO_READS(0x4) 비트가 합해진 값이다.
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;
...
}
이 함수는 fopen함수가 호출될 때 실행되는 내부 함수인 _IO_new_file_fopen이다. fopen의 두 번째 인자로 들어가는 *mode가 r, w, a중 어느 것인지를 확인 후 그에 맞는 케이스문을 실행해 각 권한에 해당하는 비트가 할당된다.
-vtable
아까 위에 _IO_FILE구조체 코드 위에 이 구조체를 포함하고 있는 _IO_FILE_plus구조체가 있었다. 그리고 그 안에 vtable이라는 포인터도 하나 있었는데.. 이건 객체 지향 프로그래밍 언어에서 클래스를 정의하고 가상 함수를 사용할 때 할당되는 테이블이다.
메모리에 가상 함수를 담을 영역을 할당하고, 함수의 주소를 기록한다. 가상 함수를 사용하면 해당 테이블을 기준으로 상대 주소를 통해 호출한다.
파일 구조체는 각 파일마다 _IO_FILE구조체와 함수 테이블을 갖고 있는데, 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);
};
이게 또 뭐냐면 파일 함수의 테이블을 정의한 _IO_jump_t 구조체이다. 더미랑.. 다양한 함수가 정의되어 있다.
gdb-peda$ p *(struct _IO_jump_t *)0x00007ffff7dca2a0
$32 = {
__dummy = 0x0,
__dummy2 = 0x0,
__finish = 0x7ffff7a6e400 <_IO_new_file_finish>,
__overflow = 0x7ffff7a6f3d0 <_IO_new_file_overflow>,
__underflow = 0x7ffff7a6f0f0 <_IO_new_file_underflow>,
__uflow = 0x7ffff7a70490 <__GI__IO_default_uflow>,
__pbackfail = 0x7ffff7a71d20 <__GI__IO_default_pbackfail>,
__xsputn = 0x7ffff7a6da00 <_IO_new_file_xsputn>,
__xsgetn = 0x7ffff7a6d660 <__GI__IO_file_xsgetn>,
__seekoff = 0x7ffff7a6cc60 <_IO_new_file_seekoff>,
__seekpos = 0x7ffff7a70a60 <_IO_default_seekpos>,
__setbuf = 0x7ffff7a6c920 <_IO_new_file_setbuf>,
__sync = 0x7ffff7a6c7a0 <_IO_new_file_sync>,
__doallocate = 0x7ffff7a601e0 <__GI__IO_file_doallocate>,
__read = 0x7ffff7a6d9e0 <__GI__IO_file_read>,
__write = 0x7ffff7a6d260 <_IO_new_file_write>,
__seek = 0x7ffff7a6c9e0 <__GI__IO_file_seek>,
__close = 0x7ffff7a6c910 <__GI__IO_file_close>,
__stat = 0x7ffff7a6d250 <__GI__IO_file_stat>,
__showmanyc = 0x7ffff7a71ea0 <_IO_default_showmanyc>,
__imbue = 0x7ffff7a71eb0 <_IO_default_imbue>
}
gdb로 까 보면 이렇게 생겼다.
#define fread(p, m, n, s) _IO_fread (p, m, n, s)
size_t
_IO_fread (void *buf, size_t size, size_t count, FILE *fp)
{
size_t bytes_requested = size * count;
size_t bytes_read;
CHECK_FILE (fp, 0);
if (bytes_requested == 0)
return 0;
_IO_acquire_lock (fp);
bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
_IO_release_lock (fp);
return bytes_requested == bytes_read ? count : bytes_read / size;
}
이건 fread함수의 코드이다. fread는 _IO_fread와 같다고 정의돼 있는데, 그 함수 내부를 보면 _IO_sgetn함수를 호출한다.
#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
#define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable ( (THIS)))
size_t
_IO_sgetn (FILE *fp, void *data, size_t n)
{
/* FIXME handle putback buffer here! */
return _IO_XSGETN (fp, data, n);
}
이게 그 _IO_sgetn함수의 구현체이다. 여기서 또 _IO_XSGETN을 호출하는데, 위에 매크로를 따라가보면 결국 vtable을 참조한다.
fread를 포함한 모든 파일 관련 함수는 이렇게 가상 함수 테이블을 참조해 파일 작업을 시도한다. -> 이 함수 테이블은 동적 할당 영역에 존재 -> 쓰기 권한 있음 -> 악용 가능
'Hack > DreamHack(로드맵)' 카테고리의 다른 글
[System_Hacking] AD: stage6_문제풀이(_IO_FILE Arbitrary Address Write) (0) | 2022.07.15 |
---|---|
[System_Hacking] AD: stage6_문제풀이(_IO_FILE Arbitrary Address Read) (0) | 2022.07.14 |
[System_Hacking] AD: stage5_문제풀이(send_sig) (0) | 2022.07.14 |
[System_Hacking] AD: stage5_문제풀이(SigReturn-Oriented Programming) (0) | 2022.07.14 |
[System_Hacking] AD: stage5_SigReturn Oriented Programming (0) | 2022.07.14 |