Hidden
Hidden
Platform: TaipanByte CTF | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-02-14 | Status: Solved Techniques: 3-bit (octal) encoding via 8 unique character types, Brute-force mapping using known plaintext prefix, Hex analysis of multi-byte UTF-8 sequences, Unicode whitespace steganography
Summary
Given a file hide.tb for download. The task name "Hidden" hints at hidden data.
Hidden — TaipanByte CTF
Description
Given a file hide.tb for download. The task name "Hidden" hints at hidden data.
Analysis
-
Initial file inspection:
bash read-only • copy allowedfile hide.tb # hide.tb: Unicode text, UTF-8 textThe file is only 198 bytes but is identified as Unicode text.
-
Visual inspection: The file contains two lines:
- Line 1:
Some message is here— visible text, but with two hidden Unicode whitespace characters inserted between visible characters - Line 2: Appears completely empty in text editors, but is actually filled with invisible Unicode whitespace characters
- Line 1:
-
Hex analysis (xxd):
bash read-only • copy allowedxxd hide.tbLine 2 contains various multi-byte UTF-8 sequences — different types of Unicode whitespace. These are not regular ASCII spaces (0x20), but special Unicode characters that are visually indistinguishable from each other.
-
Identifying unique characters: Extracting all whitespace characters revealed 8 unique types of Unicode whitespace, used a total of 70 times (including 2 hidden in line 1):
Symbol Unicode Name U+0020 SPACE U+00A0 NO-BREAK SPACE U+2000 EN QUAD U+2002 EN SPACE U+2003 EM SPACE U+2004 THREE-PER-EM SPACE U+2005 FOUR-PER-EM SPACE U+2008 PUNCTUATION SPACE
- Key observation:
- 8 unique types = 2^3 = 3 bits per character (octal/base-8 encoding)
- 70 characters x 3 bits = 210 bits = 26 bytes + 2 extra bits
- 26 bytes — perfectly fits the flag length in
TB{...}format
Solution
Step 1: Extracting whitespace characters
python read-only • copy allowed#!/usr/bin/env python3 """Extract hidden Unicode whitespace from hide.tb file.""" with open('hide.tb', 'r', encoding='utf-8') as f: content = f.read() # Target Unicode whitespace characters WHITESPACE_CHARS = { '\u0020', '\u00A0', '\u2000', '\u2002', '\u2003', '\u2004', '\u2005', '\u2008' } # Extract all whitespace characters (except \n) hidden = [ch for ch in content if ch in WHITESPACE_CHARS] print(f"Found {len(hidden)} hidden characters") print(f"Unique types: {len(set(hidden))}")
Step 2: Brute-force mapping
Since the flag format TB{...} is known, we iterate through all 8! = 40320 possible mappings of 8 whitespace types to values 0-7:
python read-only • copy allowed#!/usr/bin/env python3 """ Brute-force mapping of Unicode whitespace to 3-bit values. Using known prefix TB{ for filtering. """ from itertools import permutations with open('hide.tb', 'r', encoding='utf-8') as f: content = f.read() WHITESPACE_CHARS = { '\u0020', '\u00A0', '\u2000', '\u2002', '\u2003', '\u2004', '\u2005', '\u2008' } # Extract hidden characters hidden = [ch for ch in content if ch in WHITESPACE_CHARS] unique_chars = sorted(set(hidden), key=lambda c: ord(c)) # Iterate through all permutations of values 0-7 for perm in permutations(range(8)): mapping = dict(zip(unique_chars, perm)) # Convert to bits bits = '' for ch in hidden: bits += format(mapping[ch], '03b') # Decode to ASCII try: decoded = '' for i in range(0, len(bits) - 7, 8): byte = int(bits[i:i+8], 2) if byte == 0: continue decoded += chr(byte) # Check prefix if decoded.startswith('TB{') and decoded.endswith('}'): print(f"FOUND: {decoded}") print(f"Mapping: {mapping}") break except (ValueError, UnicodeDecodeError): continue
Step 3: Brute-force result
Out of 40320 permutations, only one produced a correct result with the TB{ prefix:
| Symbol | Unicode | Value (3 bits) |
|---|---|---|
| SPACE | U+0020 | 0 (000) |
| NO-BREAK SPACE | U+00A0 | 1 (001) |
| EM SPACE | U+2003 | 2 (010) |
| EN SPACE | U+2002 | 3 (011) |
| EN QUAD | U+2000 | 4 (100) |
| THREE-PER-EM SPACE | U+2004 | 5 (101) |
| FOUR-PER-EM SPACE | U+2005 | 6 (110) |
| PUNCTUATION SPACE | U+2008 | 7 (111) |
Step 4: Decoding
Each whitespace character is converted to a 3-bit value, all bits are concatenated, then read as 8-bit ASCII bytes:
[space1][space2][space3]... → 010 101 000 ... → 01010100 01000010 01111011 ... → T B { ...
Result: TB{REDACTED}
The flag in leetspeak decodes as "DMD is hard to compile" — a reference to the D programming language compiler.
Lessons
Unicode contains dozens of visually indistinguishable whitespace characters, making them an ideal channel for steganography. Key recognition algorithm:
fileshows "Unicode text" → suspect Unicode steganographyxxd/ hex analysis → identify multi-byte sequences in "empty" areas- Count unique types → if power of two, it's N-bit encoding
- Known flag format → brute-force mapping (N! permutations)
Typical Unicode whitespace for steganography:
| Range | Characters |
|---|---|
| U+0020 | Regular space |
| U+00A0 | Non-breaking space |
| U+2000-U+200A | Typographic spaces (en quad, em space, thin space, etc.) |
| U+2028-U+2029 | Line/paragraph separator |
| U+202F | Narrow no-break space |
| U+205F | Medium mathematical space |
| U+3000 | Ideographic space |
| U+200B-U+200D | Zero-width spaces (invisible, 0 width) |
| U+FEFF | Zero-width no-break space (BOM) |
| |