Низкоуровневое программирование для Дzenствующих

Вид материалаДокументы

Содержание


Практический пример: Aspack
Aspack выполняет разжимание секций по LZ
Full Dump: fix header, Full Dump: rebuild image. DumpFix, ValidatePE, RebuildPE
OEP – на popad
Нiзкоуровнђвое программiрованiе для Дzђnствующих# 39
Подобный материал:
1   ...   34   35   36   37   38   39   40   41   42

Практический пример: Aspack


Что делает новичок, увидев программу, запакованную Aspack? Ну, берется Soft-Ice, берется дампер процессов и, обязательно, Imprec. Потом над всем этим инструментарием начинают интенсивно издеваться – зацикливать на OEP, дампить целиком, посекционно или еще как, немедленно запускать Imprec и вставлять полученный дамп директории импорта в файл. Ну что ж. Можно и так. Работает. Только давайте усложним задание. Положим, дампер процессов не имеет никакого движка по перестройке импорта, а Imprec и иже с ним у нас просто нет. Что тогда?

А вот тут-то и надо рассматривать сам алгоритм работы упаковщика. К счастью, есть и такие статьи. Например: «ссылка скрыта» - довольно толковая статья (когда будете читать – хм, вы уже знаете, что такое KTEB). Несмотря на то, что рассматривается старая версия, в новой (2.12) не так уж и много изменений с нашей точки зрения. Для кросс-проверки и некоторого дополнения сведений можно также проглядеть статью «ссылка скрыта».

В связи с тем, что есть такие великолепные материалы, мы не станем рассматривать код ASPack подробно. Для начала учтите – что это не криптор, это самый обычный упаковщик. Здесь нет ни антиотладки, ни сколь-нибудь сложных приемов противостояния дизассемблерам. Лишь в самом начале есть нечто, робко напоминающее полиморфный код:

;версия 2.12
.aspack:01019001 pusha
;КРЕПКО запомните эту инструкцию! Проникнитесь! Она нам ох как пригодится!
;да, меж прочим, дизассемблирована эта команда неверно!

;В 32-битном режиме это pushad.
.aspack:01019002 call loc_101900A
.aspack:01019002 ; ------------------------------------------------------------
.aspack:01019007 db 0E9h ; щ
.aspack:01019008 ; ------------------------------------------------------------
.aspack:01019008 jmp short loc_101900E
.aspack:0101900A ; ------------------------------------------------------------
.aspack:0101900A
.aspack:0101900A loc_101900A: ; CODE XREF: start+1p
.aspack:0101900A pop ebp
.aspack:0101900B inc ebp
;++ebp = eip – хороший пример позиционно-независимого кода – PIC
.aspack:0101900B start endp
.aspack:0101900B
.aspack:0101900C push ebp
.aspack:0101900D


.aspack:0101900D locret_101900D: ; CODE XREF: start+7u
.aspack:0101900D retn
;учтите – это не антиотладка, это лишь достаточно красивый

;пример вывертов с ассемблером – смотреть приятно!
.aspack:0101900E ; -------------------------------------------------------------
.aspack:0101900E
.aspack:0101900E loc_101900E: ; CODE XREF: start+7j
.aspack:0101900E call loc_1019014
.aspack:01019014 loc_1019014: ; CODE XREF: .aspack:0101900Ep
.aspack:01019014 pop ebp
;вопрос на засыпку – на что показывает ebp?
;здесь очень удобна возможность IDA переходить по G “+”

; – просто ставьте курсор на нужное смещение и вперед.

Итак, ясно, что ebp показывает на начало кода/данных. В связи с этим весь остальной код теперь достаточно четок и ясен. Полагаем, понять, что делает этот код, после фокуса с ebp, уже предельно просто:

.aspack:01019035 lea eax, [ebp+42Eh] ;offset на строку kernel32.dll
.aspack:0101903B push eax
.aspack:0101903C call dword ptr [ebp+0F4Dh] ;вызывается GetModuleHandleA
; явное получение хендла нужно для вызова некоторых

; дополнительных функций – VirtualAlloc/VirtualFree и т.п.

Фокус с

.aspack:0101906C lea eax, [ebp+77h]
.aspack:0101906F jmp eax

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

Теперь давайте посмотрим, как правильно надо сбрасывать дамп из программ, запакованных Aspack. Здесь предельно четко нужно понимать, что нам нужен файл ДО обработки его самим упаковщиков, т.е. СРАЗУ ЖЕ после разжатия. Такой момент существует не для всех упаковщиков, однако для Aspack он проявляется предельно наглядно.

Итак, Aspack выполняет разжимание секций по LZ+Хаффман-подобному алгоритму с использованием VirualAlloc для отведения памяти под временный буфер и VirtualFree для высвобождения этого буфера. В буфер вбрасывается разжатое содержимое, которое копируется двойными словами (хорошая оптимизация!) (и докопируется побайтно при необходимости) на то место, что принадлежит ему по праву – на оригинальный RVA секции. После полного разжатия и копирования Aspack принимается обрабатывать директорию импорта (см. ниже), директорию перемещаемых элементов, вычисляет и предает управление на OEP. Наша задача – сбросить дамп ДО обработки импорта, fixup-элементов и т.п. Сделаем мы это как раз здесь:

.aspack:0101916D mov ecx, eax ; счетчик
.aspack:0101916F mov edi, [esi]
.aspack:01019171 add edi, [ebp+422h] ; ImageBase + section RVA
.aspack:01019177 mov esi, [ebp+152h] ; внутренние таблицы упаковщика
.aspack:0101917D sar ecx, 2

; копирование двойнми словами – edi - приемник
.aspack:01019180 rep movsd
.aspack:01019182 mov ecx, eax
.aspack:01019184 and ecx, 3
.aspack:01019187 rep movsb ; докопировать хвостик
.aspack:01019189 pop esi
.aspack:0101918A push MEM_RELEASE ; DWORD dwFreeType
.aspack:0101918F push 0 ; в точности после VirtualAllloc
.aspack:01019191 push dword ptr [ebp+152h] ; LPVOID lpAddress
.aspack:01019197 call dword ptr [ebp+551h] ; VirtualFree
.aspack:0101919D add esi, 8
.aspack:010191A0 cmp dword ptr [esi], 0
.aspack:010191A3 jnz

unpack_loop ; для каждой секции
.aspack:010191A9 push MEM_RELEASE ;---> это и есть наша цель
.aspack:010191AE push 0
.aspack:010191B0 push dword ptr [ebp+156h]
.aspack:010191B6 call dword ptr [ebp+551h] ; VirtualFree

Адрес 0х010191A9 здесь как раз и является «заветным». Зацикливая программу на этом адресе мы можем быть уверены, что дамп является полностью рабочим и не подвергнут ни обработке импорта, ни чему-либо еще. Ввести программу в бесконечный цикл можно по-разному, к примеру, введите ассемблерную команду jmp eip, воспользуйтесь командой !dump в IceExt, выберите SuspendThread, словом, вам и карты в руки. Мы же здесь, разумеется, объясним как использовать PE Tools.

Итак, после того как jnz не сработает – все секции распакованы и можно приступать. Полный дамп файла с опциями Full Dump: fix header, Full Dump: rebuild image. DumpFix, ValidatePE, RebuildPE. В дампе ручками в PE Editor: Optional Header > пересчитываем: SizeOfImage, SizeOfHeaders, Checksum (при помощи кнопки "?"). Уменьшаем размер. Делаем RebuildPE, с опциями: DumpFix, ValidatePE, RebuildPE.

Файл ПОЧТИ валиден. Осталась мелочь – параметры директории импорта и правка EP. Нам более не нужен код пакера, поэтому EP должна быть переориентирована назад – на OEP и директория импорта должна быть поправлена. Для этого залезем в алгоритм Aspack еще раз. Вспоминаем первую часть и действие функции LdrpSnapIAT, что в ntdll.dll. Все пакеры должны эмулировать ее действие, заключающееся (не только) в превращении RVA полей директории импорта в VA – взгляните на код:

.aspack:01019278 mov esi, 12A40h ; RVA на директорию импорта
;обратите внимание – вычисляется упаковщиком

;при обработке файла – гляньте на RVA в нормальном файле
;остается в файле в открытом виде, что очень удобно для поиска по константе
.aspack:0101927D mov edx, [ebp+422h] ; ImageBase
.aspack:01019283 add esi, edx ; в VA (VA = RVA + ImageBase)
.aspack:01019285
.aspack:01019285 process_IID: ; CODE XREF: .aspack:01019395j
.aspack:01019285 mov eax, [esi+IMAGE_IMPORT_DESCRIPTOR.Name]
.aspack:01019288 test eax, eax
.aspack:0101928A jz finish ; конец директории импорта?
.aspack:01019290 add eax, edx ; RVA на имя dll -> в VA на имя dll
.aspack:01019292 mov ebx, eax
.aspack:01019294 push eax
.aspack:01019295

call dword ptr [ebp+0F4Dh] ; GetModuleHandle
.aspack:0101929B test eax, eax
.aspack:0101929D jnz short module_already_loaded
.aspack:0101929F push ebx
.aspack:010192A0 call dword ptr [ebp+0F51h] ; LoadLibraryA
...
.aspack:010192F5 push ebx ; или имя, или ординал
.aspack:010192F6 push dword ptr [ebp+545h] ; handle
.aspack:010192FC call dword ptr [ebp+0F49h] ; GetProcAddress
.aspack:01019302 test eax, eax
.aspack:01019304 pop ebx ; *PIMAGE_THUNK_DATA32
.aspack:01019305 jnz short addr_ok ; функция вернула валидный адрес
...
.aspack:01019376

addr_ok: ; CODE XREF: .aspack:01019305j
.aspack:01019376 ; .aspack:0101935Ej
.aspack:01019376 mov [edi+IMAGE_THUNK_DATA.u1], eax

; +sizeof(IMAGE_THUNK_DATA32)
.aspack:01019378 add dword ptr [ebp+549h], 4
.aspack:0101937F jmp loc_10192B6
; после заполнения IMAGE_THUNK_DATA реальными

; адресами функций и замене RVA на VA
; директория теряет смысл для нас

Итак, RVA директории импорта ясен, осталось узнать размер. Делаем так:

:g 1019278 ;встать на mov esi, 12A40h
:d esi+edx ;видим начало IID и визуально определяем

; конец
001B:01012A40 C0 2B 01 00 FF FF FF FF-FF FF FF FF E6 2C 01 00 .+..........ж,..
001B:01012A50 F4 10 00 00 60 2B 01 00-FF FF FF FF FF FF FF FF ....+..........
001B:01012A60 08 2E 01 00 94 10 00 00-CC 2A 01 00 FF FF FF FF ....”....*......
;он выглядит так:
:d 010134D0
0023:010134D0 65 01 47 65 74 57 69 6E-64 6F 77 54 65 78 74 57 e.GetWindowTextW
0023:010134E0 00 00 55 53 45 52 33 32-2E 64 6C 6C 00 00 00 00 ..USER32.dll....
0023:010134F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
:? 10134f0-1012a40 ;вычисляем длину
= 0xAB0, 2736, "\n°"

Параметры директории должны выглядеть так: RVA – 0x12A40, Size – 0xAB0. Теперь OEP:

.aspack:0101939A mov eax, 12420h ; OEP RVA –

; и OEP хранится в чистом виде
.aspack:0101939F push eax
.aspack:010193A0 add eax, [ebp+422h] ; OEP VA = OEP RVA + ImageBase
.aspack:010193A6 pop ecx
.aspack:010193A7 or ecx, ecx
.aspack:010193A9 mov [ebp+3A8h], eax
;заметьте, в коде aspack много подобных инструкций –

;это его отличительная черта, сбросьте флажок Writable у

;секции aspack в вашем файле и немедленно увидите результат
.aspack:010193AF popa
.aspack:010193B0 jnz short loc_10193BA
.aspack:010193B2 mov eax, 1
.aspack:010193B7 retn 0Ch ; оригинальная dll не имела

; точки входа
.aspack:010193BA ; --------------------------------------------------------------
.aspack:010193BA
.aspack:010193BA loc_10193BA: ; CODE XREF: .aspack:010193B0j
.aspack:010193BA push 0
;операнд инструкции push заполняется динамически,

;инструкцией по адресу 010193A9
.aspack:010193BF retn ;на OEP

Меняем RVA OEP на 0х12420. Все. Дамп рабочий и полностью готов. В качестве домашнего задания – попытайтесь удалить секции Aspack. Если с тем, что здесь написано, есть внутренние сомнения – почитайте дополнительно очень хорошую статью: «ссылка скрыта», а мы, тем временем, рассмотрим один оригинальный, хотя и не новый, прием. Надеемся, вы еще помните, что мы советовали обратить внимание на pusha в начале этой главы. Вы уже знаете, что дампить программу нужно не на OEP, а, желательно, задолго до него. В случае Aspack, где в открытом виде остается директория импорта, перемещаемых элементов, распаковываются секции, до OEP добираться нет нужды. Однако, возможно представить ситуации, когда времени просто нет и нужно добежать. Тогда используйте трюк с bpm esp-4. Немедленно после pushad ставьте эту точку останова. Действие pushad можно глянуть в интеловских талмудах в виде псевдокода. Если лень глядеть, вот:

Temp ? (ESP);
Push(EAX); // 0x1C
Push(ECX); // 0x18
Push(EDX); // 0x14
Push(EBX); // 0x10
Push(Temp); // 0x0C
Push(EBP); // 0x08
Push(ESI); // 0x04
Push(EDI); // 0x00

Точка останова сработает поблизости от OEP – на popad – при вытаскивании регистров назад. Вот здесь:

.aspack:010193AF popa ;-> точка останова сработает здесь
.aspack:010193B0 jnz short loc_10193BA
.aspack:010193B2 mov eax, 1
.aspack:010193B7 retn 0Ch ; оригинальная dll не имела точки входа
.aspack:010193BA ; ---------------------------------------------------------
.aspack:010193BA
.aspack:010193BA loc_10193BA: ; CODE XREF: .aspack:010193B0j
.aspack:010193BA push 0
.aspack:010193BF retn ;на OEP

Заметим напоследок, что трюк с bpm esp-4 сейчас работает не так уж и часто. Многие крипторы специально осложняют нам жизнь. В некоторых случаях это можно обойти, к примеру, поставив точку останова еще ниже, но с некоторыми крипторами не помогает и это. К примеру, вполне возможно поставить в блок try/catch пример на исчерпание стека в бесконечном цикле, а обработчик исключения должен это дело разобрать. Для успешного понимания таких защит необходимо четко разбираться в SEН – структурных исключениях, что и рассматривается в следующей главе.

(продолжение следует...)

Нiзкоуровнђвое программiрованiе для Дzђnствующих
# 39