Post

GaslightCTF: Icon Sketch Forensics Writeup

GaslightCTF: Icon Sketch Forensics Writeup

Surprise, I decided to bring you a non-pwn writeup for a change. In recent CTFs I have been looking at forensics challenges involving Steganography with image, audio and video files. This challenge Icon-sketch is an image steganography challenge from Gaslight CTF 2026. Well to be honest, it is more like a Misc/Crypto challenge if you ask me, but it is what it is. Let’s see how I solved this challenge.

Handout

We are given this png file, hopefully metadata isn’t destroyed here:

Handout Image

Next thing is to look at metadata, use an online tool like https://exif.tools/ or exiftool app and look at unusual text in description, title, other fields etc. These 3 stood out to me:

1
2
3
Document name = dGhlIHBlZSBwZW9wbGUgc2FpZCB0byBkZWNvZGUgYW5kIHB1dCB0aGUgdGl0bGVzIHRvZ2V0aGVy
Title         = dHpob3J0c2cuLi57cjUuZy4uZy5oZi4uLi4ud18uLi5rLi5oPy4=
ArtworkTitle  = MmUgMmUgMmUgMmUgMmUgMmUgMmUgMmUgNDMgNTQgNDYgMmUgMmUgMmUgNWYgMmUgNjggMzQgMmUgNWYgMmUgMmUgNzAgNzAgMzAgNzMgMzMgMmUgMmUgMzIgNjIgNWYgMmUgMzEgNzMgMmUgMmUgN2Q=

After that I also tried zsteg to see if there is anything unusual, like LSB stego etc but it didn’t flag anything other than the title field already mentioned above.

Decode Document Name

This one was quite straightforward, just base64 decode:

1
2
3
4
5
import base64

document_name = 'dGhlIHBlZSBwZW9wbGUgc2FpZCB0byBkZWNvZGUgYW5kIHB1dCB0aGUgdGl0bGVzIHRvZ2V0aGVy'
decoded_str   = base64.b64decode(document_name).decode("utf-8")
print(decoded_str)

This gives us the decoded text: the pee people said to decode and put the titles together. Sounds like an important clue where we need to combine titles.

Decode Title

This one also looked like base64 encoded, decoding it with base64 gives us this flag looking string:

tzhortsg...{r5.g..g.hf.....w_...k..h?.

If you can notice, beginning part has the same length as gaslight and probably the following three dots is CTF making up the flag’s prefix. How do we convert this to gaslight ? If you align them:

1
2
tzhortsg
gaslight

Notice the changes: t->g g->t a->z, specially a to z change indicates reversal of alphabet which is called Atbash. To decode it, we simply translate lower case letters to their reverse order and ignore numbers or dots:

1
2
3
4
5
6
7
8
9
10
def atbash_decoder(text: str) -> str:
    # Map letters to their reversed counterparts
    lower_abc = "abcdefghijklmnopqrstuvwxyz"
    translation_table = str.maketrans(lower_abc, lower_abc[::-1])
    return text.translate(translation_table)

title = 'dHpob3J0c2cuLi57cjUuZy4uZy5oZi4uLi4ud18uLi5rLi5oPy4='
b64_decoded = base64.b64decode(title).decode("utf-8")
part1 = atbash_decoder(b64_decoded)
print(part1)    

And this gives us one piece of the puzzle: gaslight...{i5.t..t.su.....d_...p..s?.

Decode Artwork Title

Given the hint and previous result, this should give us the missing letters/dots of the flag. Again we first base64 decode it and it gives us this result:

2e 2e 2e 2e 2e 2e 2e 2e 43 54 46 2e 2e 2e 5f 2e 68 34 2e 5f 2e 2e 70 70 30 73 33 2e 2e 32 62 5f 2e 31 73 2e 2e 7d

It is a string of hexadecimal numbers, we just need to conver it to a string:

1
2
3
4
artwork_title = 'MmUgMmUgMmUgMmUgMmUgMmUgMmUgMmUgNDMgNTQgNDYgMmUgMmUgMmUgNWYgMmUgNjggMzQgMmUgNWYgMmUgMmUgNzAgNzAgMzAgNzMgMzMgMmUgMmUgMzIgNjIgNWYgMmUgMzEgNzMgMmUgMmUgN2Q='
b64_decoded = base64.b64decode(artwork_title).decode("utf-8")
part2 = bytes.fromhex(b64_decoded).decode('utf-8')
print(part2)

And this gives us the second part of the flag: ........CTF..._.h4._..pp0s3..2b_.1s..}

Combine Parts

We just need to combine two parts to get the final flag:

1
2
3
# Combine by picking the character that is not a dot '.'
flag = "".join(c1 if c1 != '.' else c2 for c1, c2 in zip(part1, part2))
print(flag)

And we get the flag: gaslightCTF{i5_th4t_supp0s3d_2b_p1ss?}.

Final Script

Here is the full script combining all these steps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import base64

def atbash_decoder(text: str) -> str:
    # Map letters to their reversed counterparts
    lower_abc = "abcdefghijklmnopqrstuvwxyz"
    translation_table = str.maketrans(lower_abc, lower_abc[::-1])
    return text.translate(translation_table)

# the pee people said to decode and put the titles together
document_name = 'dGhlIHBlZSBwZW9wbGUgc2FpZCB0byBkZWNvZGUgYW5kIHB1dCB0aGUgdGl0bGVzIHRvZ2V0aGVy'
decoded_str   = base64.b64decode(document_name).decode("utf-8")
print(decoded_str)

title = 'dHpob3J0c2cuLi57cjUuZy4uZy5oZi4uLi4ud18uLi5rLi5oPy4='
b64_decoded = base64.b64decode(title).decode("utf-8")
part1 = atbash_decoder(b64_decoded)
print(part1)

artwork_title = 'MmUgMmUgMmUgMmUgMmUgMmUgMmUgMmUgNDMgNTQgNDYgMmUgMmUgMmUgNWYgMmUgNjggMzQgMmUgNWYgMmUgMmUgNzAgNzAgMzAgNzMgMzMgMmUgMmUgMzIgNjIgNWYgMmUgMzEgNzMgMmUgMmUgN2Q='
b64_decoded = base64.b64decode(artwork_title).decode("utf-8")
part2 = bytes.fromhex(b64_decoded).decode('utf-8')
print(part2)

# Combine by picking the character that is not a dot '.'
flag = "".join(c1 if c1 != '.' else c2 for c1, c2 in zip(part1, part2))
print(flag)

Nice challenge, during the CTF I actually used CyberChef: https://gchq.github.io/CyberChef/ to decode all these steps :) It is much easier and faster that way, script was just a challenge to myself to see how I would solve it in python. My suggestion, use CyberChef it is amazing. As always, keep learning!

This post is licensed under CC BY 4.0 by the author.