← Back to Writeups
CUSTOMMediumSteganography

Foreign Body

XESXOR9/2/20263 min read
#steganography#custom#medium#empty_passphrase_extraction#payload_carving_validation#steghide_seed_discovery#zip_header_repair#linux

Foreign Body

Platform: hackerlab | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-04-08 | Status: Solved Techniques: empty_passphrase_extraction, payload_carving_validation, steghide_seed_discovery, zip_header_repair

Summary

Task: a clean-looking JPEG with 'YOU HAVE BEEN HACKED!' hides a steghide payload without obvious metadata or appended data. Solution: use stegseek to identify steghide, extract the payload with the empty passphrase, repair a one-byte ZIP header corruption, and read the embedded Python file containing the flag.

Foreign Body — Hackerlab

Description

Инородное тело

English summary: the challenge provides a JPEG image named hacked.jpg. The file looks ordinary and displays YOU HAVE BEEN HACKED!, but the goal is to recover the hidden CODEBY{...} flag.

Analysis

Initial reconnaissance

The archive unpacked to a single file: hacked.jpg.

Basic triage gave a very clean result:

  • file and exiftool identified a normal baseline JPEG
  • image size was 480x272
  • metadata was minimal JFIF only
  • no appended bytes appeared after the JPEG EOI marker FF D9
  • no obvious embedded ZIP, RAR, 7z, PNG, or PDF signatures were present ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Visually, the image only showed the message YOU HAVE BEEN HACKED!.

That pushed the investigation toward classic JPEG steganography rather than metadata tricks or appended-file carving.

False lead: channel, bitplane, and OutGuess-style probing

Several standard JPEG stego paths were tested first:

  • RGB channel inspection
  • bitplane extraction
  • additional contrast and difference views
  • OutGuess-style extraction attempts

OutGuess produced multiple random-looking blobs and inconsistent output sizes depending on the key. This was a false lead: the extracted data did not contain stable plaintext, recognizable magic bytes, or the flag. The important lesson here is that noisy output from JPEG stego tools is not proof of a correct path. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The pivot: confirming steghide with stegseek --seed

The decisive clue came from:

bash
stegseek --seed hacked.jpg

This reported a likely hidden steghide payload and exposed several useful properties:

  • possible seed / passphrase candidate: 3b75655e
  • embedded plain size: about 1.1 KB (compressed)
  • encryption: rijndael-128
  • mode: cbc

At that point the file no longer looked like a generic JPEG puzzle. It looked like a steghide container inside a perfectly normal JPEG.

Solution

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

Step 1: Extract the steghide payload

Run stegseek against the image with a wordlist. In this case, the successful passphrase was the empty string:

bash
docker run --rm -v "$PWD":/steg -v "/Users/sergeyskorobogatov/Projects/Agents/CTF/resources/wordlists/passwords":/wordlists:ro rickdejager/stegseek:latest /steg/hacked.jpg /wordlists/rockyou.txt -xf /steg/steghide_extracted.bin

Important output:

  • passphrase found: ""
  • original embedded filename: data.bin

This produced steghide_extracted.bin. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Step 2: Validate the extracted blob

The blob looked like ZIP data, but unzip failed because the local file header was corrupted.

The start of the file was:

text
50 4b ff 04

but a valid ZIP local header must begin with:

text
50 4b 03 04

At the same time, the central directory and end-of-central-directory records were still present. That strongly suggested a deliberately damaged container rather than a wrong extraction.

Step 3: Repair the ZIP header

Fix the third byte from 0xff to 0x03, then save the result as steghide_fixed.zip: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

python
#!/usr/bin/env python3
from pathlib import Path

data = bytearray(Path("steghide_extracted.bin").read_bytes())
assert data[:4] == bytes.fromhex("50 4b ff 04")
data[2] = 0x03
Path("steghide_fixed.zip").write_bytes(data)

After that, the archive extracted normally.

Step 4: Read the recovered file

Unzip the repaired archive:

bash
unzip steghide_fixed.zip -d zip_fixed

The ZIP contained CODEBY.py. Opening it revealed:

python
print("CODEBY{REDACTED}")

So the final flag is recovered directly from the embedded script. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Full working recovery script

python
#!/usr/bin/env python3
from pathlib import Path
import zipfile

blob_path = Path("steghide_extracted.bin")
fixed_zip = Path("steghide_fixed.zip")
out_dir = Path("zip_fixed")

data = bytearray(blob_path.read_bytes())

if data[:4] == bytes.fromhex("50 4b ff 04"):
    data[2] = 0x03
else:
    raise SystemExit(f"Unexpected header: {data[:4].hex()}")

fixed_zip.write_bytes(data)

with zipfile.ZipFile(fixed_zip, "r") as zf:
    zf.extractall(out_dir)
    code = zf.read("CODEBY.py").decode("utf-8", errors="replace")
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

print(code)

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