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 fu2.zip contained the following files:
With the following contents of 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.
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:
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
And if you run it…
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.
Thanks!
tl;dr static iv’s are bad and break your crypto