← Back to Writeups
CUSTOMEasySteganography

Warmup

XESXOR9/2/20262 min read
#steganography#custom#easy#base4_encoding#zero_width_steganography

Warmup

Platform: hackerlab | Category: Steganography | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2025-12-21 | Status: Solved Techniques: base4_encoding, zero_width_steganography

Summary

Task: Text file with visible string but suspiciously large size. Solution: Extract zero-width Unicode characters (ZWNJ, ZWJ, PDF, BOM) and decode as base-4 encoding to reveal the flag.

Warmup — hackerlab.pro

Description

Steganography task. Given a text file Warmup.txt that contains the visible string "Did you get a good stretch?", but has a suspiciously large file size.

Analysis

When analyzing the file through xxd or opening it in a text editor that supports displaying invisible characters, a large number of zero-width Unicode characters are found hidden between regular characters.

The following characters were identified:

  • ZWNJ (\u200c, UTF-8: \xe2\x80\x8c) — Zero Width Non-Joiner
  • ZWJ (\u200d, UTF-8: \xe2\x80\x8d) — Zero Width Joiner
  • PDF (\u202c, UTF-8: \xe2\x80\xac) — Pop Directional Formatting
  • BOM (\ufeff, UTF-8: \xef\xbb\xbf) — Byte Order Mark ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The presence of four different invisible characters hints at using a base-4 numeral system, where each character encodes 2 bits of information.

Solution

To solve the task, a Python script was written that extracts these characters and decodes them according to the scheme:

  • ZWNJ -> 00
  • ZWJ -> 01
  • PDF -> 10
  • BOM -> 11

The resulting bit sequence is converted to bytes. The result is a string containing the flag.

python
#!/usr/bin/env python3
import sys

def solve():
    try:
        with open('Warmup.txt', 'r', encoding='utf-8') as f:
            content = f.read()
    except FileNotFoundError:
        print("File Warmup.txt not found")
        return
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

    # Unicode characters
    ZWNJ = '\u200c'  # \xe2\x80\x8c -> 00
    ZWJ  = '\u200d'  # \xe2\x80\x8d -> 01
    PDF  = '\u202c'  # \xe2\x80\xac -> 10
    BOM  = '\ufeff'  # \xef\xbb\xbf -> 11

    mapping = {
        ZWNJ: '00',
        ZWJ:  '01',
        PDF:  '10',
        BOM:  '11'
    }

    bits = ""
    for char in content:
        if char in mapping:
            bits += mapping[char]

    if not bits:
        print("No hidden characters found")
        return

    # Convert bits to bytes
    bytes_data = bytearray()
    for i in range(0, len(bits), 8):
        byte_str = bits[i:i+8]
        if len(byte_str) == 8:
            bytes_data.append(int(byte_str, 2))
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

    # The result might be UTF-16 or ASCII with null bytes
    # Try to decode or just print printable characters
    print("Decoded data (raw):", bytes_data)
    
    # Clean up null bytes and try to show the flag
    flag = bytes_data.replace(b'\x00', b'').decode('ascii', errors='ignore')
    print("Flag:", flag)

if __name__ == "__main__":
    solve()

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