Вы находитесь на странице: 1из 31

Распаковка ASProtect 2.13 на примере Icolover.exe.

Автор: SergSh
Дата: 1 ноября 2005г
Инструменты:
OllyDbg1.10 с плагином: OllyScript
ImpRec1.6F с плагином: ASProtect1.2x
PEiD

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


топике Распаковка AsProtect >1.23 - от А до Я по поводу VM. Статья не претендует на
оригинальность.

ASProtect 2.13 новый мощный протектор от Алексея Солодовникова. Основной особенностью


данного протектора является создание виртуальной машины. Идея заключается в том, что при
работе программы основная ветка кода разбросана по разным участкам памяти при этом она
сильно разбавлена мусорным кодом. На мой взгляд существует три варианта распаковки
подобных программ. Это In-line патч, являющийся наиболее простым решением, написание своей
основной ветки и восстановление разбросанного в памяти кода в отдельную секцию .exe файла,
что является наиболее трудоёмким. В данной статье мы рассмотрим только третий вариант, как
наиболее сложный и обобщающий все варианты.

Приступим. Классическое начало посмотрим, что нам покажет PEiD:

Проверим. Для начала нам необходимо найти оригинальную точку входа в программу(OEP). Для
этого загружаем нашу программу в OllyDbg. Видим стандартное начало нового ASProtectа:
Действуем согласно классическому методу. Убедимся, что убраны галочки у всех Ignore в
Options->Debugging options->Exceptions.

И начинаем проходить исключения. Нажимаем Shift+F9 пока не окажемся здесь:

Как я нашел это место? Если сделать Shift+F9 еще раз, то программа запустится. Теперь выделим
00ЕЕА7АD и поставим на него брейкпоинт (точка останова) (F2). Это необходимо чтобы
отработал обработчик исключений и программа остановилась. Жмём Shift+F9. И окажемся на
00ЕЕА7АD. Теперь убираем брейкпоинт и жмем Alt+M и видим карту памяти находим наш
Exeшник и ставим Set Memory Breakpoint on Access на секции code. В ранних версиях ASProtectа
после срабатывания этой точки останова мы оказывались на ОЕР, посмотрим, что произойдёт
сейчас. Делаем Shift+F9 и оказываемся здесь:
Как видите на начало выполнения программы этот код совсем не похож. Следовательно этот адрес
не может быть ОЕР. Где же находится оригинальная точка входа и как её найти? Обратите
внимание на стек:

Мы видим, что после обработки этой процедуры программа вернётся по адресу 01110СА5,
посмотрим где находится этот адрес, жмем Alt+M, видим, что этот адрес находится в памяти и
размер этой области 3000 (Следующая область памяти тоже имеет размер 3000, но как правило
ОЕР находится именно в первой области).

Т. к. код начала выполнения программы находится в памяти, то можно сделать вывод, что мы
столкнулись с виртуальной машиной. Методика нахождения VOEP (виртуальная оригинальная
точка входа) следующая. Перезапускаем нашу программу в OllyDbg, доходим до последнего
исключения, устанавливаем брейкпоинт на адрес 00ЕЕА7АD, нажимаем Shift+F9, убираем
брейкпоинт и устанавливаем Set Memory Breakpoint on Access на адрес в памяти 01110000, в
надежде, что после срабатывания точки останова мы окажемся на VOEP. Нажимаем Shift+F9 и мы
здесь:
Правда это выглядит не совсем так, как мы представляли себе, но это и есть виртуальная
оригинальная точка входа в программу. Как видим она изрядно разбавлена мусорным кодом. Т.к.
доходить до этого места нам придётся очень много раз, поэтому напишем не большой скрипт дабы
автоматизировать проход до VOEP.
//Скрипт необходимо запускать при отсутствии посторонних точек останова
var addr // Вводим переменную addr
var voep // Вводим переменную voep
mov voep, 00111028F // Присваиваем значение нашего voep
mov addr,00EEA76A // Присваиваем значение последнего исключения
eoe Label // Перемещение к метке Label при исключении
run // Запускаем программу
Label:
cmp eip,addr // Произошло исключение, сравниваем его адрес с нашим
je rrr // Если адреса совпали переходим
esto // Иначе продолжаем выполнение программы (SHIFT+F9)
rrr:
add addr, 43 // Добавляем к текущему адресу 43 (00ЕЕА7АD-00EEA76A)
bp addr // Устанавливаем брейкпоинт на 00ЕЕА7АD
esto
bc addr // Убираем брейкпоинт
bp voep
run
bp voep
ret

Запускаем скрипт и мы на VOEP.


Теперь давайте разберёмся, что у нас происходит с таблицей импорта (IAT). Стоя на VOEP
переходим в начало нашей программы, т.е. по адресу 00401000. Соответственно Goto ->
Expression -> 00401000. Нажимаем Ctrl+B и вбиваем FF25, производя тем самым поиск бинарной
строки.
Мы это делаем для того, чтобы определить место нахождения нашей IAT. Т.к. обращение к IAT
имеет вид JMP [addr IAT], опкод FF25 как раз и является опкодом этой команды. Сразу же
оказываемся здесь:

Как видите этот прыжок обращается непосредственно к IAT. Посмотрим в окне дампа, нашу IAT.
Соответственно Follow in Dump -> Memory address:

Прокрутив окно дампа верх, вниз определим размеры нашей IAT. Началу IAT соответствует адрес
006Е121С, концу 006Е1В08. Внимание здесь главное не ошибиться с концом IAT, т.к.
восстановленные функции мы будем вставлять в конец IAT. С IAT разобрались теперь
производим поиск всех процедур Search for -> All intermodular calls и видим:

Обратите внимание на CALL 01130000 их здесь достаточно много это и есть “спрятанные” аспром
процедуры из нашей IAT. Поэтому наша задача сводится к восстановлению процедур типа CALL
01130000 к их первоначальному значению. Напомню, что новый ASProtect копирует часть
оригинальной API в выделенный участок памяти, тем самым эмулируя работу этой функции.
Следовательно метод восстановления будет такой: В командной строке окна дампа пишем:
Переходим по адресу 77Е7АС72 и ставим брейкпоинт (F2) на первый же находящийся ниже RET.
Далее Search for -> All intermodular calls прокручиваем вверх и переходим на самый первый
CALL 01130000, жмём Nev origin here, затем устанавливаем точку останова на секцию code в
окне памяти отладчика Set memory breakpoint on access . Запускаем программу по F9 2 раза.
Сработала точка останова на RET в VirtualAlloc. Смотрим в окне стека по адресу ESP+40 и есть
искомая нами функция.

Нажимаем ещё F9 1 раз опять сработала точка останова на RET в VirtualAlloc. Но теперь искомая
нами функция находится по адресу ESP+2С:

Этот факт будем использовать в нашем скрипте для восстановления оригинальных функций. Т.е.
если по адресам ESP+40 и ESP+2С разные значения, следовательно что-то идёт не так. Нажимаем
ещё F9 2 раз и оказываемся здесь:
0040127C E8 83EDE300 CALL 01240004
00401281 118B C0E877ED ADC DWORD PTR DS:[EBX+ED77E8C0],ECX
00401287 D200 ROL BYTE PTR DS:[EAX],CL
Т.е. вернулись на тот же адрес, с той лишь разницей, что теперь мы имеем CALL 01240004 вместо
CALL 01130000. Следовательно необходимо производить изменения по адресу 0040127С и т.к.
команды ниже имеют непонятный смысл, то надо приводить его к виду JMP [addr IAT]. Для
этого необходимо найти адрес этой API в IAT. Открываем IAT в окне дампа и делаем бинарный
поиск 77E7E3FB.
Поиск не дал результатов, поэтому прокручиваем IAT до конца, т.е. до адреса 006Е1В08 и по
адресу 006Е1В0С вбиваем адрес найденной нами API.

Затем вместо CALL 01240004 по адресу 0040127С вбиваем JMP [006Е1В0С], получается:
0040127C - FF25 0C1B6E00 JMP DWORD PTR DS:[6E1B0C] ; kernel32.GetFileType
00401282 8BC0 MOV EAX,EAX
00401284 E8 77EDD200 CALL 01130000
Внимание не забудьте убрать галочку в Fill with NOP’s иначе результат будет не правильный.
Хочу сразу вас предупредить, что CALL 01130000 необходимо приводить не только к виду JMP
[addr IAT], но и к виду CALL [addr IAT]. Как же определить где какой? Обратите внимание на
опкод 8BC0 он присутствует перед каждым прыжком в IAT и после него, на это и будем
опираться. Т.е. находим CALL 01130000 и если предыдущая команда MOV EAX,EAX
( соответственно опкод 8BC0 ), или команда после CALL 01130000 будет MOV EAX,EAX,
следовательно этот CALL преобразуем в JMP [addr IAT] иначе в CALL [addr IAT]. Найдём адрес
последней процедуры CALL 01130000, соответственно делаем поиск всех CALLов прокручиваем
в низ и видим, что последняя восстанавливаемая процедура находится по адресу 0052ЕАСС:

И так мы разобрали ручной способ восстановления процедур. Но это очень длительный процесс,
поэтому напишем скрипт, который будет делать все эти операции автоматически. Несколько слов
о поиске. Идея организации поиска заключается в том, что после нахождения опкода #E8#,
рассчитываем смещение исходя из предположения, что по этому адресу находится CALL
01130000 и затем полученное смещение сравниваем с тем которое действительно там находится и
соответственно делаем вывод.
//Запуск скрипта производится стоя на VОЕР.
//Скрипт работает довольно долго, порядка 10минут.
//Скрипт необходимо запускать при отсутствии посторонних точек останова
var calladdr // Определяем переменные
var startscan
var endscan
var VirtualAllocAddr
var realfunction
var iatstart
var iatend
var endcall
var jmpstart
var jmpend
var temp
var OEP
var x
var y
var shot
var ickcall
var z
var r
mov OEP,eip // Сохраняем адрес VОЕР
gpa "VirtualAlloc", "kernel32.dll" // Получили адрес VirtualAlloc
add $RESULT,17 // Определяем адрес команды RET
mov VirtualAllocAddr, $RESULT // Сохраняем адрес команды RET
mov ickcall,01130000 // Искомая процедура
mov r,01130000 // Искомая процедура
mov iatstart, 006E121C // Адрес начала таблицы импорта
mov y, 006E121C // Адрес начала таблицы импорта
mov iatend, 006E1B0C // Адрес конца таблицы импорта
mov endscan,0052EACC // Адрес последней восстанавливаемой процедуры
mov startscan,00401000 // Начало сканирования программы
mov eip, startscan
bp startscan // Установили брейкпоинт на 00401000
run // Перешли на адрес 00401000
bc startscan // Убрали брейкпоинт
poick: // Начинаем поиск CALL 01130000
mov ickcall, r
findop startscan,#E8# //Ищем опкод команды call
mov temp,$RESULT
mov startscan,$RESULT
sub ickcall ,temp
sub ickcall,5
add $RESULT,1
cmp [$RESULT], ickcall
jne poick
sub $RESULT,1
mov calladdr, $RESULT
cmp shot,1
je preobrazov
cmp calladdr,endscan
je kon
preobrazov:
mov eip,calladdr
mov z,eip
bp VirtualAllocAddr
run
mov temp, esp
add temp, 40
mov realfunction, [temp]
run
mov temp, esp
add temp, 2C
cmp realfunction, [temp]
je ee
bc VirtualAllocAddr
jmp er
ee:
bc VirtualAllocAddr
mov eip,z
bp z
run
bc z
sub z,2
mov temp,[z]
and [z],0000FFFF
cmp [z],C08B // Проверяемый опкод # 8ВС0#
je jmps1
add z,8
mov temp,[z]
and [z],0000FFFF
cmp [z],C08B
je jmps2
mov x, 15FF //Опкод #FF15#
jmp DD
jmps1:
mov [z],temp
add z,2
mov x, 25FF //Опкод #FF25#
jmp DD
jmps2:
mov [z],temp
sub z,5
mov x, 25FF //Опкод #FF25#
DD:
mov iatstart, y
dalee:
cmp [iatstart], realfunction // Ищем найденную функцию в IAT
je dal
cmp iatstart, iatend
je nov // Если не находим то дописываем в конец IAT
add iatstart,4
jmp dalee
dal:
mov [calladdr],x // Записываем опкод и адрес функции из IAT
add calladdr,2 // в место CALL 01130000
mov [calladdr], iatstart
sub calladdr,2
cmp calladdr,endscan
je konn
cmp shot,1
je poick1
jmp poick
nov:
mov [iatstart],00000000 // Запись в конец IAT через 00000000
add iatstart,4
mov [iatstart], realfunction
add iatstart,4
mov iatend, iatstart
sub iatstart,4
jmp dal
kon:
MSG "Начинаем второй круг поиска?"
mov shot,1
mov startscan,00401000
poick1: // Начинаем повторный поиск CALL 01130000
mov ickcall, r
findop startscan,#E8# //Ищем опкод команды call
mov temp,$RESULT
mov startscan,$RESULT
sub ickcall ,temp
sub ickcall,5
add $RESULT,1
cmp [$RESULT], ickcall
jne poick1
sub $RESULT,1
mov calladdr, $RESULT
jmp preobrazov
konn:
MSG "Восстановление call и JMP завершено успешно."
jmp endscript
er:
MSG "Ошибка при восстановлении IAT."
endscript:
mov eip, OEP
ret
Скрипт успешно закончил работу, но давайте посмотрим всё ли он исправил:

Как видим две процедуры остались не восстановленными.

Это не помеха, восстановим их в ручную, описанным выше способом. Получается так:


Делаем поиск CALL 01130000 ещё раз и видим, что отладчик нашёл ещё не восстановленные
процедуры:

Восстановим и их в ручную. Но т.к. этих значений нет в нашей IAT, то не забывайте их вставить.

Ещё раз Ctrl+L и отладчик ничего не находит, следовательно мы восстановили всё. Давайте
посмотрим как выглядит новая IAT и определим её конец. Как видим окончание нашей
восстановленной IAT находится по адресу 006Е1DF4.

Теперь необходимо сделать дамп нашей программы и окончательно разобраться с импортом. Т. к.


регистр EIP у нас указывает на VOEP, находящуюся за пределами то нам необходимо его
изменить. Пусть пока адрес ОЕР будет 006В6120, значение может быть любым лишь бы не
указывало за пределы дампа. Соответственно Ctrl+G -> 006B6120 -> New origin here и снимаем
дамп, Plugins -> OllyDump -> Dump debugger process:
Открываем ImportREC, выбираем нашу программу, вбиваем ОЕР=0002B6120. Размер IAT
RVA=002E121C, SIZE=00001000. Далее GetImports->ShovInvalid->TraceLevel1(Disasm)-
>PluginTracerc->ASProtect1,22. Получили восстановленный импорт, и прикручиваем его к
нашему дампу.

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


Перезапускаем нашу программу в отладчике и с помощью скрипта доходим до VОЕР. Давайте по
трассируем код по F7. Видим, что вперемешку с мусорными байтами видны команды
характеризующие начало выполнения программы. Доходим до этого места:
Видим , что этот CALL ведёт нас в другую область памяти. Обратите внимание на PUSH 1110822.
Если сейчас снять дамп этого участка памяти и прикрутить его к нашему .EXEшнику, то в нашем
файле так и будет PUSH 1110822, а т.к. этой командой задаётся адрес перехода после выполнения
CALL 01210000, то догадайтесь куда перейдёт наш .EXEшник. Забегая вперёд скажу, решение
этой проблемы следующее, необходимо определить значение этого PUSHа в новом месте, т.е. в
созданной нами секции дампа. Для этого необходимо рассчитать это значение: PUSH-
BASE+NEVBASE, где PUSH -значение команды PUSH равное 01110822; BASE-адрес начала
выделенного участка памяти равный 01110000; NEVBASE –адрес начала нашей новой секции в
памяти. Следовательно получится 01110822-01110000+007B6000=007B6822, т.е. так:
01110C7F PUSH 7B6822
Отразим это в нашем скрипте. Продолжаем трассировать дальше, входим в CALL 01210000 и
трассируем до команды вида:
01210120 FFD3 CALL EBX
Входим в CALL EBX и оказываемся здесь:
00EF84FC 55 PUSH EBP
00EF84FD 8BEC MOV EBP,ESP
Не будем долго описывать трассировку по этому прокручиваем вниз и устанавливаем брейкпоинт
как показано ниже:

Нажимаем F9, убираем этот брейкпоинт и входим в CALL 00EF8364. Прокручиваем вниз и
устанавливаем брейкпоинт как показано ниже:

Нажимаем F9, убираем этот брейкпоинт и входим в CALL [EAX+20] и далее трассируем по F7 до
такого места:

Это то место, которое мы и искали. Как видим переход осуществляется на адрес 01110Е59. Из
всего выше изложенного делаем вывод: необходимо стоя на VОЕР произвести поиск CALL
01210000, затем установить брейкпоинт на адрес прыжка JMP [ESP-4] и запустить программу на
выполнение, после срабатывания брейкпоинта, на прыжке, адрес перехода записать в место CALL
01210000, продолжить поиск CALL 01210000 и т.д. Вот такой алгоритм восстановления CALL
01210000. Но здесь есть один нюанс адрес прыжка JMP [ESP-4] после каждой загрузки
программы разный, поэтому запомним адрес участка памяти где он находится ( у меня он
01220000 ), а сам прыжок будем искать по сигнатуре #FF6424FC#. Сразу разберём ещё один
нюанс, JMP [ESP-4] происходит и в процедуры находящиеся непосредственно в исследуемой
программе и в процедуры находящиеся в памяти. Поэтому если мы просто сдампим и прикрутим,
то смещения этих процедур не совпадут. Для устранения этого недостатка сделаем так. Создадим
новую секцию в нашем, полученном ранее дампе, размером 3000. Изменим точку входа на
003В628F, т.е. ОЕР в создаваемом нами файле будет находится в искусственно созданной новой
секции и соответственно туда и будем копировать восстановленные байты из памяти. И не
забывайте создать саму секцию с помощью Hex-редактора. Должно получится примерно так:

Если возвращаемый из JMP [ESP-4] адрес находится в этой же области памяти то его приводим
так: addr из JMP [ESP-4]–addr CALL 01210000–5.
Если возвращаемый из JMP [ESP-4] адрес находится в исследуемой прграмме то его приводим
так:
1. Определяем адрес в нашем вампе: addr CALL 01210000-BASE+NEVBASE=NEVADDR
2. Определяем новое смещение: addr из JMP [ESP-4]- NEVADDR-5
3. Приводим все возвращаемые процедуры к виду JMP addr
Кажется все моменты оговорили, приступим к написанию скрипта.
//Скрипт необходимо запускать при отсутствии посторонних точек останова
var x
var base
var cal
var calladdr
var ickcall
var startscan
var endscan
var VirtualAllocExAddr
var realfunction
var ojmps
var jmps
var temp
var r
var y
var OEP
var NOEP
mov OEP, eip
mov startscan, 01110000 // Начало сканирования
mov base, 01110000 // Начало сканирования
mov endscan, 01111B4A // Адрес последней восстанавливаемой процедуры
mov ickcall, 01210000 // Адрес искомой процедуры
mov r, 01210000 // Адрес искомой процедуры
mov ojmps ,01220000 // Адрес памяти где JMP [ESP-4]
mov NOEP, 007B6000 // Адрес начала созданной нами секции
poick0: // Осуществляем поиск JMP [ESP-4]
findop ojmps,#FF6424FC#
mov jmps ,$RESULT
poick:
findop startscan, #E8# //Ищем опкод callа
add startscan,1
mov temp,$RESULT
sub ickcall ,temp
sub ickcall,5
add $RESULT,1
cmp [$RESULT], ickcall
mov ickcall, r
jne poick
mov calladdr,temp
mov eip,calladdr
mov y,calladdr
// Здесь будет кусок кода, рассмотренный ниже. Скрпт работает и без этого куска.
DD:
bp jmps
run
bc jmps
mov temp,esp
sub temp,4
mov cal,[temp]
mov eip,calladdr
sti
sub y,5
mov x,[y]
and x,000000ff
cmp x,00000068
je preobrazov1
add y,5
mov x,y
sub cal,5
sub cal,x
mov [y],E9
add y,1
mov [y], cal
jmp dal
preobrazov1:
add y,1
mov x,[y]
sub x,base
add x, NOEP
mov [y],x
add y,4
mov x,cal
and x,ffff0000
cmp x,base
je dalee
mov x,y
sub x,base
add x, NOEP
sub cal,5
sub cal,x
mov [y],E9
add y,1
mov [y], cal
dal:
cmp calladdr, endscan
je kon
jmp poick
dalee:
sub cal,5
sub cal,y
mov [y],E9
add y,1
mov [y], cal
jmp dal
kon:
MSG "Восстановление call и JMP завершено успешно."
endscript:
mov eip, OEP
ret //Вышли из скрипта

Скрипт успешно отработал. Теперь открываем PETools, выбираем наш процесс:

И делаем дамп восстановленного нами участка памяти (Dump Region):


Затем с помощью Hex-редактора вставляем сдампленые из памяти байты в нашу новую секцию.
На этом первый этап восстановления виртуальной машины можно считать завершённым.
Запускаем наш дамп в OllyDbg жмём F9, дамп падает. Смотрим стек:

Переходим соответственно на адрес 00403ВВ0:

Выше видим CALL EAX, выполняющийся в цикле. Кто распаковывал предыдущие версии
ASProtect 2х без VM сразу поймёт, что мы имеем дело с программой написанной на Delphi и этот
CALL EAX должен выполняться очень много раз. Запускаем в отладчике оригинальную
программу, доходим до VOEP, переходим на адрес 00403ВВ0, поднимаемся вверх и
устанавливаем брейкпоинт на начало этой процедуры:
Пускаем отладчик по F9 и останавливаемся на нашем брейкпоинте. Начинаем трассировать
по F7. Обрамите внимание команду MOV EAX,DWORD PTR DS:[6D34AC], там
находится другой адрес из программы DS:[006D34AC]=006B64E8, в нём 006B64E8 01 00
00 00. С кажу сразу, что по адресу 006В64Е8 должно находится число повторений работы
CALL EAX, пока там 1 и это мы должны исправить, но об этом позже. Заходим в CALL EAX
трассируем по F7 заходя во все CALLы. Трассируем пока не увидим код типа:

Это уловка ASProtect самосканирующий код, F2 ставить нельзя, поэтому ставим брейкпоинт на
доступ SBB EBX,F1105286 и после его срабатывания трассируем по F7 пока в ECX не станет
адрес следующей команды:

Обратите внимание на значение в регистре EAX. По этому адресу находится закриптованная


таблица CALL EAX а выглядит она так:

Прокрутим eё вниз и определимся с адресом последнего закриптованного CALL EAX и


соответственно размер её 864:
Как видим этот адрес равен 011Е0864, он понадобится нам далее. Продолжим. После этого смело
F2 на адрес SBB EBX,F1105286 брейкпоинт сработал, убрали его. Трассируем по F7 пока не
увидим код:

Обратите внимание на CALL EBP в нём как раз и происходит выполнение интересующих нас
процедур. Поэтому напишем скрипт который будет сохранять эти процедуры в виде таблицы в
памяти. Посмотрите в секцию 007B1000 .adata там полно нулей, туда и будем копировать уже
раскриптованную таблицу CALL EAX.
//Скрипт необходимо запускать при отсутствии посторонних точек останова
var temp
var startscan
var d
var prg
mov d,0
mov prg,0
mov temp,7B1000 //сюда будем писать восстанавливаемые келы
r:
mov startscan, 12000F3 // мой адрес CALL EBP
bphws startscan, "x" //Установили брейкпоинт на CALL EBP
run
bphwc startscan //Убрали брейкпоинт с CALL EBP
add d,1
cmp d,3
jae r
mov prg,eax
cmp prg,11E0868 //Это адрес последнего последнего закодированного CALLа
je rr
mov [temp],ebp
add temp,4
mov d,0
jmp r
rr:
ret
Открываем в окне дампа секцию .adata и запускаем скрипт. Программа запустилась, а в окне
дампа мы увидели искомую нами таблицу CALL EAX:
Как видите последнее значение CALL EAX находится по адресу 007В142С следовательно
количество повторений CALL EAX будет равно 42С/4=10B. Мы говорили раньше, что размер этой
таблицы должна быть 864, это говорит о том, что в программе присутствует ещё один CALL EAX.
Так и есть, забегая вперёд скажу, что второй CALL EAX отвечает за сворачивание ASProtectа
после закрытия программы. Выделяем полученную нами таблицу, копируем её в наш дамп и
сохраняем изменения. Давайте восстановим и вторую таблицу CALL EAX. Для этого необходимо
просто закрыть запущенную программу и т. к. наш скрипт ещё не закончил работу, то в окне
дампа мы увидим продолжение таблицы. Разделим таблицы нулями в нашем дампе и тоже
сохраним. Не забывайте перезапускать в отладчике ваш дамр, а то получите ошибку при
повторном сохранении. Получается примерно так:

Как видим количество повторений второй CALL EAX составит 430/4=10С, примем пока это к
сведению.
Наступает завершающий этап распаковки, а именно правка полученного нами дампа. Запускаем
наш дамп в отладчике и останавливаемся на входе в процедуру с CALL EAX. Начинаем
трассировать по F7 и сохраняем найденное нами число повторений по адресу 006В64Е8.
Трассируем дальше. Видим:

По адресу 00403ВА0 в ЕАХ записывается значение 00ЕЕ6ЕD8 и дальше ЕАХ не изменяется,


следовательно CALL EAX перенесёт нас в пустоту, т.к. в нашем дампе нет такой области в
памяти. Необходимо это исправить так, чтобы в EAX находились адреса из созданной нами
первой таблицы. Посмотрим откуда берётся это значение. Перезапустим дамп и с ново трассируем
по F7 видим:

Посмотрим, что находится по адресу 006В6910:

А там как раз тот адрес, который нам не нужен. Следовательно необходимо, чтобы вместо
006В6910 по адресу [eax+4] был адрес нашей таблицы. Исправляем, сохраняем:

Перезапускаем наш дамп и начинаем проходить CALL EAX, предварительно сделав так:
Лучше всего установить F2 на адрес 00403ВАЕ и нажимая F9 следить за счётчиком в ЕВХ:

Жмём F9 пока наш дамп не упадёт. Упали. Смотрим в стек:

Посмотрим на этот адрес в окне дизассемблера и поставим брейкпоинт перед ним:

Перезапускаем дамп и после остановки входим в CALL dump_.00409FFC. Там:

Естественно этого адреса нет в нашем дампе. Боремся с этим так, запускаем оригинальную
программу, доходим до VOEP и всё, что есть в памяти по адресу 01180000 копируем в наш дамп,
после таблицы CALL EAX (благо там не много). И по адресу 00409FFC делаем прыжок в это
место:
Поглядите чуть ниже, там тоже есть прыжки в неизвестность поэтому их лучше сразу исправить
аналогичным способом. Вместо JMPа это может выглядеть так :

Таких мест 6 и все их желательно восстановить.


Исправили, идем дальше. Все CALL EAX удачно пройдены. Трассируем дамп дальше по F8 и
доходим до этого места:

Давайте остановимся на этом прыжке. Не торопитесь, посмотрите в куда прыгнет оригинал в этом
месте (напоминаю в оригинале это место будет равно 7В6ЕВВ-7В6000+1110000=01110ЕВВ) :

Как видим прыжок происходит на адрес 01110DE9. Что это ошибка в скрипте? Давайте
разбираться. Обратите внимание на регистр флагов:

Давайте сбросим флаги Р и Z, и посмотрим куда будет переход:


Как видим адрес перехода сменился, следовательно нюанс с регистром флагов мы должны
учитывать. Т.е. в скрипте, который преобразует CALL 01210000 в соответствующий JMP мы
должны добавить проверку на наличия сравнения перед CALL 01210000 и если оно есть, то этот
CALL 01210000 будем проходить несколько раз изменяя регистр флагов. Полученные значения
будем записывать отдельно, с соответствующим условным переходом. Начнём с поиска места
сравнения. Запускаем под отладчиком оригинальную программу доходим до VOEP устанавливаем
брейкпоинт на адрес 01110ЕВВ, жмём F9 и начинаем трассировку по F7. Делаем всё аналогично
тому, что мы делали при поиске JMP [ESP-4]. Доходим до такого места:

эти условные переходы и влияют на формирование адреса перехода. Если по трассировать


программу и понаблюдать за переходами в этом участке кода, то можно сделать вывод, что
переход по адресу 00ЕF83B8 происходит только при наличии команды PUSH?????? перед CALL
01210000, следовательно этот переход нас не интересует. Остаются ещё 3, давайте разбираться.
Состояние флагов нам известно, с ново доходим до CALL 01210000 по адресу 01110ЕВВ,
устанавливаем F2 на 00EF83B8 и смотрим где будет переход при нормальном выполнении
программы:
Давайте перезапустим программу, но теперь перед входом в CALL 01210000 по адресу
01110ЕВВ сбросим флаги P и Z, и посмотрим где будет прыжок, прыжок происходит на том же
месте, но адрес перехода в JMP [ESP-4] изменяется. Напрашивается следующий вывод: при
нахождении подобных CALLов необходимо изменять регистр флагов ( в основном это касается
флага Z ) в определённом порядке, т.е. искусственно его с начало устанавливать, а затем убирать и
соответственно составлять таблицу переходов. Вопрос о переходах можно считать пока не
решённым до конца мне кажется есть более рациональное решение, но в данной программе
достаточно будет и этого. В данном случае мы сделаем так:

Пусть будет JMP и мы будем проходить дважды только CALL 01210000 по адресу 01110ЕВВ,
т.к. в этой программе этого достаточно, но в идеале мы должны проходить дважды все CALL
01210000 кроме тех которые содержат перед собой PUSH??????. Трассируем дальше видим:

теперь меняя значения регистра EAX перед выполнением команды TEST AL,AL мы можем
воздействовать на флаг Z в необходимой нам последовательности. Полученные в JMP [ESP-4]
результаты будем записывать в конце этого участка памяти:

напишем такой код:


cmp y,01110EBB
jne DD
mov [00EF83BA],#EB#
bp 00EF8418
run
bc 00EF8418
mov temp,eax
mov eax,0
bp jmps
run
bc jmps
mov temp,esp
sub temp,4
mov cal,[temp]
mov x,1112FdF
sub cal,6
sub cal,x
mov [x],840F
add x,2
mov [x],cal
add x,4
mov eip,y
sti
bp 00EF8418
run
bc 00EF8418
mov eax,temp
bp jmps
run
bc jmps
mov [00EF83BA],#74#
mov temp,esp
sub temp,4
mov cal,[temp]
sub cal,5
sub cal,x
mov [x],E9
add x,1
mov [x],cal
mov x,1112FdF
sub x,5
sub x,y
mov [y],E9
add y,1
mov [y],x
pause
jmp dal
Этот кусок вставляется в указанное место в одном из предыдущих скриптов. Затем повторяем
процедуру снятия дампа участка памяти и прикручивания его к нашему дампу.
Сделали. Трассируем дальше. Наш дамп падает здесь:

Смотрим в стек:

Переходим в окно дизассемблера на адрес 006В5052 и устанавливаем брейкпоинт выше:

Перезапускаем наш дамп и смотрим регистр EDX:


Смотрим, что находится по адресу 006D1CFC:

Как видите этих адресов у нас тоже нет в памяти. Давайте запустим оригинал и дойдём до VOEP в
ручную, наблюдая за данными по адресу 006D1CFC. Произошло очередное исключение:

В окне дампа видим:

Просто выделяем это всё и копируем в наш дамп. Насколько я понял эти значения отвечают, за
время использования программы, т.е за триальный период.
Трассируем дальше. Далее падаем тут:

Как видим это связано с IAT. Неужели мы не правильно восстановили импорт? Нет импорт мы
восстановили правильно, просто это ещё одна уловка ASProtectа. Посмотрим в оригинале после
прохода скрипта восстанавливающего IAT:
Как видите всё нормально. В чём же загвоздка? Посмотрим что происходит в нашей IAT:

Как видим по адресу 006Е13А0 находятся нули. Эти нули заделал ImportREC когда выполнял
команду Cut thunk(s). Произошло это из-за того, что эта функция находилась на границе между
различными библиотеками, а как известно разные библиотеки должны разделяться нулями – вот и
результат. Бороться с этим очень просто. Берём и переносим адрес этой функции в другое место, к
своей же библиотеке и не забывайте исправить адрес в тексте программы:

Трассируем дальше. Доходим до этого места:

Видим в оригинале, что кода там не много и копируем всё в нашу секцию как и при
восстановлении CALL EAX. Трассируем дальше. Подходим к запуску программы:

Трассируем дальше. Обратите внимание на CALL 006B642F, там находится следующий код:
Здесь интересная проверка по адресу 006B5FE6 посмотрим что у нас в EBX:

Заменим значение 1Е8480 на значение из ЕВХ.

Перестрахуемся, ведь подобная проверка может быть не одна. Для этого зайдём в CALL 006B642F
и поставим брейкпоинт на первую команду:

Жмём F9 и останавливаемся на 0046636С. Смотрим в стеке адрес откуда была вызвана эта
процедура, переходим в окно дизассемблера и меняем значения:
Не забываем всё это сохранить. Жмём F9 и сново останавливаемся на 0046636С, делаем всё тоже
самое и видим:

Как видим проверки закончились. Не убирая брейкпоинта перезапустим и видим, что есть ещё
одна проверка:

Исправляем и её. Пускаем программу по F9, всё вроде нормально работает, но ели попытаться
запустить без отладчика то меню программы ведёт себя как-то странно. Где-то читал, что новый
ASProtect использует функцию GetTickCount и запоминает время выполнения участков кода и т.д.
Что ж найдём эту функцию в нашей IAT и поставим брейкпоинт на её использование:

Запускаем наш дамп, остановились на нашей точке останова, переходим в место вызова:

Откровенно сказать я не дотумкал до того, как он это делает, поэтому просто занопим CALL
006B5F68:
и проблема с меню будет решена. Теперь будем разбираться с завершением работы программы.
Помните про 2-й CALL EAX. Закройте наш запущенный дам и смотрим где упадём:

Устанавливаем на CALL EBP брейкпоинт и перезагружаем наш дамп. Как видим первый раз
CALL EBP отработал нормально, а на втором дамп упал. На этом пожалуй и закончим, т.е.
переориентируем прыжок по адресу 007В636С в пустое место, а там напишем код завершения
программы. Примерно так:

А там соответственно так:

Сохраняем все наши изменения и запускаем наш дамп. Как видите программа полностью
работоспособна. И можно сказать почти полностью распакована. По техническим причинам
продолжить не могу.

Спасибо ВСЕМ!

1 ноября 2005 г.
SergSh
г. Асбест.