|
Туториал по написанию
трейнеров для DMA и не-DMA игр
Содержание:
1. Немного теории
2. Не-DMA игры
3. DMA игры - поиск поинтера
4. Пишем трейнер для:
4.1 Трейнер для не-DMA игры
4.2 Трейнер для DMA игры
4.3 Общие замечания
5. Заключение
6. Некоторые оговорки
7. Контакты и ссылки
8. Условия распространения и всё прочее
В этой статье я
рассмотрю написание трейнера (trainer'а) для игры. В рунете
совсем мало информации на эту тему, а про написание
DMA-трейнеров я вообще ничего не нашёл (может быть, конечно,
плохо искал), но в процессе написания мною была дана
торжественная клятва, что когда закончу - обязательно напишу
нормальный туториал на эту тему. Хотелось написать туториал, в
котором бы затрагивалось в равной степени как теория, так и
поиск поинтера и написание собственно трейнера.
Основную часть я
намерен посвятить именно DMA играм.
Итак, что
потребуется:
-
- Язык
программирования. Я буду использовать Delphi для примеров. В
принципе, подойдёт любой - для работы с памятью процесса нам
потребуются только WinAPI функции.
-
- Программа
типа ArtMoney (чтобы искать значения в памяти).
Настоятельным образом рекомендую TSearch (несмотря даже на
то, что весит он полтора метра), буду использовать его в
примерах.
-
- Отладчик.
Опять же рекомендую TSearch - он содержит в себе простой и
удобный отладчик, которого вполне хватит. Если у вас есть
SoftICE, и вы умеете им пользоваться - то флаг в руки.
-
- Минимальные
знания ассемблера, общее (хотя бы теоретическое)
представление об отладке программ, устройстве памяти.
приступим...
1. Немного
теории:
Прежде всего, что
такое DMA? DMA - dynamic memory allocation, т.е. динамическое
распределение памяти. Проще говоря, DMA
игры, в отличие от не-DMA игр, хранят используемые ими величины
(нас будут интересовать деньги, жизни и
т.п.) по адресам в памяти, которые меняются после каждого
запуска/перезапуска/загрузки игры.
Все программы DOS
не используют DMA, тогда как большинство игр под Win32 его
использует (не используют только игры
времён Win95-Win98). С не-DMA играми всё
предельно просто - нужно просто найти адрес в памяти, где игра
хранит интересующее нас значение, и
изменить его. А вот с DMA могут сложнее -
что делать, если адреса постоянно изменяются? Для того, чтобы
ответить на этот вопрос, нужно понять,
как сама игра находит нужный адрес. Для этого используются
поинтеры (pointer - указатель, я буду их
называть и так, и так). Адреса поинтеров, в отличие ото всех
остальных, не изменяются. Поинтер
содержит значение, которое соответствует адресу какой-то
величины, используемой игрой. Не важно какой, важно, что смещение других адресов относительно адреса,
на который указывает поинтер, также не меняется (*). (Если не очень понятно - уверен, что станет
понятнее в практической части) Итак, трейнер для DMA игры будет сначала считывать из указателя адрес
интересующего значения, а затем уже его изменять. Осталось только его найти и написать соответствующую
программу. (Всего-то делов, да? :) )
Про регистры
процессора, про то, как устроен стек и т.д. я рассказывать не
намерен. Для этого есть учебники по ассемблеру (вообще говоря, эти темы так или иначе затрагиваются
в любом учебнике программирования на
любом языке).
Теперь к
практике...
2. Не-DMA игры.
Как я уже
говорил, тут всё предельно просто. Запускаем ArtMoney/TSearch/GameHack
и т.п., ищем, затем отсеиваем, определяем
адрес интересующего значения. Пишем трейнер в пункте 4.1.
3. DMA игры -
поиск поинтера.
Итак, сначала нужно
найти адрес интересующей величины. Теперь нужно поставить
брейкпоинт на этот адрес (TSearch - в
меню: AutoHack -> Enable Debugger, Enable AutoHack window, там
нажимаем на кнопку добавления брейкпоинта
и вводим адрес нашей величины). Мы ставим брейкпоинт на
чтение/запись этого адреса, т.е. при чтении или записи этого адреса нам будет показано, какие
инструкции в программе и в каком месте читали/писали из/в него (в более профессиональных отладчиках,
например, SoftICE, выполнение всех программ приостановится, и вылезет окно отладчика). Далее нужно
переключиться в игру и изменить величину. После того, как он изменится, в окне отладчика TSearch появится
строка, например, mov [ebx+A], eax (A - некоторое смещение, может быть любым целым числом, например 4). Эта
ассемблерная инструкция устанавливает значение по адресу ebx+A равным eax. Что такое ebx+A? Это и есть наш
адрес, +A - смещение относительно ближайшего указателя. То есть, поинтер указывает на какой-то адрес,
а через A от него находится интересующее нас значение, и это значение всегда будет смещено на A
относительно адреса, на который указывает поинтер. Но мы ещё не знаем адрес поинтера (адрес у нас был в регистре
ebx, но он должен быть где-то в памяти). Искать его придётся как и любое другое числовое значение. Вычтем из
адреса интересующего нас значения, который мы нашли в самом начале, A, затем переведём его в
десятеричную систему счисления, а затем будем искать. Возможно, мы найдём несколько адресов, содержащий такое
число, но совсем не факт, что все эти адреса - адреса указателей. Для этого придётся перезагрузить игру,
затем вновь найти интересующее значение, из его адреса вычесть A, перевести в десятеричную систему
счисления, и отсеивать.
Слова, конечно,
хорошо, но на примерах любое изучение идёт лучше.
Пример: Red Alert
2. Для наглядности буду использовать
TSearch. Запускаем. Open Process - выбираем нужный процесс (game.exe), приступаем к поиску: деньги - Exact Value, 4 bytes.
Меняем количество денег. Отсеиваем. В результате
останется 3 значения, но только одно из них - значение
собственно денег, остальные два - значения счётчика
и ещё что-то. (Я знаю, что
значение с самым большим адресом - то, которое нужно) У меня
этот адрес получился равным 72C6DAC.
Далее устанавливаем на этот адрес брейкпоинт. Изменяем значение.
В окошке отладчика видим: 4E48FF:
mov [ebx+0x24C], eax. Ассемблерная инструкция mov [ebx+0x24C],
eax устанавливает значение, равное eax по
адресу ebx+024C (ebx - регистр процессора, содержащий адрес,
квадратные скобки указывают на то, что число,
содержащееся в них, - адрес, и нужно изменять значение по
этому адресу), но нас интересует только
ebx+0x24C. Отнимаем от 72C6DAC (адреса, по которому хранится
значение денег) 24Ch - получаем 72C6B60,
переводим в десятеричную систему счисления - 120351584. Теперь
ищем это число в памяти. В начале я получил
37 адресов. Теперь по новой ищем адрес, по которому
находятся деньги, не забыв предварительно добавить в
таблицу уже найденные адреса потенциальных указателей. На
этот раз это 73B29FC. Вновь отнимаем 24С,
переводим, ищем. В результате у меня осталось несколько адресов.
Пожалуй, можно выбрать любой. Один из них
- A1E0C4, сгодится. Перезапускаем. Итак, прибавим к значению,
хранящемуся по адресу A1E0C4, 24С. Теперь
переводим результат сложения в шестнадцатеричную систему
счисления и смотрим на значение по этому адресу.
Если мы видим там количество наших денег - значит всё
удалось, поздравляю :) (Если нет - придётся повторить
всё с начала)
Итак, поинтер мы
нашли. Пожалуй, это был самый сложный этап.
4. Пишем трейнер.
Для записи и чтения
в памяти мы будем использовать две WinAPI функции -
ReadProcessMemory и WriteProcessMemory.
(Всё предельно просто)
4.1. Трейнер для
не-DMA игры.
К примеру, возьмём
старую DOS-овскую игрушку Raptor: Call of The Shadows.
var
Form1: TForm1;
WindowName: integer; // Для удобства объявим как глобальные
переменные.
ProcessId: integer; // Все эти переменные нужны для того, чтобы
найти
ThreadId: integer; // процесс с игрой.
HandleWindow: Integer; //
write: cardinal; // В эту переменную попадёт количество
записанных байтов.
buf: dword; // Тут будет содержаться значение, на которое будем
изменять.
const
WindowTitle = 'RAP'; // Заголовок окна с игрой
Address = $83C4BF64; // Адрес, по которому будем изменять
значение.
NumberOfBytes = 4; // Количество байт, которые будем заменять.
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
WindowName := FindWindow(nil,WindowTitle);
If WindowName = 0 then begin // Если окошка у нас нет, то и
изменять нечего.
MessageDlg('Игра должна быть запущена до трейнера. Запустите ее,
потом трейнер', mtwarning,[mbOK],0);
end;
ThreadId := GetWindowThreadProcessId(WindowName,@ProcessId); //
Ищем хэндл процесса
HandleWindow := OpenProcess(PROCESS_ALL_ACCESS,False,ProcessId);
// с нашей игрой.
buf:=$DEAD; // :) DEADh = 57005d
WriteProcessMemory(HandleWindow, ptr(address), @buf, 4, write);
// Изменяем значение по этому адресу на
наше.
end;
4.2. Пишем
трейнер для DMA-игры.
Тут уже будем
читать из поинтера адрес, по которому будем далее менять
значение. Возьмём в качестве примера, скажем, SimCity 4.
var
Form1: TForm1;
WindowName : integer;
ProcessId : integer;
ThreadId : integer;
HandleWindow : Integer;
b:dword; // Всё по-прежнему, кроме этой переменной - сюда
прочитаем адрес из поинтера.
readwrite:cardinal;
buf : dword;
Const WindowTitle = 'SimCity 4';
Address = $B321E4; // Это адрес поинтера.
NumberOfBytes = 4;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
WindowName := FindWindow(nil,WindowTitle);
If WindowName = 0 then
begin
MessageDlg('Игра должна быть запущена до трейнера. Запустите ее,
потом трейнер', mtwarning,[mbOK],0);
end;
ThreadId := GetWindowThreadProcessId(WindowName,@ProcessId);
HandleWindow := OpenProcess(PROCESS_ALL_ACCESS,False,ProcessId);
ReadProcessMemory(HandleWindow,ptr(address),@b,4,readwrite); //
Прочитали в b значение из адреса поинтера.
b:=b+40; // Смещение адреса денег относительно адреса, на
который указывает поинтер равно 40. Прибавляем.
buf:=$FFFFFFFF; // Денег должно быть много :) (**)
WriteProcessMemory(HandleWindow, ptr(b), @buf, 4, readwrite); //
Наконец, запишем по адресу,
// содержащемуся в b, новое значение денег.
end;
Для того, чтобы
значение "заморозить" нужно выложить на форму таймер или
использовать бесконечный цикл (думаю, это
итак понятно :) ), но в этом случае будет очень полезно
проверять значение по адресу в b, т.к.
всё в том же SimCity 4 если выйти со включённым таймером на
экран выбора города - игра вывалится.
Также хорошей идеей
является использование горячих клавиш. В тему борьбы с DMA это
не входит, так что всё это на вкус
читателя.
4.3. Общие
замечания
Хочется ещё
обратить внимание на то, сколько мы байт читаем и записываем (константа
NumberOfBytes). Есличитаем из поинтера - то читать нужно 4 байта
(т.е. dword - переменная, в которую читаем, должна уместить в
себя это значение; все адреса - 32-разрядные). Если пишем
значение, размер которого 1 байт - то
соответственно и писать надо 1 байт :) Иначе, опять же, чревато
аварийным завершением игры.
Напоминаю, что:
byte это 1 байт (это 8 бит ;) ) - число от 0 до 255.
word это 2 байта - число от 0 до 65535.
dword это 4 байта - число от 0 до 4294967295.
Приводить описание
всех использованных WinAPI функций я счёл ненужным, так что если
интересно - смотри MSDN сам.
5. Заключение.
Ну вот и всё.
Трейнер написан и работает (по крайней мере, надеюсь на это),
остаётся лишь пожелать всем удачи в этом
непростом деле :)
Удачи,
br0k3n_MinD.
6. Некоторые
оговорки
* - я пишу, что
смещение адресов относительно поинтера не меняется. Это так. Но
иногда его сложнее найти - встречаются
конструкции типа mov [ebx+ecx], eax (правда, встречается
значительно реже - это всякие полоски с
жизнью и другие неявные величины). Тут хорошо бы вооружиться
отладчиком посерьёзнее (SoftICE) и либо
смотреть, что откуда попадает в регистры (быть указаны явно),
либо ставить брейкпоинт на адрес, когда
брейкпоинт сработает - смотреть содержимое регистров и
искать их в памяти по-отдельности, точно так же, как
мы это делали в случае с одним регистром. Но так или
иначе, раз игра находит адрес - то можем найти и мы.
(Это здесь написано для того, чтобы
не возникало сомнений относительно неизменности адреса поинтера
8) )
** - я смело
заменяю значение на FFFFFFFFh. Это прокатит для SimCity 4, но в
общем случае также чревато последствиями.
(Значение может быть и 4 байта, но такая цифра может не влезть в
строку, вылезти за пределы экрана или ещё
как-нибудь заглючить ;), в общем, лучше не жадничать)
7. Контакты и
ссылки
Персональные уроки
по почте я давать не намерен. Откровенно ламерские письма будут
отвергнуты (типа, "ничего не понимаю,
объясни" или "ссылка на TSearch сдохла, пришли мне его на мыло")
и, хуже того, рискуют быть где-нибудь
опубликованы мной 8) Если у вас есть какая-нибудь более или
менее конструктивная критика,
действительно принципиальные и хорошие вопросы, идеи,
предложения - то пишите, буду рад.
Сообщения об
ошибках и неточностях также приветствуются.
<
Обсуждение статьи на нашем форуме >
E-mail:
br0k3n_MinD@mail.ru
Сайт, в деятельности которого я, некоторым образом, принимаю
участие:
http://amdf.pp.ru (Там есть кое что ещё из того, что было
написано мной)
Официальный сайт TSearch: http://fly.to/mtc TSearch был также в
разное время замечен здесь:
http://www.xcheater.com/download.ashx/cheat_tools/tsearch_16.zip
http://www.phuzion.com/14/?p=downloads&id=5
Если что - гугл в помощь.
8. Условия
распространения и всё прочее
БЕЗ изменений и при
ОБЯЗАТЕЛЬНОМ оповещении автора о публикации, статья может
публиковаться где угодно, без прочих
условий.
Первый вариант
туториала - 18 ноября 2004 года, усовершенствованный вариант -
05 апреля 2006 года.
То, что было
прочитано перед тем, как был написан этот туториал:
http://tsongkie.com/
http://www.ghu.as.ro/
...и оттуда дальше по ссылкам 8)
|