_IO_FILE
_IO_FILE
_IO_FILE 구조체
_IO_FILE
구조체는 리눅스 표준 라이브러리에서 파일 스트림을 나타내기 위한 구조체이며, 파일을 열기 위한fopen
함수를 호출했을 때 힙 영역에 할당된다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
struct _IO_FILE_plus { FILE file; const struct _IO_jump_t *vtable; }; struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ /* The following pointers correspond to the C++ streambuf protocol. */ 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; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; int _flags2; __off_t _old_offset; /* This used to be _offset but it's too small. */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE };
_flags
: 파일에 대한 각종 권한. 매직 값0xfbad0000
을 사용하며, 하위 2바이트를 비트 플래그로 사용한다._IO_read_ptr
: 파일 읽기 버퍼에 대한 포인터_IO_read_end
: 파일 읽기 버퍼 주소의 끝을 가리키는 포인터_IO_read_base
: 파일 읽기 버퍼 주소의 시작을 가리키는 포인터_IO_write_ptr
: 파일 쓰기 버퍼에 대한 포인터_IO_write_base
: 파일 쓰기 버퍼 주소의 시작을 가리키는 포인터_IO_write_end
: 파일 쓰기 버퍼 주소의 끝을 나타내는 포인터_chain
: 프로세스의_IO_FILE
구조체는 이 필드를 통해 연결 리스트를 만든다. 연결 리스트의 헤더는 라이브러리의_IO_list_all
에 저장된다._fileno
: 파일 디스크립터 값_IO_jump_t *vtable
: 파일 관련 작업을 수행하는 가상 함수 테이블
_flags
멤버변수는fopen
함수로 파일을 열 때 전달한 모드에 따라 값이 설정된다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
FILE *_IO_new_file_fopen(FILE *fp, const char *filename, const char *mode, int is32not64) { int oflags = 0, omode; int read_write; int oprot = 0666; int i; FILE *result; const char *cs; const char *last_recognized; if (_IO_file_is_open(fp)) return 0; switch (*mode) { case 'r': omode = O_RDONLY; read_write = _IO_NO_WRITES; break; case 'w': omode = O_WRONLY; oflags = O_CREAT | O_TRUNC; read_write = _IO_NO_READS; break; case 'a': omode = O_WRONLY; oflags = O_CREAT | O_APPEND; read_write = _IO_NO_READS | _IO_IS_APPENDING; break; ... }
fopen
함수의 두 번째 인자인mode
변수가 ‘r’, ‘w’, ‘a’ 문자인지 확인하고, 각 권한에 해당하는 비트가 할당된다.read_write
뿐만 아니라omode
변수에O_RDONLY
,O_WRONLY
등이 저장되는데,fopen
함수가 결국open
시스템 콜을 호출해 파일을 열어야 하기 때문이다.
- _
IO_FILE_plus
구조체를 살펴보면vtable
포인터가 있다. 파일 구조체는 각 파일마다 함수 테이블을 가지고 있다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
struct _IO_jump_t { JUMP_FIELD(size_t, __dummy); JUMP_FIELD(size_t, __dummy2); JUMP_FIELD(_IO_finish_t, __finish); JUMP_FIELD(_IO_overflow_t, __overflow); JUMP_FIELD(_IO_underflow_t, __underflow); JUMP_FIELD(_IO_underflow_t, __uflow); JUMP_FIELD(_IO_pbackfail_t, __pbackfail); /* showmany */ JUMP_FIELD(_IO_xsputn_t, __xsputn); JUMP_FIELD(_IO_xsgetn_t, __xsgetn); JUMP_FIELD(_IO_seekoff_t, __seekoff); JUMP_FIELD(_IO_seekpos_t, __seekpos); JUMP_FIELD(_IO_setbuf_t, __setbuf); JUMP_FIELD(_IO_sync_t, __sync); JUMP_FIELD(_IO_doallocate_t, __doallocate); JUMP_FIELD(_IO_read_t, __read); JUMP_FIELD(_IO_write_t, __write); JUMP_FIELD(_IO_seek_t, __seek); JUMP_FIELD(_IO_close_t, __close); JUMP_FIELD(_IO_stat_t, __stat); JUMP_FIELD(_IO_showmanyc_t, __showmanyc); JUMP_FIELD(_IO_imbue_t, __imbue); };
- 디버깅을 통해
vtable
의 모습을 볼 수 있다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
pwndbg> p *(struct _IO_jump_t *)0x7ffff7dca2a0 $5 = { __dummy = 0, __dummy2 = 0, __finish = 0x7ffff7a6e2d0 <_IO_new_file_finish>, __overflow = 0x7ffff7a6f2b0 <_IO_new_file_overflow>, __underflow = 0x7ffff7a6efd0 <_IO_new_file_underflow>, __uflow = 0x7ffff7a70370 <__GI__IO_default_uflow>, __pbackfail = 0x7ffff7a71c00 <__GI__IO_default_pbackfail>, __xsputn = 0x7ffff7a6d8d0 <_IO_new_file_xsputn>, __xsgetn = 0x7ffff7a6d530 <__GI__IO_file_xsgetn>, __seekoff = 0x7ffff7a6cb30 <_IO_new_file_seekoff>, __seekpos = 0x7ffff7a70940 <_IO_default_seekpos>, __setbuf = 0x7ffff7a6c7f0 <_IO_new_file_setbuf>, __sync = 0x7ffff7a6c670 <_IO_new_file_sync>, __doallocate = 0x7ffff7a600b0 <__GI__IO_file_doallocate>, __read = 0x7ffff7a6d8b0 <__GI__IO_file_read>, __write = 0x7ffff7a6d130 <_IO_new_file_write>, __seek = 0x7ffff7a6c8b0 <__GI__IO_file_seek>, __close = 0x7ffff7a6c7e0 <__GI__IO_file_close>, __stat = 0x7ffff7a6d120 <__GI__IO_file_stat>, __showmanyc = 0x7ffff7a71d80 <_IO_default_showmanyc>, __imbue = 0x7ffff7a71d90 <_IO_default_imbue> }
- 디버깅을 통해
_IO_FILE vtable 호출 과정
fread
함수는_IO_fread
함수와 동일하게 정의되어 있으며, 해당 함수 내부에서_IO_sgetn
함수를 호출한다.1 2 3 4 5 6 7 8 9 10 11 12 13 14
#define fread(p, m, n, s) _IO_fread (p, m, n, s) size_t _IO_fread (void *buf, size_t size, size_t count, FILE *fp) { size_t bytes_requested = size * count; size_t bytes_read; CHECK_FILE (fp, 0); if (bytes_requested == 0) return 0; _IO_acquire_lock (fp); bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested); _IO_release_lock (fp); return bytes_requested == bytes_read ? count : bytes_read / size; }
_IO_sgetn
함수는_IO_XSGETN
매크로를 호출하며, 이를 순차적으로 확인해보면vtable
변수를 참조하는 것을 확인할 수 있다.1 2 3 4 5 6 7 8 9
#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N) #define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2) #define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable ( (THIS))) size_t _IO_sgetn (FILE *fp, void *data, size_t n) { /* FIXME handle putback buffer here! */ return _IO_XSGETN (fp, data, n); }
fread
를 포함한 모든 파일 함수는 가상 함수 테이블을 참조하여 파일 작업을 시도한다. 그러나 해당 함수 테이블은 동적으로 할당되는 영역으로 쓰기 권한이 있기 때문에 공격에 악용될 수 있다.
_IO_FILE AAW
파일 읽기 과정
fread
,fgets
등 파일 읽기 함수는 내부에서_IO_FILE_xsgetn
함수를 호출한다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
_IO_size_t _IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n) { _IO_size_t want, have; _IO_ssize_t count; _char *s = data; want = n; ... /* If we now want less than a buffer, underflow and repeat the copy. Otherwise, _IO_SYSREAD directly to the user buffer. */ if (fp->_IO_buf_base && want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base)) { if (__underflow (fp) == EOF) break; continue; } ... }
- 이 함수에서는 인자로 전달된
n
이_IO_buf_end - _IO_buf_base
보다 작은지 검사하고,_IO_new_file_underflow
함수를 호출한다. - 실제로 파일을 읽는 과정은
_IO_new_file_underflow
를 시작으로 다양한 함수가 호출되면서 이뤄진다.
- 이 함수에서는 인자로 전달된
_IO_new_FILE_underflow
- 해당 함수 내부에서는 파일 포인터의
_flags
변수에 읽기 권한이 부여되어 있는지 확인한다. 이후_IO_SYSREAD
함수의 인자로 파일 포인터와 파일 구조체의 멤버 변수를 연산한 값이 전달된다.1 2 3 4 5 6 7 8 9 10 11 12 13 14
#define _IO_SYSREAD(FP, DATA, LEN) JUMP2 (__read, FP, DATA, LEN) int _IO_new_file_underflow (FILE *fp) { ssize_t count; if (fp->_flags & _IO_NO_READS) { fp->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return EOF; } ... count = _IO_SYSREAD (fp, fp->_IO_buf_base, fp->_IO_buf_end - fp->_IO_buf_base); }
- 이때 _
IO_SYSREAD
함수는 vtable의_IO_file_read
함수이다.
- 이때 _
_IO_FILE_read
_IO_FILE_read
함수 내부에서는read
시스템 콜을 사용해 파일의 데이터를 받는다.1 2 3 4 5 6 7
_IO_ssize_t _IO_file_read (_IO_FILE *fp, void *buf, _IO_ssize_t size) { return (__builtin_expect (fp->_flags2 & _IO_FLAGS2_NOTCANCEL, 0) ? __read_nocancel (fp->_fileno, buf, size) : __read (fp->_fileno, buf, size)); }
- 시스템 콜의 인자로 파일 구조체에서 파일 디스크립터를 나타내는
_fileno
,_IO_buf_base
인buf
, 그리고_IO_buf_base
로 연산된size
변수가 전달된다.
- 시스템 콜의 인자로 파일 구조체에서 파일 디스크립터를 나타내는
How to AAW?
- 임의 주소에 값을 쓰기 위해서는
_IO_buf_end
와_IO_buf_base
를 조작한다. 파일의 내용을 읽을 때 아래와 같은 코드가 실행된다.1
read(f->_fileno, _IO_buf_base, _IO_buf_end - _IO_buf_base);
_IO_buf_base
는 원하는 주소로 조작하고,_IO_buf_end
는 원하는 주소에 원하는 크기보다 큰 수를 더한 값으로 조작한다.- 원하는 크기보다 큰 수를 더하는 이유는,
_IO_new_file_underflow
코드 내에서_IO_buf_end - _IO_buf_base
값이fread
함수의 인자로 전달된 읽을 크기보다 커야 하는 조건이 있기 때문이다. - 위 조건을 만족했다면, 파일에 데이터를 쓰는 것이 아닌 프로그램에서 표준 입력을 통해 값을 쓰기 위해
fileno
를stdin
을 나타내는 0으로 조작한다.
_IO_FILE_AAR
fwrite
,fputs
등 파일 쓰기 함수는 내부에서_IO_sputn
함수를 호출한다.1 2 3 4 5 6 7 8 9 10 11
#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n) _IO_size_t _IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n) { ... if (to_do + must_flush > 0) { _IO_size_t block_size, do_write; /* Next flush the (full) buffer. */ if (_IO_OVERFLOW (f, EOF) == EOF)
_IO_sputn
은_IO_XSPUTN
함수의 매크로이며, 실질적으로_IO_new_file_xsputn
함수를 호출한다.- 이 함수에서는 파일 함수로 전달된 인자인 데이터와 길이를 검사하고
_IO_new_file_overflow
함수를 호출한다.
_IO_new_file_overflow
- 파일 포인터의
_flags
변수에 쓰기 권한이 있는지 확인한다.1 2 3 4 5 6
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */ { f->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return EOF; }
_flags
에_IO_CURRENTLY_PUTTING
이 설정되어 있지 않으면, 파일 구조체의_IO_write_ptr
,_IO_write_base
,_IO_write_end
필드 등을 다른 값으로 바꾼다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL) { /* Allocate a buffer if needed. */ if (f->_IO_write_base == NULL) { _IO_doallocbuf (f); _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base); } /* Otherwise must be currently reading. If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end, logically slide the buffer forwards one block (by setting the read pointers to all point at the beginning of the block). This makes room for subsequent output. Otherwise, set the read pointers to _IO_read_end (leaving that alone, so it can continue to correspond to the external position). */ if (__glibc_unlikely (_IO_in_backup (f))) { size_t nbackup = f->_IO_read_end - f->_IO_read_ptr; _IO_free_backup_area (f); f->_IO_read_base -= MIN (nbackup, f->_IO_read_base - f->_IO_buf_base); f->_IO_read_ptr = f->_IO_read_base; } if (f->_IO_read_ptr == f->_IO_buf_end) f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base; f->_IO_write_ptr = f->_IO_read_ptr; f->_IO_write_base = f->_IO_write_ptr; f->_IO_write_end = f->_IO_buf_end; f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end; f->_flags |= _IO_CURRENTLY_PUTTING; if (f->_mode <= 0 && f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED)) f->_IO_write_end = f->_IO_write_ptr; }
- 해당 함수의 인자로 전달된
ch
가 EOF 즉 -1이라면,_IO_do_write
함수를 호출한다.1 2 3 4
if (ch == EOF) return _IO_do_write (f, f->_IO_write_base, f->_IO_write_ptr - f->_IO_write_base); ...
- 앞서
_IO_new_file_overflow
를 호출할 때의 인자를 확인해보면 EOF를 전달하므로_IO_do_write
함수가 호출되는데, 전달되는 인자가 파일 구조체의 멤버변수임을 알 수 있다.
- 앞서
_IO_do_write
함수는 내부적으로new_do_write
함수를 호출한다.1 2 3 4 5 6 7
int _IO_new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do) { return (to_do == 0 || (_IO_size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF; } libc_hidden_ver (_IO_new_do_write, _IO_do_write)
new_do_write
new_do_write
함수 내부에서는 플래그 검사와_IO_SYSWRITE
함수 호출이 이루어진다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
#define _IO_SYSWRITE(FP, DATA, LEN) JUMP2 (__write, FP, DATA, LEN) static _IO_size_t new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do) { _IO_size_t count; if (fp->_flags & _IO_IS_APPENDING) /* On a system without a proper O_APPEND implementation, you would need to sys_seek(0, SEEK_END) here, but is not needed nor desirable for Unix- or Posix-like systems. Instead, just indicate that offset (before and after) is unpredictable. */ fp->_offset = _IO_pos_BAD; else if (fp->_IO_read_end != fp->_IO_write_base) { _IO_off64_t new_pos = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1); if (new_pos == _IO_pos_BAD) return 0; fp->_offset = new_pos; } count = _IO_SYSWRITE (fp, data, to_do); if (fp->_cur_column && count) fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1; _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base); fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base; fp->_IO_write_end = (fp->_mode <= 0 && (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED)) ? fp->_IO_buf_base : fp->_IO_buf_end); return count; }
- 파일 포인터의
_flags
변수에_IO_IS_APPENDING
플래그가 포함되어 있지 않다면,_IO_read_end
와_IO_write_base
가 다를 경우lseek
시스템 콜을 호출한다. new_do_write
함수의 인자인 파일 포인터와data
,to_do
를 인자로_IO_SYSWRITE
함수를 호출한다. 이는 매크로이며, vtable의_IO_new_file_write
가 호출된다.
- 파일 포인터의
_IO_new_file_write
IO_new_file_write
함수 내부에서는write
시스템 콜을 호출한다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
_IO_ssize_t _IO_new_file_write (_IO_FILE *f, const void *data, _IO_ssize_t n) { _IO_ssize_t to_do = n; while (to_do > 0) { _IO_ssize_t count = (__builtin_expect (f->_flags2 & _IO_FLAGS2_NOTCANCEL, 0) ? write_not_cancel (f->_fileno, data, to_do) : write (f->_fileno, data, to_do)); if (count < 0) { f->_flags |= _IO_ERR_SEEN; break; } to_do -= count; data = (void *) ((char *) data + count); } n -= to_do; if (f->_offset >= 0) f->_offset += n; return n; }
- 시스템 콜의 인자로 파일 구조체에서 파일 디스크립터를 나타내는
_fileno
,_IO_write_base
인data
,_IO_write_ptr - _IO_write_base
로 연산된to_do
변수가 전달된다.
- 시스템 콜의 인자로 파일 구조체에서 파일 디스크립터를 나타내는
How to AAR?
- 임의 주소 값을 읽기 위해서
_flags
변수를 매직 값인0xfbad0000
과IO_CURRENTLY_PUTTING
을 포함한 값으로 변경한다.IO_CURRENTLY_PUTTING
이 포함되어 있지 않으면_IO_new_file_overflow
함수 내부에서_IO_write_base
와_IO_write_ptr
을 다른 값으로 바꿔버리기 때문이다.
_IO_write_base
를 원하는 주소로 조작하고,_IO_write_ptr
을 원하는 주소에서 원하는 크기만큼 더한 주소로 조작한다.new_do_write
함수에서lseek
시스템 콜 호출을 방지하기 위해_IO_read_end
역시 원하는 주소로 조작한다.
fileno
역시 원하는 file descriptor로 조작한다. (stdout: 1)
vtable validation
- 파일 함수가 호출될 때에는 전달된 파일 포인터의 vtable 주소를 참조한다.
IO_validate_vtable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
const char *ptr = (const char *) vtable;
uintptr_t offset = ptr - __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
섹션에 할당되며, 코드에서는 해당 섹션의 시작 주소와 끝 주소를 뺄셈하여 섹션의 크기를 알아내고, 호출하려는 vtable의 주소가 섹션의 크기를 벗어나는 값이라면_IO_vtable_check
함수를 호출해 에러를 발생시킨다. - 이전 버전에서는 파일 포인터의 vtable 포인터를 덮을 수 있는 상황이라면 파일 함수가 참조하는 함수 포인터의 주소를 덮어써서 쉽게 공격할 수 있었으나,
IO_validate_vtable
함수가 추가되면서 더 이상 같은 기법으로 공격할 수 없다. - 해당 검사를 우회하기 위해
__libc_IO_vtables
섹션에 존재하는 함수들 중 공격에 사용될 수 있는 함수를 찾아야 한다.
_IO_str_overflow
- 해당 함수는
__libc_IO_vtables
섹션에 존재하는 함수이다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
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);
- 코드 하단에서 아래와 같이 파일 포인터 내
_s._allocate_buffer
라는 함수 포인터를 호출한다.1
new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
- 함수 포인터의 인자로
new_size
를 전달한다. 이는 아래와 같이 결정된다.1 2 3 4 5
#define _IO_blen(fp) ((fp)->_IO_buf_end - (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;
_IO_FILE
구조체 변수인_IO_buf_end
와_IO_buf_base
변수의 연산 값을 사용한다.- 파일 구조체를 조작할 수 있는 상황이라면
new_size
변수를 조작할 수 있다.
- 함수 포인터를 호출하기 위해서는 조건문을 통과해야 한다.
1 2 3 4
int flush_only = c == EOF; _IO_size_t pos; pos = fp->_IO_write_ptr - fp->_IO_write_base; if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
_IO_FILE
구조체 변수인_IO_write_ptr
과_IO_write_base
변수의 연산 값을 사용한다.- 비교 구문에서 사용되는
flush_only
변수는 기본값이 0이므로pos >= _IO_blen(fp)
가 된다. - 따라서
_IO_write_base
를 0으로 초기화하면_IO_write_ptr
값이 곧pos
변수의 값이 되므로 쉽게 해당 비교 구문을 통과할 수 있고, 함수 포인터를 호출할 수 있다.
- 함수 포인터의 인자로
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.