Files
ctf/2026/cscg/rev/connivance/decode_blob.py
2026-03-22 21:53:40 +01:00

67 lines
2.4 KiB
Python

import struct
import argparse
import os
from Crypto.Cipher import AES
def derive_aes_key():
# 1. The hardcoded 64-bit key chunks from the VM bytecode
chunk1 = 0x034898EFB531EE6F
chunk2 = 0x2A6F20B991CAA263
# 2. Pack them into a 16-byte array (Little-Endian to match the x86/VM architecture)
key_bytes = struct.pack('<QQ', chunk1, chunk2)
# 3. Apply the VM's BITWISE_NOT operation to invert every byte
final_key = bytes(~b & 0xFF for b in key_bytes)
return final_key
def decrypt_payload(input_path, output_path):
if not os.path.exists(input_path):
print(f"[-] Error: Could not find '{input_path}'")
return
key = derive_aes_key()
print(f"[*] Derived 128-bit AES Key: {key.hex()}")
# Initialize AES in ECB mode (standard for VMs lacking an explicit IV instruction)
# Note: If the first 16 bytes decode correctly but the rest is garbage,
# change this to AES.MODE_CBC with iv=b'\x00'*16
cipher = AES.new(key, AES.MODE_ECB)
print(f"[*] Starting block decryption (512-byte chunks)...")
with open(input_path, 'rb') as f_in, open(output_path, 'wb') as f_out:
bytes_processed = 0
while True:
# The VM processes exactly 512 bytes (0x200) per loop iteration
chunk = f_in.read(512)
if not chunk:
break
# AES requires 16-byte alignment. 512 is perfectly divisible by 16.
# If the very last chunk is cut off, we trim it to prevent crypto crashes.
if len(chunk) % 16 != 0:
print(f"[!] Warning: Truncating unaligned trailing bytes at EOF.")
chunk = chunk[:-(len(chunk) % 16)]
if not chunk:
break
# Decrypt the block and write it to disk
decrypted_chunk = cipher.decrypt(chunk)
f_out.write(decrypted_chunk)
bytes_processed += len(chunk)
print(f"[+] Success! Decrypted {bytes_processed} bytes.")
print(f"[+] Unpacked payload saved to: {output_path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Static Decryptor for Connivance Payload")
parser.add_argument("input_file", help="Path to the encrypted 'blob' file")
parser.add_argument("-o", "--output", default="decrypted_payload.bin", help="Output file path")
args = parser.parse_args()
decrypt_payload(args.input_file, args.output)