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

ВИДЕОКУРС ВЗЛОМ
выпущен 12 ноября!


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

Распаковка и регистрация Net Vampire 4

Обсудить статью на форуме

Очень удобно, когда все крэкерские инструменты, книги и статьи в одном месте. Используйте сборник от EXELAB - вот тут.

Используемый инструментарий:

SoftICE & APIMon

для отладки и исследования

ProcDump

для снятия дампа

Revirgin

для восстановления импорта

PEIDentifier

для распознавания большинства упаковщиков, протекторов и компиляторов для PE файлов

Воспользовавшись программой PEIdentifier, я убедился, что она запакована “ASProtect 1.2x [New Strain] -> Alexey Solodovnikov”. Работа будет нелегкой.

Мало того, что ASProtect пакует программу и мне придется искать то место, где начинает работать программа, так он еще затрудняет ее поиск, применяя всяческие антиотладочные приемы.

Вообще ASProtect работает таким образом, что сначала начальный распаковщик распаковывает тело протектора, затем передает ему управление. Протектор распаковывает программу, выполняет необходимые действия и передает управление программе.

Ну, что же, когда-то надо начинать. У меня есть небольшой опыт в написании программ под WIN32, и я знаю, что при создании диалоговых окон, нужно значение, которое возвращает функция GetModuleHandleA. К тому же, это одна из первых функций, вызываемых программой, что мне несравненно играет на руку. Т.е. если мы найдем, где эта функция вызывается первый раз из NetVampire (далее программа), то мы будем рядом с OEP (Original Entry Point – оригинальная точка входа в программу; если бы не было ASProtect, то программа начиналась выполняться именно отсюда). Я стал отлавливать в программе то место, когда её вызовет не ASProtect, а NetVampire, адрес должен был начинаться с 4ххххх, но не как не с 9ххххх или с Аххххх. Для этого я поставил бряк на функцию GetModuleHandleA (bpx GetModuleHandleA). Но прерываний было слишком много, я устал жать клавишу F5 L . Поэтому, я пошел другим путем: запустил APIMon. В меню выбрал Monitor\spy\Add API…, ModuleName = kernel32.dll, APIName = GetModuleHandleA. Однако, иногда APIMon вел себя странно и не отлавливал все вызовы GetModuleHandleA, а только несколько десятков (должно быть 2-3 тысячи). В этом случае не пишите название библиотеки kernel32.dll и название функции GetModuleHandleA, а нажмите Export и выберите библиотеку kernel32.dll вручную, затем в появившемся списке выберите GetModuleHandleA, нажмите Add и затем OK. Для мониторинга, я нажал F5 и выбрал vampire.exe. Дождавшись появления окна, я закрыл программу NetVampire, чтобы больше не появлялись события, а то их и так больше 3 тысяч. Теперь моя задача – найти первый вызов функции GetModuleHandleA из модуля vampire.exe. Первый вызов функции GetModuleHandleA из программы должен принимать параметр 0 и возвращать значение ImageBase, т.е. в нашем случае 400000. Это справедливо для более чем 90% Windows-программ. Поэтому, используя функцию поиска, я нашел такое событие, в котором в стеке лежали бы адреса модуля Vampire.exe и возвращаемое значение было бы 400000. Я нажал на кнопку поиска и ввел 400000. И только четвертое вхождение увенчалось успехом. Логично, что найдя это место, где первый раз вызывается функция GetModuleHandleA из программы, а не из тела протектора, то я окажусь рядом с OEP.

Вот как выглядит найденный вызов в APIMon:

После найденного вызова я переписал все адреса в стеке, которые относились к модулю vampire.exe.

Затем в SoftICE поставил бряки на эти адреса, использовав команду bpm “addr” x (где “addr” это то, что закрашено серым цветом). BPX отпадает, т.к. физически SoftICE ставит команду INT3 по текущему адресу, что нарушает целостность программы. Об этом нам напоминает ASProtect, выдавая ошибку №15 при запуске. А команда bpm x целостности программы не нарушает и ASProtect не догадывается, что мы поставили бряк. Ключ Х означает, что бряк сработает, когда должна будет выполниться команда по указанному адресу. Для начала мне надо попасть в контекст программы. Ставлю бряк на функцию GetModuleHandleA (bpx GetModuleHandleA), и жму F5, пока в нижнем правом углу не появится имя модуля (Vampire). Все теперь можно ставить бряки на два адреса, которые были получены из APIMon (bpm 403774 x, bpm 4037CA x).

Запустил заново, не забыв отключить бряк на GetModuleHandleA, чтобы он мне не мешал. Сработал бряк по адресу 004037СА. Ниже была пара команд, а затем RET. Поэтому я решил протрейсить (идти пошагово, с помощью F10; можно также и F12 для выхода из функции, но не всегда она работает) программу дальше, и найти её "верхушку" – самую верхнюю процедуру, выше которой нельзя подняться. И в итоге после двух выходов из функций (например после двух нажатий F12) я оказался на адресе 4AD349. Ниже идет куча вызовов (CALL'ов), а по адресу:

4AD538 INVALID

Обычно так можно определить что это самая главная процедура, на которую передает управление загрузчик (в моем случаи ASProtect) и единственный выход из нее, это завершения процесса, т.к. никаких RET’ов в конце нет.

004AD333:

0078CE

add [eax][-0032],bh

004AD336:

4A

dec edx

004AD337:

00558B

add [ebp][-0075],dl

004AD33A:

EC

in al,dx

004AD33B:

83C4F4

add esp,-00C

004AD33E:

53

push ebx

004AD33F:

B8A0CE4A00

mov eax,0004ACEA0

004AD344:

E84B88F5FF

call .000405B94

004AD349:

E86A6AFCFF

call .000473DB8

Где-то тут должно быть начало (OEP), т.к. работа с портом (команда in) не к месту. К тому же большинство программ начинаются с push ebp (байт 55h), по которому можно ориентироваться, но push ebx смотрится правильной командой, и чтобы узнать какие адреса выполнялись до нее (для нахождения OEP), отключаю все старые бряки, ставлю новый по адресу 04AD33E (bpm 04AD33E x) и перезапускаю программу. Когда происходит прерывание по этому бряку, SoftICE пишет, на каком адресе мы были до того, как попали на этот адрес. Это можно видеть из следующих строчек в окне СофтАйса:

MSR LastBranchFromIp=0A36306
MSR LastBranchToIp=04AD338

Значит по адресу 04AD338 тоже инструкция, опять удаляю старый бряк, добавляю новый на адрес 04AD338 и перезапускаю программу. Получаю сообщение:

MSR LastBranchFromIp=0A3493E ; этот адрес может быть другим!
MSR LastBranchToIp=04AD338

Гляну что там по адресу 0А3493Е (u 0А3493Е).

0A3493E

jmp eax

; переход на Original Entry Point (OEP)

Этот адрес не принадлежит модулю (иначе было бы написано над окном кода в айсе имя секции + смещение), значит, его создал распаковщик, и это явный переход от распаковщика к программе. Таким образом OEP = 4AD338 !

Для снятия дампа надо иметь девственную программу, где переменные не только не заданы, но и не инициализированные. Для этого её надо зациклить на адресе 0A3493E (этот адрес, как я уже сказал, меняется и может быть другим). Чтобы зациклить программу, в тот момент, когда мы прервались по бряку и находимся на адресе 4AD338, вводим в СофтАйсе следующие команды:

R eip A3493E

; вы вводите свой адрес!

A eip

; начать ввод опкодов

Jmp eip

; зацикливаем на себя

<Enter>

 
<F5>  

Затем запускаю ProcDump для снятия дампа. Нахожу процесс vampire.exe в списке, далее кликаю правой кнопкой на его пути, потом в меню – Dump(Full). Ввожу любое имя файла для сохранения дампа. И убиваю процесс (в ProcDump’e правой кнопкой мыши (далее ПКМ) по процессу и выбираем kill task), т.к. зацикленный процесс занимает все свободное процессорное время :( .

Теперь надо восстановить таблицу адресов импорта. Так как таблица импорта уже инициализирована, то все ссылки из нее жестко прописаны на конкретные адреса функций, и на другой машине могут и не работать, если библиотека имеет другой build. Тем более таблицу импорта необходимо восстановить еще и потому, что протектор заменил адреса некоторых API-функции на адреса внутри себя, где эмулируются эти функции. Когда же мы распаковали программу, то адреса в тело протектора нужно заменить адресами исходных API-функций, т.к. протектор уже отключен и эти адреса не существуют, память не инициализирована. В ЕХЕ файле вместо реальных адресов, стоят ссылки на текстовые имена функций, которые потом в процессе инициализации заменяются адресами на начало функции. Поэтому надо вместо адреса на начало функции, поменять адрес имени функции. Можно это делать вручную, но это долго и муторно, да и незачем - лучше использовать специальные утилиты.

Отрубаю все бряки в айсе, пока они мне не понадобятся, и запускаю vampire.exe и программу Revirgin. Выбираю процесс vampire.exe. Программа ругается, что таблица импорта плохая и просит ввести ОЕР. Если ввести тот, что мы получили, она почему-то зависает L , поэтому я оставил тот, что по умолчанию - 401000. Жму Fetch IAT, она находит импорт и его длину. Потом кнопку IAT Resolved, немного подумав, программа выплевывает список всех импортируемых процессом функций. Потом нажал Resolve again, чтобы функции модуля kernel32, которые перенаправлены, определились (в принципе если непонятно, то надо практически всегда потом нажимать Resolve again). Осталось 8 неопределенных функций, которые ссылаются на адреса распаковщика. Вот эти адреса: A20DBC, A21210, A2122C, A2125C, A21264, A21270, A21280, A21290. Неплохо бы узнать, что эти функции возвращают, чтобы заменить их на реально существующие. Параметры функции передаются через стек, а ответ функция возвращает в регистре ЕАХ. Идем дальше.

Листинг можно получить в айсе (команда u). Но сначала надо сделать наш процесс текущим (для этого запускаем программу, затем в СофтАйсе вводим (addr vampire), и затем “u xxxxxx”, где xxxxxx – один из вышеуказанных адресов распаковщика (протектора))

0A20DBC:

55

push ebp

 

0A20DBD:

8BEC

mov ebp,esp

 

0A20DBF:

8B550C

mov edx,[ebp][0000C]

; второй параметр, переданный функции

0A20DC2:

8B4508

mov eax,[ebp][00008]

; первый параметр, переданный функции

0A20DC5:

8B0D2454A200

mov ecx,[000A25424]

 

0A20DCB:

8B09

mov ecx,[ecx]

 

0A20DCD:

3BC8

cmp ecx,eax

 

0A20DCF:

7509

jne 000A20DDA

; переход

0A20DD1:

8B04955053A200

mov eax,[000A25350][edx]*4

 

0A20DD8:

EB07

jmps 000A20DE1

 

0A20DDA:

52

push edx

 

0A20DDB:

50

push eax

 

0A20DDC:

E87F43FFFF

call kernel32!GetProcAddress

; <=

0A20DE1:

5D

pop ebp

 

0A20DE2:

C20800

retn 00008

 

Понять, что это за эмулируемая функция, можно, если прерваться на адресе A20DBC и проследить, как будет выполняться программа. В конце концов, я приду на адрес A20DDC и выполнится функция GetProcAddress, которая вернет адрес запрашиваемой функции в регистре eax и с тех пор, eax больше меняться не будет. Значит это так и есть функция GetProcAddress.

0A21210:

55

push ebp

 

0A21211:

8BEC

mov ebp,esp

 

0A21213:

8B4508

mov eax,[ebp][00008]

; первый параметр, переданный функции

0A21216:

85C0

test eax,eax

; проверка на неравенство нулю

0A21218:

7507

jne 000A21221

; если не равен 0, то прыгаем

0A2121A:

A17469A200

mov eax,[000A26974]

; eax=400000

0A2121F:

EB06

jmps 000A21227

; переход

0A21221:

50

push eax

 

0A21222:

E8313FFFFF

call kernel32!GetModuleHandleA

; <=

0A21227:

5D

pop ebp

 

0A21228:

C20400

retn 00004

 

GetModuleHandleA(0)=400000. Значение по адресу А26974 было получено, где-то ранее, и по ходу выполнения программы оно не меняется, поэтому вызов функции не требуется. А если параметр не равен 0, то вызывается функция GetModuleHandleA. Так пусть она будет вызываться всегда.

Это замаскированная функция GetModuleHandleA.

0A2122C:

6A00

push 000

 

0A2122E:

E8253FFFFF

call 000A15158

; не важно что за функция

0A21233:

FF35E06CA200

push d,[000A26CE0]

; еах все равно изменит след. команда

0A21239:

58

pop eax

; eax=[000A26CE0]=0A280105=GetVersion()

0A2123A:

C3

retn

 

Это замаскированная функция GetVersion. Как я это узнал, да очень просто ;)

Ставлю бряк на GetModuleHandleA (bpx GetModuleHandleA), что бы попасть в контекст программы. Стираю бряк и ставлю новый на первый адрес программы - 401000 (bpm 401000 x). Жму F5, и после того, как программа запустилась, я ее перезапускаю. Сработает бряк на адресе 401000. Теперь я всегда буду останавливаться в этом месте и могу добавлять новые бряки. Дело в том, что все бряки, которые я ставлю на адреса А2хххх, после выхода из программы пропадают L . Теперь надо найти то место когда заполняют ячейку по адресу А26СЕ0.

Ставлю бряк на чтение/запись с этого адреса (bpm A26CE0) и когда он срабатывает вижу следующие строки:

MSR LastBranchFromIp=0A210E5

MSR LastBranchToIp=0A210E7

0A210E5

 

ret

 

0A210E7

 

mov [ebx-0A], eax

; eax=0A280105

Удаляю все бряки, ставлю новый на адрес 401000 (bpm 401000 x) и перезапускаю программу. Когда прерываюсь, удаляю либо просто отключаю (потом можно включить) бряк, по которому только что прервались и ставлю бряк на адрес 0A210E5 (bpm 0A210E5 x). Первый раз меня выкинуло, когда ЕАХ=400000, не то число, должно быть 0А280105, жму (F5) и вижу:

MSR LastBranchFromIp=0A21072

MSR LastBranchToIp=0A210DE

0A21072

 

jmp 0A210DE

 

0A210DE

 

push 0A210E7

Опять делаю, что надо чтобы прерваться на адресе 401000, после чего ставлю бряк на адрес 0A21072 (bpm 0A21072 х). И когда бряк срабатывает, вижу:

MSR LastBranchFromIp=77E7C486

MSR LastBranchToIp=0A2106D

77E7С486

 

ret

; kernel32!GetVersion

 

 

0A2106D

 

push 0A21075

 

По адресу 77E7C486 находится последняя команда функции GetVersion, с тех пор значение ЕАХ не менялось и попало в ячейку по адресу А26СЕ0. Значит это функция GetVersion.

Смотрим дальше:

0A2125C:

A1E46CA200

mov eax,[000A26CE4]

; eax=[000A26CE4]=FFFFFFFF

0A21261:

C3

retn

 

Это замаскированная функция GetCurrentProccess=FFFFFFFF. Аналогично тому, как мы находили GetVersion, ставим бряк на адрес 0А26СЕ4 (bpm 0А26СЕ4). Вылезает айс по адресу А210ЕА. Айс пишет, с какого адреса перешли на этот – а именно …ToIP=0A210E7. Ставим бряк туда (bpm 0A210E7 x). Оттуда на 0A210E5, затем на 0A210DE, 0A210D9, 0A210D4. При последнем возврате …ToIP=последняя команда функции GetCurrentProccess.

0A21264:

E8073FFFFF

call kernel32!GetVersion

; камуфляж

0A21269:

A1EC6CA200

mov eax,[000A26CEC]

; eax=[000A26CEC]=ProccessID

0A2126E:

C3

retn

 

Видно, что хоть и вызывается функция GetVersion, которая возвращает значение в eax, но все равно, следующей командой значение в eax перезаписывается. Число подозрительно маленькое. Вообще из опыта известно, и вы запомните, что ASProtect часто в числе функций, которые он маскирует, маскирует и GetCurrentProcessID а такое небольшое относительно число, которое записалось в eax, напоминает ID процесса. Набираем команду “addr” и в списке имен процессов ищем vampire. Находим и видим, что в колонке PID, напротив vampire, как раз находится то же число, которое и записывается в регистр eax в рассматриваемой нами функции (или ? PID, находясь в контексте программы). Значит эта функция возвращает в eax ID процесса, а этим занимается функция GetCurrentProcessID, хотя конечно никто не мешал мне опять делать рутинную работу по отлову места сохранения этой переменной как в случае с GetVersion.

0A21270:

6A00

push 000

 

0A21272:

E8E13EFFFF

call 000A15158

 

0A21277:

FF35F06CA200

push d,[000A26CF0]

 

0A2127D:

58

pop eax

; eax=[000A26CF0]=142360=GetCommandLineA

0A2127E:

C3

retn

 

Хоть и вызывается какая-то функция call A155158 , но значение eax потом все равно меняется. Сначала в стек ложится какое-то значение, хранящееся по адресу А26CF0, и потом это значение восстанавливается из стека в регистр eax. Смотрю, что у нас в eax, и вижу некое значение. Скорее всего это какой-то адрес в памяти. Смотрю, что лежит по адресу eax (“d eax”), и вижу, что это путь к файлу vampire.exe, а точнее командная строка. Адрес командной строки возвращает функция GetCommandLineA , значит это и есть замаскированная функция GetCommandLineA. Если вас что-то смущает – используйте предыдущий метод =))

 

0A21280:

55

push ebp

 

0A21281:

8BEC

mov ebp,esp

 

0A21283:

E8983EFFFF

call kernel32!GetCurrentProcess

 

0A21288:

8B4508

mov eax,[ebp][00008]

; первый параметр, переданный функции

0A2128B:

5D

pop ebp

 

0A2128C:

C20400

retn 00004

 

Эта функция возвращает то же самое, что и принимает. Т.е. это как бы пустышка. Почему? Давайте разберемся. Перед вызовом данной функции в стек помещается передаваемый параметр. Когда эта функция вызывается с помощью команды call, то в стек еще помещается адрес возврата, потом уже в функции по адресу 0A21280 в стек еще помещается ebp.

Теперь, с учетом смещения указателя стека, в стеке по адресу esp будет лежать ebp, по адресу esp+4 будет адрес возврата, и по адресу esp+8 – передаваемый параметр. Mov eax, [ebp][0008] это то же самое, что и mov eax, [ebp+8], а так как выше мы приравняли ebp к esp , то это тоже самое что и mov eax, [esp+8], значит в eax помещается передаваемый параметр. А так как в eax помещается значение, возвращаемое функцией, то получается, что функция возвращает тот же параметр, который ей и передали. Т.е., как я уже сказал, это функция-пустышка. Такие функции заменяются функцией LockResource.

0A21290:

55

push ebp

0A21291:

8BEC

mov ebp,esp

0A21293:

E8D83EFFFF

call kernel32!GetVersion

0A21298:

5D

pop ebp

0A21299:

C20400

retn 00004

Это замаскированная функция GetVersion.

В итоге я разгадал все функции. Осталось в Revirgin’е вписать адреса реальных функций, вместо тех, что не определились. Для получения реальных адресов достаточно написать в айсе exp <ExportFunct>. Например:

exp GetVer

 

KERNEL32

 

001B:77E7C486

GetVersion

001B:77E7C657

GetVersionExA

001B:77E7C61c

GetVersionExW

77Е7С486 и надо будет вписывать.

Для внесения изменений нажал ПКМ и выбрал меню EDIT, а в колонке адрес, где до этого были адреса Аххххх, вписал адреса распознанных API-функций. Например, вместо A21290 я вписал 77E7C486 – адрес функции GetVersion, и так со всеми адресами. Затем жму кнопку generate и выбираю дамп (файл, который создали, когда делали dump в ProcDump’е). Осталось сменить ЕР на ОЕР. В ProcDump’e жму кнопку PE Editor, выбираю дамп, и в поле Entry Point, ввожу новое значение, а именно 0AD338 = (4AD338-400000).

С радостью запускаю и … облом, ошибка чтения по адресу 0А13861. Дело в том, что раньше эти адреса занимал распаковщик, а сейчас там его нет. Запускаю оригинал с целью посмотреть что там есть (делаю процесс текущим, как я объяснял выше и пишу команду “d A13861”), а там 0 L . Ну тут опять мне помог предыдущий опыт использования и исследования ASProtect, в особенности его примеры; ASProtect предоставляет кроме защиты ЕХЕ, еще 4 функции: ASProtect Key, Keygen, Trial, User Key, с примерами. В нашем случае, в программе NetVampire защита построена с помощью функции ASProtect’a – Trial. В этой функции защита построена на ненадежном способе (если бы не запакованный файл), когда проверка на регистрацию продукта сводится к проверке длины строки, которая возвращает функция GetRegistrationInformation. За эту функцию отвечает сам ASProtect. Если строка пусто (т.е. первый байт = 0), то продукт не зарегистрирован. Вся регистрация сводилась к тому, что вместо адреса 0А13861, я подсунул другой, в теле программы, и по тому адресу написал свое имя :) . Этим я сделал 2 дела: теперь программа запускалась, т.к. обращение по адресу A13861 уже не происходило, и ошибки не возникало, и программа думала, что она зарегистрирована. Вообще надо найти такое место в программе, где было бы много нулей, и если туда, что-то вписать, то это не повлияет на работу программу. Такие места обычно бывают в конце секций, в конце файла. Надо найти такое место, вписать там свое имя и указать этот адрес вместо A13861. Я использовал в качестве такого места – участок по адресу 4AD55B (AD55B в дампе). Приведу листинг:

4A4FD2:

89903C060000

mov [eax][00000063C],edx

 

4A4FD8:

A1B8F94A00

mov eax,[0004AF9B8]

; еах = 4AEDF0

4A4FDD

8B00

mov eax,[eax]

; еах = адрес, по которому будем проверять длину строки

4A4FDF:

E8BC2FF6FF

call .000407FA0

 

4A4FE4:

85C0

test eax,eax

 

407FA0:

89FA

mov edx,edi

 

407FA2:

89C7

mov edi,eax

 

407FA4:

B9FFFFFFFF

mov ecx,0FFFFFFFF

 

407FA9:

30C0

xor al,al

 

407FAB:

F2AE

repne scasb

; здесь и происходит ошибка

407FAD:

B8FEFFFFFF

mov eax,0FFFFFFFE

 

407FB2:

29C8

sub eax,ecx

 

407FB4:

89D7

mov edi,edx

 

407FB6:

C3

retn

 

По адресу 4AEDF0 лежит 0A13861, вот его я и заменил на своё - 4AD55B. В НЕХ-редакторе по адресу AEDF0 в файле вписал 5B D5 4A 00, именно в таком перевернутом порядке. А по адресу AD55B вписал своё имя на веки :) .

 

Автор freeExec благодарит MozgC <MozgCstopSpam @ avtograd.ru> за то, что согласился проверить статью и

исправить все недочеты

04.07.2003 – 13.07.2003 – by freeExec



Обсуждение статьи: Распаковка и регистрация Net Vampire 4 >>>


Материалы находятся на сайте https://exelab.ru



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


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