Weekly Reading List for 1/25/14

This week, we’re focusing on binary exploitation and reversing. (Thanks to Ghost in the Shellcode for making me feel stupid with all their binary pwning challenges!)

Basic Shellcode Examples

Gal Badishi has a great set of Basic Shellcode Examples. It’s almost two years old, but a good primer into how basic shellcode works. x86 hasn’t changed (yes, I’m ignoring x64 for now), so still quite a relevant resource for those of us who have leaned on msfvenom/msfpayload for our payload needs.

Project Shellcode

Going beyond the basic, Project Shellcode is a site full of resources for crafting and understanding shellcode. Based on training classes used at BlackHat 2012, they walk through all the steps in writing shellcode.

x86 Assembly Guide

If the shellcode above looked like Greek, perhaps it’s time for an x86 assembly primer/refresher. UVA’s CS department has you covered with their x86 Assembly Guide, used in their CS216 class. It also has some useful reference to how the instructions work.

GNU Debugger Tutorial

If you want to observe the behavior of a running program, you’re going to want a debugger. If you’re running on Linux and haven’t spent the $1200 for IDA pro, you’re probably using the GNU Debugger, better known as GDB. RMS (no, not that RMS) has a great gdb tutorial.


Ghost in the Shellcode 2014

A quick Ghost in the Shellcode 2014 summary. Great CTF, but you better know your binary exploitation. I’m pretty happy with the overall 27th finish Shadow Cats managed. Here’s a summary of our team writeups, the first 3 by me, the last one by Dan.


Ghost in the Shellcode 2014: Radioactive

Radioactive was a crypto challenge that executed arbitrary python code, if you could apply a correct cryptographic tag. Source was provided, and the handler is below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!python
class RadioactiveHandler(SocketServer.BaseRequestHandler):
  def handle(self):
    key = open("secret", "rb").read()
    cipher = AES.new(key, AES.MODE_ECB)

    self.request.send("Waiting for command:\n")
    tag, command = self.request.recv(1024).strip().split(':')
    command = binascii.a2b_base64(command)
    pad = "\x00" * (16 - (len(command) % 16))
    command += pad

    blocks = [command[x:x+16] for x in xrange(0, len(command), 16)]
    cts = [str_to_bytes(cipher.encrypt(block)) for block in blocks]
    for block in cts:
      print ''.join(chr(x) for x in block).encode('hex')

    command = command[:-len(pad)]

    t = reduce(lambda x, y: [xx^yy for xx, yy in zip(x, y)], cts)
    t = ''.join([chr(x) for x in t]).encode('hex')

    match = True
    print tag, t
    for i, j in zip(tag, t):
      if i != j:
        match = False

    del key
    del cipher

    if not match:
      self.request.send("Checks failed!\n")
    eval(compile(command, "script", "exec"))

    return

So, it looks for a tag:command pair, where the tag is hex-encoded and the command is base64 encode. The command must be valid python, passed through compile and eval, so you’ll need to send a response back to yourself via self.request.send.

So how’s the tag calculated? Every 16-byte block of the command is encrypted in AES-ECB mode (so, two identical plaintexts == two identical ciphertexts) and then the encrypted blocks are xored together, producing the final tag. My first thought was to generate a plaintext such that len(plain) % 16 == 0, then repeat it twice, so the XORs will cancel out and give a tag of 00…00. Unfortunately, the padding must be at least one byte long, and my plaintext cannot contain null bytes.

So, we were also provided some sample code. One such example, decoded from its base64 representation, turns out to be:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!python
import os

self.request.send("Send command to eval: \n")
cmd = self.request.recv(1024).strip()


good = True
for b in cmd:
	if b not in '0123456789+-=/%^* ()':
		good = False

if good:
	self.request.send(str(eval(cmd)) + "\n")
else:
	self.request.send("???\n")

It turns out the line good = False (and two trailing newlines) are their own 16-byte block. We can append “good = True \n\n” to reset the value of true, and append it a 2nd time to get our tag to come out correctly. Then we can simply provide self.request.send(open('key').read()) when we receive our “Send command to eval:” prompt. And this got our flag, but it turns out there are two simpler solutions.

Alternate solution 1

Provide your own code as a multiple of 16. Provide it again. This gives you a tag of 00…00. But we know this doesn’t work. So instead, append one of the code samples provided, and use the tag for that, as 0{16} ^ known_tag = known_tag.

Alternate solution 2

Start your input with : followed by whatever base-64 encoded code you want. If you provide no tag at all, then the loop for tag comparison is never checked, leaving match=True. (This was probably an accident in the design of the problem, possibly inteding to provide constant-time tag comparison. As a side note, that tag comparison is not even constant time, even for strings of the proper length.)


Ghost in the Shellcode 2014: Lugkist

Lugkist was an interesting “trivia” challenge. We were told “it’s not crypto”, but it sure looked like a crypto challenge. We had a file like:

Find the key.

GVZSNG
AXZIOG
YNAISG
ASAIUG
IVPIOK
AXPIVG
PVZIUG
AXLIEG

Always 6 letters, but no other obvious pattern. I did notice that the 4th character always was S or I and the final character G or K, but couldn’t make anything of that. I realized the full character set was ‘AEGIKLONPSUTVYXZ’. Searching for this string revealed nothing, but searching for the characters space separated revealed that this was the same character set as used by the codes for the original Game Genie. And Game Genie codes were 6 characters long.

So, they’re Game Genie codes. What now? Well, there’s a handy (NES Game Genie Technical Notes)[http://tuxnes.sourceforge.net/gamegenie.html] that tells us how the characters decode. Turns out the characters are an alphabetical hexadecimal (there are 16 of them) but not in alphabetical order. Decoding the resulting hex was useless. The technical notes also tell us how to decode the address and data from the codes. Giving that a try doesn’t reveal anything obvious, but it was noteworthy that the data values were all within the normal printable ASCII range. The addresses seemed randomly ordered, but were contiguous. Maybe sorting by address and taking the values as characters to build a string?

“Power overwhelming? Back in my day cheats did not have spaces.”

Full translation code (in python) is below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!python
codes = 'APZLGITYEOXUKSVN'
xlate = {c: i for i,c in enumerate(codes)}

def numeric(s):
  return [xlate[x] for x in s]

def address(s):
  n = numeric(s)
  return (0x8000 +
      ((n[3] & 7) << 12) |
      ((n[5] & 7) << 8) |
      ((n[4] & 8) << 8) |
      ((n[2] & 7) << 4) |
      ((n[1] & 8) << 4) |
      (n[4] & 7) |
      (n[3] & 8))

def data(s):
  n = numeric(s)
  return (((n[1] & 7) << 4) |
          ((n[0] & 8) << 4) |
          (n[0] & 7) |
          (n[5] & 8))

prev_addr = 0
addrs = []
addr_dict = {}
with open('lugkist.2') as f:
  for l in f:
    l = l.strip()
    a = address(l)
    #print 'A: %04x [%04x]' % (a, a-prev_addr)
    prev_addr = a
    addrs.append(a)
    addr_dict[a] = l

chars = []
for x in sorted(addrs):
  chars.append(chr(data(addr_dict[x])))

print ''.join(chars)

Ghost in the Shellcode 2014: Pillowtalk

Pillowtalk was a 200 point crypto challenge. Provided was a stripped 64-bit binary along with a pcap file. I started off by exercising the behavior of the binary, looking at system calls/library calls to see what it was doing.

  • Client connects to server
  • Server reads 32 bytes from /dev/urandom
  • Server sends 32 bytes on the wire (not same bytes as read from /dev/urandom)
  • Client does same 32 byte read/send
  • Loop:
    • Server reads a line from stdin
    • Server sends 4 byte length
    • Server sends encrypted line
    • Client does the same steps

My first approach was by trying to use scapy to replay the pcap to the server, but this only gave complete noise, so I decided the two 32 byte values must be significant. I even tried controlling /dev/urandom (via LD_PRELOAD) to see if putting in the 32 bytes from the pcap would get to the right key. It didn’t.

I started reversing the binary and the behavior of the translation of the 32 bytes from /dev/urandom to the 32 bytes put on the wire. Eventually I came to the conclusion (later confirmed) that the 32 bytes from /dev/urandom were used as a private key to Curve25519, leading to the 32 bytes on the wire as a public key. The 2 values would then be used by each end for key agreement for the symmetric cipher. Breaking Curve25519 seemd a bit out of scope for a CTF, so I assumed it was an implementation problem with the symmetric crypto.

It was worth noting that the ciphertext was the exact same length as the plaintext, implying that a stream cipher was in use, and that if the same plaintext was sent twice, it encrypted the same way, implying that the cipher was being reset before sending each message. This leaves messages vulnerable to known-plaintext attacks by revealing the keystream.

I started by xoring the last byte of each message with ‘\n’, and recording the position/value pairs, then xoring each byte at those positions in each message with our newly revealed keystream bytes. I also ventured a guess that the first message was ‘hi\n’, so repeated the same process for the first 2 bytes. This gave me enough revealed plaintext to start making guesses about other bytes, and repeating this process with each set of bytes revealed the flag.