by
Pietro "peter_" Ferretti
on November 7, 2017
under HITCONQuals2017
21 minute read ·
The previous one is too easy. Try this!
This challenge naturally follows from Secret Server, of which you can find a writeup here.
We are given another python script, which pretty much resembles the first one from Secret Server.
What has changed? This time the server won’t provide us with the ciphertext for the flag, but we will instead need to find the value of a 56-byte random token. If we prove to the server that we know its value, it will kindly give us the flag in exchange.
To complicate things further, we are only allowed to send 340 requests before the connection is closed and the token reset.
It’s obvious that our previous approach will not work, since it required up to 256 request per character. We need to find a different way to solve this.
A different approach
Trying to find a character at a time is a good technique.
The problem is that we can’t send 256 requests for every single character. We would like to have, if possible, an oracle that can immediately reveal to us the value of a byte, with a single request.
Let’s look around and try to find something like this. To start, let’s just think about everything that could give us information about the value of unknown plaintexts.
Recall: the unpad method can unpad up to 256 characters, depending on the value of the last byte of the last block. Good! If we can recognize how many of the characters have been unpadded, this means that we immediately know the value of the last byte! (without making any additional requests)
Finding how many of the bytes have been unpadded
We need a way to distinguish among all 256 possible lengths at which the plaintext has been cut.
This is how we can do it: we craft a long, fixed message (longer than 256 bytes). We then replace the beginning of the message with get-md5, so that the server will return to us the encrypted hash of the decrypted and unpadded message.
We can then append any block we want to this fixed message. Whatever the additional block is, the first part of the message will stay the same. Depending on the decrypted value of the last byte of the block we append, the fixed message will be cut at different positions after unpadding.
The server will then reply with the encrypted hash of the truncated fixed message, which is indipendent of the block we appended, and only depends on the value of the last byte.
We can use unpad as an oracle!
(Note: to preserve the original plaintext of the added block we will also have to add the block before it)
NB: if the unpadding comes short of removing the added blocks, the MD5 hash will include part of them, thus returning an unknown ciphertext. We can avoid this problem by recognizing this case, flipping the most significant bit on the last byte and making it fall in our preferred range.
This method has a limitation though: we can only find the value of the last byte of a block. This is therefore not enough to discover the value of the token.
Generalizing to characters in every position
We can bypass the issue of only knowing the last byte of each block with some creativity.
We can move to a different problem: instead of finding the characters of the tokens at every index (which is currently impossible) we can obtain the encrypted MD5 hash of the token, truncated at every possible index, then find the last character of the hashes.
(Note: this is possible since the MD5 hash is a raw digest, not a hexdigest, thus the last byte can assume any of the 256 ASCII values.)
Knowing the last byte of the hashes, it is trivially possible to find all the preimages with a hash that ends with the same character. As before, we can proceed one character at a time.
Collisions on a single byte are possible, but we can just keep all possible candidates and gradually reduce the number later.
The attack
The complete attack is something like this:
Collect the encrypted MD5 hashes for every truncation of the token (56 requests)
Send the long fixed message (starting with get-md5) and get the encrypted MD5 hashes of the message at every unpadding length (256-32=224 requests)
Add each encrypted token MD5 hash to the end of the long fixed message, without the final padding block, send it to the server and check the reply (56 requests):
if the resulting hash is one of the known ones, we know the value of the last byte of the MD5 hash of that cut of the token
otherwise flip the most significant bit of the last byte and check again (i.e. send a new request)
Locally find all possible token candidates that produce the correct last byte when hashed.
At the end of this procedure we will have a list of possible values for the token.
How to reduce the pool of candidates
Since the possible candidates which satisfy the constraints on the last byte of the hashes are usually more than one, we need to find a way to reduce their number.
A simple idea is revealing the actual value of the last byte of every token block, instead of using their hashes. We can do this for the first, second and third block of the token. This is still usually not enough to uniquely identify the token, but in practice the number of candidates is reduced to less than 30 and often less than 10.
Since the whole attack requires around 300 requests we still have some leeway to find other ways to shrink the amount of candidates, like using the SHA1 hashes. The effort though is not really worth it, since with a little testing I found that the attack as it is already succeeds once every ~10 runs.
We just run it a few times and get our flag: hitcon{uNp@d_M3th0D_i5_am4Z1n9!}
Appreciation to HITCON for the amazing challenge! The whole CTF was challenging but rewarding, I will be waiting to play again next year :)
Here is the complete exploit. You can also download it from here.
HITCON, Crypto, AES, 2017
We feedback.
Let us know what you think of this article on twitter @towerofhanoi or leave a comment below!
On October 14th and 15th 2022 we participated in the Reply Cyber Security Challenge 2022. We solved many challenges and overall placed second (CTFtime). These are the writeups of the...
Continue Reading