def aes_encrypt(key: bytes, msg: bytes) -> bytes: """ Encrypts the input with AES and the given key """ padded_msg = pkcs7_pad(msg, 16) cipher = AES.new(key, AES.MODE_ECB) encrypted_msg = cipher.encrypt(padded_msg) return encrypted_msg
def aes_encrypt(msg: bytes) -> bytes: """ Encrypts the input with AES, adding mystery padding! Spooky! """ padding = b64decode(MYSTERY_PADDING) padded_msg = pkcs7_pad(msg + padding, 16) # Use our unknown but consistent key cipher = AES.new(KEY, AES.MODE_ECB) encrypted_msg = cipher.encrypt(padded_msg) return encrypted_msg
def randcryptor(msg: bytes) -> bytes: """ Encrypts the input with AES in either ECB or CBC, randomly. """ # Pad the message with 5-10 random bytes padding = os.urandom(randint(5, 10)) padded_msg = pkcs7_pad(padding + msg + padding, BLOCK_SIZE) # Generate a random key key = generate_aes_key() # Randomly choose whether to use ECB or CBC mode = 'cbc' if bool(randint(0, 1)) else 'ecb' if mode == 'cbc': iv = os.urandom(BLOCK_SIZE) encrypted_msg = cbc_encrypt(padded_msg, key, iv) else: # ECB cipher = AES.new(key, AES.MODE_ECB) encrypted_msg = cipher.encrypt(padded_msg) return encrypted_msg, mode # FOR TESTING ONLY
def cbc_encrypt(msg: bytes, key: bytes, iv: bytes = None) -> bytes: """ Encrypt a message with AES in CBC mode. If `iv` is not set, the vector is set to all zeroes. """ assert iv is None or len(iv) == BLOCK_SIZE cipher = AES.new(key, AES.MODE_ECB) padded_msg = pkcs7_pad(msg, BLOCK_SIZE) previous_block = iv if iv is not None else b'\x00' * BLOCK_SIZE output = [] for idx in range(0, len(padded_msg), BLOCK_SIZE): block = padded_msg[idx:idx+BLOCK_SIZE] # XOR with previous block xored_block = xor(block, previous_block) result = cipher.encrypt(xored_block) output.append(result) previous_block = result return b''.join(output)
api.reset() # 1. Create a normal message that is block-aligned (doesn't require any # padding). # from=#4567&tx_list=#12345:123455 txns = OrderedDict({12345: 123455}) base_request = attacker_client.request_v2(txns) base_msg = base_request[:-BLOCK_SIZE] base_mac = base_request[-BLOCK_SIZE:] assert len(base_msg) % BLOCK_SIZE == 0 # Given our desired block, modify the block by: # 1. XOR with our base mac, essentially zeroing that MAC out when the # MAC for the longer message is computed # 2. XOR with the original valid MAC, knowing that that will be what is # fed in in the real, server-side MAC calculation # # Append this to our original message, and then sign it to get a valid MAC # that we can append to the original, valid message along with the desired # block amount = 1000000 desired_block = bytes(f';4567:{amount}'.encode()) hacked_block = xor(xor(pkcs7_pad(desired_block), base_mac), valid_mac) hacked_mac = attacker_client.generate_mac(base_msg + hacked_block) hacked_request = valid_msg + desired_block + hacked_mac assert api.process_v2(hacked_request) assert api.accounts[ ATTACKER_ID] == amount, "Attacker's account should have the amount credited"
# Create duplicate blocks to identify where the # first full block we have access to begins, after the unknown prefix for i in range(block_size * 2, block_size * 3): profile = bytes(str(profile_for(' ' * i)).encode()) dups = detect_duplicate_blocks(aes_encrypt(key, profile)) if dups: break # Padding to align input + unknown prefix to the next block index initial_padding = i - block_size * 2 # Fails if earlier duplicates exist first_block = min(map(min, dups.values())) # Create "admin" + padding input that exactly occupies one block profile = initial_padding * ' ' + pkcs7_pad(b'admin').decode() encrypted_profile = aes_encrypt(key, bytes(str(profile_for(profile)).encode())) admin_block = encrypted_profile[first_block:first_block+block_size] # Create an input that shifts the "user" portion of "role=user" into its # own block profile = (end_align_padding + len('user')) * ' ' encrypted_profile = aes_encrypt(key, bytes(str(profile_for(profile)).encode())) # Now swap out the last block with our padded "admin" block chopped_profile = encrypted_profile[:-block_size] + admin_block decrypted_profile = aes_decrypt(key, chopped_profile) assert b'role=admin' in decrypted_profile