자꾸 헷갈려서 간단하게 요약해본다.
추가로 확인되는 사항이 있으면 지속적으로 update 할 예정이다.
_IO_FILE_plus
많은 함수들이 단독으로 존재하는 것 같지만 사실은 _IO_FILE_plus 라는 구조체의 형식으로 존재하는 것들이 많다.
파일을 열고 닫는 함수들이 대부분 해당되며, stdin, stdout, stderr 등도 이 형식을 띄고 있다.
_IO_FILE_plus 는
_IO_FILE 구조체와
vtable 구조체의 집합이다.
_IO_FILE_plus 구조체의 모양은 아래와 같다.
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
_IO_FILE 구조체
_IO_FILE 구조체의 형식은 아래와 같다.
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_file 구조체의 각 offset은 아래와 같다.
0x0 _flags
0x8 _IO_read_ptr
0x10 _IO_read_end
0x18 _IO_read_base
0x20 _IO_write_base
0x28 _IO_write_ptr
0x30 _IO_write_end
0x38 _IO_buf_base
0x40 _IO_buf_end
0x48 _IO_save_base
0x50 _IO_backup_base
0x58 _IO_save_end
0x60 _markers
0x68 _chain
0x70 _fileno
0x74 _flags2
0x78 _old_offset
0x80 _cur_column
0x82 _vtable_offset
0x83 _shortbuf
0x88 _lock
0x90 _offset
0x98 _codecvt
0xa0 _wide_data
0xa8 _freeres_list
0xb0 _freeres_buf
0xb8 __pad5
0xc0 _mode
0xc4 _unused2
0xd8 vtable
구조체 내에 주요 변수들은 아래와 같다.
_flags
이는 파일의 권한을 압축해서 알려준다.
자세한 사항은 아래 링크의 _flags 참조.
https://wyv3rn.tistory.com/110
_read (임의의 주소에 데이터 입력 시 사용)
기본사항
fread 함수의 경우 결국 read 함수를 참조하여 동작하는데,
read 함수의 구조는
read(f->_fileno, _IO_buf_base, _IO_buf_end - _IO_buf_base);
이며, 각 인자는 아래와 같이 사용 가능하다.
fileno : 읽어들일 방식, 보통 표준 입력하면 되기에 0을 사용
_IO_buf_base : 입력을 시작할 주소
_IO_buf_end : 입력을 종료할 주소
유의사항
- _flag 값은 0xfbad2488 이어야 한다.
- 만약 100 byte로 선언된 buffer 변수에 overwrite 하기 위해서는
_IO_buf_ end > _IO_buf_base + 100
이어야 한다. - 삽입되는 값의 크기는
data = _IO_buf_end - _IO_buf_base
이어야 한다.
_write(임의 위치의 값을 출력할때 사용)
기본사항
fwrite 함수 또한 결국 write 함수를 참조하여 동작하는데,
write 함수의 구조는
read(f->_fileno, _IO_buf_base, _IO_buf_end - _IO_buf_base);
이며, 각 인자는 아래와 같이 사용 가능하다.
fileno : 출력 방식, 보통 표준 출력하면 되기에 1을 사용
_IO_buf_base : 출력을 시작할 주소
_IO_buf_end : 출력을 종료할 주소
유의사항
- _flags의 값은 0xfbad1000 이어야 한다.
- _IO_read_end에는 값이 있어야하며, _IO_write_base와 같은 값을 삽입하면 된다.
_buf(stdin으로 값을 삽입하거나 stdout으로 출력 시 사용)
기본사항
stdin / stdout 함수 또한 _IO_FILE 구조체를 따른다.
특히 stdin 구조체의 buf_base 변수가 변경되어있다면,
fgets(buf, size, stdin)
과 같이 값을 입력 받을 때 buf 변수가 아닌 변조된 buf_base 주소에 값을 써버린다.
특이사항
- _flags 값은 보통 0xfbad208b를 쓴다.
- buf_end는 당연히 buf_base보다 커야한다.
vtable 구조체
vtable 구조체라고 불리지만 사실은 _IO_jump_t 구조체가 맞다. 이의 모양은 아래와 같다.
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish); // fclose()
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); // fwrite()
JUMP_FIELD(_IO_xsgetn_t, __xsgetn); // fread()
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이라는 것은 절대적인 명칭이 아니다.
예를 들면 _IO_str_jump, _IO_wstr_jump 등이 vtable 종류들이며 그 구조가 위와 같다.
vtable의 특이 사항이, 만일 vtable이 call 된다면, 해당 함수에 맞게 offset을 통해 함수가 실행된다.
vtable 값
기본사항
vtable의 값은 위에서 설명 했듯이 _IO_FILE_plus 내의 값이기에
_IO_FILE_plus 구조체에서 _IO_FILE_plus + 8 byte 위치에 있는 값이며,
_IO_FILE 구조체 기준으로는 _IO_FILE + 0xd8 위치의 값이다.
vtable의 값은 offset을 더하기 위한 기준 값이 된다.
유의사항
vtable 값은 호출할 함수의 주소를 담고 있는 주소여야 한다.
즉,
vtable 값 = system 함수의 주소를 값으로 가진 주소 - offset
여야 한다. offset은 아래를 참조.
_IO_finish_t & _IO_xsputn_t & _IO_xsgetn_t
아래와 같이 각각의 함수 실행 시 참조할 주소의 위치이다.
직접적으로 해당 값을 변조하여 사용되지는 않고 offset시에 활용된다고 생각하면 된다.
_IO_finish_t
fclose 함수 call 시 사용되는 값이며, 그 offset이 0x10 이다.
즉,
vtable 값 = system 함수의 주소를 값으로 가진 주소 - 0x10
_IO_xsputn_t
fprintf, fwrite 함수 call 시 사용되는 값이며, 그 offset이 0x38 이다.
즉,
vtable 값 = system 함수의 주소를 값으로 가진 주소 - 0x38
_IO_xsgetn_t
fread 함수 call 시 사용되는 값이며, 그 offset이 0x40 이다.
즉,
vtable 값 = system 함수의 주소를 값으로 가진 주소 - 0x40
libc 버전에 따른 문제.
libc 버전에 따라 공격 방법이 조금 다르다.
이유는 vtable을 검증하는 코드가 추가되었기 때문이다.
다만 우회 방법은 단순하게도 _IO_FILE 구조체의 값을 잘 조절해주면 된다.
자세한 내용은 아래 링크를 참조하자. (추후 여기로 가져올 예정)
https://wyv3rn.tistory.com/114
'Tips & theory' 카테고리의 다른 글
함수 호출 규약. (0) | 2022.09.05 |
---|---|
system hacking을 register 기초 (0) | 2022.09.05 |
FSOP - _IO_flush_all_lockp () (0) | 2022.08.18 |
Bypass IO_validate_vtable (0) | 2022.08.17 |
_IO_FILE Arbitrary Address Write (0) | 2022.08.16 |