Всем привет! Мы продолжаем наш цикл статей, посвященный практической стеганографии в самых, казалось бы, обыденных файлах. Мы уже научились прятать данные в «слепых зонах» документов MS Office, внедрять «файлы-призраки» в EPUB и даже создавать скрытые каналы данных внутри PDF.
В комментариях к прошлым материалам наши читатели справедливо заметили: «А что насчет WebP?».

И это отличный вопрос. Мы прислушались к этому предложению и реализовали поддержку WebP в последней версии «ChameleonLab». А теперь, как и обещали, делимся техническими деталями.
В эпоху, когда скорость загрузки страниц решает все, формат от Google стал стандартом де-факто, вытесняя «старичков» JPEG и PNG. Он гибкий, эффективный и… как оказалось, превосходный «контейнер» для наших задач.
Сегодня мы препарируем WebP, разберемся, как он устроен, и почему его lossless-режим делает его идеальным кандидатом для классической LSB-стеганографии.
«Пациент»: Формат WebP (Web Picture)
История: Формат был представлен Google в 2010 году. Его главной целью было создание единого стандарта, который мог бы заменить сразу все — и JPEG, и PNG, и даже GIF, — обеспечивая лучшее сжатие при том же визуальном качестве. Изначально он был основан на алгоритмах сжатия ключевых кадров из видеокодека VP8.
Особенности и отличия:
WebP — это не просто «еще один формат», это настоящий швейцарский нож. Его ключевая особенность в том, что он является контейнером (на базе формата RIFF), который может хранить данные изображения, сжатые разными способами.
-
Два режима в одном флаконе:
-
Lossy (С потерями): Как и JPEG, этот режим использует предиктивное кодирование (он предсказывает блоки пикселей на основе соседних), но делает это эффективнее, чем JPEG. Файлы получаются на 25-35% меньше, чем JPEG-файлы аналогичного качества.
-
Lossless (Без потерь): Это прямой конкурент PNG. Вместо DEFLATE (как в PNG), WebP использует собственный, более сложный алгоритм (включающий предсказание и энтропийное кодирование), что позволяет ему создавать файлы в среднем на ~26% меньше, чем идентичные PNG.
-
-
Альфа-канал (Прозрачность): В отличие от JPEG (который вообще не поддерживает прозрачность), WebP поддерживает полноценный 8-битный альфа-канал даже в режиме с потерями (lossy). Это позволяет создавать полупрозрачные изображения с очень малым размером файла.
-
Анимация: WebP поддерживает анимацию (также с потерями или без), что делает его прямой заменой тяжеловесным GIF-файлам, предлагая 24-битный цвет и альфа-канал, о которых GIF мог только мечтать.
Именно наличие режима Lossless делает его нашим сегодняшним пациентом.
«Гипотеза»: Классический LSB под прикрытием Lossless
Если формат поддерживает сжатие без потерь, это означает, что он гарантирует побитовое восстановление исходной пиксельной сетки при декодировании. PNG и BMP — классические примеры.
Наша гипотеза проста: если WebP Lossless ведет себя как PNG, мы можем применить к нему самый надежный и емкий метод стеганографии для растровых изображений — LSB (Least Significant Bit).
Процесс должен выглядеть так:
-
Берем любой файл WebP (даже сжатый с потерями, это не важно).
-
Декодируем его в «сырую» пиксельную карту — то есть в RGB-представление в виде, например, массива NumPy. На этом этапе любое исходное сжатие (lossy или lossless) уже исчезло. Перед нами просто сетка пикселей.
-
К этому сырому массиву пикселей мы применяем наш стандартный алгоритм LSB-внедрения: прячем данные (файл + заголовок с метаданными) в младшие значащие биты (например, в 2 последних бита) каждого цветового компонента (R, G и B).
-
(Критический шаг) Полученный модифицированный массив пикселей мы должны снова закодировать в WebP. Но на этот раз мы принудительно указываем кодировщику использовать режим
lossless=True.
Зачем нужен шаг 4? Если бы мы попытались сохранить наш LSB-модифицированный массив обратно в lossy WebP (или JPEG), алгоритм сжатия с потерями (основанный на предсказании и квантовании) просто «не заметил» бы наши деликатные изменения в младших битах и полностью бы их уничтожил.
Используя lossless=True, мы гарантируем, что кодировщик WebP аккуратно сожмет нашу модифицированную пиксельную сетку без потери единого бита, сохранив наш payload в целости.
«Эксперимент»: Реализация на Python (с помощью PIL)
Наша программа уже использует библиотеку Pillow (PIL), которая отлично справляется с WebP.
Шаг 1. Чтение и подготовка контейнера
Первый шаг — универсальное чтение любого изображения (включая .webp, который мы добавили в список поддерживаемых) в массив NumPy. На этом этапе PIL сам справляется со всем декодированием.
# (Упрощенная логика из file_handlers.py) from PIL import Image import numpy as np def read_image_to_array(filepath: str): with Image.open(filepath) as img: # Мы приводим все к единому формату RGB, # чтобы наш LSB-алгоритм работал универсально img = img.convert('RGB') data = np.array(img, dtype=np.uint8) return data # Добавляем .webp в список поддерживаемых SUPPORTED_IMAGE_FORMATS = ['.png', '.bmp', '.tiff', '.jpeg', '.jpg', '.webp']
Шаг 2. LSB-внедрение (Ядро)
Полученный массив data отправляется в наш основной модуль steganography_core.py. Эта функция не знает ничего о форматах; она просто работает с байтами массива NumPy. Она последовательно заменяет N (от 1 до 8) младших бит в каждом байте массива битами нашего payload, предварительно добавив заголовок с магическим числом, длиной данных и флагом шифрования.
# (Описание логики из steganography_core.py) import steganography_core as stego # ... получаем payload (секретные байты) ... # ... получаем carrier_data (массив NumPy с Шага 1) ... # n_bits = 2 (например) # is_encrypted = False # Эта функция возвращает НОВЫЙ массив NumPy с уже внедренными данными stego_data_array = stego.hide(carrier_data, payload, n_bits, is_encrypted)
Шаг 3. Критически важное сохранение
Теперь у нас есть stego_data_array — модифицированный массив пикселей. Мы должны сохранить его обратно в файл. Здесь вступает в игру наша специальная логика сохранения из file_handlers.py.
Мы используем PIL для сохранения, но передаем ему дополнительные аргументы (**save_kwargs) в зависимости от расширения файла.
# (Логика из функции save_file в file_handlers.py) def save_image_from_array(payload_array: np.ndarray, filepath: str): image_to_save = Image.fromarray(payload_array.astype(np.uint8), 'RGB') # Собираем kwargs для функции save() save_kwargs = {} # ВОТ КЛЮЧЕВОЙ МОМЕНТ: # Если пользователь выбрал сохранение в .webp, # мы ОБЯЗАНЫ включить флаг 'lossless'. if filepath.lower().endswith('.webp'): save_kwargs['lossless'] = True # (Тут также добавляется логика для EXIF, если он есть) # ... # PIL получит команду: image.save("file.webp", lossless=True) image_to_save.save(filepath, **save_kwargs)
Эта логика гарантирует, что наши LSB-данные выживут. Благодаря этому, на вкладке «Встраивание» наша программа теперь по умолчанию предлагает сохранить стего-контейнер в его исходном формате (.webp -> .webp, .bmp -> .bmp), а уже вторым вариантом предлагает безопасный .png.
Выводы
WebP — это не просто «еще один формат». Для целей стеганографии он представляет собой вершину эволюции растровых контейнеров.
Он сочетает в себе несовместимые ранее вещи:
-
Популярность (как у JPEG): Он используется повсеместно, не вызывая подозрений.
-
Lossless-режим (как у PNG): Он гарантирует сохранность LSB-данных.
-
Высокое сжатие (лучше PNG): В режиме Lossless он создает файл меньшего размера, чем PNG, при той же пиксельной емкости, что делает его более эффективным «транспортом» для того же объема скрытых данных.
WebP — это настоящий хамелеон, идеально маскирующий свое содержимое как за счет современного алгоритма сжатия, так и за счет своей способности к побитовой сохранности данных.
Последнюю версию программы «Steganographia» от ChameleonLab для Windows и macOS можно скачать на нашем официальном сайте https://chalab.ru.
Будем рады, если вы опробуете новую версию. Ждем ваших отзывов, сообщений об ошибках и, конечно же, предложений по новым форматам для исследований. Присоединяйтесь к нашему Telegram-каналу https://t.me/ChameleonLab !
Спасибо за внимание!
ссылка на оригинал статьи https://habr.com/ru/articles/945136/
Добавить комментарий