In this post I will provide some background information on the Kendall challenge of the Boston Key Party CTF. The focus is rather on how the challenge was designed than how to solve it. I'm sure others will cover that perspective in writeups.
This CTF is mostly run by BUILDS, but also with some challenges from others including Northeastern SecLab. The game board was organized by MBTA stations as a Google Maps layover, courtesy of Jeff Crowell.
The challenge categories were organized by train lines. The blue line was crypto, orange was reversing, red line was pwning. Everything else ended up on the green line.
For the Kendall challenge (pwning, 300 pts) we wanted to combine multiple tasks that require different skills into a single more complicated challenge. Also, we also wanted to create something around the recent Lenovo / Superfish news stories. However, creating a challenge titled "Super*" or "*fish" would have given away too much. We had to be more sneaky about this, but also avoiding giving away too little having players try to guess what to do.
We ended up with a combination of a remote exploitable router that leads on to man-in-the-middling a SSL connection that has the superfish certificate installed. Players were provided with IP/Port of the pwnable router and the binary that was running there.
A breakdown of the steps necessary to finish:
pwn the binary
Bypass authentication
Overwrite DNS entries with DNS controlled by team
Trigger DHCP renew
Intercept Browsing
Set up DNS server that responds with team's IP
Listen to the requests and make them succeed
Interpret the HTTP request
Set up SSL interception with Superfish CA
Part 1: The Router
The router software was remote accessible. When connecting, users were greeted by the following screen:
The user can operate as guest, anything important requires to be administrator. Read: there is an easy buffer overflow in the "filter" menu option, it allows to overwrite the admin flag. We included log files which hinted at the DHCP setting being important (it reads a static file). Players had to bypass authentication and then change the DNS to point to one of their machines. Next, trigger "renew leases". What happens in the background: the program will call another program in the same directory which pushes the DNS setting to something that drives the browser via sockets. This process will directly kick off an artificial browser that issues two web requests. We separated the accounts of the binary and the browser to make finding shortcuts to the flag harder.
Note: much of the work with the router binary was done by Georg Merzdovnik.
Part 2: The Browser
We simulated a user browsing websites. First a HTTP site, later log into their bank account where some sensitive information is revlealed (the flag). Should any step in this process fail, the browser aborts operation. The "browser" was a python script using urllib2. Parts that were important to get right were the DNS queries and certificate validation. The DNS lookups had to be performed through the server the teams provide by owning the router only. The SSL request verifies against the superfish certificate only. By default urllib2 will not check authenticity of certificates.
Once teams pushed their IP address as DNS server, they could see two incoming DNS queries. One for yandex.ru and the second one for a made up hostname "my.bank"
Next, players had to reply with an IP they control and have a running web server to intercept the requests. For my local testing I used minidns, a dependency-free python script that will resolve any hostname to a single IP address.
One thing I dislike while solving challenges is pointless guessing. So, before making a HTTPS request we issued a HTTP request to give a hint what to do with the SSL connection. We added a new header, namely "X-Manufacturer" with the value "Lenovo". This is a completely made up header which was supposed to be a hint towards Superfish without being blatantly obvious.
The second request was pointed at "https://my.bank" Teams had to make the browser establish a legitimate SSL connection and we would issue a request to: "https://my.bank/login/username={0}".format(self.FLAG)
Although we had no specific format for keys, we decided to prefix the key with "FLG-" to make it obvious once players got that far.
To get this right, teams could either run a web server with the Superfish private key, or MITM and point the request somewhere else.
A writeup using sslsplit for the latter option is available on Rob Graham's blog.
Closing
The source code of the challenges will be released as a tarball at some point in the near future, follow @BKPCTF (or me) for updates. I hope the challenge was fun and am looking forward to hear in writeups how teams did it.
The 2012 Qualification round for CSAW CTF was fun. I was playing with the Northeastern Seclab hacking group - PTHC. One of the more interesting challenges was networking 300.
As input we received a file called "dongle.pcap", no further description.
The first thing to do with pcaps is to load them in wireshark. The type for most packets is URB_Interrupt or URB_Control (URB is a USB request block). Most of the packets don't look interesting - by browsing we found packet 67 which contains the following string: "Teensy Keyboard/Mouse/Joystick", this made us assume that we have to deal with recovering key presses.
Some quick googling lead us to this website, we downloaded the source files and inspected the code to analyze the protocol. We figured out that the packets we are interested in are pretty specific, they should be:
- 72 bytes long
- the 10th byte is \x01
- the 12th one is \x1a
- the 66th byte should be non-zero
- also we only care about the last 8 bytes and can disregard the rest
import binascii
from scapy.all import *
dongle = rdpcap("dongle.pcap")
for d in dongle:
sd = str(d)
if len(sd) == 0x48 and sd[9] == '\x01' and sd[11] == '\x1a':
x = sd[len(sd) -8:]
print binascii.hexlify(x)
This leaves us with 1338 packets, the last four bytes are all \x00. We inspect the file usb_keyboard_debug.h from the keyboard's source file and can see the key mapping. "A" = 4 etc. We created a mapping keycode-output and prepended it to our script so we would see the keyboards behavior. By inspecting the pcap we found that there were no ALT or STRG key presses, only shift and only for a couple of keys (3). For the sake of simplicity we decided just to check for these three cases in additional if clauses without making a generalized upper-case function. What we get from running the updated program:
The string "KEY{...}" made us assume we finished, but the scoreboard disagreed. It took us a while to figure out that we also have to take the RXTERM lines into account - the last two numbers (separated by "+" are the coordinates). That was the last part of the puzzle and we got the points.
The following program is the cleaned up, final version that will parse the pcap file, pull out the key presses, write the keys into a matrix with the corresponding coordinates and print them in order.
from scapy.all import *
import binascii
keys = {}
keys[4]='A'
keys[5]='B'
keys[6]='C'
keys[7]='D'
keys[8]='E'
keys[9]='F'
keys[10]='G'
keys[11]='H'
keys[12]='I'
keys[13]='J'
keys[14]='K'
keys[15]='L'
keys[16]='M'
keys[17]='N'
keys[18]='O'
keys[19]='P'
keys[20]='Q'
keys[21]='R'
keys[22]='S'
keys[23]='T'
keys[24]='U'
keys[25]='V'
keys[26]='W'
keys[27]='X'
keys[28]='Y'
keys[29]='Z'
keys[30]='1'
keys[31]='2'
keys[32]='3'
keys[33]='4'
keys[34]='5'
keys[35]='6'
keys[36]='7'
keys[37]='8'
keys[38]='9'
keys[39]='0'
keys[41]='ESC'
keys[43]='TAB'
keys[45]='-'
keys[46]='EQUAL'
keys[47]='LEFT_BRACE'
keys[48]='RIGHT_BRACE'
keys[49]='BACKSLASH'
keys[50]='NUMBER'
keys[51]='SEMICOLON'
keys[52]='QUOTE'
keys[53]='TILDE'
keys[54]='COMMA'
keys[55]='PERIOD'
keys[56]='SLASH'
keys[57]='CAPS_LOCK'
keys[58]='F1'
keys[59]='F2'
keys[60]='F3'
keys[61]='F4'
keys[62]='F5'
keys[63]='F6'
keys[64]='F7'
keys[65]='F8'
keys[66]='F9'
keys[67]='F10'
keys[68]='F11'
keys[69]='F12'
keys[72]='PAUSE'
keys[74]='HOME'
keys[75]='PAGE_UP'
keys[76]='DELETE'
keys[77]='END'
keys[79]='RIGHT'
keys[80]='LEFT'
keys[81]='DOWN'
keys[82]='UP'
keys[83]='NUM_LOCK'
keys[89]='KEYPAD_1'
keys[90]='KEYPAD_2'
keys[44]=' '
keys[40]='ENTER'
dongle = rdpcap("dongle.pcap")
buf = {}
buf[1] = ""
for d in dongle:
sd = str(d)
if len(sd) == 0x48 and sd[9] == '\x01' and sd[11] == '\x1a' and sd[0x42] != '\x00':
x = sd[len(sd) -8:]
# only these three keys get used with shift
if x[0] == '\x02':
if keys[ord(x[2])] == "EQUAL":
key = "+"
elif keys[ord(x[2])] == "RIGHT_BRACE":
key = "}"
elif keys[ord(x[2])] == "LEFT_BRACE":
key = "{"
else:
key = keys[ord(x[2])]
if key == "ENTER":
print buf[len(buf)]
buf[len(buf)+1]=""
key = "\n"
else:
buf[len(buf)] += key
# putting keys on their corresponding coordinates
matrix = {}
for i in range(1,len(buf),2):
(a,b,c) = (buf[i]).split("+")
if int(c) not in matrix.keys():
matrix[int(c)] = {}
# look ahead for one line - the echo command
matrix[int(c)][int(b)] = buf[i+1][-1:]
# print pressed keys in order
for x in sorted(matrix.keys()):
for y in sorted(matrix[x].keys()):
print "{a1e0ea66cab8e2163484f308b55c8b73124cdcf3d3411fefce873f0243ccaee6}03d-{a1e0ea66cab8e2163484f308b55c8b73124cdcf3d3411fefce873f0243ccaee6}03d-{a1e0ea66cab8e2163484f308b55c8b73124cdcf3d3411fefce873f0243ccaee6}s" {a1e0ea66cab8e2163484f308b55c8b73124cdcf3d3411fefce873f0243ccaee6} (x,y,matrix[x][y])
This weekend PPP organized its second PlaidCTF which was a lot of fun. Below is a quick writeup for the bittorrent forensics challenge.
Description:
It turns out that robots, like humans, are cheap and do not like paying for their movies and music. We were able to intercept some torrent downloads but are unsure what the file being downloaded was. Can you figure it out?
Provided was a file torrent.pcap, we used tshark (the command line tool for wireshark) to extract data from the packet capture. The only interesting data points are bittorrent.piece, from those we only need index, begin and data. By printing them in this order we can run a simple sort to make sure the file contents are in order.
Next we strip everything but the data field and the colons. Finally we use translate and sed to turn the hex representation into binary. After running the below script we have a file binout.
By using the file command and consequently unpacking we figure out its a bz2-ed tar file. Inside we find the files key.mp3 and key.txt. key.txt contains "t0renz0_v0n_m4tt3rh0rn", which turned out to be the valid key. We couldn't extract any hidden information from key.mp3 :-)
Note: if you are trying to reconstruct a file from a bittorrent pcap you might want to check for retransmits, missing indices, multiple files in one capture etc. It would make sense not to strip the headers directly with sed but keep them and run some script to analyze them.
One of my iCTF challenges was a simple JavaScript obfuscation, a backup of the code is available here. What happens is obvious, window.alert is triggered with the message “why?”. “Why” is less obvious since the code was encoded with jjencode. There are no other visible hints.
To further look into window.alert, we can overwrite the function:
After re-running the code we see that window.alert is not being called with a String as argument, but with an object which contains the attribute:
This is a writeup of the PlaidCTF 500 pts challenge “Fun with Firewire”.
Introduction
Given is a memory dump (128 MB) of a running Windows XP SP3 machine as well as a 32 MB file containing random data (a TrueCrypt volume image, according to the problem description). The memory dump was supposedly extracted via the Firewire port: The Firewire specification allows devices to have full DMA access. This allows forensic analysts (or a malicious hacker) to plug into any running computer that has a Firewire port and gain full access to the machine within seconds. Papers describing the attack and tools can be found at http://www.hermann-uwe.de/blog/physical-memory-attacks-via-firewire-dma-part-1-overview-and-mitigation. A different way to get a dump of the memory would be to conduct a “cold boot attack” as described in this paper.
Overview
To get an overview of the memory dump we inspect it with volatility. We see that TrueCrypt was running at the moment the dump was taken … good.
Further inspection of the memory dump reveals that the Operating System is Windows XP SP3, and the latest version of TrueCrypt (7.0a) is used. We reconstruct the setup by launching a VirtualBox installation, and we extract the memory using Mantech Memory Dumper mdd http://sourceforge.net/projects/mdd/. TrueCrypt offers the possibility to cache the passwords for mounting encrypted volumes. Comparing different memory dumps let us conclude that password caching was not enabled in the TrueCrypt software.
We briefly summarize the relevant technical details of TrueCrypt. More information can be found at http://www.truecrypt.org/docs/. In order to mount an encrypted volume, TrueCrypt uses the password and/or one or more key-files in order to decrypt the header (first 512 bytes of the volume). If the header gets correctly decrypted (a magic cookie is found), TrueCrypt reads the configuration (encryption algorithm and mode, etc.) as well as the master and secondary key into memory, and safely overwrites the memory regions where the password / key-file location was stored. The extracted master and secondary key is used for any further encryption and decryption of data. Since the data is encrypted and decrypted on the fly, these keys remain in memory. (Note that recent papers suggest storing the keys in CPU registers, more specifically in SSE registers http://portal.acm.org/citation.cfm?id=1752053 or in MSR registers http://arxiv.org/abs/1104.4843 instead of in the RAM in order to mitigate against these attacks.).
The default cipher used by TrueCrypt is AES in XTS mode which uses two 256 Bit AES-keys. We have to locate these keys in the memory dump. One option would be to analyze the data-structures and locate the memory region where TrueCrypt stores the keys.
But it is easier to use a generic approach to locate AES keys since a tool for that task was already written for the “cold boot attack” research: AESKeyFinder.
Once we have the right keys, we replace the header of the encrypted volume with the header of an identical volume which we created and where we set the password (so that TrueCrypt starts the mounting process correctly), but have TrueCrypt patched so that it uses the extracted keys from the memory dump instead of the ones from the newly generated header.
Finding the keys
AESKeyFinder inspects memory dumps (or actually any kind of files) and performs a simple heuristic to estimate entropy. The tool targets the expanded AES keys and tests whether a contiguous region in memory satisfies the constraints of a valid AES key schedule https://secure.wikimedia.org/wikipedia/en/wiki/Rijndael_key_schedule.
So we run the tool in verbose mode:
The “constraint on rows”-output tells us that the expanded keys are valid according to the AES key schedule. If we had bit errors in the respective memory regions (likely in cold boot attacks), not all constraints would have been met and AESKeyFinder would have calculated a guess for the original valid key.
So we have three keys after only a few of seconds of runtime - so far so good.
The entropy of (3) is really low, and we can definitely exclude it if we assume TrueCrypt is not totaly broken. This is good news since we have exactly two remaining 256-bit AES keys, as used by TrueCrypt in default configuration (AES in XTR mode).
Patching TrueCrypt
Next we read the source of TrueCrypt. Remember that TrueCrypt first decrypts the header with the password, and then reads the AES-key from the decrypted header. Reading in the header is done in Volume/VolumeHeader.cpp:VolumeHeader::Deserialize(.,.,.). We patch the code there, right after the master and secondary key was read from the decrypted header, and replace it with the hard-coded key value we found in the previous step. Our quick and dirty patch looks as follows:
Mounting the Volume
In order for TrueCrypt to reach the patched code it must first correctly decrypt a valid header. So we copy the header from an identically sized TrueCrypt volume configured with the default parameters:
and open ppp.challenge.vol with the patched TrueCrypt software and find the file KEY.TXT in the correctly decrypted volume.
Summary
This was a really nice challenge letting us explore TrueCrypt internals. If you think this is too complicated - you are right. You can also solve the challenge with available tools.
People involved in solving this challenge: Clemens Hlauschek, Michael Weissbacher