RSS Feed

Entries in CTF (2)


Hackers Puzzle Challenge in the CSAW 2010 CTF Final Round

A few weeks ago NYU Polytechnic held the final round of their Capture the Flag. Marcin previously wrote about his challenge for the qualification round. We both wrote challenges for the final round, and my challenge was primarily based around steganographic tricks with file formats, surrounded by some simple cryptography.


The first things you received were a bat script and a multi-part file, without an extension. The bat file copied the second file twice and appended .jpg and .zip extensions as hints. It's a fairly well known secret that you can combine jpg and zip files into a single file, and it's 'valid' as both - but 'valid' is in quotes for a reason. You actually need to do a bunch of byte manipulation to get this into a legal format - you can read about how it works over in my stackoverflow answer here.

The jpg-part of the multi-part file was a reference to the movie Hackers, after which the challenge was themed. Within the zipfile-part was an executable which when run would look for the multi-part file and then display a prompt:

Password Prompt UI

Entering Hackers-themed words would get you images, an mp3 snippet, hints, and even the hacker's manifesto:
Decrypted Content Examples

Exploring the Code

But none of these were the key of course. .Net is trivial to disassemble, and I was counting on that. The code in the program seeks past the jpg-part of the multi-part file, and then reads blocks of data from the middle - stopping when it reaches the zip-part of the file. (So the file had three parts: jpg, zip, and in-between: an arbitrary binary format). The password entered is hashed, and used to key a dictionary. The value of the dictionary is used to attempt decryption of each block of data read from the middle of the file. When it succeeds it will - depending on a sentinel byte - show an image, text, play a song, or write out a file. Now because you can disassemble the program - you can see all the dictionary values, and therefore you can decrypt the blocks of data without ever needing to know the password.

But that would be too easy - so not all the encryption keys are in the dictionary. If your hashed password is not found in the dictionary, it is used itself. The encryption algorithm chosen was XTEA, and the key itself was neutered down to 27 bits. The encrypted blocks were easily brute-forced. (And XTEA is a very simple algorithm to implement).

After you brute-forced all the blocks and matched up their corresponding filetypes from the code, you were left with a slew of images, an mp3, several textfiles, and two very promising files with the extensions .key.gpg and .txt.gpg. Upon examining these files with gnupg, you found that .txt.gpg was an asymmetrically encrypted file with a key you did not possess; and .key.gpg was symmetrically encrypted with a passphrase you did not know.

You discovered that in the .key.gpg file - either through verbose gnupg output, looking at it in a hex-editor, or by running strings - there were a number of userid and marker packets at the end of the file. (The OpenPGP file format is a collection of different types of packets.) These extra packets contained the string "dot", "dash", or "PGP". Dot and dash were morse code, and PGP was the letter-delimiter, and it decoded to the word 'morse' - which was the passphrase to the file.

Upon decryption, you had a .key file, which was the public and private key used to encrypt the second .txt.gpg file - but without any indicator of what the passphrase was. Again, either through gnupg options, a hex-editor, or the 'strings' utility - you found that the preferred keyserver for the key was set to a particular URL. When you visited it, you were given the passphrase, and the file decrypted to a text file containing the key for the CTF.


When the files were given to the teams, they also received a hint that the challenge would require brute forcing - Julian Cohen (one of the CTF organizers) and I argued back and force for a while about whether it was acceptable, and how much time it should take. I wanted the teams to only have enough time to run their program twice - while Julian felt it should be instant. He argued they didn't have much time (the competition was 5 hours for a half-dozen challenges) while I argued they should understand the code and write it correctly the first time - I didn't want the challenge to become trial and error. In the end, not only did they get a neutered keyspace (27 bits took me 5 minutes to run) but they received the challenge the night before. However, the hint given threw off at least one team - they spent a long time finding hash collisions in the first 27 bits of the MD5 output.

In the end, this was the challenge solved by the most teams. I don't know if it was because they spent more time on it than other challenges by receiving it early, because they could easily retrieve the code so the challenge was more accessible to them, or if it was just too darn easy. I'll have to start brainstorming next year's challenge... If you'd like to attempt the challenge yourself, you can download the multi-part file.

Bonus Trivia

This challenge is extremely small - the multi-part file weighs in at only 220KB, despite containing many photos and a small snippet of an mp3. While I had parts of the code from a project a year ago, the bulk of the challenge was actually written for a Hackers-Themed party in Brooklyn where I intended to distribute the challenge on 5 1/4" disks:

Straight Blingin

Unfortunately, both of my 5 1/4" drives not only didn't work, but blew out one of my motherboards. I had to resort to 3 1/2" disks. However, since only one of my friends I gave it to was even able to find a 3 1/2" drive, I decided to repurpose the challenge (adding the gpg elements, neutering the key) for the CTF. Apparently I could have handed out blank 5 1/4" disks and no one would have known the difference. As a final aside, in a fresh box of multi-colored 3 1/2" disks sitting on my shelf since the '90s, the green disks exihibited a much higher failure rate than the others: 7 dead green disks, 2 dead orange, 1 dead yellow, and 0 dead red or blue.


Crypto Challenges at the CSAW 2010 Application CTF Qualifying Round

On the weekend of September 24-26, NYU Polytechnic held a CTF qualifying round for its annual Capture The Flag competition to be held during Cyber Security Awareness Week, attracting high school students, undergrads, graduates and industry professionals to compete for a spot in the finals. Teams from all over brought a wide range of skillsets, and had just 48 hours to rack up as many points as possible. The results of this qualifying round can be viewed at CSAW-CTF Finalists.

Two of of our former interns, Julian Cohen and Luis Garcia, who were responsible for organizing the CTF asked that I help write some crypto challenges, as well as be one of the judges of the competition. Besides myself, Dino Dai Zovi designed the Exploitation challenges; Jon Chittenden, Alex Sotirov and Erik Cabetas the Reversing challenges; Efstratios Gavas on Forensics; and lastly, Julian and Luis wrote the web security challenges.

For this round of the CTF, I wrote four crypto challenges, each of varying difficulty. I wrote the challenges in Python, and served them using the CherryPy HTTP framework. This worked pretty well, and I'm happy to hear there were no complaints regarding server availability/performance. In addition, using CherryPy made deployment a breeze, since CherryPy also includes an embedded WSGI server which required almost no configuration.


I designed all of the challenges to reflect actual examples of insecure crypto implementations we've encountered at GDS during security assessments at various clients. In this post, I describe the premise behind each of the challenges as well as any potential hurdles that players would have ran into. I leave the actual exploitation of each challenge open as an exercise for the reader. I look forward to reading any write-ups by teams who completed the challenges and plan to include links to them in future updates to this post.

Crypto #1 / Padding Oracles

Given all the recent discussion surrounding the ASP.NET Padding Oracle attack by Juliano Rizzo and Thai Duong, I found it fitting to include a challenge involving padding oracles. In this challenge, a user submits a form containing a "name" and "team" parameter. These values are encrypted using AES in CBC mode, then encoded using URL-base64, and returned within a "SID" cookie value. The format of the plaintext looks like so, where %s was substituted with the name and team parameter values respectively:

challenge = 'CSAW 2010 CRYPTO #01|%s|%s|role=5|CHALLENGE' % (name, team)

The goal of this challenge was to decrypt the cookie using a padding oracle attack and modify the role to equal 0 using CBC-R. I specifically introduced several obstacles to thwart use of any existing exploit tools. First, I used URL-base64 encoding to throw off tools like PadBuster, which did not support URL-base64 at the time. URL-base64 encoding simply replaces the "+" character with "-", "/" with "_", and strips any padding characters. When the application caught a BadPaddingError during decryption, it would return an error page with the same HTTP status code and content-length as other errors. This would confuse most tools that relied on HTTP status codes, exceptions/stack traces and response content-lengths to distinguish invalid and valid padding. I only introduced a very subtle difference in the error message itself:


Sorry, an error had occurred.

Other errors:

Sorry, an error has occurred.

Sneaky, eh? One intersting thing of note is that the Python Crypto library, by default does not validate decrypted blocks are properly padded. I had to verify the padding manually, and throw a BadPaddingError if the padding was incorrect. I understand this challenge could have been done via CBC bit flipping, though that is the premise behind Crypto #2. For an in-depth discussion of Exploiting Padding Oracles, see our related post on Automated Padding Oracle Attacks with PadBuster.

On successful completion of this challenge, the server returns a page with the flag as well as a form to submit for challenge2. Without this form and the role parameter value that was set, it was not possible to move onto the next challenge.

Crypto #2 / CBC Bit Flipping

CBC mode bit flipping attacks can allow an attacker to manipulate portions of an encrypted message by flipping specific bits of ciphertext. If you are not familiar with the mechanics of this attack, theres a great post on the Matasano Chargen blog from 2009 (see resources below) that discusses the attack in detail. The following diagram provides a visual explanation that shows how bit flipping works:

CBC Bit Flipping Example

The block containing the flipped byte will be mangled when decrypted, however the corresponding byte in the next decrypted block will be altered.

Upon completing Crypo #1, the success response contained a form with an already populated role parameter (again URL-base64 encoded). This value was an AES encrypted string containing the user's name and team name submitted to Crypto #1. In addition, it reset the user's role level back to 5, and the goal again was for the players to attain a role level of 0. A hexdump of the URL-base64 decoded "role" value is shown below:


On the server, this block would decrypt to:


To perform a CBC bit flipping attack, we're going to need to manipulate a byte in the 3rd cipher block that when decrypted, lines up with the 5 in "role=5", (0xa8 in the 3rd cipherblock, highlighted in blue in the first hexdump above). So XOR "5" with "168" (the ordinal value of 0xa8), and get 173 (0xad). Replace 0xa8 with 0xad in our original ciphertext (this time, highlighted in red):


Which the server would then decrypt to:


Notice how the 3rd block (highlighted in red) was corrupted during the decryption process in order to flip "role=5" to "role=0". This is an inherit property of CBC (as described earlier), because each decrypted block is XOR'd with the previous encrypted block to produce the plaintext. The key here is that as long as the garbled block does not contain any critical data that would otherwise cause the application logic to fail or error out, this block can be safely sacrificed and the exploit will still work.

Crypto #3

For the third challenge gimme, I created a scenario that allowed users to specify a username to login with, provided they also submit a special key in absence of a password. Unforunately, this challenge contained a critical bug in some validation logic that failed spectacularly. As a result, I will not be publishing the actual technique for this challenge at this time. Sorry guys, my bad.

Crypto Bonus / ECB Block Swapping

The number of players to complete this challenge took me a little by surprise, as this probably is one of the more easier attacks to perform. For those not familiar with ECB mode, individual plaintext blocks are encrypted completely independent of each other. As a result, it is easy to identify patterns in ciphertext, as two identical plaintext blocks will produce the same ciphertext. More importantly, ECB is vulnerable to "block swapping," which is the underlying technique that is used to complete this challenge.

In this challenge, a user is allowed to login to the system with just their username, however admin and root users are not permitted to login. Fortunately, due to the use of ECB and lack of an HMAC, an attacker could forge a valid token that allowed them to impersonate the "root" user. To make things more difficult, each token only had a lifetime of 5 minutes... oh no! Let's walk through how to exploit this:

Signing in as the user "AAAAAAAAA", we receive the the following token. I've also included the equivalent base64 decoded hexdump for reference below it:



Submitting "AAAAAAAAA" again, we get the following token (decoded/hexdump output):


Note, only the second 8-byte block has changed (highlighted in green)... hmmmmm, could that be [part of] the timestamp? Let's increase the length of our username to "AAAAAAAAAAAAAAAAAA" and see what happens.


Notice how the 3rd cipher block repeated (the blocks highlighted in blue). Now let's do "AAAAAAAAAAAAAAAAAAAAAAAAadmin":


On the server, this would decrypt to "  1285874686664|AAAAAAAAAAAAAAAAAAAAAAAAadmin|CSAW_CHALLENGE#4\x02\x02".

For sake of time, I increased the number of A's to make "admin" within its own block (highlighted in red above). If we then swap the 6th block (highlighted in red) with the 3rd (highlighted in blue), to produce the following ciphertext, and then submit it in URL-base64 encoded form we successfully authenticate as the administrator:


The above would decrypt to: "  1285874686664|admin|CSAW_CHALLENGE#4\x02\x02".

Another potential expoit vector in this challenge was the ability to create future-dated tokens. Combine an admin token with a future-dated timestamp, and you've got yourself an "uber-token," if you will. Below is a code snippet from some of the timestamp validation logic:

timestamp, userid, m = decrypted_token.split('|')

if (time.time() * 1000) - int(timestamp) > 300000:
cookie['CSAW_ID'] = ''
cookie['CSAW_ID']['expires'] = 0


Following the CTF Finals held October 28-29, I plan to publish the source code to the challenges I've written to CSAW Crypto Challenges on GitHub, so be sure to check back for more updates!


Below are links to some excellent resources that provide further information on some of the topics covered in this post.

CSAW CTF team writeups

Below are links to individual teams challenge writeups (to be added):