Информация Чит/трейнер своими руками. Хакер. Часть 1


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 125
Репутация
8 243
2_windowed.png


Приватная статья, часть первая:

Играть в игры любят все, но это гораздо интереснее, когда у тебя имеется нескончаемый запас патронов и здоровья. Чтобы обзавестись и тем и другим, можно погуглить читы и трейнеры для твоей любимой игры. Но как быть, если их еще не разработали? Написать самому! Обладая навыками реверс‑инжиниринга и программирования, сделать это намного проще, чем кажется.

ВЫБОР ИГРЫ​

Для начала определимся с игрой. Мой выбор пал на (далее HLD). Если ты планируешь поэкспериментировать с коммерческой игрой, обрати внимание на сайт , а также на игры .

Так как для написания этой статьи я буду использовать коммерческую игру, мне нужно удостовериться, что лицензионное соглашение (EULA) позволяет это делать.

Начав установку и внимательно прочитав текст EULA, я убедился, что в нем явно запрещается написание и распространение только тех читов и трейнеров, которые мешают работе сервиса, а в нашем случае ничего подобного не планируется. Поэтому смело продолжаем установку.

1_EULA.png


ПОИСК ЗНАЧЕНИЙ​

Для поиска значений, которые будет изменять чит, мы станем использовать (далее CE).

Запустим игру и в настройках игры выберем оконный режим — нам нужно, чтобы на экране помещалось еще что‑то, кроме игры.

2_windowed.png


Как видим, в оконном режиме отсутствует панель заголовка, с помощью которой мы могли бы перетаскивать окно игры по экрану. Чтобы исправить эту неприятность, откроем отладчик , а именно его 32-битную версию (x32dbg) и запустим под ним HLD.

Поставим брейк‑пойнты на функции CreateWindowExA и CreateWindowExW, которые отвечают за создание окна. Найти их можно на вкладке Symbols, выбрав библиотеку user32.dll.

3_symbols.png


Видим, что наше окно создается с параметром dwStyle, имеющим значение WS_POPUP = 0x80000000.

4_WS_POPUP.png


Поменяем это значение на WS_OVERLAPPED = 0x00000000.

5_WS_OVERLAPPED.png


И вот результат: теперь мы можем перемещать окно.

6_windowed.png


После того как мы настроили окно игры с помощью отладчика, ненадолго отложим его. Чтобы найти нужные нам значения в Cheat Engine, разберемся с теорией.


ЧТО ТАКОЕ СТАТИЧЕСКИЙ АДРЕС​

Статический адрес — это адрес, который изменяется предсказуемо по отношению к модулю, которому он принадлежит. Если переменная глобальная, то можно найти ее в сегменте данных.

Статические адреса указываются в формате [module+offset]. Например, в library.dll мы могли обнаружить значение по адресу 0x700004C0 (base = 0x70000000, offset = 0x4C0). Поскольку library.dll может перемещаться и ее базовый адрес загрузки будет меняться, чтобы получить доступ к нашему значению, мы не используем этот адрес напрямую. Вместо этого возьмем адрес [library.dll + 0x4C0]. Следовательно, когда library.dll загружается по базовому адресу 0x10000000, [library.dll + 0x4C0] дает нам 0x100004C0 и у нас появится доступ к нашему значению.

Если же переменная локальная, то искать нужно в стеке. Для этого получаем TebBaseAddress определенного потока, а затем второй указатель из этой структуры (FS:[0x04] или GS:[0x08], в зависимости от разрядности процесса), которая содержит вершину стека. TebBasePointer может быть получен с помощью NtQueryInformationThread (если это 64-битный процесс) или же с помощью Wow64GetThreadSelectorEntry (если это 32-битный процесс в 64-битной системе).


ПОИСК ПОКАЗАТЕЛЕЙ ЗДОРОВЬЯ​

Запускаем Cheat Engine и подключаемся к процессу игры.

7_pl.png


Так как мы не знаем, в каком типе хранится показатель здоровья, выставляем следующие параметры для первого сканирования.

8_fs.png


Далее продолжаем сканирование, не забывая при этом терять hp (показатель здоровья) в игре. Делаем мы это для того, чтобы отслеживать изменения значения hp в памяти игры через CE, а также уменьшать значение в поиске для следующих сканирований. Делать мы это будем до тех пор, пока не будет достигнуто адекватное количество значений в окне CE. Адекватное количество значений в данном случае — это такое количество адресов, проверка которых займет максимум минут пять.

9_search_hp.png


Мне приглянулись вот эти два адреса, которые я добавил в нижнее окно двойным щелчком мыши на них. Приглянулись они мне в первую очередь потому, что значения по этим адресам среди всех остальных имеют наибольший тип — double. Всегда нужно проверять от большего типа к меньшему. То есть сначала проверяем адреса, хранящие тип double, затем float, после integer и так далее. Более подробно о размере типов данных можно прочитать в .

10_add_ct.png


Если мы поменяем значение по адресу 0x36501940, то на экране появится полоса здоровья, но его количество не поменяется.

11_hp_bar.png


Если теперь мы поменяем значение по адресу 0x36501A30, то на экране появится полоса hp и значение изменится. Это значит, что мы нашли адрес, в котором хранится значение здоровья в игре. Значение хранится в формате double (стандарт ).

12_hp.png


Дадим название найденным нами адресам: hp_bar и hp соответственно. Однако, как я уже рассказывал в разделе, посвященном статическим адресам, найденный нами адрес будет бесполезен после того, как мы выйдем в меню или перезапустим игру.

Поиск статического адреса для индикатора здоровья​

Для дальнейшего поиска статического адреса вернемся к отладчику. В окне дампа переходим по ранее полученному адресу 0x36501A30, в котором хранится значение hp.

13_hp_addr.png


Ставим по адресу 0x36501A34 аппаратный брейк‑пойнт на запись и теряем в игре здоровье. Брейк‑пойнт срабатывает, и мы видим, что новое значение hp берется из регистра EDI. Это значение является первым параметром текущей функции.

14_static_addr.png


Выйдя из этой функции, проследим, откуда она получает свой первый параметр. Мы увидим, что передаваемый параметр — это возвращаемое значение функции по адресу 0x003EFCE9.

15_static_addr.png


Поставим брейк‑пойнт на вызов функции по адресу 0x003EFCE9, а дальше продолжим отладку, пока не остановимся на ее вызове. Зайдя внутрь функции, выполняем ее до конца. Как только мы достигнем адреса 0x00F88E19, мы увидим, что регистр EAX хранит адрес значения hp. Очевидно, что в этой функции происходит доступ к нашему адресу через арифметику с указателями для структур, а именно через прибавление к указателю смещений и дальнейшего его разыменования. Более подробно об этом можно прочитать . Нам нужно будет повторно пройтись по этой функции, чтобы узнать, через какой адрес и смещения она получает адрес значения hp.

16_static_addr.png


После того как мы узнали адрес 0x353F9BB0, из которого получается адрес значения hp, начинаем выходить из функций. При этом внимательно отслеживаем, что передается им в качестве параметров. Спустя пару выходов мы наткнемся на следующее.


17_final_static_addr.png


Мы нашли статический адрес! Если посмотреть его расположение в памяти, он находится в секции .data.

18_final_static_addr_memory_map.png


Зная все смещения, добавим их в CE, нажав Add Address Manually.

19_static_addr_ct.png


ПОИСК ЗНАЧЕНИЯ ЧИСЛА ПАТРОНОВ​

Теперь приступим к поиску значения числа патронов (ammo). Первое сканирование делаем с такими же параметрами поиска, как когда мы искали здоровье.

20_ammo_fs.png


В данном случае мы смогли найти лишь одно значение, и это значение полосы, которая показывает число боеприпасов.

21_ammo_bar.png


В игре этот индикатор не появился. В отличие от полосы здоровья, он отображается только после нажатия на кнопку E или во время выстрелов.

22_show_ammo_bar.png


Поиск статического адреса для ammo​

Мы понимаем, что показания индикаторов в игре всегда сравниваются с фактическими. Если одна из полос показывает не то, что нужно, ее длина изменяется. Поэтому возвращаемся к отладчику и начинаем с аппаратного брейк‑пойнта на запись по адресу 0x365014С4. Как видим по комментариям, эта функция уже нам встречалась.

23_ammo.png


По аналогии с поиском hp, выходим из функции.

24_ammo.png


Так как мы уже знаем, что индикатор должен получать значение где‑то раньше, нам придется пролистать окно дизассемблера выше, пока мы не увидим функцию, предположительно получающую фактическое значение ammo.

25_ammo.png


Мы видим, что в этой функции мы уже были, а это значит, что она тоже получает значение, но уже ammo — 365014E0. Только какое‑то оно странное.

26_ammo.png


Добавив это «странное» значение в Cheat Engine, а потом изменив его, к примеру, на 100, мы увидим, что на экране появится индикатор патронов и его значение поменяется. Значит, мы нашли адрес, в котором хранится значение ammo в игре.

27_ammo.png


Зная все смещения от статического адреса к адресу значений ammo, добавим их в CE, нажав Add Address Manually.

28_static_addr_ct.png


Скорее всего, боеприпасы в HLD представляют собой заряд энергии и поэтому хранятся в процентах, ведь при их поиске через отладчик можно было увидеть строки, содержащие слово energy. Которое намекает на то, как будет выглядеть значение в памяти. Например, игра от одной небезызвестной польской компании хранила патроны в памяти вместе, а для игрока показывала раздельно: как рожок, так и количество оставшихся патронов, поэтому при их поиске не удавалось найти каждое из значений, а нужно было искать их сумму.

ПРОВЕРКА ПОЛУЧЕННОГО СТАТИЧЕСКОГО АДРЕСА​

Чтобы проверить, правильно ли мы определили адреса, нужно выйти в меню игры и вернуться к игровому процессу или же перезапустить игру.

Проверка для HP​

Так выглядит наша cheat table для hp.

29_hp.png


А вот так она выглядит после перезапуска игры.

30_hp.png


Проверка для ammo​

Так выглядит наша cheat table для ammo.

31_ammo.png


А вот так она выглядит после перезапуска игры.

32_ammo.png


КАК БУДЕТ ВЫГЛЯДЕТЬ НАШ УКАЗАТЕЛЬ В C++​

В нашем чите доступ к найденным адресам значений будет таким.

C++:
static_addr = (DWORD)GetModuleHandle(0);
static_addr = *(DWORD*)(static_addr + 0x255AF150);
static_addr = *(DWORD*)(static_addr);
static_addr = *(DWORD*)(static_addr + 0xD48);
static_addr = *(DWORD*)(static_addr + 0x0C);
static_addr = (DWORD*)(static_addr + 0xC4);
static_addr = *(DWORD*)(*static_addr + 0x08);
static_addr = *(DWORD*)(static_addr + 0x44);
static_addr = *(DWORD*)(static_addr + 0x10);
drifter_hp = (double*)(DWORD*)*(DWORD*)(static_addr + 0x1FD8);
drifter_ammo = (double*)(DWORD*)*(DWORD*)(static_addr + 0x268C);

НАПИСАНИЕ ТРЕЙНЕРА​

По принципу действия читы можно разделить на две группы: внутренние и внешние. Внешние читы — это отдельное приложение, запущенное в системе в виде процесса. Внутренние читы обычно реализованы как динамическая библиотека, внедряемая в процесс игры.

Мы будем писать внутренний чит, поэтому нам понадобится не только сама библиотека, но и инжектор, который внедрит нашу библиотеку в процесс игры. Инжектор получит список процессов, найдет процесс игры, выделит в ней память, в которую запишет наш внутренний чит, а после создаст удаленный поток внутри игры для выполнения кода нашего чита.

Injector​

Код нашего инжектора выглядит следующим образом.

C++:
#include <windows.h>
#include <tlhelp32.h>
// Имя внедряемой dll
const char* dll_path = "internal_trainer_hld.dll";
int main(void) {
    HANDLE process;
    void* alloc_base_addr;
    HMODULE kernel32_base;
    LPTHREAD_START_ROUTINE LoadLibraryA_addr;
    HANDLE thread;
    HANDLE snapshot = 0;
    PROCESSENTRY32 pe32 = { 0 };
    DWORD exitCode = 0;
    pe32.dwSize = sizeof(PROCESSENTRY32);
    // Получение снапшота текущих процессов
    snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    Process32First(snapshot, &pe32);
    do {
        // Мы хотим работать только с процессом HyperLightDrifter
        if (wcscmp(pe32.szExeFile, L"HyperLightDrifter.exe") == 0) {
            // Во-первых, нам нужно получить дескриптор процесса, чтобы использовать его для следующих вызовов
            process = OpenProcess(PROCESS_ALL_ACCESS, true, pe32.th32ProcessID);
            // Чтобы не повредить память, выделим дополнительную память для хранения нашего пути к DLL
            alloc_base_addr = VirtualAllocEx(process, NULL, strlen(dll_path) + 1, MEM_COMMIT, PAGE_READWRITE);
            // Записываем путь к нашей DLL в память, которую мы только что выделили внутри игры
            WriteProcessMemory(process, alloc_base_addr, dll_path, strlen(dll_path) + 1, NULL);
            // Создаем удаленный поток внутри игры, который будет выполнять LoadLibraryA
            // К этому вызову LoadLibraryA мы передадим полный путь к нашей DLL, которую мы прописали в игру
            kernel32_base = GetModuleHandle(L"kernel32.dll");
            LoadLibraryA_addr = (LPTHREAD_START_ROUTINE)GetProcAddress(kernel32_base, "LoadLibraryA");
            thread = CreateRemoteThread(process, NULL, 0, LoadLibraryA_addr, alloc_base_addr, 0, NULL);
            // Чтобы убедиться, что наша DLL внедрена, мы можем использовать следующие два вызова для синхронизации
            WaitForSingleObject(thread, INFINITE);
            GetExitCodeThread(thread, &exitCode);
            // Наконец, освобождаем память и очищаем дескрипторы процесса
            VirtualFreeEx(process, alloc_base_addr, 0, MEM_RELEASE);
            CloseHandle(thread);
            CloseHandle(process);
            break;
        }
    // Перебор процессов из снапшота
    } while (Process32Next(snapshot, &pe32));
    return 0;
}

DLL​

Наша библиотека будет состоять из следующих модулей:
  • главный модуль (dllmain.cpp);
  • модуль хуков (directx_hook.cpp и directx_hook.h);
  • модуль обратных вызовов (directx_hook_callbacks.h);
  • модуль работы с памятью (memory.h).

Главный модуль​

При загрузке нашей библиотеки через функцию LoadLibraryA создаетcя поток, в котором будет происходить вызов двух функций. Первая установит хук, а вторая отрисует меню нашего чита и изменит ранее найденные значения через нажатие клавиш.

C++:
#include "directx_hook.h"
double full_health = 6;
double full_ammo = 100;
DWORD static_addr = 0;
DWORD *drifter = 0;
// Получаем player class
void get_player_class() {
    if (drifter == NULL) {
        static_addr = (DWORD)GetModuleHandle(0);
        static_addr = *(DWORD*)(static_addr + 0x255AF150);
        static_addr = *(DWORD*)static_addr;
        static_addr = *(DWORD*)(static_addr + 0xD48);
        static_addr = *(DWORD*)(static_addr + 0x0C);
        drifter = (DWORD*)(static_addr + 0xC4);
    }
}
// Отрисовываем наше меню
void draw_menu(directx_hook* hook) {
    hook->draw_text(10, 70, D3DCOLOR_ARGB(255, 255, 255, 255), "Full health press F1");
    hook->draw_text(10, 90, D3DCOLOR_ARGB(255, 255, 255, 255), "Full ammo press F2");
}
// Изменяем по нажатию клавиш значения hp и ammo
void run(directx_hook* hook) {
    double* drifter_hp;
    double* drifter_ammo;
    draw_menu(hook);
    get_player_class();
    if (*drifter) {
        static_addr = *(DWORD*)(*drifter + 0x08);
        static_addr = *(DWORD*)(static_addr + 0x44);
        static_addr = *(DWORD*)(static_addr + 0x10);
        drifter_hp = (double*)(DWORD*)*(DWORD*)(static_addr + 0x1FD8);
        drifter_ammo = (double*)(DWORD*)*(DWORD*)(static_addr + 0x268C);
        if (GetAsyncKeyState(VK_F1) & 1) {
            *drifter_hp = full_health;
        }
        if (GetAsyncKeyState(VK_F2) & 1) {
            *drifter_ammo = full_ammo;
        }
    }
}
void injected_thread() {
    // Добавляем нашу функцию, которая будет вызываться при inline hook
    directx_hook::get_instance()->add_callback(&run);
    // Начинаем работу по установке inline hook
    directx_hook::get_instance()->initialize();
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
            CreateThread(0, 0, (LPTHREAD_START_ROUTINE)injected_thread, 0, 0, 0);
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}

Модуль хуков​

Это модуль, в котором будет выполняться перехват функции — она отвечает за отрисовку сцен. Это необходимо для того, чтобы отрисовывать наше меню.

Хуки​

Есть множество , но мы будем использовать самый простой из этого списка — byte patching/inline hook. Выбор объясняется просто: наша игра поддерживает только одиночный режим, а другие хуки служат для обхода античитов в многопользовательских играх и нам попросту не нужны.

C++:
#pragma once
#include <windows.h>
#include <stdint.h>
#include <d3d9.h>
#include <d3dx9.h>
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
typedef HRESULT(WINAPI* _endscene)(LPDIRECT3DDEVICE9 pDevice);
class directx_hook;
typedef void (*_callback_run)(directx_hook* hook);
class directx_hook {
private:
    static directx_hook* instance;
    static uint8_t* orig_endscene_code;
    static uint32_t endscene_addr;
    static LPDIRECT3DDEVICE9 hooked_device;
    static bool hook_ready_pre, hook_ready;
    _callback_run callback_run;
    LPD3DXFONT font;
    void set_hooks();
    uint32_t locate_endscene();
public:
    static _endscene orig_endscene;
    static directx_hook* get_instance() {
        if (!directx_hook::instance) {
            directx_hook::instance = new directx_hook();
        }
        return directx_hook::instance;
    }
    static void delete_instance() {
        if (directx_hook::instance) {
            delete directx_hook::instance;
            directx_hook::instance = NULL;
        }
    }
    void initialize();
    void add_callback(_callback_run cb);
    void draw_text(uint32_t x, uint32_t y, D3DCOLOR color, const char* text);
    uint32_t init_hook_callback(LPDIRECT3DDEVICE9 device);
    HRESULT WINAPI endscene_hook_callback(LPDIRECT3DDEVICE9 pDevice);
};

В остальных модулях мы выполним следующие действия:
  • создадим временное устройство Direct3D и получим его таблицы VF, чтобы найти адрес функции EndScene;
  • установим временный хук на функцию EndScene;
  • при выполнении хука выполним обратный вызов и передадим ему адрес устройства, которое использовалось для вызова функции EndScene. Затем удалим хук и восстановим нормальное выполнение функции EndScene;
  • выполним нашу функцию run;
  • вернемся ко второму пункту.
Все это делается, чтобы написанный нами код отрисовывал меню чита и по нажатию кнопок изменялись значения hp и ammo.

C++:
#include "directx_hook.h"
#include "directx_hook_callbacks.h"
#include "memory.h"
directx_hook* directx_hook::instance = NULL;
uint8_t* directx_hook::orig_endscene_code = NULL;
uint32_t directx_hook::endscene_addr = NULL;
LPDIRECT3DDEVICE9 directx_hook::hooked_device = NULL;
_endscene directx_hook::orig_endscene = NULL;
bool directx_hook::hook_ready = false;
bool directx_hook::hook_ready_pre = false;
void directx_hook::initialize() {
    // Проверяем, находится ли в адресном пространстве d3d9.dll
    while (!GetModuleHandleA("d3d9.dll")) {
        Sleep(10);
    }
    // Получаем адрес функции endscene
    directx_hook::endscene_addr = this->locate_endscene();
    // Проверяем, успешно ли получен адрес EndScene
    // Если да, то устанавливаем наш inline hook
    if (directx_hook::endscene_addr) {
        directx_hook::orig_endscene_code = hook_jmp(directx_hook::endscene_addr, (uint32_t)&endscene_trampoline);
    }
    // Ждем
    while (!directx_hook::hook_ready_pre) {
        Sleep(10);
    }
    // Сигнализируем, что наш хук готов
    directx_hook::hook_ready = true;
}
// Заменить присвоением
// Как только наш хук будет установлен
void directx_hook::add_callback(_callback_run cb) {
    if (!directx_hook::hook_ready)
        callback_run = cb;
}
// Обертка для отрисовки текста
void directx_hook::draw_text(uint32_t x, uint32_t y, D3DCOLOR color, const char* text) {
    RECT rect;
    rect.left = x + 1;
    rect.top = y + 1;
    rect.right = rect.left + 1000;
    rect.bottom = rect.top + 1000;
    this->font->DrawTextA(NULL, text, -1, &rect, 0, color);
}
uint32_t directx_hook::init_hook_callback(LPDIRECT3DDEVICE9 device) {
    directx_hook::hooked_device = device;
    while (directx_hook::orig_endscene_code == NULL) {}
    unhook_jmp(directx_hook::endscene_addr, orig_endscene_code);
    D3DXCreateFont(directx_hook::hooked_device, 15, 0, FW_BOLD, 1, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Arial", &this->font);
    this->set_hooks();
    directx_hook::hook_ready_pre = true;
    return directx_hook::endscene_addr;
}
HRESULT WINAPI directx_hook::endscene_hook_callback(LPDIRECT3DDEVICE9 pDevice) {
    HRESULT result;
    callback_run(this);
    result = orig_endscene(pDevice);
    this->set_hooks();
    return result;
}
void directx_hook::set_hooks() {
    uint32_t ret;
    // Устанавливаем хук
    ret = hook_vf((uint32_t)directx_hook::hooked_device, 42, (uint32_t)&my_endscene);
    if (ret != (uint32_t)&my_endscene) {
        *(uint32_t*)&directx_hook::orig_endscene = ret;
    }
}
uint32_t directx_hook::locate_endscene() {
    LPDIRECT3D9 pD3D;
    D3DPRESENT_PARAMETERS d3dpp;
    LPDIRECT3DDEVICE9 pd3dDevice;
    uint32_t endscene_addr;
    // Cоздаем временный объект D3D9 и проходимся по виртуальной таблице, чтобы получить адрес метода EndScene
    pD3D = Direct3DCreate9(D3D_SDK_VERSION);
    if (!pD3D) {
        return 0;
    }
    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = GetForegroundWindow();
    HRESULT result = pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3dpp.hDeviceWindow, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pd3dDevice);
    if (FAILED(result) || !pd3dDevice) {
        pD3D->Release();
        return 0;
    }
    // Получаем адрес, который хранится в rdata
    endscene_addr = get_vf((uint32_t)pd3dDevice, 42);
    // Очищаем
    pD3D->Release();
    pd3dDevice->Release();
    return endscene_addr;
}

Модуль обратных вызовов​

Это модуль, в котором реализована функция inline hook — endscene_trampoline, a также функции init_endscene, на которую хук передаст управление. Она, в свою очередь, запустит функцию init_hook_callback для установки хука. А функция my_endscene вызовет функцию endscene_hook_callback, которая отвечает за запуск функции run.

C++:
#include "directx_hook.h"
DWORD WINAPI init_endscene(LPDIRECT3DDEVICE9 device_addr) {
    return directx_hook::get_instance()->init_hook_callback(device_addr);
}
HRESULT WINAPI my_endscene(LPDIRECT3DDEVICE9 pDevice) {
    return directx_hook::get_instance()->endscene_hook_callback(pDevice);
}
// Наш inline hook
__declspec(naked) void endscene_trampoline() {
    __asm {
        MOV EAX, DWORD PTR SS:[ESP + 0x4]
        PUSH EAX
        CALL init_endscene
        JMP EAX
    }
}

Модуль работы с памятью​

Функция Direct3DCreate9 возвращает указатель на интерфейс IDirect3D9, который содержит таблицу виртуальных функций (далее VMT). Среди них по индексу 42 располагается нужная нам функция EndScene. Поэтому напишем несколько функций, которые позволят получить VMT, а также писать, читать и изменять права доступа памяти. Более подробно о таблице виртуальных функций рассказано .

C++:
#pragma once
#include <windows.h>
#include <stdint.h>
// Чтение из памяти
template<typename T>
T read_memory(uint32_t address) {
    return *((T*)address);
}
// Запись в память
template<typename T>
void write_memory(uint32_t address, T value) {
    *((T*)address) = value;
}
template<typename T>
uint32_t protect_memory(uint32_t address, uint32_t prot) {
    DWORD old_prot;
    VirtualProtect((LPVOID)address, sizeof(T), prot, &old_prot);
    return old_prot;
}
// Получение адреса EndScene в VMT
uint32_t get_vf(uint32_t class_inst, uint32_t func_idx) {
    uint32_t vf_table;
    uint32_t hook_addr;
    vf_table = read_memory<uint32_t>(class_inst);
    hook_addr = vf_table + func_idx * sizeof(uint32_t);
    return read_memory<uint32_t>(hook_addr);
}
// Получение адреса EndScene из VMT
uint32_t hook_vf(uint32_t class_inst, uint32_t func_idx, uint32_t new_func) {
    uint32_t vf_table;
    uint32_t hook_addr;
    uint32_t old_prot;
    uint32_t orig_func;
    vf_table = read_memory<uint32_t>(class_inst);
    hook_addr = vf_table + func_idx * sizeof(uint32_t);
    old_prot = protect_memory<uint32_t>(hook_addr, PAGE_READWRITE);
    orig_func = read_memory<uint32_t>(hook_addr);
    write_memory<uint32_t>(hook_addr, new_func);
    protect_memory<uint32_t>(hook_addr, old_prot);
    return orig_func;
}
// Установка хука
unsigned char* hook_jmp(uint32_t hook, uint32_t new_func) {
    uint32_t new_offest;
    uint32_t old_prot;
    uint8_t* originals = new uint8_t[5];
    // Вычисляем смещение до нашего inline hook
    new_offest = new_func - hook - 5;
    // Чтобы не произошло краша, так как все виртуальные функции
    // хранятся в rdata, изменяем права доступа по адресу функции
    old_prot = protect_memory<uint8_t[5]>(hook, PAGE_EXECUTE_READWRITE);
    // Сохраняем оригинальный адрес
    for (uint8_t i = 0; i < 5; i++) {
        originals[i] = read_memory<uint8_t>(hook + i);
    }
    // Перезаписываем нашим кодом
    write_memory<uint8_t>(hook, 0xE9);
    write_memory<uint32_t>(hook + 1, new_offest);
    // Восстанавливаем прежние права
    protect_memory<uint8_t[5]>(hook + 1, old_prot);
    return originals;
}
// Снимаем хук
void unhook_jmp(uint32_t hook, uint8_t* originals) {
    uint32_t old_prot;
    // Чтобы не произошло краша, изменяем права доступа по адресу функции
    old_prot = protect_memory<uint8_t[5]>(hook, PAGE_EXECUTE_READWRITE);
    // Записываем прежний адрес
    for (unsigned int i = 0; i < 5; i++) {
        write_memory<uint8_t>(hook + i, originals[i]);
    }
    // Восстанавливаем прежние права
    protect_memory<uint8_t[5]>(hook + 1, old_prot);
    delete[] originals;
}

ПРОВЕРКА РАБОТОСПОСОБНОСТИ​

Запускаем наш чит, получаем урон, тратим боезапас, нажимаем F1-F2 и видим, как значения hp и ammo становятся максимальными. Значит, чит работает!

33_check.png


ВЫВОДЫ​

С помощью Cheat Engine мы научились искать фактическое значение той переменной, которую мы хотим модифицировать. Затем с помощью отладчика мы нашли статические адреса для значений, определенных с помощью CE, при этом разобрались, как работает игровой движок. Например, есть общая функция для получения значений игрока, таких как hp и ammo, есть общая функция для записи изменений этих значений. С этими знаниями поиск других значений будет гораздо проще и быстрее. А написанный нами код можно использовать как основу, базу для читов к другим играм.

Несколько полезных ссылок по теме взлома игр и написанию читов:
Классная статья! wink1
 
Верх Низ