Получение исходного кода PowerPacker Cruncher от AmigaOS
Всем привет,
Демо-сцена существует очень давно. Зачастую, в процессе разработки очередной крутой демки приходится изобретать крутые алгоритмы: как для красивых анимаций и трекерной музыки, так и для кода. Иногда код получается большого объёма, поэтому его требуется сжать.
Понятно, что можно взять любой доступный алгоритм сжатия и использовать его у себя, но не существовало бы сейчас такого огромного количества различных упаковщиков, если бы всем хватало одного единственного алгоритма. Кому-то не нравится скорость работы, кому-то — качество сжатия, вот и изобретаются всё новые и новые алгоритмы. Одним из них и стал PowerPacker, исходные коды которого хотели получить многие, но удалось только мне.
Кранчер (упаковщик) PowerPacker использовался во множестве старых игр (для AmigaOS в частности). Видимо, на то время он обладал очень хорошим сжатием и временем работы, по сравнению с другими кранчерами. К тому же, он позволяет шифровать сжимаемые данные, давая возможность защитить ресурсы игры или программы (да, можно упаковывать и исполняемые файлы).
Сначала PowerPacker распространялся в виде самостоятельных исполняемых файлов: упаковщика и распаковщика. Затем, похоже, спрос на данный алгоритм сжатия вырос, и автор (Nico François) решил сделать своё творение платным, при этом перейдя на распространение уже в виде библиотеки 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\\.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