Category: Misc / Steganography
Difficulty: Medium-Hard
Flag: KIB{aes_k3y_sl1ngs_christm4s_tun3}
Download Music Box v2.7z - Try to solve it yourself before reading the writeup!
You’re given a 7z archive:
Music Box v2.7z
The solution chain:
Instead of just extracting, we inspect the archive content as raw bytes:
strings "Music Box v2.7z" | grep -E "[0-9A-Fa-f]{32,}"
Among the output, we find a long hex string, e.g.:
XOR1_HEX:3027242730272c21271d363023212978620430232c2962...
We copy only the hex part (no XOR1_HEX: label).
This looks like an obfuscated message: long, clean hex, not random noise.
We suspect this hex is XOR-encoded with a single-byte key.
Using Python:
python3 - << 'EOF'
import binascii
hex_str = "3027242730272c21271d363023212978620430232c2962112b2c23363023626f62112b2e272c36620c2b252a366c352334"
data = binascii.unhexlify(hex_str)
key = 0x42 # XOR key used by the elves
decoded = bytes(b ^ key for b in data)
print("As text:", decoded.decode())
EOF
Output (example):
As text: reference_track: <name_of_song>.wav
This tells us exactly which track to analyze for Morse in the next step.
Extract the archive and list the contents:
7z x "Music Box v2.7z" -oMusicBox_v2
cd MusicBox_v2
ls
Among the audio files, we locate the reference_track mentioned above.
We open it in Audacity (or any audio editor), switch the track view to Spectrogram, and zoom in.
We see a clear dot–dash pattern: Morse code rendered as tones in the spectrogram.
Decoding the Morse (visually or using a decoder) gives us a passphrase:
holly!jolly!xmas
This will be used later as the AES passphrase.
The archive also contains two image files representing the presents, for example:
elf.png
santa.png
Initially, they appear broken. When we inspect them:
file elf.png santa.png
After reversing the XOR (see below), they turn out to actually be WebP (RIFF) images with .png extensions.
The challenge used a global XOR with key 0x42 over the entire image files.
We undo this:
python3 - << 'EOF'
key = 0x67
names = ["elf.png", "santa.png"]
for name in names:
with open(name, "rb") as f:
data = f.read()
fixed = bytes(b ^ key for b in data)
out = name.replace(".png", "_fixed.png")
with open(out, "wb") as f_out:
f_out.write(fixed)
print(f"{name} -> {out}")
EOF
Now we check:
file elf_fixed.png santa_fixed.png
Example result:
elf_fixed.png: RIFF (little-endian) data, Web/P image, ...
santa_fixed.png: RIFF (little-endian) data, Web/P image, ...
Opening them shows valid images (Santa vs Grinch).
The presents are not separate files; they are appended to the image files.
Because they’re raw AES ciphertext with no magic header, binwalk shows nothing useful.
WebP files are wrapped in a RIFF container:
0–3: "RIFF"4–7: a 32-bit little-endian size value = total_file_size - 88–11: "WEBP"Therefore, the expected length of the WebP file is:
expected_len = size_val + 8
Anything beyond that is extra data → our attached present.
We parse and extract that tail with Python:
python3 - << 'EOF'
import struct
for name in ["elf_fixed.png", "santa_fixed.png"]:
with open(name, "rb") as f:
data = f.read()
if data[:4] != b"RIFF":
print(f"{name}: not RIFF, magic =", data[:4])
continue
size_val = struct.unpack("<I", data[4:8])[0]
expected_len = size_val + 8
actual_len = len(data)
extra_len = actual_len - expected_len
print(f"{name}: actual={actual_len}, expected={expected_len}, extra={extra_len}")
if extra_len > 0:
extra = data[expected_len:]
out = name.replace(".png", "_present.gift")
with open(out, "wb") as f_out:
f_out.write(extra)
print(f" -> extracted attached data to {out}")
else:
print(" -> no attached data found")
EOF
We end up with:
elf_fixed_present.giftsanta_fixed_present.giftThese are small, high-entropy binary blobs - our encrypted presents.
From the challenge design:
File layout for each .gift:
salt (16 bytes) | iv (16 bytes) | ciphertext (...)
holly!jolly!xmas (from the Morse code)100000 (example value; must match the encryption script)We decrypt both *_present.gift files and see which one contains the real flag.
Using a virtual environment and pycryptodome:
python3 -m venv ctfenv
source ctfenv/bin/activate
pip install pycryptodome
Then:
nano decrypt_presents.py
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import MD5
ITERATIONS = 100000 # Must match the encrypt-side setting
KEY_LEN = 16 # AES-128
def pkcs7_unpad(data):
pad_len = data[-1]
if pad_len < 1 or pad_len > 16:
raise ValueError("Invalid padding")
if data[-pad_len:] != bytes([pad_len]) * pad_len:
raise ValueError("Invalid padding pattern")
return data[:-pad_len]
def decrypt_file(fname, password):
with open(fname, "rb") as f:
blob = f.read()
if len(blob) < 32:
raise ValueError(f"{fname}: blob too small for salt+iv")
salt = blob[0:16]
iv = blob[16:32]
ciphertext = blob[32:]
# PBKDF2-HMAC-MD5 → 16-byte key for AES-128
key = PBKDF2(password.encode(), salt,
dkLen=KEY_LEN,
count=ITERATIONS,
hmac_hash_module=MD5)
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext)
plaintext = pkcs7_unpad(plaintext)
return plaintext
if __name__ == "__main__":
password = "holly!jolly!xmas"
for name in ["elf_fixed_present.gift", "santa_fixed_present.gift"]:
print(f"=== {name} ===")
try:
pt = decrypt_file(name, password)
try:
print(pt.decode())
except UnicodeDecodeError:
print(repr(pt))
except Exception as e:
print(f"Error decrypting {name}: {e}")
print()
Run it:
python decrypt_presents.py
One present is a decoy (Grinch), the other contains the real flag.
The correct decrypted present yields the final flag:
KIB{aes_k3y_sl1ngs_christm4s_tun3}
This challenge is a nice multi-stage chain involving:
strings on a 7z container).