This was one of my favorite challenges at this year’s CTF. Thanks to the guys who put it on, it was a load of fun and I appreciate all the hard work I know that goes into this sort of thing.

The Challenge

Posted on one of the websites was the file contained the following files:

zoidberg% unzip -d ./fu2
zoidberg% cd fu2
zoidberg% file ./*
./BouncyCastle.Crypto.dll: PE32 executable for MS Windows (DLL) (console) Intel 80386 32-bit Mono/.Net assembly
./FileEncryptor.exe:       PE32 executable for MS Windows (console) Intel 80386 32-bit Mono/.Net assembly
./plain1_encrypted:        data
./plain2:                  ASCII text, with no line terminators
./plain2_encrypted:        data
zoidberg% ls -al ./plain*
-rw-r--r--  1 mgill  staff  128 Sep 24 13:14 ./plain1_encrypted
-rw-r--r--  1 mgill  staff  112 Sep 17 06:18 ./plain2
-rw-r--r--  1 mgill  staff  128 Sep 24 13:14 ./plain2_encrypted

With the following contents of plain2:

zoidberg% cat plain2

There are a few clues here if you just go off file names and file sizes, but let’s take the easy route and simply decompile the .NET binary. A quick google search on the md5sum of the BouncyCastle lib tells us it’s unlikely it has been modified so we only want to see what FileEncryptor.exe does. My favorite .net decompiler is dotPeek, and lucky us .net allows for some awesome code generation.

The view in dotpeek

After looking through the code we get a better idea on what is going on. We pass in a password, a plain text file, and a output file. The program then encrypts the plain text file with the password, to produce the output. Here are the interesting functions:

public static void EncryptFile(string password, string inFile, string outFile)
    string salt = "s@1tValue";
    string s = "1234567890123456";
    int keySize = 128;
    byte[] plain = Aes.readBytesFromFile(inFile);
    byte[] bytes1 = Encoding.ASCII.GetBytes(s);
    byte[] key = Aes.GenerateKey(password, salt, keySize);
    byte[] bytes2 = Aes.Encrypt(plain, key, bytes1);
    Aes.writeBytesToFile(outFile, bytes2);
public static byte[] Encrypt(byte[] plain, byte[] key, byte[] iv)
    PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher((IBlockCipher)new OfbBlockCipher((IBlockCipher)new AesEngine(), 128));
    ICipherParameters parameters = (ICipherParameters)new ParametersWithIV((ICipherParameters)new KeyParameter(key), iv);
    cipher.Init(true, parameters);
    return Aes.cipherData(cipher, plain);

Now we have all the key pieces of information. Our plain2 file, which is 112 bytes of repeating characters (‘KC57’*28) produces plain2_encrypted, at 128 bytes. We have plain1_encrypted, but no plain1. A brute force attack here seems a bit inelegant not to mention being the ballers we are we don’t have time for that shit.

Notice that the password is seeded with a salt and the IV is static. Generally speaking the IV should be a noonce value to stop different types of attacks against block encryptions. Static IV’s are bad. The Encrypt() function above shows that we’re using the OfbBlockCipher or Output-FeedBack. OFB is a stream cipher which generally means the keystream is xor’d with the plaintext value to produce the output. Really what we’re interested in is attacks against OFB, especially when the programmer makes the mistake of using a static IV.

All the stars align. We have a static IV making the keystreams identical for each encryption and a plaintext and encrypted value pair. This means we can decrypt the plain1_encrypted file without ever needing to know the key!  Simply compute the value of the keystream by xoring the plain2 against the encrypted plain2_encrypted, and apply that keystream to the plain1_encrypted. Voila! Here is a bit of python code to get the job done

import itertools

#take in the plain1 encrypted file and convert to ints
p1e = open('plain1_encrypted','r').read()
p1e_ord = map(ord, p1e)

#take in the plain2 encrypted file and convert to ints
p2e = open('plain2_encrypted','r').read()
p2e_ord = map(ord, p2e)

#take in the plain2 file and convert to ints
p2 = open('plain2','r').read()
p2_ord = map(ord, p2)

keystream = []
for x,y in itertools.izip_longest(p2_ord, p2e_ord, fillvalue=0):

final = []
for x,y in zip(p1e_ord, keystream):

print ''.join(final)

And if you run it…

zoidberg% python      

Success, the flag has been disclosed! While this is a workable solution, I’m not exactly sure how the bytes are properly padded (instead of using 0 in my case) nor am I sure why there are unprintable characters in the decoded stream. You can see them by directly printing the repr of the finalResult object. But it works and the flag is decoded. If you have any input please throw it my way, it would be nice to know if there is any way to improve on it all.


tl;dr static iv’s are bad and break your crypto