Олдфаг
-
Автор темы
- #1
- Описание
Пожалуй, если кто-то и находится на 3-м месте, то это PECompact этой версии. Он менее распространен, чем UPX или AsPack и его автор не распространяет исходные тексты. Бесплатно можно получить триальную версию на 7 дней, а для неограниченного использования надо зарегистрироваться!
По сравнению с другими упаковщиками, у PECompact 2.x гораздо больше опций. Предусматривается оптимизация под скорость распаковки или минимальный размер. Можно выбрать - какие типы ресурсов оставить нетронутыми. И, самое главное, есть возможность:
1) внедрять перехватчики API-функций
2) выбрать другой кодек (это изменит алгоритм упаковки).
3) (и очень важно) - можно внедрить в программу водяные знаки.
Только мне непонятно - зачем они нужны.
- Описание
В файле находится 2 секции:
Во второй секции находятся ресурсы и импорт:
После импорта расположены еще какие-то данные, о назначении которых мы еще не знаем.
Итак, импорт и ресурсы находятся во второй секции, стало быть, весь упакованный образ лежит в первой секции. Точка входа указывает на начало первой секции, что очень необычно.
Аттрибуты секций:
Секции имеют все аттрибуты, которые только можно. У первой секции есть и Execute, и Code и даже Initdata. У второй секции - только Execute и Code, что наводит на мысль, что в ней расположен код распаковщика.
- Сигнатура и Водяные знаки
Просмотрев файл, упакованный PECompact 2.x, мы увидим сигнатуру PEC2. Она находится в неиспользуемом поле 'relocation table offset' первой секции. Как было сказано ранее, упаковочная программа может внедрить в модуль "водяной знак".
- Анализ
Ну что ж, приступим к анализу кода.
Видно, что пакер регистрирует свой SEH-обработчик, расположенный по адресу 0047335Ch, создав на стеке структуру след. вида:
0047335Ch
Old_SEH
Заметьте, что этот адрес находится в самом конце 2-ой секции, потому что ее физический конец находится по RVA-адресу 73600h (так как вирт. адрес конца секции = 74000, а разница между физич и вирт. размером секций составляет A00h).
Дальше пакер собирается нарушить доступ, записав ecx по нулевому адресу:
В результате управление должен получить SEH-обработчик по адресу 0047335Ch, ставим бряк на этот адрес, и снимаем его, когда мы на нем остановимся. После перехода в обработчки содержимое регистров полностью изменится. Что же делает обработчик?
Этот записывает по адресу (47337F+1) число 0B2134h. Как вы видите - адрес почти сопадает с текущим, значит происходит модификация кода. В результате модифицируется число в команде:
Далее код начинает разбираться в структуре, переданной через стек, в к-ой записана информация об исключении. На стеке лежит адрес возврата из ф-ии, которая нам сообщила об исключении, а ниже лежат 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]). В этом регистре оказывается адрес, по которому возникло исключение:
Затем записывает туда E9:
, в результате чего команды, следующие за 'xor eax, eax':
превращаются:
в элегантные шорты! Далее распаковщик модифицирует адрес этого перехода, записывая по адресу
В результате:
Следующие 2 команды возвращают 0, сообщая операционной системе, что исключение обработано и выполнение процесса можно продолжить:
Итак, мы вернулись назад, на тот адрес (00401016h), где возникло исключение. Теперь тут находится jmp 0047337Fh и он передает управление на код... расположенный за нашим SEH-обработчиком! Выходит, мы просто перешли к следующей команде:
push'ы сохраняют регистры на стек (значит где-то они будут сняты pop'ами). Поскольку контекст регистов сейчас восстановился, то на стеке лежит адрес старого обработчика исключений (0x0012FFBC) и 'pop fs:[0]' его восстанавливает в звании. Это говорит о том, что извращения закончились и началась распаковка. Так как в алгоритме распаковки нам разбираться не надо, я не буду вас долго мучить и скажу лишь несколько слов:
Первый вызов (call eax) вызывает функцию VirtualAlloc, чтобы выделить память для распаковки.
'call ecx' вызывает функцию, лежащую ниже в памяти (неподалеку) - по адресу 0x0047328Ah.
'call edi' передает управление коду, скопированному в выделенный блок памяти.
В конце секции, перед нулями находится код:
Команды 'pop' восстанавливают значения регистров, что говорит о завершении распаковки. 'jmp eax' осуществляет переход на оригинальную точку входа.
- Точка входа
Итак, пакер мы изучили. Как же будет выглядеть его точка входа?
Первые 2 команды можно заменить на 'push 0047335Ch', а лишний байт занять nop'ом и поменять местами. Поэтому на них не стоит пологаться.
4-я и 5-ая команды могут быть заменены на другие. Причина в том, что пакер модифицирует код, вставляя команду 'jmp 0047337Fh' вместо 'mov [eax], ecx'. Поскольку jmp состоит из 5-ти байтов, а mov - из 2-х, то после команды 'mov [eax], ecx' у нас есть еще 3 свободных байта. Этого достаточно чтобы:
Пакер не различает - какое исключение произошло, поэтому Access Violantion можно заменить на Divide by zero. Правда, адрес исключения увеличится на 2 байта, но это не скажется на работоспособности программы. Возможно, что 2-ю и 3-ю команды тоже можно заменить на что-нибудь другое без потери работоспособности.
Так что... по точке входа ориентироваться нельзя! PEiD так и делает - ей наплевать на entrypoint, определяя PECompact 2.x как-то по-другому.
- Ручная распаковка
Ничего сложного. В точке входа на стек заносится адрес обработчика исключений:
Переходим по этому адресу и ищем внизу retn и ставим бряк на следующей команде:
Теперь запускаем программу и передав исключение программе, останавливаемся в точке входа останова. Все, теперь как попасть в OEP - каждый выбирает сам. Можно найти точку выхода из пакера - тогда ищите внизу кучу pop'ов и jmp, расположенные перед нулями:
Ставим бряк на 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 занимается самомодификацие, запутыванием алгоритма и копированием данных в памяти - из-за этого код распаковщика занимает больше места и портит статистику упаковки
Пожалуй, если кто-то и находится на 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' первой секции. Как было сказано ранее, упаковочная программа может внедрить в модуль "водяной знак".
- Анализ
Ну что ж, приступим к анализу кода.
Код:
mov eax, 0047335Ch
push eax
push fs[0]
mov fs:[0], esp
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
Код:
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
Код:
mov [edx], E9
Код:
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
Код:
xor eax, eax
retn
Код:
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]
Первый вызов (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
...
- Точка входа
Итак, пакер мы изучили. Как же будет выглядеть его точка входа?
Код:
mov eax, 0047335Ch
push eax
push fs[0]
mov fs:[0], esp
xor eax, eax
mov [eax], ecx
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
Так что... по точке входа ориентироваться нельзя! PEiD так и делает - ей наплевать на entrypoint, определяя PECompact 2.x как-то по-другому.
- Ручная распаковка
Ничего сложного. В точке входа на стек заносится адрес обработчика исключений:
Код:
mov eax, 0047335Ch
push eax
Код:
xor eax, eax
retn
mov eax, 0B2134h <- сюда ставим бряк
pop fs:[0]
Код:
pop edx
pop esi
pop edi
pop ecx
pop ebx
pop ebp
jmp eax
00 00
00 00
...
Можно после остановки на команде '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 занимается самомодификацие, запутыванием алгоритма и копированием данных в памяти - из-за этого код распаковщика занимает больше места и портит статистику упаковки