Кратко:
Используется утилка https://github.com/jewalky/DLLInject. Эта утилка добавляет пользовательский загрузчик в конец исполняемого файла, а также создает кастомную таблицу импортов:
- Сначала создается DLL.
- Затем берется оригинальный исполняемый файл (например, сервер A2).
- В этот исполняемый файл в определенные адреса, где расположены функции сервера А2, вставляется код из DLL, изменяя эти функции.
Author: Vladimir Chebotarev aka ex-lend
Edit: igroglaz, As Bestos, Zidane
Server modification to support more than 2 billions of gold coins
Пришел в голову простой способ реализации хранения более 2G денег.
Суть в следующем:
1) Делаем программу с базой, которая будет хранить соответствие (login name, char id) <-> money
2) Добавляем команды:
#money_get XXX
— забирает из вышеперечисленной программы деньги данного персонажа#money_put XXX
— делает обратный процесс, то есть добавляет деньги в программу и убавляет у персонажа#money_info
— пишет на экране баланс дополнительного счета персонажа
В итоге можно создавать для каждого персонажа бесконечное хранилище бабла. Правда использовать единовременно можно не больше 2G. Но зато все будет работать без глюков и не нужно модифицировать клиент (в отличие от реализации нативного варианта).
Возможные трудности реализации:
- Как вставить код в сервер?
Нужно сделатьdll, экспортировать в ней подпрограмму. C помощью программы add_dll из раздела Файлы можно воткнуть вызов подпрограммы из dll в любое место.
Как добавить новую команду?
Ищем в дизассемблере #ready/#set latency/#show latency. Эти коды доступны клиентам. Потом идем по обращениям к ним. Оказываемся в месте, где обрабатываются коды. То куда идет команда - функция поиска подстроки в строчке, пришедшей от клиента. Добавьте свою команду аналогичным образом.Откуда вытащить char id и имя логина?
В упомянутой подпрограмме обработки команд arg_0 - указатель на экземпляр класса Player. Имя логина лежит по адресу [[arg_0] + 0xA78]. Идентификатор персонажа - два двойных слова: [[arg_0] + 0x10], [[arg_0] + 0x14].Как работать с количеством денег игрока?
Метод Player'а - a2server.exe:00534AC1, типа вызова stdcall, аргумент 1 - число добавляемых денег, аргумент 2 - флаг, если 1, то клиент не увидит сообщения "Вы подняли сколько-то там бабла". В ecx само собой записывается указатель на Player. Если посмотреть на его внутренности, то выяснится, что деньги лежат по адресу +0x3C относительно Player.Как отправить игроку сообщение?
Метод a2server.exe:0051CD89. В ecx нужно записать 006C3A08. Тип вызова stdcall. Аргумент 1 - указатель на указатель на строку (char **), аргумент 2 - Player, которому написать сообщение, или 0, если нужно отправить его всем.
Дополнение статьи:
Я смотрю экзешник в hex editor’e обычном и он начинается с 0 (адрес)… А в гидре тоже самое, но начинается с 004000. Почему?
Zidane: Потому что hex-редактор просто просматривает любой файл, а в гидре он «загружен» в соответствии с заголовками PE. Ну и по идее, если говорить про старые режимы работы проца(времена дос, 16 бит) — возможно на адреса от 0 было что-то «замаплено».
As Bestos: В хекс-редакторе оффсет от начала файла В гидре оффсет от начала файла + от базового адреса указанного в PE заголовке.
Offset — это оператор ассемблера, который передает в какой-нибудь регистр смещение определенных данных относительно начала сегмента данных. Фактически адрес начала каких-то данных.
Теперь разберем пример:
// добавить денег игроку void __declspec(naked) GiveMoney(byte* pptr, unsigned long count, unsigned long flags) { __asm { push ebp mov ebp, esp push [ebp+0x10] push [ebp+0x0C] mov ecx, [ebp+0x08] mov edx, 0x00534AC1 call edx mov esp, ebp pop ebp retn } }
As Bestos:
в стеке (esp) хранятся аргументы 0x8
pptr
0xC
count
0x10
flags
код запихивает два последних в стек, а первый в ecx и вызывает функцию по адресу 0x00534AC1
которая судя по всему и есть собственно гив мани внутри а2 она уже вытаскивает их опять из стека и делает гив мани с какими именно соглашениями имеем дело — мне не очень понятно для самой GiveMoney аргументы идут через стек а для вызываемой через call edx
один идет в регистр ecx
, так что я хз. перед вызовом функции GiveMoney имеем
push flags
push long
push pptr
в стеке получается 0x0
pptr
0x4
count
0x8
flags
при вызове GiveMoney в стек закидывается ret addr и адрес аргументов сдвигается >0x4
pptr
и ещё в самой GiveMoney в стек накидывается push ebp
>0x8
pptr
_declspec(naked) говорит не делать вот это
Перед вызовом функции вставляется код, называемый прологом (англ. prolog) и выполняющий следующие действия:
сохранение значений регистров, используемых внутри функции;
запись в стек аргументов функции.
После вызова функции вставляется код, называемый эпилогом (англ. epilog) и выполняющий следующие действия:
восстановление значений регистров, сохранённых кодом пролога;
очистка стека (от локальных переменных функции).
при вызове GiveMoney во кстати глянь в гидре шо там в 0x00534AC1…
Что такое hi.com ?
p.bat
for %%A in (bug_0*.xck) do call p %%A ..\a2server.exe
патчи эти тупо ищут кусочек, его затирают и ровно такой же по размеру вставляют кусочек вместо.
в патче судя по всему старый байт и новый байт. Например:
00085A76: 85 90
00085A77: C0 90
00085A78: 0F 90
90 это NOP; так что какой-то код затирается nop’ами.
int __declspec(naked) __stdcall this_call_impl() { __asm { pop eax // pop esp pop eax // pop eax from the wrapper pop edx // pop method ptr pop ecx // pop this push eax // restore esp from call to the wrapper jmp edx // jump to the method } }