tmxklab

FSOP를 위한 fclose()분석 본문

Security/01 System Hacking

FSOP를 위한 fclose()분석

tmxk4221 2020. 9. 15. 17:11

 

_IO_FILE struct, _flags, _IO_FILE_plus struct, vtable 등에 관한 개념은 다음 링크를 참고하길 바란다.

 

stdout flag를 이용한 libc leak

stdout flag값을 변경하여 libc주소를 leak하는 방법을 정리하였다 따로 libc주소를 leak할 방법이 없을 때 유용하게 쓰일 것 같당ㅎㅎ 관련 문제 : [HackCTF/Pwnable] ChildHeap stdout을 이용한 libc주소 leak &..

rninche01.tistory.com

 


1. fclose() 내부 루틴

fclose에 대한 코드는 glibc/libio/iofclose.c에 있으며 함수 이름은 _IO_new_fclose이다.

 

1.1 _IO_new_fclose()

int
_IO_new_fclose (FILE *fp)
{
  int status;
  CHECK_FILE(fp, EOF);
#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
  /* We desperately try to help programs which are using streams in a
     strange way and mix old and new functions.  Detect old streams
     here.  */
  if (_IO_vtable_offset (fp) != 0)
    return _IO_old_fclose (fp);
#endif
  /* First unlink the stream.  */
  if (fp->_flags & _IO_IS_FILEBUF)
    _IO_un_link ((struct _IO_FILE_plus *) fp);
  _IO_acquire_lock (fp);
  if (fp->_flags & _IO_IS_FILEBUF)
    status = _IO_file_close_it (fp);
  else
    status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
  _IO_release_lock (fp);
  _IO_FINISH (fp);
	...
	...
  return status;
}
versioned_symbol (libc, _IO_new_fclose, _IO_fclose, GLIBC_2_1);
strong_alias (_IO_new_fclose, __new_fclose)
versioned_symbol (libc, __new_fclose, fclose, GLIBC_2_1);

밑에 매크로에 의해 fclose()가 _IO_new_fclose()로 바인드됨으로써 fclose심볼이 _IO_new_fclose로 파싱된 것을 확인

 

① 먼저, CHECK_FILE()을 통해 fp를 검증한다.

② (2번쨰 if문) fp->_flags가 _IO_IS_FILEBUF(0x2000)으로 세팅되어 있으면 _IO_un_link()를 호출

③ (3번째 if문) fp->_flags가 _IO_IS_FILEBUF(0x2000)으로 세팅되어 있으면 _IO_file_close_it()를 호출

_IO_FINISH ()를 호출

 

이제 ② ~ ④에서 사용한 함수들을 살펴보자

  • _IO_un_link()
  • _IO_file_close_it()
  • _IO_FINISH()

 

1.2 _IO_un_link()

void
_IO_un_link (struct _IO_FILE_plus *fp)
{
  if (fp->file._flags & _IO_LINKED)
    {
      FILE **f;
#ifdef _IO_MTSAFE_IO
      _IO_cleanup_region_start_noarg (flush_cleanup);
      _IO_lock_lock (list_all_lock);
      run_fp = (FILE *) fp;
      _IO_flockfile ((FILE *) fp);
#endif
      if (_IO_list_all == NULL)
        ;
      else if (fp == _IO_list_all)
        _IO_list_all = (struct _IO_FILE_plus *) _IO_list_all->file._chain;
      else
        for (f = &_IO_list_all->file._chain; *f; f = &(*f)->_chain)
          if (*f == (FILE *) fp)
            {
              *f = fp->file._chain;
              break;
            }
      fp->file._flags &= ~_IO_LINKED;
#ifdef _IO_MTSAFE_IO
      _IO_funlockfile ((FILE *) fp);
      run_fp = NULL;
      _IO_lock_unlock (list_all_lock);
      _IO_cleanup_region_end (0);
#endif
    }
}

fp->file._flags가 _IO_LINKED(0x0080)로 세팅되어 있으면 연결 목록에서 fp를 제거한다. file stream들은 _IO_list_all(연결 목록 헤더)를 통해 chain형식으로 이어져 있음(_IO_FILE구조체를 가지는 스트림들도 stdout, stdin, stderr와 연결되어 있음)

 

1.3 _IO_new_file_close_it()

  • _IO_file_close_it심볼이 _IO_new_file_close_it로 파싱됨
int
_IO_new_file_close_it (FILE *fp)
{
  int write_status;
  if (!_IO_file_is_open (fp))
    return EOF;
  if ((fp->_flags & _IO_NO_WRITES) == 0
      && (fp->_flags & _IO_CURRENTLY_PUTTING) != 0)
    write_status = _IO_do_flush (fp);
  else
    write_status = 0;
  _IO_unsave_markers (fp);
  int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0
                      ? _IO_SYSCLOSE (fp) : 0);
  /* Free buffer. */
  if (fp->_mode > 0)
    {
      if (_IO_have_wbackup (fp))
        _IO_free_wbackup_area (fp);
      _IO_wsetb (fp, NULL, NULL, 0);
      _IO_wsetg (fp, NULL, NULL, NULL);
      _IO_wsetp (fp, NULL, NULL);
    }
  _IO_setb (fp, NULL, NULL, 0);
  _IO_setg (fp, NULL, NULL, NULL);
  _IO_setp (fp, NULL, NULL);
  _IO_un_link ((struct _IO_FILE_plus *) fp);
  fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS;
  fp->_fileno = -1;
  fp->_offset = _IO_pos_BAD;
  return close_status ? close_status : write_status;
}
libc_hidden_ver (_IO_new_file_close_it, _IO_file_close_it)
  • (첫 번째 if문) 파일이 열려 있는지 확인
    • 그렇지 않은 경우 EOF를 반환
  • (두 번쨰 if문) 파일이 쓰기 모드로 열렸는지 확인
    • flags_ : _IO_NO_WRITES(0x0008) = 0, _IO_CURRENTLY_PUTTING(0x0800) = 1
    • 참이면 _IO_do_flush를 호출하여 버퍼를 비우고 포인터들을 초기화
  • (삼항 연산 부분) : _IO_SYSCLOSE()를 호출하여 파일을 닫는다.
#define _IO_SYSCLOSE(FP) JUMP0 (__close, FP)

_IO_SYSCLOSE()는 매크로 형식이며 vtable의 __close에 해당하는 _IO_file_close()를 호출한다. 

 

1.4 _IO_new_file_finish()

IO_FINISH()는 매크로 형식이며 vtable의 __finish를 호출한다.

#define _IO_FINISH(FP) JUMP1 (__finish, FP, 0)

  • 0x602010은 FILE구조체의 fp포인터임
  • 최종적으로 vtable의 __finish에 해당하는 IO_new_file_finish를 호출
void
_IO_new_file_finish (FILE *fp, int dummy)
{
  if (_IO_file_is_open (fp))
    {
      _IO_do_flush (fp);
      if (!(fp->_flags & _IO_DELETE_DONT_CLOSE))
        _IO_SYSCLOSE (fp);
    }
  _IO_default_finish (fp, 0);
}
libc_hidden_ver (_IO_new_file_finish, _IO_file_finish)
#define _IO_file_is_open(__fp) ((__fp)->_fileno != -1)
  • 파일이 열려있다면(fp→_fileno ≠ -1) _IO_do_flush()를 호출하여 버퍼를 비우고 마지막에 _IO_default_finish()를 호출한다.

 

1.4.1 _IO_default_finish()

/* The way the C++ classes are mapped into the C functions in the
   current implementation, this function can get called twice! */
void
_IO_default_finish (FILE *fp, int dummy)
{
  struct _IO_marker *mark;
  if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
    {
      free (fp->_IO_buf_base);
      fp->_IO_buf_base = fp->_IO_buf_end = NULL;
    }
  for (mark = fp->_markers; mark != NULL; mark = mark->_next)
    mark->_sbuf = NULL;
  if (fp->_IO_save_base)
    {
      free (fp->_IO_save_base);
      fp->_IO_save_base = NULL;
    }
  _IO_un_link ((struct _IO_FILE_plus *) fp);
#ifdef _IO_MTSAFE_IO
  if (fp->_lock != NULL)
    _IO_lock_fini (*fp->_lock);
#endif
}
libc_hidden_def (_IO_default_finish)
  • 여기서 할당받았던 버퍼들을 free시킨다.
  • 실제로 FILE구조체를 가지는 포인터들은 heap영역에 할당받음

 

나름 위에서 각 함수들이 호출하는 과정을 그림으로 표현하면 다음과 같다.

fclose에서 vtable에 있는 함수를 호출하는 부분 → 빨간색 박스

 

 

만약에 fclose에 들어가는 fp포인터에 값을 쓸 수 있는 상황이라면 vtable을 변조하여 원하는 함수를 넣으면

fclose실행 시 원하는 함수로 이동할 것이다.

 


2. 참고자료

코드 참고 : https://code.woboq.org/userspace/glibc/libio/fileops.c.html#_IO_file_close

'Security > 01 System Hacking' 카테고리의 다른 글

RTC(Return to csu)  (0) 2020.09.15
FSOP(File Stream Oriented Programming)  (0) 2020.09.15
stdout flag를 이용한 libc leak  (6) 2020.08.26
heap(5) - tcache 정리  (0) 2020.08.24
Linux system call table 정리(32bit, 64bit)  (0) 2020.08.20
Comments