Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
выпущен 12 ноября!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

Распаковываем самое простое

Обсудить статью на форуме

Хорошая подборка видеоуроков, инструментов крэкера, книг и статей - здесь.

Автор: AndreyMust19 <AndreyMust19@mail.ru>


Present: в статье имеются рисунки - в виде ссылок на ресурсы (выделены жирным цветом). Поэтому сначала скачайте все рисунки, а потом сохраняйте статью на диск.

Навесные PE-упаковщики.


1. Об упаковщиках
Программу можно упаковать и внедрить в нее код, который перед передачей управления ее распакует в памяти. Некоторые из упаковщиков содержит антиотладочные приемы, но почти ни один из них не контролирует целостность виртуального образа. Когда мы узнали, что программа чем-то обработана, то нужно точно узнать - упакована она или только закодирована.
Любой упаковщик (за исключением UPX) упаковывает секции по отдельности. То есть порядок расположения секций в файле и их виртуальный размер не изменяется.
Стоит обратить внимание на то, как упаковщики обращаются с директорией ресурсов (Resourse). Секцию ресурсов они тоже упаковывают (так как ее содержимое хорошо поддается сжатию), но не всю. Упаковке подвергается только:
- текстовые строки (String)
- изображения, курсоры (кроме главной иконки).
Другую информацию изменять нельзя, так как она нужна операционной системе даже когда программа не работает. В эту категорию относится: главная иконка приложения (которая отображается на файле и ярлыке), информация о продукте и компании и прочая информация, которую вы можете увидеть на вкладке "Версия" в Свойствах файла.
Как видите, в секции ресурсов мало чего можно эффективно упаковать, поэтому многие упаковщики предоставляют выбор - сжимать ресурсы или их не трогать.

2. "Правила" поведения
- Саму структуру ресурсов и имя секции .rsrc изменять нельзя. А вот содержимое ресурсов - можно.
- После распаковки модуль должен вернуться в начальное состояние (то есть вирт. образ).

3. Импорт
Пакеры почти полностью упаковывают таблицу импорта, оставляя на каждую библиотеку нетронутой только одну функцию. Почему же они оставляют объедки? Возможно, считают что имена модулей упаковывать невыгодно. Почему? Ну, в основном программа импортирует 4-5 библиотек, а 4-5 строк - очень мало для упаковки. К тому же, код для их распаковки нельзя будет применить к другим частям образа, так как массив структур IMAGE_IMPORT_DESCRIPTOR встречается только в одном месте - в таблице импорта. А оставлять модуль без единой импортируемой функции... - антивирусы засмеют!
Преследуя цели по тотальной упаковке импорта, пакеру все же не обойтись без нескольких API-функций. Но обходятся они -минимальным количеством. Все пакеры добавляют 2 функции: LoadLibrary и GetProcAddress. После распаковки импорта, пакер этими функциями загружает модули в память и определяет адреса импортируемых функций. То есть делает то, что в обычном случае делает операционная система. Эти 2 наиважнейшие функции можно найти самостоятельно, отыскав kernel32 и вручную разбирая его экспорт, но... полученный код займет больше места, чем 2 строки в импорте.
Иногда пакеры добавляют другие функции:
GetModuleHandle
Для определения адреса загрузки уже загруженного модуля. Одни считают, что лучше потерять 10-15 байт на определение адреса этой функции с помощью GetProcAddress, а другие - что выгоднее просто добавить эту функцию в импорт.
ExitProcess
Чтобы завершить работу в случае ошибки. Тоже самое можно сделать, если выполнить return из WinMain.
VirtualAlloc, VirtualFree
В основном пакеры выделяют необходимую память, увеличивая виртуальный размер секции до нужного значения или создавая новую. А некоторые выделяют память вручную с помощью этих функций.

4. Общепринятые ошибки, ставшие стандартом
* Не сбрасывают права доступа секций на начальные
Как известно, для распаковки секций у них должны быть права на запись. После распаковки ни один из упаковщиков не убирает это право у страниц тех секций, которые изначально были только для чтения. Объясняется это тем, что код фиксатора будет занимать дополнительное место в программе. Из-за этого (а именно - из-за секции кода с правом на запись) возникают следующие побочные эффекты:
1) Если в программе произойдет ошибка, в результате которой происходит запись, н-р, в секцию '.text', то нарушения доступа не произойдет, так как у секции '.text' есть доступ на запись. Это продлит мучения программы от ошибки до тех пор, пока не произойдет исключение другого типа.
2) Также это сильно уменьшает защищенность программы от эксплуатации уязвимостей. Ведь теперь можно не затирать адрес возврата, а писать в секцию кода! Поэтому сетевые приложения (браузеры, web-серверы, брендмауэры) ради защищенности никогда не обрабатываются упаковщиками.
3) Наконец, разрешает запись в секцию кода любым процессом с теми же привилегиями. Это делает возможным InLine патчинг - взлом приложения путем записи изменений в код уже запущенного приложения. Без права на запись пришлось бы внедрять свой код, который бы с помощью ф-ии VirtualProtect установил секции кода право на запись.
* Не убирают за собой мусор после распаковки (не удаляют память, в к-й хранились упакованные данные)
* Сохраняют имя .rsrc и крайне бережно относятся к ресурсам
* Сохраняют регистры (что необязательно, так как это не DOS)

5. Признаки обработки навесным упаковщиком
* Виртуальный размер секций намного больше физического размера секций
Конкретно - виртуальный размер секции больше (физического размера + вирт. выравнивание секции). Это самый надежный признак упакованной программы.
Если выполняется это условие, значит свободное место в конце виртуальной секции используется не только для выравнивания, но и самой программой. В неупакованных программах это условие выполняется только для секции, в которой будут храниться неинициализированные данные. В упакованной программе это условие выполняется почти для всех секций (и для первой секции тоже!), так как им нужно дополнительное место для распаковки. Секцию ресурсов лучше не проверять - если она содержит только необходимую информацию, то она не будет упакована.
Этот признак достаточно надежен и пригоден к применению. Единственное что - он может давать осечку на маленьких файлах, так как в них 1000h свободных байт (от выравнивания) может вполне хватить для распаковки. Впрочем, если файл плохо упаковывается, упаковщик может отказаться это делать.
* Архивирование файла
Есть еще другой способ определить упакованный файл. Обработайте его файловым архиватором. Если размер файла почти не изменился, значит программа плохо упаковывается. Использовать лучше RAR, а не ZIP, так как в RAR-архиве меньше избыточной информации и архиве весит меньше.
Этот признак говорит только о том, что программа плохо упаковывается и не дает гарантии, что файл обработан упаковщиком. Например, программа может оказаться установочным архивом или же ее размер настолько мал, что упаковывать почти нечего.
К тому же этот способ не годится для программного использования, поскольку вашей программе тогда придется тащить за собой файловый архиватор и упаковывать каждую программу в файл, а не в память. Поэтому этот способ лучше подходит для ручного применения, а в программах приходится считать энтропию, тоже обладающую недостатками.

6. Методы распаковки
Если в пакере отсутствует антитрассировка, значит мы можем трассировку можно использовать для распаковки (к ним относится трассировка с условием и бряки на чтение / запись).
В распаковке наша цель - остановится в настоящей точке входа (OEP - Original Entry Point). Если она нам неизвестна, мы можем:
1) Трассировать код, пока управление не попадет в первую секцию программы. Для этого делаем трассировку с условием.
2) Если в начале распаковщика стоит pushad / pushfd, значит, скорее всего, перед передачей управления будут выполнены обратные команды - popad / popfd. Делаем условную трассировку с остановкой на эти команды. Остановка произойдет на выходе из распаковщика.
Если мы знаем адрес OEP, то можно:
1) Поставить туда аппаратный бряк на исполнение. Когда на OEP передадут управление, выполнение остановится.
2) Либо поставить бряк на запись - выполнение остановится во время распаковки. Далее можно поставить точку останова и, если пакер больше туда ничего не пишет, остановка сработает.

7. Определение упаковщика
Все отличительные признаки можно разделить на 3 группы:
1) текстовые строки и именам секций
2) точка входа
3) структура PE-файла
Нумерация секций проводится не по их расположению в таблице секций, а по их расположению в памяти процесса!



Наиболее распространенные упаковщики

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




UPX


- Что это такое?
UPX - очень распространенный упаковщик, который легко найти в сети. Переводится как "Ultimate Packer for eXecutables", то есть "Крайний Упаковщик для Исполнительных файлов". Крайне переносим, то есть способен обрабатывать программы практически любых форматов - MZ, PE, ELF.
В Windows им упаковано больше программ, чем всеми остальными упаковщиками вместе взятыми. Он никак не противостоит отладке и другим хакерским приёмам, так как не нацеливался на них. К тому же упаковывающая программа содержит распаковщик, позволяющий распаковать только что упакованную программу.

- Как выглядит?
Давайте внимательно поглядим на обработанный UPX'ом файл - он очень интересный!
Обычно навесные упаковщики не передвигают исходные секции программы и упаковывают их по отдельности.
Но файл, обработанный UPX'ом устроен по-другому! Он упаковывает все секции в одну, тем самым мы не теряем место на файловое выравнивание секций. В программе обязательно есть хотя бы 2 секции - первая содержит упакованный образ, а вторая - распаковщик (причем обе имеют атрибуты Execute):

UPX1		rva 00001000, vsize 001B70000, offset 00000400, psize 00000000
UPX2		rva 001B8000, vsize 000AA0000, offset 00000400, psize 000A9C00

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

Рис 1. UPX
http://img413.imageshack.us/img413/7296/upx.jpg

Точка входа указывает на вторую секцию, стало быть - там распаковщик. Он распаковывает оригинальный образ секцию UPX0 и передает туда управление. Тем самым, UPX очень похож на ДОСовские упаковщики - там тоже распаковщик был в конце образа и распаковывал оригинал в начало. И не мудрено, если вспомнить что UPX способен обрабатывать MZ-файлы. А вообще - это единственный вариант, переносимый на исполняемые файлы любого формата.

- Точка входа
Заглянем в ентрипоинт. В начале стоит pushad, значит перед переходом на OEP будет команда popad.
Просмотрев пару десятков упакованных файлов, мы поймем, что в точке входа обязательно должны присутствовать следующие команды (назовем их обязательными):

pushad / nop
mov esi, Значение1
lea edi, [esi][Значение2]
push edi
or ebp,-001
jmps КороткийОтносительныйАдрес

Тем самым сигнатура точки входа выглядит так:
60 * BE ?? ?? ?? ?? * 8D BE ?? ?? ?? ?? * 57 * 83 CD ?? * EB ??
Между указанными командами могут стоять любые другие команды, но не очень много. Каждую обязательную команду может разделять максимум 1-2 других команды. После команды перехода (EB ??) может стоять некоторое количество nop'ов (обычно от 3-х до 6-ти). Иногда можно встретить nop вместо первой команды pushad (90h вместо 60h). Это нарушает баланс стека - на стек заносится меньше данных, чем снимается оттуда. Но в большинстве случаев это не нарушает работу команды. Зато с nop'ом в точке входа большинство антивирусов (например самый знаменитый Kaspersky AVP), перестают считать файл упакованным UPX'ом. А все потому что они закладываются на сигнатуру точки входа, а не на особенности структуры PE-файла.
Как мы уже выяснили - обязательно встретится 2-4 команды nop. Это - ничего хорошего - стоит их заменить на незначащие команды (н-р убавить на 1, а потом прибавить на 1), как большинство антивирусов посчитают файл неупакованным.

- Автоматическая распаковка
Мы уже сказали, что упакованный файл можно распаковать самой упаковывающей программой. Так вот, в файле перед началом первой секции находится оверлей в ~25h. В нем находится особая структура, в начале которой записано версия упаковщика и сигнатура 'UPX!'. Это - особый заголовок, который нужен распаковщику. В нем записана инфа как в обычном архиве - размер файла до и после упаковки, размер вирт. образа до и после упаковки, методы упаковки и др. При наличии этой структуры вы сможете распаковать файл, дав команду:
upx.exe -d file
где 'upx.exe' - упаковочная программа, а 'file' - сам файл.
Искажение этой структуры лишает нас такой возможности:
1) Изменение версии
Если версия, записанная в начале структуры больше, чем версия упаковочная программы, то она откажется распаковывать. Так, например, заменяем 1.23 на 2.00 и упаковочная программа версии 1.93 откажется работать.
Качаем из интернета последнюю версию упаковочной программы. Если последняя версия меньше записанной в программе, значит ее точно изменили в верхнюю сторону. Меняем ее на последнюю версию и распаковываем.
2) Удаление сигнатуры
Зачем менять версию, если можно удалить 'UPX!'? Если этого слова не окажется, значит его умышленно стерли. Напишите его после версии.
3) Искажение или удаление структуры
Зачем удалять сигнатуры, если можно изменить любую часть структуры. Исказив структуру, упаковочная программа неправильно распакует и файл не будет работать. Чаще всего удаляется весь заголовок, лишая нас всей информации, нужной для автоматической распаковки.

- Восстановление заголовка UPX
Итак, заголовок удалили. Но его можно восстановить! Проанализируя программу, зная формат и подбирая некоторые поля, возможно, мы сможем так восстановить заголовок, что программу можно будет распаковать. Формат заголовка можно найти в исходниках или в статьях на некоторых сайтах.

- Ручная распаковка
Распаковщик от начала и до конца занимается распаковкой. Поэтому в конце секции UPX2 будет переход на OEP:

popad
jmp	OEP
00 00 00 00 00 ... 00h

popad снимает регистры со стека, занесенные командой pushad в точке входа, а jmp осуществляет переход.
в секций UPX1 заканчивается командами.
Ставим бряк в отладчике на jmp'е делаем шаг и мы в оригинальной точке входа. Сбрасываем дамп. Проще пареной морковки!

- Признаки упаковщика
Итак, давайте обсудим все признаки программы, упакованной UPX. Сначала посмотрим на текстовые признаки:
1) Первые две секции называются UPX0 и UPX1
2) Перед началом первой секции записана версия упаковки в виде текста и сигнатура 'UPX!'.
Как я уже говорил - текстовые признаки ненадежны. Названия секций можно изменить, а заголовок распаковки - удалить, не повлияв на работу программы.
Тогда давайте распознавать по точке входа:
1) Чаще всего начинается с pushad, иногда с nop;
2) Далее встречаются команды

or ebp,-001

и короткий прыжок (jmps или jmp).
3) Может быть разбавлена несколькими nop'ами.
Точку входа можно исказить скрэмблерами или зашифровать, внедрив небольшой код. Теперь рассмотрим особенности структуры PE-файла:
1) Программа содержит максимум 3 секций (+.rsrc). В большинстве программ - >= 4 секций
2) Первая секция обычно заметно больше второй (по виртуальному размеру).
3) Обе секции имеют одинаковое физическое смещение в файле (offset).
4) Первая секция имеет нулевой физический размер.
5) Обе секции имеют атрибут "Execute"
Эти признаки - самые надежные, так как исказить структуру PE-файла можно только если обработать файл ещё чем-нибудь (криптором или протектором). 4-ый признак - не очень надежен, так как вместо нулевого физического адреса можно поставить какой-нибудь другой. В результате первая секция в памяти перестанет быть пустой и заполнится данными из файла. Но это не повлияет на алгоритм работы - первая секция используется только для распаковки, а значит, данные туда только пишутся, а не читаются. 5-ый признак - тоже не надежен, так как атрибут "Execute" можно убрать, не повлияв на работу.
Самые надежные признаки UPX'а - это 1-ый, 2-ой и 3-ий.



AsPack


- Что это такое?
Это упаковщик, стоящий на одну ступеньку выше, чем UPX. Ничем не отличается в сложности от UPX, так как антиотладочных приемов - ноль, а найти истинную точку входа AsPack-программе почти также просто. Отличает его другая модель упаковки. Код распаковшика более изменчив от версии к версии, чем у UPX, но эти изменения - только в деталях, поэтому распознавание остается тем же.
В живой природе встречается раза в 2 реже чем UPX, но несмотря на это в сети легко найти руководство по его снятию.

- Как выглядит?
Заархивировав RAR'ос упакованный AsPack'ом файл, мы увидим... что упаковщик со своей задачей справляется плохо. Причина в том, что AsPack, как и многие другие, обрабатывает каждую секцию отдельно. Из-за этого теряется много места на выравнивание, что особенно заметно на файлах с множеством маленьких секций. Зато имена секций и их виртуальные адреса остаются нетронутыми:

Рис. 2 AsPack
http://img413.imageshack.us/img413/1245/aspack.jpg

Распаковщик находится в предпоследней секции программы, которая получает имя '.aspack'. Последняя секция 'adata' - пустая (physsize = 0) и всегда занимает в памяти 1000h байт. В самых первых версиях упаковщика этой пустой секции не было, посему распаковщик оказывался последней секцией. Обе секции получают атрибуты 'read', 'write', и 'initdata'. Автору наплевать, что у распаковщика нет атрибута 'execute' или 'code', из-за чего файл откажет запускаться на архитектурах где право 'read' не считается правом исполнения. Также известны проблемы с AsPack'ом в эмуляторе Wine.

Пример 1. Delphi-программа (comersan-1.00b)

CODE		rva 00001000, vsize 00086000, offset 00000400, psize 0003EC00
DATA		rva 00087000, vsize 00002000, offset 0003F000, psize 00000C00
BSS		rva 00089000, vsize 00001000, offset 0003FC00, psize 00000000
.idata		rva 0008A000, vsize 00003000, offset 0003FC00, psize 00001000
.tls		rva 0008D000, vsize 00001000, offset 00040C00, psize 00000000
.rdata		rva 0008E000, vsize 00001000, offset 00040C00, psize 00000200
.reloc		rva 0008F000, vsize 00009000, offset 00040E00, psize 00000000
.rsrc		rva 00098000, vsize 0004F000, offset 00040E00, psize 00030400
.data		rva 000E7000, vsize 00001000, offset 00071200, psize 00000800

Пример 2. программа на Visual C (Drwebupw-2.12)

.text		rva 00001000, vsize 00029000, offset 00000600, psize 00013E00
.rdata		rva 0002A000, vsize 00008000, offset 00014400, psize 00002400
.data		rva 00032000, vsize 0000C000, offset 00016800, psize 00001600
.rsrc		rva 0003E000, vsize 00008000, offset 00017E00, psize 00002200
.drweb		rva 00046000, vsize 00003000, offset 0001A000, psize 00002400
.adata		rva 00049000, vsize 00001000, offset 0001C400, psize 00000000

Теперь рассмотрим примеры. Первый пример - одна из первых версий, поэтому распаковщик находится в посл. секции, а во втором примере секция '.aspack' переименована в '.drweb'.

!!! с NonExecute-битом

- Импорт
AsPack импортирует ф-ю GetModuleHandle, считая зазорным узнавать ее адрес самому :).

- Точка входа
Поскольку код распаковщика меняется с каждой версией, точка входа может выглядеть как угодно. Но опознать AsPack по точке входа все же можно!
Во-первых, она отстоит от начала секции на 1 байт и начинается с pushad (60h), сразу после которой расположены 2 относительные команды перехода - call (E8 или E9) и jmp (EB), причем между ними может стоять пара мусорных байт. Это нормальный случай.

pushad
call +3$
??
jmp +4$

То, что идет дальше - отличается от версии к версии. В большинстве случаев перед командой pushad стоит байт 90h, который является командой nop. Поэтому иногда точку входа переставляют на него (и тогда она уже точно совпадает с началом секции). Смещения всего на 1 байт уже достаточно, чтобы сигнатуры антивирусов не сработали. В результате антивирусы, распознающие AsPack по сигнатуре в точке входа (например, хваленый Касперский), уже не узнают, что файл упакован!
Еще можно команды nop и pushad поменять местами. Иногда можно рискнуть и затереть команду pushad командой nop (как в случае с UPX). Поскольку мы внесли изменения в тело распаковщика, это изменение может повлиять на его работу (но в большинстве случаев этого не случится). Просто запустите пропатченную программу - если заработает - все нормально, если нет - верните команду pushad на место! Максимум получится 2 команды nop, которые теперь можно заменить на любые 2 однобайтные ничего не делающие команды (например inc eax, dec eax). И программы с такими командами уже встречаются.
Кроме этого перед началом секции расположено много нулей, оставшихся от выравнивания. Их тоже можно заменить незначащими командами, правда тогда точка входа попадет в другую секцию.
Как видите, вариантов - множество. Но - самое главное понять, что файл обработан навесным упаковщиком, а распознать упаковщик можно вручную по опыту (через тот же HEX-редактор).
Сигнатуру можно описать так:
* 90/60, 90/60, E8/E9 03 00 00 00 ?? EB 04

- Ручная распаковка
Вобщем, почти не отличается от UPX. Поскольку в начале выполняется pushad, то перед передачей управления надо сделать popad:

popad
jnz addr
mov eax, 1
retn 0C
push oep
retn

Ставим бряк на popad и продолжаем. jnz addr перепрыгивает первый retn, а далее push заносит адрес возврата и retn делает по нему переход на настоящую точку входа. Сбрасываем дамп.

- Признаки упаковщика
Итак, давайте обсудим, как мы можем узнать AsPack. Сначала текстовые строки:
1) последняя секция называется '.aspack'
2) или предпоследняя секция называется '.aspack', а последняя - '.adata'
По точке входа:
1) находится в последней или предпоследней секции
2) указывает на 0-ой или 1-ый байта секции
3) начинается с pushad или nop, далее должен быть call на 3 байта вперед (EB 03), потом неск. лишних байт и jmp.
Наконец, самый надежный способ - по формату файла
1) после секции ресурсов .rsrc или, если их нет, после секции неинициализированных данных должно быть 1-2 секции, причем если их там две, то 2-ая должна иметь нулевой физический размер и вирт. размер = 1000h.
2) обе последние секции имеют аттрибуты только read, write и initdata.
Второй признак не надежен, так как без влияния можно заменить read на execute или code. Так что стоит ориентироваться по первому признаку.



PECompact версии 1.x


За время существования сменилась 1 старшая версия. Последняя версия - 2.98 и не загорами 3-я.
Между упаковщиками версий 1.x и 2.x есть заметная разница. Поэтому будем рассматривать их отдельно.

- Описание
Итак, продолжается наша эпопея с упаковщиками без антиотладочных приемов. Чем же он тогда отличается? А тем, что в нем есть:
1) одна ловушка (на которую часто попадаются новички)
2) копирование и раскодирование кода, из-за которого нахождение точки выхода в самом начале работы невозможно (как мы это делали с UPX и AsPack).
В связи с этим, перед описанием ручной распаковки мы проведем анализ упаковщика.

- Как выглядит?
Посмотрим файл, обработанный версией 1.33. Содержит 3 секции (на примере файла start.exe):

pec1	rva 00001000, vsize 0003A000, offset 00000400, psize 00015E00
pec2	rva 0003B000, vsize 00023000, offset 00016200, psize 00003800
.rsrc	rva 0005E000, vsize 00001000, offset 00019A00, psize 00000400

Очень похоже на UPX. Однако, нет - первая секция не пуста. И судя по разнице физического и виртуального размера - они обе упакованы. В первой секции находятся упакованные секции, а во второй - ресурсы и распаковщик (по точке входа).
Третья секция (.rsrc) - очень интересная. Она занимает всего 1000h и вместо ресурсов в ней - таблица импорта. Неужели автор пакера решил нас обмануть? На самом деле ресурсы лежат во второй секции (pec2) в упакованном состоянии:

import directory			rva 00196000, vsize 00000447
resource directory		rva 00192000, vsize 00000F08

Итак, перед нами - пока первый пример, когда секция ресурсов теряет свое родное имя. Причем, вопреки распространенной ситуации, ресурсы занимают не всю секцию - остальная часть занята данными упаковщика.
С аттрибутами секций PECompact ведет себя правильно, в отличие от AsPack. В первой секции после распаковки будет секция кода, поэтому у нее установлены необходимые аттрибуты Execute и Code. Во второй секции - ресурсы и данные, поэтому - Initdata. Из третьей секции распаковкщик только читает, поэтому она имеет только аттрибуты Read и Shared.
Один файл PECompact'а более старшей версии (1.68-1.84) выглядит немного по-другому (на примере файла AdobeUpdateManager.exe версии 2.0):

pec1	rva 00001000, vsize 0007A000, offset 00000400, psize 00033400
.rsrc	rva 0007B000, vsize 00039000, offset 00033800, psize 00015E00
pec	rva 000B4000, vsize 00004000, offset 00049600, psize 00000600
.rsrc	rva 000B8000, vsize 00001000, offset 00049C00, psize 00000600

Откуда здесь еще одна секция ресурсов? При рассмотрении оказывается что ничего особенного тут нет - просто секция 'pec2' разбита на 2 части. В секцию '.rsrc' попали ресурсы, а в секцию '.pec' - данные упаковщика. Плюс у 'pec' установлен аттрибут Execute. Такую хирургическую операцию легко сделать в PE-редакторе, так что, скорее всего, это не исключение из правил, я чье-то личное вмешательство.
В итоге файловый и виртуальный образ выглядят так:

Рис 3. PE-Compact 1.x
http://img413.imageshack.us/img413/7720/pecompact1x.jpg

- Импорт
PECompact дополнительно использует VirtualAlloc и VirtualFree для выделения памяти.

- Сигнатура
Во всех файлах, упакованных PECompact'ом есть знак. В PE-заголовке появляется сигнатура PECO. Она находится в неиспользуемом поле "pointer to symbol table".

- Анализ
Начинаем изучать код распаковщика и ищем точку выхода.
Сначала надо пройти через pushfd без трассировки, чтобы бит трассировки не попал в флаги:

jmps  .00406608	; Прыгаем на (1)
push 000012С0	
retn			
pushfd			; (1)
pushad
call 00406611	; Вызываем (2)

Для этого можно поставить бряк на следующую команду и продолжить выполнение. call пропускает одну команду, поэтому положенный на стек адрес, - скорее всего не для возврата, а определения адреса загрузки. Изучив код, можно сказать, что все хитрости релизованы в только в точке входа, а дальше код открыт как зонтик.
Н-р, вот таким завуалированным способом выделяется 4 байта на стеке:

mov eax, esp
add eax, 4
xchg eax, ebx
mov esp, ebx

А вот адрес, занесенный на стек call'ом:

mov ebx, [ebx-4]

Он используется для относительной адресации. Но пакер выполняет с ним не одну операцию, а две - сначала вычитает, а потом прибавляет, получается абсолютный адрес. Получив адрес точки входа, пакер завуалировано модифицирует код в точке входа следующим образом:
[00406603] += 4066A6h
[00406600] = 9090h

Выполняемые действия непросто понять через диззассемблер, но элементарно определить в отладчике.
В результате в entrypoint возникают след. изменения:

jmp 00406608h  -> nop, nop
push 000012C0h -> push 004012C0h
retn           == retn


Адрес 004012C0h очень близко к базе загрузки, ничего другого, кроме OEP здесь быть не может! Ура! Мы узнали настоящую точку входа! Но ставить бряки на этот код бессмыслено. Чтобы мы не ставили - бряки на выполнение, чтение, запись, условная трассировка - ничего не срабатывает. Почему, блин!? Потому что это ловушка, на которую пакер никогда не передаст управления! Он даже постарался привлечь наше внимание изменением кода.

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

pop edi
lea esi, [ebp+408070]		//esi = 00406670h
mov ecx, 39Dh

Прямо за циклом есть код по адресу 00406670h размером 39Dh. Сначала пакер модифицирует число в первой команде этого кода:

mov [ebp+408071h], eax

В результате 'mov ebp, 0h -> mov ebp, -18Eh'. А потом цикл копирует этот код вперед на адрес 00407EE2h и передает туда управление retn'ом. Скопированный код выполняет системный вызов:
Enter 0,0
eax = FFFFFE72
ecx = 000000000
edx = 7C90E4F4
ebx = 00407EE2
ebp = FFFFFE72
esi = 00407F24
edi = 00408D54

Который делает неизвестно чего [не знаю я :( ].
Потом пакер опять в цикле копирует небольшой кусок кода:

pop esi
pop edi
mov eax, ecx
sar ecx, 2
rep movsd
add ecx, 3
rep movsb
jmp 00407F24

И копирует опять неподолеку. Останавливаемся на jmp'е и делаем шаг, попадая в только что скопированный код. Непродалеку вызывается функция VirtualAlloc:

push 4				//PAGE_READWRITE
push 1000			//MEM_COMMIT
push [ebp+40854Ch]	//5000h
push 0
call [ebp+408540h]	// вызов VirtualAlloc
mov edi, eax 		// запоминаем адрес выделенного блока

Так пакер просит выделить 5000h байт памяти с правами чтения и записи. И сразу бросается туда копировать (lodsd). Можно сделать вывод, что выделенный блок используется для хранения временных данных при распаковке.

Дальше идет цикл с насильным выходом:

push edi
or eax, eax		// eax равен нулю?
je 00407FA4		// равен, выходим из цикла
...
pop esi
pop edi
jmp 00407F6D	// продолжаем цикл
// конец цикла
pop eax
lea esi, ss:[ebp+4085e2]


Ставим бряк за циклом и ждем остановки.
после цикла нас ждет еще больший цикл, замешанный из команд:

....
lodsd,
....
rep movsb
....
sar ecx, eax
....
rep stosd
....

, к-е явно занимаются распаковкой. В конце мы находим - jmp на начало, а в самом начале - все теже

or eax, eax
je addr2

, направленные на конец цикла. Все это наталкивает нас на мысль о таком же бесконечном цикле с выходом в начале. Поэтому переходим по адресу, указанному в je addr2. Вообщем, весь дальнейший код занимается распаковкой и нам надо только найти точку выхода из пакера.
В скопированном коде есть передача управления на OEP. Логично предположить, что если в начале были:

pushfd
pushad
...
add esp, 4

, то стало быть, в конце будут popad и popfd.
Ищем в памяти ниже текущей позиции следующий фрагмент:

popad
popfd
push eax
push 004012С0
retn 4

Команда retn 4 передает управление на OEP, стало быть, 004012С0 - это ее адрес. Кстати, он совпадает с адресом перехода в ловушке. Так что кроме ловушки автор подложил нам хорошую подсказку, которую можно использовать при ручной распаковке.

- Точка входа
Посмотрим...

jmps  .0004B4008		; Прыгаем на (1)
push 000022C77
retn
pushfd				; (1)
pushad
call 0004B4011		; Вызываем (2)
xor eax, eax
mov eax, esp		; (2)
add eax, 4
xchg ebx, eax
mov ebx, [ebx][-4]
sub ebx, 0040903F
...
push ebx
push ebx
push ebx
push ebx

Встречается команда xcng для обмена 2-х регистров местами. Поскольку в начале находится jmp, то можно его проигнорировать и установить точку входа сразу на адрес перехода (pushfd). И антивирусы обманываются, и PEiD считает, что это 'PEBundle 02-3.x'. Значит, точка входа может может начинаться начинаться с jmp или pushfd. Команды pushfd и pushad можно поменять местами - для этого придется поменять и pop'ы в точке выхода. Или совсем убрав, заменив на nop'ы или незначащие команды, повторив операцию в точке выхода. Команда 'xor eax, eax' используется для выравнивания и может быть любой. В дальнейших командах можно использовать другие регистры или просто сделать 'add esp, 4'. В итоге получается, привязываться - не к чему.
Сигнатура будет такой:
*, 9C/60, 60/9C, E8 02 00 00 00 ?? ?? * 93 * 87
93 и 87 - это xchg. Но как уже сказано, сигнатура у PECompact надежной быть не может. В конце, на расстоянии 45-55h байт, есть 4 push'а, регистры в которых можно изменить.

- Ручная распаковка
Рассмотрим 2 способа:

*** На основе адреса возврата в ловушке ***
Во время анализа мы определили, что адрес в ловушке совпадает с адресом настоящей точки входа. Это значит, что адрес OEP мы будем знать в самом начале:

jmps  .00406608
push 000012С0	; RVA-OEP	
retn		
pushfd
pushad
call 00406611

Можно прогнать код до retn, чтобы упаковщик добавил к нему адрес загрузки:

nop, nop
push 004012C0	; VA OEP
retn

Ну а дальше дело техники - нам надо на нем остановиться:
1) Либо ставим аппаратный бряк на исполнение
2) Либо трассируем с остановкой при записи в этот адрес. Когда остановимся по записи, ставим обычный бряк и при условии что пакер больше сюда писать не будет, мы остановимся в OEP.
Этот способ - "неправильный". Мы ориентировались на адрес в ловушке, а на нее никогда не передается управление. Можно изменить этот адрес, и тогда этот способ не будет работать.

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

mov exc, 39D
rep movsd
pop edi
retn

Проходим его и останавливаемся на команде retn. Эта команда передаст управление на только что скопированный код, который находился ниже.
Далее нас ждет еще 2 небольших цикла:

mov eax, ecx
sar ecx, 2
rep movsd
add ecx, 3
rep movsb
jmp addr1

Останавливаемся на последней команде и делаем шаг. Как уже известно, дальше пойдет распаковка - это значит, что весь код уже скопирован и раскодирован, и точка выхода из пакера - тоже.
Ищем выход:

pushfd
pushad
push eax
push OEP
retn 4

Это и будет точкой выхода. Останавливаемся на retn 4, делаем шаг - и мы в настоящей точке входа! Делаем дамп.

- Признаки
Текстовые строки и имена секций:
1) В поле 'pointer to symbol' у PE-заголовка находится сигнатура 'PECO'.
2) первые 2 секции имеют имена 'pec1' и 'pec2'.
Точка входа очень не надежна и позволяет сильные изменения. Самые постоянные признаки, это пожалуй:
1) на значительном расстоянии (до 30h байт) должны быть 2 команды xcng
2) на расстоянии 45-55h байт есть четыре команды push с одинаковым регистром
Структура PE-файла:
1) как минимум 3 секции
2) в начале второй секции находятся ресурсы
3) точка входа - во второй секции, считая с конца
4) последнюю секцию занимает только таблица импорта
5) у первой секции есть аттрибуты Execute и Code, у второй - Initdata, а у последней - Initdata и Shared.
Последний признак ненадежен, так как все эти аттрибуты можно убрать, не потеряв функциональность.



PECompact версии 2.x


- Описание
Пожалуй, если кто-то и находится на 3-м месте, то это PECompact этой версии. Он менее распространен, чем UPX или AsPack и его автор не распространяет исходные тексты. Бесплатно можно получить триальную версию на 7 дней, а для неограниченного использования надо зарегистрироваться!
По сравнению с другими упаковщиками, у PECompact 2.x гораздо больше опций. Предусматривается оптимизация под скорость распаковки или минимальный размер. Можно выбрать - какие типы ресурсов оставить нетронутыми. И, самое главное, есть возможность:
1) внедрять перехватчики API-функций
2) выбрать другой кодек (это изменит алгоритм упаковки).
3) (и очень важно) - можно внедрить в программу водяные знаки.
Только мне непонятно - зачем они нужны.

- Описание
В файле находится 2 секции:

.text		rva 00001000, vsize 0006A000, offset 00000200, psize 00026000
.rsrc		rva 0006B000, vsize 00009000, offset 00026200, psize 00008600

Во второй секции находятся ресурсы и импорт:

resource directory		rva 0006B000, vsize 000077DE
import directory		rva 00072834, vsize 0000008F

После импорта расположены еще какие-то данные, о назначении которых мы еще не знаем.
Итак, импорт и ресурсы находятся во второй секции, стало быть, весь упакованный образ лежит в первой секции. Точка входа указывает на начало первой секции, что очень необычно.
Аттрибуты секций:
Секции имеют все аттрибуты, которые только можно. У первой секции есть и Execute, и Code и даже Initdata. У второй секции - только Execute и Code, что наводит на мысль, что в ней расположен код распаковщика.

- Сигнатура и Водяные знаки
Просмотрев файл, упакованный PECompact 2.x, мы увидим сигнатуру PEC2. Она находится в неиспользуемом поле 'relocation table offset' первой секции. Как было сказано ранее, упаковочная программа может внедрить в модуль "водяной знак". Если водяного знака нет, то после сигнатуры будет слово 'NO':

Рис 4. PeCompact_sign
http://img413.imageshack.us/img413/4843/4pecompactsign.jpg

Иначе, в поле 'line number table offset' в первой и второй секции будут 2 похожих числа:

Рис 5. PeCompact_water
http://img413.imageshack.us/img413/7738/5pecompactwater.jpg

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

- Анализ
Ну что ж, приступим к анализу кода.

mov eax, 0047335Ch
push eax
push fs[0]
mov fs:[0], esp

Видно, что пакер регистрирует свой SEH-обработчик, расположенный по адресу 0047335Ch, создав на стеке структуру след. вида:
0047335Ch
Old_SEH

Заметьте, что этот адрес находится в самом конце 2-ой секции, потому что ее физический конец находится по RVA-адресу 73600h (так как вирт. адрес конца секции = 74000, а разница между физич и вирт. размером секций составляет A00h).
Дальше пакер собирается нарушить доступ, записав ecx по нулевому адресу:

xor eax, eax
mov [eax], ecx

В результате управление должен получить SEH-обработчик по адресу 0047335Ch, ставим бряк на этот адрес, и снимаем его, когда мы на нем остановимся. После перехода в обработчки содержимое регистров полностью изменится. Что же делает обработчик?

0047335С:		mov eax, 0B2134h
00473361:		lea ecx, [eax+3C124Bh]
00473367:		mov [ecx+1], eax

Этот записывает по адресу (47337F+1) число 0B2134h. Как вы видите - адрес почти сопадает с текущим, значит происходит модификация кода. В результате модифицируется число в команде:

mov eax, 12345678h -> mov eax, 000B2134h

Далее код начинает разбираться в структуре, переданной через стек, в к-ой записана информация об исключении. На стеке лежит адрес возврата из ф-ии, которая нам сообщила об исключении, а ниже лежат 4 аргумента - это указатели, на информацию об исключении:
struct _EXCEPTION_RECORD *ExceptionRecord,
void * EstablisherFrame,
struct _CONTEXT *ContextRecord,
void * DispatcherContext

Вот значения этих указателей:
0012FCD4
0012FFBC
0012FCF0
0012FCA8

Код распаковщика записывает в edx значение первого указателя (mov edx, [esp+4]). Этот указатель ведет на самую главную структуру - ExceptionRecord, к-я содержит информацию о самом исключении:
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
UINT_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;

Код обращается по смещению 0Ch от начала этой структуры, которому соответствует поле ExceptionAddress, и записывает его значение в edx (mov edx, [edx+C]). В этом регистре оказывается адрес, по которому возникло исключение:

edx = 00401016h

Затем записывает туда E9:

mov [edx], E9

, в результате чего команды, следующие за 'xor eax, eax':

89 08		mov [eax], ecx
50		push eax
45		inc ebp
43		inc ebx

превращаются:

E9 08 50 45 43		jmp 43856023

в элегантные шорты! Далее распаковщик модифицирует адрес этого перехода, записывая по адресу
00401017h число 00072364h:
add edx, 5
sub ecx, edx
mov [edx-4], ecx

В результате:

jmp 43856023 -> jmp 0047337F

Следующие 2 команды возвращают 0, сообщая операционной системе, что исключение обработано и выполнение процесса можно продолжить:

xor eax, eax
retn

Итак, мы вернулись назад, на тот адрес (00401016h), где возникло исключение. Теперь тут находится jmp 0047337Fh и он передает управление на код... расположенный за нашим SEH-обработчиком! Выходит, мы просто перешли к следующей команде:

xor eax, eax
retn				// были здесь
mov eax, 0B2134h	// а теперь мы здесь!
pop fs:[0]
add esp, 4
push ebp
push ebx
push ecx
push edi
push esi
push edx
lea ebx, [eax+3C1204h]

push'ы сохраняют регистры на стек (значит где-то они будут сняты pop'ами). Поскольку контекст регистов сейчас восстановился, то на стеке лежит адрес старого обработчика исключений (0x0012FFBC) и 'pop fs:[0]' его восстанавливает в звании. Это говорит о том, что извращения закончились и началась распаковка. Так как в алгоритме распаковки нам разбираться не надо, я не буду вас долго мучить и скажу лишь несколько слов:
Первый вызов (call eax) вызывает функцию VirtualAlloc, чтобы выделить память для распаковки.
'call ecx' вызывает функцию, лежащую ниже в памяти (неподалеку) - по адресу 0x0047328Ah.
'call edi' передает управление коду, скопированному в выделенный блок памяти.
В конце секции, перед нулями находится код:

pop edx
pop esi
pop edi
pop ecx
pop ebx
pop ebp
jmp eax
00 00
00 00
...

Команды 'pop' восстанавливают значения регистров, что говорит о завершении распаковки. 'jmp eax' осуществляет переход на оригинальную точку входа.

- Точка входа
Итак, пакер мы изучили. Как же будет выглядеть его точка входа?

mov eax, 0047335Ch
push eax
push fs[0]
mov fs:[0], esp
xor eax, eax
mov [eax], ecx

Первые 2 команды можно заменить на 'push 0047335Ch', а лишний байт занять nop'ом и поменять местами. Поэтому на них не стоит пологаться.
4-я и 5-ая команды могут быть заменены на другие. Причина в том, что пакер модифицирует код, вставляя команду 'jmp 0047337Fh' вместо 'mov [eax], ecx'. Поскольку jmp состоит из 5-ти байтов, а mov - из 2-х, то после команды 'mov [eax], ecx' у нас есть еще 3 свободных байта. Этого достаточно чтобы:

xor eax, eax 	-> mov eax, 00000000h
mov [eax], ecx	-> div eax, eax

Пакер не различает - какое исключение произошло, поэтому Access Violantion можно заменить на Divide by zero. Правда, адрес исключения увеличится на 2 байта, но это не скажется на работоспособности программы. Возможно, что 2-ю и 3-ю команды тоже можно заменить на что-нибудь другое без потери работоспособности.
Так что... по точке входа ориентироваться нельзя! PEiD так и делает - ей наплевать на entrypoint, определяя PECompact 2.x как-то по-другому.

Рис 6. PeCompact 2
http://img413.imageshack.us/img413/5336/pecompact2x.jpg

- Ручная распаковка
Ничего сложного. В точке входа на стек заносится адрес обработчика исключений:

mov eax, 0047335Ch
push eax

Переходим по этому адресу и ищем внизу retn и ставим бряк на следующей команде:

xor eax, eax
retn
mov eax, 0B2134h	<- сюда ставим бряк
pop fs:[0]

Теперь запускаем программу и передав исключение программе, останавливаемся в точке входа останова. Все, теперь как попасть в OEP - каждый выбирает сам. Можно найти точку выхода из пакера - тогда ищите внизу кучу pop'ов и jmp, расположенные перед нулями:

pop edx
pop esi
pop edi
pop ecx
pop ebx
pop ebp
jmp eax
00 00
00 00
...

Ставим бряк на jmp и достигнув его, делаем шаг, оказываясь в OEP. Снимаем дамп.
Можно после остановки на команде 'mov eax, 0B2134h' включить трассировку, установив условием трассировки попадание в границы первой секции.

- Признаки
Текстовые строки:
1) 1-я и 2-ая секция имеет имя '.text' и '.rsrc'.
2) У первой секции в поле relocation table offset записана сигнатура 50h, 45h, 43h, 32h, которая в текстовом виде выглядит как 'PEC2'.
Точка входа ненадежна, но можно сказать, что в ней присутствует регистрация SEH-обработчика и последующая генерация исключения.
О структуре PE-файла можно сказать следующее:
1) Ресурсы и таблица импорта находятся в последней секции
2) Точка входа расположена близко к концу последней секции
3) Первая секция обладает аттрибутами Execute, Code, Initdata, а последняя - Execute и Code.

- Качество упаковки
По качеству упаковки можно сказать, что PECompact упаковывает:
* лучше, чем AsPack, так как в нем всего 2-3 секции, благорадя чему теряется мало места на выравнивание
* хуже, чем UPX, так как помимо распаковки PECompact занимается самомодификацие, запутыванием алгоритма и копированием данных в памяти - из-за этого код распаковщика занимает больше места и портит статистику упаковки



FSG 2.0


Давайте познакомимся с одним из самых странных и редких упаковщиков для Windows - FSG 2.0. Посмотрим nfo-файл, судя по нему FSG расшифровывается как (F[ast] S[mall] G[ood]). Упаковщик нацелен на обработку маленьких программ размером в 4-64 KB, таких как intro и crack.
Упаковывающая программа занимает всего-лишь 22 килобайта и не предоставляет нам никаких опций. Она без лишних слов сразу предлагает открыть нужный файл, затем показывает степень упаковки и все. Настоящий минимализм. Все 4 опции спрятаны в ini-файле, что очень похоже на UNIX (все опции - в сад). Качество упаковки чуть-чуть превосходит UPX, особенно на маленьких файлах. Из недостатков можно назвать отсутсвие поддержки:
- .NET-файлов
- DLL-библиотек
- TLS-директории
- Отложенного импорта
Но самое главное - содержит несколько подлянок с целью обломать распространенные утилиты, не до конца понимающие PE-формат и заодно усложнить сброс дампа.

- Опции упаковки
Принимают значение 1 или 0 (Да или Нет). В скобках - значение по умолчанию:
strip_overlays (1) - вырезать оверлеи.
strip_exports (0) - вырезать экспорт.
strip_delphi (1) - вырезать неиспользуемые ресурсы из Delphi-программ.
strip_version (0) - вырезать ли информацию о версии
handle_icons (0) - сжимать ли главную иконку приложения (тогда файл останется без иконки).

- Как выглядит?
Он крайне бережно относится к PE-структурам, правильно заполняя даже незначимые поля. Например, code base указывает на точку входа (1000h), а data base - на... начало PE-заголовка (000c). Чего? Да ничего. PE-заголовок расположен в самом начале файла, на смещении 0Ch от его начала. Так получилось, что по смещению 3Ch лежит поле data base, а в нем должно быть записано смещение PE-заголовка. Упаковщик всячески экономит пространство, прижимая структуры ближе друг к другу.
В файле всего 2 секции с пустыми именами:

"    rva 00001000, vsize 00014000, offset 00000000, psize 00000000
"    rva 00015000, vsize 0000C000, offset 00000200, psize 0000B665

Похоже на UPX - первая секция пуста, а вторая - нет, хотя находится почти в том же месте. Значит, распаковщик находится во 2-ой секции, а в первую - будет вестись распаковка.
Точка входа расположена в PE-заголовке (ep=00000154h, SizeOfHeaders=200h). Это вводит в ступор HTEditor, который считает, что точка входа может быть расположена только в секции. Раньше на это ловились другие программы, но теперь - нет.
В остальном упакованный файл не отличается от остальных. Вторая секция начинается с ресурсов:

resource directory	rva 00015000, vsize 00006de4

Как всегда - упакованы только текстовые строки (String) и изображения (кроме главной иконки). Информация о версии и манифест остаются нетронутыми. После ресурсов находится фрагмент, явно содержащий упакованный образ. Данные о распаковке находятся здесь же. Конец секции довершает обычная таблица импорта - всего из 2-х ф-ий - GetProcAddress и LoadLibrary. Но в импорте есть одна ловушка. Название библиотеки "KERNEL32.DLL" находится не в секции, а в конце PE-заголовка. Это обламывает почти все знаменитые тулзы - LordPE, PeTools и HIEW считают, что весь импорт обязан быть расположен только в секциях (и это мне еще говорят, что новые статьи по PE-формату не нужны, поскольку все его знают?). Исключением оказывается PeExplorer, воспринимая импорт правильно. Зато ему не нравятся ресурсы!
Виртуальный образ FSG - на рисунке

Рис 6. FSG 2.0
http://img413.imageshack.us/img413/1916/fsg20.jpg

- Аттрибуты секций
Обе секции имеют неиспользуемые COFF-аттрибуты Code, Initdata и Uninitdata. К сожалению, аттрибута Execute нет, что приведет к неработоспособности на машинах с битом запрета исполнения.

- Метка упаковщика
Вместо Timestamp стоит 'FSG!', в результате дата принимает 01:35:02 11.09.1987 год. Возможно, именно поэтому FSG не поддерживает динамические библиотеки - для них Timestamp очень важен и его нельзя затирать. По этой сигнатуре упаковывающая программа узнает что файл уже упакован FSG. Впрочем и без нее FSG отказывается обрабатывать уже упакованные файлы - это он определяет по низкой степени сжатия.
Метка FSG управляется значением опции pe_tag. Максимальная длина текста - 10 символов. Это значит, что кроме Timestamp метка может затереть неиспользуемые поля PointerToSymbolTable и NumbersOfSymbols. По умолчанию эта опция пустая, что соответсвует тексту 'FSG!'.

- Распаковка
Поверхостный просмотр кода доказывает что упаковщик:
1) Часто использует команду XCHG, меняющей содержимое 2-х регистров.
Это осложняет понимание алгоритма работы
2) Указатель стека используется как обычный регистр:

xchg [1020628], esp

Из-за этого нельзя пологаться на него в отладчике.
3) В вызове функций пологается на относительную адресацию.
Это означает, что придется трассировать всю программу, следя за изменением регистров.
Но мы ничего не будем анализировать. Мы поступим по-хитрому. Код очень долго выполняется в заголовке, не передавая другому коду управление. Если начать внутреннюю трассировку (Trace into) с условием пока не выйдем из заголовка, отладчик будет трассировать целых 10 секунд. После этого вызывается ф-я LoadLibrary и поэтому выполнение кода вышло за границы заголовка. Сравнение образа файла с доупакованным состоянием показывает, что программа уже распакована. Поэтому теперь можно ставить бряк на OEP и вуала... Вот только точку входа сначала надо найти!
Опять воспользуемся трассировкой. Ставим условие прекращение трассировки когда управление попадет в образ программы, то есть от первой секции до последней (например EIP от 1001000h до 1021000h). Логично что это первое попадание будет точкой входа. Только теперь выбираем внешнюю трассировку (Trace over) - внутренняя будет длиться вечность, перебирая все системные функции после LoadLibrary. И вот, трассировка завершается на адресе 100739Dh - если кто еще не догадался, все экперименты походили над блокнотом.
Есть более быстрый способ найти OEP, основанный на том, что состояние стека должно быть таким же, как до упаковки. Помните мы ставили условие на выход EIP из заголовка? Вот, теперь вместо трассировки внутрь выберите внешнюю трассировку. Так вы пропустите 10 секунд, потраченные на распаковку образа.

- Признаки
Текстовые строки:
1) В поле Timestamp PE-заголовка строка 'FSG!'
2) Все секции имеют пустые имена
3) В конце заголовка
А по структуре PE-файла:
1) Всего 2 секции, первая секция пустая
2) Во второй секции импорт идет после ресурсов
3) Точка входа указывает на заголовок
4) имя библиотеки Kernel32.dll находится в конце заголовка

Существует еще множество менее распространенных упаковщиков - PEcrypt, PEpatch, PEcompact. О них я говорить не буду.






Обсуждение статьи: Распаковываем самое простое >>>


Комментарии к статье: Распаковываем самое простое

Gauri 13.12.2009 14:20:46
Классно азы расписаны, побольше бы таких подробных статей!
А из мелочей: нумерацию бы поправить (два шестых рисунка), да фраза странно смотрится "Существует еще множество ... PEcompact. О них я говорить не буду" после того, как про этот самый PEcompact так хорошо расписано)
---
theCollision 19.12.2009 13:12:16
http://securitylabs.websense.com/content/Assets/HistoryofPackingTechnology.pdf

Это примерно как развивались упаковщики и защита исполнимых модулей, думаю каждый реверсер должен знать чего ждать от упакованного, защищенного кода ?
---
s0l 31.01.2010 13:42:23
Дайте больше тру-стотей по распаковке UPX'a!!!!!!!
---
vxcoder 07.05.2010 10:50:19
статья реально грамотная
---

Материалы находятся на сайте https://exelab.ru



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS