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

ВИДЕОКУРС ВЗЛОМ
выпущен 2 июля!


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


Взлом SafeDisc Advanced и исследование SDAPI второй версии




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

Исследование защитных функций SDAPI второй версии на примере Need For Speed Most Wanted 1.2


Автор : DillerInc

Содержание

Введение

Портрет героя

Этап второй - преддверие протектора

Секция данных защищённого файла, структуры протектора

Этап третий - библиотека ~df394b.tmp

Приоткрываем завесу...

Секция данных 'revisited' - Trigger Tables

Пару мыслей напоследок

Благодарности



Введение


Всё нижеизложенное предназначено только в образовательных целях ‼


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

В качестве примера мы рассмотрим игру "Need For Speed: Most Wanted" версии 1.2 от компании EA GAMES.Подобный выбор обусловлен не только широкой популярностью этой игры,но и некоторыми иными обстоятельствами, которые прояснятся по ходу повествования.

Кстати,стоит заметить,что именно EA GAMES является одной из немногих крупных компаний,которая сейчас активно использует данную “усиленную” вариацию протектора SafeDisc,защищая ею свои игровые релизы. Подобное обстоятельство обусловливает также тот факт,что версия протектора в этих релизах не поддаётся анализу стандартными методами,поэтому сейчас в качестве небольшого отступления от основной темы я позволю себе прояснить данную ситуацию:


Методы получения версионной информации


...говоря о “стандартных методах” определения версии протектора, я имею ввиду поиск в заголовке PE-файла строки “BoG_ *90.0&!! Yy>”, после которой идут значения типа Unsigned Long,которые и являют собой версию. Однако,как я уже упоминал,данная строка может быть просто затёрта. Именно в таких случаях можно воспользоваться следующим методом:


offset stxt774 + vsize stxt774 + 38h


...где:

offset stxt774 - смещение секции в файле

vsize stxt774 - виртуальный размер секции


По получившемуся смещению считываем три значения того же типа Unsigned Long. Вышеприведёнными способами мы получаем стандартный вид версии:

* например, 4.60.00

Для особых гурманов можно предложить также возможность узнавать полную версию протектора:

* например, 4.60.00.1702 (2005/08/30 )

...считывая её в виде ресурса из одного из файлов,которые хранятся во временной директории во время функционирования защищённого приложения. Необходимый файл может иметь разные названия в различных случаях,но его т.н. InternalName(смотрите справку о ресурсах на каком-нибудь MSDN) будет всегда "AuthServ".

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


Портрет героя


На мой взгляд,SDAPI второй версии как защитный механизм довольно резко выделяется на фоне всего остального,что предлагает протектор SafeDisc(далее SD).Под “всем остальным” я подразумеваю тут навесную сущность большинства механизмов протектора(переходники импорта, наномиты и т.д.).

Цитата: Relayer, author of ExeCryptor

То что навесные протекторы рано или поздно снимаются раз и навсегда - было тоже давным-давно известно. Нужно было найти некоторый универсальный метод, который позволял бы достаточно надежно защитить код от исследования и добиться достаточно прочного «сращивания» кода протектора с кодом приложения.

Именно с помощью второй версии SDAPI компания Macrovision,занимающаяся разработкой SD,сделала достаточно серьёзный шаг к тому самому “сращиванию”.

Ранее существовала также и первая версия защитного механизма,принцип которого однако был абсолютно другим - что-то связанное с шифрованием отдельных функций.

SDAPI второй версии представляют из себя в большинстве случаев своего рода вычислительные функции,прочно вшиваемые в основной код защищаемого приложения и работающие примерно по следующему принципу: посредством определённых параметров и таблиц значений протектор с помощью этих функций высчитывает необходимые величины и возвращает их в защищённое приложение. Среди возвращаемых результатов вычислений могут быть как значения протектора, использующиеся для последующих вычислений,так и переменные/константы,необходимые самому приложению для функционирования.

SDAPI,вносимые программистом,вероятнее всего,в исходник защищаемого приложения,образуют таким образом единое целое с основным кодом.

Вот небольшой пример использования этих функций в NFSMW:


Код - вариант вызова SDAPI


 			 			.text:006E7751                 push    ebx      			               ; 			bMenu
 			 			.text:006E7752 			               push    			54052D06h 			      ; значения протектора
 			 			.text:006E7757                push 			   54052D06h
 			 			.text:006E775C               call 			     sub_6E6F90      ; вызов 			SDAPI
 			 			.text:006E7761                 add      			esp, 8
 			 			.text:006E7764         			       push    eax                     ; dwStyle  			
 			 			.text:006E7765                 lea      			 eax, [esp+4+X]
 			 			.text:006E7769                 push    eax      			                ; lpRect
 			 			.text:006E776A                call 			     ds:AdjustWindowRect
 			


...после вызова SDAPI в данном случае в регистре EAX оказывается значение 80000000h,которое соответствует стилю окна WS_POPUP.Чуть выше по коду находятся ещё вызовы функций протектора,которые,так сказать,подготавливают почву для получения “полезного” значения,которое необходимо самой игре.

Этот пример в некотором роде иллюстрирует,что такой подход как пропатчивание (NOPим то,что принадлежит протектору, и вставляем свою полезную команду) здесь не совсем уместен.


Объяснение этому следующее:

* во-первых,нет никакой твёрдой гарантии,что возвращаемый результат отдельно взятой SDAPI будет при любых условиях одинаков.

* во-вторых,пойдя путём подобного патча, придётся - уж поверьте мне - изгадить NOP’ами и тому подобным добрую часть секции кода.


Тело защитных функций можно разложить на несколько частей - этапов:


  • Этап первый - непосредственный вызов API протектора из секции кода защищённого приложения. Перед этим в стек,как правило,помещаются два параметра,которые и определяют в некоторой степени выходящий результат. Далее происходит вызов,осуществляемый обычной командой CALL.

  • Этап второй - т.н. преддверие протектора,находящееся тоже в секции кода.

  • Этап третий - сам код протектора,находящийся в отдельном модуле.


Последние два требуют остановки и более подробного рассмотрения.


Этап второй - преддверие протектора


Нашей отправной точкой будет секция кода защищённого файла.

Поместив в стек два параметра и последовав за командой CALL, мы окажемся в следующем месте:


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


Код - Преддверие протектора


 			.text:00666E40 			            sub     esp, 20h
 			.text:00666E43 			            push    esi
 			.text:00666E44 			            mov     esi, dword ptr 			[.data] section
 			.text:00666E4A 			           test    esi, esi
 			.text:00666E4C 			           jnz     short loc_666E57
 			.text:00666E4E 			           mov     eax, dword ptr 			[.data] section
 			…
 			.text:00666E57 			            push    ebx
 			.text:00666E58 			            mov     ebx, dword ptr 			[.data] section
 			.text:00666E5E 			            push    edi
 			.text:00666E5F 			            mov     edi, dword ptr 			[.data] section
 			...
 			.text:00666E73 			            call    CryptParamsArray
 			.text:00666E78 			            lea     eax, [esp+2Ch+var_20]
 			.text:00666E7C 			            cdq
 			.text:00666E7D 			            push    edx
 			.text:00666E7E 			             push    eax
 			.text:00666E7F 			             push    ebx
 			.text:00666E80 			             push    edi
 			.text:00666E81 			             call    esi             			
 			…
 			.text:00666E8C 			             retn
 			


…я намеренно опустил в данном листинге некоторые команды,оставив только наиболее существенные на данный момент. В любом случае в секции кода можно без труда найти одно из таких преддверий(их достаточно много) по сигнатуре:

 83h ECh 20h 56h 8Bh 35h
 

Итак,для начала стоит сконцентрировать общее внимание на функции CryptParamsArray(название моё),где можно увидеть следующий код:


Код - функция CryptParamsArray


 			.text:0065FC40 			            push    ecx
 			.text:0065FC41 			            push    esi
 			...
 			.text:0065FC50 			            mov     edx, [esp+4]
 			.text:0065FC54 			            and     edx, 3
 			.text:0065FC57 			           mov     eax, 			dword_8F7648[edx*4]  ; 1. DWORD пары
 			.text:0065FC5E 			          mov     edx, 			dword_8F7658[edx*4] ; 2. DWORD 			пары
 			...
 			.text:0065FC6B 			           mov     dword ptr [ecx+4], 			10804201h
 			.text:0065FC72 			            mov     dword ptr [ecx+8], 			10804205h
 			.text:0065FC79 			            mov     dword ptr [ecx+0Ch], 			10804201h
 			...
 			...много команд XOR, 			IMUL
 			...
 			.text:0065FCEA 			           retn    8
 			


Основная идея данной процедуры - это шифровка данных,передаваемых далее в протектор. Выполняется это с помощью определённых значений из секции данных защищённого файла. Дело в том,что в этой секции по адресам 008F76xx и 00900Axx находятся два массива,из которых с некоторой долей вероятности выбираются два DWORD’а, участвующих в зашифровке входных данных посредством команд XOR и IMUL.

К примеру,в качестве тех двух DWORD’ов возьмём пару значений - F6B234CFh и A216268Bh - в итоге мы получим зашифрованный массив, который я и назвал ParamsArray и который будет выглядеть примерно так:

SafeDisc Advanced cracking

Указатель на этот массив мы помещаем в регистр EAX сразу после вызова функции CryptParamsArray.Заметьте,что первое значение пары в конце процедуры затирается в то время,как второе значение из пары остаётся неизменным и занимает первое место в массиве - оба будут играть ключевую роль в последующей расшифровке.

Если проследить какие значения зашифровываются,то у нас должно создастся некое представление о передаваемых данных. В их число безусловно входят два параметра протектора,которые кладутся в стек при вызове SDAPI, в добавок мы наблюдаем два значения,находившиеся в тот момент случайным образом над вершиной стека,а также три константы(я отметил их в листинге синим цветом).Последние в дальнейшем будут определять общий ход вычислений в теле протектора,посему являются достаточно важными вехами.

Взглянув вновь на листинг Преддверия...,можно заметить,что данный код активно что-то берёт из секции .data - что ж...будем разбираться.


Секция данных защищённого файла, структуры протектора

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

Последовав по тому адресу,откуда берётся первый DWORD для регистра ESI,мы окажемся в секции .data и увидим значение 6672F2F7h.

Если присмотреться внимательнее и прокрутить вверх/вниз шестнадцатеричные “колонны”(например, в WinHex),то можно заметить некий порядок расположения близлежащих данных.

Дабы сразу прояснить ситуацию,скажу,что здесь мы имеем дело с таблицей, элементами которой являются экземпляры структуры SDArgsStruc(смотрите ниже), содержащие определённую информацию,необходимую для функционирования тех самых SDAPI.

Элементов в таблице(экземпляров структуры),как правило,бывает 40h штук.

Размер структуры составляет 184h байт.


Определение этой структуре я дал следующее:

 SDArgsStruc     struc
      LeadIn                           dd        ?                     ; 67231341h - маркер начала  
      SDArgsStrucSize          dd        ?                     ; 184h  
      LeadIn_                         dd        ?                     ; 00000001h
      MagicAreaOrdinal        dd        ?             
      TrashDwordA               dd        ?                     ; 00
      HeapHead                     dd        ?                 
      HeapArray                    dd        8 dup(?)
      SDRoutineAddr            dd        ?
      TrashDwordB               dd        ?                    ; 00  
      SDRoutineAddrDouble dd       ?
      TrashBytesArrayA124  db        124 dup(?)    ; 00
      MagicAreaPtr                dd         ?
      TrashDwordC                dd         ?                  ; 00
      TrashBytesArrayB112  db        112 dup(?)    ; 00
      HeapArrayAux             dd         8 dup(?)
      TrashBytesArrayC32    db        32 dup(?)      ; 00
      PtrToCurrentStruc        dd         ?                   ; Указатель на начало структуры
      DataSectionOrdinal      dd         ?  
      LeadOut                        dd         ?                   ; 78822408h - маркер конца
 SDArgsStruc     ends                  
  

Отмечу сразу на всякий случай,что поля типа Trash* - это всего-навсего нулевые мусорные байты.


Разбирая эту структуру,стоит отметить,что изюминкой каждой является двойное слово HeapHead и массив HeapArray,состоящий из восьми DWORD’ов.Сейчас однако мы не будет останавливаться на них, т.к. будет более разумно сделать это тогда,когда мы совершим погружение в защитный код SD.

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

41h 13h 23h 67h

...с этих байт начинается каждая структура,своего рода маркер.


Сейчас же мы рассмотрим другие немаловажные поля структуры на подходящем примере.

Имея теперь ввиду особенности секции .data, можно обработать в IDA необходимый кусок данных. В результате у нас получится примерно такой листинг:


Код - Преддверие протектора


 			.text:00666E40 			               sub     esp, 20h
 			.text:00666E43 			               push    esi
 			.text:00666E44 			              mov     esi, 			stru_8FACC8.SDRoutineAddr
 			...
 			.text:00666E4E 			               mov     eax, 			stru_8FACC8.TrashDwordB
 			...
 			.text:00666E58 			               mov     ebx, 			stru_8FACC8.TrashDwordC
 			...
 			.text:00666E5F 			              mov     edi, 			stru_8FACC8.MagicAreaPtr
 			...
 			.text:00666E73 			                call    CryptParamsArray
 			.text:00666E78 			                lea     eax, [esp+2Ch+var_20]
 			...
 			.text:00666E81 			               call    esi ; в модуль ПРОТЕКТОРА !
 			.text:00666E83 			                add     esp, 10h
 			…
 			.text:00666E8C 			               retn
 			


Особенно интересными тут являются поля SDRoutineAddr и MagicAreaPtr:

  • SDRoutineAddr - это адрес процедуры протектора,где собака как раз и зарыта. В данном случае это 6672F2F7h - именно туда мы и переходим по команде CALL ESI.

  • MagicAreaPtr представляет из себя указатель на область в куче исследуемого процесса,где хранится весьма важная информация.

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

    SafeDisc Advanced cracking

    Начало выглядит знакомым,не правда ли??

    Ларчик на самом деле открывается просто - в куче находится та же структура SDArgsStruc только со слегка изменёнными составляющими(полями).Подобное сходство поможет нам в дальнейшем.

    Двойное слово,стоящее следом за единицей - MagicAreaOrdinal - является идентичным полю DataSectionOrdinal соответствующей структуры в секции данных. Исходя из названия,это значение есть своего рода порядковый номер структуры,поэтому,можно,например,заключить,что поле DataSectionOrdinal последней структуры в секции данных будет равно 0000003Fh.

    Далее в памяти следуют девять DWORD’ов...точнее один + восемь DWORD’ов,которые особенным образом связаны с полями HeapHead и HeapArray,но к которым однако мы вернёмся немного позже.

    Итак.

    CALL ESI

    Глубокий вдох...мы в теле протектора.

      Этап третий - библиотека ~df394b.tmp

    Сердцем протектора SD является библиотека ~df394b.tmp.При запуске защищённого приложения она расшифровывается из оверлея,прикреплённого к защищённому файлу,и помещается во временную директорию,откуда потом и загружается в адресное пространство основного процесса с помощью функции LoadLibraryA.После этого происходит получение адреса и вызов функции инициализации протектора - Ox12121212 - находящейся в этой библиотеке(для справки: первый символ названия функции - это заглавная “O”, а не нуль).Во время этой инициализации генерируются некоторые величины,с которыми нам скоро придётся столкнуться.

    Оказавшись,наконец-то,в протекторе по адресу 6672F2F7,нас будет интересовать процедура 6672F045,являющаяся главной веткой кода,от которой берёт своё начало всё остальное.

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

    Где начало того конца,которым оканчивается начало??”

    Главным образом это обусловлено тем,что код защитной библиотеки в данном случае(в данной версии протектора) содержит порядочную порцию метаморфа.

      Цитата: Ms-Rem

      SafeDisc Advanced cracking

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

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

  1. Создание массива HeapArray

  2. Получение значения необходимого для расшифровки массива ParamsArray

  3. Сама расшифровка массива ParamsArray

  4. Получение значения CASE для первого оператора switch..case

  5. Первый оператор switch..case

  6. Получение значения CASE для второго оператора switch..case

  7. Второй оператор switch..case

  8. Вычисление конечного значения

    Итак,взглянем на начальный код процедуры 6672F045:

      Код - процедура 6672F045

      
       					.text:6672F045  					       mov     eax, offset 					loc_6677E55E
       					.text:6672F04A  					      call    __EH_prolog
       					.text:6672F04F  					       sub     esp, 0CCh
       					.text:6672F055  					        push    ebx
       					.text:6672F056  					        push    esi
       					.text:6672F057 					        mov     esi, [ebp+8]
       					

    В регистр ESI передаётся указатель на зашифрованный массив ParamsArray.Для расшифровки массива нам необходимо первое значение из той пары,с помощью которой мы зашифровывали массив,но оно оказывается недоступным, т.к. в конце процедуры CryptParamsArray оно затирается. Второе же значение из пары находится в неизменном виде в самом начале массива. Именно с помощью него мы и получаем/восстанавливаем первое значение пары. Однако для этих вычислений нам понадобятся дополнительные данные,а именно - массив HeapArray.

      Код - процедура 6672F045

      
       					.text:6672F069        					         call    sub_66716F10
       					.text:6672F06E        					         and     dword ptr [ebp-4], 0
       					.text:6672F072        					         lea     eax, [ebp-84h]
       					.text:6672F078        					         push    eax
       					.text:6672F079  					               lea     eax, [ebp-0BCh]
       					.text:6672F07F        					         push    eax
       					.text:6672F080        					         lea     ecx, [ebp-14h]
       					.text:6672F083 					               call    					sub_6672620A
       					.text:6672F088        					         mov     ecx, eax
       					

    Собственно нашей целью на данный момент является процедура 6672620A,но прежде чем зайти в неё,я хотел бы объяснить один момент,касающийся представления разного рода значений/параметров в коде протектора.

    Дело в том,что важные значения - результаты вычислений - не гуляют по коду просто так. Они шифруются. Есть как минимум две функции,отвечающие за это - 66716F10(которую можно увидеть в представленном выше листинге) и 66716F53.

    Работают они по примерно следующему принципу - перед тем,как задействовать какое-либо значение в вычислении,последнее расшифровывается. После получения результата,последний снова зашифровывается и передаётся дальше по коду.

      Код - процедура 66716F10

      
       				
    .text:66715658 call sub_667146F8 .text:6671565D mov ecx, eax .text:6671565F xor ecx, @@Value .text:66715662 mov eax, ecx .text:66715664 not eax .text:66715666 xor eax, ecx .text:66715668 and eax, 32884FD1h .text:6671566D not ecx .text:6671566F xor eax, ecx

    ...зашифровка либо расшифровка значения @@Value происходит при помощи особого значения - маски - которое в этой версии протектора является динамическим, т.е. при каждом запуске защищённого приложения вычисляется новая маска. Цель подобной хитрости - максимально запутать исследователя, потому как при каждом новом запуске он будет сталкиваться с совершенно новыми и непривычными зашифрованными значениями.

    Маска появляется в регистре EAX после вызова процедуры 667146F8, где можно увидеть следующее:

      Код - процедура 667146F8

      
       					...
       					.text:667146FE  					              cmp     dword_667A03F8, 0
       				
    .text:66714705 mov ecx, dword_667ADB0C .text:6671470B jnz short loc_6671471C ; прыгаем .text:6671470D imul ecx, ecx .text:66714710 imul ecx, 0CB495FD8h .text:66714716 mov dword_667ADB0C, ecx .text:6671471C test ecx, ecx .text:6671471E jnz short loc_66714779 ; прыгаем ...

    Если инициализация протектора прошла успешно,то необходимые значения были сгенерированы и размещены по своим ячейкам,следовательно мы совершаем прыжок в обоих случаях условных переходов приведённого листинга. Конкретно нас здесь интересует ячейка памяти по адресу 667ADB0C,где и размещается та самая маска.

    Мы можем отследить запись по этому адресу,установив туда аппаратную точку останова на чтение/запись.

    Что это даст?

    Немного удобства при исследовании. Мы сможем тогда каждый раз подменять новое значение уже известным и привычным(однажды сгенерированным), дабы не так сильно путаться среди зашифрованных данных.

    Итак, заходим внутрь процедуры 6672620A:

      Код - процедура 6672620А

      
       				
    .text:6672620A mov eax, offset loc_6677D175 .text:6672620F call __EH_prolog .text:66726214 sub esp, 1FCh ... .text:66726235 call sub_66725B7D

    Если говорить в общем,то данная процедура вызывается три раза в течение одного сеанса(вызова SDAPI),высчитывая и возвращая каждый раз особенные величины,необходимые для функционирования защитного кода в целом.

    В первом случае процедура получает/восстанавливает первое значение из пары,с помощью которого затем расшифровывается массив ParamsArray.Но как я уже упоминал,для этого необходимо создать массив HeapArray.Именно поэтому в самом начале процедуры 6672620A незаметно притаилась процедура 66725B7D, которая как раз этим и занимается.

    Массивы HeapArray являются чрезвычайно важными элементами защитного механизма SDAPI, т.к. выступают в роли своего рода посредника в наиболее существенных вычислениях. Являясь составной частью каждого экземпляра структуры SDArgsStruc,любой конкретный массив HeapArray оказывается логически связанным с полем *Ordinal своей структуры. Отсюда можно вновь сделать вывод,что максимальное количество возможных массивов равняется числу 40h.

    Для каждого массива генерируется восемь значений типа DWORD.Ключевым элементом,принимающим участие в вычислении каждого члена массива является

    соответствующий массив из восьми DWORD’ов,который находится в куче, в области,на которую указывает MagicAreaPtr.

    Массив,находящийся в “магической области”, я назвал MagicArray.

    Ответственный код протектора, сгенерировав один раз массив для определённого Ordinal’а,выставляет специальную метку,которая предотвращает повторное получение того же массива.

    На этот случай существуют две аналогичные проверки в начале процедуры:

      Код - процедура 66725B7D

      
       				
    ... .text:66725BA3 call sub_66720075 .text:66725BA8 or dword ptr [ebp-4], 0FFFFFFFFh .text:66725BAC test al, al ; Если массив уже получен, .text:66725BAE jz loc_66725CDF ; тогда прыгаем на выход

Если вспомнить,то в куче помимо восьми DWORD’ов(массива MagicArray) находился ещё один DWORD. Так вот это значение (например, 9BE6BF89h) соответствует полю HeapHead структуры SDArgsStruc и тоже проходит процесс вычисления. Происходит это здесь:

      Код - процедура 66725B7D

      
       				
    ... .text:66725C04 call dword ptr [ebx+8] ; [ebx+8] = 667286ED .text:66725C07 push eax .text:66725C08 mov ecx, edi .text:66725C0A mov byte ptr [ebp-4], 3 .text:66725C0E call sub_66725199

    Но на самом деле значение поля HeapHead для любого массива HeapArray всегда будет неизменным - 5CAC5AC5h - поэтому я не особо вдавался в подробности вычисления этой величины. Чего однако нельзя сказать о самом массиве HeapArray,код получения которого необходимо не только разобрать, но и потом реализовать самостоятельно. В общем,ближе к делу.

    Все восемь значений массива вычисляются в цикле:

    Код - процедура 66725B7D

    
     					...
     					.text:66725C32 loc_66725C32:
     					...
     				
.text:66725C4E call sub_66725882 .text:66725C53 test al, al ... .text:66725C5F jz short loc_66725CC6 ; выходим из цикла ... .text:66725CC1 jmp loc_66725C32


Для полученных значений подготавливаются ячейки в буфере,находящемся также в куче(именно поэтому я назвал этот массив HeapArray):


Код - процедура 66725B7D


 				.text:66725C64 				    call    sub_66716F53  ; 				достаём индекс элемента массива
 				.text:66725C69 				    imul    eax, 1Ch
 				.text:66725C6C 				   mov     ecx, [esi+94h] ; в 				ECX кладём указатель на буфер  				
 				.text:66725C72 				    add     eax, ecx            				; высчитываем смещение в буфере
 				


Основные же вычисления происходят опять в процедуре 667286ED, которая на этот раз вызывается с помощью команды:

.text:66725C88 call dword ptr [edx+8]

Зайдя вовнутрь,необходимо сразу перейти к процедуре 6672619F, т.к. именно в ней происходит то,что нам нужно. Заглянем туда и выделим основные точки интереса.


Код - процедура 6672619F


 				...
 				.text:667261C0 				                call    dword ptr [eax+0Ch] ; 66725EA0
 				...
 				.text:667261D4 				                call    dword 				ptr [eax+0Ch] ; 66725EA0
 				...
 				.text:667261E2 				                call    sub_66725175
 				...
 				.text:667261F2 				                call    sub_667258E3
 				...
 				


Теперь давайте вспомним о том,что я говорил о метаморфе. В качестве примера возьмём функцию 66725175, которую я назвал GetDerivative(от англ. derivative - производное).Ядром этой функции является следующий код:


Код - функция GetDerivative


 				.text:66720CE2 				          call    				ds:off_66789EA8 ; MFC 				3.1/4.0/4.2/7.1 32bit
 				.text:66720CE8 				           push    4B495FD8h
 				.text:66720CED 				          push    109h
 				.text:66720CF2 				           push    2
 				.text:66720CF4 				           lea     ecx, [ebp-24h]
 				.text:66720CF7 				          call    sub_667388E2
 				


В обоих вызовах применяются некие библиотечные функции языка C , в которых происходят просто неимоверные вычисления.

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


Отмечу,что функция GetDerivative встречается в коде протектора в двух ипостасях, немного отличающихся выполняемыми командами. Этот момент я передал с помощью параметра WAY,который определяет какое вычисление необходимо произвести в конкретном случае.



 				Код - 				функция GetDerivative 				без метаморфа
 				
GetDerivative proc arg @@ValueA:DWORD, @@ValueB:DWORD, @@WAY:DWORD uses edi mov edi, @@ValueB mov eax, @@ValueA cmp dword ptr @@WAY, 00 jne @@m1 imul eax, edi jmp @@ret @@m1: add eax, edi @@ret: ret GetDerivative endp


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

либо - к сложению. Чтобы ощутить разницу,советую немного пошерстить тамошний код протектора.

У вас,читатели,наверное возникнет вопрос: “Как же он смог это сделать?”

На самом деле всё очень просто - всё познаётся в сравнении.

Дело в том,что не во всех версиях протектора(...или же конкретных случаях) используется такая обфускация. Поэтому исследование множества примеров может быть весьма эффективным в плане общего понимания материала.


Функция 667258Е3 также стоит того,чтобы её детально рассмотрели. Здесь необходимо обратить внимание на следующий кусок кода:




 				Код - 				процедура 667258Е3
 				...
 				.text:66725921 				                mov     dword ptr [ebp-10h], 24C3E94Dh
 				.text:66725928      				           call    sub_667173DF
 				…
 				


Суть функции в том,что она принимает два параметра и может производить над ними одну из трёх операций: XOR, AND или OR.

Какая именно операция будет совершена,или,иными словами,какая ветка кода в процедуре 667173DF будет выполнена,определяется тем DWORD’ом, который выделен в листинге жирным шрифтом. В данном случае эта метка соответствует операции XOR.

Отсюда следует,что мы можем заменить вызов всей процедуры на следующий код:



 			Код - 			одна из альтернатив процедуре 667258E3
 			
mov eax, @@ValueA mov ebx, @@ValueB xor eax, ebx


На данном этапе дело осталось за процедурой 66725EA0.

Она вызывается два раза,в первом из которых ключевую роль играет значение актуального Ordinal’а, во втором же - актуальный индекс элемента массива.

В вычислениях снова принимают участие процедуры 667173DF,где производится операция XOR,а также процедуры,в которых применяются команды линейного логического сдвига:



 				Код - 				процедуры 66726055 и 667260С0
 				
.text:667260A3 shl esi, cl ...либо .text:6672610E shr esi, cl

В качестве счётчика сдвигов выступают константы,которые на протяжении всей процедуры 66725EA0 последовательно помещаются в стек командой PUSH:



 				Код - 				процедура 66725EA0
 				
... .text:66725EE4 push 1 ... .text:66725F24 push 2 ... .text:66725F54 push 3 ...и так далее


В конце концов,всё действует по следующему сценарию:

  1. Получаем производное от актуального Ordinal’а в процедуре 66725EA0

  2. Получаем производное от актуального индекса элемента массива в процедуре 66725ЕА0

  3. Скрещиваем получившиеся значения между собой при помощи функции GetDerivative

  4. Результат XOR’им с соответствующим DWORD’ом из массива MagicArray


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


Для получения более полного и наглядного примера реализации как данной,так и последующих процедур обращайтесь к файлу “SD Procs examples.inc ”,прилагающемуся к статье.


Возвращаясь вновь к процедуре 6672620A,которая должна вернуть первое значение из пары,необходимое для расшифровки массива ParamsArray,хочу отметить,что и здесь основными рабочими лошадками(имеются ввиду функции) являются те,что были уже рассмотрены выше. В качестве параметра,как я уже упоминал,передаётся второе значение из пары,которое находится в неизменённом виде в ParamsArray.Оно затем обрабатывается с помощью значения HeapHead, а также двух первых значений из HeapArray.


Опять-таки загляните в прилагающийся файл.




Получив необходимую величину,мы приступаем к расшифровке массива. Если вспомнить,то массив зашифровывался с помощью команд XOR и IMUL.

Так вот в качестве операции умножения со знаком тут выступает функция 6672512A, она же GetDerivative, которая в упрощённом варианте наглядно демонстрирует такую возможность. А операция XOR открыто лежит на поверхности:



 				Код - 				процедура 6672F045
 				
.text:6672F0D9 xor [esi+1Ch], eax ...и аналогичным образом далее

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

SafeDisc Advanced cracking


Два нулевых DWORD’а - это два параметра протектора,которые были помещены в стек при вызове SDAPI. Но сейчас нас больше будут интересовать те самые три (точнее две) константы протектора,с которыми мы встречались в функции CryptParamsArray и которые я отметил там синим цветом.

Если вспомнить нашу карту местности,то сейчас у нас на носу должен быть первый по счёту оператор switch..case, для которого мы сперва должны получить значение CASE. Тут мы снова сталкиваемся с процедурой 6672620A, в которой и происходит вычисление этого значения. Ключевым параметром,передающимся в функцию,в данном случае является одна из констант протектора, а именно:


[ESI+08] = 10804205 ; не забывайте,что указатель на массив ParamsArray был

; помещён именно в регистр ESI

Остальной ход вычислений в процедуре 6672620А аналогичен уже рассмотренному выше варианту. После того,как мы получаем наш результат - значение CASE - мы подходим к самому оператору.



 				Код - 				первый оператор SWITCH..CASE
 				
.text:6672F208 cmp eax, 7 ; switch 8 cases .text:6672F20B ja short loc_6672F280 ; default .text:6672F20D jmp ds:off_6672F2B0[eax*4] ; switch jump


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

Итак,всего возможных вариантов существует восемь:

  • CASE 0 и 1 - размерность операнда: байт

  • CASE 2 и 3 - размерность операнда: слово

  • CASE 4 и 5 - размерность операнда: двойное слово

  • CASE 6 и 7 - размерность операнда: учетверённое слово


Приняв это пока что как должное,двинемся дальше.

Если мы отлаживаем в данный момент самый первый вызов SDAPI, о котором я упоминал выше,то первым значением CASE будет число 5, которое после команды декремента становится равным четырём. Теперь заходим в нужную ветку кода и почти сразу замечаем вызов процедуры 6672620А - уже третий по счёту и на этот раз последний. В нём вычисляется значение CASE для второго оператора посредством константы протектора:

[ESI+0С] = 10804201


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

Для этого необходимо ещё раз взглянуть на расшифрованный массив ParamsArray, указатель на который в нужный момент снова появляется в регистре ESI.

Идея тут следующая - формируются две пары значений,каждая из которых содержит,так сказать,первостепенное и второстепенное значения. Первостепенные берутся из тех основных параметров,помещаемых в стек при вызове SDAPI (в данном случае это два нулевых DWORD’а), второстепенные - из двух последних DWORD’ов массива ParamsArray,о которых я говорил,как о находящихся случайным образом над вершиной стека. Вообще,если откровенно,то решающими значениями здесь являются лишь первостепенные. Второстепенные же добавлены видимо просто “до кучи” (чтобы придать “объёму” вычислениям),и своим присутствием никак не влияют на конечный результат.

Вот один из вариантов подобного формирующего кода:



 				Код - 				процедура 6672ЕА14
 				
.text:6672EA26 mov esi, [ebp+8] ; ESI - указатель на массив ...достаём из массива второстепенные члены для пар .text:6672EA58 mov edi, [esi+18h] .text:6672EA5B mov edx, [esi+14h] ...формируем пары .text:6672EA69 mov eax, [esi+10h] .text:6672EA6C xor ebx, ebx .text:6672EA6E or ebx, eax .text:6672EA70 mov eax, [esi+1Ch] .text:6672EA73 xor ecx, ecx .text:6672EA75 xor esi, esi .text:6672EA77 or edi, ecx .text:6672EA79 or eax, esi .text:6672EA7B or ecx, edx .text:6672EA7D push eax ; Сохраняем в стеке .text:6672EA7E push ecx ; первую пару .text:6672EA7F lea ecx, [ebp-0F8h] .text:6672EA85 call sub_667149DB .text:6672EA8A push edi ; Сохраняем в стеке .text:6672EA8B push ebx ; вторую пару .text:6672EA8C lea ecx, [ebp-88h] .text:6672EA92 mov dword ptr [ebp-4], 2 .text:6672EA99 call sub_667149DB


Сформировав пары параметров,а также уже имея в кармане очередное вычисленное значение CASE,мы переходим ко второму оператору switch..case.


Второй оператор в отличие от первого более обширный:



 				Код - 				процедура 6672D83E - 				второй оператор SWITCH..CASE
 				
.text:6672D877 cmp eax, 16h .text:6672D87A ja loc_6672DAC5 ; default .text:6672D880 cmp eax, 16h ; switch 23 cases .text:6672D883 ja loc_6672DAC5 ; default .text:6672D889 jmp ds:off_6672DB04[eax*4] ; switch jump


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

Возьмём,к примеру,CASE со значением 2:



 				Код - 				процедура 6672А883
 				
.text:6672A898 push dword ptr [ebp+0Ch] ; Кладём в стек вторую пару ... .text:6672A8A1 call sub_667284C4 ; Производим вычисления ... .text:6672A8A6 push dword ptr [ebp+10h] ; Кладём в стек первую пару ... .text:6672A8B3 call sub_667284C4 ; Производим вычисления ... .text:6672A8C7 call sub_66723219 ; Производим CASE-вычисления ... .text:6672A8DA call sub_66728508 ; Производим финальные вычисления


На основе этого можно выделить следующую пошаговую схему вариантов второго оператора:

  1. Вычисляем производное от второй пары с помощью функции 667264А2

  2. Вычисляем производное от первой пары с помощью функции 667264А2

  3. Оба результата используем в CASE-вычислении,которое зависит от конкретного CASE’а

  4. Полученный результат используем в функции 66727342, где вычисляется финальное значение


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

сложения - ADD:



 				Код 				- процедура 				667212DA
 				
.text:667212DF call sub_6671F6E6 ; Достаём второй результат .text:667212E4 mov ecx, [esp+4+arg_4] .text:667212E8 mov esi, eax .text:667212EA call sub_6671F6E6 ; Достаём первый результат .text:667212EF add eax, esi ; Складываем их

Вышеописанная пошаговая схема присутствует во всех CASE’ах кроме номеров 1 и 0Dh - в них используется только одна строго определённая пара и одна функция:

  • в CASE1 - первая пары и функция 66727342

  • в CASE0D - вторая пара и функция 667264A2

...и никаких CASE-вычислений.


Я не стану сейчас приводить остальные операции,характерные для других CASE’ов, т.к. вы,уважаемые читатели,можете это запросто сделать сами,просмотрев нужные куски кода в дизассемблере,руководствуясь приведённой выше схемой.


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

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



 				Код - 				Шаг 4 приведённой выше схемы
 				
.text:667282E6 call dword ptr [eax] ; Достаём результат Шага 3 .text:667282E8 movsx eax, al .text:667282EB cdq ; Определяем в EDX параметр “до кучи” .text:667282EC push edx ; Помещаем параметры в стек .text:667282ED push eax ... .text:66728300 call sub_66727342 ; Финальное вычисление


Как несложно догадаться по команде,выделенной жирным шрифтом, - здесь мы имеем дело с байтами,т.е. с CASE’ами 0 или 1 первого оператора.

Причём эта команда пересылки с расширением слегка варьируется между CASE’ами одной и той же размерности.

Например,CASE’ы 2 и 3 принадлежат к “слову”,поэтому в первом случае мы увидим примерно следующее:



 				Код - 				Шаг 4 приведённой выше схемы
 				
.text:66728401 call sub_66721182 ; Достаём результат Шага 3 .text:66728406 movsx eax, ax ; пересылка со знаковым расширением .text:66728409 cdq .text:6672840A push edx .text:6672840B push eax

...во втором же случае это будет:



 				Код - 				Шаг 4 приведённой выше схемы
 				
.text:66728490 call sub_66721210 ; Достаём результат Шага 3 .text:66728495 movzx eax, ax ; пересылка с нулевым расширением .text:66728498 cdq .text:66728499 push edx .text:6672849A push eax


Вот мы и добрались,наконец,до финальных вычислений. Выше уже упоминалось,что ответственных функций тут две: 66727342 и 667264А2.

Несмотря на колоссальный размер кода(обфускация), принцип последнего достаточно прост. Вследствие этого я буду краток.

В функцию передаётся одна из пар сформированных параметров,которая подвергается обработке значениями из массива HeapArray.Одним из ключевых моментов каждой из функций является получение особой величины,которую я назвал базой, - именно с её участием строятся последние вычисления,в результате которых получается финальное значение. Если внимательно разбирать код функций,то выясняется,что эта база вычисляется аж четыре раза на протяжении всего алгоритма, т.е. одинаковый код многократно дублируется - за счёт этого,видимо,старались придать объёму.

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


Приоткрываем завесу...


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


Дело в том,что всю рутинную работу,связанную с функционированием SDAPI, можно успешно выполнять вне(!) присутствия основного модуля протектора - библиотеки ~df394b.tmp.Иными словами,весь код,способный производить необходимые вычисления и возвращать требуемые результаты, имеется в наличии в самом защищённом приложении - в его секции кода. Достаточно вновь взглянуть на одно из Преддверий:



 				Код - 				Преддверие протектора
 				
.text:00666E40 sub esp, 20h .text:00666E43 push esi .text:00666E44 mov esi, stru_8FACC8.SDRoutineAddr .text:00666E4A test esi, esi .text:00666E4C jnz short loc_666E57 .text:00666E4E mov eax, stru_8FACC8.TrashDwordA .text:00666E53 test eax, eax .text:00666E55 jz short loc_666E8D ... loc_666E8D: .text:00666E8D mov edx, [esp+24h+arg_4] ; второй параметр .text:00666E91 mov eax, [esp+24h+arg_0] ; первый параметр .text:00666E95 push edx .text:00666E96 push eax .text:00666E97 call sub_6665B0 ; Производим все вычисления .text:00666E9C add esp, 8 ; Восстанавливаем стек .text:00666E9F pop esi .text:00666EA0 add esp, 20h .text:00666EA3 retn ; И выходим из преддверия


То есть если адрес процедуры протектора - 6672F2F7 - будет равен нулю,то мы попадаем на заветный код.

Однако для того,чтобы должным образом оперировать,этот код,как и код протектора,нуждается в данных. Нетрудно догадаться,что этими данными являются массивы HeapArray,которые теперь должны располагаться где-то под рукой...именно - в секции данных защищённого файла,в структурах SDArgsStruc.

Если заглянуть в главную процедуру этой своеобразной лазейки,то первый же вызов приведёт нас к следующему коду:



 				Код 				- процедура 				00656740
 				
.text:00656740 mov eax, offset stru_8FACC8 .text:00656745 retn


В регистр EAX помещается указатель на актуальную структуру SDArgsStruc, которая должна уже иметь правильно заполненные поля HeapHead и HeapArray.

Если вспомнить про сходство структур,находящихся в секции данных и в области памяти в куче,то можно с уверенностью предположить,что массив HeapArray займёт позицию в структуре SDArgsStruc секции данных соответствующую позиции массива MagicArray в структуре,находящейся в куче. То же и с полем HeapHead.И если значение поля HeapHead всегда будет равно 5CAC5AC5h,

то значения массивов HeapArray необходимо(лучше всего) будет вычислить самостоятельно,написав для этого необходимый код.

Таким образом,правильно настроив структуры в секции данных,мы сможем освободить защищённое приложение от цепей и оков протектора.


Глядя на такую развязку,возникает вполне резонный вопрос:


“Зачем же Macrovision оставили такую потенциальную дыру?” .


Существует мнение,что этот код создаётся на этапе разработки защищаемого приложения в целях тестирования и после этого этапа должен уничтожатся. Однако как показывает практика,уничтожаются только массивы HeapArray из секции данных,код же в секции .text часто остаётся нетронутым.

Исходя из целей применения,сей код окрестили “отладочными обработчиками”(или debug handlers).

Игра NFSMW версий 1.2 и 1.3 - это как раз тот самый случай,когда отладочные обработчики целы,и сложность составляет только восстановление структур в секции данных.

Всё это можно окрестить как хорошую новость,но есть и плохая. Она заключается в том,что в ряде случаев отладочные обработчики уничтожены,затёрты инструкциями 0CCh. Причём последние являются вовсе не наномитами, а обычными командами третьего прерывания. Очевидно,что при подобном раскладе снятие протектора становится на порядок сложнее,но тем не менее остаётся возможным. И тут уже сам исследователь,основываясь на своих знаниях материала, решает,как именно он собирается это сделать.


Но постойте,спустимся пока что на землю...всё ли нам удалось восстановить до сих пор?Не проявляется ли какая-то нестабильность в работе распакованного приложения?


Секция данных 'revisited' - Trigger Tables


Защитный механизм SDAPI таит в себе ещё один трюк,с которым необходимо разобраться,чтобы дать возможность распакованному приложению должным образом функционировать. Нам вновь придётся иметь дело с секцией данных защищённого файла,где содержится очередной тип структур,требующих восстановления(прям навождение какое-то...).

Структуры эти по некоторым слухам именуются как Trigger tables.

Обращения к этим данным можно отследить в секции кода по таким командам:

     mov dword ptr [esp+XX], offset data-section
mov dword ptr [esp+XX], offset data-section
...т.е. поиску должен подвергатся следующий опкод: C7h 44h 24h byte dword …где: byte - неизвестная величина,являющая собой т.н. смещение в команде dword - смещение в секции данных

В NFSMW этих структур четыре штуки,обращение к одной из которых можно увидеть здесь:



 				Код - 				пример обращения к Trigger table
 				
.text:006E772E mov dword ptr [esp+28h], offset dword_8F8290 .text:006E7736 mov dword ptr [esp+2Ch], offset off_8F7B20 .text:006E773E call sub_6E6E40 ; вызов SDAPI .text:006E7743 push eax .text:006E7744 push 899CC300h .text:006E7749 call sub_6E6DD0 ; вызов SDAPI

Местонахождение структуры узнаётся с помощью команды,отмеченной в листинге жирным шрифтом. В данном случае структура находится в секции данных по адресу 008F8290.

Дамп памяти по этому адресу не выглядит многообещающе:

SafeDisc Advanced cracking


...но тем не менее мы получаем необходимую нам информацию - это четвёртое по счёту двойное слово,а точнее байт(в данном случае 0Ch) - так называемый “известный байт”.С помощью него мы и будем потом определять нужную структуру...опять в памяти,выделенной под кучу.

Следом за двумя командами MOV в листинге идёт вызов SDAPI,который мы пропускаем. Нас интересует следующий вызов функции протектора по адресу 006E7749. Дело в том,что эти структуры обрабатываются в CASE с номером 16h второго оператора(возможно,что также и в пятнадцатом варианте).Номер CASE’а первого оператора тут,по-видимому,роли не играет. Код,ответственный за обработку структурных данных,является как бы надстройкой у номеров 16h и 15h.



 			Код - 			вызов необходимой процедуры в CASE16
 			
.text:66725E54 call dword ptr [eax+4] ; [EAX+04] = 66724396




Собственно единственное место среди всех тамошних вычислений,которое нас может действительно заинтересовать,выглядит так:



 				Код 				- процедура 				6671А297
 				
.text:6671A334 mov eax, [esi+0Ch] .text:6671A337 mov esi, [ebp-0A0h] .text:6671A33D cmp esi, eax .text:6671A33F jz loc_6671ABA7


В регистр EAX кладётся указатель на область памяти,где содержатся указатели на три искомые структуры:

SafeDisc Advanced cracking


В регистр ESI кладётся один из этих указателей,например:

SafeDisc Advanced cracking


При анализе содержимого регистра ESI важным для нас пока является то, что четвёртый по счёту DWORD (00000001h) совпадает с седьмым,и после этого следует тот самый “известный байт”, который определяет для нас нужную структуру.

Стоит сразу отметить,что представление структуры в куче далеко от того идеала,который должен оказаться,в конце концов,в секции данных. Сравнивая обе области можно прийти к выводу,что первые четыре DWORD’а в выделенной памяти просто отсекаются. Дальнейшее же приведение структуры в порядок почерпнуто из трудов небезызвестной команды ReLOADeD, за что им отдельная благодарность.

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

SafeDisc Advanced cracking


Итак. Открывает структуру маркер начала - 69241641h. В первой половине было заменено только четырёхбайтовое значение FFFFFFFFh на нулевой DWORD. Вторая половина,начинающаяся в выделенной памяти с двойного слова 00000027h, просто заменена двойными словами со значениями от 00000001h до 00000008h включительно. Завершает структуру маркер конца - 73872468h.

Аналогичным образом и у других структур.

Однако как я сказал,структур всего четыре(четыре подлинных обращения из секции кода),но указателей в регистре EAX только три. Взгляните ещё раз на содержимое регистра ESI - вторым DWORD’ом идёт указатель на вторую структуру. Так вот,в одной из трёх структур в куче должен таким же образом присутствовать указатель на отсутствующую четвёртую. На худой конец,можно,в принципе,просто поискать в близлежащей памяти “известный байт” недостающей структуры.


Пару мыслей напоследок


В начале этой статьи я отмечал,что игра "NFSMW" выбрана неслучайно. Причина тут кроется в особенностях реализации рассматриваемого защитного механизма.

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


То, что неясно, следует выяснить.

То, что трудно творить, следует делать с великой настойчивостью”.

Конфуций



Благодарности


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

Особое спасибо Tim'у,который в своё время натолкнул меня на эти статьи.

Спасибо тем людям,которые тем или иным образом способствовали появлению этой статьи на свет.

Спасибо Bad_guy'ю за то,что в просторах сети есть такой исследовательский портал как CrackL@B.

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



Скачать статью "Взлом SafeDisc Advanced и исследование SDAPI второй версии" в авторском оформление + файлы.
пароль архива на картинке



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


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