Here are a few challenges I found interesting and fun to solve from CubeCTF 2025.
Discord
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!
Initial findings
From the disk image, I found a user named Admin. In Downloads, there are two executables: a Discord installer and encrypt.exe.
Running DIE on encrypt.exe shows it is a PyInstaller-packed Python script. I extracted it with pyinstxtractor_ng, which suggested encrypt.pyc as the entry point. Decompiling it gives the following:
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
def get_appdata_path() -> Path:
if os.getenv('APPDATA') is None:
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'
with open(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():
if not file.is_file():
continue
if file.suffix == '.enc':
print(f'Skipping {file} (already encrypted)')
continue
try:
with open(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}...')
with open(file.with_suffix('.enc'), 'wb') as fp2:
fp2.write(ciphertext)
file.unlink()
This script kills Discord, reads the user ID from the sentry scope file, and encrypts cache files with AES-CBC. The encrypted files are stored with a .enc extension.
Decryption
To recover the images, I derived the same key and decrypted all .enc files:
import json
from pathlib import Path
from Cryptodome.Cipher import AES
from Cryptodome.Protocol.KDF import PBKDF2
from Cryptodome.Util.Padding import unpad
def detect_file_type(data):
if not data:
return None
signatures = {
b'\xff\xd8\xff': 'jpg',
b'\x89PNG\r\n\x1a\n': 'png',
b'GIF87a': 'gif',
b'GIF89a': 'gif',
b'RIFF': 'webp',
}
for signature, ext in signatures.items():
if data.startswith(signature):
if signature == b'RIFF' and len(data) > 12 and data[8:12] == b'WEBP':
return 'webp'
return ext
return None
base = Path('artifacts')
sentry_path = base / 'scope_v3.json'
cache_path = base / 'Cache_data'
with sentry_path.open('r', encoding='utf-8') 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'
print(f"Decrypting .enc files in {cache_path}...")
image_exts = {"jpg", "png", "gif", "webp"}
for file in cache_path.iterdir():
if not file.is_file() or file.suffix != '.enc':
continue
try:
ciphertext = file.read_bytes()
except PermissionError:
print(f"Skipping {file} (file open)")
continue
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
try:
plaintext = unpad(cipher.decrypt(ciphertext), 16)
except ValueError:
print(f"Skipping {file} (decryption failed)")
continue
detected_ext = detect_file_type(plaintext)
base_name = file.stem
if detected_ext in image_exts:
out_name = f"{base_name}.{detected_ext}"
else:
out_name = f"{base_name}.decrypted"
out_path = file.with_name(out_name)
counter = 1
while out_path.exists():
out_path = file.with_name(f"{base_name}_{counter}{out_path.suffix}")
counter += 1
out_path.write_bytes(plaintext)
print(f"Decrypted {file} -> {out_path}")
The decrypted image contained the flag.
Operator
I think someone has been hiding secrets on my server. Can you find them?
Initial findings
From the TCP streams, I observed an executable being transferred and extracted the ELF. Loading it in IDA revealed it’s a simple chat server/client that uses XOR encryption for communication with the following key:
[04, 07, 17, 76, 42, 69, B0, 0B, DE, 18, 23, 22, 1E, ED, F7, AE]
Solution
The chat function reads from stdin and the socket, XORing all data with the 16-byte key before sending or after receiving. After decrypting the traffic with the extracted key, we can recover the conversation:
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...
Flag: cube{c00l_0p3r4t0rs_us3_mult1_st4g3_p4yl04ds_8ab49338}