Испорченное селфи (Corrupted Selfie)
Испорченное селфи (Corrupted Selfie)
Platform: bug-makers | Category: Misc | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-05-29 | Status: Solved Techniques: chosen_plaintext_oracle, index_encoding_probe, inverse_permutation_scatter, pixel_permutation_recovery
Summary
Task: a 1024x1024 selfie whose pixels were shuffled by an online photo filter, plus the URL of that service. Solution: chosen-plaintext oracle — upload a probe image where each pixel encodes its own flat index in RGB, read off the deterministic permutation from the lossless PNG output, then invert it (orig[dec]=shuffled) to recover the original image and read the flag.
Испорченное селфи (Corrupted Selfie) — Bug Makers CTF
Description
Моя подруга хотела обработать фото в новом сервисе, но что-то пошло не так и все пиксели на картинке перемешались. А оригинал был утерян. Осталось только испорченное фото. Помогите восстановить оригинал, расставьте все пиксели в правильном порядке.
English summary: A friend processed a photo in a new online service; something went wrong and all the pixels in the image got shuffled. The original was lost — only the corrupted photo remains. The goal is to restore the original by putting every pixel back in the correct order.
Provided artifacts:
selfie.jpg— a 1024x1024 RGB JPEG that looks like pure random noise (all pixels shuffled).- The URL of the service that corrupted it:
http://31.207.77.216:4021/— a web app "Photo Bug Filter Pro 3000" that lets you upload an image and applies a "rejuvenating filter" viaPOST /upload, returning the processed image.
Analysis
The service applies a fixed, deterministic pixel permutation (a seeded shuffle that is identical for every upload of the same dimensions), and the output is a lossless PNG. This is the entire vulnerability:
- Because the permutation is deterministic and dimension-stable, the same shuffle that scrambled the selfie can be reproduced on any image we choose.
- Because the output is lossless PNG, the permutation can be extracted exactly through a chosen-plaintext oracle attack: upload an image whose pixels are individually identifiable, observe where each pixel lands, then invert the permutation on the corrupted selfie.
No seed reverse engineering is needed — the permutation is recovered fully black-box. The only assumption that must be validated is determinism, which is confirmed by uploading the same probe twice and comparing the results.
The fact that the corrupted input is a lossy JPEG is irrelevant: the filter is a pure pixel permutation, so inverting the permutation reassembles whatever pixel values are present, JPEG artifacts and all.
Solution
-
Recon. Downloaded
selfie.jpg(1024x1024 RGB) — looks like random noise. Fetched the homepage and identified the upload endpointPOST /upload, which returns a processed image blob. -
Build an index-encoded probe. Create a 1024x1024 PNG where each of the 1,048,576 pixels encodes its own flat index in RGB:
R = idx & 0xFF,G = (idx>>8) & 0xFF,B = (idx>>16) & 0xFF. 20 bits of index fit comfortably into 24 bits of RGB, so every pixel is unique. -
Upload the probe and capture the lossless PNG output.
-
Decode the permutation. For each output position
p, decode the index it carries:dec[p] = R | (G<<8) | (B<<16). This means output positionpholds the original indexdec[p]. Verifydecis a valid bijection (1,048,576 unique values, min 0, max 1048575). -
Confirm determinism. Upload the probe a second time and verify the decoded permutation is byte-for-byte identical (
np.array_equal). This justifies reusing the recovered permutation to invert the selfie. -
Invert on the selfie. Since output position
pholds original indexdec[p], the relation isoriginal[dec[p]] = shuffled[p], i.e. a single numpy scatter assignmentorig[dec] = shuf. -
The recovered image is a clear anime-style selfie with the flag printed on the character's t-shirt.
python read-only • copy allowed#!/usr/bin/env python3 import subprocess import numpy as np from PIL import Image W = H = 1024 N = W * H URL = "http://31.207.77.216:4021/upload" # 1) Build index-encoded probe: each pixel encodes its own flat index in RGB idx = np.arange(N, dtype=np.uint32) r = (idx & 0xFF).astype(np.uint8) g = ((idx >> 8) & 0xFF).astype(np.uint8) b = ((idx >> 16) & 0xFF).astype(np.uint8) probe = np.stack([r, g, b], axis=1).reshape(H, W, 3) Image.fromarray(probe, "RGB").save("probe.png") def upload(path, out): subprocess.run( ["curl", "-s", "-X", "POST", "-F", f"file=@{path}", URL, "-o", out], check=True, ) def decode_perm(png_path): out = np.array(Image.open(png_path).convert("RGB")).reshape(-1, 3).astype(np.uint32) # dec[p] = original index now sitting at output position p return out[:, 0] | (out[:, 1] << 8) | (out[:, 2] << 16) # 2) Upload probe, decode the permutation upload("probe.png", "probe_out.png") dec = decode_perm("probe_out.png") assert len(np.unique(dec)) == N and dec.min() == 0 and dec.max() == N - 1, "not a bijection" # 3) Confirm determinism: same shuffle every time upload("probe.png", "probe_out2.png") dec2 = decode_perm("probe_out2.png") assert np.array_equal(dec, dec2), "permutation is not deterministic!" # 4) Invert the permutation on the corrupted selfie shuf = np.array(Image.open("selfie.jpg").convert("RGB")).reshape(-1, 3) orig = np.empty_like(shuf) orig[dec] = shuf # original[dec[p]] = shuffled[p] Image.fromarray(orig.reshape(H, W, 3), "RGB").save("recovered.png") print("recovered.png written — flag is printed on the t-shirt")