Category: Steganography
Flag: UVT{N0th1nG_iS_3mp7y_1n_sP4c3}
Challenge Description
HTTP 404: Everything Not Found
Analysis
I started with basic archive triage to see whether the challenge was truly “empty” or just layered. The ZIP held three files: a PNG, a whitespace-heavy TXT, and a JS artifact, which immediately suggested a multi-stage stego chain rather than a single hidden string.
file "empty.zip"
7z l "empty.zip"empty.zip: Zip archive data, made by v2.0 UNIX, extract using at least v2.0, ...
Listing archive: empty.zip
...
2026-02-27 01:16:51 ..... 1486 779 empty.png
2026-02-27 01:16:51 ..... 8700 1024 empty.txt
2026-02-27 01:16:51 ..... 24126 2529 empty.js
...The JS file looked like mostly decoy text, but it contained a giant VOID_PAYLOAD string made of zero-width characters. Decoding just U+200B/U+200C as bits gave an actual ZIP stream with PK\x03\x04 magic, so that was the first real pivot point.
import re
from pathlib import Path
js = Path("empty_work/empty.js").read_text("utf-8", errors="ignore")
p = re.search(r"VOID_PAYLOAD\\s*=\\s*`(.*?)`\\s*;", js, re.S).group(1)
zw = [c for c in p if c in "\\u200b\\u200c"]
bits = "".join("0" if c == "\\u200b" else "1" for c in zw)
out = bytes(int(bits[i:i+8], 2) for i in range(0, len(bits) - 7, 8))
print("zero_width_chars", len(zw))
print("decoded_len", len(out))
print("magic", out[:4])
zero_width_chars 7664
decoded_len 958
magic b'PK\x03\x04'Once that hidden ZIP was carved, listing it showed a single encrypted flag.png using AES-256 Deflate, so the remaining problem was password recovery.
7z l -slt "empty_work/decoded/map_200b0_200c1.bin"Path = empty_work/decoded/map_200b0_200c1.bin
Type = zip
Physical Size = 958
Path = flag.png
Size = 8237
Packed Size = 786
Encrypted = +
Method = AES-256 Deflate
Characteristics = NTFS WzAES : EncryptThe whitespace document was not junk either. Decoding each line as 8 bits (space=0, tab=1) revealed operator notes explicitly hinting that the signal lives in blue-channel faint bits and must be sampled with an every-third cadence.
from pathlib import Path
raw = Path("empty_work/empty.txt").read_bytes().splitlines()
out = []
for ln in raw:
ws = [b for b in ln if b in (0x20, 0x09)]
if len(ws) == 8:
bits = "".join("0" if b == 0x20 else "1" for b in ws)
out.append(int(bits, 2))
text = bytes(out).decode("utf-8", "replace")
for i, line in enumerate(text.splitlines(), 1):
if any(k in line for k in ["CAPTAIN", "faintest", "blue starlight", "every third heartbeat", "void is hiding"]):
print(f"{i}:{line}")6:CAPTAIN'S NOTE
9:The real clue is in the faintest part of the signal, not the color you see,
12:We only saw it when sampling the blue starlight... and not at every point.
13:A pattern. A cadence. Like taking every third heartbeat along the grid.
15:Once you recover the whisper from the image, it opens what the void is hiding.That clue matched the image perfectly: pulling blue LSB bits with the row-wise cadence row0 x=0::3, row1 x=2::3, row2 x=1::3 produced a short plaintext containing the ZIP password.
from PIL import Image
import numpy as np
img = np.array(Image.open("empty_work/empty.png").convert("RGB"))
b = img[:, :, 2] & 1
seq = np.concatenate([b[0, 0::3], b[1, 2::3], b[2, 1::3]])
msg = bytes(int("".join(str(int(x)) for x in seq[i:i+8]), 2) for i in range(0, 256, 8))
print(msg.decode("latin1"))\x00\x1dZIP_PASSWORD=D4rKm47T3rrr;END\xffAfter stripping framing bytes, the password D4rKm47T3rrr decrypted the hidden archive cleanly and flag.png contained the literal flag string.
7z x -y -p"D4rKm47T3rrr" "empty_work/decoded/map_200b0_200c1.bin" -o"empty_work/final"Extracting archive: empty_work/decoded/map_200b0_200c1.bin
...
Everything is Okstrings -a "empty_work/final/flag.png" | rg -o 'UVT\{[^}]+\}'UVT{N0th1nG_iS_3mp7y_1n_sP4c3}The satisfying part here was how each artifact carried one precise clue for the next layer: zero-width payload to hidden ZIP, whitespace note to cadence rule, cadence rule to password, then final extraction.

Solution
# solve.py
#!/usr/bin/env python3.12
import re
import subprocess
from pathlib import Path
import numpy as np
from PIL import Image
def decode_void_zip(js_path: Path, out_zip: Path) -> None:
js = js_path.read_text("utf-8", errors="ignore")
payload = re.search(r"VOID_PAYLOAD\s*=\s*`(.*?)`\s*;", js, re.S).group(1)
zw = [c for c in payload if c in ("\u200b", "\u200c")]
bits = "".join("0" if c == "\u200b" else "1" for c in zw)
raw = bytes(int(bits[i:i + 8], 2) for i in range(0, len(bits) - 7, 8))
out_zip.write_bytes(raw)
def recover_password_from_blue_lsb(png_path: Path) -> str:
img = np.array(Image.open(png_path).convert("RGB"))
b = img[:, :, 2] & 1
seq = np.concatenate([b[0, 0::3], b[1, 2::3], b[2, 1::3]])
msg = bytes(int("".join(str(int(x)) for x in seq[i:i + 8]), 2) for i in range(0, 256, 8)).decode("latin1")
m = re.search(r"ZIP_PASSWORD=([^;]+);", msg)
if not m:
raise RuntimeError("password marker not found")
return m.group(1)
def main() -> None:
work = Path("empty_work")
decoded_zip = work / "decoded" / "map_200b0_200c1.bin"
decoded_zip.parent.mkdir(parents=True, exist_ok=True)
decode_void_zip(work / "empty.js", decoded_zip)
password = recover_password_from_blue_lsb(work / "empty.png")
final_dir = work / "final"
final_dir.mkdir(parents=True, exist_ok=True)
subprocess.run(
[
"7z",
"x",
"-y",
f"-p{password}",
str(decoded_zip),
f"-o{final_dir}",
],
check=True,
)
out = subprocess.check_output(
"strings -a empty_work/final/flag.png | rg -o 'UVT\\{[^}]+\\}'",
shell=True,
text=True,
).strip()
print(out)
if __name__ == "__main__":
main()python3.12 solve.pyUVT{N0th1nG_iS_3mp7y_1n_sP4c3}