|
Программирование ICQ 378204653 от Turbokherson |
|
Главная » 2012 » Февраль » 21 » Расширение функциональности готовых программ
11:37 Расширение функциональности готовых программ |
В мире ПО существует огромное количество программ, забытых своими
разработчиками. Хорошо, когда уже есть хорошая альтернатива. А если ее
нет? В программе может катастрофически не хватать каких-то мелочей,
некоторые досадные ошибки могут годами доставлять массу неудобств
пользователям, а на новых версиях ОС программа и вовсе может отказаться
работать. Далеко не всегда имеются исходные коды, чтобы привести
программу в порядок. Если программа простая — не составит труда за
короткий срок создать альтернативу. Но если программа большая и сложная,
что же делать в таком случае? Не всегда рационально тратить время и
деньги на разработку полного аналога, ведь расширить в разумных рамках
функциональность и исправить большинство ошибок можно уже в готовом
исполняемом файле.
В этой статье будут продемонстрированы методики модификации исполняемых
файлов на примере расширения функциональности легендарной игры Age of Empires II (стратегия реального времени).
Итак, что же мы имеем:- игра вышла в 1999 году и имеет статус abandonware — то есть она
более не выставляется на продажу производителем и не приносит прибыли
- последнее обновление выпущено в 2000 году, доработка не производится
- игра является хорошим примером сложного ПО, полный аналог которого практически невозможно создать заново
- имеются досадные ошибки, которые не были исправлены разработчиками
Пожалуй, можно попробовать что-то изменить, не глядя на запрет в лицензионном соглашении. Вы так не думаете? :)
Отказ от ответственности
Работа проведена исключительно в образовательных целях, и ни в коем
случае не является призывом к нарушению действующего законодательства.
Автор не несёт никакой ответственности за незаконное использование
представленных материалов.
Сведения о «пациенте»
Исследуемая игра написана на движке Genie, разработкой которого с 1997
до 2000 года занималась компания Ensemble Studios. За это время на
данном движке было выпущено 4 игры из серии Age of Empires. Далее Genie
был лицензирован компании LucasArts, которая в 2001-2002 годах выпустила
еще 2 игры на этом движке, но уже из серии Star Wars. К этому времени
движок уже сильно устарел, поскольку работал в режиме 256 цветов, что
очень скромно по сравнению с играми даже 2000 года. Именно поэтому
доработка движка была остановлена, новых игр на его основе не
выпускалось.
Игровой движок Genie загружает всю игровую графику, музыку, а главное —
списки юнитов и их характеристики — из внешних файлов. То есть все игры
на базе этого движка по большей части имеют очень похожие исполняемые
файлы, и практически все модификации для одной игры без особых проблем
портируются на другие.
Чего мы хотим добиться?
1. Отключить проверку наличия CDВ современных ноутбуках
(нетбуках и т.д.) уже не редкость, когда нет встроенного CD привода.
Более того, поскольку игра очень стара, оригинальный CD может перестать
читаться, а кто-то и вовсе мог потерять его. В итоге владельцы вполне
легальной копии игры теряют возможность запускать игру. Так что,
пожалуй, разрешим игре запускаться без диска.
2. Добавить поддержку оконного режимаAge of Empires
поддерживает 3 фиксированных разрешения: 800×600, 1024×768 и 1280×1024. С
такими разрешениями картинка на широкоформатных мониторах выглядит
растянутой, что доставляет дискомфорт. Добавить поддержку новых
разрешений в игру можно, но так как в игре для каждого разрешения
используются своя графика интерфейса, оставим это на потом, и
ограничимся пока что добавлением оконного режима работы в игру.
3. Добавить поддержку конфигурационных файловДля включения
каких-то системных игровых опций, их приходится передавать через
командную строку при каждом запуске программы. Можно поместить все
параметры в ярлык, но после перемещения игры в другой каталог ярлык
перестанет работать. Выход из ситуации — добавление поддержки
конфигурационных файлов.
4. Сделать игру переносимойИгра не переносима. Для запуска на
другом компьютере недостаточно скопировать игровые файлы, необходимо
пройти весь процесс установки игры. В связи с широким распространением
внешних накопителей хотелось бы наделить игру этим свойством.
5. Исправить известные ошибкиВ интерфейсе игры имеются
неприятные ошибки. Например, в диалоге подключения к IP игре в списке
последних игр обрезаются ранее введенные адреса, что сводит удобство
всей функции быстрого подключения на нет, приходится каждый раз вводить
адрес сервера вручную.
Что нам для этого понадобится?
IDA Pro — интерактивный
дизассемблер и отладчик, широко используется для реверс-инжиниринга.
Умеет строить понятные даже новичкам блок-схемы. Вся его сила
проявляется в интерактивном взаимодействии с пользователем. После
автоматического анализа пользователь может давать функциям и переменным
осмысленные имена, комментировать код и т.д. Достаточно небольших
познаниях в области ассемблера для того, чтобы начать заниматься
исследованием программ прямо сейчас!
Flat Assembler — свободно
распространяемый многопроходной ассемблер. FASM обладает небольшими
размерами и очень высокой скоростью компиляции, имеет богатый и ёмкий
макро-синтаксис, позволяющий автоматизировать множество рутинных задач.
Одна из полезных возможностей — генерация чистого машинного кода без
заголовков и т.д. Все это пригодится нам для генерации ассемблерных
вставок.
Hexplorer —
бесплатный HEX-редактор. Имеет простой дизассемблер x86, что позволяет
удобнее ориентироваться внутри исполняемого файла. Используется
непосредственно для ручной модификации двоичного файла без изменения его
размера.
Итак, начнем.
Отключение проверки наличия CD
Найти код, который отвечает за проверку наличия CD, нам поможет IDA. Это
оказалось очень просто сделать. Достаточно проследить вызовы функций
GetDriveTypeA и GetVolumeInformationA, которые используются для
получения информации о приводе, как мы обнаружим функцию проверки
наличия CD по адресу 4485A0h (базовый адрес 400000h, то есть физически в
файле машинный код находится по адресу 485A0h), которая при удачной
проверке возвращает в eax единицу.
CheckCD proc near
sub esp, 214h
push ebx
push esi
; ...
; множество проверок
; ...
test eax, eax
jz cd_check_fail
cd_check_ok:
pop esi
mov al, 1
pop ebx
add esp, 214h
retn 4
cd_check_fail:
pop esi
pop ebx
add esp, 214h
retn 4
CheckCD endp
При этом стоит заметить, что эта функция запрашивает
значение CDPath из реестра, где хранится буква диска, с которого была
установлена игра. То есть игра привязывается конкретно к той букве
диска, с которого она была установлена, и при наличии оригинального
диска в другом приводе игра все равно не запустится. Наиболее простое
решение в данном случае — это заменить команду jz cd_check_fail, которая
занимает 2 байта, на 2 операции nop, чтобы после проверки диска в eax
всегда возвращалась единица. Однако, функция устроена таким образом, что
при отсутсвии значения CDPath в реестре, функция сразу возвращает 0,
что мешает переносимости программы. Поэтому всю функцию мы заменим на 3
простые команды:
use32
xor eax, eax
inc eax
retn 4 Для получения машинных кодов этих команд нам пригодится FASM.
fasm src.asm dst.bin Основной плюс
этого ассемблера для нас сейчас — это то, что он позволяет генерировать
чистый машинный код без заголовков. Получаем (по строчке на команду):
31 C0
40
C2 04 00
То есть нам нужно поместить эти 6 байт в начало функции определения наличия CD, а остальные команды заменить на nop (код 90h).
Исходный машинный код |
Полученный машинный код |
8B 44 24 04 81 EC 14 02
00 00 85 C0 53 56 8B D9
... |
33 С0 40 С2 04 00 90 90
90 90 90 90 90 90 90 90
... |
В обычном HEX редакторе заменяем начиная с смещения
485A0h указанные выше машинные коды, после чего игра больше никогда не
требует для запуска CD, даже если в реестре нет необходимого ключа. При
этом мы освободили 288 байт для дополнительного машинного кода, что
пригодится нам в дальнейшем.
Исправление ошибки
При подключении к сетевой игре по IP игра выводит список IP и названий
игр, к которым игрок подключался ранее. Однако, по каким-то причинам IP
адрес часто сохранялся не полностью, обрезалось несколько его последних
символов. Необходимо разобраться в чем дело и исправить ошибку.
Займемся поиском кода, отвечающего за сохранение IP адресов последних
игр в реестре. Нам известно, что игра сохраняет список последних IP игр в
ключах с именами вида RecentGameName%d и RecentGameAddr%d. При помощи
IDA находим по 3 ссылки на эти строки. Определить где нужный код очень
просто — в коде со смещением 0512116h происходит запись этих ключей, в
остальных случаях — считывание. Не будем приводить найденный участок
машинного кода — там около 50 инструкций, общий смысл которых можно
представить в виде маленького фрагмента кода на C.
for(int i = 0; i < count; i++)
{
char* keyname;
sprintf(keyname, "RecentGameName%d", i);
RegSetVal(1, keyname, gamename[i], strlen(gamename[i]));
sprintf(keyname, "RecentGameAddr%d", i);
RegSetVal(1, keyname, gameaddr[i], strlen(gamename[i]));
} Для установки значения в реестре игрой используется
собственная функция-обертка RegSetVal(int, char* ValueName, char* Data,
int DataSize) — название функции и параметров конечно же придуманы, в
машинном коде их нет. Как видно, игра сохраняет в реестре ровно столько
символов IP адреса, сколько их в названии сетевой игры. Судя по всему,
программист скопировал 2 строчки сохранения названия игры для сохранения
адреса, но при этом забыл исправить название массива в вызове strlen.
Для исправления ошибки у нас есть 2 варианта: подставить в strlen
правильную строку или вместо strlen помещать константу 16 (максимальная
длина строки адреса, например «255.255.255.255»).
Как же выглядит вызов strlen(gamename[i]) в машинном коде?
call _sprintf ; +0x1022CF
mov edi, ebp
or ecx, 0FFFFFFFFh
xor eax, eax
add esp, 0Ch
repne scasb
not ecx
push ecx Компилятор оптимизировал код и заменил
вызов функции strlen() вычислением длины на месте. В предыдущем вызове
strlen() использовалась та же строка, в вот опять оптимизация —
указатель помещается в ebp один раз при первом вызове. То есть у нас нет
драгоценных пары байт для того, чтобы поместить правильный указатель в
ebp. Можно, конечно, сделать jmp за пределы функции, потом обратно — но
это не самый красивый метод. Поэтому остановимся пока на самом простом
варианте исправления ошибки — будем всегда сохранять 16 байт адреса. Для
этого заменим приведенный выше машинный код на следующий:
Ассемблерный код |
Машинный код |
call _sprintf ; +0x1022CF
add esp, 0Ch
mov ecx, 0Fh |
E8 CF 22 10 00
83 С4 0С
B9 0F 00 00 00 |
Поддержка файла конфигурации
Игра поддерживает множество полезных параметров командной строки,
которые было бы удобно сохранить где-нибудь в файле, чтобы не передавать
их каждый раз при запуске. Лучший способ реализовать подобное — это
заставить игру при каждом старте загружать внешнюю библиотеку и
выполнять функцию инициализации. При такой организации можно добавить
множество полезных вещей в конфигурационные файлы, такие как: загрузка
дополнительных библиотек (модулей) без необходимости модификации
исполняемого файла, перехват функций работы с реестром для организации
хранения всех игровых данных в конфигурационном файле игры, а не в
реестре. Не лишним будет поддержка множества профилей с различными
наборами настроек с возможностью быстрого их переключения. Итак,
приступим.
Наиболее удобным местом подключения внешней библиотеки оказалась функция
загрузки библиотеки ebueulax.dll, отвечающей за отображение
лицензионного соглашения. Она как раз вызывается практически сразу после
начала работы WinMain, однако помимо загрузки библиотеки ebueulax.dll и
выполнения функции EBUEula она делает много лишних действий, поэтому мы
полностью перепишем ее, чтобы она загружала библиотеку config.dll и
выполняла библиотечную функцию LoadConfig с указателем на расположение
командной строки в памяти (для её изменения) в качестве параметра.
Ассемблерный код |
Машинный код |
LoadConfigLib proc near
sub esp, 208h
push esi
push edi
push ebx
push ecx
push edx
mov esi, ds:LoadLibraryA
push 67B8C4h; lpLibFileName
call esi ; LoadLibraryA
mov ebx, eax
test ebx, ebx
jz short loc_587443
push offset aLoadconfig
push ebx ; hModule
call ds:GetProcAddress
mov edi, eax
test edi, edi
jz short loc_58743C
mov eax, [esp+220h];**args
push eax
call edi
jmp short loc_587443
loc_58743C:
push ebx ; hLibModule
call ds:FreeLibrary
loc_587443:
pop edx
pop ecx
pop ebx
pop edi
pop esi
add esp, 208h
retn
LoadConfigLib endp |
81 EC 08 02 00 00
56
57
53
51
52
8B 35 E0 51 63 00
68 C4 B8 67 00
FF D6
8B D8
85 DB
74 25
68 B8 B8 67 00
53
FF 15 C8 50 63 00
8B F8
85 FF
74 0C
8B 84 24 20 02 00 00
50
FF D7
EB 07
53
FF 15 20 51 63 00
5A
59
5B
5F
5E
81 C4 08 02 00 00
C3 |
Приведенный код делает несколько вызовов библиотечных
функций. Их адреса были подставлены вручную в машинный код. В следующей
статье (если вы не будете против :) ) я постараюсь рассказать как при
помощи FASM можно генерировать код, полностью готовый к вставке в
исполняемый файл.
Заменяем исходную функцию (начинается со смещения 187400h) новой при
помощи обычного HEX-редактора. Поскольку новая функция значительно
короче исходной, не забываем заменить оставшиеся байты кодом операции
nop — 90h. Мы получили еще 192 байта свободных байта для дополнительного
машинного кода. Это место можно будет использовать в дальнейшем.
Исходный машинный код |
Полученный машинный код |
81 EC 08 02 00 00 53 56
8B 35 E0 51 63 00 57 68
CC B8 67 00 FF D6 8B D8
85 DB 75 0A 5F 5E 5B 81
C4 08 02 00 00 C3 68 C4
B8 67 00 53 FF 15 C8 50
63 00 8B F8 85 FF 75 13
53 FF 15 20 51 63 00 5F
5E 33 C0 5B 81 C4 08 02
00 00 C3 8B 84 24 18 02
00 00 50 FF D6 8B F0 85
... |
81 EC 08 02 00 00 56 57
53 51 52 8B 35 E0 51 63
00 68 C4 B8 67 00 FF D6
8B D8 85 DB 74 25 68 B8
B8 67 00 53 FF 15 C8 50
63 00 8B F8 85 FF 74 0C
8B 84 24 20 02 00 00 50
FF D7 EB 07 53 FF 15 20
51 63 00 5A 59 5B 5F 5E
81 C4 08 02 00 00 C3 90
90 90 90 90 90 90 90 90
... |
Всю остальную работу берет на себя модуль config.dll.
Его можно написать на C/C++/Delphi. Его задача проста — принимать от
исполняемого файла указатель на командную строку приложения и в
зависимости от настроек в конфигурационном файле изменять ее. Как бонус —
нам пригодится возможность подгружать дополнительные модули, настраивая
это в конфигурационном файле. Для этого нам нужно просто вызывать
WinAPI функцию LoadLibrary именем файла модуля, которое необходимо
загрузить в адресное пространство игры.
Поддержка оконного режима
Поддержка оконного режима организована в виде отдельного модуля
wndmode.dll, загрузкой которого занимается config.dll. Этот модуль
занимается тем, что перехватывает все вызовы DirectDraw, изменяет
параметры таким образом, чтобы программа работала в окне, и только после
этого передаёт управление оригинальным функциям DirectDraw.
Это реализуется путем изменения перехватываемой функции. Первые
несколько байт перехватываемой функции заменяются на команду
безусловного перехода к функции перехвата. Этот трюк достаточно просто
реализуется в WinNT (поскольку в WinNT для каждого процесса создается
своя копия образов системных библиотек), но практически нереализуем в
Win9X (так как в Win9X если и можно внести изменения в образ системной
библиотеки, то только в адресных пространствах всех процессов сразу).
Сам по себе модуль wndmode.dll является сильно модифицированной версией библиотеки d3dhook.dll, где реализовано:
- Полная независимость от программы D3D Windower
- Настройки загружаются из секции [WINDOWMODE] файла wndmode.ini
- Настройки по умолчанию заменены для совместимости с Genie
- Добавлен параметр Border, который включает/выключает рамку вокруг окна
- Если игровое разрешение равно системному, автоматически убирается рамка
Теперь эта библиотека готова внедриться практически в любую
DirectDraw игру для того, чтобы сделать ее оконной. Так что если кто-то
будет заниматься модификацией других игр — это пригодится.
Что получилось?
Работа по модификации проделана огромная, здесь мы рассмотрели лишь
некоторые изменения, поскольку описание даже небольших изменений требует
огромных усилий. Остальные модификации при желании вы сможете
исследовать в IDA, скачав готовый модифицированный файл.
В целом удалось добиться таких результатов:
- Вместо ebueulax.dll (отображение лицензии) загружается config.dll (поддержка конфигурационных файлов)
- Оконный режим работы (модуль wndmode.dll — модифицированный d3dhook.dll)
- Код функций работы с курсором адаптирован для работы в оконном
режиме (решает много неприятных проблем, вызванных тем, что игра не
рассчитана на работу в окне)
- Игра корректно работает без ее предварительной установки (достаточно скопировать файлы игры), для игры не нужен CD
- Исправлена ошибка оригинальной игры, когда при игре по IP в списке последних игр обрезались IP адреса
- Изменен порядок и расположение кнопок в меню «Один игрок» и
«Редактор», исправлена ошибка с выделением кнопок в меню «Один игрок»
(переписан внушительный участок кода)
- Исполняемый файл может запускаться из любого подкаталога, а не
только из age2_x1 или корня (практически полностью переписано начало
процедуры WinMain)
- Нет проверки наличия файла empires2.exe (его можно удалить)
- При включенном параметре MIDIMUSIC после сворачивания мелодия начинается заново (не сбрасываются инструменты)
Все изменения работают стабильно, никаких сбоев не обнаружено. В
оконном режиме осталась пока что нерешенной проблема отображения
системного курсора одновременно с игровым после того, как он покинет
область игрового окна и вернется назад. Однако, это вызвано лишь только
тем, что игровой движок не рассчитан на запуск в окне и не
предусматривает вариант, когда курсор покинет игровое окно. В ближайшей
версии модификации это будет исправлено.
Логическим завершением развития данной модификации может стать внедрение
в игру поддержки игровых комнат с созданием соответсвующего игрового
сервера, поскольку официальные игровые комнаты Age of Empires на Zone
были закрыты ещё несколько лет назад. Всё это вполне возможно
реализовать, не имея при этом на руках никаких исходных кодов.
Заключение
Если вам интересно попробовать в действии то, что у меня получилось, можете скачать Age of Empires II: The Conquerors (Lite Edition) (всего 94МБ) с моего сайта.
Простите, что здесь написано обо всем и сразу, и не сильно подробно. Я
старался описать общую схему. В следующих статьях можно рассматривать
каждый этап модификации в отдельности, давать какие-то советы и т.д.
|
Просмотров: 845 |
Добавил: Turbokherson
| Рейтинг: 0.0/0 |
|
|