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.
3539333437353461373137333532333833333333333933303335333036383639dcb33a3cca39412b58f4095cbc30faf95f72c9c1e71c01aa0a1b0f89c11f03b751dccaa5bb3ec011cc0a40a08ba87827071e1fb52716c891a1263a53af721a18
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[1])
with open('half_flag.txt', 'w') as f:
f.write(RSA_done.decode('utf-8') + stuff[0])
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
encrypt_RSA
function - 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.