Text & Unicode steganography
Plain text looks like the last place to hide data, which is exactly why it works. The payload lives in characters you cannot see — trailing whitespace, zero-width code points, or letters that look identical but are not. The tell is almost always a byte count larger than the visible text, or text that "copies wrong".
Whitespace encoding (SNOW)
Data is encoded in trailing spaces and tabs at the end of lines — invisible in most editors.
- Reveal it:
cat -A file.txtshows$at line ends and^Ifor tabs; VS Code's Render Whitespace does the same visually. - Extract it: stegsnow implements the SNOW scheme (up to 7 spaces + a tab per column group, optional password):
$ stegsnow -C file.txt # extract, no password
$ stegsnow -p 'secret' -C file.txt # extract with a password
Zero-width characters
Zero-width space (U+200B), non-joiner (U+200C) and joiner (U+200D) render as nothing but carry bits. Two zero-width characters → binary → bytes.
- Inspect code points with
xxd/cat -A, or paste into the 330k zero-width decoder; the AES-protected variant is StegCloak. - Compare
wc -c file.txtto the number of characters you can actually see — a large gap means hidden code points.
# List every non-ASCII or whitespace code point and its position
import sys
for i, ch in enumerate(sys.stdin.read()):
if ord(ch) > 127 or ch.isspace():
print(i, hex(ord(ch)), repr(ch))
Tag characters, variation selectors and bidi
The same "invisible character" idea has several modern variants. Run the code-point dump above first, then match the ranges you find:
- Unicode tag block (U+E0000) — "ASCII smuggling". Deprecated tag characters
U+E0020–U+E007F map 1:1 to ASCII with offset
0xE0000(soA→ U+E0041). They render as nothing but are read by parsers and LLMs. Decode with ASCII Smuggler.
# Tag characters -> ASCII
print("".join(chr(ord(c) - 0xE0000) for c in text if 0xE0000 <= ord(c) <= 0xE007F))
- Variation selectors (emoji smuggling) — any byte can ride on a single base
character: bytes 0–15 → U+FE00…U+FE0F, bytes 16–255 → U+E0100…U+E01EF. One
visible glyph (😀,
a, …) can carry an arbitrary byte string. Decode with Paul Butler's tool. - Sneaky bits — invisible-math operators: ASCII→binary with
0→ U+2062 (invisible times) and1→ U+2064 (invisible plus). Map the two symbols back to bits. - Bidirectional override (RLO) — U+202E / U+202D and the isolates
U+2066–U+2069 reorder how text displays versus how it is stored (the
"Trojan Source" trick, CVE-2021-42574). Detect with
hexdump -Corgrep -P '[\x{202a}-\x{202e}\x{2066}-\x{2069}]', then strip them to read the true logical order.
Homoglyphs
Different Unicode code points can look identical — Latin a (U+0061) versus
Cyrillic а (U+0430). Mixing them encodes data or watermarks the text. An
Irongeek homoglyph decoder
recovers the message.
Normalize carefully
When you clean up suspicious text, keep a copy of the original — Unicode normalization destroys exactly the code points that carry the payload.
Exotic encodings
Recovered a blob that is not yet a flag? Recognize the encoding:
- ASCII in 8-bit binary blocks starts each letter with
0; strip it and you have 7-bit blocks. Text may also be a single big integer (sometimes in a non-decimal base). - Braille — Unicode Braille pattern glyphs (U+2800 block,
⠿) decode with a Braille translator. Morse — a string of only./-(or two repeated tokens); CyberChef From Morse Code. Both frequently yield a password for a later stage rather than the flag. - CyberChef with the Magic operation (enable Intensive mode) auto-detects and peels nested encoding chains (base64 → base32 → hex → …) — iterate until the flag prefix appears.
python2> "48656c6c6f20776f726c6421".decode("hex") # -> "Hello world!"
Esoteric languages
Esolangs have unusual charsets that stand out in a file. The ones seen most in CTF:
- Brainfuck — only
+-<>[]., - Whitespace — only spaces, tabs, newlines (overlaps with whitespace stego above)
- Piet — the program is an image of colored blocks
- Malbolge — deliberately unreadable
brainfuck>
++++++++++[>+>+++>+++++++>++++++++++<<<<-]>>>++.>+.+++++++..+++.
The esolangs language list helps identify an unknown one.
Related
- Drive a challenge from the cheatsheet.
- Whitespace also appears in source files and archives — Files & Archives.
