← Back to Writeups
CUSTOMMediumMisc

Испорченное селфи (Corrupted Selfie)

XESXOR9/2/20264 min read
#misc#custom#medium#chosen_plaintext_oracle#index_encoding_probe#inverse_permutation_scatter#pixel_permutation_recovery

Испорченное селфи (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" via POST /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

  1. Recon. Downloaded selfie.jpg (1024x1024 RGB) — looks like random noise. Fetched the homepage and identified the upload endpoint POST /upload, which returns a processed image blob.

  2. 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. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  3. Upload the probe and capture the lossless PNG output.

  4. Decode the permutation. For each output position p, decode the index it carries: dec[p] = R | (G<<8) | (B<<16). This means output position p holds the original index dec[p]. Verify dec is a valid bijection (1,048,576 unique values, min 0, max 1048575).

  5. 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. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  6. Invert on the selfie. Since output position p holds original index dec[p], the relation is original[dec[p]] = shuffled[p], i.e. a single numpy scatter assignment orig[dec] = shuf.

  7. The recovered image is a clear anime-style selfie with the flag printed on the character's t-shirt.

python
#!/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")

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍