Santhacklaus 2019: Jacques ! Au secours !
Funny crypto challenge in which we have to crack the vulnerable AES implementation of a Python ransomware to decrypt JPG files.
One of our VIP clients, who whises to remain anonymous, has apparently been hacked and all their important documents are now corrupted.
Can you help us recover the files? We found a starnge piece of software that might have caused all of this.
MD5 of the file:
Decompile the malware code
Here is what the chall_files.zip looks like inside.
So we have a compiled Python script that encrypted 4 images, 1 of them containing the flag.
There is also a READ_THIS.txt:
First thing to do is decompile virus.cpython-37.pyc. For that we can use
uncompyle6, installable via
$ pip install --user uncompyle6
$ uncompyle6 virus.cpython-37.pyc > virus.py
Here is what
virus.py looks like.
The script has precise targets. If the infected user is one of the them, then the script will encrypt all files in
C:\Users using AES128-ECB.
That what happened with our “client” and their 4 images. Let’s try to find what goes wrong with this script and how to recover the files.
Spot the vuln
The function we have to analyze is the
lock_file() one, which is used on each file separatly.
The malware works like the following:
- Generate a 128-bits key
- Generate a 128-bits random value (called iv)
- Open a file and cut it into 128-bits blocks
- XOR each block with the iv
- Encrypt the XOR result with the key using AES-ECB
The first interesting thing is the choice of the key used for encryption: it is the MD5 digest of the target’s username.
At this point we don’t know who has been hacked so we will have to test all possible keys, that means all names in
TARGETS list (100 values). However, the iv value is completely secret and we have no way to retrieve it. This is embarrassing because we only need the key and the iv to decrypt all our files.
But if we take a closer look, the only iv value that we can’t predict is the one used to encrypt the first block only. In fact, after encrpyting a block, the iv is overridden by the value of the encrypted block.
Without this line, it would have been impossible to decrypt the files. But now that we have spotted this, we can predict the iv value for each block, except the first one of each file. Personally, I understood it well when I did a diagram, so here is a little one.
Now in order to decrypt a file, we will have to do the opposite operation:
- Decrypt the block with the key
- XOR the obtained bytes with the previous block
This will work except for the first block. It is XORed with an unknown value, so how to recover it? Fortunately, the malware is very kind with us and leaves the encrypted file’s extension after locking it: “DCIM-0533.jpg.hacked”. This is very useful because it allows us to guess the first bytes of the file thanks to magic numbers! Wikipedia tells us that a .jpg file starts as below.
FF D8 FF E0 00 10 4A 46 49 46 00 01
There are 12 of them, and we know a block has a length of 16. Let’s just append some null bytes.
FF D8 FF E0 00 10 4A 46 49 46 00 01 00 00 00 00
And we have our (theorical) first block.
Here is a little recap.
- To decrypt a block, we need
- The key
- The corresponding iv
- The key is common to each block and we have 100 possible candidates
- The iv changes for each encrypted block
- 1st block: random unknown value
- Other blocks: value of previous encrypted block
Script the recovery tool
First, let’s copy the
xorbytes function from virus.py to our decrypt.py, as XORing a value is the same operation than unXORing it.
(Though I had to change the line
res = '' to
res = b'' to avoid a
Then, let’s write our
decrypt function, which will take
key as parameters and generate a (hopefully valid) .jpg file.
We get the value of the first block thanks to the magic bytes.
Our AES cipher:
The algo of block decryption:
- Take 2 blocks of image.jpg.hacked
- Decrypt the 2nd block with
key: it gives the XORed block
- XOR the XORed block with the 1st block: it gives the clear block
- Write the clear block to image.jpg
- Do that again
Now we have to run this function for each possible key, i.e. each value of this list:
I was telling me this was going to be a little time consuming when, by luck, my eye stuck on a “Jack Sheerack” value right in the middle of the list.
I remembered the name of the chall and decided to test this single value first, so I created a
TARGET variable and decrypted all the files using it.
So we got it! Our VIP client was effectively “Jack Sheerack”, and the trick with the magic bytes + the null bytes worked :D
Thanks Mathis Hammel for the chall, I am a crypto-newbie and challs like this make me want to practice more!