DOOM на экране осциллографа Siglent SDS5000X

Запуск легендарного шутера DOOM на устройствах, которые для этого совершенно не предназначены, давно стал своего рода технологическим ритуалом. В ход идет всё: от домофонов и калькуляторов до электронных книг и осциллографов. Глубокий разбор самого феномена «думозапуска» можно найти в материале от @shiru8bit — крайне рекомендую к ознакомлению.

DOOM на картофеле
DOOM на картофельном питании

Признаюсь, я довольно скептически отношусь к подобным экспериментам, если они попадают под один из следующих критериев:

  • Подмена оригинальной начинки на современное железо, способное без труда запустить игру (как это было в нашумевшей истории с тестом на беременность);

  • Процесс портирования не потребовал от автора серьезных интеллектуальных усилий.

Стоит конкретизировать понятие «серьезных усилий» в моем понимании:

  • Развертывание собственной системы сборки под специфическую архитектуру (когда стандартные компиляторы недоступны или не работают);

  • Глубокая адаптация подсистемы ввода-вывода под специфические органы управления (простое подключение USB-периферии не считается);

  • Получение низкоуровневого доступа к системе (рутирование смартфона по шаблону и установка APK-файла — это слишком просто).

Разобравшись с моими принципами «честного портирования», перейдем к делу: запуску DOOM на цифровом осциллографе Siglent SDS5034X. Для реализации задуманного пришлось найти способ получения шелла (о чем был отправлен отчет производителю), переписать систему управления под энкодеры передней панели и задействовать встроенный пьезодинамик для вывода звука.

В поисках доступа к системе

Я на осциллографе
Автопортрет через консоль осциллографа

Наиболее очевидный путь — использование протоколов Telnet или SSH. Однако в свежих версиях прошивки вендор предусмотрительно их отключил.

Существовал и «железный» вариант: вскрытие корпуса, выпаивание флеш-памяти для записи игровых данных. Но такой деструктивный метод не принес бы должного удовлетворения, да и гарантию на новый прибор терять не хотелось. Я выбрал более изящный путь — исследование механизма обновления ПО.

Анализ файлов обновления

Официальные апдейты для линейки SDS5034X имеют расширение ADS. Сравнение бинарных данных моделей SDS2000X и SDS5000X выявило структурные различия. Тем не менее я решил протестировать специализированную утилиту для SDS2000X, найденную на просторах сети.

Список обновлений
История версий ПО

Вместо самой программы мне удалось разыскать только её хеш в теме, где пользователи обсуждали ложные срабатывания антивирусов на VirusTotal. Как бывший специалист в сфере кибербезопасности, я воспользовался сервисом VirusTotal Intelligence для загрузки искомого файла.

Реверс-инжиниринг Nuitka

Программа оказалась Python-скриптом, скомпилированным в исполняемый файл с помощью Nuitka. Вместо того чтобы просто запустить его, я решил разобраться в алгоритмах работы утилиты.

Типичное начало main() для Nuitka
Характерная точка входа main() для Nuitka-бинаря

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

  • Внешняя оболочка — это контейнер с основным бинарем и runtime-компонентами Python. Распаковывается при помощи nuitka-extractor;

  • Полное автоматическое восстановление исходников невозможно, но логику можно воссоздать вручную;

  • Текстовые строки хранятся в открытом виде в секции ресурсов, что позволяет анализировать поведение программы.

В результате кропотливого анализа мне удалось извлечь:

  • Ключ для дешифровки заголовка ADS-файла;

  • Модифицированный алгоритм 3DES (на базе pyDes.py, вот сравнение реализаций);

  • Функцию деобфускации контента, которая оказалась идентичной версии для SDS2000X.

Часть отличий от оригинала
Ключевые отличия в логике обработки

Декомпозиция прошивки

Полученные данные позволили расшифровать первые 0x70 байт (заголовок) файла прошивки:

Расшифрованный заголовок прошивки
Результат расшифровки заголовка

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

Окончание ADS-файла
Структура данных в конце ADS-файла

После простого разворота байтов через data[::-1] проявилась структура ZIP-архива, хотя и в искаженном виде.

Файл обновления после разворачивания байт
Инвертированные данные прошивки

Применив алгоритм деобфускации к инвертированным данным, я получил полноценный ZIP-архив. Характерные байты PK\x01\x02 окончательно подтвердили успех операции.

Сборка модифицированных обновлений

Следующим этапом стала подготовка кастомного ADS-файла. Здесь я позволю себе небольшое отступление:

В этом проекте я впервые активно использовал ИИ для генерации кода. Это кардинально меняет подход к работе. То, на что раньше уходили часы рутинного написания кода, теперь делается за минуты. Да, это вызывает определенную профессиональную рефлексию, но эффективность процесса неоспорима.

С помощью нейросети был написан инверсный деобфускатор. Соединив его с шифрованием заголовка, я получил почти идеальный клон оригинального файла. Камнем преткновения стали лишь 4 байта в заголовке, ради которых все же пришлось прибегнуть к разборке прибора.

Загадка контрольной суммы

Для детального изучения пришлось вскрыть Siglent SDS5034X и сделать дамп памяти.

Осциллограф Siglent SDS5034X
Внутреннее устройство Siglent SDS5034X

Как выяснилось позже, механическое вмешательство было избыточным — все ключи лежали внутри ZIP-архива обновления. Пройдя через цепочку вложенных архивов (uImage -> Linux Kernel -> многократные gz), я добрался до файловой системы и бинарного файла adsdec, отвечающего за валидацию обновлений.

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

Обновление прошивки через Web-интерфейс
Процесс загрузки кастомного ПО

Нюанс заключался в том, что системный архиватор осциллографа требовал сохранения прав доступа (POSIX permissions) внутри ZIP-файла, что удалось реализовать только средствами Linux-системы.

Часть файлов в ZIP-архиве
Содержимое архива обновления

Получение Shell-доступа

Добавив telnetd в скрипты автозагрузки, я успешно открыл порт управления. Использование порта выше 10000 позволило избежать конфликтов, а отсутствие пароля в системе значительно упростило задачу.

Пароль не нужен
Успешный вход в систему без пароля

Шелл получен. Настало время для DOOM.

Гайд по сборке DOOM для встраиваемых систем

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

Основные препятствия при портировании:

  • Ограниченный объем памяти для хранения MIDI-семплов;

  • Специфический ввод: необходимость маппинга физических кнопок и энкодеров вместо клавиатуры;

  • Отсутствие графических фреймворков вроде SDL2;

  • Вывод звука через низкоуровневые интерфейсы (в нашем случае — зуммер).

Buzzer
Тот самый пьезодинамик

Компиляция

За основу взят fbDOOM. Сначала определяем архитектуру целевого устройства командой file на любом системном бинаре. Наша цель — добиться полной совместимости форматов ELF.

file ./bin/busybox
./bin/busybox: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 2.6.16, stripped

Работа с зуммером buzzer_zynq потребовала прямого обращения к памяти. Экспериментально через devmem был определен диапазон частот (14000-65536) по адресу 0xF800100C.

В Makefile проекта fbDOOM вносим следующие правки:

  • NOSDL=1 — отключаем зависимости от SDL;

  • Указываем корректный кросс-компилятор (arm-linux-gnueabi-);

  • Добавляем флаг -static для статической линковки.

Система ввода

Адаптация органов управления была реализована через чтение устройства /dev/siglent_kb. Анализ показал, что каждое действие на панели генерирует 4-байтовое значение в потоке данных.

#include 
#include 
#include 

int main(int argc, char* argv[]) { int fd = open("/dev/siglent_kb", O_RDONLY); if (fd == -1) return -1;

while (1) {
    int val = 0;
    if (read(fd, &val, 4) > 0) {
        printf("%08X\n", val);
    }
}
return 0;

}

Интеграция в i_input_tty.c потребовала реализации искусственных задержек между событиями нажатия и отпускания клавиш, чтобы движок игры корректно распознавал команды.

Аудиосистема

Самая трудоемкая часть — звук. Стандартные звуковые схемы DOOM не подходят для пьезодинамика. Был создан новый тип аудиоустройства SNDDEVICE_SIGLENT с собственной реализацией в i_sdsmusic.c и i_sdssound.c.

Музыка в игре была преобразована в последовательность команд {частота, длительность}, которые воспроизводятся в отдельном потоке. Для корректной работы звуковых эффектов пришлось реализовать механизм микширования, так как физический излучатель в системе всего один, а звуковых каналов в игре — шестнадцать.

Особое внимание уделили управлению потоками: использование pthread_detach() решило проблему переполнения таблицы тредов, из-за которой звуки пропадали спустя пару минут игры.

Графический вывод

Для повышения производительности стандартный метод write() во фреймбуфер /dev/fb0 был заменен на mmap() с последующим memcpy(). Это позволило добиться плавной частоты кадров без артефактов отрисовки.

Итоги

Проект завершен: игра загружается в /tmp, права доступа выданы, и адские легионы на экране осциллографа готовы к бою. Результат можно увидеть на видео. Это был увлекательный опыт реверс-инжиниринга и низкоуровневой разработки.

Исходный код форка доступен в репозитории: https://github.com/lab313ru/fbDOOM_SDS5000X.

Бонус

P.S. Больше интересных исследований в области Hardware Security ищите в нашем телеграм-канале.

 

Источник

Читайте также