Всем привет,
Демо-сцена существует очень давно. Зачастую, в процессе разработки очередной крутой демки приходится изобретать крутые алгоритмы: как для красивых анимаций и трекерной музыки, так и для кода. Иногда код получается большого объёма, поэтому его требуется сжать.
Понятно, что можно взять любой доступный алгоритм сжатия и использовать его у себя, но не существовало бы сейчас такого огромного количества различных упаковщиков, если бы всем хватало одного единственного алгоритма. Кому-то не нравится скорость работы, кому-то качество сжатия, вот и изобретаются всё новые и новые алгоритмы. Одним из них и стал PowerPacker, исходные коды которого хотели получить многие, но удалось только мне.
Немного о PowerPacker
Кранчер (упаковщик) PowerPacker использовался во множестве старых игр (для AmigaOS в частности). Видимо, на то время он обладал очень хорошим сжатием и временем работы, по сравнению с другими кранчерами. К тому же, он позволяет шифровать сжимаемые данные, давая возможность защитить ресурсы игры или программы (да, можно упаковывать и исполняемые файлы).
Сначала PowerPacker распространялся в виде самостоятельных
исполняемых файлов: упаковщика и распаковщика. Затем, похоже, спрос
на данный алгоритм сжатия вырос, и автор (Nico Franois) решил
сделать своё творение платным, при этом перейдя на распространение
уже в виде библиотеки powerpacker.library
.
Получение исходников
Для получения исходников, как и в случае с RNC ProPack, пришлось написать множество вспомогательного инструментария:
- Плагин-отладчик для IDA Pro (не работает, забросил)
- Загрузчик Amiga Hunk для Ghidra (помог)
- Загрузчик для library-файлов для Ghidra (очень помог)
- gdb-сервер для AmigaOS, работающий на ней же (не работал на моих файлах)
Отдельным пунктом идёт покупка kickstart rom
(это
что-то типа биоса, нутрянки AmigaOS, без него работать ничего не
будет).
Потом у IDA появилась возможность отлаживать через
GDB
в том числе и для m68k
. Правда
серверной части, которая могла бы при этом эмулировать и мои файлы,
и AmigaOS, у меня не было. WinUAE не умеет в gdb
до
сих пор.
Затем, спустя несколько лет, появилось расширение для
Visual Code
: https://github.com/BartmanAbyss/vscode-amiga-debug,
которое позволяет отлаживать исходные файлы на C, при помощи
модифицированного WinUAE с добавленным в него
gdb
-сервером. Вот здесь я и осознал шанс на
декомпиляцию есть.
Декомпиляция
Этот процесс без собственно самого декомпилятора превращается в долгое и мучительное преобразование ассемблерных инструкций в сишный код. И, если с кодом, который генерировался C-компилятором, проблем обычно не возникает, то вот с вручную написанным ассемблерным кодом проблем достаточно. Вот самые основные из них:
- циклы (бесконечные goto)
- использование одного и того же регистра как для хранения 16-битных значений, так и для хранения 32-битных. А ещё они в какой-то момент становятся знаковыми, хотя до этого использовались как беззнаковые.
Отладочный стенд
Для начала пришлось понять, как именно работает указанное выше расширение. Устанавливается оно в следующий каталог:
C:\Users\<USER>\.vscode\extensions\bartmanabyss.amiga-debug-1.0.0
Создаём и компилируем тестовый пример (да, у расширения он
имеется). В подпапке .\bin
имеется следующий список
файлов:
- dh0\
- dh0\runme.exe
- dh0\s\
- dh0\s\startup-sequence
- opt\
- default.uae
- elf2hunk.c
- elf2hunk.exe
- gnumake.exe
- winuae.ini
- winuae-gdb.exe
Подкаталог .\dh0\s
содержит файл
startup-sequence
, в котором указываются команды,
запускаемые при старте операционной системы. У меня он выглядит вот
так:
:runme.exe
Здесь можно добавить нужные аргументы или команды. Для моих
целей необходимо заменить файл runme.exe
на
исполняемый файл от PowerPacker-а, который затем будет загружать ту
самую powerpacker.library
. А вот куда класть эту
библиотеку я понял не сразу. Оказывается, нужно было создать в
каталоге .\dh0\
подкаталог Libs
(я
подсмотрел эту структуру в уже запущенной AmigaOS) и положить туда.
Запускаю.
После выполнения данной команды произойдёт запуск
winuae-gdb.exe
, открытие порта 2345
для
работы с gdb
, и остановка на точке входа запускаемой
программы. Остаётся только подключиться с помощью IDA и её
Remote GDB debugger
к сессии WinUAE.
Меняем порт на 2345
, жмём
Debugger
->Attach to process...
, затем
выбираем процесс с id = 0
.
После этого у нас появляется окно отладки:
Как видим, адрес на котором мы стоим, отличается от адреса, на
котором создавалась idb 0x10000
, поэтому останавливаем
отладку и делаем Rebase на 0x27D30
. Это поможет в
дальнейшей отладке не терять изменений, сделанных в базе.
С этого момента можно спокойно заниматься пошаговой отладкой
до тех пор, пока вы не превысите количество брейкпоинтов равное
20. Сначала я не догадывался, в чём причина, но мои
брейкопоинты вдруг становились неактивными, невалидными. Лишь
посмотрев в исходник WinUAE (который, к тому же, собрать совершенно
не просто), я нашёл ограничение в 20
брейкопоинтов.
Собрав новую сборку с количеством, равным 999
, мне
удалось наконец-то безболезненно заниматься самим процессом
отладки.
Библиотека powerpacker.library
Тут пришлось изощряться, попутно найдя изящное решение, которое
может помочь и вам при отладке загружаемых библиотек. Дело в том,
что загруженные в память библиотеки (как и другие появляющиеся
только во время отладки регионы памяти), можно сохранять прямо в
idb
, и работать с ними, при желании, в статике. При
этом, при перезапуске процесса отладки, вы не потеряете свои
наработки по переименованию переменных, меток, и т.п. Для
проворачивания этой хитрости необходимо на необходимом сегменте,
после загрузки нужной библиотеки, зайти в его свойства (выбрав
Edit segment...
):
Вы увидите, что там присутствует галка Debugger
segment
, при снятии которой и нажатии OK
,
данный сегмент будет сохранён в базу. Единственный момент: стоит
следить за размером сегмента, иначе сохранение его в базу может
растянуться, или вообще не закончиться.
Теперь можно входить в вызовы экспортируемых функций, и, при одном и том же адресе загрузки библиотеки, вы будете попадать в свой, уже проанализированный, код. Удобно.
В случае AmigaOS вызовы экспортируемых функций выглядят как вызовы по отрицательным смещениям относительно базы, по которой загружена библиотека:
Далее выяснилось, что у библиотеки имеется множество различных версий, которые, как оказалось, отличаются силой сжатия. Изначально, кое-как разреверсив одну версию библиотеки, я столкнулся с тем, что на выходе получались отличные от оригиналов файлы. Пришлось реверсить вторую библиотеку, более новую. Различие оказалось всего лишь в размере окна.
Результаты работы
Спустя три недели каждодневного реверс-инжиниринга по вечерам (а по выходным так и целые сутки) мне удалось всё таки получилось алгоритм, используемый в оригинальной библиотеке. К тому же, я добавил флаг, позволяющий сжимать старым алгоритмом, где использовался меньший размер окна.
Протестировав всё на 210
файлах, найдя и исправив
другие вылезающие баги (такие как выход за границы массива в
оригинальном алгоритме), я готов опубликовать результаты своей
работы:
Ссылки
Исходники: https://github.com/lab313ru/powerpacker_src
Релизы: https://github.com/lab313ru/powerpacker_src/releases