Guidepoint Security CTF 2021 - Half (crypto)
For this challenge we get some encoded/encrypted string and the python script that was used to produce it.
from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP, AES from Crypto import Random # May be able to get rid of from random import choice, shuffle from OpenSSL import crypto import binascii import base64 pub = crypto.load_publickey(crypto.FILETYPE_PEM, base64.b64decode('LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF0YmEwY2x2RWllS3A0MUtYaGlpUwpqWnluc2E5RlQzTmpVUE1ZaVBVLzN5L2IySU8zcnFaZmh5RTRCNlAzYXpueDRZMkRoYVVVZFNnN0V5OHJzZ29jCis0dzlIdDYwSTdEWWUxblVKeUt1ekZyTDZESmdxSFR6Sml3SHBCWFNTVnVhaU5KY2NRVXJKMWNaRzdTVG44YmcKbzBCdHNGT0tyVzVzTzNyOGxNWitxWDVldXNZWW9UMDd6U0p5T1V4WVNJcWlwUVpPcEc3Y2JNYVhQZlZaaERDbwpyOW9UVFZaUFA1ZzlqOHNoSmdDVnJLeXE4V2dQTk1sWDRBMVhKQnpIcXFZN2RTK2NZRFhuMmc2dmxOa2RESXpmCjd6U1ZxL0NWZzA1MG1CdXZYdTVWaWVheHhZQnREb0xUQ0JWMmcyYXlOY2pac2tJVmhFbXpvTjNveEd5dFFVNFIKUXdJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==')) pubkey = crypto.dump_publickey(crypto.FILETYPE_PEM, pub) def random_string(size, chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"): return ''.join(choice(chars) for z in range (size)) def encrypt_FileString( raw ): BS = 16 pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) key = bytes(random_string(16), 'utf-8') raw = bytes(pad(raw),'utf-8') iv = Random.new().read( AES.block_size ) cipher = AES.new( key, AES.MODE_CBC, iv ) return binascii.hexlify(iv + cipher.encrypt( raw )).decode('utf-8').strip(), binascii.hexlify(key) def encrypt_RSA(pubkey, aeskey): try: rsakey = RsA.importKey(pubkey) rsakey = PKCS1_OAEP.new(rsakey) encrypted = rsakey.encrypt(aeskey) except: encrypted = aeskey return binascii.hexlify(encrypted) flag = 'FlagWentHere!' stuff = encrypt_FileString(flag) RSA_done = encrypt_RSA(pubkey, stuff) with open('half_flag.txt', 'w') as f: f.write(RSA_done.decode('utf-8') + stuff) f.close()
A lot of the time spent on this challenge was just figuring out what the heck this script is doing exactly, so let’s get that cleared up first;
- There is a flag
- The flag gets AES-CBC encrypted
- The AES encryption key gets passed to the
- The RSA encrypted AES key, the hex encoded AES-CBC IV, and the hex encoded AES-CBC ciphertext of the flag gets written to a file
The big “AH-HAH!” in this challenge is in the
encrypt_RSA function. Specifically what happens if there is some exception in the RSA encryption process;
except: encrypted = aeskey
AH-HAH! So the AES key is not RSA-encrypted at all but only hex encoded when an exception occurs. Since this is a CTF I did not dig into why the RSA operation might have failed but just proceeded under the assumption that it did.
Now it just becomes a matter of decoding the output string we got and doing the AES decryption. From the
encrypt_FileString function we can see the key should be 16 bytes long, but was hex encoded before being returned and passed to the
encrypt_RSA function, which hex encoded it again. So we need to take the first 64 characters of our string and hex decode them twice to get our AES-CBC key. Then we get the next 32 characters for our 16-byte hex encoded IV. (We know it’s 16 bytes because
AES.block_size is 16.) The remainder of our given output is our hex encoded ciphertext.
Now all we need to do is pass these parameters to cyberchef and have it do the decryption to get our flag.