Главная » Статьи » Распаковка

Распаковка. Общий подход
Содержание:

1. Вступление
2. Нам понадобится
3. Ликбез
Стек
Несколько слов о RVA
Способы адресации
4. Приступим к распаковке
В поисках OEP
Сбрасываем дамп
Восстановление импорта
5. Ссылки


1. Вступление
Попав внутрь запакованной программы, начинающий cracker начинает волноваться и паникивать, ведь там так одиноко без WinAPI, а кругом вражески настроенные опкоды. И вроде незачто зацепиться, однако это не так, и распокавать программу на самом деле очень просто. Сегодня я расскажу о том как это делается.

2. Нам понадобится
а) Отладчик OllyDbg и плагины к нему (CommandBar, OllyDump, OllyScript).
б) Редактор PE-файлов - LordPE
в) Программа для восстановления импорта - ImpRec

3. Ликбез
а) Стек
Чего я не знаю о стеке? - спросит читатель. Push - pop и все дела, ан нет, им можно пользоваться и более умело.
В регистре esp храниться адрес вершины стека, тоесть параметра помещённого в стек последним. К примеру:
Код:
push 5h
push 6h
mov eax,dword ptr [esp] - после выполнения в eax будет 6h

А как обратится к 5 через esp? Опятьтаки спросит читатель.
Для начала мы сохраним esp в ebp, но так как ebp используем не мы одни, то нужно сохранить его.
Код:
push ebp
mov ebp,esp - теперь в ebp адрес вершины стека

Также необходимо понимать, что стек растёт от старших адресов к младшим. Это очень важно!!!
Так как 5h было помещено раньше чем все остальное, то и адрес у него будет больше.
Также мы знаем, что размер одного кадра стека равен 4h.
Следовательно чтобы обратится к пяти надо к ebp прибавить 2h*4h.
Код:
push 5h
push 6h
push ebp
mov ebp,esp
mov eax, dword ptr [ebp+8] - в eax 5h

А параметры помещенные после сохранения ebp?
Просто теперь вычетаем номер желаемого параметра помноженный на размер кадра.
mov eax,dword ptr [ebp-1*4h] - первый параметр, запушенный после ebp
И во всём этом легко убедится.
Напишем простую программу на MASM, где будет следующий код:
Код:
push 5h
push 6h
push ebp
mov ebp,esp; <- сохраняем esp
mov eax, dword ptr [ebp+4]; <- в eax 6h
mov eax, dword ptr [ebp+8]; <- в eax 5h
push 7h
mov eax,dword ptr [ebp-4h]; <- в eax 7h
add esp,4h; <- увеличиваем esp на 4h.В данный момент esp=ebp-4h (тоесть по этому адресу находится семёрка)
;после увеличения по адресу esp будет находится, сохранённое значение ebp
pop ebx <- востонавливаем ebp
push 0
call ExitProcess

Открываем в Olly, убеждаемся в моей правоте, читаем далее.

б) Несколько слов о RVA
Термины:
Entry Point (EP) - точка входа в программу
Original Entry Point (OEP) - это адрес, с которого бы начала выполняться программа если бы не была упакованна
Virtual Address (VA) - виртуальный адрес элемента в памяти
Relative Virtual Adress (RVA) - относительный виртуальный адрес. Адрес относительно ImageBase.
Image Base - это адрес в памяти, начиная с которого программа загружена в память
Те самые несколько слов:
Очевидно, что OEP не равно EP, иначе вы бы эту статью не читали. Его мы будем искать. Но дело не в этом, нам понадобится RVA OEP. Как вы, думаю, догадались нужно просто из VA OEP вычесть ImageBase. Из полного виртуалного адреса VA вычитаем адрес самого начала (даже не программы) файла, и получется RVA.
RVA OEP = VA OEP - ImageBase
Ну вот к примеру, мы нашли OEP равный 00402000, а ImageBase равно 00400000, тогда RVA OEP будет равно 2000.
Где мы найдём ImageBase - это второстепенный вопрос, о котором я расскажу позже.

в) Способы адресации
Сейчас я хочу поговорить с вами о том, как передать управление в другую часть кода.
Первый способ:
Код:
jmp metka
metka:
mov eax,metka
jmp eax

Код:
Втрой способ:
push metka
retn
metka:

Третий способ:
Код:
call metka
metka:

Четвёртый способ:
Код:
stc
jc metka
metka:

Пятый способ:
Код:
mov cl,1
loop metka
metka:

Эти примеры могут нам пригодится при нахождении OEP.

4. Приступим к распаковке
а) В поисках OEP
Общая часть:
Резонно будет предположить, что код, распаковывающий прогамму, во время распаковки тоже использует стек, следовательно после его выполнения он будет восстанавливать esp. И неприменно обратится к адресу esp-4, на него мы и ставим break. В OllyDbg это делается так: в CommandBar пишем "hr esp-4" enter. А дальше? А дальше, после восстановления регистров и стека, перейдёт на OEP, который мы подсмотим.

Пример с UPX 0.89.6 - 1.02 / 1.05 - 1.24 -> Markus & Laszlo:
Запускаем программу по F9. Break срабатывает, и мы видим следующие инструкции:
Код:
popad
jmp xxxxxxxx

В этом месте программа восстанавливает все регистры и передаёт управление коду по адресу xxxxxxxx. Скорее всего это и есть переход на OEP. Проверим. Жмем F8 и попадаем сюда.
Код:
PUSH 0
CALL Crackme.0040109C ; JMP to kernel32.GetModuleHandleA
MOV DWORD PTR DS:[404094],EAX
CALL Crackme.00401096 ; JMP to kernel32.GetCommandLineA
MOV DWORD PTR DS:[404098],EAX

Явно не код распоковщика, а следовательно распакованной программы. Тоесть jmp - это переход на OEP программы.
Обращение к esp-4 может встерчаться много раз в программе, вот для чего я привел способы адресации. Вам придётся нераз трасировать программу (F7), чтобы распознать где заканчивается код распаковщика и начинается код программы.

upx_script1.txt
Код:
/* Transfer execution to some label on next breakpoint. */
eob Break:
/* Мы заметели, что прога использут popad(61h) для восстановления регистров. Найдём его. */
findop eip, #61#
/* Поставим бряк на найденый адрес */
bphws $RESULT, "x"
/* Executes F9 in OllyDbg */
run
Break:
/* Execute F8 in OllyDbg */
sto
/* Execute F8 in OllyDbg */
sto
/* Снимаем брейк */
bphwc $RESULT
/* Выходим */
ret

Можно короче(если убрать коменты и cmt, конечно):

upx_sript2.txt
Код:
findop eip, #61#
/* go: Executes to specified address (like G in SoftIce) */
/* Выполнять до адреса $RESULT*/
go $RESULT
sto
sto
/* cmt - комментировать строку, по адресу (в данном случае - eip) */
cmt eip," <------------- OEP"
ret


Пример с ASPack 2.12 -> Alexey Solodovnikov:
Запускаем программу по F9. Break срабатывает, и мы видим следующие инструкции:
Код:
JNZ SHORT Crackme.004053BA
MOV EAX,1
RETN 0C
PUSH Crackme.00402000
RETN

Жмём по F8, jnz срабатывает и передаёт управление на push, конструкция push-retn представлена в способах адресации.

push xxxxxx
retn
можно представить так:
jmp xxxxxx

После перехода на xxxxxx, мы видим стандартный код начала программы на MASM (На C, Delphi посмотрите в отладчике. Меняться будут ток адреса да параметры.):

Код:
PUSH 0
CALL Crackme.0040109C ; JMP to kernel32.GetModuleHandleA
MOV DWORD PTR DS:[404094],EAX
CALL Crackme.00401096 ; JMP to kernel32.GetCommandLineA
MOV DWORD PTR DS:[404098],EAX


aspack.txt
Код:
eob Break
/* 61h=popad, 75h=jnz, дальше всё как в предыдущем примере. */
findop eip, #6175#
bphws $RESULT, "x"
run
Break:
bphwc $RESULT
sto
sto
sto
log eip
ret


Пример с FSG 2.0 -> bart/xt:
Запускаем программу по F9. Break срабатывает, на этот раз всё сложнее прога остановилась на каком то mov, явно не конец распаковки, ещё F9 и ещё пока не окажемся здесь:

Код:
PUSH EAX
CALL DWORD PTR DS:[EBX+10]
XCHG EAX,EBP
MOV EAX,DWORD PTR DS:[EDI]
INC EAX
JS SHORT Crackme#.004001C2
JNZ SHORT Crackme#.004001D4
JMP DWORD PTR DS:[EBX+C]


Мне так кажется, что переход на OEP будет тут (JMP DWORD PTR DS:[EBX+C]). Ставим бряк на этот jump, а старый на esp-4 снимаем, это можно опятьтаки сделать в CommandBar'е. Command: hd esp-4. Жмём F9 и останавливаемся на jump, F8. Да, так оно и есть, и мы на OEP.

FSG.txt
Код:
/* FF630C - это наш jump на OEP*/
findop eip, #FF630C#
go $RESULT
sto
cmt eip, "OEP Reached !"
ret


Пример с PECompact 2.x -> Jeremy Collake:
Запускаем программу по F9. Однако бряк не срабатывает, прога останавливается, возникает исключительная ситуация, возможно это антиотладка( в данной статье нас это не интересует). Смотрим на вершину стека:

Код:
0012FFC0 0012FFF0
0012FFC4 7C816D4F RETURN to kernel32.7C816D4F
0012FFC8 7C910738 ntdll.7C910738


Просто прога ставит SEH. Не думаю, что нас что-либо может остановить. Shift-F9, Shift-F9, F9 и т.д, пока не найдём что-нибудь интересное. Меня заинтересовал следующий код:

Код:
POP EDX
POP ESI
POP EDI
POP ECX
POP EBX
POP EBP
JMP EAX ; Crackme#.00402000


Трейсим и действительно в eax OEP. Готово!!

PECompact.txt
Код:
// Author: hacnho/VCT2k4
// Website: http://nhandan.info/hacnho
// Что могу сказать !!? Читайте readme к OllyScript !!! =)
var CS
var CB
var Temp

sto
findop eax, #C3#
bp $RESULT
esto
esto

gmi eip, CODEBASE
mov CB, $RESULT
log CB

gmi eip, CODESIZE
mov CS, $RESULT
log CS

bpwm CB, CS
esto
sto
bpmc
findop eip, #FFE0#
mov Temp, $RESULT
bp $RESULT
esto
jmp exit

Return:
esto
jmp exit

exit:
cmp eip, Temp
jne Return
sto
log eip
cmt eip, "This is the OEP! Found by hacnho/VCT2k4"
MSG "Dumped and fix IAT now! Thanx for using my Script...!"
ret


Пример с winupack_029b
С ним всё было как то необычно, бряк сробатывал в ненужных местах. Затем прога вылетела. Shift-F9 и я оказался в какой то функции, F9 опять вылетела, далее опять в функцию, я посмотрел на стек и увидел там адрес функции GetCommandLineA. Проделал эти операции ещё раз, там появилась следущая функция, распаковывает или востанавливает (не разбирался) IAT, блеснуло у меня в голове, Жал Shift-F9,F9 пока не дошёл до ExitProcess, далее тассировал более аккуратно и вследствии вышел на OEP.

Можно было ещё разобрать пару пакеров, но у меня на компе больше нет. Но идея, думается мне, понятна.
Если Вы не поняли как находится OEP, то просто юзайте скрипты для Olly, благо в инете их много, для различных пакеров и протектеров.

б) Сбрасываем дамп
Итак мы перешли на OEP, что делать дальше?
Пока ничего не трогаем. Просто заходим в плагины > OllyDump > dump debugged process.
Жмём "Dump".
Всё готово!!! Можете пользоваться.

в) Восстановление импорта
Для восстановления импорта нам нужно узнать ImageBase. Запускаем LordPE, жмём PE Editor и выбираем нашу программу, смотрим ImageBase. Закрываем LordPE.
Запускаем нашу программу(всмысле запакованную), открываем ImpRec, выбираем нужный процес. В поле OEP пишем RVA OEP, подсчитаный по формуле представленной выше. Жмём IAT AutoSearch для автоматического поиска таблицы импорта, и видим сообщение, что скорее всего адрес найден. Теперь жмём Get Imports и видим, что в списке появлись используемые dll и функции. Жмём Fix Dump и выбираем наш дамп. Ну вот и всё.

Возможные вопросы:
Q: Половина функций в библиотеке опознана, а другая нет!! Что делать?
A: Выбери неопределённую функцию, и нажми на правую клавишу. Выбери "Disassembly / HexView", выделяй, например "comctl32.dll/002D/ImageList_Destroy", и теперь на обе клавиши. Выберай Get Import. Всё, функция определена, библиотека тоже.
Q: А если много таких функций?
A: Выбираем все, на правую клавишу, Trace level1 (Disassm). Результат налицо.
Q: А если всётаки не хочет определяться?
A: Выбираем любую функцию, на правую клавишу, Advanced commands > Get API Call > OK, теперь отсекаем всё не нужное.

5. Ссылки
cracklab.ru
wasm.ru (Об упаковщиках в последний раз)
_____________________________________________
Author: Taha
Date: 22.11.2006
Категория: Распаковка | Добавил: -=Hellsing=- (24.06.2009)
Просмотров: 5560 | Рейтинг: 5.0/2
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]