# Challenge Overview: Lost Signal **Category:** Forensics **Event:** L3m0nCTF 2025 **Role:** Challenge Author > 🛠️ **Author Note** > This challenge was authored by me for **L3m0nCTF 2025**. > The following explanation describes the **intended forensic analysis path**. image ## Intended Analysis Path The challenge was designed to test: - interpretation of a provided seed as an ordering mechanism - rejection of traditional steganography tools in favor of programmatic analysis - pixel-level manipulation rather than file-level extraction - reconstruction of shuffled data using deterministic randomness - multi-bitplane extraction guided by a controlled permutation Without applying the seed correctly, all extracted data appears as noise. ## Evidence Provided File: [lost_signal.tar.gz](https://github.com/rozariyomartin/L3m0nCTF2025-Writeups/blob/main/Forensics/LostSignal/lost_signal.tar.gz) Clue: ``Seed = 739391`` No other hints. ## Analysis Phase 1 — Interpreting the Seed The challenge explicitly provides a **seed**. A seed is **not encryption** by itself — it is used to: - Initialize a pseudo-random number generator - Produce a **deterministic order** So from the question alone, the solver can infer: > The hidden data depends on a **specific order**, reproducible using the seed. Since: - The file is an image - Metadata, strings, binwalk, StegSolve show nothing The only reasonable conclusion is: The seed defines an **order of pixels**, not bytes or files. ## Analysis Phase 2 — Pixel-Level Analysis StegSolve fails because: - Any meaningful structure has been **shuffled** - No spatially coherent pattern exists So you must: - Load the image programmatically - Treat it as a pixel array ## Analysis Phase 3 — Isolating the Luminance Channel RGB mixes color and brightness. For hidden data, brightness is the most likely carrier because: - Small brightness changes are visually invisible - Color changes are more noticeable So convert the image to **YCbCr** and extract **Y (luminance)**. Code: ``` python3 - << 'EOF' from PIL import Image import numpy as np img = Image.open("challenge_color_random.png").convert("YCbCr") Y, Cb, Cr = img.split() Y_arr = np.array(Y, dtype=np.uint8) np.save("Y.npy", Y_arr) print("Y channel extracted:", Y_arr.shape) EOF ``` ## Analysis Phase 4 — Reconstructing the Permutation The seed must recreate the exact pixel order used during embedding. Code: ``` python3 - << 'EOF' import numpy as np seed = 739391 Y = np.load("Y.npy") h, w = Y.shape rng = np.random.RandomState(seed) indices = np.arange(h * w) rng.shuffle(indices) np.save("perm.npy", indices) print("Permutation generated") EOF ``` Now indices represents the correct pixel visit order. ## Analysis Phase 5 — Controlled Bitplane Extraction A simple LSB dump fails, which means: - More than one bit is used - Bits are interleaved The correct approach is: - Extract two least significant bits - Alternate between them - Follow the shuffled pixel order Bitplanes used: ``[0,1]`` Code: ``` python3 - << 'EOF' import numpy as np Y = np.load("Y.npy") perm = np.load("perm.npy") h, w = Y.shape LSBS = [0, 1] total_bits = h * w qr_flat = np.zeros(total_bits, dtype=np.uint8) for i in range(total_bits): pix_idx = perm[i // len(LSBS)] bitplane = LSBS[i % len(LSBS)] y = pix_idx // w x = pix_idx % w qr_flat[i] = (Y[y, x] >> bitplane) & 1 np.save("qr_flat.npy", qr_flat) print("Bitstream recovered") EOF ``` Without: - the seed - the correct order - both bitplanes the output is pure noise. ## Final Output The recovered bitstream must be reshaped back into an image. Code ``` python3 - << 'EOF' from PIL import Image import numpy as np qr_flat = np.load("qr_flat.npy") Y = np.load("Y.npy") h, w = Y.shape qr = qr_flat.reshape((h, w)) # invert for proper QR colors qr_img = (255 * (1 - qr)).astype(np.uint8) Image.fromarray(qr_img).save("solved_qr.png") print("QR image saved as solved_qr.png") EOF ``` The QR will be generated. Scanning the reconstructed QR code reveals the final flag. image **Flag : ``L3m0nCTF{1nv1s1bl3_b1tpl4n3_x0r_qr}``**