Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
выпущен 8 мая!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь


Распаковка протектора SafeDisk 4.6, часть 3




Массу крэкерских инструментов, видеоуроков и статей вы сможете найти на видеокурсе от нашего сайта. Подробнее здесь.

Распаковка SafeDisk 4.6 на примере

Launch Sid Meier's Civilization 4


ИНСТРУМЕНТЫ: SoftIce v4.3.2+IceExt v0.67, OllyDbg+plugun Olly advanced v1.26 beta 9, PETools v1.5, ImpREC v1.6.

VaZeR


ЧАСТЬ III. Восстановление импорта и эмулированные опкоды


Теперь нам надо восстановить импорт файла. Здесь в принципе нет, не чего нового по сравнению со 2.XX версией. Если посмотреть на наш дамп то можно увидеть, что есть нормальные вызовы, а также и те которые идут через переходник SafeDisk. Чтобы восстановить импорт достаточно будет использовать только OllyDbg. Для начала найдём размеры таблицы импорта. Запустив в OllyDbg наш защищенный файл. И дождавшись, когда игра уйдёт в бесконечный цикл. Перейдём на OEP и посмотрим на какие-нибудь вызовы API функции:

Safedisc Восстановление импорта и эмулированные опкоды

Самая первая API - это GetModuleHandleA. Обратим внимание на адрес B4E154. Перейдём на него в окне дампа и начнём смотреть, где эта таблица начинается и кончается.

Начало:

Safedisc Восстановление импорта и эмулированные опкоды

Адреса, начинающиеся с 01A9XXXX, ведут как раз на эти переходники, поэтому их нам тоже нужно учитывать в определении размера. То, что это затёртый импорт это можно узнать, просто взяв какой-нибудь адрес и перейдя на него посмотреть, куда мы попали в библиотеку приложения или в выделенную секцию. Посмотрев на карту памяти в OllyDbg.



Конец:

Safedisc Восстановление импорта и эмулированные опкоды

Конец таблицы можно легко найти, так как там идёт больше одного dword нулей. Получается, что:

Начало - B4E000

Конец - B4EB74

Теперь, чтобы начать изучать импорт, нам надо остановится в OllyDbg до того, как он зависнет, но и после того как секция кода расшифруется. Чтобы найти такое место я начал трассировать программу, начиная с EP и смотреть в дамп (d 401000), когда она расшифруется. Если в начале мы, проходя какой то CALL расшифровывали секцию и одновременно зависали, то ставим на этот адрес брекпоинт - Hardware, on execution. И в следующий раз заходим в него. Трассировать там не много. Я выбрал следующее место:

Safedisc Восстановление импорта и эмулированные опкоды

Пройдя CALL - 66704B34. Вся секция кода расшифровывается. Здесь и поставим наш брекпоинт - 66704B39.

Теперь посмотрим на нашу таблицу импорта и выберем какое-нибудь значение, которое не ведёт не к одной библиотеке. Далее перейдём на этот адрес (Я взял 1A92A0B):

Safedisc Восстановление импорта и эмулированные опкоды

Здесь у нас в CALL - 66784СDC передаются два параметра: BFEA12B0 (начинается всегда с BF) и 1A92A4B (адрес 1A92A0B + 40h). Поставим eip на адрес 01A92A0B (New origin here). И начнём трассировать код зайдя в CALL, код начиная с адреса 1A92A1D не когда не получит исполнения. Трассируем до выхода из этого CALL:

Safedisc Восстановление импорта и эмулированные опкоды

Здесь этот RET ведёт на API функцию GDI32 - GetDeviceCaps. Отсюда можно сделать вывод, что нам нужно написать такой код, который бы нашёл все неправильные адреса API, перешёл на него и дошёл выхода, как мы сейчас сделали, и заменил неправильный адрес в таблице правильным. Но так как в секции кода указатели на импорт тоже не правильные то нам необходимо создать временную таблицу импорта. Её мы расположим в секции stxt371. А вот секцию stxt774 трогать не будем, так как туда ведут LongJMP (это мы узнаем немного позднее). Наш код можно разместить в секции заголовка файла. Сам код я писал на FASM, а потом уже после компиляции вставлял в саму игру, просто открыв наш скомпилированный файл в другом OllyDbg и скопировав весь код, в секцию заголовка нашего защищенного приложения, после чего сохраняем наши изменения. Также трассируя код этого CALL, можно заметить две проверки на предмет уже восстановленных адресов API:

Safedisc Восстановление импорта и эмулированные опкоды

Это всегда сравнение с нулём. Здесь нам нужно изменить JE на JMP. Также есть ещё одна такая же проверка:

Safedisc Восстановление импорта и эмулированные опкоды

Здесь мы должны изменить JNZ на NOP. Это нужно делать по двум причинам: чтобы мы всегда попадали на одно и тоже место 66784F9E на выходе, а также, для того чтобы каждый раз вычислялся заново адрес API.


Для начала напишём код, который бы создал временную таблицу импорта:

 			.code
 			
start: mov eax,0B4E000h ;Кладём в EAX начало таблицы импорта mov byte[66784E01],0EBh ; mov word[66784F3A],9090h ;Патчим наши проверки mov byte[66784F99], 0E9h mov dword[66784F9A], 99C7B4DAh ; Делаем переход на наш код main: cmp eax, 0b4EB74h ; Проверяем не закончилась ли таблица импорта? je exit ; Если да то выходим mov ecx,dword[eax] ;Кладём в ECX адрес API cmp ecx,1A90000h ;Проверяем затёртый ли адрес импорта jbe add_API ; Переходим если нет cmp ecx,1AB9FFFh ja add_API jmp ecx ; В ECX находится затёртый адрес импорта на который мы и переходим @ mov esp, dword[ebp+C] ; Здесь мы восстанавливаем затёртые мнемоники popad popfd mov ecx, dword[esp] ; Кладём в ECX адрес API add esp, 4 ; Коррекция стека jmp add_API
add_API: ;Суда мы переходим если адрес нормальный add eax,29C000h ;Прибавляем к EAX поправку mov dword[eax],ecx ; Кладём адрес во временную таблицу импорта sub eax,29C000h ;Приводим в порядок EAX add eax,4 ; jmp main
exit: int3 ; После окончания работы кода мы остановимся здесь .end start

Примечание: Сначала мы патчим все проверки, а также делаем переход на адрес, который отмечен @, его мы узнаём уже, когда вставим код в секцию заголовка, также как и байты 99C7B4DA. В моём случае это выглядит так:

Safedisc Восстановление импорта и эмулированные опкоды

Это тот же конец только вместо команд mov esp, dword[ebp+C], popad, popfd, мы вставили JMP на адрес 400478 (в моём случае). Можно конечно вставлять его вручную, но нам ещё придётся менять его адрес, а это будет впоследствии не очень удобно.

Адреса 1A90000 и 1AB9FFF - это размеры секции, куда указывают наши неправильные значения импорта.

Так как я решил временную таблицу импорта расположить в секции stxt371, начиная с адреса DEA000. То у меня получилась следующая поправка: DEA000 - B4E000 = 29C000.


Теперь нам нужно исправить ссылки на импорт (начнём с самых распространенных, которые начинаются с FF15) так как некоторые из них неправильные. К примеру, возьмём:

Safedisc Восстановление импорта и эмулированные опкоды

Вызвав API c адреса 4088F3, а также с какого-нибудь другого адреса один и тот же указатель - 01A9FEBB, мы получим разные адреса API, это связано с тем, что существует всего три параметра, которые мы передаем протектору, и как раз третий это адрес возврата. Когда мы создавали временную таблицу импорта, то мы вообще не использовали его. Но, чтобы восстановить все ссылки на импорт нам нужно заходить на наши затёртые адреса импорта уже с нужным нам адресом возврата в стеке. После того как мы найдём опять адрес API, мы должны найти его во временной таблице импорта и в случае необходимости изменить ссылку на API. Только не на нашу временную таблицу, а с учётом поправки на настоящею. Теперь напишем соответствующий код:

 			 			start:
 			       			 mov dword[66784F9Ah],99C7B453h ; 			Изменим 			адрес 			JMP на 			@
 			       			 mov eax,401000h
 			 			main:
 			       			 cmp 			word[eax],15FFh 			; Ищем в секции кода опкод 15FF
 			       			 je proverka_FF15
 			 			        inc eax
 			 			        cmp eax, 0B4DFFFh
 			 			        je exit
 			 			        jmp main
 			 			
proverka_FF15: add eax,2 mov ecx, dword[eax] cmp ecx,0B4E000h ; Фильтруем не нужные нам значения jl main cmp ecx,0B4EB74h ja main mov edx,[ecx] cmp edx,1A90000h jl main cmp edx,1AB9FFFh ja main sub eax,2 jmp eax @ mov esp, dword[ebp+0Ch] popad popfd mov ecx,0DEA000h
poisk: mov edx,dword[ecx] ; Ищем полученный адрес API во временной таблице импорта cmp dword[esp],edx je write add ecx,4 cmp ecx,0DEAB74h je errol jmp poisk
write: sub ecx,29C000h ; В этом блоке записываем правильную ссылку add eax,2 mov dword[eax],ecx add eax,4 jmp main
errol: int3 ; Суда мы в принципе не должны попасть
exit: int3
.end start

Примечание: Знак @ просто метка, она ведена мной, чтобы понять на какой адрес мы должны перейти.

Исправив все CALL [Ссылка на API], начнём исправлять LongJMP. Они представляют из себя следующее:

Safedisc Восстановление импорта и эмулированные опкоды

Это JMP, который ведёт в секцию протектора stxt774. Если посмотреть ниже этого JMP, то можно увидеть странную команду: mov EAX, dword[5415FF50]. Это происходит из-за одного мусорного байта A1. Если вместо него вписать NOP, то увидим следующее:

Safedisc Восстановление импорта и эмулированные опкоды

Исходя из этого можно сказать, что этот JMP должен быть в конечном итоге преобразован в следующее:

Safedisc Восстановление импорта и эмулированные опкоды

Но нам ещё нужно учесть, что у нас может JMP занимать 5 байт, а не 6. Тогда он должен быть преобразован к виду:

CALL адрес

Адрес JMP [API]

В этом случае, узнав адрес API, мы должны найти в секции кода соответствующий JMP[API]. И поставить CALL c адресом на него. Но в моей программе не оказалось JMP c 5 байтами. Количество байт мы будем определять по адресу возврата. Теперь посмотрим, что находится по адресу DE7527:

Safedisc Восстановление импорта и эмулированные опкоды

Заходим в CALL по адресу DE7527:

Safedisc Восстановление импорта и эмулированные опкоды

Выходим через RET и попадаем вот сюда:

Safedisc Восстановление импорта и эмулированные опкоды

Сначала мы кладём адрес возврата - 40ABF5. А далее, уже попадаем на знакомую нам процедуру получения адреса API. Получается, что мы должны написать код, который бы искал все JMP в секцию протектора. Далее находил бы адрес API, а также сравнивал адрес возврата с 5 и 6. Если он не соответствует этим числам, то останавливаемся и разбираемся вручную. Ну а когда все нормально, то изменяем наш LongJMP соответствующим образом. Но есть одна сложность с поиском этих самых JMP. Мы должны сначала найти байт E9. Далее проверить, куда указывает адрес. Сам адрес здесь высчитывается следующим образом:

Непосредственно сам адрес JMP + смешение (то число, что после байта E9) + 5

Пример: 40ABEF + 9DC933 + 5 = DE7527


После того как мы проверим адрес, указывает ли он на нашу секцию протектора. Мы всё равно не можем точно быть уверенными, что мы имеем действительный LongJMP. К примеру, можно взять вот этот кусок кода:

Safedisc Восстановление импорта и эмулированные опкоды

Если подсчитать выделенные байты, то получится, что у нас есть JMP DE7160. А этот адрес как раз попадает, под наше описание. Поэтому, чтобы лишний раз не зависать в программе ведём ещё одну проверку первых двух байт по нашему адресу (E8 53). И только если и эта проверка прошла успешно, то начнём находить API.

Теперь реализуем все выше сказанное в коде:

 			.code
 			
start: mov dword[66784F9Ah],99С7B54Dh ; Корректируем наш JMP на @ mov eax,401000h main: cmp byte[eax],0E9h je proverka inc eax cmp eax,0B4DFFFh je exit jmp main
proverka: inc eax mov ecx, dword[eax] add ecx, eax add ecx, 4 ;Увеличиваем на 4, а не на 5. т.к. уже увеличили до этого EAX на 1 cmp ecx,0DE7000h jl main cmp ecx,0DEA000h ja main cmp word[ecx],0E853h ; Наша дополнительная проверка jnz main dec eax jmp eax @ mov esp, dword[ebp+0Ch] popad popfd mov edx,dword[esp+4] ; Поэту адресу находится адрес возврата sub edx, eax cmp edx,5 je JMP_5 ; Переходим если у нас 5 байтов cmp edx,6 jnz errol ; если количество байтов другое то идём на выход mov word[eax],15FFh ; вписываем CALL add eax,2 mov edx,0DEA000h mov ebx,dword[esp]
poick_iat: cmp dword[edx],ebx ; Ищем во временной таблице импорта подходящий адрес je write add edx,4 cmp edx,0DEAB74h je errol ; jmp poick_iat
write: sub edx,29C000h mov dword[eax],edx ; Изменяем на правильную ссылку к импорту add esp,8 ; Коррекция стека jmp main
errol: int3
JMP_5: int3 ; Если попали сюда, тогда в коде есть пяти байтовые LongJMP и придётся дополнительно дописывать код
exit: int3
.end start

Примечание: Код, преобразующий пяти байтовые LongJMP, я не стал приводить код, так как у меня, их нет, а значит, я могу, что-то опустить в реализации.


Но мы рассмотрели ещё не все вызовы API, в моём случае ещё используется вызов через регистры:

Safedisc Восстановление импорта и эмулированные опкоды

В EBP сначала ложится адрес API, а потом уже командой CALL EBP она и вызывается. Может использоваться не только регистр EBP, а также и другие. Для начала нам нужно написать код, который бы находил бы эти места. Сначала будем искать адрес затёртого API в секции кода. Когда найдем, проверим, ложится ли он в регистр. Если да, то начинаем искать ниже CALL регистр. Ну а далее опять находим адрес API, как мы это уже делали. И корректируем команду - mov регистр, dword[адрес].

Вот соответствующий код:

 			.code
 			
start: mov dword[66784F9Ah],99C7B67Ah ; Изменяем адрес на новый @ mov eax,401000h
poick_1: inc eax mov edx, dword[eax] cmp eax,0B4DFFFh je exit cmp edx,0B4E000h ; Фильтрация значений jl poick_1 cmp edx,0B4EB74h ja poick_1 mov ecx,0B4DFFCh
poick_2: add ecx,4 cmp ecx,0B4EB74h je poick_1 mov ebx, dword[ecx] cmp ebx,1A90000h ; Находим затёртый импорт jl poick_2 cmp ebx,1AB9FFFh ja poick_2 cmp edx,ebx ; Сравниваем наше значение с импортам je poick_3 jmp poick_2
poick_3: sub eax,2 cmp word[eax],15FFh ; Проверяем не CALL ли это? jnz poick_4 add eax,6 jmp poick_1
poick_4: cmp word[eax],2D8Bh ; Проверяем какой регистр используется - EBP je EBP_reg cmp word[eax],0D8Bh ; ECX je ECX_reg cmp word[eax],158Bh ; EDX je EDX_reg cmp word[eax],1D8Bh ; EBX je EBX_reg cmp word[eax],358Bh ; ESI je ESI_reg cmp word[eax],3D8Bh ; EDI je EDI_reg inc eax cmp byte[eax],0A1h ; EAX je EAX_reg add eax,4 jmp poick_1 ; Если там, что то другое идём искать дальше
EBP_reg: mov edx,0D5FFh ; Устанавливаем соответствующие байты опкодов jmp poick_6 ECX_reg: mov edx,0D1FFh jmp poick_6 EDX_reg: mov edx,0D2FFh jmp poick_6 EBX_reg: mov edx,0D3FFh jmp poick_6 ESI_reg: mov edx,0D6FFh jmp poick_6 EDI_reg: mov edx,0D7FFh jmp poick_6 EAX_reg: mov edx,0D0FFh dec eax jmp poick_6
poick_6: mov esi,eax add esi,6 poick_7: cmp word[esi],DX ; Ищем команду CALL регистр je opr_API inc esi cmp esi,0B4DFFFh je errol jmp poick_7
opr_API: add esi,2 push esi jmp ebx @ mov esp, dword[ebp+0Ch] popad popfd mov edx,0DEA000h
poick_iat: mov esi,dword[esp] ; Ищем во временной таблице импорта адрес cmp dword[edx],esi je write cmp edx,0DEAB74h je errol add edx,4 jmp poick_iat
write: add eax,2 sub edx,29C000h mov dword[eax],edx ; корректируем вызов API add eax,4 add esp,8 jmp poick_1
errol: int3
exit: int3
.end start


Теперь нам осталось только исправить эмулированные опкоды. К примеру, вот один из вызовов:

Safedisc Восстановление импорта и эмулированные опкоды

По команде JMP EAX мы перейдём в секцию протектора. Код с адреса 4027A0 некогда не получит исполнения. Чтобы попасть на такое место лучше воспользоваться бинарным поиском по сигнатуре (FF E0). Теперь чтобы понять, как работает этот защитный механизм нам нужно по исследовать его. Мы не можем просто поставить eip на адрес 402793 и начать трассировать. Нужно найти CALL с помощью которого мы попадаем сюда. Ставим указатель на 402793 и Find references to => Selected command.

Найдётся всего один CALL:

Safedisc Восстановление импорта и эмулированные опкоды

Перейдём на него:

Safedisc Восстановление импорта и эмулированные опкоды

Далее ставим указатель на адрес 4010A9 и опять ищем CALL, который ссылается на этот адрес. А вот тут мы получим около сотни ссылок. И все они ведут к одному и тому же JMP EAX.

Перейдём на какой-нибудь из этих адресов:

Safedisc Восстановление импорта и эмулированные опкоды

Здесь мы видим обычный CALL XXXXXXXX. Ставим на него eip и начинаем идти вглубь протектора, через JMP EAX. Сначала попадаем на следующий код, который похож на тот, который стоит перед кодом восстановления импорта:

Safedisc Восстановление импорта и эмулированные опкоды

Начинаем трассировать код протектора. Пока не дойдём до выхода (Примечание: Лучше всего трассировать код через F7, кроме самих вызовов API). Кстати выходим мы тоже своеобразно через стек:

Safedisc Восстановление импорта и эмулированные опкоды

Safedisc Восстановление импорта и эмулированные опкоды

Откуда мы попадаем уже в секцию кода:

Safedisc Восстановление импорта и эмулированные опкоды

Получается, что мы попали на JMP [API]. Теперь если взглянуть обратно на адрес 4011F8 то увидим следующее:

Safedisc Восстановление импорта и эмулированные опкоды

Вместо адреса 4010A9 он поменялся на правильный. А это значит, что нам нужно написать код, который нашёл бы все вызовы CALL 4010A9, заходя в каждый, а внутри процедур протектора нам нужно найти место, где бы мы поставили JMP адрес на наш патч. Но перед этим мы должны ещё запомнить текущее значение регистра ESP, это нужно сделать пока мы ещё стоим на адресе 99B850, для последующей корректировки. У меня там находится - 12AB0C.

Я решил поставить его здесь:

Safedisc Восстановление импорта и эмулированные опкоды

Если мы стоим на этом CALL и посмотрим на CALL, откуда мы зашли то увидим, что он ужё восстановился. Но тут может быть много всяких разных вариантов. Также ещё нужно сразу сказать, что у меня только одна команда JMP EAX в программе. А это может быть не всегда так. Поэтому лучше проверить внимательно всю секцию. Ну и также нельзя быть уверенным на 100% что вызов происходит только через регистр EAX, а не через какой-нибудь другой. Значит нужно проверить вызовы через другие регистры. Ещё можно сказать, что не всегда у CALL просто сменяется только адрес на JMP [API], может быть и такой случай:


До:

Safedisc Восстановление импорта и эмулированные опкоды

После:

Safedisc Восстановление импорта и эмулированные опкоды


Рассмотрев работу этого кода, напишем теперь наш:

 			.code
 			
start: mov byte[6676142Dh],0E9h ; Вставляем в процедуре протектора JMP на адрес @ mov dword[6676142E],99C9F265 mov eax,401000h
poick_call: cmp byte[eax],0E8h je poick_adr inc eax cmp eax,0B4DFFFh je exit jmp poick_call
poick_adr: inc eax mov ecx, dword[eax] add ecx,eax add eax,4 ; Прибавляем 4 потому что уже увеличили EAX на 1 cmp ecx,4010A9h je main jmp poick_call
main: push eax jmp eax @ mov esp,12AB0Ch pop eax add eax,5 jmp poick_call
exit: int3
.end start


Ассемблировав весь этот код и все другие, и расположив их в секции заголовка, нужно ещё обязательно настроить адреса и байты для пропатчивания. А также можно соединить все эти куски кода в один, т.е. конец первого кода - JMP - на начало следующего и т.д. Также перед запуском нужно обязательно скопировать в наш файл, с кодами по восстановлению импорта, секцию кода с восстановленными наномитами. Просто открыв в другом OllyDbg дамп, полученный во второй части и сделать следующее: Copy - Select all => Binary - Binary copy, на вопрос об слишком большом размере ответить «ДА». И вставить в наш файл Copy - Select all => Binary - Binary paste. Проделав всё это ставим eip на самое начало нашего кода и запускаем его, остаётся только дождаться окончания работы кода, вставить временную таблицу импорта на место настоящей и сдампить в PETools. Также открыть ImpREC выбрать процесс, вести OEP, нажать IAT AutoSearch и обязательно проверяем RVA и размер импорта, если всё сходится то Get Imports. Смотрим всё ли правильно определилось, нажав на Show Invalid и Сut thunk(s). Пока не будем править дамп, а просто сохраним Save Tree в какой-нибудь файл. Сейчас нам нужно будет ещё подправить наш дамп, а именно удалить две последние секции протектора:


Safedisc Восстановление импорта и эмулированные опкоды


После чего фиксим наш дамп в ImpREC. Ну и в конце можно сделать ещё и ребилд файла. Вот здесь мне PETools не смог помочь. Рабочий файл вышел только в LordPE. Также перед ребилдом можно удалить строку с версией SafeDisk из секции заголовка файла, этим мы уменьшим его размер, так как эта строка нам мешает это сделать.

Проделав всю эту работу, мы восстановили импорт файла, указатели к нему, а также эмулированные опкоды, но этого оказывается все равно мало, существуют ещё защитные механизмы, которые мы рассмотрим в следующей части.

Также хочется сказать, что эмулированные опкоды могут не всегда восстанавливаться сами. В моём случае видимо использовалась облегченная версия этого защитного механизма. Который возможно носит название SDAPI v1.





Скачать статью "Распаковка протектора SafeDisk 4.6, часть 3" в авторском оформление + файлы.
пароль архива на картинке



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS