_IO_FILE관련 내용 정리 겸 작성합니다.
이해가 완벽히 되지는 않아서
틀린 점이나 모호하게 쓰는 점 이해 부탁드립니다. 혹시라도 잘못된 점 있으면 댓글 달아주세요!!
추가로 해당글은 Dreamhack강의를 기반으로 작성하였습니다.
_IO_FILE
리눅스 시스템 표준 라이브러리에서 파일 스트림을 나타내기 위한 구조체.
fopen(),fclose(),fwrite()...와 같은 함수들에 대해서 적용된다.
해당 구조체는 아래와 같으며 /libio/bits/types/struct_FILE.h에 정의되어 있다.
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
해당 설명은 /usr/include/libio.h에 명시되어있다.
/* Magic numbers and bits for the _flags field.
The magic numbers use the high-order bits of _flags;
the remaining bits are available for variable flags.
Note: The magic numbers must all be negative if stdio
emulation is desired. */
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
#define _IO_BAD_SEEN 0x4000
#define _IO_USER_LOCK 0x8000
flags값은 기본적으로 Magic number인 0xFBAD0000으로 설정이 되며,
나머지는 하위 2바이트는 비트 플래그들로 값을 조합을하여 설정된다.
_IO_read_ptr
파일 읽기 버퍼에 대한 포인터
_IO_read_end
파일 읽기 버퍼의 끝을 명시하는 포인터
_IO_read_base
파일 읽기 버퍼의 시작을 명시하는 포인터
_chain
_IO_FILE구조체는 _chain을 통해서 링크드 리스트로 만들고 이를 _IO_list_all에 저장시킴
_fileno
파일 디스크립터
_IO_FILE - vtable
실제 파일 스트림을 열때는 _IO_FILE이 아닌 _IO_FILE_plus구조체가 리턴된다.
이는 파일스트림에서의 함수 호출을 용이하게 하기 위해서 vtable을 추가로 넣어서 만든 구조체이다.
struct _IO_FILE_plus
{
FILE file; // typedef struct _IO_FILE FILE; (glibc/libio/bits/types/FILE.h)
const struct _IO_jump_t *vtable;
};
FILE은 _IO_FILE 구조체 이므로 넘어가고
새롭게 _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);
};
_IO_jump_t를 확인해보면 fread,fwrite,fopen,write,read...들과 같은 여러가지 함수 포인터들이 존재한다.
여기서 fread,fwrite는 없는데?? 할수도 있지만!
size_t
_IO_fwrite (const void *buf, size_t size, size_t count, FILE *fp)
{
size_t request = size * count;
size_t written = 0;
CHECK_FILE (fp, 0);
if (request == 0)
return 0;
_IO_acquire_lock (fp);
if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
written = _IO_sputn (fp, (const char *) buf, request);
_IO_release_lock (fp);
.
.
.
#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)
_IO_XSPUTN은 vtable 내부에 있는 함수 포인터다.
이와 같이 vtable 내부의 함수 포인터들을 이용해서 함수를 호출하는 것으로 나타난다.
그러면 여기서 생각해볼 수 있는 것이
vtable을 overwrite하면 원하는 함수를 호출할 수 있지 않을까?
실제로 vtable을 덮으면 원하는 함수 호출이 가능하다.
(glibc 2.24이상 버전부터 _IO_validate_vtable()이 생겨서 안됩니다!)
Dreamhack에 있는 코드로 실제로 메모리 확인을 해보면서 진행해보겠습니다!
(https://learn.dreamhack.io/11#47)
실제로 _IO_FILE 구조체를 본 것과 메모리에 할당되어있는 값들의 offset이 조금씩 다른 것 같아서
(제가 모르는 것 일수도 있슴다)
실제로 메모리 확인을 통해서 어느 부분이 어디를 가리키는지 확인했습니다.
조작되기 이전의 메모리 상태입니다.
현재 fp는 0x602010을 가리키고 있는 상태입니다.
0x602010을 확인 해보면 _IO_FILE 구조체가 있는 것을 확인 할 수가 있습니다.
그중에서 0x602e8주소를 보면 vtable주소가 들어있습니다.
이를 조작하기 위해서 fp변수에 넣어줄 값을
name변수에 _IO_FILE 구조체에 맞게 값을 작성해주고
vtable주소에 원하는 함수가 들어있는 주소로 할당이 되게 값을 조작해준다.
임의로 *vtable 값에 junk값을 넣어서 확인하면 아래와 같은 부분에서 오류가 뜨는 것을 확인 할 수가 있다.
..vtable 값을 넘겨줄때는 rax+0x40된 부분에 원하는 함수 주소를 적어주어야 한다는 것이다.
Exploit code (https://learn.dreamhack.io/11#49)를 확인하면 익스코드가 존재한다..
.
.
.
.
.
Glibc 2.24 버전 이후의 방법은 다음 글에서 설명하겠습니다.
'SYSTEM HACKING > 기법' 카테고리의 다른 글
Exploit using _rtld_global & exit() (0) | 2022.05.28 |
---|---|
House of Force? (0) | 2022.01.25 |
Unsafe unlink in glibc 2.23, 2.27 and over 2.27 (0) | 2022.01.22 |
Poison Null Byte in glibc 2.27 (0) | 2022.01.16 |
Fastbin double free in Glibc 2.3.x (0) | 2022.01.01 |