Содержание: 1. Вступление 2. Нам понадобится 3. Ликбез Стек Несколько слов о RVA Способы адресации4. Приступим к распаковке В поисках OEP Сбрасываем дамп Восстановление импорта5. Ссылки 1. Вступление Попав внутрь запакованной программы, начинающий cracker начинает волноваться и паникивать, ведь там так одиноко без WinAPI, а кругом вражески настроенные опкоды. И вроде незачто зацепиться, однако это не так, и распокавать программу на самом деле очень просто. Сегодня я расскажу о том как это делается. 2. Нам понадобится а) Отладчик OllyDbg и плагины к нему (CommandBar, OllyDump, OllyScript). б) Редактор PE-файлов - LordPE в) Программа для восстановления импорта - ImpRec 3. Ликбез а) Стек Чего я не знаю о стеке? - спросит читатель. Push - pop и все дела, ан нет, им можно пользоваться и более умело. В регистре esp храниться адрес вершины стека, тоесть параметра помещённого в стек последним. К примеру: Code: push 5h push 6h mov eax,dword ptr [esp] - после выполнения в eax будет 6h А как обратится к 5 через esp? Опятьтаки спросит читатель. Для начала мы сохраним esp в ebp, но так как ebp используем не мы одни, то нужно сохранить его. Code: push ebp mov ebp,esp - теперь в ebp адрес вершины стека Также необходимо понимать, что стек растёт от старших адресов к младшим. Это очень важно!!! Так как 5h было помещено раньше чем все остальное, то и адрес у него будет больше. Также мы знаем, что размер одного кадра стека равен 4h. Следовательно чтобы обратится к пяти надо к ebp прибавить 2h*4h. Code: 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, где будет следующий код: Code: 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 - это второстепенный вопрос, о котором я расскажу позже. в) Способы адресации Сейчас я хочу поговорить с вами о том, как передать управление в другую часть кода. Первый способ: Code: jmp metka metka: mov eax,metka jmp eax Code: Втрой способ: push metka retn metka: Третий способ: Code: call metka metka: Четвёртый способ: Code: stc jc metka metka: Пятый способ: Code: 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 срабатывает, и мы видим следующие инструкции: Code: popad jmp xxxxxxxx В этом месте программа восстанавливает все регистры и передаёт управление коду по адресу xxxxxxxx. Скорее всего это и есть переход на OEP. Проверим. Жмем F8 и попадаем сюда. Code: 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 Code: /* 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 Code: 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 срабатывает, и мы видим следующие инструкции: Code: 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 посмотрите в отладчике. Меняться будут ток адреса да параметры.): Code: 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 Code: 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 и ещё пока не окажемся здесь: Code: 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 Code: /* FF630C - это наш jump на OEP*/ findop eip, #FF630C# go $RESULT sto cmt eip, "OEP Reached !" ret Пример с PECompact 2.x -> Jeremy Collake: Запускаем программу по F9. Однако бряк не срабатывает, прога останавливается, возникает исключительная ситуация, возможно это антиотладка( в данной статье нас это не интересует). Смотрим на вершину стека: Code: 0012FFC0 0012FFF0 0012FFC4 7C816D4F RETURN to kernel32.7C816D4F 0012FFC8 7C910738 ntdll.7C910738 Просто прога ставит SEH. Не думаю, что нас что-либо может остановить. Shift-F9, Shift-F9, F9 и т.д, пока не найдём что-нибудь интересное. Меня заинтересовал следующий код: Code: POP EDX POP ESI POP EDI POP ECX POP EBX POP EBP JMP EAX ; Crackme#.00402000 Трейсим и действительно в eax OEP. Готово!! PECompact.txt Code: // 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