Распаковываем Perl-скрипты, обработанные PerlApp

Как известно, для Perl, впрочем, как и для других скриптовых языков, существуют утилиты, позволяющие создавать из скрипта полноценный exe-файл, который можно переносить на другие компьютеры и запускать, даже если интерпретатор языка на них не установлен. В случае с perl'ом наиболее популярными утилитами являются Perl2Exe и PerlApp.
Принцип работы этих утилит довольно прост и состоит в упаковке внутрь результирующего exe-файла библиотеки перла, основного скрипта и зависимых модулей. Содержимое, естественно, сжимается, шифруется (с помощью XOR) и не хранится в открытом виде внутри файла. Исследуем чуть подробнее внутреннее устройство результирующих exe-файлов, которые получаются с помощью PerlApp.
Для начала, определим с помощью чего сжимаются данные. Это сделать довольно просто, например, с помощью PeID (с плагином Krypto Analyzer) или какого-нибудь hex-редактора. В случае с PeID все тривиально: указываем путь к файлу, запускаем плагин и получаем список найденных крипто-сигнатур.

С hex-редактором тоже просто: открываем нужный файл, нажимаем Alt+F6 (справедливо для Hiew), получаем список строковых ресурсов, гуглим эти строки.

Таким образом определяем, что для сжатия используется библиотека zlib, причем довольно старая версия - 1.1.4. Конечно, можно начать искать, где именно в файле хранятся сжатые данные, но мне захотелось пойти другим путем.

Итак, нам понадобится какой-нибудь дизассемблер, например, IDA или OllyDbg, а также пара подопытных exe-файлов, желательно, упакованных разными версиями PerlApp, чтобы однозначно определить сигнатуру функции распаковки. Функция распаковки элементарно ищется, если ориентироваться по строке с версией (1.1.4), но, как видно, функции довольно сильно могут отличаться от версии к версии:



Однако, если обратить внимание на хвост функции, то мы увидим, что там встречается устойчивая последовательность байтов, которая вдобавок уникальна для файла в целом. Это довольно удобно, да и нас как раз интересует указатель на буфер с распакованными данными, который она возвращает.


Как видно из скриншотов, она может слегка различаться (всего 2 байта), но это не проблема, так как никто не мешает реализовать поиск по маске. Теперь нам надо как-то перехватить данные, которые помещаются в EAX в конце функции, чтобы затем записать их в файл. Один из вариантов - организовать в конце функции JMP в тело своей функции, в ней выполнить затертые прыжком инструкции, записать содержимое буфера, куда надо и вернуться назад, но опять же, мне захотелось пойти немного другим путем. Вместо создания "трамплина" я просто переписываю инструкцию RETN инструкцией INT3, которая передает управление в VEH, в нём содержимое буфера записывается в файл, изменяются регистры EIP и ESP (через структуру PEXCEPTION_POINTERS) и программа продолжает работать дальше, как будто ничего не произошло и вместо INT3 была выполнена инструкция RETN.

Теперь приведу код, которые реализует то, что я описал выше. В результате получится DLL, которую нужно прописать в импорты exe-файла со скриптом, либо подгрузить её каким-нибудь другим способом.
Для начала обозначим инклюды, пару констант и создадим пустую экспортируемую функцию:

DUMP_DIRECTORY - определяет имя папки, куда будут сохранены "перехваченные данные". SEARCH_LIMIT - максимальная дальность поиска сигнатуры функции от указанного начала (можно считать за размер секции кода, он вроде бы не меняется от версии к версии и как раз равен 0xB000). RANGE_LIMIT - максимальная дальность поиска инструкции RETN относительно найденной сигнатуры функции. SIG - сама сигнатура, i - счетчик, на основе которого формируется имя очередного файла для записи содержимого буфера. dummy - пустая функция, которая будет экспортироваться библиотекой.
Теперь нам понадобятся функции поиска по маске указанной последовательности байт. Функции я честно позаимствовал из какого-то сорца за авторством sn0w, вот они:

Настал черед функции, которая найдет сигнатуру конца функции и заменит RETN на INT3.

Функция довольно тривиальная. Сначала определяется адрес, по которому загрузился exe-файл, потом по сигнатуре ищется функция (смещаемся на 0х1000, чтобы пропустить заголовок PE), если сигнатура найдена, то ищется опкод инструкции RETN, меняется тип защиты региона и вместо RETN (0xC3) записывается INT3 (0xCC).
Теперь функция записи в файл и VEH:

В функции Write формируется имя файла вида "имя_директории/число.txt", далее определяется размер буфера (вплоть до первого нулл-байта) и его содержимое записывается в файл. В VEH мы сначала вызываем функцию Write, передав ей в качестве аргумента адрес буфера, который хранится в EAX, далее меняем содержимое EIP, чтобы выполнение продолжилось с адреса, на который указывает верхушка стека, меняем ESP (указатель на верхушку стека), как это делает инструкция RETN, и, наконец, возвращаем EXCEPTION_CONTINUE_EXECUTION, чтобы нормально продолжить выполнение с места возникновения исключения.
Теперь осталась только функция DllMain:

В ней мы создаем директорию для хранения текстовиков с дампами, устанавливаем обработчик исключений (Vectored Exception Handler) и вызываем функцию Hook. Вот и всё. Почему мы ставим именно обработчик векторных исключений, а не структурных (Structured Exception Handling, SEH)? Все просто - внутри exe-файла, созданного PerlApp, вполне могут использоваться собственные обработчики структурных исключений, которые перебьют установленный нами в DLL обработчик. А у VEH перед SEH всегда приоритет, так что SEH-обработчики внутри файла даже ничего не узнают о том, что возбуждалось исключение INT3.
Теперь компилируем этот код как DLL'ку, берем какой-нибудь скрипт, обработанный с помощью PerlApp, прописываем библиотеку в импорты, например, с помощью CFF Explorer:

И наслаждаемся результатом:

В файле 3.txt видим исходный скрипт, что нам и требовалось.
Исходный код и скомпилированная библиотека: скачать
GitHub: perlapp-unpacker

Про распаковку скриптов после Perl2Exe читайте здесь.

Распаковываем Perl-скрипты, обработанные PerlApp: 7 комментариев

  1. что ты наделал,теперь уже не как не скрыть исходники перла,удали быстра
    ты слишком глубоко капаешь.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *