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

Видеокурс программиста и крэкера 5D 2O17
(актуальность: декабрь 2O17)
Свежие инструменты, новые видеоуроки!

  • 400+ видеоуроков
  • 800 инструментов
  • 100+ свежих книг и статей

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


Распаковка ASProtect 1.35 (на примере Reactive MYCOP Cleaner 1.2), часть 3




Хорошая подборка видеоуроков, инструментов крэкера, книг и статей - здесь.

ASProtect Stolen Code. Виртуальная машина


Давайте рассмотрим, что такое виртуальная машина, которую почему-то считают самой сложной частью Asprotect. Автор Asprotect Алексей Солодовников, решил значительно усложнить процесс распаковки программ, упакованным этим пакером, еще одной опцией - он перенес выполнение части кода программы в специальную область памяти, создаваемую пакером во время распаковки программы в память машины. Причем в этой специальной области памяти выполняется не только код, расположенный возле Entry Point программы, но также, из секции кода программы идет довольно много прыжков в эту область памяти. Относительно несложно скопировать эту область памяти, которая называется виртуальной машиной (VM), и присоединить к изготовленному нами дампу памяти программы в виде отдельной секции EXE-файла. Довольно просто обеспечить загрузку этой секции EXE-файла по заданному адресу, установив нужное значение секции в параметре Virtual Offset. Но имеется одно НО! Код этой области памяти разорван огромным числом инструкций CALL XXXXXXXX, которые выполняют прыжок в другую область памяти программы. А если мы посмотрим структуру этих инструкций CALL XXXXXXXX, то там мы увидим еще кучу инструкций, которые перебрасывают программу в другие области памяти, созданные пакером. Поэтому, тупое копирование этой области памяти нам ничего не даст, и нам нужно сделать что-то такое, что позволило бы программе выполнять код только в одной области памяти.


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


Распаковка ASProtect 1.35 - часть 3


Код заполнен мусором, а ниже VOEP мы видим инструкцию PUSH 9502F9, за которой идет инструкция CALL 00B30000. Эта пара инструкций является еще одним типом прыжков на область памяти, созданной asprotect при запуске программы.


Инструкция PUSH 9502F9, при выполнении кода, в данном случае обеспечивает выполнение прыжка на адрес, который указан в качестве параметра этой инструкции (009502F9). Значительно больший интерес для нас представляет инструкция CALL 00B30000. Давайте, выполним поиск всех инструкций CALL 00B30000:



Распаковка ASProtect 1.35 - часть 3


И на каждый адрес, где находится инструкция CALL 00B30000, установим BP:


Распаковка ASProtect 1.35 - часть 3


Нажимаем клавишу F9, и программа останавливается здесь:


Распаковка ASProtect 1.35 - часть 3


Заходим в CALL 00B30000 с F7:


Распаковка ASProtect 1.35 - часть 3


Мы видим, что эта область памяти заполнена мусорным кодом, содержащим огромное число разного рода прыжков. Но, если прокрутим этот код немного вниз, то увидим одну вразумительную инструкцию - CALL ESI (вместо регистра ESI может быть любой другой регистр):



Распаковка ASProtect 1.35 - часть 3


Давайте установим BreakPoint на адрес этого CALL ESI, и нажмем клавишу F9. Программа останавливается на BP, и в справочном окне мы видим адрес, на который пакером будет выполнен прыжок - 00994BC0. Адрес этого прыжка задан инструкцией PUSH 994BC0:


Распаковка ASProtect 1.35 - часть 3


Инструкция CALL ESI указывает на подпрограмму, которая выполняет идентификацию украденной инструкции. Удаляем BP, входим в CALL ESI по F7:


Распаковка ASProtect 1.35 - часть 3


Эта подпрограмма не содержит мусорный код, и данной подпрограмме нас интересуют только две инструкции CALL:



Распаковка ASProtect 1.35 - часть 3


Инструкция CALL EDX (вместо регистра EDX может быть любой регистр) занимается извлечением и декодированием хэшей из таблицы хэшей. Когда хэш декодирован, производится сравнение полученного хэша с хэшем текущей функции. Если данный хэш не совпадает с хэшем текущий функции, то выполняется проверка, не является ли этот хэш последним хэшем в таблице. Если этот хэш является последним хэш в таблице, то прыжок JA SHORT 00994C27 не выполняется, и нам показывается сообщение с ошибкой “Error: 111”.


Найти расположение нужной нам инструкции (CALL 00994668) можно путем поиска следующей последовательности инструкций:



  PUSH EXX
  MOV EXX, EXX
  MOV EXX, DWORD PTR SS:[EXX+18]  
  MOV EXX, EXX
  CALL: 00ХХХХХХ → ЭТО ТОТ CALL, КОТОРЫЙ НАС ИНТЕРЕСУЕТ
  DEC EDI
  


Устанавливаем BP на CALL 00994668, и нажимаем F9. Удаляем BP, и входим в CALL 00994668 по F7:


Распаковка ASProtect 1.35 - часть 3



Чуть ниже находится селектор обработки типа украденной инструкции:


Распаковка ASProtect 1.35 - часть 3


Инструкция CALL EDX определяет тип украденной инструкции:


AL = 0 - украден call

AL = 1 - украден jmp

AL = 2 - украден jcc (один из 16 прыжков)

AL = 3 - украдены cmp+jcc


И, в зависимости от полученного значения в регистре AL, будет выполнен один из условных прыжков, находящихся ниже инструкции SUB AL,2. Я не буду подробно описывать работу этого селектора, поскольку все это можно прочитать в прекрасной работе PE_Kill “Распаковка ASProtect v 2.xx (отрезание секций, восстановление скрамблерного кода, декомпиляция VM, восстановление импорта, инлайн-патч)”.


Но, какой бы прыжок не выполнялся, в любом случае, пакер приходит сюда:


Распаковка ASProtect 1.35 - часть 3


Инструкция JMP DWORD PTR DS:[EAX+20] всегда выполняет прыжок на начало области памяти 00B40000 (в данной программе). Нажимаем клавишу F8, чтобы выполнить прыжок, и попадаем сюда:


Распаковка ASProtect 1.35 - часть 3


Вся эта область памяти заполнена мусорным кодом. Но, если немного потрассировать этот код, то мы приходим сюда:


Распаковка ASProtect 1.35 - часть 3


Здесь находится самый главный прыжок JMP DWORD PTR SS:[ESP-4], который определяет, с какого адреса необходимо продолжить выполнение программы. В окне справки отладчика мы видим, что пакер выполнит прыжок на адрес 00950ED8, который находится в одной области памяти с VOEP. Записываем базовый адрес области памяти, в которой находится прыжок JMP DWORD PTR SS:[ESP-4], поскольку адрес этого прыжка меняется при каждой перезагрузке программы, и, по этой причине, в нашем скрипте, этот прыжок мы будем находить по его опкоду #FF6424FC#. Нажимаем клавишу F8, и попадаем сюда:


Распаковка ASProtect 1.35 - часть 3


Давайте установим Hardware BreakPoint on execute на инструкцию JMP DWORD PTR SS:[ESP-4], и понажимаем несколько раз клавишу F9. На одном из прыжков мы попадаем сюда:


Распаковка ASProtect 1.35 - часть 3


Теперь прыжок выполняется на область .text; т.е. мы выходим из виртуальной машины. Давайте подведем итоги проделанной работы. Инструкция CALL 00B30000 определяет адрес, с которого программа должна продолжить свое выполнение. Причем, в зависимости от хэша украденной инструкции, дальнейшее выполнение кода может осуществляться как в области памяти виртуальной машины, так и области кода самой программы. Если мы заменим инструкцию CALL 00B30000 прыжком на адрес, на который указывает инструкция JMP DWORD PTR SS:[ESP-4], то проблема с виртуальной машиной будет решена.


Итак, мы имеем следующее:


  1. Инструкции PUSH XXXXXXXX, которые указывают на адрес прыжка в области виртуальной машины;

  2. Инструкции CALL 00B30000, которые определяют адрес дальнейшего выполнения программы.



Далее у нас имеется два варианта решения этой задачи:


  1. Привести все инструкции CALL 00B30000 к инструкциям JMP XXXXXXXX, сдампировать всю область памяти виртуальной машины 00950000, и прикрутить этот дамп в конец нашего файла dumped_.exe, указав Virtual Offset прирученной секции - 00950000, чтобы обеспечить загрузку этой секции в области памяти машины по ее родному адресу.

  2. Найти свободное место в нашем файле dumped_.exe, сделать переадресацию всех инструкций PUSH XXXXXXXX и JMP XXXXXXXX, на это свободное место, затем скопировать область памяти виртуальной машины 00950000, и вставить скопированные данные на свободное место.


Я решил использовать второй вариант, поскольку он позволяет уменьшить размер файла. А если мы не найдем достаточного количества свободного места, то тогда мы сможем создать дополнительную секцию в файле dumped_.exe, и сделать размер этой секции таким же, какой имеет эта область памяти виртуальной машины 00950000. Проходим на вкладку Memory Map, и находим область памяти 00950000:


Распаковка ASProtect 1.35 - часть 3


Здесь мы видим размер области VM - 3000 байтов.


Теперь ищем свободное место в секциях файла dumped_.exe. Свободное место в 3000 байтов можно найти в секции .tls, поскольку она имеет размер A000 байтов:


Распаковка ASProtect 1.35 - часть 3


Давайте проверим, используется ли эта секция файла при запуске программы, для чего проходим на VOEP, и устанавливаем BPM on access на секцию .tls. Нажимаем клавишу F9, и программа не останавливается на доступе к этой секции. Значит, восстановленный код VM мы сможем записать в секцию .tls. Однако проведем некоторую доработку этой секции; мы разделим ее на две части, одна часть будет иметь размер 7000 байтов, и будет содержать данные TLS Directory, а вторая часть будет иметь размер 3000 байтов, в которую мы запишем восстановленный код VM. Для этих действия мы опять будем использовать PE Tools v1.5 RC7, с помощью которого сначала удаляем секцию .rsrc, и затем корректируем размер секции .tls:


Распаковка ASProtect 1.35 - часть 3


Затем добавляем новую секцию с размером 3000 байтов, и указываем ее имя .code, поскольку эта секция будет содержать восстановленный код VM:


Распаковка ASProtect 1.35 - часть 3


Распаковка ASProtect 1.35 - часть 3



И вставляем секцию .rsrc, как это мы делали ранее. Корректируем значение Virtual offset секций .code и .rsrc, чтобы они были одинаковыми со значениями Raw offset. И корректируем параметр Size of Image, как это мы делали ранее, при удалении секций, нажав три кнопки с вопросительными знаками. Проверяем нашу доработку, загружая файл dump_.exe в отладчик:


Распаковка ASProtect 1.35 - часть 3


Базовый адрес секции .code - 0048C000, и этот адрес мы будем использовать в скрипте.


Нам надо определить значение инструкций PUSH для секции .code файла dumped_.exe. Возьмем, например, инструкцию PUSH 950CB3. Значение 00950CB3 для новой секции .code можно легко вычислить по следующей формуле:


Новое значение = PUSH - BASE + NEWBASE, где PUSH - это значение, записанное в инструкции PUSH (00950CB3); BASE - это базовый адрес данной области памяти (00950000); NEWBASE - это базовый адрес секции .code файла dump_.exe (0048C000).


Следовательно, для этой инструкции PUSH, значение аргумента исправляем следующим образом:


Новое значение = 00950CB3 - 00950000 + 0048C000 = 0048CCB3


Таким образом, инструкцию PUSH 950CB3 восстанавливаем как PUSH 0048CCB3.


Теперь нам надо восстановить инструкции CALL 00B30000.


Ранее мы видели, что прыжок JMP DWORD PTR SS:[ESP-4] может выполняться как на область виртуальной машины (область памяти 00950000), так и на область секции .text (это показано на предыдущих рисунках). Следовательно, при восстановлении инструкций CALL 00B30000 возможны два варианта.


1-й вариант. Прыжок JMP DWORD PTR SS:[ESP-4] выполняется на область памяти виртуальной машины, где находится VOEP (а мы видели, что первый прыжок был именно таким):


Распаковка ASProtect 1.35 - часть 3


Адрес 00950ED8 находится в области виртуальной машины, в которой находится VOEP. Тогда, этот прыжок мы можем записать следующим образом:




Новый адрес = (Адрес назначения прыжка JMP DWORD PTR SS:[ESP-4]) - (Адрес, на котором расположена инструкция CALL 00B30000) - 5


В нашем случае, адрес первого прыжка будет таким:


Новый адрес = 00950ED8 - 009506D9 - 5 = 000007FA


Примечание: Такие вычисления я делаю в калькуляторе Windows, потому что иногда приходится из меньшего числа вычитать большее число, а выполнение таких вычислений в command bar дает неправильный результат.


Давайте пройдем в дамп на адрес 009506D9 нашей инструкции CALL 00B30000, и заменим инструкцию CALL (опкод E8) на инструкцию JMP (опкод E9), и после опкода E9 запишем полученный нами результат в обратном порядке:


Распаковка ASProtect 1.35 - часть 3


Итак, мы получили первые инструкции переадресации:


009506D4 68 B3CC4800 PUSH 0048CCB3 ; адрес возврата (в добавленную нами секцию)

009506D9 E9 FA070000 JMP 00950ED8 ; переадресация CALL


Здесь мы видим, что замененная инструкция CALL 00B30000 показывает на адрес, который принадлежит области памяти VOEP! Но, об этом не нужно беспокоиться; когда мы скопируем эту область памяти в добавленную секцию файла dumped_.exe, то там все будет записано правильно.


2-й вариант. Прыжок JMP DWORD PTR SS:[ESP-4] выполняется на адрес, который находится в секции .text:



(Адрес, на котором расположена инструкция CALL 00B30000) - BASE + NEWBASE = NewAddres


Новый адрес = (Адрес назначения прыжка JMP DWORD PTR SS:[ESP-4]) - NewAddres - 5


Во втором случае, адрес второго прыжка будет таким:


NewAddres = 0095135E - 00950000 + 0048C000 = 0048D35E


Новый адрес = 00406AC4 - 0048D35E - 5 = FFF79761


Таким образом, мы выполняем переадресацию всех CALL 00B30000 в добавленную нами секцию файла dump_.exe.


И, наконец, нам нужно найти последний CALL 00B30000, чтобы пометить его в скрипте, для чего выполняем команду Searh for → All commands.


Распаковка ASProtect 1.35 - часть 3


В нашем случае, последняя инструкция CALL 00B30000 находится по адресу 00951580.


Теперь у нас имеются все необходимые данные для разработки скрипта. Этот скрипт, когда программа остановлена на VOEP, будет искать все инструкции CALL 00B30000, затем будет искать адрес прыжка JMP DWORD PTR SS:[ESP-4], после чего установит Hardware BP на этот прыжок. Затем скрипт восстановит инструкции CALL 00B30000 и PUSH, и продолжит свою работу со следующей парой инструкций PUSH - CALL.

Поскольку инструкция JMP DWORD PTR SS:[ESP-4] находится в области памяти, создаваемой asprotect при каждой перезагрузке программы, то адрес этой инструкции будет все время меняться. Поэтому будем искать этот прыжок с помощью цепочки байтов его опкода #FF6424FC#. Запоминаем область памяти, где находится этот прыжок, в моем случае - это область памяти 00B40000.


Скрипт будет таким:



  //Разработчик скрипта: Ulaterck, доработка - vnekrilov.
  //Описание: Скрипт разработан для восстановления инструкций PUSH - CALL в области памяти VOEP
  //программы eactive MYCOP Cleaner v1.2, защищенной ASProtect v1.35 build 06.26.
  //Цель: RMC.EXE
  //Условия применения: ODBGScript 1.48, Olly Advanced v1.26 Beta 12, PhantOm v1.04.
 
var OPCode // Переменная для хранения опкода инструкций var VOEP_BASE // Переменная для хранения базового адреса области VOEP var ESP_4 // Переменная для хранения значения инструкции JMP [ESP-4] var Address_CALL // Переменная для хранения адреса, где имеется CALL 00B30000 var Field_Asprotect // Переменная для хранения области памяти CALL 00B30000 var Temp_Field_Asprotect // Переменная для временного хранения памяти CALL 00B30000 var Start_scan // Переменная для хранения адреса начала сканирования var End_scan // Переменная для хранения адреса конца сканирования var Address_JMP // Переменная для хранения найденного адреса JMP [ESP-4] var Field_JMP // Переменная для хранения адреса области памяти JMP [ESP-4] var Address_Opcode_Call // Переменная для хранения найденного опкода инструкции CALL var Temp_EIP // Переменная для хранения текущего значения EIP var OEP // Переменная для хранения адреса OEP var NEWBASE // Переменная для хранения параметра NEWBASE var Temp_ESP // Переменная для хранения текущего значения ESP var Field_ESP_4 // Переменная для хранения области памяти ESP-4 var Flag // Переменная для хранения значения флажка
preparation: // Подготовка скрипта к работе mov OEP,eip // Сохраняем значение VOEP в переменной OEP. mov Start_scan,00950000 // Указываем адрес начала поиска CALL 00B30000 mov VBASE,00950000 // Сохраняем базовый адрес области памяти VOEP mov End_scan,00951580 // Указываем адрес последнего CALL 00B30000 mov Field_Asprotect,00B30000 // Указываем значение, которое имеет CALL 00B30000. mov Temp_Field_Asprotect,00B30000 // Сохраняем значение CALL 00B30000в переменной mov Field_JMP,00B40000 // Указываем область памяти, где находится JMP [ESP-4] mov NEWBASE,0048C000 // Сохраняем начало созданной секции в файле dump_.exe
SearchJMP: // Поиск адреса прыжка JMP [ESP-4] findop Field_JMP,#FF6424FC# // Ищем опкод прыжка JMP [ESP-4] mov Address_JMP,$RESULT // Сохраняем найденный адрес прыжка JMP [ESP-4].
SearchCall: // ** Процедура поиска CALL 00B30000 ** findop Start_scan,#E8# // Ищем опкод инструкции Call (E8) add Start_scan,1 // Прибавляем к найденному значению 1 mov Address_Opcode_Call,$RESULT // Сохраняем адрес найденного опкода инструкции call sub Field_Asprotect,Address_Opcode_Call // Вычитаем из 00B30000 адрес, где находится CALL sub Field_Asprotect,5 // Из полученного значения вычитаем число 5 add $RESULT,1 // К полученному результату добавляем 1 cmp [$RESULT],Field_Asprotect // Полученный результат сравниваем с 00B30000 mov Field_Asprotect,Temp_Field_Asprotect // Восстанавливаем значение в переменной jne SearchCall // Если это не CALL 00B30000, то продолжаем поиск mov Address_CALL,Address_Opcode_Call // Сохраняем адрес найденного опкода Call 00B30000 mov eip,Address_CALL // Изменяем EIP на адрес найденной инструкции Call 00B30000 mov Temp_EIP,Address_CALL // Сохраняем, на всякий случай, адрес Call 00B30000. log Address_CALL // Регистрируем адрес, на котором находится Call 00B30000
JMPESP_4: bp Address_JMP // Устанавливаем breakpoint на инструкции JMP [ESP-4] run // Запускаем программу bc Address_JMP // Удаляем breakpoint на инструкции JMP [ESP-4] mov Temp_ESP,esp // Сохраняем значение регистра ESP в переменной Temp_ESP sub Temp_ESP,4 // Вычитаем 4, чтобы перейти на (JMP [ESP-4]) mov ESP_4,[Temp_ESP] // Сохраняем полученный результат (адрес) в переменной ESP_4 log ESP_4 // Регистрируем значение ESP_4 mov eip,Address_CALL // Снова переходим на Call 00B30000 sti // Выполняем F7 sub Temp_EIP,5 // Вычитаем 5, и переходим на адрес инструкции PUSH mov OPCode,[Temp_EIP] // Сохраняем значение инструкции PUSH XXXXX and OPCode,000000ff // Удаляем ненужный opcod инструкции PUSH cmp OPCode,00000068 // Проверяем, находимся ли мы на инструкции PUSH je EditPush // Если Да (инструкция push), прыгаем на редактирование. mov Flag,1 // Если Нет, то редактируем только Call 00B30000 jmp section
EditPush: // Процедура для редактирования PUSH mov Flag,0 // Записываем в переменную Flag 0 add Temp_EIP,1 // К значению Temp_EIP прибавляем 1. mov OPCode,[Temp_EIP] // Сохраняем полученное значение адреса в OPCode sub OPCode,VBASE // Вычитаем из адреса PUSH базовый адрес области памяти VOEP. add OPCode,NEWBASE // Прибавляем базовый адрес новой секции файла dump_.exe mov [Temp_EIP],OPCode // Сохраняем полученное значение восстановленной PUSH
section: cmp Flag,1 // Записываем в переменную Flag 1 jne inspection // Прыгаем на метку inspection
increment: add Temp_EIP,1 // Увеличиваем значение Temp_EIP на 1
inspection: add Temp_EIP,4 // Снова переходим на Call 00B30000 mov Field_ESP_4,ESP_4 // Сохраняем в переменной содержимое переменной ESP_4 and Field_ESP_4,ffff0000 // Имеется ли адрес области памяти ESP-4 cmp Field_ESP_4,VBASE // Проверяем, находится ли адрес ESP-4 в области VOEP je EditCall1 // Если Да, прыгаем на EditCall1 (1-й вариант расчета) // Если Нет, переходим на EditCall2 (2-й вариант расчета)
EditCall2: // Редактирование Call 00B30000, если ESP-4 не в области VOEP mov OPCode,Temp_EIP // Записываем адрес Call 00B30000 в переменную OPCode sub OPCode,VBASE // Вычитаем из значения OPCode значение VBASE add OPCode,NEWBASE // Складываем значение OPCode со значением NEWBASE sub ESP_4,5 // Из значения ESP_4 вычитаем 5 sub ESP_4,OPCode // К полученному результату прибавляем значение OPCode mov [Temp_EIP],E9 // Записываем опкод прыжка (E9) вместо опкода Call (E8) add Temp_EIP,1 // Переходим на следующий байт после байта E9 mov [Temp_EIP],ESP_4 // Записываем значение прыжка JMP XXXXXX
check: cmp Address_CALL,End_scan // Проверяем, не последний ли это Call 00B30000 je Message // Если это последний Call 00B30000, прыгаем на message jmp SearchCall // Если нет, ищем следующий Call 00B30000
EditCall1: // Редактирование Call 00B30000, если ESP-4 в области VOEP sub ESP_4,5 // Из значения ESP_4 вычитаем 5 sub ESP_4,Temp_EIP // Из полученного результата вычитаем адрес инструкции Call mov [Temp_EIP],E9 // Записываем опкод прыжка (E9) вместо опкода Call (E8) add Temp_EIP,1 // Переходим на следующий байт после байта E9 mov [Temp_EIP],ESP_4 // Записываем значение прыжка JMP XXXXXX jmp check // Прыгаем на метку check, для проверки, не последний ли Call
Message: MSG "Восстановление CALL и JMP успешно завершено." endscript: mov eip,OEP ret // Завершаем работу скрипта.


Я постарался подробно объяснить код скрипта. Выделенные желтым цветом адреса нужно откорректировать в соответствии с адресами Вашей машины.

Итак, перезагружаем программу, проверяем, нет ли у нас установленных BP, проходим на VOEP с помощью скрипта, после чего запускаем наш скрипт, который должен восстановить инструкции PUSH и CALL, я его назвал - Recovery_PUSH_CALL_VOEP.osc.


Примечание: Следует отметить, что скрипт, при восстановлении CALL 00B30000, который находится по адресу 00950F11, дает сбой. Здесь я поступил следующим образом: сохранил всю область памяти виртуальной машины с частично восстановленными инструкциями PUSH и CALL 00B30000 в Hex Workshop тем же способом, какой мы применяли при восстановлении таблиц INIT и IAT. Перезагрузил программу, и вручную восстановил инструкцию CALL 00B30000, которая находится по адресу 00950F11. Записал адрес прыжка при выполнении этой инструкции CALL 00B30000. Опять перезагрузил программу, прошел на VOEP, вставил данные, сохраненные в Hex Workshop, вручную откорректировал прыжок на адресе 00950F11, после чего опять запустил скрипт. Больше сбоев в работе скрипта не было.



Нажимаем клавиши Alt+C, и переходим на VOEP. Попытаемся найти инструкции CALL 00B30000, которые могли быть не восстановленными, но их нет. Скрипт восстановил все инструкции PUSH и CALL.


Мы видим изменения в коде, выполнена переадресация инструкции PUSH, а инструкция CALL 00B30000 заменена инструкцией JMP на нашу новую секцию. Поэтому можно сказать, что мы закончили работу с виртуальной машиной asprotect, но нужно иметь в виду, что мы не пытаемся очистить код от мусорных инструкций, типа прыжков и каких-то непонятных инструкций, потому что это очень тяжелая и нудная работа. Проще всего выполнить переадресацию PUSH и CALL, скопировать всю эту область памяти VOEP, и вставить ее в dumped_.exe.


Выделяем всю область памяти виртуальной машины, и выполняем бинарное копирование выделенной области памяти:


Распаковка ASProtect 1.35 - часть 3


Сохраняем скопированные данные в Hex Workshop v4.23, как это мы делали при сохранении восстановленных таблиц INIT и IAT. Сохраняем файл с именем Recovery_VM.


В отладчике OllyDbg открываем файл dumped_.exe, а в HexWorkshop - файл Recovery_VM. В окне dump отладчика нажимаем клавиши Ctrl+G, и вводим адрес созданной нами секции .code - 0048C000, куда мы будем вставлять восстановленный код VM.


Переходим на этот адрес, и выделяем всю выбранную секцию файла. В HexWorkshop выделяем все байты файла Recovery_VM:


Распаковка ASProtect 1.35 - часть 3


Копируем все байты, вставляем их в наш файл dumped_.exe:


Распаковка ASProtect 1.35 - часть 3


Сохраняем изменения.


Теперь нам надо изменить OEP файла dumped_.exe, в который мы вставили байты из скопированной области памяти программы. Для этого нужно изменить прыжок на новую секцию, которая теперь содержит OEP. VOEP в оригинальной программе была по адресу 00950294, поэтому OEP будет находиться, в новой секции, по адресу:


00950294 - 00950000 + 0048C000 = 0048C294


Открываем dumped_.exe в Olly, останавливаемся на Entry Point, и, записываем прыжок на адрес 0048C294 (JMP 0048C294):


Распаковка ASProtect 1.35 - часть 3


Сохраняем изменения, перезагружаем dumped_.exe, и видим, что мы восстановили виртуальную машину asprotect. Если мы нажмем клавишу F7, то попадаем на адрес реальной OEP.




Распаковка ASProtect 1.35 - часть 3


8. Доработка полученного дампа, и запуск программы


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


Распаковка ASProtect 1.35 - часть 3


Идет ссылка на адрес 00AE0000, т.е. на область памяти, которая была создана пакером в оригинальной программе. Давайте посмотрим, откуда вызывается этот адрес. Переходим в секцию кода, и выбираем команду Search for → Constant. В появившемся диалоговом окне вбиваем значение 00AE0000:


Распаковка ASProtect 1.35 - часть 3


Нажимаем кнопку ОК, и попадаем сюда:


Распаковка ASProtect 1.35 - часть 3


Эта пара инструкций означает прыжок на область памяти 00AE0000, которой нет в нашем файле.




Давайте загрузим оригинальную программу во второй отладчик, и посмотрим, что у нас имеется по адресу 00AE0000:


Распаковка ASProtect 1.35 - часть 3


Размер кода здесь небольшой, всего 9F байтов, поэтому мы копируем этот код, и вставляем его на свободное место секции кода нашего дампа, по адресу 0047D3D0:


Распаковка ASProtect 1.35 - часть 3


Теперь корректируем инструкцию по адресу 004089FC:


Распаковка ASProtect 1.35 - часть 3


Сохраняем все изменения, перезагружаем программу, и нажимаем клавишу F9. Программа показывает нам следующее сообщение об ошибке:


Распаковка ASProtect 1.35 - часть 3


Здесь идет ссылка на область памяти VM. Откуда вызывается этот адрес? Опять ищем константу 00950708. И находим эту константу здесь:


Распаковка ASProtect 1.35 - часть 3


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


00950708 - 00950000 + 0048C000 = 0048C708


Однако мы не будем сейчас изменять адрес этого прыжка, поскольку таких прыжков в программе может быть очень много. Нам необходимо написать скрипт, который должен найти и восстановить все прыжки, идущие в область VM. Такой скрипт написал PE_Kill; к этому скрипту я добавил свои комментарии. Работа этого скрипта основана на инжекции кода подпрограммы, которая сканирует всю секцию кода, находит прыжки на VM, и изменяет адрес этих прыжков на секцию IAT, куда мы вставили восстановленную VM:



  //Разработчик скрипта: PE_Kill, комментарии - vnekrilov.
  //Описание: Скрипт разработан для восстановления прыжков, идущих в VM, для
  //программы Reactive MYCOP Cleaner v1.2, защищенной ASProtect v1.35 build 06.26.
  //Цель: RMC.EXE
  //Условия применения: ODBGScript 1.48, Olly Advanced v1.26 Beta 12, PhantOm v1.04.
 
var scan_start // Переменная для хранения адреса начала сканирования кода var scan_end // Переменная для хранения адреса конца сканирования кода
var RegionVM_Start // Переменная для хранения адреса начала области VM var RegionVM_End // Переменная для хранения адреса конца области VM var RegionMain_Start // Переменная для хранения адреса начала области // восстановленной VM (в секции IAT)
var OEP // Переменная для хранения адреса OEP var Info // Переменная для записи информации LOG var Counter // Переменная для хранения чтсла обработанных JMP // на область VM var temp // Переменная для настройки работы скрипта
mov Counter,0 // Записываем в счетчик начальное значение - 0 mov RegionVM_Start,00950000 // Адрес начала области VM mov RegionVM_End,00953000 // Адрес конца области VM mov RegionMain_Start,0048C000 // Адрес начала восстановленной области VM в dump_.exe
mov OEP,eip // Сохраняем адрес OEP
mov temp,eip // Записываем в переменную temp адрес OEP mov scan_start,[eip] // Записываем в переменную scan_start байты OEP mov [eip],#6A00# // Записываем в OEP инструкцию PUSH 0 sto // Выполняем F8 add temp,4 // Прибавляем к значению OEP число 4 mov scan_end,[temp] // Полученное значение записываем в переменную scan_end asm eip,"call GetModuleHandleA" // Записываем в EIP вызов API GetModuleHandleA, для // получения в регистре EAX базового адреса нашего файла sto // F8 (Выполняем CALL GetModuleHandleA) mov eip,OEP // Переходим на адрес OEP (Команда Новый EIP здесь) mov [eip],scan_start // Восстанавливаем оригинальные байты OEP mov [temp],scan_end // Записываем адрес конца сканирования кода add eax,1000 // К базовому адресу добавляем 1000 (получаем адрес начала // секции кода) mov scan_start,eax // В переменную scan_start записываем адрес 00401000 // (начало сканирования кода) mov scan_end,eax // В переменную scan_start записываем адрес 00401000 // (начало сканирования кода) gmi scan_start,CODESIZE // Получаем размер секции кода add scan_end,$RESULT // Прибавляем к 401000 размер секции кода, чтобы // получить адрес конца сканирования кода

// Регистрация в LOG основных данных при восстановлении прыжков на VM (для контроля) log "__________________________________________________" log "" log "Запуск переадресации прыжков на Virtual Machine..." log "_____________________________________________________" eval "Диапазон сканирования [{scan_start}..{scan_end}] ..." mov Info,$RESULT log Info

mov eip,scan_start //Выполняем команду Новый EIP здесь на адресе 401000 sub eip,200 // Указываем место для записи кода инжектора (на этом // месте записан мусорный код)
// Записываем код подпрограммы-инжектора mov [eip],#60413BCA73238039E975F6418B0103C183C0043BC672EA3BC773E62BC603C32BC183E804890145EBD861# sto // F8 (выполняем инструкцию PUSHAD) mov ecx,scan_start // Записываем в ECX адрес начала сканирования кода dec ecx // Уменьшаем значение ECX на единицу mov edx,scan_end // Записываем в EDX конец сканирования кода mov ebx,RegionMain_Start // Записываем в EBX адрес начала восстановленной VM в // секции IAT mov ebp,0 // Обнуляем регистр EBP (используем его как счетчик // восстановленных JMP) mov esi,RegionVM_Start // Записываем в ESI адрес начала VM mov edi,RegionVM_End // Записываем в EDI адрес конца VM add eip,28 // Переходим на инструкцию POPAD bp eip // Устанавливаем на эту инструкцию BP, для остановки // программы sub eip,28 // Возвращаем начальное значение EIP run // Запускаем инжектор bc eip // Удаляем BP mov Counter,ebp // записываем число восстановленных JMP в переменную // Counter sto // F8 (Выполняем инструкцию POPAD) sub eip,2A // Возвращаемся в начало кода подпрограммы-инжектора fill eip,30,00 // Заполняем код подпрограммы-инжектора нолями mov eip,OEP // Переходим на OEP (Выполняем команду Новый EIP здесь) bp eip // Устанавливаем BP на OEP ai // Выполняем Animate into в OllyDbg bc eip // Удаляем BP
// Оцениваем число восстановленных прыжков eval "Готово! Всего было восстановлено {Counter} прыжков!" msg $RESULT // Выводим сообщение pause // Делаем паузу в работе скрипта ret // Выход из скрипта

Подпрограмма-инжектор выглядит следующим образом:




Распаковка ASProtect 1.35 - часть 3


Вначале, с помощью инструкции PUSHAD мы сохраняем в стеке значения всех регистров, и начинаем поиск в секции кода инструкции JMP (опкод E9), увеличивая значение регистра ECX на 1. Как только найдена инструкция прыжка, проверяем назначение этого прыжка (прыгает ли он на область памяти VM). Если он не прыгает на область памяти VM, производим поиск другого прыжка, (поиск прыжка выполняется с адреса нахождения предыдущего прыжка). Если же прыжок выполняется на область VM, то из его адреса вычисляется адреса начала области VM, к полученному адресу прибавляется базовый адрес в секции IAT, куда мы вставили восстановленную VM, вычисляются байты прыжка на восстановленную VM, и корректируется адрес назначения этого прыжка.


Запускаем скрипт, и через секунду получаем сообщение:


Распаковка ASProtect 1.35 - часть 3


Сохраняем сделанные изменения в файле, перезагружаем программу, нажимаем клавишу F9, и получаем следующее сообщение об ошибке:


Распаковка ASProtect 1.35 - часть 3


Нажимаем кнопку ОК, и появляется следующая заставка:



Распаковка ASProtect 1.35 - часть 3


Знакомая заставка! И та же самая процедура работы. Переходим в секцию кода, и выбираем команду Search for → Constant; вводим в диалоговое окно значение 00AF0000, и оказываемся здесь:


Распаковка ASProtect 1.35 - часть 3


Размер кода в этой области памяти - небольшой, всего 8B байтов. Поэтому копируем этот код из оригинальной упакованной программы, и вставляем его после предыдущего вставленного кода, по адресу 0047D470:


Распаковка ASProtect 1.35 - часть 3


И корректируем значение инструкции PUSH:


Распаковка ASProtect 1.35 - часть 3


Сохраняем изменения, перезагружаем программу, и нажимаем клавишу F9. Получаем еще одно сообщение об ошибке:


Распаковка ASProtect 1.35 - часть 3


Нажимаем кнопку ОК, и появляется следующее сообщение об ошибке:



Распаковка ASProtect 1.35 - часть 3


При распаковке программ, упакованных Asprotect, мне приходилось встречаться с неправильным адресом назначения прыжка, получаемого скриптом из инструкции CALL XXXXXXXX. Причина получения неправильного адреса назначения прыжка заключается в том, что скрипт определяет его по назначению инструкции JMP DWORD PTR SS:[ESP-4], когда программа остановлена на VOEP. Скрипт поочередно ищет инструкции CALL XXXXXXXX, затем определяет назначение прыжка в инструкции JMP DWORD PTR SS:[ESP-4], после чего заменяет инструкцию CALL инструкцией JMP. При обработке уже первого CALL XXXXXXXX, меняются значения регистров. А, при запуске программы, также меняются значения регистров, что приводит к ошибкам в определении реального адреса прыжка в инструкции JMP DWORD PTR SS:[ESP-4]. Естественно, что при запуске дампа, прыжок выполняется не на тот адрес, с которого программа должна продолжить свою работу, и возникает сбой в работе программы.


Решение здесь только одно - запустить в двух отладчиках оригинальную упакованную программу и полученный дамп, и проверить назначение прыжка JMP DWORD PTR SS:[ESP-4] в оригинальной программе, с назначением прыжка в полученном дампе. Это - кропотливая и нудная работа, но другого решения этой задачи я не нашел.


И, действительно, довольно быстро, получаем следующие данные; при выполнении инструкции CALL 00B30000, расположенной по адресу 009509CA, мы видим:


Распаковка ASProtect 1.35 - часть 3


А в нашем дампе по этому адресу мы видим следующее:


Распаковка ASProtect 1.35 - часть 3


Поэтому корректируем адрес назначения прыжка на 0048C528:


Распаковка ASProtect 1.35 - часть 3


Сохраняем изменения, и нажимаем клавишу F9:


Опять появляется сообщение об ошибке:


Распаковка ASProtect 1.35 - часть 3


Нажимаем клавишу ОК, и программа зависает. Почему? Опять переходим в оригинальную программу. После нескольких остановок на инструкциях CALL 00B30000, мы опять приходим на адрес 009509CA, но видим уже другой адрес прыжка:


Распаковка ASProtect 1.35 - часть 3


Мы видим здесь то значение, которое было восстановлено скриптом. Получается очень интересная ситуация. При выполнении инструкции CALL 00B30000, которая находится по адресу 009509CA, сначала программа прыгает на адрес 00950528, а при втором выполнении этой же инструкции, программа прыгает на адрес 00951547. Значит, нам нужно в дампе сделать динамическую замену этого адреса; сначала выполнить прыжок на адрес 0048C528, а затем - на адрес 0048D547. Как это можно сделать? После первого выполнения инструкции JMP 0048C528, которая у нас в дампе находится по адресу 0048C9CA, нам нужно изменить назначение этого прыжка на адрес 0048D547. Я нашел следующее место для патчирования этой инструкции, выполняя трассирование программы после выполнения инструкции JMP 0048C528:


Распаковка ASProtect 1.35 - часть 3


Здесь у нас имеется длинный прыжок на адрес 0048CBFE. Мы можем изменить адрес этого прыжка на свободное место в конце кода этой секции файла, чтобы написать там прививку для патчирования проблемного прыжка. У меня получилось так:


Распаковка ASProtect 1.35 - часть 3



И, сама прививка:


Распаковка ASProtect 1.35 - часть 3


Вместо имеющихся байтов назначения прыжка по адресу 0048C9CA, мы, после первого выполнения этой инструкции, записываем новые байты назначения прыжка, чтобы получить инструкцию JMP 0048D547.


Действительно, при втором выполнении инструкции JMP по адресу 0048C9CA, мы видим:


Распаковка ASProtect 1.35 - часть 3


Нажимаем клавишу F9, и опять появляется знакомое нам сообщение об ошибке:


Распаковка ASProtect 1.35 - часть 3


Нажимаем кнопку ОК, и программа запускается:


Распаковка ASProtect 1.35 - часть 3


Нам нужно убрать появление сообщения об ошибке перед запуском программы. Давайте посмотрим в окно регистрации LOG, нажав кнопку “L” на панели кнопок OllyDbg:


Распаковка ASProtect 1.35 - часть 3




В окне LOG видим генерацию исключения 0EEDFADE по адресу 7C81EB33. Проходим на этот адрес в отладчике, и видим, что выше этого адреса находится инструкция CALL, которая и генерирует это исключение:


Распаковка ASProtect 1.35 - часть 3


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


Распаковка ASProtect 1.35 - часть 3


Перезагружаем программу, нажимаем клавишу F9, и программа устанавливается на BP. В окне стека видим следующее:


Распаковка ASProtect 1.35 - часть 3


RaiseException вызывается из 00474744. Переходим на этот адрес в отладчике, и устанавливаем на него BP:



Распаковка ASProtect 1.35 - часть 3


Перезагружаем программу, нажимаем клавишу F9, и останавливаемся на BP. Входим в инструкцию CALL 0040289C с F7:


Распаковка ASProtect 1.35 - часть 3


Если мы дойдем в дампе до прыжка JNZ SHORT 004028B1, то этот прыжок выполняется, приводит нас к генерации исключения. В оригинальной упакованной программе, этот прыжок не выполняется, и программа нормально загружается:


Распаковка ASProtect 1.35 - часть 3


Причиной выполнения прыжка является то, что содержимое адреса 00153104 равно 8, а не 0, как в оригинальной упакованной программе. Причем значение 8 записывается при загрузке программы в память машины. Чтобы не усложнять нам жизнь, просто заменим инструкцию JNZ инструкцией NOP, и сохраним эти изменения:


Распаковка ASProtect 1.35 - часть 3


Перезагружаем программу, нажимаем клавишу F9, и программа нормально запускается, без каких-либо сообщений об ошибках:


Распаковка ASProtect 1.35 - часть 3


Попытаемся закрыть программу, и нам появляется следующее сообщение об ошибке:


Распаковка ASProtect 1.35 - часть 3


Почему же появилось это сообщение? Видимо, опять где-то записан неверный адрес прыжка JMP, которым мы заменили инструкцию CALL 00B30000. Поэтому продолжим работу в двух отладчиках; в одном отладчике загружена оригинальная упакованная программа, а во втором отладчике - файл dumped_.exe. Запускаем обе программы в отладчике, после чего закрываем программы, и смотрим адреса назначения прыжков JMP в оригинальной упакованной программе, и дампе dumped_.exe. Находим еще 4 прыжка JMP с неправильным адресом назначения:


Распаковка ASProtect 1.35 - часть 3


Распаковка ASProtect 1.35 - часть 3


Распаковка ASProtect 1.35 - часть 3




Распаковка ASProtect 1.35 - часть 3


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


9. Заключение


Этот туториал оказался большим, и занял у меня достаточно много времени (во всяком случае, больше, чем я предполагал). Я думаю, что изложенный здесь материал окажется полезным как для начинающих cracker’s, так и для профессионалов в области реверсинга программ.

Я хотел бы поблагодарить Gideon Vi, за предоставленную им упакованную программу, на базе которой я написал этот туториал. Также мне хочется выразить признательность bronco, за его моральную поддержку и пожелания в написании туториалов. Хотелось бы выразить признательность PE_Kill за его прекрасную работу “Распаковка ASProtect v 2.xx (отрезание секций, восстановление скрамблерного кода, декомпиляция VM, восстановление импорта, инлайн-патч)”, из которой я почерпнул много полезной для себя информации, и использовал его прекрасный скрипт для восстановления адресов из секции кода в область расположения виртуальной машины.

И мне хотелось бы пожелать всяческих успехов всем читателям CRACKL@B, которые занимаются очень интересным процессом реверсинга программ.


До встречи в следующем туториале.


vnekrilov



Скачать статью "Распаковка ASProtect 1.35 (на примере Reactive MYCOP Cleaner 1.2), часть 3" в авторском оформление + файлы.



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


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