This article continues the series of challenges write ups from Cryptohack. This time we are going to look at the vulnerability in AES-CFB8 which lays in the heart of Logonzero vulnerability (CVE-202–1472). I will explain the essentials, but if you feel like you need some more details, read this paper.
For the challenge we are provided with the server network address and its source code. Let’s check the source code:
We should consider two pieces of the provided code.
encryptis never called, so we don’t look there).
challengefunction, of course.
decrypt function does the following:
IVto first 16 bytes of the provided
ctto other part of
AESinstance and empty byte string
- For every byte of
stateand XOR corresponding
ctbyte with only first byte of the encrypted state. Add XOR result to resulting
ptstring. Finally remove first byte of
stateand add current
ctbyte to the end of
challenge function performs some actions according to the provided
option. We have three options:
authenticate— we must provide
password, it will be compared to the current password, that server holds. If passwords are equal, we get the flag.
reset_connection— just reinitializes
CFB8object with the new random key.
reset_password— changes server’s password according to our input. It takes some provided
token, decodes it from hex value and decrypts it with
CFB8. Next, it takes 4 last bytes of decrypted value as a password length and then takes corresponding amount of data from the start as a new password.
Now, it is obvious that we should somehow apply our attack within
reset_password option. We should be able to send such a
token that after it is decrypted we can guess
new_password value. But what can we do?
Once again let’s look at the
decrypt function. We can control
ciphertext, therefore we actually control the state. Too bad the resulting
pt value depends on the
cipher.encrypt result, and even knowing the
state at every iteration there is no way we can predict its output without the key. Well, the least we can do, is we can manufacture such a state that
ciphertext.encrypt will always return the same value, at least that’s good.
Another good thing is that only first byte of the AES encryption is used, so there are actually only 256 possible values for that byte. We could try and guess that value, but there is a better way.
Remember, that we also have
reset_connection option, which changes the key. The new AES key is still random, but there is a high possibility that new key will yield new encryption result for the same state. The most amazing thing is that there are such keys that will encrypt the state into some value that starts with
0. And there is 1 to 256 chance that new key will be such a key. What does it mean for us? It means, that if the first byte of encrypted state is
0, than the
ct byte will be put to
pt as is.
The last thing left — we need
state value to be the same for every iteration. Well, that’s easy, we just need to send single repeated byte as a ciphertext. Any byte will do, but the best value is
ciphertext only contains zeroes, state never changes, and if first byte of encrypted state is also
0 we will make
pt to contain only zeroes. And that’s great, because than we will have new password’s length equal to
0 as well, so we effectively erase the password.
Now let’s combine all above into an attack algorithm. There are actually only 3 steps in our algorithm:
tokenequal to all zeroes
- Try to
- If authentication successful, we get the flag. Otherwise, we send
reset_connectionto change the encryption key.
We repeat these steps over and over until we lucky enough to get such a random key that will encrypt our all zeroes state into some value that also starts with
0. There is 1 to 256 chance of that, so we will find that key soon enough. But we should automate that, of course. Here is the exploit scrip: