Menu

Онлайн база чит кодов на русском языке

0-9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z РУС

Туториал по написанию трейнеров для 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)
 

0-9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z РУС