← Back to Writeups
CUSTOMEasySteganography

Find Me If You Can

XESXOR9/2/20263 min read
#steganography#custom#easy#office_archive_inspection#png_trailer_extraction#whitespace_binary_decoding

Find Me If You Can

Platform: hackerlab.pro | Category: Steganography | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2025-04-14 | Status: Solved Techniques: office_archive_inspection, png_trailer_extraction, whitespace_binary_decoding

Summary

Task: inspect a PPTX presentation with embedded PNG files and find hidden data outside the visible slides. Solution: extract bytes after PNG IEND and decode whitespace-only lines as binary with space=0 and tab=1.

Find Me If You Can — hackerlab.pro

Description

Найди меня, если сможешь

English summary: the archive contained a PowerPoint presentation. The goal was to inspect the presentation contents, locate hidden data, and recover a flag in the CODEBY{...} format.

Analysis

The first useful observation is that a .pptx file is an Office Open XML container, so it can be unpacked like a ZIP archive. After extraction, the interesting files were the embedded images under ppt/media/.

Running exiftool on ppt/media/image2.png reported extra data after the normal PNG end marker. That is a strong stego indicator because a valid PNG should end at the IEND chunk, and trailing bytes are ignored by viewers but still preserved in the file. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Extracting the trailer showed that it was not random binary data. It consisted only of spaces, tabs, carriage returns, and line feeds. A similar Hackerlab whitespace-stego pattern appeared in local writeups: data lines encoded bits with spaces and tabs, while single-tab lines acted as separators.

The trailer had 98 lines. Ignoring empty lines and lines equal to a single tab left the actual payload lines. Decoding each remaining line with space -> 0 and tab -> 1, then interpreting the result as one ASCII byte per line, produced the flag. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Solution

  1. Download and extract the archive.
  2. Open the PPTX as a ZIP archive and inspect ppt/media/.
  3. Run exiftool on the embedded PNG files.
  4. Notice that image2.png has trailer data after the PNG IEND chunk.
  5. Extract the bytes after IEND.
  6. Treat the trailer as whitespace steganography:
    • skip empty lines
    • skip lines equal to a single tab separator
    • map space -> 0
    • map tab -> 1
    • decode each remaining line as one binary ASCII byte
  7. Join the decoded bytes and remove the trailing NUL from the final padding line. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍
python
#!/usr/bin/env python3
from pathlib import Path


def extract_png_trailer(png_path: Path) -> bytes:
    data = png_path.read_bytes()

    # PNG file signature
    if not data.startswith(b"\x89PNG\r\n\x1a\n"):
        raise ValueError("Not a PNG file")

    iend = data.find(b"IEND")
    if iend == -1:
        raise ValueError("IEND chunk not found")

    # A PNG chunk layout is: length(4) + type(4) + data + crc(4)
    # For IEND, data length is 0, so the file should end 8 bytes after 'IEND'.
    trailer_offset = iend + 8
    return data[trailer_offset:]
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍


def decode_whitespace_lines(blob: bytes) -> str:
    text = blob.decode("ascii")
    result = []

    for raw_line in text.splitlines():
        if raw_line == "" or raw_line == "\t":
            continue
        if any(ch not in " \t" for ch in raw_line):
            continue

        bits = "".join("0" if ch == " " else "1" for ch in raw_line)
        value = int(bits, 2)
        if value != 0:
            result.append(chr(value))

    return "".join(result)


def main():
    png_path = Path("pptx_unpacked/ppt/media/image2.png")
    trailer = extract_png_trailer(png_path)
    flag = decode_whitespace_lines(trailer)
    print(flag)
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍


if __name__ == "__main__":
    main()

The script prints:

text
CODEBY{REDACTED}

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