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.


Weekly Reading List for 1/18/14

I’ve decided to start posting a weekly reading list of interesting security-related articles I’ve come across in the past week. They’re not guaranteed to be new, but should at least still be relevant.

Using a BeagleBone to bypass 802.1x

Most security practitioners are already aware that NAC doesn’t provide meaningful security. While it’ll keep some random guy from plugging in to an exposed ethernet port in the lobby (shouldn’t that be turned off?), it won’t stop a determined attacker. You can just MITM the legitimate device, let it perform the 802.1x handshake, then send packets appearing to be from the legitimate device. To make it easier, ShellSherpa has put together a BeagleBone-based device to automatically MITM the NAC connection.

Screwing with microcontroller devices

Matasano and Square have announced a joint microcontroller-based CTF.

Which SDR to buy?

SDR continues to be a hot button topic, and Taylor Killian has a post from a couple of months ago to help you choose what SDR device you should get: HackRF vs. bladeRF vs. USRP. While you’ll need to understand basic radio concepts to get much out of the comparison, you should probably be at least at that level before you start doing much with SDR.

New disassembly framework

Capstone is a project to create a framework for disassembling binaries from a wide variety of architectures. Doesn’t seem to have binary extraction yet, so you’ll need to get the text segment yourself and rebase it, but I’m hopeful it’ll make its way into some interesting projects.

Dead Tree Reading

Although I’m mostly reading eBook formats, wanted to highlight which books I’m reading lately.


LD_PRELOAD for Binary Analysis

During the BreakIn CTF, there were a few challenges that depended on the return value of of libc functions like time() or rand(), and had differing behavior depending on those return values. In order to more easily reverse those binaries, it can be nice to control the return values of those functions. In other cases, you have binaries that may call functions like unlink(), system(), etc., where you prefer not to have those functions really called. (Though you are running these untrusted binaries in a VM, right?)

So let’s say there’s a program that’s built from the following source (over simplified for example):

1
2
3
4
5
6
7
8
9
10
11
12
#!c
#include <time.h>
#include <stdio.h>

int main(int argc, char **argv){
  if (time() % 86400 == 0) {
    puts("Win!\n");
    return 0;
  }
  puts("Lose\n");
  return 1;
}

So this is obviously a highly contrived example, but you only win if you hit it exactly at midnight. Now, I suppose you could change your computer clock, but you still only have a one second window to get it right. How can you control this to improve your outcomes?

There’s a wonderful trick that allows you to inject a custom shared library to be loaded before your program is run (and the symbols in it resolved) so that the functions provided by your library are used preferentially over the ones provided by the C library. First, let’s create our replacement version of time, and create one that allows us to set the time by setting an environment variable. (Note that we don’t have to replicate the same behavior of time(), as we know only the return value is used, and no pointer is passed in.)

1
2
3
4
5
6
7
8
9
10
#!c
#include <time.h>
#include <stdlib.h>

time_t time(time_t *out){
    char *tstr = getenv("TIME");
    if (tstr)
      return (time_t)atol(tstr);
    return (time_t)0;
}

Now we can build the shared object and run the program with our version of time. Note that you must use an absolute path for LD_PRELOAD, or it will only look in the LD search path.

1
2
3
4
5
6
#!sh
$ gcc -Wall -fPIC -shared -o time.so time.c
$ TIME=0 LD_PRELOAD=`pwd`/time.so ./challenge
Win!
$ TIME=1 LD_PRELOAD=`pwd`/time.so ./challenge
Lose

Note that you don’t have to completely re-implement the function you want to replace, you can actually get it and call it within your replacement function. (Note that this only works on GNU libc.) Maybe you only want unlink to work if called with the path “/deleteme”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#define TARGET "/deleteme"

int unlink(const char *path){
  int (*real_unlink)(const char *) = dlsym(RTLD_NEXT, "unlink");
  
  if (!strncmp(TARGET, path, strlen(TARGET))) {
    return real_unlink(path);
  }
  
  fprintf(stderr, "Would unlink(%s)", path);
  return 0;
}

You can use the same build and preload instructions as before, and this will allow to delete “/deleteme”, otherwise it will just print out what it would’ve done. There are many other uses of LD_PRELOAD, but these are a couple of ways I’ve found useful for quick-and-dirty hacks.