IO_validate_vtable 함수

dandb3·2023년 6월 2일
0

pwnable

목록 보기
10/17

함수 코드는 다음과 같다.

/* Perform vtable pointer validation.  If validation fails, terminate
   the process.  */
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 값이 __libc_IO_vtables section을 벗어나지 않는다면 함수는 종료되고, 벗어난다면 _IO_vtable_check() 함수가 실행된다.

_IO_vtable_check함수까지 분석하기에는 너무 간 것 같아서 하지 않고 넘어간다.

대신, __libc_IO_vtables section 안에 존재하는 공격가능한 함수를 찾은 후, vtable값을 섹션 안의 값으로 잘 조정하면 exploit할 수 있게 된다.

  • _IO_str_overflow 함수 - glibc-2.37 버전
int
_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;
        }
    }

  if (!flush_only)
    *fp->_IO_write_ptr++ = (unsigned char) c;
  if (fp->_IO_write_ptr > fp->_IO_read_end)
    fp->_IO_read_end = fp->_IO_write_ptr;
  if (flush_only)
    return 0;
  else
    return c;
}
libc_hidden_def (_IO_str_overflow)

어... write하는 부분도 없고 read하는 부분도 없고 malloc하고 free만 이루어진다.
너무 최신 버전이라 그런가..
dreamhack에서 참고하는 2.27버전을 보자.

  • _IO_str_overflow 함수 - glibc-2.27 버전
int
_IO_str_overflow (_IO_FILE *fp, int c)
{
  int flush_only = c == EOF;
  _IO_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 >= (_IO_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);
          _IO_size_t new_size = 2 * old_blen + 100;
          if (new_size < old_blen)
            return EOF;
          new_buf
            = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
          if (new_buf == NULL)
            {
              /*	  __ferror(fp) = 1; */
              return EOF;
            }
          if (old_buf)
            {
              memcpy (new_buf, old_buf, old_blen);
              (*((_IO_strfile *) fp)->_s._free_buffer) (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;
        }
    }

  if (!flush_only)
    *fp->_IO_write_ptr++ = (unsigned char) c;
  if (fp->_IO_write_ptr > fp->_IO_read_end)
    fp->_IO_read_end = fp->_IO_write_ptr;
  return c;
}
libc_hidden_def (_IO_str_overflow)

오.. 뭔가 다르다.
_IO_strfile 구조체를 사용하는 것이 보인다. 구조체를 살펴보자.

typedef void *(*_IO_alloc_type) (_IO_size_t);
typedef void (*_IO_free_type) (void*);

struct _IO_str_fields
{
  _IO_alloc_type _allocate_buffer;
  _IO_free_type _free_buffer;
};

struct _IO_streambuf
{
  struct _IO_FILE _f;
  const struct _IO_jump_t *vtable;
};

typedef struct _IO_strfile_
{
  struct _IO_streambuf _sbf;
  struct _IO_str_fields _s;
} _IO_strfile;

_IO_streambuf 를 보니 뭔가 익숙해서 찾아봤더니 _IO_FILE_plus와는 다른 struct였다..
glibc 2.37과 비교해 보면, 이 부분이 딱 malloc, free가 이루어지는 부분에 해당한다.
실제로 위에 struct, typedef 들을 확인해 보면

  • _IO_alloc_type : size_t형 인자를 받고, void *형을 리턴해주는 함수
  • _IO_free_type : void *형 인자를 받고, void 리턴해주는 함수

즉, 각자가 malloc, free 대신 역할을 해주는 함수 포인터들이다.
아마 여기가 취약점인 것 같다.
dreamhack에서 2.29 이후로는 쓸 수 없는 방법이라고 한 이유가 이거인듯.

struct _IO_str_fields 의 멤버를 원하는 함수로 덮을 수 있다면, exploit이 가능할 것이다. (특히 _IO_free_type - "/bin/sh"를 인자로 system 함수를 호출해주면 될 듯하다.)

  • 2.27버전 구조체 분석
    혹시나 다를까 해서 봤더니 다를 것은 없더라.
    그러면 _IO_str_fields는 어디에 위치해 있는 것인가?
    그림으로 확인해 보자. 짱림판 최고!

  • exploit 하기
    파일 관련 함수가 딱 _IO_str_overflow를 호출하게끔 vtable값을 조작.
    _free_buffer 함수포인터가 system함수를 가리키게 조작.
    _free_buffer 함수포인터가 실행되도록 _IO_str_overflow에서 조건 충족시키기.

  • 참고 자료

profile
공부 내용 저장소

0개의 댓글