← Back to Writeups
CUSTOMMediumSteganography

Ворота есть... А флаг где? (Font Steganography)

XESXOR9/2/20263 min read
#steganography#custom#medium#custom-font-glyph-extraction-w#font-based-steganography-detec#odt-file-structure-analysis-(z#svg-path-visualization-from-fo#linux

Ворота есть... А флаг где? (Font Steganography)

Platform: hackerlab | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2025-12-22 | Status: Solved Techniques: Custom font glyph extraction with fontTools, Font-based steganography detection, ODT file structure analysis (ZIP-based format), SVG path visualization from font glyphs

Summary

Task: ODT file with embedded custom font. Solution: Extract font glyphs using fontTools to reveal hidden message drawn in each glyph.

Ворота есть... А флаг где? — hackerlab.pro

Description

"Ворота есть... А флаг где?.." (Gates are there... But where is the flag?)

Provided file task.zip containing task.odt.

Analysis

1. ODT File Structure

ODT (OpenDocument Text) is a ZIP archive. Extracting:

bash
unzip task.odt -d task_extracted/

Structure:

task_extracted/
├── Pictures/
│   └── gates.jpg          # Image of gates (red herring)
├── Fonts/
│   └── Font_Myfont_1.ttf  # Custom font "Myfont"
├── content.xml            # Document content
├── styles.xml
├── meta.xml
└── ...

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

2. Document Content Analysis

In content.xml found:

  • Document contains only one character: ;
  • Character is rendered with custom font "Myfont"
  • Gates image is a distraction (matches the hint "Gates are there")

3. Custom Font Analysis

Font metadata Font_Myfont_1.ttf:

  • Name: "Myfont"
  • Creation date: 25-01-2023
  • Number of glyphs: 66 (A-Z, a-z, punctuation marks)

Key observation: Each glyph contains not a standard letter, but hand-drawn text!

Solution

Extracting Glyphs from Font

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

python
#!/usr/bin/env python3
from fontTools.ttLib import TTFont
from fontTools.pens.svgPathPen import SVGPathPen
import os

font = TTFont('Font_Myfont_1.ttf')
glyph_set = font.getGlyphSet()

os.makedirs('glyphs', exist_ok=True)

for i, glyph_name in enumerate(font.getGlyphOrder()):
    if glyph_name == '.notdef':
        continue
    
    pen = SVGPathPen(glyph_set)
    glyph_set[glyph_name].draw(pen)
    path = pen.getCommands()
    
    if not path:
        continue
    
    # Get bounding box for scaling
    glyph = glyph_set[glyph_name]
    
    svg_content = f'''<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -800 1000 1200">
  <g transform="scale(1,-1) translate(0,-400)">
    <path d="{path}" fill="black"/>
  </g>
</svg>'''
    
    with open(f'glyphs/{i:02d}_{glyph_name}.svg', 'w') as f:
        f.write(svg_content)
    
    print(f"Exported: {glyph_name}")
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

print(f"\nTotal glyphs: {len(font.getGlyphOrder())}")

Visualizing All Glyphs

python
#!/usr/bin/env python3
"""Creating a single image of all glyphs"""
from fontTools.ttLib import TTFont
from fontTools.pens.svgPathPen import SVGPathPen

font = TTFont('Font_Myfont_1.ttf')
glyph_set = font.getGlyphSet()

glyphs_data = []
for glyph_name in font.getGlyphOrder():
    if glyph_name == '.notdef':
        continue
    
    pen = SVGPathPen(glyph_set)
    glyph_set[glyph_name].draw(pen)
    path = pen.getCommands()
    
    if path:
        glyphs_data.append((glyph_name, path))
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Create SVG with all glyphs in a row
cols = 10
rows = (len(glyphs_data) + cols - 1) // cols
cell_width = 150
cell_height = 200

svg = f'''<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" 
     width="{cols * cell_width}" height="{rows * cell_height}"
     viewBox="0 0 {cols * cell_width} {rows * cell_height}">
  <rect width="100%" height="100%" fill="white"/>
'''

for i, (name, path) in enumerate(glyphs_data):
    col = i % cols
    row = i // cols
    x = col * cell_width + 20
    y = row * cell_height + 150
    
    svg += f'''
  <g transform="translate({x},{y}) scale(0.15,-0.15)">
    <path d="{path}" fill="black"/>
  </g>
  <text x="{col * cell_width + 75}" y="{row * cell_height + 190}" 
        text-anchor="middle" font-size="12">{name}</text>
'''
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

svg += '</svg>'

with open('all_glyphs.svg', 'w') as f:
    f.write(svg)

print("Created all_glyphs.svg")

Visualization Result

When viewing all glyphs in their order in the font, they form the text:

Hello!_Here_is_your_flag:CODEBY{REDACTED}

Each font "character" actually contains a hand-drawn letter of the message, not a standard glyph.

Detection Evasion Techniques

The challenge author used:

  1. Red herring — gates image distracts attention
  2. Minimal content — only one character ; in the document
  3. Hiding in structure — flag in font glyphs, not in text ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Alternative Approaches

  1. FontForge — GUI tool for viewing font glyphs
  2. Online font viewers — web services for TTF file analysis
  3. Manual inspection — install the font and type all characters

Related Techniques

  • Steganography in document metadata
  • Font substitution attacks
  • Homoglyph attacks (visually similar characters)
  • Zero-width character steganography ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍