← Back to Writeups
CUSTOMEasySteganography

Just a Text

XESXOR9/2/20263 min read
#steganography#custom#easy#binary_character_substitution#cyrillic_latin_detection#homoglyph_steganography#linux

Just a Text

Platform: spbctf | Category: Steganography | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2017-08-31 | Status: Solved Techniques: binary_character_substitution, cyrillic_latin_detection, homoglyph_steganography

Summary

Task: UTF-8 text file with hidden message using Cyrillic/Latin homoglyphs. Solution: Identified visually identical characters with different Unicode codepoints, decoded Latin=1/Cyrillic=0 as binary to reveal the flag.

Just a Text — SPbCTF

Description

Ыж перехватил кусок текста из переписки сотрудников разведки. Выглядит как обычный текст, но есть подозрение, что в нём они скрытно передали флаг! Достаньте.

Download: https://fyrkbomb.ru/files/2017-08-31_flag.txt

Analysis

The downloaded file flag.txt is UTF-8 text (with BOM), containing ordinary English text about biography, genius, aesthetics, etc. Visually the text looks completely normal.

Initial analysis:

bash
$ file flag.txt
flag.txt: Unicode text, UTF-8 (with BOM) text, with very long lines (728), with no line terminators

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

Hex analysis reveals an anomaly: When viewing through xxd, two-byte UTF-8 sequences are found where single-byte ASCII characters should be. For example, Cyrillic е (U+0435, bytes d0 b5) instead of Latin e (U+0065).

Identified homoglyph pairs:

LatinCyrillic
a (U+0061)а (U+0430)
e (U+0065)е (U+0435)
o (U+006F)о (U+043E)
p (U+0070)р (U+0440)
c (U+0063)с (U+0441)
y (U+0079)у (U+0443)
x (U+0078)х (U+0445)
A (U+0041)А (U+0410)
B (U+0042)В (U+0412)
E (U+0045)Е (U+0415)
K (U+004B)К (U+041A)
M (U+004D)М (U+041C)
H (U+0048)Н (U+041D)
O (U+004F)О (U+041E)
P (U+0050)Р (U+0420)
C (U+0043)С (U+0421)
T (U+0054)Т (U+0422)
X (U+0058)Х (U+0425)
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Solution

Steganography technique: for each character that has both Latin and Cyrillic variants, the choice of variant encodes one bit of information:

  • Latin character → bit 1
  • Cyrillic character → bit 0

Collect all bits, group by 8 and decode as ASCII.

python
#!/usr/bin/env python3
"""
Homoglyph steganography decoder
Decodes hidden message from Cyrillic/Latin character substitution
"""

with open('flag.txt', 'r', encoding='utf-8-sig') as f:
    content = f.read()

# Cyrillic homoglyphs (lowercase and uppercase)
cyrillic_all = set(
    '\u0430'  # а
    '\u0435'  # е
    '\u043e'  # о
    '\u0440'  # р
    '\u0441'  # с
    '\u0443'  # у
    '\u0445'  # х
    '\u0410'  # А
    '\u0412'  # В
    '\u0415'  # Е
    '\u041a'  # К
    '\u041c'  # М
    '\u041d'  # Н
    '\u041e'  # О
    '\u0420'  # Р
    '\u0421'  # С
    '\u0422'  # Т
    '\u0425'  # Х
)
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

# Latin homoglyphs
latin_all = set('aeopycxABEKMHOPCTX')

bits = ''
for ch in content:
    if ch in cyrillic_all:
        bits += '0'
    elif ch in latin_all:
        bits += '1'

# Decode bits to ASCII
result = ''
for i in range(0, len(bits), 8):
    byte = bits[i:i+8]
    if len(byte) == 8:
        result += chr(int(byte, 2))

print(result)
# Output: THE FL@G IS: REDACTED

Note: The @ character instead of A in the word "FLAG" is the result of the letter A in that position being encoded as Cyrillic А (U+0410), which gave bit 0 instead of 1. This yields 0x40 (@) instead of 0x41 (A). This is an intentional leet-speak style. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍