← Back to Writeups
HTBN/ASteganography

Deep Down There's something in the water...

XESXOR8/23/20264 min read
#steganography#htb#n/a

Deep Down There's something in the water...

Platform: Umasscybersec | Category: Steganography | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-11 | Status: Solved Techniques: duplicate_palette_recoloring, frame_montage_visualization, gif_palette_analysis, palette_index_isolation

Summary

Task: a 100x70 animated GIF hid text inside indexed color data rather than metadata or trailing bytes. Solution: preserve GIF palette indices with Pillow, isolate the duplicate-looking palette entry, and recolor index 1 to reveal the flag in the water; the final flag is the user-confirmed UMASS{REDACTED}.

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: umasscybersec | ID: 20260411_umasscybersec_deep_down_theres_something_in_the_water
  • Tags: hidden_text, pillow, animated_gif, palette_steganography, indexed_color, duplicate_palette
  • Indicators: Animated GIF with multiple frames but no useful metadata or appended payload, Indexed-color image where two palette entries look visually identical or nearly identical, Hidden content appears only after recoloring one palette index separately, Text is embedded in a visually busy area such as water or texture, Preserving original GIF palette indices matters more than RGB conversion
  • Source: 20260411_umasscybersec_deep_down_theres_something_in_the_water.md

Foothold

Vulnerability / Misconfiguration

  1. Duplicate_palette_recoloring
  2. Frame_montage_visualization
  3. Gif_palette_analysis
  4. Palette_index_isolation
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • duplicate_palette_recoloring
  • frame_montage_visualization
  • gif_palette_analysis
  • palette_index_isolation
  • Tags: hidden_text, pillow, animated_gif, palette_steganography, indexed_color, duplicate_palette

Original Writeup

<details><summary>Click to expand original content</summary>

Challenge

Provided file: CHALL.gif

This challenge gave a small animated GIF. The important clue was that it was an indexed-color format, so the hidden content could live in palette indices rather than in EXIF, appended data, or classic LSB channels.

Recon

Basic triage showed a normal GIF with no obvious extra payload:

file CHALL.gif
# GIF image data, version 89a, 100 x 70

exiftool CHALL.gif
# GIF Version : 89a
# Image Size  : 100x70
# Frame Count : 12

So the file was a GIF89a, size 100x70, with 12 frames.

Standard file-level checks did not reveal anything useful, which pushed the investigation toward the GIF's indexed-color structure. The task already had extracted frames and helper images under:

  • ./tasks/umasscybersec/Deep Down There's something in the water.../frames/
  • ./tasks/umasscybersec/Deep Down There's something in the water.../indexviz/
  • ./tasks/umasscybersec/Deep Down There's something in the water.../analysis/

The analysis directory included a montage and separated glyph view, which helped confirm that the hidden text sat inside the water region.

Analysis

The key idea is palette-index steganography in a GIF.

In an indexed image, a pixel does not directly store RGB values. It stores a palette index. If two palette entries are visually identical or nearly identical, the picture can look unchanged to the eye while still encoding different information through index choice.

That is exactly what happened here:

  • the GIF palette contained duplicate-looking / near-duplicate colors
  • those indices were used selectively in the water region
  • when one suspicious index was recolored independently, hidden red text appeared

For this kind of task, preserving palette information is critical. A naive RGB conversion destroys the distinction between equal-looking palette entries. Pillow can avoid that problem with:

from PIL import GifImagePlugin
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY

That loading strategy keeps the frame data usable for palette/index analysis instead of flattening everything too early.

Solution

1. Keep the GIF indexed and inspect frames

The useful workflow was:

#!/usr/bin/env python3
from pathlib import Path
from PIL import Image, ImageSequence, GifImagePlugin

GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY

gif = Image.open("CHALL.gif")
outdir = Path("analysis")
outdir.mkdir(exist_ok=True)

for i, frame in enumerate(ImageSequence.Iterator(gif)):
    idx = frame.copy()
    idx.save(outdir / f"frame_{i:02d}_indexed.png")

Then inspect palette entries and where each index appears in each frame.

2. Recolor suspicious palette indices

The breakthrough was isolating the duplicate-looking palette entries and recoloring them one at a time. In this challenge, index 1 was the meaningful one.

Example snippet:

#!/usr/bin/env python3
from PIL import Image, ImageSequence, GifImagePlugin

GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY

gif = Image.open("CHALL.gif")

for i, frame in enumerate(ImageSequence.Iterator(gif)):
    idx = frame.copy().convert("P")
    w, h = idx.size
    out = Image.new("RGB", (w, h), (0, 0, 0))
    pix = idx.load()
    dst = out.load()
    for y in range(h):
        for x in range(w):
            if pix[x, y] == 1:
                dst[x, y] = (255, 0, 0)
            else:
                dst[x, y] = (20, 20, 20)
    out.save(f"analysis/frame_{i:02d}_idx1.png")

Once index 1 was recolored separately, the hidden text became readable in the water.

3. Use montage / glyph separation to read the flag

The extracted outputs under analysis/ made the text much clearer:

  • indexviz_montage_x4.png
  • glyphs_separate.png
  • several enlarged / sheared crops of the exposed text

The font was ambiguous enough that OCR initially suggested:

UMASS{1N_A_G1FFY}

However, the final answer should be the user-confirmed flag:

UMASS{REDACTED}
</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR