Всем, привет!
В этой части я хочу рассказать про поиск объектов в игре. А так же про перехват функций.
И так. Начнём
Запустим игру и выберем то что хотим перехватить. Например у нас с какой-то частотой падает солнце, запустим Cheat Engine, и попробуем найти массив где находятся солнце в игре.
Например мы на экране видим 1 солнце в Cheat Engine ищем 1, потом когда солнца будет 2шт, нужно отсеять уже 2, и так далее пока не будет минимальное количество адресов.
[Ссылки могут видеть только зарегистрированные пользователи. ]
Вот мы нашли 2 адреса в памяти игры, попробуем узнать функцию которая записывает в этот адрес в памяти. Показывать как это найти в Cheat Engine, я не буду, так как в интернете полно видео уроков как пользоваться Cheat Engine.
Первый статический, а значит что после перезапуска игры, этот адрес не изменится. Попробуем с него начать.
[Ссылки могут видеть только зарегистрированные пользователи. ]
И видим инкрементирование и декрементирование когда солнце пропадает, очень похоже на то что нам нужно.
Копируем адрес инструкции инкрементирования, и переходим в IDA Pro. Для того чтобы понять что там происходит. Запустим под отладчиком, и поставим breakpoint чтобы узнать какая функция вызывает sub_476100. Смотрим на скриншот.
И мы увидели что создалось солнце, и через 1-2 сек игра крашнулась. А нам интересен был факт того что солнце создалось, а значит это не та функция что нам нужна.
Берем второй адрес и точно так же делаем все с ним.
[Ссылки могут видеть только зарегистрированные пользователи. ]
И видим что здесь немного другие инструкции. Но не важно, выбираем первую когда солнце создается. И переходим в IDA Pro.
Видим что выполняются они в функции sub_420D90, теперь пробуем найти откуда эта функция вызывается, все в точности как и с прошлым вариантом.
Нашли sub_40F400, а эта функция вызывается из sub_4163D0. И видим.
[Ссылки могут видеть только зарегистрированные пользователи. ]
И видим что солнышко больше никогда не создастся. Ура мы нашли функцию которая создает солнце. А если быть более точным это функция sub_40F400.
Давайте попробуем проанализировать что это за функция.
int __thiscall sub_40F400(_DWORD *this, int a2, int a3, int a4, int a5)
Видим что первый параметр это указатель на объект который управляет созданием солнц в игре. Остальные параметры не понятные, знаем что последний равен 0, а3 равен 60, а параметр а4 принимает значение 4 или 6.
А второй параметр получается после вызова функции с передачей аргумента 550, и прибавлением к результату 100. Очень похоже на рандом но сказать окончательно не могу.
И так что мы имеем в плане этого класса который управляет солнцами. Некий класс в котором есть функция sub_40F400(int, int, int, int)
Например класс у нас такой.
Код:
class Creator
{
public:
void sub_40F400(int, int, int, int);
}
Попробуем реализовать это в нашей DLL.
И так чтобы работала та функция которая нам нужна нам нужно указать её адрес в памяти. Смотрим код ниже
Код:
void Creator::CreateSun(int a2, int a3, int a4, int a5)
{
typedef void(__thiscall *t)(Creator*, int, int, int, int);
t f = (t)0x0040F400;
f(this, a2, a3, a4, a5);
}
Всё класс объекта создан теперь осталось найти адрес этого объекта в памяти игры. Для этого вернемся в IDA Pro. И найдем какую-то функцию которая использует соглашение о вызове __cdecl, __stdcall.
Чуть не забыл о соглашениях, подробно можно о них почитать вот [Ссылки могут видеть только зарегистрированные пользователи. ]
Почему именно __cdecl, __stdcall, а все потому что эти функции проще всего перехватить. Так как аргументы функции передаются через стек, а не через регистры.
Хотя я покажу в следующих частях, как перехватить функцию где соглашение о вызове не позволяет это просто сделать. Например __usercall это последствия оптимизации программы/игры.
Помним что функция sub_40F400 вызывается в sub_4163D0, ищем где вызывается sub_4163D0. В функции sub_4181E0, но здесь все еще __thiscall, идем дальше sub_418600, тоже самое, дальше...
class Creator
{
Creator() {};
public:
void CreateSun(int a2, int a3, int a4, int a5);
/* 000 */ virtual ~Creator() {}; // Помним что внутри объекта есть виртуальные функции, а значит по адресу 0000, лежит таблица виртуальных функций
/* 004 */
/* 0004 */ unsigned long __uUnkValue0004[0x15F6 - 1]; // 0x57D8u делим на 4 так как мы используем тип long,
/* 57D8 */ //а 0x57D8u размер в байтах. -1 потому что первые 4 байта это указатель на таблицу виртуальных функций
};
CompileTimeSizeCheck(Creator, 0x57D8);
#include "stdafx.h"
#include "Creator.h"
void Creator::CreateSun(int a2, int a3, int a4, int a5)
{
typedef void(__thiscall *t)(Creator*, int, int, int, int);
t f = (t)0x0040F400;
f(this, a2, a3, a4, a5);
}
и видим что CompileTimeSizeCheck(Creator, 0x57D8); не подсвечивается красным, для примера просто измените значение размера.
Продолжаем поиск __cdecl, __stdcall. И видим что функция sub_4528B0, вызывается из sub_452B30. Идем дальше sub_452820, и дальше
Поиск очень долгая и нудная работа. Для того чтобы не писать здесь, я сделал видео о том как я нашел эту функцию.
[Ссылки могут видеть только зарегистрированные пользователи. ]
И так вот мы нашли эту функцию
int __stdcall sub_40D840(signed int a1)
Пробуем её заменить на свою, для того чтобы определить объект который создает солнце. Смотрим на код.
#pragma once
class Creator
{
Creator() {};
public:
static int __stdcall sub_40D840(Creator* pCreator);
void CreateSun(int a2, int a3, int a4, int a5);
/* 000 */ virtual ~Creator() {}; // Помним что внутри объекта есть виртуальные функции, а значит по адресу 0000, лежит таблица виртуальных функций
/* 004 */
/* 0004 */ unsigned long __uUnkValue0004[0x15F6 - 1]; // 0x57D8u делим на 4 так как мы используем тип long,
/* 57D8 */ //а 0x57D8u размер в байтах. -1 потому что первые 4 байта это указатель на таблицу виртуальных функций
};
extern Creator* g_Creator;
CompileTimeSizeCheck(Creator, 0x57D8);
Теперь мы можем управлять нашим объектом, из любого места в DLL. Например вот так:
Код:
if (g_Creator)
g_Creator->CreateSun(100, 60, 4, 0);
Круто ведь, да?)
Можем когда угодно создать солнце)
Например можем по нажатию кнопки, или еще после чего-то) Это уже в пределах вашей фантазии)
Теперь я покажу как в игре, расширить объект, например создадим переменную(объект, любого другого класса) в которой будет хранится количество солнц, созданные нами с помощью нашей DLL.
Создадим класс CreatorEx.
class Creator
{
Creator() {};
public:
static int __stdcall sub_40D840(Creator* pCreator);
void CreateSun(int a2, int a3, int a4, int a5);
/* 000 */ virtual ~Creator() {}; // Помним что внутри объекта есть виртуальные функции, а значит по адресу 0000, лежит таблица виртуальных функций
/* 004 */
/* 0004 */ unsigned long __uUnkValue0004[0x15F6 - 1]; // 0x57D8u делим на 4 так как мы используем тип long,
//а 0x57D8u размер в байтах. -1 потому что первые 4 байта это указатель на таблицу виртуальных функций
/* 57D8 */ CreatorEx objCreatorEx;
/* 57E0 */
};
extern Creator* g_Creator;
CompileTimeSizeCheck(Creator, 0x57E0);
Очевидно что размер изменился, заставим теперь игру выделить память под нашу переменную.
Вспоминаем место где мы видели размер нашего класса, а это было в функции sub_4528B0.