← Back to Writeups
CUSTOMMediumSteganography

Imposter

XESXOR9/2/20264 min read
#steganography#custom#medium#inverted_bit_extraction#lsb_url_encoding#multi_stage_steganography#steghide_bruteforce#linux

Imposter

Platform: hackerlab | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2025-12-22 | Status: Solved Techniques: inverted_bit_extraction, lsb_url_encoding, multi_stage_steganography, steghide_bruteforce

Summary

Task: PNG image with Among Us character containing hidden data. Solution: Extract URL from LSB in red channel using inverted bit positions, download second JPG image, bruteforce steghide password with stegseek to get the flag.

Imposter — hackerlab

Description

"There is 1 Imposter."

Provided a ZIP archive with an Among Us character image (red imposter on green background).

Analysis

Step 1: Initial Image Analysis

File Imposter.png — PNG image 842x1024, RGB. Red Among Us character on green background.

bash
file Imposter.png
# PNG image data, 842 x 1024, 8-bit/color RGB, non-interlaced

Step 2: LSB Analysis

When analyzing the first row of pixels, anomalies were found — 92 pixels differ from the background by 1 in the red channel: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

  • Background: RGB(71, 112, 76)
  • "Imposter" pixels: RGB(70, 112, 76)

The difference is only in the LSB of the red channel (71 → 70, i.e., bit changed from 1 to 0).

Step 3: Decoding the Hidden URL

Positions of "imposter" pixels in the first row encode a binary message. After bit inversion, we get a URL:

python
from PIL import Image
import numpy as np

img = Image.open('Imposter.png')
arr = np.array(img)

# Analyze first row
row0 = arr[0]
background = (71, 112, 76)

# Find pixels that differ from background
imposter_cols = []
for col in range(arr.shape[1]):
    pixel = tuple(row0[col])
    if pixel != background:
        imposter_cols.append(col)
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

print(f"Found {len(imposter_cols)} imposter pixels")
# Found 92 imposter pixels

# Positions encode bits (presence = 0, absence = 1 after inversion)
# Decoding gives URL: https://ibb.co/d2TxBjh

Hidden URL: https://ibb.co/d2TxBjh

Step 4: Second Image

The URL downloads image sticker.jpg — a robot/cyborg with Russian text "УЛЬТРАХОРОШ" (ULTRAKHOROSH).

bash
curl -L "https://ibb.co/d2TxBjh" -o sticker.jpg

Step 5: Steghide Bruteforce

JPG image — classic candidate for steghide. Using stegseek for password bruteforce: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

bash
# Via Docker
docker run --rm -v $(pwd):/data -v /tmp:/wordlists \
    rickdejager/stegseek /data/sticker.jpg /wordlists/rockyou.txt \
    -xf /data/extracted.txt

# Result:
# [i] Found passphrase: "696969"
# [i] Original filename: "flag.txt"
# [i] Extracting to "extracted.txt"

Found password: 696969

Step 6: Flag Extraction

bash
steghide extract -sf sticker.jpg -p "696969" -xf flag.txt
cat flag.txt
# CODEBY{REDACTED}

Solution (Full Script)

python
#!/usr/bin/env python3
"""
Imposter - Multi-stage steganography solver
Stage 1: LSB in PNG -> URL
Stage 2: Steghide in JPG -> Flag
"""
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

from PIL import Image
import numpy as np
import subprocess
import requests

# Stage 1: Extract URL from PNG LSB
def extract_url_from_png(filename):
    img = Image.open(filename)
    arr = np.array(img)
    
    # Background color
    bg = (71, 112, 76)
    
    # Find imposter pixels in row 0
    row0 = arr[0]
    width = arr.shape[1]
    
    # Build binary string from pixel positions
    bits = []
    for col in range(width):
        pixel = tuple(row0[col])
        # Imposter pixel (differs by 1 in R channel) = 0, background = 1
        if pixel[0] == bg[0] - 1 and pixel[1:] == bg[1:]:
            bits.append('0')
        else:
            bits.append('1')
    
    # Invert and decode
    inverted = ''.join('1' if b == '0' else '0' for b in bits)
    
    # Convert to bytes
    url_bytes = []
    for i in range(0, len(inverted), 8):
        byte = inverted[i:i+8]
        if len(byte) == 8:
            char = chr(int(byte, 2))
            if char.isprintable() or char in '\n\r\t':
                url_bytes.append(char)
    
    url = ''.join(url_bytes).strip()
    # Extract actual URL
    if 'https://' in url:
        start = url.find('https://')
        end = url.find(' ', start) if ' ' in url[start:] else len(url)
        return url[start:end].strip()
    return url
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

# Stage 2: Steghide bruteforce with stegseek
def extract_from_steghide(jpg_file, wordlist='/tmp/rockyou.txt'):
    cmd = [
        'docker', 'run', '--rm',
        '-v', f'{jpg_file}:/data/image.jpg',
        '-v', f'{wordlist}:/wordlists/rockyou.txt',
        'rickdejager/stegseek',
        '/data/image.jpg',
        '/wordlists/rockyou.txt',
        '-xf', '/data/output.txt'
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)
    return result.stdout

if __name__ == "__main__":
    # Stage 1
    url = extract_url_from_png('Imposter.png')
    print(f"[+] Hidden URL: {url}")
    
    # Download second image
    # ... download sticker.jpg from url ...
    
    # Stage 2
    # stegseek sticker.jpg rockyou.txt
    # Password: 696969
    # Flag in flag.txt

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

Additional Notes

Multi-stage Steganography

The task demonstrates classic multi-stage steganography:

  1. PNG → URL — LSB in red channel with inverted bits
  2. URL → JPG — external image hosting (ImgBB)
  3. JPG → Flag — steghide with dictionary password

Tools for Steghide Bruteforce

  • stegseek — fast bruteforce for steghide (recommended)
  • stegcracker — alternative, slower
  • John the Ripper — with steghide module

Typical Steghide Passwords

  • Numeric patterns: 123456, 696969, 111111
  • Simple words: password, secret, hidden
  • Context-related: imposter, amongus, red ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Why Bit Inversion?

Authors often invert bits to:

  • Complicate automatic detection
  • Make "0" more rare (fewer modifications)
  • Add an additional layer of obfuscation ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍