Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Реверс‑инженеру далеко не всегда удается без труда исследовать написанные на Python приложения. Разработчики умеют хранить свои тайны, используя обфускацию, шифрование, кастомный маршалинг и собственные интерпретаторы. Но для любого замка отыщется отмычка: сегодня мы разберемся, как реверсить такие программы, научимся восстанавливать исходный код и узнаем, как создать для них собственный дизассемблер.
Мы уже писали о распространенных способах защиты исходного кода Python от реверс‑инжиниринга и принципах их обхода. В сегодняшней статье я продолжу эту тему и расскажу об особенностях реверса кастомной нестандартной реализации Python-интерпретатора.
warning
Статья написана в исследовательских целях, имеет ознакомительный характер и предназначена для специалистов по безопасности. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Использование или распространение ПО без лицензии производителя может преследоваться по закону.
В качестве примера возьмем некое приложение для нейросетевой обработки видео с нехорошей особенностью: приложение проверяет лицензию на удаленном сервере при выполнении всех более‑менее полезных функций. Если проверка завершается успешно, оно продолжает работать офлайн, что дает нам надежду на возможность запускать программу без подключения к интернету.
То, что это классическое Python-приложение, видно с первого взгляда, даже без запуска Detect It Easy.
Поскольку рядом с исполняемым модулем лежат питоновские библиотеки, а подкаталоги заполнены файлами с расширением .pyc
, никаких сомнений в использовании Python не остается. Что это за файлы и с чем их едят, я подробно рассказывал в статье «Змеиная анатомия. Вскрываем и потрошим PyInstaller». В двух словах поясню для тех, кто не читал эту публикацию: упомянутые бинарные файлы содержат прекомпилированный байт‑код, подаваемый непосредственно на вход интерпретатора для ускорения обработки. В этой же статье приведены и известные декомпиляторы и дизассемблеры файлов подобного формата (uncompyle6, pycdc, pydasm…).
На этой оптимистичной ноте можно было бы и закончить статью, но нет. Несмотря на валидный заголовок .pyc-файла с сигнатурой версии 3.10 (выделено красным), ни один из стандартных декомпиляторов или дизассемблеров не ест такие файлы, выдавая ошибку типа CreateObject: Got unsupported type 0x5D
. При просмотре файла в HEX-редакторе мы видим, что он весь, собственно, и состоит из явно зашифрованного объекта нестандартного типа (выделено желтым, 0xDD & 0x7F = 0x5D
).
Налицо явно кастомная нестандартная питоновская реализация, благо, как я уже говорил, исходный код Python в сети присутствует, и нет ничего сложного подогнать его под свои нужды. По счастью, шифрование здесь используется явно не шибко взрослое. На скриншоте прослеживается повторяющийся через каждые 16 байт шаблон, который, похоже, наложен операцией XOR на исходный код и местами проглядывает на месте нулевых последовательностей.
Поискав в нативных библиотеках явный фрагмент такого шаблона (например, 25 02 39 04
), мы тут же натыкаемся в файле python311.dll
на шаблон (выделено красным) и код, расшифровывающий данные сразу после чтения из файла.
Чуть поигравшись с отладчиком x64dbg, мы обнаруживаем и место вызова этого кода: Py_Main -> PyRun_SimpleFileObject -> PyMarshal_ReadLastObjectFromFile
, начиная с которого в отладчике можно следить за расшифровкой и интерпретацией байт‑кода.
Попробуем для начала добиться какой‑то более‑менее понятной декомпиляции .pyc-файлов. Для этого напишем простенький расшифровщик, расксоривающий объект 0x5D
полученной маской. Благодаря этому исходный файл удается привести в гораздо более читаемый вид.
К сожалению, этот код, хоть и сильно похож на настоящий, все равно не работает. На этот раз вылезает ошибка CreateObject: Got unsupported type 0x0
. У меня возникло сильное подозрение на нестандартный маршалинг объектов внутри .pyc-файла.
Источник: xakep.ru