I opened up the zip file containing the encrypted files and malware.py.
One thing I noticed about malware.py is that it uses the AES CTR algorithmto encrypt the files. That's essentially a stream cipher and I will referto the stream being xor'd with the plaintext as "keystream".
First, let's review AES CTR. What AES CTR does is that, instead of encryptingthe plaintext by running AES on the plaintext, as is done in ECB, it generatesa keystream by taking a counter block and, for every block that needs to be encrypted,runs AES on the counter block and increments it by 1 after each iteration. Thiskeystream is then xor'd with the plaintext to get the ciphertext. An importantthing to note here is that this is completely deterministic.
More documentation about AES can be found herehttps://pycryptodome.readthedocs.io/en/latest/src/cipher/aes.html
Now, I also notice that the IV used to encrypt the files varies only by 1every time. That's an absurdly small amount and is enough to raisedoubt on the efficacy program used here.
I noticed that a PNG is in there. Now, those who have studied PNGs will knowthat the first 16 bytes of a good PNG are predictable (8 byte magic header,and 8 bytes for a length of 13 and chunk header 'IHDR'). So one might have theinitial idea to get the keystream for that file by doing an xor of the encryptedfile with the predicted bytes to get the original keystream.
This is a good idea, except that 16 bytes isn't enough to get the flag.Let's not abandon this idea completely yet, though.
It took me a bit to actually realize that one of the files encrypted ismalware.py. That's actually really awesome, because we have the entireplaintext of that file, so we can get a really long keystream from that.
How do we use that keystream, though? It's not immediately obvious.After a bit of thinking, I realized I don't know what order os.listdir()actually outputs filenames. I decided copy the encrypted files to a test folder,renamed them without the extension. Then I ran os.listdir on my local machine inside this new folder and I had an epiphany after I saw the following output
['malware.py', 'flag.txt', 'shopping_list.txt', 'CTF-favicon.png']
If this was the order the files were encrypted in, then getting the flagis actually really easy. malware_keystream starting at byte 16 is the sameas flag_keystream starting at byte 0, since malware_keystream is initializedwith initial value IV and flag_keystream is initialized with initial valueIV + 1 (thus, at block 1 in malware, counter block = (IV) + 1, and at block 0 in flag, counterblock = (IV + 1)).
So I then opened up the python interpreter and went ahead to write the codeI needed. I made an assumption when writing it, which is that malware.py isthe first file encrypted.
After that, I entered the following in my interpreter
plaintext = open('malware.py', 'rb').read()
ciphertext = open('malware.py.enc', 'rb').read()
keystream = bytes([p ^ c for p, c in zip(plaintext, ciphertext)])
open('keystream', 'wb').write(keystream)
flagplain = open('flag.txt.enc', 'rb').read()
flagkeystream = keystream[16:]
print(bytes([p ^ k for p, k in zip(flagplain, flagkeystream)]))
flagkeystream = keystream[32:]
print(bytes([p ^ k for p, k in zip(flagplain, flagkeystream)]))
It actually outputs the flag when flagkeystream = keystream[32:]but you'd keep going with this pattern for keystream[48:],keystream[64:], ..., until you get the flag, or untilyou realize that flag.txt was actually encrypted before malware.py(which you hope is not the case)
After you do this, you get the flag, which isUMASS{m4lw4re_st1ll_n33ds_g00d_c4ypt0}