Привет, Хабр!

В прошлой статье мы разбирались, как можно спрятать данные внутри файлов Microsoft Office, причем не только в текстовых документах (.docx), но и в таблицах (.xlsx) и презентациях (.pptx), используя стандартный механизм Custom XML Parts. Сегодня мы продолжим исследовать мир офисной стеганографии и обратим свой взор на открытый и популярный формат OpenDocument Format (ODF), на котором работают LibreOffice и Apache OpenOffice. Этот метод так же универсален и применим ко всей линейке форматов: .odt (текстовые документы), .ods (таблицы) и .odp (презентации).

Мы рассмотрим, в чем его принципиальное отличие от формата Microsoft, напишем Python-скрипт для встраивания полезной нагрузки и, конечно же, посмотрим на код.

ZIP — всему голова. Снова

Как и современные файлы MS Office, документы OpenDocument по своей сути являются обычными ZIP-архивами. Если вы возьмете любой .odt файл и поменяете его расширение на .zip, вы сможете заглянуть внутрь и увидеть его структуру: набор XML-файлов и папок.

Эта особенность и лежит в основе нашего метода. Мы можем добавить собственный файл внутрь этого архива. Но просто добавить файл недостаточно — пакет ODF содержит специальный «паспорт» или «опись», в которой перечислены все его составные части. Без обновления этой описи документ, скорее всего, будет считаться поврежденным.

Этот ключевой файл — META-INF/manifest.xml.

OpenDocument vs Open XML: Разница в подходе

Здесь и кроется главное идеологическое различие между ODF и OOXML (формат Microsoft) в контексте нашей задачи.

  • В Open XML (.docx, .xlsx, .pptx) мы действовали «по правилам». Мы создавали специальную XML-часть (customXml/item1.xml), оборачивали наши данные в hex-представление внутри XML-тега и добавляли на нее ссылку в файле связей (_rels/document.xml.rels). Это более структурированный, но и более сложный путь.

  • В OpenDocument (.odt, .ods, .odp) подход гораздо более прямолинейный. Нам не нужно создавать хитрых XML-оберток. Мы можем просто положить наш файл с полезной нагрузкой (например, secret/payload.dat) внутрь архива и затем честно «задекларировать» его в манифесте.

Этот метод проще и позволяет внедрять бинарные данные как есть, без кодирования в текст (вроде hex или base64).

Реализация на Python: шаг за шагом

Давайте разберем код, который реализует этот процесс в файле odf_stego.py.

Встраивание данных (hide_in_odf)

Вот основная функция, которая выполняет всю работу:

# odf_stego.py
import zipfile
import shutil
import os
from xml.etree import ElementTree as ET

PAYLOAD_FILENAME = 'secret/payload.dat'
MANIFEST_PATH = 'META-INF/manifest.xml'
MANIFEST_NS = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"

def hide_in_odf(src_odf_path: str, dest_odf_path: str, payload_bytes: bytes):
    """Распаковывает ODF, добавляет секретный файл, обновляет манифест и запаковывает обратно."""
    temp_dir = dest_odf_path + "_temp"
    if os.path.exists(temp_dir):
        shutil.rmtree(temp_dir)

    try:
        # 1. Распаковываем ODF-контейнер во временную папку
        with zipfile.ZipFile(src_odf_path, 'r') as odf_zip:
            odf_zip.extractall(temp_dir)

        # 2. Создаем и записываем наш секретный файл
        payload_path = os.path.join(temp_dir, PAYLOAD_FILENAME)
        os.makedirs(os.path.dirname(payload_path), exist_ok=True)
        with open(payload_path, 'wb') as f:
            f.write(payload_bytes)

        # 3. Обновляем манифест (самая важная часть)
        manifest_full_path = os.path.join(temp_dir, MANIFEST_PATH)
        ET.register_namespace('manifest', MANIFEST_NS)
        tree = ET.parse(manifest_full_path)
        root = tree.getroot()

        # Создаем новую запись для нашего файла
        new_entry = ET.Element(f'{{{MANIFEST_NS}}}file-entry', {
            f'{{{MANIFEST_NS}}}full-path': PAYLOAD_FILENAME,
            f'{{{MANIFEST_NS}}}media-type': 'application/octet-stream'
        })
        root.append(new_entry)
        tree.write(manifest_full_path, encoding='UTF-8', xml_declaration=True)

        # 4. Запаковываем все обратно в новый ODF-файл
        shutil.make_archive(dest_odf_path.replace(os.path.splitext(dest_odf_path)[1], ''), 'zip', temp_dir)
        
        # Переименовываем .zip в нужное расширение (.odt, .ods и т.д.)
        if os.path.exists(dest_odf_path):
            os.remove(dest_odf_path)
        shutil.move(dest_odf_path.replace(os.path.splitext(dest_odf_path)[1], '.zip'), dest_odf_path)

    finally:
        # 5. Очищаем за собой временные файлы
        if os.path.exists(temp_dir):
            shutil.rmtree(temp_dir)

Извлечение данных (reveal_from_odf)

Извлечение данных становится тривиальной задачей. Нам даже не нужно парсить XML, если мы знаем точный путь к нашему файлу.

def reveal_from_odf(odf_path: str) -> bytes | None:
    """Простой метод извлечения: ищет секретный файл по известному пути."""
    # ... (код функции, как в предыдущем ответе)

Философия проекта: Зачем всё это?

Прежде чем двигаться дальше, стоит ответить на вопрос: зачем вообще создавать такой инструмент? Цели проекта выходят за рамки простого "спрятать файлик".

  1. Исследование и образование. Современные файловые форматы — это сложные и интересные структуры. Разработка такого инструмента заставляет глубоко погрузиться в спецификации OOXML и ODF, понять их внутреннюю логику. Стеганография — это отличный прикладной способ изучить, как устроены файлы, с которыми мы работаем каждый день.

  2. Цифровые водяные знаки (Watermarking). Не всегда речь идет о секретности. В документ можно незаметно встроить идентификатор автора, информацию о лицензии или уникальную метку для отслеживания распространения файла. Это помогает защитить интеллектуальную собственность.

  3. Целостность данных. Представьте, что у вас есть документ и к нему — файл с ключами, заметками или метаданными. Наш подход позволяет "вшить" эти связанные данные прямо в основной файл, создавая единый, самодостаточный пакет. Это гарантирует, что дополнительная информация не потеряется при передаче.

  4. Правдоподобное отрицание. Классическая цель стеганографии. В отличие от очевидно зашифрованного архива (secret_data.zip.enc), обычный на вид отчет или презентация не вызывают подозрений. Это позволяет скрыть сам факт наличия тайной переписки.

А есть ли аналоги?

Рынок стеганографических программ довольно насыщен, но есть нюанс. Подавляющее большинство утилит (например, знаменитый Steghide) работают с медиаконтейнерами: изображениями, аудио и видео. Ниша офисных документов освоена гораздо слабее.

При поиске аналогов можно найти:

  • Академические проекты и PoC. Существует множество исследований и скриптов на GitHub, которые демонстрируют возможность сокрытия данных в офисных файлах. Однако чаще всего это узкоспециализированные консольные утилиты, поддерживающие один конкретный формат (обычно .docx) и один метод.

  • Специализированный коммерческий софт. Некоторые решения для DLP (Data Loss Prevention) и форензики умеют анализировать офисные файлы на предмет скрытых данных, но редко предоставляют функционал для их простого создания.

Что касается комплексных решений с графическим интерфейсом, которые бы удобно поддерживали и Open XML, и OpenDocument форматы, то их найти практически не удалось. Обычно поддержка ограничивается чем-то одним, либо вовсе отсутствует.

Это и стало одной из ключевых причин для создания нашего инструмента «ChameleonLab» — дать пользователю простой и универсальный способ работать с самыми популярными офисными форматами в одном месте.

Программа "ChameleonLab"
Программа «ChameleonLab»

Интеграция и будущие планы

В нашем приложении «Хамелеон» этот функционал будет интегрирован в основной рабочий процесс, позволяя пользователю выбирать между разными типами контейнеров.

Интерфейс выбора контейнера и файла для встраивания в приложении.

Поддержка ODF-документов делает наш инструмент более универсальным, так как позволяет работать с файлами, созданными в популярных бесплатных офисных пакетах.

И самое главное: в будущих сборках нашего приложения мы планируем добавить полноценную поддержку этого метода для работы с ODF-документами, предоставляя пользователям еще больше гибкости.

Заключение

Мы рассмотрели простой, но эффективный метод сокрытия данных в файлах OpenDocument. В отличие от более сложного подхода, необходимого для .docx, здесь мы работаем с архивом напрямую, что упрощает код и убирает необходимость в дополнительном кодировании данных.

Оба метода — и для OOXML, и для ODF — имеют право на жизнь и показывают, какими гибкими могут быть современные офисные форматы, если заглянуть к ним «под капот».

Где скачать и следить за проектом?

Последнюю версию программы «Steganographia» от ChameleonLab для Windows и macOS можно скачать на нашем официальном сайте.

На данный момент мы активно работаем над подготовкой сборки для пользователей Linux, которая появится в ближайшее время.

А чтобы быть в курсе обновлений, обсуждать новые функции и общаться с единомышленниками, присоединяйтесь к нашему Telegram-каналу.

Спасибо за внимание!

Комментарии (4)


  1. unC0Rr
    05.09.2025 14:32

    • Цифровые водяные знаки (Watermarking).

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

    • Целостность данных.

    ... которую может проверить только тот, кто знает о вшитых метаданных (от которых так же легко избавиться методом copy-paste).

    • Правдоподобное отрицание.

    ... ровно до момента, пока кто-нибудь не обнаружит лишние файлы в зипе. После чего отрицать становится затруднительно.


    1. Lomakn Автор
      05.09.2025 14:32

      Спасибо за очень хорошие замечания. По факту любой материал в стеганографии так лечится.


  1. kilfoy
    05.09.2025 14:32

    Симпатично, но до CrypTool по уровня пользы далеко. Хотелось бы больше инструментов анализа и ближе к сырым байтам быть, а не только кнопки тыкать.


    1. Lomakn Автор
      05.09.2025 14:32

      Спасибо за сервис CrypTool. Изучим его. Много интересного для себя возьмём. Наш проект молодой и поэтому есть куда расти. Благодаря Вам мы и собираем идеи, которые будем реализовывать