NYU Poly CSAW CTF Finals Challenge
18 Nov 2010 23:08:34 EST

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.

This writeup originally appeared on the Gotham Digital Science blog.

Add a comment...
required, hidden, gravatared

required, markdown enabled (help)
you type:you see:
[stolen from reddit!](http://reddit.com)stolen from reddit!
* item 1
* item 2
* item 3
  • item 1
  • item 2
  • item 3
> quoted text
quoted text
Lines starting with four spaces
are treated like code:

    if 1 * 2 < 3:
        print "hello, world!"
Lines starting with four spaces
are treated like code:
if 1 * 2 < 3:
    print "hello, world!"