what is _IO_FILE Arbitrary Address Write
https://dreamhack.io/wargame/challenges/367
해당 문제를 풀면서 배운 점을 다룬다.
위글을 읽고 오면 편하다.
문제를 풀면서 배운점이다.
int main() {
FILE *fp;
char file_buf[1024];
init();
fp = fopen("/etc/issue", "r");
printf("Data: ");
read(0, fp, 300);
fread(file_buf, 1, sizeof(file_buf)-1, fp);
printf("%s", file_buf);
if( overwrite_me == 0xDEADBEEF)
read_flag();
fclose(fp);
}여기서 보면 read(0, fp, 300); 으로 파일 구조체를 덮어쓸 수 있고
fread(file_buf, 1, sizeof(file_buf)-1, fp); 코드로 해당 파일을 사용하게 된다.
fread 는 vtable을 사용하는 대표적인 함수이다. 따라서 vtable overwrite도 가능하겠지만, 이번에는 문제의 의도답게 vtable 내에 함수에서 일어날 수 있는 취약점을 분석해보겠다.
pwndbg> bt
#0 __GI__IO_file_read (fp=0x602260, buf=0x602490, size=4096) at fileops.c:1153
#1 0x00007ffff7a6f148 in _IO_new_file_underflow (fp=0x602260) at fileops.c:531
#2 0x00007ffff7a6d748 in __GI__IO_file_xsgetn (fp=0x602260, data=<optimized out>, n=1023) at fileops.c:1344
#3 0x00007ffff7a613c1 in __GI__IO_fread (buf=<optimized out>, size=1, count=1023, fp=0x602260) at iofread.c:38
#4 0x00000000004008b6 in main ()
#5 0x00007ffff7a03b97 in __libc_start_main (main=0x40082b <main>, argc=1, argv=0x7fffffffe668, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe658) at ../csu/libc-start.c:310
#6 0x00000000004006ca in _start ()
pwndbg>
main → fread → __GI__IO_fread → __GI__IO_file_xsgetn 으로 vtable 함수인 __GI__IO_file_xsgetn 이 실행되었음을 알 수 있다.
__GI__IO_file_xsgetn 코드에서
if (fp->_IO_buf_base
&& want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
{
if (__underflow (fp) == EOF)
break;
continue;
}다음과 같이 내부 버퍼가 비면 새로운 데이터를 읽어 오기 위해 _IO_new_file_underflow 을 vtable을 통해 실행하게 된다.
_IO_new_file_underflow 함수에는
count = _IO_SYSREAD (fp, fp->_IO_buf_base,
fp->_IO_buf_end - fp->_IO_buf_base);다음과 같은 부분이 있는데
여기서 이미 정리한것처럼 read함수를 실행한다. 즉, 여기에서 내부적으로
_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));
}다음과 같은 함수를 실행하게 된다. 어 근데 인자가 fp->_IO_buf_base.가 buf로
fp->_IO_buf_end - fp->_IO_buf_base 가 size 으로 들어가게 된다. 즉, 내가 _IO_FILE 구조체를 덮어써서 해당 구조체 필드 값들을 조작하면 buf와 size를 조작해서 특정 위치에 내가 원하는 값을 쓰는
Arditary Writable 공격이 가능하게 된다.
‣https://elixir.bootlin.com/glibc/glibc-2.27/source/libio/bits/libio.h#L245
참고로 덮어써야할 _IO_FILE 구조체는
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
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;
#if 0
int _blksize;
..다음과 같다 저 자세한 건 위에 링크
참고로
추가 내용 ) 문제를 풀다보니 fileops.c:1313
에 있는
while (want > 0)
{
have = fp->_IO_read_end - fp->_IO_read_ptr;
if (want <= have)
{
memcpy (s, fp->_IO_read_ptr, want);
fp->_IO_read_ptr += want;
want = 0;while 문에 무한으로 갇히는 문제가 있었다. 해당 문제를 해결하기 위해 억지로 overwrite 크기를 want 보다 크게 해서. if (want <= have) 을 만족 시켜 want = 0; 코드를 실행시켜야 한다.