← Back to Writeups
CUSTOMEasySteganography

Platform X

XESXOR9/2/20263 min read
#steganography#custom#easy#html_entity_decoding#homoglyph_stego_decoding#holloway_twitter_stego

Platform X

Platform: hackerlab | Category: Steganography | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-04-08 | Status: Solved Techniques: html_entity_decoding, homoglyph_stego_decoding, holloway_twitter_stego

Summary

Task: a ZIP archive contains an HTML post whose payload is hidden behind HTML entities and Unicode lookalikes inside social-media style text. Solution: decode the entities first, then apply Holloway's Twitter homoglyph mapping with its 48-character alphabet to recover the secret string and wrap it as the flag.

Platform X — Hackerlab

Description

Мы нашли данный пост в популярной социальной сети, возможно, тут есть что-то интересное.

English summary: the archive contains a single post.html file with a suspicious social-media post. The goal is to extract hidden text from the post and recover the CODEBY{...} flag.

Analysis

The first useful observation was that post.html did not directly contain readable hidden text. Instead, the interesting content was inside a <code> block filled with HTML entities. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

That means a naive inspection can fail: before testing any steganography idea, the HTML entities must be decoded into normal visible text.

After entity decoding, the text still looked slightly wrong. Some characters were Unicode lookalikes rather than normal ASCII letters, and the spacing pattern was unusual. This strongly suggested classic text steganography based on homoglyph substitutions in a social-media post.

The matching technique was Holloway's "Secret Messages: Steg of the Dump" / Twitter homoglyph encoder-decoder: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  • carrier text stays readable to a human
  • selected characters are replaced with visually similar Unicode variants
  • the decoder maps those substitutions back into bit chunks

An important detail is that the Holloway decoder does not use 5-bit chunks. Its secret alphabet contains 48 characters:

text
 abcdefghijklmnopqrstuvwxyz123456789'0.:/\%-_?&;

Since 48 values require 6 bits, the hidden message must be reconstructed in 6-bit chunks.

Applying the Holloway homoglyph mapping to the already HTML-decoded text produced: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

text
REDACTED

Wrapping it in the required format gives the final flag.

Solution

Step 1: decode the HTML entities

Extract post.html, copy the encoded text from the <code> block, and decode the entities first.

Step 2: identify the homoglyph stego scheme

The decoded text is readable, but some letters and spaces are Unicode variants. That pattern matches the Holloway Twitter/X homoglyph steganography scheme.

Step 3: decode using the Holloway mapping

Use the mapping from:

https://holloway.nz/steg/steg-of-the-dump.js ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The decoder scans each character in the carrier text:

  • if the character is plain ASCII, it contributes the zero-code for that glyph family
  • if it is a homoglyph variant, it contributes the corresponding non-zero bit pattern
  • all collected bits are then split into secret_alphabet_bitlength chunks
  • each chunk indexes the 48-character secret alphabet

Minimal reproduction snippet

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

secret_alphabet = " abcdefghijklmnopqrstuvwxyz123456789'0.:/\\%-_?&;"
bitlen = len(secret_alphabet).bit_length()  # 48 chars -> 6 bits
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# 1) Read the entity-encoded text from post.html and decode it first.
entity_encoded = "...text copied from the <code> block..."
tweet = html.unescape(entity_encoded)

# 2) Build the homoglyph lookup from Holloway's steg-of-the-dump.js.
#    For each supported base character, ASCII maps to zero bits and each
#    Unicode lookalike maps to a non-zero value of the same width.
holloway_lookup = {
    # Example only. Use the full table from the Holloway JS.
    # 'A': '00', 'A': '01', 'Α': '10', 'А': '11',
}
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

bits = "".join(holloway_lookup[ch] for ch in tweet if ch in holloway_lookup)

while len(bits) % bitlen:
    bits += "0"

decoded = ""
for i in range(0, len(bits), bitlen):
    value = int(bits[i:i+bitlen], 2)
    if value < len(secret_alphabet):
        decoded += secret_alphabet[value]

print(decoded)
# REDACTED

In practice, using the full lookup table from Holloway's script on the HTML-decoded text returns the hidden message exactly.

Step 4: wrap the result as the flag

text
CODEBY{REDACTED}

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