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

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


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

Отучаем Empire Earth 2 от CD (SecuRom)

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

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

Автор: Ra$cal <rascalspb@mail.ru>

Здрасти всем. Я тут впал в детство, заигрался в игру EmpireEarth 2. Симпатичная графика, много юнитов, интересный геймплей, плохо что подтормаживает, но ничё не поделать – за качество надо платить, а не хочется. Ну вот значит, диск лицензионный (точнее два), и не мой. Пора бы отдавать, а игра не пашет без него. И тут меня понесло :) Со мной всё понятно – я должен пользоваться этой игрой, только если куплю лицензионный диск, а вот как же мой друг. У меня (да и у многих наверное) не раз диски рассыпались в приводе, ну или на худой конец царапались до невозможности чтения. На этот случай они скинуты на RW’хи. С этим я поступил также. Итог запуска заставляет задуматься – вставьте настоящий диск, а не копию. Это откровенная наглость. Вот так, из чисто демократических побуждений (хочу иметь выбор – с лицензии запускать или с копии, всё равно заплатил всю стоимость) появилась эта статья. Цель её сугубо научная – проверить, сможем ли мы играть в свою любимую игру после того, как настоящий диск с ней разлетится или испортится. Разлетать и портить диск мы не будем, а вот поисследовать можно, достаточно вытащить ЦД из привода и запустить файл с игрой :)

Инструменты:
OllyDebug
ImpRec
Microsoft Visual Studio 7.x

Ну ладно, я вроде успокоился, теперь приступим к исследованию. Сначала вытаскиваем диск и видим просьбу его вставить. Теперь вставляем копию и видим уже описанное сообщение, но обращаем внимание на последнюю строчку – www.securom.com/copy. Это означает, что игра запротекчена SecuRom’ом. В принципе, диск можно скопировать с помощью Alcohol’a, правда для его запуска тоже нужен Alcohol. Но раз уж тут SecuRom, то почему бы не попытаться его снять, тогда игра будет запускаться вообще без диска. Приступим. Сначала общие данные - этот протектор пионерит часть кода, шифрует его данными с диска, и если у нас в сидюке оригинал (или точная копия), то восстанавливает код правильно, иначе всё без толку. Поэтому вставляем лицензию и смотрим в олю. Пишет расшифрованный код он с помощью функции WriteProcessMemory, поэтому бряка на неё и смотрим. Первый раз пишем в секцию игры, второй в SFX, третий тоже в SFX, теперь в секцию игры, а дальше много пишется в rdata, то есть импорт меняется. После n-ого вызова игра запускается. Можно повторить на n-1 вызов, но я Вам подскажу, что проще поставить бряк на CreateEventA, и по параметру EventName = “EV_APPL_STARTED" можно догадаться, что вот-вот игра запустится. Пройдя ещё чуть-чуть видим:


00C80A52   58               POP EAX
 00C80A53   A1 781DE100      MOV EAX,DWORD PTR DS:[E11D78]
 00C80A58   FFE0             JMP NEAR EAX


Это переход на OEP. Прыгаем туда и делаем дамп. Самое простое позади. Теперь посмотрим, что же делалось с импортом. Видим очень грустную картину – нормальных вызовов минимум и куча переходников. Их можно разделить на 2 группы – перенаправление в память, выделенную из кучи:

004022CF   50               PUSH EAX
 004022D0   FF15 1098D100    CALL NEAR DWORD PTR DS:[D19810] ; 01C20000


У меня это адреса с 1C000000 по 01CB0000.

Или перенаправление в секцию SecuRom’a:

// Обычный вызов
 00401EBF   E8 DCFE8A00      CALL EE2.00CB1DA0
 // И по значению DWORD’a с адресом хххх (D3668C в данном случае)
 004087B3   FF15 8C66D300    CALL NEAR DWORD PTR DS:[D3668C]          ; EE2.00CB1DA0


Какую именно апи функцию вызвать в обоих случаях решается из адреса возврата. Алгоритм получения достаточно громоздок и использует много данных, причём инициализируемых во время выполнения программы. Поэтому переписать алгоритм и выполнить его над сдампленным файлом не получится. Что мы можем сделать? Для начала посмотрим, как же работает этот механизм. Сперва обратим внимание на переходники в динамическую память. Ставим бряки на все их вызовы и отпускаем игру. Стопоримся здесь:


00A01AE9   FF7424 04        PUSH DWORD PTR SS:[ESP+4]
 00A01AED   FF15 3498D100    CALL NEAR DWORD PTR DS:[D19834]


Теперь заходим и осторожно трейсим...


01CB0695   8B85 68FFFFFF    MOV EAX,DWORD PTR SS:[EBP-98]
 01CB069B  -FF2485 55F5C200  JMP NEAR DWORD PTR DS:[EAX*4+C2F555]     ; EE2.00C2ECF3


Как видим, прыг возвращает нас в наш файл, точнее в секцию секурома.

После продолжительных путешествий приходим сюда. Вот здесь мы и переходим на апи функцию.


Адрес АПИ-функции в таблице импорта ([EBP-34])
 00C2F4AA   8B45 CC          MOV EAX,DWORD PTR SS:[EBP-34]
 00C2F4AD   8BF0             MOV ESI,EAX
 А потом берём значение по этому адресу. Видим, что за апи – onexit. Адрес апи в EAX
 00C2F4AF   8B06             MOV EAX,DWORD PTR DS:[ESI]
 00C2F4B1   5F               POP EDI
 00C2F4B2   5E               POP ESI
 00C2F4B3   5B               POP EBX
 00C2F4B4   8BE5             MOV ESP,EBP
 00C2F4B6   5D               POP EBP
 И прыгаем туда...
 00C2F4B7   FFE0             JMP NEAR EAX


Теперь жмём F9. Опять переход в ту же память (01CB0000). Мы всё про неё посмотрели. Жмём F9. Жмём, ещё. Короче, в другой участок переходим отсюда

00421CC9 FF15 1498D100 CALL NEAR DWORD PTR DS:[D19814]

Это прыг в 01C30000

01C3069B -FF2485 55F5C200 JMP NEAR DWORD PTR DS:[EAX*4+C2F555] ; EE2.00C2EDBE

Опять возвращаемся в программу.
И вот интересное место.


Адрес функции в таблице импорта программы в EAX
 00C2F4AA   8B45 CC          MOV EAX,DWORD PTR SS:[EBP-34]
 00C2F4AD   8BF0             MOV ESI,EAX
 Адрес апи-функции 
 00C2F4AF   8B06             MOV EAX,DWORD PTR DS:[ESI]
 00C2F4B1   5F               POP EDI
 00C2F4B2   5E               POP ESI
 00C2F4B3   5B               POP EBX
 00C2F4B4   8BE5             MOV ESP,EBP
 00C2F4B6   5D               POP EBP
 И прыгаем туда...
 00C2F4B7   FFE0             JMP NEAR EAX


Как Вы, надеюсь, заметили, переход на апи осуществляется в том же месте. Это, несомненно, хорошо. Мы можем написать код, который будет перехватывать управление, брать значение по адресу EBP-34 (адрес вызываемой апи в таблице импорта) и писать его куда-нибудь, лучше всего прямо в файл с дампом и править вызов типа call near 01C00000 на вызов апи. Теперь осталось решить, как мы вычислим, куда в дампе писать. Это просто, останавливаемся на месте, где мы планируем сделать переход на наш исправляющий код (00C2F4AA) и глядим в стэк:
00328A94 00421CCF RETURN to EE2.00421CCF from 01C30000
Это значит, что по адресу 00328A94 (а точнее ESP-54) хранится адрес, куда вернётся управление после вызова апи, а так как длина команды вызова 6 байт (call near – FF15, плюс 4 байта адреса в IAT), то мы легко найдём, куда надо писать новые байты (адрес возврата - 4). Для разнообразия исправляющий код будем писать в библиотеке, а её подгружать перед переходом на OEP.

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

004016F0 FF15 3098D100 CALL NEAR DWORD PTR DS:[D19830]
и
004018F1 FF15 2498D100 CALL NEAR DWORD PTR DS:[D19824]

Вызов состоит из 2 байт FF 15 - это сам вызов, и 3098D100 - по этому адресу берётся значение и на него происходит прыжок. Для начала в файле будем искать FF 15, а потом смотреть следующие 4 байта. Видно, что эти 4 байта различаются всего одним байтом, последним, так что делаем из трёх константу
DWORD DestBytesInMemory = 0x00D198FF;
Последний байт мы сделали FF для упрощения проверки, т.к. бинарной операцией ИЛИ мы обЭФим младший прочитанный из дампа байт вызова.

DWORD ReturnAddress, // адрес возврата в программу после вызова апи
ImportTableAddress, // Адрес в таблице импорта, где хранится настоящее значение адреса АПИ-функции
OffsetInFile;


DWORD BeginFileInMemory = 0x401000,
EndFileInMemory = 0x0A6A000,
CurAddress = 0;


WORD NearCall = 0x15FF; // опкод near call’a

DWORD DestBytesInMemory = 0x00D198FF; // В выделенную память типа 1C00000-1C80000

WORD ReadBufferCall; // прочитанные байты из файла, проверять на опкоды прыгов
DWORD BytesReaded, ReadBufferBytesCall;
DWORD AddrOfFindCallFunc;
static int i = BeginFileInMemory;


BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
DUMP_Handle = CreateFile ("dmp.exe", GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
return TRUE;
}

__declspec(dllexport) void FindCall()
{
for ( ; i < EndFileInMemory; )
{
i++;
CurAddress = i - 0x400000;
SetFilePointer ( DUMP_Handle, CurAddress, 0, 0);
ReadFile ( DUMP_Handle, &ReadBufferCall, 2, &BytesReaded, 0 );

// Проверка на nearCall
if ( ReadBufferCall == NearCall )
{
CurAddress += 2;


// Проверка, первый ли тип переходника
SetFilePointer ( DUMP_Handle, CurAddress, 0, 0);
ReadFile ( DUMP_Handle, &ReadBufferBytesCall, 4, &BytesReaded, 0 );
// Сбрасываем младший байт и сравниваем с нашей константой
ReadBufferBytesCall |= 0x000000FF;
if ( ReadBufferBytesCall == DestBytesInMemory )
{
// Переходим туда
__asm jmp near i
}
}
}
}

Ну вот, поиск сделали, теперь стоит подумать о поправке данных в дампе. Просто берём адрес, ведущий в IAT (EBP-34) и пишем в дамп

// Исправление импорта типа CALL NEAR DWORD PTR DS:[D36664] в выделенную память
__declspec(dllexport) void FixImportCall ()
{
__asm
{
pop ebp ; Инлайн-асм автоматом кладёт в стэк любой регистр, которым мы воспользуемся, поэтому восстанавливаем, чтоб ESP не испортить
mov eax, ebp ; Настоящие
sub eax,0x34 ; байты
mov ImportTableAddress, eax
mov eax, [esp+0x54] ; откуда вызов апи в исходном файле
mov ReturnAddress, eax
sub eax, 0x400004 ; смещение в файле байтов адреса вызова
push 0
push 0
push eax
push DUMP_Handle
call NEAR DWORD PTR DS:[SetFilePointer]
push 0
push offset ReturnAddress ; Запишем сюда, сколько байтов записалось. Это нам не важно, но если не задать - упадём
push 4
push ImportTableAddress
push DUMP_Handle
call near DWORD PTR ds:[WriteFile]
jmp AddrOfFindCallFunc
}
}


Теперь надо загрузить нашу библиотеку. Где-нибудь в конце файла делаем изменения:


00A69764   75 6E            JNZ SHORT EE2.00A697D4
 00A69766   2E:              PREFIX CS:                               ; Superfluous prefix
 00A69767   64:6C            INS BYTE PTR ES:[EDI],DX                 ; I/O command
 00A69769   6C               INS BYTE PTR ES:[EDI],DX                 ; I/O command
 00A6976A   0000             ADD BYTE PTR DS:[EAX],AL
 00A6976C   0000             ADD BYTE PTR DS:[EAX],AL
 00A6976E   0000             ADD BYTE PTR DS:[EAX],AL
 00A69770   0000             ADD BYTE PTR DS:[EAX],AL
 00A69772   0000             ADD BYTE PTR DS:[EAX],AL
 00A69774   0000             ADD BYTE PTR DS:[EAX],AL
 00A69776   68 6497A600      PUSH EE2.00A69764                        ; ASCII "un.dll"
 00A6977B   E8 E1414177      CALL kernel32.LoadLibraryA
 00A69780  ^E9 7B85F9FF      JMP EE2.00A01D00
 00A69785   90               NOP


И на переходе на ОЕП в EAX пишем 00A69776. Теперь мы можем пользоваться нашим кодом в библиотеке. На ОЕП переходим на поиск переходников

00A01D00 -E9 FBF22901 JMP un.?FindCall@@YAXXZ

Ну а дальше смотрим, что делает наша библиотека


01CA105E   0FB705 10ACCA01  MOVZX EAX,WORD PTR DS:[ReadBufferCall]
 01CA1065   0FB70D 88A0CA01  MOVZX ECX,WORD PTR DS:[NearCall]
 01CA106C > 3BC1             CMP EAX,ECX
 01CA106E   75 5E            JNZ SHORT un.01CA10CE
 01CA1070   A1 34ACCA01      MOV EAX,DWORD PTR DS:[CurAddress]


Ставим бряк за JNZ, чтобы остановиться на первом коле. Как видим, это не тот тип, тогда ставим бряк на
01CA10C8 -FF25 A4A0CA01 JMP NEAR DWORD PTR DS:[ i ] ; EE2.00401432

Отлично. Теперь ставим бряк 00C2F4AA (выше это место уже нашли). Здесь меняем на
00C2F4AA -E9 921C0701 JMP un.?FixImportCall@@YAXXZ

Пока всё отлично, адрес в таблице импорта 00A6A0DC. Теперь дошли сюда. Нам нельзя возвращать управление обратно в игру, поэтому я сделал прыг обратно на поиск, но как получить адрес функции перед компиляцией я не понял и сделал прыжок на 99. Здесь нам надо поставить правильное значение адреса функции FindCall

01CA118B -FF25 04ACCA01 JMP NEAR DWORD PTR DS:[AddrOfFindCallFun>]

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


01C90446   8B45 BC          MOV EAX,DWORD PTR SS:[EBP-44]
 01C90449   83C0 04          ADD EAX,4
 01C9044C   8945 BC          MOV DWORD PTR SS:[EBP-44],EAX
 01C9044F   8B0D 4445E100    MOV ECX,DWORD PTR DS:[E14544]
 01C90455   C1E9 02          SHR ECX,2
 01C90458   394D BC          CMP DWORD PTR SS:[EBP-44],ECX
 01C9045B   7D 1E            JGE SHORT 01C9047B
 01C9045D   8B15 E472E100    MOV EDX,DWORD PTR DS:[E172E4]
 01C90463   8B02             MOV EAX,DWORD PTR DS:[EDX]
 01C90465   8B4D BC          MOV ECX,DWORD PTR SS:[EBP-44]
 01C90468   8B15 6485E100    MOV EDX,DWORD PTR DS:[E18564]            ; EE2.00C2C620
 01C9046E   03048A           ADD EAX,DWORD PTR DS:[EDX+ECX*4]
 01C90471   8B0D E472E100    MOV ECX,DWORD PTR DS:[E172E4]
 01C90477   8901             MOV DWORD PTR DS:[ECX],EAX
 01C90479  ^EB CB            JMP SHORT 01C90446


Такие куски кода встречаются во всех выделенных участках памяти. Обходится эта штука элементарно:


01C904A1   2B08             SUB ECX,DWORD PTR DS:[EAX]
 01C904A3   81C1 7A32B100    ADD ECX,0B1327A


Здесь после вычитания ECX и значения по адресу EAX должен быть ноль, то есть они должны быть равны, после к разности прибавляется константа. Теперь меняем


01C904A3   81C1 7A32B100    ADD ECX,0B1327A
 
 на
 
 01C904A3   B9 7A32B100      MOV ECX,0B1327A
 01C904A8   90               NOP


И радуемся :)

Вот мы остановились на конце цикла. Жмём Alt+F2, фиксим импорт у дампа и грузим его в олю. Теперь открываем запакованную, доходим до ОЕП, ищем Intermodular Calls, сортируем по адресу местоположения и смотрим, где был первый переходник
Address=004016F0
Disassembly=CALL NEAR DWORD PTR DS:[D19830]
Destination=DS:[00D19830]=01C80000

Теперь топаем на адрес 004016F0 в пофиксеном дампе

004016F0 FF15 DCA0A600 CALL NEAR DWORD PTR DS:[<&kernel32.GetPr>; kernel32.GetProcAddress

Хорошо, апи определилась. Значит с первым типом вроде разобрались. Давайте разбираться со вторым...

В дампе опять ищем все вызовы и видим наши цели:
00401432 FF15 5866D300 CALL NEAR DWORD PTR DS:[D36658] ; dmp_.00CB1B90

0040141F E8 3C0B8B00 CALL dmp_.00CB1F60


Теперь сортируем по Destination и видим, что колы ведут сюда:
00CB1B90, 00CB1DA0, 00CB1F60 - то есть в 3 места, поэтому, видимо, придётся править 3 штуки. Теперь разбираемся, каким образом здесь происходит переход на апи. Ставим бряки на прыги туда в запакованной проге и смотрим.

0041ABA8 FF15 5066D300 CALL NEAR DWORD PTR DS:[D36650] ; EE2.00CB1B90



00CB1C3A   8B45 EC          MOV EAX,DWORD PTR SS:[EBP-14]            ; EE2.00A6A1C0
 00CB1C3D   5F               POP EDI
 00CB1C3E   5E               POP ESI
 00CB1C3F   5A               POP EDX
 00CB1C40   59               POP ECX
 00CB1C41   5B               POP EBX
 00CB1C42   8BE5             MOV ESP,EBP
 00CB1C44   5D               POP EBP
 00CB1C45   8B00             MOV EAX,DWORD PTR DS:[EAX]
 00CB1C47   FFE0             JMP NEAR EAX
 

Здесь адрес функции в IAT хранится в [EBP-14]. Переход на апи обычным джампом. Следующий.

004B7E9D E8 BEA07F00 CALL EE2.00CB1F60


00CB2008   8945 EC          MOV DWORD PTR SS:[EBP-14],EAX
 00CB200B   5F               POP EDI
 00CB200C   5E               POP ESI
 00CB200D   5A               POP EDX
 00CB200E   59               POP ECX
 00CB200F   5B               POP EBX
 00CB2010   8BE5             MOV ESP,EBP
 00CB2012   58               POP EAX
 00CB2013   95               XCHG EAX,EBP
 00CB2014   8B40 EC          MOV EAX,DWORD PTR DS:[EAX-14]
 00CB2017   FF30             PUSH DWORD PTR DS:[EAX]
 00CB2019   C3               RETN


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


00CB1E6C   8945 EC          MOV DWORD PTR SS:[EBP-14],EAX
 00CB1E6F   8B75 EC          MOV ESI,DWORD PTR SS:[EBP-14]
 00CB1E72   8B06             MOV EAX,DWORD PTR DS:[ESI]
 00CB1E74   8945 04          MOV DWORD PTR SS:[EBP+4],EAX
 00CB1E77   58               POP EAX
 00CB1E78   5F               POP EDI
 00CB1E79   5E               POP ESI
 00CB1E7A   5A               POP EDX
 00CB1E7B   59               POP ECX
 00CB1E7C   5B               POP EBX
 00CB1E7D   5F               POP EDI
 00CB1E7E   5E               POP ESI
 00CB1E7F   5B               POP EBX
 00CB1E80   8BE5             MOV ESP,EBP
 00CB1E82   5D               POP EBP
 00CB1E83   C3               RETN


Всё то же самое, только адрес возвращения в апи необычно устанавливается. Вывод: во всех случаях адрес функции в таблице импорта хранится в [EBP-14]. Что ж, ещё одна халява. Теперь смотрим, как делать реализацию.

004C0171 CALL NEAR DWORD PTR DS:[D366A4] ; EE2.00CB1B90

Тут всё просто. Делаем как в первом случае - меняем байты адреса на правильные, прочитанные из EBP-14.

004C7329 E8 62A87E00 CALL EE2.00CB1B90

А вот тут нас подловили. Вообще вызов апи функций осуществляется так: CALL NEAR DWORD PTR DS:[АДРЕС_В_ТАБЛИЦЕ_ИМПОРТА_ГДЕ_ХРАНИТСЯ_АДРЕС_ВЫЗЫВАЕМОЙ_АПИ]. Это вроде как разыменовывание указателя и переход на адрес, который мы получили после разыменовывания. Занимает это 6 байт. А теперь посмотрите, сколько занимает обычный call... 5, а 1 байт они не просто сделали нопом и поставили за или перед вызовом, они сделали что-то типа полиморфа - байт может быть как перед вызовом, так и после, да в добавок байт этот разный. Вот с этим бороться придётся по-новому. Для начала посмотрим, какие варианты байта-мутанта используются - CMC (F5), NOP (90), CLC (F8), INC EAX (40), DAA (27), STC (F9). Теперь как с этим бороться - для начала надо удостовериться, что этот call ведёт именно в секцию секурома, а точнее на любой из тех 3 адресов. У near’ов я решил посмотреть, куда именно ведёт прыг. Для этого надо прочиать 4 байта за опкодами кола, вычесть 0x400000, прочитать значение по полученному адресу и проверить с 3 нашими константами. Как я уже где-то писал, переход обычного call’a осуществляется не просто подстановкой следующих 4 байт за опкодом E8 в EIP (то есть 4 байта это не адрес, по которому будет передано управление), а сложением EIP с этими байтами и переходом на результат этого сложения. Алгоритм - ищем байт E8, прибавляем к смещению, где нашли четыре следующих байта, + ещё 5 (длина команды). Если получаем 00CB1B90, 00CB1DA0, 00CB1F60, то это оно. Нижеприведённый код вставляется после jmp near i в проверке на NearCall

// Адреса, куда перенаправлен импорт.
DWORD DestAddresses [] = {0x00CB1B90, 0x00CB1DA0, 0x00CB1F60};
DWORD DestBytesInFile = 0x00D366FF; // В секцию SecuRom’a
// Мусорный код, маскирующийся перед или после call’a
BYTE JunkCode [] = { 0x90, 0xf8, 0xf9, 0xf5, 0x40, 0x27 };
// Откуда именно начинается call
DWORD RealCallAddress;
WORD Call = 0xE8FF; // опкод обычного call’a

// Или в секцию SecuRom’a
SetFilePointer ( DUMP_Handle, CurAddress, 0, 0);
ReadFile ( DUMP_Handle, &ReadBufferBytesCall, 4, &BytesReaded, 0 );
ReadBufferBytesCall -= 0x400000;
SetFilePointer ( DUMP_Handle, ReadBufferBytesCall, 0, 0);
ReadFile ( DUMP_Handle, &ReadBufferBytesCall, 4, &BytesReaded, 0 );
if ( (ReadBufferBytesCall == DestAddresses[0]) || (ReadBufferBytesCall == DestAddresses[1]) || (ReadBufferBytesCall == DestAddresses[2]) )
{
RealCallAddress = i;
__asm jmp near i
}
}

// Если не near, а обычный call
ReadBufferCall |= 0xff;
if ( ReadBufferCall == Call )
{
CurAddress += 1;
SetFilePointer ( DUMP_Handle, CurAddress + 1, 0, 0);
ReadFile ( DUMP_Handle, &ReadBufferBytesCall, 4, &BytesReaded, 0 );
// Прочитано 2 байта, то есть перед E8 есть ещё байт, а смещение в файле на 1 меньше, поэтому +1
ReadBufferBytesCall += i + 1 + 5;
// Опять проверка, в секцию SecuRom’a или нет
if ( (ReadBufferBytesCall == DestAddresses[0]) || (ReadBufferBytesCall == DestAddresses[1]) || (ReadBufferBytesCall == DestAddresses[2]) )
{
AnalyzeCall ();
i++;
if ( RealCallAddress != -1 )
__asm jmp near i
}
}

Поиск сделали, теперь займёмся фиксингом. Чтобы починить переделанные колы нам для начала нужно найти настоящее начало, то есть мусорный байт стоит перед или после call’a, затем по этому смещению записать 2 байта - FF 15, чтобы превратить В CALL NEAR и дальше 4 байта адреса в IAT. Но для начала надо что-то типа анализатора :) сварганить. Ничё сложного вроде здесь нет.

void AnalyzeCall ()
{
Junk_Before_Call = false;
Junk_After_Call = false;
SetFilePointer ( DUMP_Handle, CurAddress - 1, 0, 0);
ReadFile ( DUMP_Handle, &ByteBefore, 1, &BytesReaded, 0 );
for ( int i = 0; i < 6; i++ )
{
if ( ByteBefore == JunkCode [ i ] )
Junk_Before_Call = true;
}
SetFilePointer ( DUMP_Handle, CurAddress + 5, 0, 0);
ReadFile ( DUMP_Handle, &ByteAfter, 1, &BytesReaded, 0 );

for ( int i = 0; i < 6; i++ )
{
if ( ByteAfter == JunkCode [ i ] )
Junk_After_Call = true;
}

if ( (Junk_Before_Call & Junk_After_Call) )
{
RealCallAddress = -1;
return;
}

if ( Junk_After_Call == true )
{
RealCallAddress = CurAddress;
RealCallAddress += 0x400000;
return;
}

if ( Junk_Before_Call == true )
{
RealCallAddress = CurAddress - 1;
RealCallAddress += 0x400000;
return;
}
}

И сам исправляющий код

// Исправление импорта типа CALL EE2.00CB1F60
__declspec(dllexport) void FixPolimorphImportCall ()
{
__asm
{
pop ebp
mov eax, ebp ; Настоящие
sub eax,0x14 ; байты
mov ImportTableAddress, eax
mov eax, RealCallAddress
mov ReturnAddress, eax
sub eax, 0x400000 ; смещение в файле байтов call’a
push 0
push 0
push eax
push DUMP_Handle
call NEAR DWORD PTR DS:[SetFilePointer]
push 0
push offset ReturnAddress
push 2
push offset NearCall
push DUMP_Handle
call near DWORD PTR ds:[WriteFile]
push 0
push offset ReturnAddress
push 4
push ImportTableAddress
push DUMP_Handle
call near DWORD PTR ds:[WriteFile]
jmp AddrOfFindCallFunc
}
}

Вот вроде и всё. Теперь тормозим на прыге на оеп, добавляем загрузку dll, переходим туда, дальше на оеп. Там делаем прыг в функцию FindCall (вобщем всё как в первый раз). Меняем 99 на адрес FindCall и добавляем переходы в нашу библиотеку перед переходами на апи

00CB1C3A -E9 7FF4FE00 JMP un.?FixPolimorphImportCall@@YAXXZ

00CB1E6F -E9 4AF2FE00 JMP un.?FixPolimorphImportCall@@YAXXZ

00CB200B -E9 AEF0FE00 JMP un.?FixPolimorphImportCall@@YAXXZ

Опять ставим бряк за концом цикла... Остановились. Закрываем олю, восстанавливаем импорт (если надо), открываем в оле и смотрим на результат работы - все функции определились. Замечательно, теперь запускаем игру и радуемся :) Хотя один раз у меня неправильно определился вызов функции сохранения и через 5 минут игры мы упали на автосохранении, но, думаю, Вы с этим справитесь.
Как можно заметить, никаких особенных проблем с распаковкой у нас не возникло, хотя автор сего творения Sony corp. и ожидалось нечто большее.
Файлы с проектом доступны в разделе RAR. Всем спасибо за внимание, и пусть все игры идут у Вас без дисков :)


Обсуждение статьи: Отучаем Empire Earth 2 от CD (SecuRom) >>>


Комментарии к статье: Отучаем Empire Earth 2 от CD (SecuRom)

Inferno[mteam] 29.08.2005 10:29:07
Наконец-то среди потока мути блеснула весьма неплохая статья. Так держать !
---
Ra$cal 31.08.2005 02:17:22
Thanks. Буду стараться. Правда пока ничего интересного из ПО на горизонте не видно
---
John Freeman 05.09.2005 00:54:18
Прогресс.. уже базовый Securom
Но с каких это пор тут лицензионщики завелись?
---
V0ldemAr 05.09.2005 10:01:43
Хм, нормально, правда тутор уже есть
WarCraft 3, и SecuROM не настолько сложный протектор чтоб писать такой тутор :) Вот если бы Safe Disk 4 :) тогда можно медаль дать ;)
John Freeman:
Но с каких это пор тут лицензионщики завелись?

Кто не жлоб тот купит лицензионку, если игра хорошая!
---
MoonShiner 05.09.2005 15:47:24
А че там сложного в сэйфдиске? Я несколько дней назад отломал какой-то. 3.10 вроде или что-то так.
---
V0ldemAr 04.09.2005 20:56:05
СД 4 это не СД 3, логично вроде :) там тоже чего-то нового наделали и говорят что так как СД 3 не снимается :( кста тутор бы написал про снятие СД 3 ;)
---
MoonShiner 06.09.2005 13:00:33
Думал написать, но я не очень хорошо там разобрался с наномитами... Мож напишу до чего докопался, если сильно нужно будет. А на конкретные вопросы всегда готов ответить в форуме. Ra$cal, молодец, неплохая статья. Возьмись теперь за сэйфдиск.
---
Cr@cker 16.09.2005 13:43:40
Нечего цепляться, напишите лучше, если сможете!
---
Ra$cal 16.09.2005 13:58:14
V0ldemAr:\"правда тутор уже есть
WarCraft 3, и SecuROM не настолько сложный протектор чтоб писать такой тутор :)\"
Ну там если Вы заметили процесс распаковки отличается от моего, а на счёт сложности, я и не говорил, что это сложный протектор. Ну а если писать только про Xtreame да SafeDisk вряд ли многие поймут всё это сразу, просто забьют и в рядах кракеров народу вряд ли особо добавится :)
---
Ra$cal 16.09.2005 14:00:41
Млин, пора бы мне поспать :)) Не про SafeDisk а про StarForce
---
ReVeR 23.09.2005 01:35:16
davai, derzi eto tak, etu hernu ya znal no vseravno tut ofigenno.....
a tut pro safedisk v3=>4 nado bu napisat....:D

---
Bad_guy 20.11.2010 19:58:15
Мой текст комментария хорош.
---

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



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


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