Category: Forensics
Flag: TACHYON{change_the_header?}
Challenge Description
I tried to transfer this from my old drive, but the file was corrupted during the move. The image viewer says it’s an invalid format, but I know the data is still in there…
Analysis
This one immediately smelled like header corruption, so I started by asking the file what it thought it was.
file "chall.png"chall.png: OpenPGP Public KeyA PNG being identified as an OpenPGP key usually means the magic bytes at the start are broken, while the rest of the structure might still be intact. A quick hex peek confirmed that: the first 8 bytes were wrong, but right after that I could already see IHDR, which is the first mandatory PNG chunk.
rsxxd "chall.png" | head -300000000: 9a61 5f58 0d0a 1a0a 0000 000d 4948 4452 .a_X........IHDR
00000010: 0000 027b 0000 027b 0103 0000 00a1 fac5 ...{...{........
00000020: d400 0000 0173 5247 4200 aece 1ce9 0000 .....sRGB.......So instead of brute-forcing random repairs, I only restored the PNG signature bytes (89 50 4E 47 0D 0A 1A 0A) and left everything else untouched.
from pathlib import Path
d = bytearray(Path("chall.png").read_bytes())
d[:8] = b"\x89PNG\r\n\x1a\n"
Path("chall_fixed.png").write_bytes(d)
print("wrote", len(d), "bytes")wrote 1172 bytesThen I validated whether the repaired file was truly a valid PNG and not just “openable by luck.”
file "chall_fixed.png" && pngcheck -v "chall_fixed.png"chall_fixed.png: PNG image data, 635 x 635, 1-bit colormap, non-interlaced
File: chall_fixed.png (1172 bytes)
chunk IHDR at offset 0x0000c, length 13
635 x 635 image, 1-bit palette, non-interlaced
chunk sRGB at offset 0x00025, length 1
rendering intent = perceptual
chunk gAMA at offset 0x00032, length 4: 0.45455
chunk PLTE at offset 0x00042, length 6: 2 palette entries
chunk pHYs at offset 0x00054, length 9: 3779x3779 pixels/meter (96 dpi)
chunk IDAT at offset 0x00069, length 1047
zlib: deflated, 32K window, maximum compression
chunk IEND at offset 0x0048c, length 0
No errors detected in chall_fixed.png (7 chunks, 97.7% compression).At that point the image was structurally clean, so I checked for machine-readable hidden content. QR scan immediately returned a Base64 payload, which was delightfully direct.

zbarimg -q "chall_fixed.png"QR-Code:dGFjaHlvbntjaGFuZ2VfdGhlX2hlYWRlcj99Decoding that payload produced the flag text in lowercase prefix form.
import base64
print(base64.b64decode("dGFjaHlvbntjaGFuZ2VfdGhlX2hlYWRlcj99").decode())tachyon{change_the_header?}Given the challenge prefix requirement, the final normalized flag is TACHYON{change_the_header?}.
Solution
file "chall.png"chall.png: OpenPGP Public Keyfrom pathlib import Path
d = bytearray(Path("chall.png").read_bytes())
d[:8] = b"\x89PNG\r\n\x1a\n"
Path("chall_fixed.png").write_bytes(d)zbarimg -q "chall_fixed.png"QR-Code:dGFjaHlvbntjaGFuZ2VfdGhlX2hlYWRlcj99import base64
print(base64.b64decode("dGFjaHlvbntjaGFuZ2VfdGhlX2hlYWRlcj99").decode())tachyon{change_the_header?}TACHYON{change_the_header?}