icon-sketch
- Category: forensics · Author: riyc
- Description: "the pee people found the first version of the gaslightCTF icon... apparently theres a flag in here?"
- Attachment:
icon.zip→icon.png(2000×800 RGBA, gaslightCTF logo)
Flag: gaslightCTF{i5_th4t_supp0s3d_2b_p1ss?}
TL;DR
unzip icon.zip→icon.pngstrings icon.png→ two base64 blobs in the XMP metadata (Iptc4xmpExt:AOTitleanddc:title) + a plaintext hintdGhlIHBlZSBwZW9wbGUgc2FpZCB0byBkZWNvZGUgYW5kIHB1dCB0aGUgdGl0bGVzIHRvZ2V0aGVy.- Hint decodes to: "the pee people said to decode and put the titles together".
- Title A: base64 →
"2e 2e ..."hex → ASCII:........CTF..._.h4._..pp0s3..2b_.1s..}(dots = blanks). - Title B: base64 →
tzhortsg...{r5.g..g.hf.....w_...k..h?.→ Atbash →gaslight...{i5.t..t.su.....d_...p..s?.. - The two decoded titles are perfectly complementary (A's letters sit exactly where B has dots and vice-versa; no overlaps, no gaps). Overlay
A[i] if A[i]!='.' else Atbash(B)[i]→ flag.
Recon chain
Base64 + hex, and a raw hint
strings -a icon.png shows (besides the PNG header/XMP):
dGhlIHBlZSBwZW9wbGUgc2FpZCB0byBkZWNvZGUgYW5kIHB1dCB0aGUgdGl0bGVzIHRvZ2V0aGVy→ the pee people said to decode and put the titles together (base64, plaintext hint).
The two "titles" in XMP
<Iptc4xmpExt:AOTitle><rdf:li xml:lang='x-default'>
MmUgMmUgMmUgMmUgMmUgMmUgMmUgMmUgNDMgNTQgNDYgMmUgMmUgMmUgNWYgMmUgNjggMzQgMmUgNWYgMmUgMmUgNzAgNzAgMzAgNzMgMzMgMmUgMmUgMzIgNjIgNWYgMmUgMzEgNzMgMmUgMmUgN2Q=
</rdf:li></Iptc4xmpExt:AOTitle>
<dc:title><rdf:li xml:lang='x-default'>
dHpob3J0c2cuLi57cjUuZy4uZy5oZi4uLi4ud18uLi5rLi5oPy4=
</rdf:li></dc:title>- Title A: base64 →
2e 2e 2e ... 43 54 46 ... 7d(space-separated hex bytes) →bytes.fromhex(...)→........CTF..._.h4._..pp0s3..2b_.1s..}. Dots = missing chars. - Title B: base64 →
tzhortsg...{r5.g..g.hf.....w_...k..h?.. The leadingtzhortsgis Atbash ofgaslight(t↔g z↔a h↔s o↔l r↔i s↔h). Atbash the whole string →gaslight...{i5.t..t.su.....d_...p..s?..
Put them together
A and Atbash(B) are the same 38-char skeleton with complementary blanks: every index has a letter in exactly one of the two. Merge with A[i] if A[i] != '.' else Atbash(B)[i]:
A : ........CTF..._.h4._..pp0s3..2b_.1s..}
AB: gaslight...{i5.t..t.su.....d_...p..s?.
= gaslightCTF{i5_th4t_supp0s3d_2b_p1ss?}Commands (copy-paste)
unzip icon.zip && cd extracted
# hint
echo 'dGhlIHBlZSBwZW9wbGUgc2FpZCB0byBkZWNvZGUgYW5kIHB1dCB0aGUgdGl0bGVzIHRvZ2V0aGVy' | base64 -d
# decode titles
python3 - <<'EOF'
import base64
a='MmUgMmUgMmUgMmUgMmUgMmUgMmUgMmUgNDMgNTQgNDYgMmUgMmUgMmUgNWYgMmUgNjggMzQgMmUgNWYgMmUgMmUgNzAgNzAgMzAgNzMgMzMgMmUgMmUgMzIgNjIgNWYgMmUgMzEgNzMgMmUgMmUgN2Q='
b='dHpob3J0c2cuLi57cjUuZy4uZy5oZi4uLi4ud18uLi5rLi5oPy4='
A=bytes.fromhex(base64.b64decode(a).decode()).decode()
at=lambda s: ''.join(chr(219-ord(c)) if c.islower() else chr(155-ord(c)) if c.isupper() else c for c in s)
AB=at(base64.b64decode(b).decode())
print(''.join(A[i] if A[i]!='.' else AB[i] for i in range(len(A))))
EOFFindings / lessons
stringson a PNG is the first move: text chunks + XMP are where hidden data lives.exiftool -aorstrings | grep -i 'xmp\|title'exposes it.- Base64 output can be another encoding: title A was base64 → hex-with-spaces → ASCII. Chain until it reads.
- Atbash (a↔z, b↔y, …) turned
tzhortsgintogaslight— a reliable tell when a lowercase blob starts with a jumbled 8-letter word. - "Put the titles together" → the two decoded strings share a skeleton and complement each other exactly (A's blanks == B's letters). Overlay with blank-fill.
- Image pixels were a dead end: the logo is just the gaslightCTF icon (14 unique colors, rare colors = anti-aliasing, nothing stashed).
Escalation toolkit (if blank-fill overlay isn't the rule)
- Try XOR of the two decoded strings, or interleave A/Atbash(B) by the dot pattern.
- Check for a THIRD title/metadata field (IPTC
ArtworkTitle,DocumentName,Comment). - If pixels matter: mask the non-logo colors and OCR; check alpha-channel stego;
zstegif installed.
Flag
gaslightCTF{i5_th4t_supp0s3d_2b_p1ss?}