Привет, Хабр! На связи команда разработки ChameleonLab.

Наш проект — программный комплекс для стеганографии и защиты данных — перешагнул отметку в 300 000 скачиваний (суммарно для Windows и macOS). Такая база пользователей кардинально меняет подход к разработке. Мы больше не можем позволить себе «гаражные» методы, которые ломают структуру файлов или заставляют плееры вести себя непредсказуемо.

Нас часто спрашивают, почему в публичной версии до сих пор нет кнопки «Спрятать в музыку». Ответ прост: мы не хотим выпускать сырой функционал.

Последние два месяца мы провели в закрытом R&D, пытаясь решить одну задачу: как спрятать файл в MP3 так, чтобы ни один плеер и ни один спектроанализатор этого не заметил?

Мы прошли путь от примитивного дописывания байтов (которое сломало UX) до сложной цифровой обработки сигналов (DSP). В этой статье мы покажем анатомию MP3-фрейма, объясним математику «призрачной тишины» и расскажем, как мы используем Быстрое Преобразование Фурье (FFT), чтобы прятать данные в фазе звуковой волны.

Глава 1. Почему нельзя просто дописать байты? (Проблема «Overlay»)

Самый простой метод стеганографии, который используют новички — Overlay. Это когда вы просто берете и дописываете зашифрованный контейнер в конец файла-носителя.
В терминах командной строки это выглядит как:
cat song.mp3 secret.zip > output.mp3

С картинками (JPEG/PNG) и исполняемыми файлами (EXE) это работает идеально. Просмотрщики читают файл до маркера EOF и игнорируют «хвост». Но с аудио это приводит к катастрофе.

Проблема «Призрачной тишины»

Большинство плееров (VLC, Windows Media Player, QuickTime) не сканируют весь файл перед воспроизведением — это слишком ресурсоемко, особенно для потокового аудио. Они оценивают длительность трека (T) математически, опираясь на размер файла (S) и средний битрейт (R):

T \approx \frac{S \times 8}{R}

Допустим, у нас есть песня на 3 минуты весом 5 Мб.
Мы прячем в нее файл еще на 5 Мб. Размер становится 10 Мб.

Что делает плеер?

  1. Видит файл размером 10 Мб.

  2. Считает формулу и делает вывод: «Ага, значит песня идет 6 минут».

  3. Отрисовывает таймлайн (timeline) на 6:00.

Пользователь нажимает Play. На 3:00 песня заканчивается. Но ползунок только на середине! Плеер продолжает читать наши зашифрованные данные, пытаясь декодировать их как звук. Так как там нет валидных аудио-фреймов (синхросигналов), плеер выдает гробовую тишину или (что еще хуже) резкий цифровой скрежет (glitch noise).

Вердикт R&D: Мы отказались от этого метода. Продукт уровня Enterprise не должен отдавать пользователю «битый» файл, даже если технически данные можно извлечь.

Глава 2. Анатомия MP3: Попытка взлома фреймов

Поняв, что менять размер файла нельзя, мы решили прятать данные внутрь существующей структуры.

MP3-файл — это не сплошной поток, а последовательность фреймов. Каждый фрейм содержит 1152 сэмпла звука (около 26 мс).

Структура типичного фрейма выглядит так:

Структура фрейма MP3: Header, Side Info, Main Data, Ancillary Data
Структура фрейма MP3: Header, Side Info, Main Data, Ancillary Data

В конце каждого фрейма часто остается пустое место, которое называется Ancillary Data (вспомогательные данные). Размер фрейма в байтах фиксирован (для CBR), а вот сжатые аудиоданные (коды Хаффмана) могут занимать меньше места. Оставшиеся биты спецификация разрешает игнорировать.

Почему это сложно (Bit Reservoir)

Казалось бы — идеальное место! Но MP3 использует хитрую технологию оптимизации Bit Reservoir.

Если сложный фрагмент музыки не влезает в свой фрейм, он может занять свободное место в предыдущем фрейме (как раз в зоне Ancillary Data).

В заголовке Side Information есть параметр main_data_begin, который работает как Backpointer — он указывает, на сколько байт назад нужно отступить декодеру, чтобы найти начало данных для текущего фрейма.

Если мы начнем писать свои байты в «пустоту» Ancillary Data, не учитывая этот механизм, мы рискуем перезатереть начало следующего музыкального фрагмента. Песня превратится в цифровую «кашу».

Пример того, как мы ищем «безопасные дырки» в потоке (фрагмент нашего прототипа):

import struct

def find_safe_holes(mp3_data):
    """
    Сканирует MP3 и ищет свободное место (Ancillary bits),
    пытаясь учитывать backpointer'ы.
    """
    offset = 0
    holes = []
    
    while offset < len(mp3_data) - 4:
        # 1. Ищем синхросигнал 0xFFF (12 бит единиц)
        if mp3_data[offset] != 0xFF or (mp3_data[offset+1] & 0xE0) != 0xE0:
            offset += 1
            continue

        # Читаем заголовок (Header)
        header = struct.unpack('>I', mp3_data[offset:offset+4])[0]
        bitrate_idx = (header >> 12) & 0xF
        sample_rate_idx = (header >> 10) & 0x3
        padding = (header >> 9) & 0x1
        
        # 2. Вычисляем физический размер фрейма
        # (Упрощенно, в реальном коде нужны таблицы битрейтов)
        frame_size = int(144 * get_bitrate(bitrate_idx) / get_samplerate(sample_rate_idx)) + padding
        
        # 3. Читаем Side Info
        side_info = mp3_data[offset+4 : offset+36] 
        
        # Проблема: нужно полностью распарсить Side Info и Хаффмана, 
        # чтобы найти, где реально заканчиваются данные.
        # Это требует реализации полного MP3 декодера...
        
        offset += frame_size
    return holes

Глава 2.1. А как у других? (State of the Art)

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

Вот основные игроки и их методы:

1. MP3Stego (Классика)

Легендарная консольная утилита, написанная еще в 1998 году Фабьеном Петикола (Fabien Petitcolas).

  • Метод: Она не просто прячет данные, она работает как полноценный энкодер. Во время сжатия WAV в MP3 она вмешивается в процесс квантования (Inner Loop) и модифицирует младшие биты коэффициентов MDCT (модифицированного дискретного косинусного преобразования).

  • Плюсы: Данные «впекаются» в структуру аудио и выживают.

  • Минусы: Это Re-encoding. Вы не можете взять готовый MP3 любимой группы и спрятать туда данные. Вам нужно иметь исходник (WAV), иначе качество упадет дважды (generation loss). Плюс, это чистый CLI (командная строка), что отпугивает 90% пользователей.

2. DeepSound

Самый популярный инструмент в мире CTF (Capture The Flag) и среди обычных пользователей. Имеет удобный GUI.

  • Метод: Использует вариацию LSB (Least Significant Bit) и скрытие в заголовках фреймов, часто используя алгоритм Рида-Соломона для коррекции ошибок.

  • Плюсы: Удобно, работает с уже готовыми файлами.

  • Минусы: Из-за своей популярности стал главной целью антивирусов и аналитиков. Существуют готовые утилиты (например, stegseek), которые брутфорсят пароли DeepSound за секунды. К тому же, он часто оставляет заметные статистические аномалии в файле.

3. Метод «Спектральной картинки» (Coagula)

Это не совсем стеганография данных, но популярный визуальный трюк. Картинка преобразуется в звук так, что на спектрограмме вы видите лицо или текст (привет, Aphex Twin!).

  • Метод: Обратное преобразование Фурье, где ось Y — частота, а яркость пикселя — амплитуда.

  • Минусы: Это слышно. Такой файл звучит как жуткий инопланетный скрежет. Для скрытой передачи данных не подходит абсолютно — любой, кто откроет файл, поймет, что это не музыка.

Наш вывод: Ни один метод нас не устроил.

  • Мы не хотим перекодировать файл (как MP3Stego).

  • Мы не хотим быть уязвимыми для скриптов с GitHub (как DeepSound).

  • Мы хотим, чтобы файл звучал идеально.

Поэтому мы обратились к фазовому кодированию (Phase Coding), которое работает с готовым спектром, но делает изменения, невидимые для классических LSB-детекторов.

Глава 3. Решение: Уходим в Спектр (DSP и Phase Coding)

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

Мы перешли к методам спектральной стеганографии.

Физика процесса

Звук, который мы слышим — это колебания давления во времени (Time Domain). Но любой сигнал можно представить как сумму простых синусоид разной частоты. Это делает Преобразование Фурье.

У каждой синусоиды есть три параметра:

  1. Частота (высота ноты).

  2. Амплитуда (громкость).

  3. Фаза (сдвиг волны относительно начала отсчета).

Ключевой инсайт: Человеческое ухо — отличный спектроанализатор. Мы прекрасно слышим частоту и амплитуду. Но мы эволюционно глухи к фазе.

Если мы возьмем ноту «Ля» (440 Гц) и сдвинем её фазу на 90 градусов (\pi/2), звук для человека останется абсолютно идентичным.

Алгоритм Phase Coding

Мы решили кодировать биты информации (0 и 1) через сдвиг фазы на определенных частотах.

Математика процесса:

  1. Бьем аудио на перекрывающиеся сегменты.

  2. Применяем FFT (Fast Fourier Transform), получая массив комплексных чисел X(k).

  3. Каждое число X(k) можно представить в полярной форме как A_k \cdot e^{i\phi_k}, где A — амплитуда, \phi — фаза.

  4. Мы меняем фазу \phi в зависимости от нашего секретного бита.

Прототип реализации (Python/NumPy):
Вот пример логики, которую мы тестируем в наших лабораторных сборках:

import numpy as np

def encode_bit_into_phase(audio_segment, secret_bit):
    """
    Внедряет 1 бит информации в фазу сегмента аудио.
    (Упрощенный пример из нашего R&D)
    """
    # 1. Переводим звук из Времени в Частоту (FFT)
    # audio_segment - это массив амплитуд (PCM)
    spectrum = np.fft.fft(audio_segment)
    
    # 2. Выбираем "Несущую частоту"
    # Мы берем средние частоты (например, индекс 1024), 
    # где фазовые искажения менее заметны, но выживают при сжатии
    idx = 1024  
    
    # Получаем текущую фазу и модуль (амплитуду)
    phase = np.angle(spectrum[idx])
    magnitude = np.abs(spectrum[idx])
    
    # 3. Кодируем бит
    # Бит 1 -> сдвиг фазы на +90 градусов (Pi/2)
    # Бит 0 -> сдвиг фазы на -90 градусов (-Pi/2)
    phase_shift = np.pi / 2 if secret_bit == '1' else -np.pi / 2
    new_phase = phase + phase_shift
    
    # 4. Собираем спектр обратно
    # ВАЖНЕЙШИЙ МОМЕНТ: спектр вещественного сигнала должен быть симметричен!
    # Если мы меняем spectrum[i], нужно зеркально менять spectrum[-i]
    spectrum[idx] = magnitude * np.exp(1j * new_phase)
    spectrum[-idx] = magnitude * np.exp(1j * -new_phase) # Сопряженная симметрия
    
    # 5. Обратное преобразование (Frequency -> Time)
    # .real берем, так как мнимая часть должна быть около нуля (из-за симметрии)
    modified_audio = np.fft.ifft(spectrum).real
    
    return modified_audio.astype(np.int16)

Главная проблема: MP3-сжатие

Всё это отлично работает в WAV (без сжатия). Но MP3 — это Lossy формат. Он квантует коэффициенты DCT (дискретного косинусного преобразования) и удаляет "несущественные" детали. Если просто сохранить результат FFT в MP3, фаза "поплывет", и данные превратятся в мусор.

Сейчас мы дорабатываем алгоритм, используя относительное фазовое кодирование (разность фаз между соседними частотными полосами) и коды коррекции ошибок Рида-Соломона. Это позволяет данным "выживать" даже при стандартном битрейте 128-320 kbps.

Философия Purple Teaming: Почему мы работаем в обе стороны?

ChameleonLab изначально проектировался как образовательная платформа.
Мы исповедуем принцип Purple Teaming — объединение методов атаки (Red Team) и защиты (Blue Team).

Мы убеждены: невозможно стать хорошим специалистом по информационной безопасности, если ты не понимаешь, как работает атакующий.

Именно поэтому наша программа работает строго в две стороны:

  1. ⚔️ Offensive (Атака): Мы даем инструменты для скрытия (LSB, Phase Coding, Injection), чтобы пользователь мог своими руками «пощупать» алгоритмы и понять их ограничения.

  2. 🛡️ Defensive (Защита): Мы разрабатываем мощный модуль Стегоанализа. Пользователь должен загрузить файл (возможно, даже тот, который сам только что создал) и увидеть, как математика выдает факт вмешательства.

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

Глава 4. Blue Team: Мы учим искать

ChameleonLab позиционируется не как «хакерская утилита», а как образовательная платформа. Мы считаем, что пользователь должен понимать, ка�� работает защита, и уметь проверять свои файлы.

Поэтому в актуальной версии мы добавили мощный блок Стегоанализа. Это инструменты защиты («Blue Team»), которые позволяют находить то, что пытались спрятать другие.

1. Энтропия Шеннона

Обычная музыка или документы имеют низкую или переменную энтропию. Зашифрованные данные (благодаря шифрованию AES) выглядят как идеальный "белый шум" с максимальной энтропией (близкой к 8 битам на байт).

Наш сканер "скользит" по файлу окном в 4 Кб и строит график. Если вы видите резкий скачок в плоскую «полку» на уровне 7.99 — файл почти наверняка содержит скрытый контейнер.

def calculate_entropy(data):
    """Вычисляет энтропию блока байтов"""
    if not data: return 0
    # Подсчет частоты каждого байта
    counts = np.bincount(np.frombuffer(data, dtype=np.uint8), minlength=256)
    probs = counts[counts > 0] / len(data)
    # Формула Шеннона
    return -np.sum(probs * np.log2(probs))

2. Визуализация Битовых Плоскостей (Bitplanes)

Для изображений мы реализовали инструмент, который разбирает картинку на 8 слоев. Обычно стеганографию (LSB) прячут в младшем (нулевом) бите. В ChameleonLab вы можете отключить 7 старших бит (которые отвечают за само изображение) и увидеть "голый" шум младшего бита. Если этот шум имеет структуру, текст или логотипы — это верный признак вмешательства.

Итог: Релиз близко, Linux и борьба с UI

Мы выходим на финишную прямую перед релизом большого обновления.

С MP3-модулем мы решили взять небольшую паузу на полировку. Мы не хотим отдавать «сырой» алгоритм. Скоро доведем Phase Coding до идеала — чтобы внедрение было устойчивым к пережатию, а файл не менял длительность и не вызывал подозрений у анализаторов.

Параллельно мы активно дорабатываем версию под Linux.

Как оказалось, главная проблема настоящей кроссплатформенности в 2026 году — это не бэкенд (Python везде работает отлично), а отображение интерфейса. Сделать так, чтобы UI выглядел адекватно и не «разваливался» на Retina-экранах, Windows HiDPI и зоопарке дисплеев Linux (с разным масштабированием и DPI) — это отдельный инженерный вызов, который мы сейчас решаем.

Новая версия с исправленным UI и свежими фичами выйдет уже совсем скоро.

А пока ChameleonLab продолжает развиваться:

  • ✅ Мы поддерживаем стабильное скрытие в PDF, DOCX, ZIP, PNG, JPG.

  • ✅ Мы добавили блок Криптографии (AES-256).

  • ✅ Мы учим анализировать файлы на наличие скрытых данных.

Программа "ChameleonLab". Версия 1.5.0.0
Программа "ChameleonLab". Версия 1.5.0.0

Эпилог: Спасибо, Хабр!

Отдельно хотим поблагодарить сообщество за жесткий, но конструктивный фидбек к прошлым статьям. Именно комментарии на Хабре заставили нас пересмотреть RoadMap и добавить фичи, о которых мы даже не думали на старте.

Благодаря вашим идеям, в версии 2.0 мы начинаем работу над Сетевой Стеганографией (Network Covert Channels).

Мы больше не ограничиваемся статичными файлами. Наша новая цель — передача скрытых данных в реальном времени внутри сетевых протоколов:

  • TCP/IP Headers: Использование неиспользуемых полей и манипуляции с Sequence Numbers для передачи байтов информации.

  • ICMP Tunneling: Скрытие данных в «пингах», которые проходят через большинство фаерволов.

  • DNS Exfiltration: Визуализация того, как данные могут утекать через DNS-запросы (для обучения Blue Team специалистов).

Это сложная инженерная задача, требующая работы на уровне драйверов и сырых сокетов, но с такой поддержкой сообщества мы уверены, что справимся.

Пишите в комментариях свои безумные идеи — мы читаем всё, и, как видите, многое берем в работу!

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

Ссылки: