Олдфаг
-
Автор темы
- #1
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):
Физические смещения этих секций совпадают, значит в файле всего одна секция, которая проецируется по разным местам виртуального образа. Смотрите - физ. размер первой секции равен нулю, значит в памяти она будет пустая, а во вторую секцию попадает все содержимое файла.
Точка входа указывает на вторую секцию, стало быть - там распаковщик. Он распаковывает оригинальный образ секцию UPX0 и передает туда управление. Тем самым, UPX очень похож на ДОСовские упаковщики - там тоже распаковщик был в конце образа и распаковывал оригинал в начало. И не мудрено, если вспомнить что UPX способен обрабатывать MZ-файлы. А вообще - это единственный вариант, переносимый на исполняемые файлы любого формата.
- Точка входа
Заглянем в ентрипоинт. В начале стоит pushad, значит перед переходом на OEP будет команда popad.
Просмотрев пару десятков упакованных файлов, мы поймем, что в точке входа обязательно должны присутствовать следующие команды (назовем их обязательными):
Тем самым сигнатура точки входа выглядит так:
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 снимает регистры со стека, занесенные командой pushad в точке входа, а jmp осуществляет переход.
в секций UPX1 заканчивается командами.
Ставим бряк в отладчике на jmp'е делаем шаг и мы в оригинальной точке входа. Сбрасываем дамп. Проще пареной морковки!
- Признаки упаковщика
Итак, давайте обсудим все признаки программы, упакованной UPX. Сначала посмотрим на текстовые признаки:
1) Первые две секции называются UPX0 и UPX1
2) Перед началом первой секции записана версия упаковки в виде текста и сигнатура 'UPX!'.
Как я уже говорил - текстовые признаки ненадежны. Названия секций можно изменить, а заголовок распаковки - удалить, не повлияв на работу программы.
Тогда давайте распознавать по точке входа:
1) Чаще всего начинается с pushad, иногда с nop;
2) Далее встречаются команды
и короткий прыжок (jmps или jmp).
3) Может быть разбавлена несколькими nop'ами.
Точку входа можно исказить скрэмблерами или зашифровать, внедрив небольшой код. Теперь рассмотрим особенности структуры PE-файла:
1) Программа содержит максимум 3 секций (+.rsrc). В большинстве программ - >= 4 секций
2) Первая секция обычно заметно больше второй (по виртуальному размеру).
3) Обе секции имеют одинаковое физическое смещение в файле (offset).
4) Первая секция имеет нулевой физический размер.
5) Обе секции имеют атрибут "Execute"
Эти признаки - самые надежные, так как исказить структуру PE-файла можно только если обработать файл ещё чем-нибудь (криптором или протектором). 4-ый признак - не очень надежен, так как вместо нулевого физического адреса можно поставить какой-нибудь другой. В результате первая секция в памяти перестанет быть пустой и заполнится данными из файла. Но это не повлияет на алгоритм работы - первая секция используется только для распаковки, а значит, данные туда только пишутся, а не читаются. 5-ый признак - тоже не надежен, так как атрибут "Execute" можно убрать, не повлияв на работу.
Самые надежные признаки UPX'а - это 1-ый, 2-ой и 3-ий.
Программу можно упаковать и внедрить в нее код, который перед передачей управления ее распакует в памяти. Некоторые из упаковщиков содержит антиотладочные приемы, но почти ни один из них не контролирует целостность виртуального образа. Когда мы узнали, что программа чем-то обработана, то нужно точно узнать - упакована она или только закодирована.
Любой упаковщик (за исключением 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
Точка входа указывает на вторую секцию, стало быть - там распаковщик. Он распаковывает оригинальный образ секцию 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
в секций UPX1 заканчивается командами.
Ставим бряк в отладчике на jmp'е делаем шаг и мы в оригинальной точке входа. Сбрасываем дамп. Проще пареной морковки!
- Признаки упаковщика
Итак, давайте обсудим все признаки программы, упакованной UPX. Сначала посмотрим на текстовые признаки:
1) Первые две секции называются UPX0 и UPX1
2) Перед началом первой секции записана версия упаковки в виде текста и сигнатура 'UPX!'.
Как я уже говорил - текстовые признаки ненадежны. Названия секций можно изменить, а заголовок распаковки - удалить, не повлияв на работу программы.
Тогда давайте распознавать по точке входа:
1) Чаще всего начинается с pushad, иногда с nop;
2) Далее встречаются команды
Код:
or ebp,-001
3) Может быть разбавлена несколькими nop'ами.
Точку входа можно исказить скрэмблерами или зашифровать, внедрив небольшой код. Теперь рассмотрим особенности структуры PE-файла:
1) Программа содержит максимум 3 секций (+.rsrc). В большинстве программ - >= 4 секций
2) Первая секция обычно заметно больше второй (по виртуальному размеру).
3) Обе секции имеют одинаковое физическое смещение в файле (offset).
4) Первая секция имеет нулевой физический размер.
5) Обе секции имеют атрибут "Execute"
Эти признаки - самые надежные, так как исказить структуру PE-файла можно только если обработать файл ещё чем-нибудь (криптором или протектором). 4-ый признак - не очень надежен, так как вместо нулевого физического адреса можно поставить какой-нибудь другой. В результате первая секция в памяти перестанет быть пустой и заполнится данными из файла. Но это не повлияет на алгоритм работы - первая секция используется только для распаковки, а значит, данные туда только пишутся, а не читаются. 5-ый признак - тоже не надежен, так как атрибут "Execute" можно убрать, не повлияв на работу.
Самые надежные признаки UPX'а - это 1-ый, 2-ой и 3-ий.