Giới thiệu
Ban đầu mình nghĩ chỉ cần học kỹ thuật house of apple2 là đủ để xử lý các phiên bản libc mới, nhưng lại bị hạn chế bởi cơ chế whitelist. Vì vậy, bài viết này sẽ phân tích kỹ thuật house of cat nhằm khắc phục những hạn chế của apple2.
Kỹ thuật house of cat dự kiến sẽ chia thành ba phần. Bài viết này tập trung vào phân tích nguyên lý mã nguồn. Phần hai sẽ thảo luận về quá trình debug và một số chi tiết nhỏ chưa được đề cập trong phần này. Phần ba sẽ trình bày các ví dụ thực hành.
Mình cho rằng điểm mạnh lớn nhất của house of cat là khi chương trình không có hàm exit và sử dụng bộ lọc whitelist, đồng thời chỉ có thể khai thác thông qua lỗi largbin attack. Phân tích này được thực hiện với glibc-2.35.
Knowledge nền tảng
_IO_wfile_jumps
const struct _IO_jump_t _IO_wfile_jumps =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_new_file_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wfile_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
JUMP_INIT(xsputn, _IO_wfile_xsputn),
JUMP_INIT(xsgetn, _IO_file_xsgetn),
JUMP_INIT(seekoff, _IO_wfile_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_new_file_setbuf),
JUMP_INIT(sync, (_IO_sync_t) _IO_wfile_sync),
JUMP_INIT(doallocate, _IO_wfile_doallocate),
JUMP_INIT(read, _IO_file_read),
JUMP_INIT(write, _IO_new_file_write),
JUMP_INIT(seek, _IO_file_seek),
JUMP_INIT(close, _IO_file_close),
JUMP_INIT(stat, _IO_file_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
Trong house of apple2, chúng ta sử dụng:
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow)
Còn trong house of cat thì dùng:
JUMP_INIT(seekoff, _IO_wfile_seekoff),
Hãy xem xét hàm này:
_IO_wfile_seekoff
off64_t
_IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)
{
off64_t result;
off64_t delta, new_offset;
long int count;
if (mode == 0)
return do_ftell_wide (fp);
int must_be_exact = ((fp->_wide_data->_IO_read_base
== fp->_wide_data->_IO_read_end)
&& (fp->_wide_data->_IO_write_base
== fp->_wide_data->_IO_write_ptr));
bool was_writing = ((fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base)
|| _IO_in_put_mode (fp));
if (was_writing && _IO_switch_to_wget_mode (fp))
return WEOF;
if (fp->_wide_data->_IO_buf_base == NULL)
{
if (fp->_wide_data->_IO_read_base != NULL)
{
free (fp->_wide_data->_IO_read_base);
fp->_flags &= ~_IO_IN_BACKUP;
}
_IO_doallocbuf (fp);
_IO_setp (fp, fp->_IO_buf_base, fp->_IO_buf_base);
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
_IO_wsetp (fp, fp->_wide_data->_IO_buf_base,
fp->_wide_data->_IO_buf_base);
_IO_wsetg (fp, fp->_wide_data->_IO_buf_base,
fp->_wide_data->_IO_buf_base, fp->_wide_data->_IO_buf_base);
}
switch (dir)
{
struct _IO_codecvt *cv;
int clen;
case _IO_seek_cur:
cv = fp->_codecvt;
clen = __libio_codecvt_encoding (cv);
if (mode != 0 || !was_writing)
{
if (clen > 0)
{
offset -= (fp->_wide_data->_IO_read_end
- fp->_wide_data->_IO_read_ptr) * clen;
offset -= fp->_IO_read_end - fp->_IO_read_ptr;
}
else
{
int nread;
delta = (fp->_wide_data->_IO_read_ptr
- fp->_wide_data->_IO_read_base);
fp->_wide_data->_IO_state = fp->_wide_data->_IO_last_state;
nread = __libio_codecvt_length (cv,
&fp->_wide_data->_IO_state,
fp->_IO_read_base,
fp->_IO_read_end, delta);
fp->_IO_read_ptr = fp->_IO_read_base + nread;
fp->_wide_data->_IO_read_end = fp->_wide_data->_IO_read_ptr;
offset -= fp->_IO_read_end - fp->_IO_read_base - nread;
}
}
if (fp->_offset == _IO_pos_BAD)
goto dumb;
offset += fp->_offset;
dir = _IO_seek_set;
break;
case _IO_seek_set:
break;
case _IO_seek_end:
{
struct __stat64_t64 st;
if (_IO_SYSSTAT (fp, &st) == 0 && S_ISREG (st.st_mode))
{
offset += st.st_size;
dir = _IO_seek_set;
}
else
goto dumb;
}
}
_IO_free_wbackup_area (fp);
if (fp->_offset != _IO_pos_BAD && fp->_IO_read_base != NULL
&& !_IO_in_backup (fp))
{
off64_t start_offset = (fp->_offset
- (fp->_IO_read_end - fp->_IO_buf_base));
if (offset >= start_offset && offset < fp->_offset)
{
_IO_setg (fp, fp->_IO_buf_base,
fp->_IO_buf_base + (offset - start_offset),
fp->_IO_read_end);
_IO_setp (fp, fp->_IO_buf_base, fp->_IO_buf_base);
_IO_wsetg (fp, fp->_wide_data->_IO_buf_base,
fp->_wide_data->_IO_buf_base,
fp->_wide_data->_IO_buf_base);
_IO_wsetp (fp, fp->_wide_data->_IO_buf_base,
fp->_wide_data->_IO_buf_base);
if (adjust_wide_data (fp, false))
goto dumb;
_IO_mask_flags (fp, 0, _IO_EOF_SEEN);
goto resync;
}
}
if (fp->_flags & _IO_NO_READS)
goto dumb;
new_offset = offset & ~(fp->_IO_buf_end - fp->_IO_buf_base - 1);
delta = offset - new_offset;
if (delta > fp->_IO_buf_end - fp->_IO_buf_base)
{
new_offset = offset;
delta = 0;
}
result = _IO_SYSSEEK (fp, new_offset, 0);
if (result < 0)
return EOF;
if (delta == 0)
count = 0;
else
{
count = _IO_SYSREAD (fp, fp->_IO_buf_base,
(must_be_exact
? delta : fp->_IO_buf_end - fp->_IO_buf_base));
if (count < delta)
{
offset = count == EOF ? delta : delta-count;
dir = _IO_seek_cur;
goto dumb;
}
}
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base + delta,
fp->_IO_buf_base + count);
_IO_setp (fp, fp->_IO_buf_base, fp->_IO_buf_base);
_IO_wsetg (fp, fp->_wide_data->_IO_buf_base,
fp->_wide_data->_IO_buf_base, fp->_wide_data->_IO_buf_base);
_IO_wsetp (fp, fp->_wide_data->_IO_buf_base, fp->_wide_data->_IO_buf_base);
if (adjust_wide_data (fp, true))
goto dumb;
fp->_offset = result + count;
_IO_mask_flags (fp, 0, _IO_EOF_SEEN);
return offset;
dumb:
_IO_unsave_markers (fp);
result = _IO_SYSSEEK (fp, offset, dir);
if (result != EOF)
{
_IO_mask_flags (fp, 0, _IO_EOF_SEEN);
fp->_offset = result;
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
_IO_setp (fp, fp->_IO_buf_base, fp->_IO_buf_base);
_IO_wsetg (fp, fp->_wide_data->_IO_buf_base,
fp->_wide_data->_IO_buf_base, fp->_wide_data->_IO_buf_base);
_IO_wsetp (fp, fp->_wide_data->_IO_buf_base,
fp->_wide_data->_IO_buf_base);
}
return result;
resync:
if (fp->_offset >= 0)
_IO_SYSSEEK (fp, fp->_offset, 0);
return offset;
}
Chúng ta chỉ cần chú ý đến hai điểm sau:
- Nếu mode != 0, thì sẽ gọi hàm
_IO_switch_to_wget_mode(fp).
_IO_switch_to_wget_mode()
int
_IO_switch_to_wget_mode (FILE *fp)
{
if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF)
return EOF;
if (_IO_in_backup (fp))
fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_backup_base;
else
{
fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_buf_base;
if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_read_end)
fp->_wide_data->_IO_read_end = fp->_wide_data->_IO_write_ptr;
}
fp->_wide_data->_IO_read_ptr = fp->_wide_data->_IO_write_ptr;
fp->_wide_data->_IO_write_base = fp->_wide_data->_IO_write_ptr
= fp->_wide_data->_IO_write_end = fp->_wide_data->_IO_read_ptr;
fp->_flags &= ~_IO_CURRENTLY_PUTTING;
return 0;
}
Sau khi học qua house of apple2, chúng ta có thể nhận ra một hàm quen thuộc:
Nếu thỏa mãn điều kiện fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base, ta có thể kiểm soát luồng thực thi.
__malloc_assert
Vì có hàm exit nên có thể áp dụng kỹ thuật apple2, vì vậy bài viết này chỉ nói về các cách khác để kích hoạt nó.
Hàm _malloc_assert sẽ gọi các hàm IO dựa trên bảng vtable như _IO_xxx_jumps; cuối cùng sẽ thực hiện các thao tác IO thông qua cấu trúc stderr.
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}
__fxprintf()
int
__fxprintf (FILE *fp, const char *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
int res = __vfxprintf (fp, fmt, ap, 0);
va_end (ap);
return res;
}
__vfxprintf()
int
__vfxprintf (FILE *fp, const char *fmt, va_list ap,
unsigned int mode_flags)
{
if (fp == NULL)
fp = stderr;
_IO_flockfile (fp);
int res = locked_vfxprintf (fp, fmt, ap, mode_flags);
_IO_funlockfile (fp);
return res;
}
locked_vfxprintf
static int
locked_vfxprintf (FILE *fp, const char *fmt, va_list ap,
unsigned int mode_flags)
{
if (_IO_fwide (fp, 0) <= 0)
return __vfprintf_internal (fp, fmt, ap, mode_flags);
wchar_t *wfmt;
mbstate_t mbstate;
int res;
int used_malloc = 0;
size_t len = strlen (fmt) + 1;
if (__glibc_unlikely (len > SIZE_MAX / sizeof (wchar_t)))
{
__set_errno (EOVERFLOW);
return -1;
}
if (__libc_use_alloca (len * sizeof (wchar_t)))
wfmt = alloca (len * sizeof (wchar_t));
else if ((wfmt = malloc (len * sizeof (wchar_t))) == NULL)
return -1;
else
used_malloc = 1;
memset (&mbstate, 0, sizeof mbstate);
res = __mbsrtowcs (wfmt, &fmt, len, &mbstate);
if (res != -1)
res = __vfwprintf_internal (fp, wfmt, ap, mode_flags);
if (used_malloc)
free (wfmt);
return res;
}
Chúng ta chỉ cần làm cho nó đi vào hàm __vfprintf_internal.
__vfprintf_internal
Hàm này nằm trong file vfprintf.c. Mình không muốn đọc chi tiết, chỉ cần biết rằng cuối cùng nó sẽ gọi đến hàm _IO_wfile_xsputn trong bảng vtable _IO_wfile_jumps. Do đó, nếu đặt địa chỉ của _IO_wfile_jumps + 0x10 vào vtable, ta sẽ có thể gọi đến _IO_wfile_seekoff.
Tóm tắt luồng thực thi
__malloc_assert -->> __fxprintf() -->> __vfxprintf() -->> locked_vfxprintf()
-->> _IO_wfile_jumps -->> _IO_wfile_seekoff -->> _IO_switch_to_wget_mode()
-->> _IO_WOVERFLOW
Các điều kiện vượt qua
- fp.mode != 0
- fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base hoặc _IO_vtable_offset(fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
- fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
Lưu ý nhỏ
Từ góc nhìn FSOP, nếu muốn gọi đến hàm _IO_wfile_seekoff, ta nên đặt vtable là _IO_wfile_jumps + 0x30. Tuy nhiên, trong chuỗi gọi từ __malloc_assert, chỉ cần đặt là _IO_wfile_jumps + 0x10.
Làm thế nào để kích hoạt __malloc_assert?
Chuỗi gọi là: _int_malloc --> sysmalloc --> __malloc_assert
Chỉ cần xem xét đoạn code trong sysmalloc gọi __malloc_assert:
Điều kiện kiểm tra:
- old_size >= 0x20;
- old_top.prev_inuse = 0;
- old_top page-aligned
Nói cách khác, chỉ cần thỏa mãn một trong ba điều kiện trên là có thể kích hoạt.
Kết thúc phần 1. Nếu có sai sót, mọi người vui lòng góp ý trong phần bình luận. Cảm ơn!