tmxklab
FSOP를 위한 fclose()분석 본문
_IO_FILE struct, _flags, _IO_FILE_plus struct, vtable 등에 관한 개념은 다음 링크를 참고하길 바란다.
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. 참고자료
- http://blog.leanote.com/post/mut3p1g/file-struct
- https://youngsouk-hack.tistory.com/68
- https://wogh8732.tistory.com/207
코드 참고 : 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 |