Упаковщики исполняемых файлов. Разрабатываем свой упаковщик исполняемых файлов. Переделываем в криптор

Упаковщики исполняемых файлов. Разрабатываем свой упаковщик исполняемых файлов. Переделываем в криптор

Наш упаковщик сжал notepad.exe сильнее, чем UPX!

Переделываем в криптор

Собственно, от криптора наш пакет отличает совсем немногое: отсутствие функции шифрования и противоэмуляционных приемов. Самое простое, что можно с ходу сделать, - это добавить xor всего образа сразу после распаковки в загрузчике. Но, чтобы эмуляторы антивирусов подавились, этого недостаточно. Нужно как-то усложнить задачу. Например, не прописывать ключ xor’а в теле загрузчика. То есть загрузчик не будет знать, каким ключом ему надо расшифровывать код, он будет его перебирать в определенных нами рамках. Это может занять какое-то время, которое есть у пользователя, в отличие от антивируса.
Также ключ можно сделать зависимым от какой-нибудь неэмулируемой функции или структуры. Только их еще найти надо.
Чтобы код загрузчика не палился сигнатурно, можно прикрутить к упаковщику какие-нибудь продвинутые вирусные движки для генерации мусора и всяческого видоизменения кода, благо их в Сети навалом. Добавить метки

Под упаковкой (сжатием, уплотнением) понимают такое перекодирование данных, которое позволяет уменьшить по сравнению с исходным объем памяти, необходимый для их хранения.

Программы – архиваторы служат для сжатия файлов, что позволяет хранить их в сжатом виде в одном архивном файле (архиве), что позволяет уменьшить объём занимаемой файлами памяти.

Сжатие информации - это процесс преобразования информации, хранящейся в файле, к виду при котором уменьшается избыточность в её представлении и соответственно требуется меньший объём памяти для хранения.

Упаковка данных используется для экономии дискет при переносе программ и данных с одного компьютера на другой. В упакованном виде удобно хранить временно не используемые программы и данные, а также копии файлов, создаваемые на случай утраты оригиналов.

Созданные архивы можно обновлять, добавлять в них файлы, удалять файлы из архива, проверять архив на целостность. При необходимости файлы можно вывести их архива - разархивировать.

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

Архивация (упаковка) – помещение (загрузка) исходных файлов в архивный файл в сжатом или не сжатом виде.

Разархивация (распаковка) – процесс восстановления файлов из архива точно в таком виде, какой они имели до загрузки в архив. При распаковке файлы извлекаются из архива и помещаются на диск или в оперативную память.

Программы, осуществляющие упаковку и распаковку файлов, называют программами - архиваторами.

Для сжатия файлов пользователями ПК широко используется множество программ архиваторов.

Преимущества архиваторов:

    позволяют сжимать от 22 % до 90% информации;

    позволяют обновлять программное обеспечение, причем архиватор сам следит за процессом обновления;

    позволяют создавать самораспаковывающиеся архивы;

    позволяют содержать в одном файле группу однородных файлов.

    поддержка непрерывных архивов, в которых степень сжатия может быть на 10 - 50% больше, чем при обычных методах сжатия, особенно при упаковке большого количества маленьких похожих файлов;

    поддержка многотомных архивов;

    создание самораспаковывающихся (SFX) обычных и многотомных архивов с помощью стандартного или дополнительных модулей SFX;

    восстановление физически поврежденных архивов;

    другие дополнительные функции, например, шифрование, добавление архивных комментариев (с поддержкой ESC-последовательностей ANSI), протоколирование ошибок и пр.

А рхив содержит оглавление, в котором находится следующая информация:

    имя файла,

    дата и время создания или модификации,

    объем файла до и после архивации,

    процент сжатия,

    код циклического контроля для каждого файла (контрольная сумма)

Архиваторов очень много: ARJ,RAR,ZIP, CAB, LZH,GIF,TIF,PCX…

Архивные файлы могут быть непрерывными, многотомными, самораспаковывающимися.

Непрерывный архив - это архив, запакованный специальным способом, при котором все сжимаемые файлы рассматриваются как один последовательный поток данных.

Тома - это фрагменты архива, состоящего из нескольких частей. Тома поддерживаются только в формате RAR, вы не можете создавать тома ZIP. Обычно тома используются для сохранения большого архива на нескольких дискетах или других сменных носителях.

Самораспаковывающийся (SFX, от англ. SelF-eXtracting) архив - это архив, к которому присоединен исполнимый модуль. Этот модуль позволяет извлечь файлы, просто запустив архив как обычную программу. Таким образом, для извлечения содержимого SFX-архива не требуется дополнительных внешних программ. SFX-архивы, как и любые другие исполнимые файлы, обычно имеют расширение.EXE.

Самораспаковывающийся архивный файл - это загрузочный, исполняемый модуль, который способен к самостоятельной разархивации находящихся в нём файлов без использования программы архиватора

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

Степень сжатия информации зависит от типа файла, а также от выбранного метода упаковки. Степень (качество) сжатия файлов характеризуется коэффициентом сжатия Кс, определяемым как отношение объёма сжатого файла Vc к объёму исходного файла Vo, выраженное в процентах: Кс=.

Чем меньше величина КС, тем выше степень сжатия информации.

Несмотря на изобилие алгоритмов сжатия данных, теоретически есть только три способа сжатия. Это либо изменение содержания данных, либо изменение их структуры, либо одновременно и то и другое.

Если при сжатии данных происходит изменение их содержания, потеря информации, то метод сжатия необратим и при восстановлении данных из сжатого файла не происходит полного восстановления исходной последовательности. Такие методы называют также методами сжатия с регулируемой потерей информации. Они применимы только для тех типов данных, для которых формальная утрата части содержания не приводит к значительному снижению потребительских свойств. В первую очередь это относится к мультимедийным данным: видеорядам, музыкальным записям, звукозаписям и рисункам. Методы сжатия с потерей информации обычно обеспечивают гораздо более высокую степень сжатия, чем обратимые методы, но их нельзя применять к текстовым документам, базам данных и, тем более, к программному коду.

Если при сжатии данных происходит только изменение их структуры, то метод сжатия обратим. Из результирующего кода можно восстановить исходный массив путем применения обратного метода. Обратимые методы применяются для сжатия любых типов данных.

Существует достаточно много обратимых методов сжатия данных, однако в их основе лежит сравнительно небольшое количество теоретических алгоритмов.

CUP386 3.4

Известный хакерский распаковщик DOS COM/EXE-упакованных или защищенных файлов by Sage/CyberWare (UCF). Позволяет распаковывать программы сжатые/зашифрованные даже неизвестными или новыми утилитами. Работает через командную строку и имеет множество параметров (читай возможностей). В программе имеется несколько режимов взлома: пошаговый real-mode, пошаговый V86 и real-mode i80386-эмуляция. В зависимости от режима, CUP386 использует различные методы отладки, обходит антидебаговый код и т.п. Кстати в программе есть и встроенный визуальный дебаггер наподобие Turbo Debugger для "ручного" взлома. Лично мне оригинальной показалась опция "Я все еще жив", которая заставляет перемигиваться LED-индикаторы на клавиатуре во время распаковки, показывая, что программа пока не зависла.

ftp://ftp.elf.stuba.sk/pub/pc/pack/ucfcup34.zip (60 Кб)

DeShrink 1.6

Самая продвинутая Windows-утилита, предназначенная для "распаковки" EXE/DLL файлов, сжатых популярным EXE-пакером Shrinker (вплоть до версии 3.4). DeShrink имеет графический интерфейс, позволяющий легко указать путь и имена входного/выходного файлов, просмотреть EXE-заголовок и даже снять шифрование с отдельных секций методом простого перебора.

Программу можно скачать с сайта ftp://ftp.elf.stuba.sk/pub/pc/pack/dshrnk16.zip (190 Кб)

ExeScan 3.21

Эта DOS-утилита by STILLSON предназначена для определения паковщиков и навесных защит (довольно много), которыми были защищены EXE или COM файлы. Кроме того ExeScan позволяет распознавать наиболее распространенные компиляторы.

Программа (вернее два ее модуля) может работать в нескольких режимах: в простом ("тупое" определение), в deepscan (пытается обнаружить многослойную защиту/сжатие, например когда EXE"шник сжат PKLite -ом и защищен программой CrackStop) и в режиме generic detection (перехват Int 21h, выполнение исследуемого файла и попытка определить код).

ExeScan также позволяет просматривать и сравнивать информацию заголовков двух разных исполнимых файлов, имеет встроенный дизассемблер (есть функции показа входных точек и т.п.), может работать совместно с универсальным анпакером .

ftp://ftp.elf.stuba.sk/pub/pc/pack/es321.zip (70 Кб)

File Analyzer 1.6.x

File Analyzer - эта программа способная определить чем скомпилирован файл, чем упакован, закодирован, иммунизирован и т.д. File Analyzer также умеет показывать внутренности архивов и SFX архивов (RAR , ARJ , ZIP, LZH, LHA , ICE, ZOO , SWG, DWC, SQZ , HYP, ACE , CAB, PAC, , Stirling), выдавать информацию о разных графических файлах (GIF /JPG /PCX/BMP/ICO/IFF) - разрешение, количество цветов и т.д. Понимает следующие EXE форматы: MZ/NE/LE/LX/LE/W3/P2/P3/DL/MP.
File Analyzer способен показывать данные таблиц объектов NE/LE/LX/PE. Имеется поддержка внешних плагинов, встроенный компилятор своей базы данных, созданный для увеличения производительности. В БД можно использовать макро-команды типа "J1J1" - это означает ассемблерную команду JMP/JMPS/CALL etc, т.е. есть возможность анализа файлов с различными переходами...

Программа занимает немного места на диске и поставляется с хорошей документацией на русском и английском языках.

Программу можно скачать с сайта http://www.world.lv/vnet (135 Кб)

FileInfo 2.49

Свежая утилита от M.Hering, предназначенная для определения паковщиков и защит (в том числе самых новых), которыми были "обработаны" исполнимые файлы. Кроме того FileInfo позволяет распознавать "работу" наиболее распространенных компиляторов и форматы SFX-EXE-архивов.

Программа может работать в режиме показа краткой информации о файлах в директории(ях) или в режиме показа детальной информации о конкретном файле. Имеются встроенный HEX/TXT вьюер (с возможностью быстрого перехода по адресам, поиска, фильтрации), редактор MZ/PE заголовков и контрольных сумм, оригинальный графический байт-анализатор.

Поддерживает длинные имена файлов под Win9x.

В последней версии добавлена и улучшена поддержка определения двух десятков форматов защит/пакеров, в том числе UPX , tELock , уменьшено потребление памяти, внесены прочие улучшения...

Программу можно скачать с сайта http://www.programmerstools.org/files/utilities/fi.zip (155 Кб)

NED 2.31 (New Executable files Deshrinker)

Утилита командной строки by Snajder, позволяющая распаковывать исполнимые NE-файлы (NE-Visual Basic 3.0), сжатые утилитой Shrinker версий 3.xx.

Программу можно скачать с сайта http://www.programmerstools.com/files/unpackers/ned.zip (8 Кб)

Petite 2.x Enlarger 1.3

Простенькая Windows-утилита by r!sc, позволяющая распаковывать EXE/DLL файлы, сжатые пакером Petite версий 2.1/2.2.

В новой версии переписан обработчик командной строки (иногда не открывались файлы под NT).

PE-UnCompact 1.5

Небольшая Windows-утилита by tNO позволяет распаковывать EXE, DLL, SCR и OCX (?) файлы, сжатые популярным пакером PECompact версий 1.24-1.30. Расжатие не производится если выбранный файл не является PECompact-сжатым. При распаковке программа восстанавливает расширение файла, таблицы импорта, секции, "выравнивает" код.

В новой версии добавлена поддержка PECompact 1.30, внесены внутренние изменения.

Программу можно скачать с сайта http://pleiku.vietmedia.com/protools/files/unpackers/tnopeunc.zip (15 Кб)

PMWUnLite 1.30

Небольшая утилита, предназначенная для распаковки исполнимых файлов защищенного режима (типа DOS/4GW), сжатых утилитой PMWLite из набора PMODE/W.

Программу можно скачать с сайта http://www.suddendischarge.com/cgi-bin/antileech.cgi?pmwun130.zip (38 Кб)

TEU 1.8x (The Executables" Unpacker)

Универсальный распаковщик запакованных или защищенных исполнимых файлов by JVP. TEU распознает программы полученные в наиболее распространенных компиляторах и позволяет распаковывать файлы, если они были защищены/сжаты неизвестными или новыми утилитами. Кроме этих средств в TEU есть несколько оригинальных, даже помеченных значком TM режимов взлома: DirectY и PassiveX. Однако из-за отсутствия документации ничего определенного сказать о них не могу.

Утилита работает из командной строки и имеет n-ное количество параметров, которые как обычно не обязательно использовать. Хочу также отметить, что TEU может работать совместно с утилитой .

Программа поставляется в двух версиях: на английском (буква "e" в конце версии) и немецком языках ("d").

Программу можно скачать с сайта http://members.xoom.com/jvp/ (40 Кб)

TRON 1.30

Еще один универсальный распаковщик запакованных или защищенных исполнимых файлов by Smilesoft Company. Знает около двух десятков программ (от ComPack до Protect EXE/COM и WWPack), не считая их множества подверсий. Утилита традиционно может попытаться распаковывать файлы, сжатые/защищенные неизвестными или новыми программами. Правда для этого надо сперва зарегистрироваться и заплатить за программу 25$... При взломе TRON может опционально работать в специальном режиме "отклонения" прерываний, а также в защищенном режиме.

Программа занимает мало места на диске и очень неплохо документирована; имеется много информации по пакерам/протекторам.

Программу можно скачать с сайта ftp://ftp.elf.stuba.sk/pub/pc/pack/tron130.zip (40 Кб)

UnASPack 1.0.9.1

Эта небольшая Windows-утилита by BiWeiGuo позволяет распаковывать EXE и DLL файлы, сжатые популярным пакером ASPack до версии 2.1. Расжатие не будет производиться если выбранный файл не является ASPack"ованным (или использованная для сжатия версия ASPack неизвестна утилите).

При распаковке всегда создается резервная копия файла.

Программу можно скачать с сайта http://pleiku.vietmedia.com/protoools/files/unpackers/2unaspack.zip (73 Кб)

UNP 4.12c

Некогда очень популярный, правда немного устаревший (1995 год) универсальный распаковщик запакованных или защищенных DOS исполнимых файлов by Ben Castricum. Умеет определять и снимать множество защит и "упаковок" (знает около 50 наименований программ, не считая их множества подверсий). Кроме того, UNP может пытаться распаковывать файлы, сжатые/защищенные неизвестными или новыми утилитами (правда, эта функция не такая мощная, как в других специализированных программах, описанных на этой странице).

UNP позволяет конвертировать некоторые EXE-файлы в COM и наоборот, вставлять файлы в заголовки программ, "выдирать" и "прикреплять" оверлеи, производить поиск сжатых файлов в директории, оптимизировать EXE"шники для последующего сжатия, "выравнивать" EXE-заголовки для более быстрого запуска, удалять в них же ненужную информацию, например комментарии линкеров и т.п.

Программа хорошо документирована, занимает традиционно немного места. Как говорится "The must have program"...

Программу можно скачать с сайта ftp://ftp.elf.stuba.sk/pub/pc/pack/unp412b.zip (без док)
Полную версию можно скачать с сайта ftp://ftp.elf.stuba.sk/pub/pc/pack/unp411.zip (40 Кб)

Un-Pack (File Analizer & Unpacker) 2.2

Новая мощная DOS-утилита by Snow Panther, предназначенная для идентификации и при возможности снятия в автоматическом режиме различных навесных защит и COM/EXE-"запаковок". Программа расшифровывает большинство из тех файлов, что не "берет" и X-TRACT. Иногда после некоторых изменений, сделанных рассматриваемой утилитой, распаковка может быть произведена с помощью , о чем Un-Pack вас уведомит.

Имеются функции установки даты/времени файлов, их усечения, анализа PE файлов (импорт./экспортируемые функции), дампер (универсальный распаковщик) для COM файлов, generic детектор неизвестных типов защит/сжатия, EXE->COM конвертор, обработчик relocations, дизассемблер entry point"a, эвристический анализатор...

Программа поддерживает длинные имена файлов (LFN), может работать совместно с анпакером . Она также опознает всевозможные "левые" форматы файлов по расширениям и использует "движки" утилит , , IDArc , что сделало возможным распознавание около 170 типов архивных файлов.

В новой версии добавлены сигнатуры для MZ, NE, PE файлов, доработана конвертация EXE->COM, обновлен движок IDArc , реализована поддержка распаковки файлов модифицированных tElock, ASProtect, ASPack , GFX2EXE, PE-Nightmare и другими; исправлено несколько багов...

Программу можно скачать с сайта http://mud.sz.jsinfo.net/per/aaron/files/file-analyzers/unpack22.zip (800 Кб)

UnPECompact 1.31

Небольшая медленноватая Windows-утилита by Yoda, позволяющая распаковывать EXE файлы, сжатые популярным пакером PECompact практически любых версий (0.9-1.43). Распаковка не производится если выбранный файл не является PECompact-сжатым. Имеется возможность управления из командной строки.

Утилита обычно не работает под Win2K.

В новой версии добавлена поддержка дополнительных версий PECompact , появилась опция принудительного полного восстановления таблицы импорта.

http://y0da.cjb.net (70 Кб)

UnPEPack 1.0

Мелкая Windows-утилита by M.o.D., позволяющая распаковывать исполнимые файлы, сжатые пакером PEPack . Не работает под Win2K.

Программу с исходниками можно скачать с сайта http://pleiku.vietmedia.com/protoools/files/unpackers/unpepack.zip (135 Кб)

UnShrinker 1.0

Данная утилита командной строки предназначена для тех людей, которым вдруг понадобилось получить файл запакованный

Мы с Волком давно заметили, что разработка PE-пакеров, крипторов и навесных защит - это почему-то удел немногих. Самого разного рода троянов в инете целая куча, а софта для упаковки и скрытия от антивирусов кот наплакал. При том, что код-то там несложный, просто сорцов в паблике, от которых можно было бы отталкиваться при разработке своего стаффа, как-то не наблюдается. Мы решили исправить эту досадную ситуацию. Сейчас мы расскажем, как написать свой несложный упаковщик исполняемых файлов и научить его паре дерзких приемов.

Давным-давном, когда Windows XP еще не было, в поисках информации о пакерах мы с Волком забирались в самые дебри исходников тогда еще молодого UPX. Но то ли ацетилхолина у нас в мозгах синтезировалось меньше нужного, то ли UPX уже тогда был очень занудным - в общем, мы почти ничего из тех сорцов не извлекли. Мэтт Питрек, и тот помог больше. Сейчас с инфой значительно проще стало. Почти всё есть. Даже сорцы вполне себе нормального банковского троя можно скачать (Zeus 2.0.8.9, bit.ly/v3EiYP). Да чего уж там, сорцы винды уже давно в паблике (Windows 2000, bit.ly/rBZlCy).

Об упаковщиках информация тоже есть, но в основном исследовательская, непосредственно разработки касающаяся не с той стороны, с которой нам бы хотелось. Отличным примером тому является статья »Об упаковщиках в последний раз» (bit.ly/vRPCxZ , bit.ly/tSUxT7) в двух частях, написанная небезызвестными гуру Volodya и NEOx.

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

Алгоритм

Вот есть у нас, например, notepad.exe. В обычном своем 32-битном виде он весит где-нибудь 60 Кб. Мы хотим его существенно уменьшить, сохранив при этом всю его функциональность. Какими должны быть наши действия? Ну, для начала мы наш файлик от первого до последнего байтика прочтем в массив. Теперь мы можем делать с ним всё что угодно. А нам угодно его сжать. Берем его и отдаем какому-нибудь простому компрессору, в результате чего получаем массив уже не в 60 Кб, а, например, в 20 Кб. Это круто, но в сжатом виде образ нашего «Блокнота» - это просто набор байтов с высокой энтропией, это не экзешник, и его нельзя запустить, записав в файл и кликнув. Для массива со сжатым образом нам нужен носитель (загрузчик), очень маленький исполняемый файл, к которому мы прицепим наш массив и который его разожмет и запустит. Пишем носитель, компилируем, а затем дописываем к нему в конец наш сжатый «Блокнот». Соответственно, если полученный в результате всех действий файл (размер которого немного больше, чем у просто сжатого «Блокнота») запустить, он найдет в себе упакованный образ, распакует, распарсит его структуру и запустит.

Как видишь, нам предстоит автоматизировать не слишком сложный процесс. Нужно будет просто написать две программы, загрузчик и, собственно, упаковщик.

Алгоритм работы упаковщика:

  • считать PE-файл в массив;
  • сжать массив каким-нибудь алгоритмом сжатия без потерь;
  • в соответствии с форматом PE дописать сжатый массив к шаблону-загрузчику.

Алгоритм работы загрузчика:

  • найти в конце себя массив со сжатым PE-файлом;
  • разжать его;
  • распарсить заголовки PE-файла, расставить все права, выделить память и в итоге запустить.

Начнем разработку с загрузчика, так как именно им впоследствии будет манипулировать упаковщик.

Загрузчик

Итак, первое, что должен сделать наш загрузчик, - это найти в своем теле адрес массива со сжатым образом PE-файла. Способы поиска зависят от того, как упаковщик имплантировал этот массив в загрузчик.

Поиск сжатого образа в последней секции // Получаем адрес начала PE-заголовка загрузчика в памяти HMODULE hModule = GetModuleHandle(NULL); PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule; PIMAGE_NT_HEADERS pNTHeaders = MakePtr(PIMAGE_NT_HEADERS,hModule,pDosHeader->e_lfanew); PIMAGE_SECTION_HEADER pSections = IMAGE_FIRST_SECTION(pNTHeaders); // Структура, описывающая последнюю секцию нашего загрузчика PIMAGE_SECTION_HEADER pLastSection = &pSections; // Собственно, найденный образ LPBYTE pbPackedImage = MakePtr(LPBYTE, hModule, pLastSection->VirtualAddress); // Его размер DWORD dwPackedImageSize = pLastSection->SizeOfRawData;

Но, на наш с Волком взгляд, этим кодом в загрузчике можно пожертвовать. Вообще, всё, что может сделать упаковщик, пусть он и только он и делает. Адрес образа в адресном пространстве загрузчика можно вычислить заранее, при упаковке, а потом просто вписать в нужное место. Для этого оставляем в нашей программе две метки:

LPBYTE pbPackedImage = (LPBYTE) 0xDEADBEEF; DWORD dwPackedImageSize = 0xBEEFCACE;

Когда упаковщик будет имплантировать в загрузчик массив со сжатым образом, он пройдется сигнатурным поиском по телу загрузчика и заменит 0xDEADBEEF на адрес массива, а 0xBEEFCACE - на его размер.

Теперь, когда мы определились, как искать адрес, можно выбрать готовую реализацию алгоритма сжатия для использования в нашем упаковщике.

Неплохой вариант - использовать aplib , маленькую библиотеку с аккуратным и очень компактным кодом, реализующую сжатие на базе алгоритма Лемпеля-Зива (LZ). И мы обязательно его выбрали бы в любой другой день, однако сегодня у нас настроение для еще более простого и компактного решения - встроенных в Windows функций!

Начиная с XP, наша любимая ntdll.dll начала экспортировать две прекрасные функции:

NTSTATUS RtlCompressBuffer(__in USHORT CompressionFormatAndEngine, __in PUCHAR UncompressedBuffer, __in ULONG UncompressedBufferSize, __out PUCHAR CompressedBuffer, __in ULONG CompressedBufferSize, __in ULONG UncompressedChunkSize, __out PULONG FinalCompressedSize, __in PVOID WorkSpace); NTSTATUS RtlDecompressBuffer(__in USHORT CompressionFormat, __out PUCHAR UncompressedBuffer, __in ULONG UncompressedBufferSize, __in PUCHAR CompressedBuffer, __in ULONG CompressedBufferSize, __out PULONG FinalUncompressedSize);

Названия их говорят сами за себя - одна функция для компрессии, другая для декомпрессии. Конечно, если бы мы разрабатывали действительно серьезный продукт, мы бы эти функции не трогали, ведь остались еще компьютеры и с Windows 2000, и даже с NT 4.0, 😉 но для наших скромных целей RtlCompressBuffer\RtlDecompressBuffer вполне подойдут.

В хедерах Platform SDK этих функций нет, статически мы их прилинковать не сможем, поэтому придется воспользоваться GetProcAddress:

Определение адреса функции для распаковки // Описываем переменную RtlDecompressBuffer типа функция с шестью параметрами DWORD (__stdcall *RtlDecompressBuffer)(ULONG,PVOID,ULONG,PVOID,ULONG,PULONG); // Присваиваем ей адрес RtlDecompressBuffer в ntdll.dll (FARPROC&)RtlDecompressBuffer = GetProcAddress(LoadLibrary("ntdll.dll"), "RtlDecompressBuffer");

Когда есть чем распаковать и есть что распаковать, можно уже, наконец, это сделать. Для этого мы выделим память с запасом (так как не знаем объем распакованного файла) и запустим определенную выше функцию:

DWORD dwImageSize = 0; DWORD dwImageTempSize = dwPackedImageSize * 15; // Выделяю память под распакованный образ LPVOID pbImage = VirtualAlloc(NULL, dwImageTempSize, MEM_COMMIT, PAGE_READWRITE); // Распаковываю RtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, pbImage, dwImageTempSize, pbPackedImage, dwPackedImageSize, &dwImageSize);

Параметр COMPRESSION_FORMAT_LZNT1 означает, что мы хотим использовать классическое LZ-сжатие. Функция умеет сжимать и другими алгоритмами , но нам хватит и этого.

Теперь у нас в памяти (pbImage) есть сырой образ PE-файла. Чтобы его запустить, нужно провести ряд манипуляций, которые обычно делает нативный PE-загрузчик Windows. Мы сократим список до самых-самых необходимых:

  1. Разместить начало образа (хедеры) по адресу, указанному в поле Image Base опционального заголовка (OPTIONAL_HEADER).
  2. Разместить секции PE-файла по адресам, указанным в таблице секций.
  3. Распарсить таблицу импорта, найти все адреса функций и вписать в соответствующие им ячейки.

Естественно, стандартный PE-загрузчик выполняет целую кучу других действий, и тем, что мы их отметаем, мы ограничиваем совместимость нашего упаковщика с некоторыми PE-файлами. Но для абсолютного большинства хватит и этих действий - можно не фиксить релоки, фиксапы и прочую редкую и противную фигню.

Если вдруг тебе захочется серьезной совместимости, ты или сам напишешь крутой PE-лоадер, или найдешь в Сети наиболее полную его реализацию - нам с Волком было лень писать свою, и мы воспользовались трудами gr8 из hellknights выкинув из нее всё, что не поняли. 😉 Даже в урезанном виде функция PE-лоадера - это строчек сто, не меньше, поэтому здесь мы приведем только ее прототип (полный код лежит на диске):

HMODULE LoadExecutable (LPBYTE image, DWORD* AddressOfEntryPoint)

Она принимает указатель на наш распакованный образ и возвращает дескриптор загруженного модуля (эквивалент адреса, по которому загружен PE-файл) и адрес точки входа (по указателю AddressOfEntryPoint). Эта функция делает всё, чтобы правильно разместить образ в памяти, но не всё, чтобы можно было, наконец, передать туда управление.

INFO

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

Дело в том, что система по-прежнему ничего не знает о загруженном нами модуле. Если мы прямо сейчас вызовем точку входа, с которой сжатая программа начнет выполнение, то может возникнуть ряд проблем. Работать программа будет, но криво.

Например, GetModuleHandle(NULL) будет возвращать Image Base модуля загрузчика, а не распакованной программы. Функции FindResource и LoadResource будут рыться в нашем загрузчике, в котором никаких ресурсов нет и в помине. Могут быть и более специфические глюки. Чтобы всего этого не происходило, нужно в системных структурах процесса по возможности везде обновить информацию, заменив адреса модуля загрузчика на адреса загруженного модуля.

В первую очередь нужно пофиксить PEB (Process Enviroment Block), в котором указан старый Image Base. Адрес PEB очень легко получить, в юзермоде он всегда лежит по смещению 0x30 в сегменте FS.

PPEB Peb; __asm { push eax mov eax, FS:; mov Peb, eax pop eax } // hModule - адрес распакованного и загруженного нами PE-файла Peb->ImageBaseAddress = hModule;

Также не помешает пофиксить списки модулей в структуре LDR_DATA, на которую ссылается PEB. Всего там три списка:

  • InLoadOrderModuleList - cписок модулей в порядке загрузки;
  • InMemoryOrderModuleList - cписок модулей в порядке расположения в памяти;
  • InInitializationOrderModuleList - cписок модулей в порядке инициализации.

Нам надо найти в каждом списке адрес нашего загрузчика и заменить его на адрес загруженного модуля. Как-нибудь так:

// Первым загружается наш модуль, так что // по всему списку проходить не обязательно PLDR_DATA_TABLE_ENTRY pLdrEntry = (PLDR_DATA_TABLE_ENTRY) (Peb->Ldr->ModuleListLoadOrder.Flink); pLdrEntry->DllBase = hModule; ...

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

LPVOID entry = (LPVOID)((DWORD)hModule + AddressOfEntryPoint); __asm call entry;

AddressOfEntryPoint - это относительный виртуальный адрес (RVA, Relative Virtual Address) точки входа, взятый из optional header в функции LoadExecutable. Для получения абсолютного адреса мы просто прибавили к RVA адрес базы (то есть свежезагруженного модуля).

Уменьшение размера загрузчика

Если наш загрузчик скомпилировать и собрать в VS 2010 с флагами по умолчанию, то мы получим не двухкилобайтную программку-носитель, а монстра размером более 10 Кб. Студия встроит туда целую кучу лишнего, а нам надо всё это оттуда выгрести.

Поэтому в свойствах компиляции проекта загрузчика (вкладка С/C++) мы делаем следующее:

  • В разделе «Оптимизация» выбираем «Минимальный размер (/O1)», чтобы компилятор старался сделать все функции более компактными.
  • Там же указываем приоритет размера над скоростью (флаг /Os).
  • В разделе «Создание кода» выключаем исключения С++, мы их не используем.
  • Проверка переполнения буфера нам тоже не нужна (/GS-). Это штука хорошая, но не в нашем случае.

В свойствах линкера (компоновщика):

  • Отключаем к чертям «Манифест». Он большой, и из-за него в загрузчике создается секция.rsrc, которая нам совершенно не нужна. Вообще, каждая лишняя секция в PE-файле - это минимум 512 совершенно ненужных байт, спасибо выравниванию.
  • Отключаем создание отладочной информации.
  • Лезем во вкладку «Дополнительно». Выключаем «Внесение случайности в базовый адрес» (/DYNAMICBASE:NO), иначе линкер создаст секцию релоков (.reloc).
  • Указываем базовый адрес. Выберем какой-нибудь нестандартный повыше, например 0x02000000. Именно это значение будет возвращать GetModuleHandle(NULL) в загрузчике. Можно его даже захардкодить.
  • Указываем нашу точку входа, а не CRT-шную: /ENTRY:WinMain. Вообще, мы привыкли это делать директивой pragma прямо из кода, но раз уж залезли в свойства, то можно и тут.

Остальные настройки для линкера задаем непосредственно из кода:

#pragma comment(linker,"/MERGE:.rdata=.text")

Здесь мы объединили секцию.rdata, в которой содержатся данные, доступные только для чтения (строки, таблица импорта и т. п.), с секцией кода.text. Если бы мы использовали глобальные переменные, то нам также надо было бы объединить с кодом секцию.data.

#pragma comment(linker,"/MERGE:.data=.text") // К данным из.data нужен доступ на запись, // а не только на чтение и выполнение #pragma comment(linker,"/SECTION:.text,EWR")

Всего перечисленного хватит, чтобы получить лоадер размером в 1,5 Кб.

Переделываем в криптор

Собственно, от криптора наш пакет отличает совсем немногое: отсутствие функции шифрования и противоэмуляционных приемов. Самое простое, что можно с ходу сделать, - это добавить xor всего образа сразу после распаковки в загрузчике. Но, чтобы эмуляторы антивирусов подавились, этого недостаточно. Нужно как-то усложнить задачу. Например, не прописывать ключ xor’а в теле загрузчика. То есть загрузчик не будет знать, каким ключом ему надо расшифровывать код, он будет его перебирать в определенных нами рамках. Это может занять какое-то время, которое есть у пользователя, в отличие от антивируса.

Также ключ можно сделать зависимым от какой-нибудь неэмулируемой функции или структуры. Только их еще найти надо.

Чтобы код загрузчика не палился сигнатурно, можно прикрутить к упаковщику какие-нибудь продвинутые вирусные движки для генерации мусора и всяческого видоизменения кода, благо их в Сети навалом.

Упаковщик

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

HANDLE hFile = CreateFile(argv, GENERIC_READ,FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); DWORD dwImageSize = GetFileSize(hFile, 0); LPBYTE lpImage = new BYTE, lpCompressedImage = new BYTE; DWORD dwReaded; ReadFile(hFile, lpImage, dwImageSize, &dwReaded, 0); CloseHandle(hFile);

Далее наш пакер должен сжать полученный файл. Мы не будем проверять, действительно ли это PE-файл, корректные ли у него заголовки и т. п., - всё оставляем на совести пользователя, сразу сжимаем. Для этого воспользуемся функциями RtlCompressBuffer и RtlGetCompressionWorkSpaceSize. Первую мы уже описали выше - она сжимает буфер, вторая же нужна, чтобы вычислить объем памяти, необходимой для работы сжимающего движка. Будем считать, что обе функции мы уже динамически подключили (как и в загрузчике), остается только их запустить:

DWORD format = COMPRESSION_FORMAT_LZNT1|COMPRESSION_ENGINE_STANDARD; DWORD dwCompressedSize, dwBufferWsSize, dwFragmentWsSize; RtlGetCompressionWorkSpaceSize(format, &dwBufferWsSize, &dwFragmentWsSize); LPBYTE workspace = new BYTE ; RtlCompressBuffer(format , // тип сжатия и движок lpImage, // массив для сжатия dwImageSize, // его размер lpCompressedImage, // буфер для результата dwImageSize, // его размер 4096, // размер кусков, не важен &dwCompressedSize, // указатель на дворд для размера результата workspace); // буфер для работы

В результате у нас есть сжатый буфер и его размер, можно прикрутить их к загрузчику. Чтобы это сделать, нужно для начала скомпилированный код нашего загрузчика встроить в упаковщик. Самый удобный способ засунуть его в программу - это воспользоваться утилитой bin2h . Она конвертнет любой бинарник в удобный сишный хедер, все данные в нем будут выглядеть как-то так:

Unsigned int loader_size=1536; unsigned char loader = { 0x4d,0x5a,0x00,0x00,0x01,0x00,0x00, ...

Скармливаем ей файл с нашим лоадером и получаем всё необходимое для дальнейших извращений. Теперь, если придерживаться алгоритма, описанного в начале статьи, мы должны прицепить к загрузчику сжатый образ. Здесь нам с Волком придется вспомнить 90-е и свое вирмейкерское прошлое. Дело в том, что внедрение данных или кода в сторонний PE-файл - это чисто вирусная тема. Организуется внедрение большим количеством разных способов, но наиболее тривиальные и популярные - это расширение последней секции или добавление своей собственной. Добавление, на наш взгляд, чревато потерями при выравнивании, поэтому, чтобы встроить сжатый образ в наш загрузчик, мы расширим ему (загрузчику) последнюю секцию. Вернее, единственную секцию - мы же избавились от всего лишнего. 😉


Алгоритм действий будет такой:

  • Находим единственную секцию (.text) в загрузчике.
  • Изменяем ее физический размер, то есть размер на диске (SizeOfRawData). Он должен быть равен сумме старого размера и размера сжатого образа и при этом выравнен в соответствии с файловым выравниванием (FileAlignment).
  • Изменяем виртуальный размер памяти (Misc.VirtualSize), прибавляя к нему размер сжатого образа.
  • Изменяем размер всего образа загрузчика (OptionalHeader.SizeOfImage) по древней формуле [виртуальный размер последней секции] + [виртуальный адрес последней секции], не забывая выравнивать значение по FileAlignment.
  • Копируем сжатый образ в конец секции.

Тут есть небольшая хитрость. Дело в том, что наша студия делает виртуальный размер (Misc.VirtualSize) секции с кодом (.text) равным реальному невыравненному размеру кода, то есть указывает размер меньше физического. А значит, есть шанс сэкономить до 511 байт.

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


Вот как все наши мысли будут выглядеть в коде:

Расширение секции кода // Создаем копию образа нашего загрузчика с запасом по памяти PBYTE pbLoaderCopy = new BYTE; memcpy(pbLoaderCopy, (LPBYTE)&simple_packer, simple_packer_size); // Определяем его заголовки PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pbLoaderCopy; PIMAGE_NT_HEADERS nt = MakePtr(PIMAGE_NT_HEADERS, pbLoaderCopy, dos->e_lfanew); // Расширяемая секция PIMAGE_SECTION_HEADER text = IMAGE_FIRST_SECTION(nt); // Копируем сжатый образ в конец секции поверх нулей memcpy(&pbLoaderCopy, lpCompressedImage, dwCompressedSize); // Фиксим физический размер, учитывая фишку с Misc.VirtualSize text->SizeOfRawData = ALIGN(text->Misc.VirtualSize + dwCompressedSize, nt->OptionalHeader.FileAlignment); // Фиксим виртуальный (а на самом деле реальный) размер text->Misc.VirtualSize += dwCompressedSize; // Фиксим размер образа nt->OptionalHeader.SizeOfImage = ALIGN(test->Misc.VirtualSize + test->VirtualAddress, nt->OptionalHeader.FileAlignment); // Вычисляем размер получившегося файла DWORD dwNewFileSize = pSections->SizeOfRawData + test->PointerToRawData;

О, мы едва не забыли заменить метки 0xDEADBEEF и 0xBEEFCACE, оставленные в загрузчике, на реальные значения! 0xBEEFCACE у нас меняется на размер сжатого образа, а 0xDEADBEEF - на его абсолютный адрес. Адрес образа вычисляется по формуле [адрес образа] + [виртуальный адрес секции] + [смещение образа относительно начала секции]. Следует отметить, что замену надо производить еще до обновления значения Misc.VirtualSize, иначе полученный в результате файл не заработает.

Ищем и заменяем метки с помощью очень простого цикла:

For (int i = 0; i < simple_packer_size; i++) if (*(DWORD*)(&pbLoaderCopy[i]) == 0xBEEFCACE) *(DWORD*)(&pbLoaderCopy[i]) = dwCompressedSize; else if (*(DWORD*)(&pbLoaderCopy[i]) == 0xDEADBEEF) *(DWORD*)(&pbLoaderCopy[i]) = nt->OptionalHeader.ImageBase + text->VirtualAddress + text->Misc.VirtualSize;

Вот, собственно, и всё. Теперь в памяти у нас есть упакованный и готовый к работе файл, достаточно сохранить его на диске с помощью функций CreateFile/WriteFile.


Выводы

Если сравнивать эффективность сжатия нашего упаковщика с UPX на примере notepad.exe - мы выигрываем примерно 1 Кб: 46 592 байта у нас против 48 128 у UPX. Однако наш пакер далеко не совершенен. И это очень заметно. Дело в том, что мы сознательно проигнорировали такую важную вещь, как перенос ресурсов. Полученный в результате сжатия файл потеряет иконку! Реализовать недостающую функцию предстоит тебе самому. Благодаря полученным из этого материала знаниям, никаких сложностей у тебя с этим делом не возникнет.



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows