← Back to Writeups
CUSTOMMediumSteganography

Morse Excel (Отчёт бухгалтера)

XESXOR9/2/20264 min read
#steganography#custom#medium#xlsx-structure-analysis-(zip-w#cell-style/formatting-analysis#morse-code-via-font-formatting#bold=dash)#linux

Morse Excel (Отчёт бухгалтера)

Platform: hackerlab.pro | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2025-12-22 | Status: Solved Techniques: XLSX structure analysis (ZIP with XML), Cell style/formatting analysis, Morse code via font formatting (italic=dot, bold=dash)

Summary

Given file task.xlsx — an Excel document with a financial report.

Morse Excel (Отчёт бухгалтера) — hackerlab.pro

Description

Бухгалтер мне прислал следующий отчёт. Насколько мне известно у нашей компании совсем другие цифры, возможно, он хотел мне сказать что-то что нельзя написать открыто?

Given file task.xlsx — an Excel document with a financial report.

Analysis

1. XLSX Structure

XLSX is a ZIP archive with XML files inside:

bash
unzip task.xlsx -d xlsx_contents/

Structure:

xlsx_contents/
├── [Content_Types].xml
├── _rels/
├── docProps/
└── xl/
    ├── worksheets/sheet1.xml  # Cell data
    ├── styles.xml              # Formatting styles
    ├── sharedStrings.xml       # Text values
    └── workbook.xml

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

2. Analyzing sheet1.xml

In the first row (row 1), multiple cells from A1 to DX1 were found with different style attributes (s):

xml
<c r="A1" s="2" t="s"><v>0</v></c>
<c r="B1" s="3"/>
<c r="C1" s="4"/>
<c r="D1" s="3"/>
<c r="E1" s="5"/>  <!-- empty style = separator -->
<c r="F1" s="4"/>
...
<c r="Z1" s="6"/>  <!-- underlined = word separator -->

3. Analyzing styles.xml

Style decoding through <fonts> and <cellXfs> analysis:

Style IDfontIdDescriptionMorse value
24Normal with fill- (dash)
35Italic (<i val="true"/>). (dot)
46Bold (<b val="true"/>)- (dash)
50Normal (letter separator)
67Underlined (<u val="single"/>)/ (word separator)
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

4. Encoding Pattern

Steganography via Excel cell formatting:

  • Italic = dot (.)
  • Bold = dash (-)
  • Normal font = separator between letters
  • Underlined = separator between words (/)

Solution

Extraction Script

python
#!/usr/bin/env python3
import zipfile
import xml.etree.ElementTree as ET
import re

# Morse code dictionary
MORSE_TO_CHAR = {
    '.-': 'A', '-...': 'B', '-.-.': 'C', '-..': 'D', '.': 'E',
    '..-.': 'F', '--.': 'G', '....': 'H', '..': 'I', '.---': 'J',
    '-.-': 'K', '.-..': 'L', '--': 'M', '-.': 'N', '---': 'O',
    '.--.': 'P', '--.-': 'Q', '.-.': 'R', '...': 'S', '-': 'T',
    '..-': 'U', '...-': 'V', '.--': 'W', '-..-': 'X', '-.--': 'Y',
    '--..': 'Z', '-----': '0', '.----': '1', '..---': '2',
    '...--': '3', '....-': '4', '.....': '5', '-....': '6',
    '--...': '7', '---..': '8', '----.': '9', '..--.-': '_'
}
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

# Extract and parse XLSX
with zipfile.ZipFile('task.xlsx', 'r') as z:
    # Parse styles
    styles_xml = z.read('xl/styles.xml').decode('utf-8')
    # Parse sheet
    sheet_xml = z.read('xl/worksheets/sheet1.xml').decode('utf-8')

# Extract cell styles from row 1
# Pattern: <c r="XX1" s="N"/> or <c r="XX1" s="N" t="s">
cells = re.findall(r'<c r="([A-Z]+)1"(?: s="(\d+)")?', sheet_xml)

# Style mapping based on analysis
# Style 2, 4 = dash (-), Style 3 = dot (.), Style 5 = letter sep, Style 6 = word sep
morse_code = []
current_letter = []
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

for col, style in cells:
    style = int(style) if style else 5  # default to separator
    
    if style in [2, 4]:  # bold or filled = dash
        current_letter.append('-')
    elif style == 3:  # italic = dot
        current_letter.append('.')
    elif style == 5:  # normal = letter separator
        if current_letter:
            morse_code.append(''.join(current_letter))
            current_letter = []
    elif style == 6:  # underline = word separator
        if current_letter:
            morse_code.append(''.join(current_letter))
            current_letter = []
        morse_code.append('/')
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

if current_letter:
    morse_code.append(''.join(current_letter))

# Decode Morse
morse_string = ' '.join(morse_code)
print(f"Morse: {morse_string}")

result = []
for code in morse_code:
    if code == '/':
        result.append(' ')
    elif code in MORSE_TO_CHAR:
        result.append(MORSE_TO_CHAR[code])
    else:
        result.append(f'[{code}]')

decoded = ''.join(result)
print(f"Decoded: {decoded}")

Extracted Morse Code

-.-. --- -.. . -... -.-- / -.. ...-- ...- ...-- .-.. --- .--. . .-. ... ..--.- .-.. ----- ...- ...-- ..--.- -- ----- .-. ... .

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

Decoding

MorseSymbol
-.-. --- -.. . -... -.--CODEBY
/(space)
-..D
...--3
...-V
...--3
.-..L
---O
.--.P
.E
.-.R
...S
..--.-_
.-..L
-----0
...-V
...--3
..--.-_
--M
-----0
.-.R
...S
.E

Result: CODEBY REDACTED

The first word "CODEBY" is the flag format, the flag content is: REDACTED ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Alternative Approaches

  1. Visual analysis in Excel: Open the file and look at cell formatting
  2. LibreOffice Calc: May display styles more clearly
  3. Python openpyxl: Library for working with XLSX, can extract styles programmatically

Related Techniques

  • Font-based steganography (as in RTF documents)
  • Cell color steganography (cell colors instead of fonts)
  • Hidden rows/columns in spreadsheets
  • Comment steganography in Office documents ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍