[0x9]Легкий Геймхакинг: Шаг за шагом.

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Автор: Unseen
Специально для XSS.IS


Предыдущая часть:
https://xss.is/threads/114098/

Вступление…
Доброго времени суток дорогие форумчане! Давно не было новых частей в нашем цикле статей из-за нехватки времени. Перед новым годом как раз появилось свободное время и я решил заполнить этот пробел! Поехали =)

В прошлых частях мы уже разобрались со многими базовыми моментами и приобрели хоть какое-то понимание в том, как происходит взлом, реверс и написание читов под игры. До этого момента мы работали с игрой в варианте External(Внешний) чита – надеюсь не забыли что это за вид такой и каковы его принципы работы. (читайте прошлые части)
В этой статье, чтобы эти инь и янь не отставили друг от друга мы поговорим про такой вид, как Internal(Внутренний) читы.

Теоретическая часть …
Ну как вы могли догадаться по самому переводу слова «Internal» - внутренний, говорит само за себя, это когда программа(чит) загружается в саму игру, в ее процесс – виртуальное адресное пространство. Чаще всего Internal представляет собой файл DLL ( в виде динамической библиотеки). Но файлы DLL не могут быть запущены сами по себе - как программы .exe, на то и они динамически подключаемые. Их нужно как-то загрузить в адресное пространство процесса для доступа к их внутреннему функционалу и коду. А если более подробно рассмотреть:
DLL (Dynamic-Link Library) (<-ТЫК) — это динамическая библиотека, которая предоставляет набор функций и ресурсов, доступных для использования другими программами. Она позволяет разделить функциональность на модули и использовать её повторно в разных приложениях. Даже мы когда используем, какие-то функции из WinAPI фактически загружаем такие DLL в виртуальное адресное пространство своего процесса. Когда программа взаимодействует с ОС, она часто вызывает функции, реализованные в этих библиотеках.

Например:
kernel32.dll
предоставляет функции для работы с процессами, потоками и памятью. Допустим те же: WriteProcessMemory, ReadProcessMemory, все они находятся в этом файле DLL.

В отличие от External( внешних) читов, которые функционируют независимо от игры и обычно взаимодействуют с ней через внешние программы или модификации, Internal(внутренние) читы загружаются в виртуальное адресное пространство процесса игры и работают изнутри, как часть этого процесса. Мы получаем прямой доступ к внутренней памяти игры, функциям, разным структурам, объектам этого процесса.

Что дает такой способ? Какие преимущества и недостатки?

Преимущества и отличия от External:
  • Доступ к внутренним функциям: Возможность напрямую вызывать функции игры и использовать её ресурсы (например, рендерить на экране, изменять физику, изменять эти сами функции, ставить хуки).
  • Скорость: Работа с памятью и данными происходит быстрее, так как код выполняется внутри процесса. Нам не нужно постоянно извне через WinAPI делать запрос к процессу игры, для чтения или записи данных из памяти как с External. Вместо этого мы можем напрямую обращаться к памяти. Это очень эффективно и скорость работы будет очень отличаться.
  • Широкий функционал: Можно интегрироваться в игровой движок для получения сложных функций, таких как ESP (показ скрытых объектов) или AIMBOT (автоприцеливание).
  • Обход античитов: При правильной реализации такие читы сложнее обнаружить, так как они не зависят от внешних API вроде ReadProcessMemory.
Недостатки:
  • Более сложная разработка, требующая знаний об архитектуре процесса игры и Windows API.
  • Высокий риск краша игры при ошибках в коде чита.
  • Нужен хороший инжектор для загрузки DLL в процесс, что может привлечь внимание античитов.
Ключевые отличия Internal и External читов:
Спойлер: Спойлер 1
1.png

Хочу добавить, что External обнаружить легче из юзермода, пока о читах в режиме драйвера не идет речи!
Но как же тогда загрузить наш Internal чит в адресное пространство процесса игры? В этом нам поможет такая техника как DLL-инъекция( DLL-injection )
При помощи DLL-инъекции мы можем заставить процесс игры загрузить наш чит в свою память. А программа, которая будет это делать называется инжектором.

Практическая часть…
В этом разделе статьи я предлагаю для начала исследовать использование динамических библиотек – DLL и понять их принцип работы. Будем придерживаться концепции от легкого к сложному. В дальнейшем понимание таких вещей облегчит изучение более сложных методов. А затем к концу статьи попробуем реализовать свой простой инжектор и провести первый инжект своего чита в процесс игры! Затем поговорим об других методах инжекции.

DLL (Dynamic Link Library) — это библиотеки, содержащие общие функции и данные, которые могут быть использованы несколькими приложениями одновременно. В отличие от исполняемых файлов EXE, DLL не могут запускать код самостоятельно. Вместо этого эти библиотеки загружаются другими программами для выполнения необходимых операций. Возьмем, к примеру, функцию ReadProcessMemory, которая экспортируется из библиотеки kernel32.dll. Если процесс хочет вызвать эту функцию, он должен сначала загрузить kernel32.dll в свое виртуальное адресное пространство, чтобы получить доступ к этой функции.

Есть и такие DLL, которые загружаются автоматически в каждый процесс, поскольку они содержат функции, без которых стабильная работа процесса невозможна. Например, библиотеки ntdll.dll, kernel32.dll и kernelbase.dll — это те, что обычно загружаются по умолчанию, так как они предоставляют базовые функциональные возможности для работы системы и приложений.

Давайте к примеру посмотрим, какие модули DLL загружены в процесс Steam:
Спойлер: Спойлер 2
2.png


В операционных системах Windows используется концепция системного базового адреса для загрузки некоторых DLL по одинаковому адресу в виртуальном адресном пространстве всех процессов на компьютере. Это позволяет оптимизировать использование памяти и повысить производительность системы. Например, библиотека kernel32.dll может загружаться на один и тот же базовый адрес (например, 0x76c30000) во всех процессах, которые её используют. В этом случае кодовая часть библиотеки будет общей для всех процессов, а данные — индивидуальными, так как каждый процесс будет работать со своими собственными данными в отдельном адресном пространстве. Давайте для примера сравним два процесса имеющие одинаковую разрядность (x86):
Спойлер: Спойлер 3
3.png


Как видим базовые адреса системных модулей kernel32.dll, ntdll.dll совпадает в обоих процессах.
В DLL можно указать функцию точки входа, которая будет выполняться при наступлении определённых событий, например, когда процесс загружает библиотеку. Эта функция позволяет выполнить необходимые действия при работе с DLL, такие как инициализация ресурсов или настройка состояния.

Существует четыре основных момента, когда может быть вызвана функция точки входа:
  • Когда процесс загружает DLL в своё адресное пространство.
  • При создании нового потока в процессе.
  • Когда поток завершает свою работу (нормальный выход).
  • Когда процесс выгружает DLL из своей памяти.
Каждое из этих событий может инициировать выполнение кода, прописанного в точке входа, чтобы корректно обработать работу с библиотекой.

Создание DLL

Давайте теперь создадим свою первую программу в виде DLL. Откройте всеми любимую IDE – Visual Studio, у меня 2022года выпуска. Далее «Создание проекта». Затем в окне выбора шаблонов – «Библиотека динамической компоновки (DLL)» или же ENG: Dynamic-Link Library (DLL). Далее даем название проекту, у меня к примеру xss_dll_inject. Далее у нас создается проект с файлом dllmain.cpp:
Спойлер: Спойлер 4
4.png


С таким содержимым:
Спойлер: Спойлер 5
5.png


Как можете увидеть я дал комментарий к каждому блоку switch для определенных событий, о которых говорилось выше. Это события в которых можно указать функцию точки входа.
А теперь предлагаю написать функцию, которая выводит некое диалоговое окно, при успешной загрузки нашего DLL в память вызывающего процесса.

Готовый фрагмент кода:
Спойлер: Код DLL
C++: Скопировать в буфер обмена
Код:
// dllmain.cpp : Определяет точку входа для приложения DLL.
#include "pch.h"
#include <stdio.h>
#include <Windows.h>

VOID MessageBoxHello() {

    MessageBoxA(NULL, "XSS.is Unseen 0x9", "DLL loaded...", MB_OK);

}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH: { //Когда процесс загружает DLL в своё адресное пространство
        MessageBoxHello();
        break;
    }
    case DLL_THREAD_ATTACH:  //При создании нового потока в процессе.

    case DLL_THREAD_DETACH:  //Когда поток завершает свою работу (нормальный выход).

    case DLL_PROCESS_DETACH: //Когда процесс выгружает DLL из своей памяти.
        break;
    }
    return TRUE;
}

Здесь мы объявляем функцию MessageBoxLoad, и размещаем в ней код, который выводит диалоговое окно с неким содержимым. Затем размещаем вызов этой функции, как точку входа в блоке DLL_PROCESS_ATTACH, и теперь, когда наш DLL успешно будет загружена, будет вызвана эта функция. Теперь скомпилируйте наш проект нажатием F7. После компиляции в папке проекта у нас появится готовый файл DLL:
Спойлер: Спойлер 6
6.png

Загрузка DLL
Теперь предлагаю создать второй проект, в этой программе мы будем загружать наш готовый DLL в память своего процесса. Создайте новый проект, в шаблонах выберите «Пустой проект» , дайте логичное имя и «Создать». Затем кликнув в обозревателе решений по папке «Исходные файлы» ПКМ добавить элемент и файл main.c
Спойлер: Спойлер 7
7.png

Для загрузки других DLL в свой процесс снова пригодится WinAPI, а именно функция LoadLibraryA. Она предназначена для динамической загрузки библиотек DLL в адресное пространство вызывающего процесса, которым в данном случае является текущий процесс. Эта функция принимает в качестве входного параметра путь к DLL-файлу на диске и производит его загрузку. При успешной загрузке вызывается точка входа DLL, что соответствует событию DLL_PROCESS_ATTACH. Таким образом, если в DLL определена функция, например, MessageBoxLoad, она выполнится автоматически, и на экране появится диалоговое окно. Если функция выполнена успешно, возвращаемое значение представляет собой дескриптор модуля. Если функция не срабатывает, возвращаемое значение равно NULL. Чтобы получить расширенную информацию об ошибке, вызовите GetLastError(). Давайте напишем код, который соответствует нашей задаче.

Синтаксис фукнции LoadLibraryA:
C: Скопировать в буфер обмена
Код:
HMODULE LoadLibraryA(
  [in] LPCSTR lpLibFileName
);
Спойлер: Код main.c
C: Скопировать в буфер обмена
Код:
#include <stdio.h>
#include <Windows.h>


int main() {
   
    /* dll_name - массив хранящий путь до нашего DLL */
    char dll_name[] = "C:\\Users\\Unseen\\source\\repos\\xss_dll_inject\\x64\\Debug\\xss_dll_inject.dll";
    printf("[+] Loading dll... \n");
    if (LoadLibraryA(dll_name) == NULL) {
        printf("[-] LoadLibrary Error! Code: %d\n", GetLastError());
        return -1;
    }
   
    printf("[+] Good! DLL loaded!\n");

    printf(" <<< PRESS ENTER - QUIT >>> \n");
    getchar();

    return 0;
}
P.S. Не забудьте вставить путь до вашего DLL в массив dll_name!

Как и ожидалось после выполнения кода, у нас загрузилась DLL в память нашего процесса и выполнилась функция MessageBoxLoad() из нашей DLL:
Спойлер: Спойлер 8
8.png


Но как это поможет нам при загрузке нашего чита в виде DLL в память процесса игры? Ведь функция LoadLibraryA загружает DLL в адресное пространство того процесса, который её вызывает. Однако наша цель — загрузить DLL в удалённый процесс, а не в локальный. Поэтому прямой вызов LoadLibraryA в данном случае невозможен!
А вот тут начинается самая простая техника инъекции. Выше мы обсуждали, что некоторые системные библиотеки DLL по умолчанию загружаются в память любого процесса для их нормальной работы. А сама функция LoadLibraryA экспортируется из библиотеки kernel32.dll, а как мы знаем у таких библиотек базовый системный адрес одинаковый во всех процессах, где они подгружены. Выше на практике мы в этом убедились сами, сравнив два процесса.

Теперь давайте разберёмся, как же реализовать удалённую загрузку DLL в чужой процесс. Эта техника известна как инъекция DLL и является одной из самых популярных методик внедрения кода.

Как работает инъекция DLL?
Суть заключается в следующем: мы передаём путь к нашей DLL в адресное пространство целевого процесса и заставляем этот процесс вызвать функцию LoadLibraryA, чтобы он сам загрузил нашу библиотеку. Для этого потребуется использовать несколько функций WinAPI, а также аккуратно работать с памятью процесса. Еще добавлю то, что процесс, в который вы хотите внедрить DLL, должен совпадать по разрядности с самой DLL. И так же сам инжектор тоже. Это связано с особенностями работы операционной системы и загрузки библиотек. То есть, например для CS2 ваш чит dll, инжектор должны быть 64-битными.

Итак наш план действий следующий:

1)Получить доступ к целевому процессу.

Для работы с памятью и потоками другого процесса нужен дескриптор этого процесса. Мы используем функцию OpenProcess.

2)Выделить память в адресном пространстве целевого процесса.

С помощью функции VirtualAllocEx мы выделим участок памяти, куда запишем путь к нашей DLL.

3)Записать путь к DLL в память целевого процесса.

Это выполняется с помощью нам известной WriteProcessMemory.

4)Вычислить адрес функции LoadLibraryA

При помощи функций WinAPI: GetModuleHandle и GetProcAddress можно узнать адрес функции в модуле.

5)Создать поток внутри целевого процесса.

Мы вызовем функцию CreateRemoteThread, чтобы заставить целевой процесс выполнить функцию LoadLibraryA, передав ей путь к нашей DLL.
Спойлер: Спойлер 9
9.png


Давайте напишем код, поэтапно выполняя все по плану:
Вначале функции main() объявим нужные переменные:
Спойлер: Код: Переменные
C: Скопировать в буфер обмена
Код:
/*
    - Создаем массив в котором хранится путь до нашего DLL
    - Размер массива сохраним в переменную size_dll_name
    - PID целевого процесса сохраним в переменной pid
    - Объявим нужные нам перенные для дальнейших действий в коде:
    ->    HANDLE hProcess         - будет хранить дескриптор удаленного процесса
    ->    LPVOID pRemotePath     - будет хранить адрес памяти куда запишем путь до нашего DLL
    ->    LPVOID pLoadLibraryA - будет хранить адрес функции LoadLibraryA
    ->    HANDLE hThread         - будет хранить дескриптор потока в удаленном процессе


*/
char   dll_name[]     = "C:\\Users\\exbyroot\\source\\repos\\xss_dll_inject\\x64\\Debug\\xss_dll_inject.dll";
int       size_dll_name = sizeof(dll_name);
int    pid             = !УКАЖИТЕ ТУТ PID ПРОЦЕССА;
HANDLE hProcess         = NULL;
LPVOID pRemotePath   = NULL;
LPVOID pLoadLibraryA = NULL;
HANDLE hThread         = NULL;

1)Открываем процесс:

Сначала нужно получить идентификатор целевого процесса (PID). Это можно сделать, например, через диспетчер задач или с помощью кода из прошлых частей. В этой части, чтобы не отходить от самой темы, определим PID процесса вручную через ProcessHacker. Затем получаем дескриптор процесса с помощью OpenProcess.
Спойлер: Код OpenProcess
C: Скопировать в буфер обмена
Код:
printf("[+] OpenProcess... Process Open!\n");
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hProcess == NULL) {
    printf("[-] OpenProcess Error! Code :%d\n", GetLastError());
    return -1;
}

2. Выделяем память:

В адресном пространстве целевого процесса выделяем память для строки с путём к нашей DLL. В этом нам поможет функция VirtualAllocEx. Она принимает дескриптор удаленного процесса, размер буфера который необходимо выделить в памяти, тип распределения и защиту памяти. В нашем случае мы выделяем размер памяти необходимый, чтобы туда вместилось путь до нашего DLL. А так же делаем этот регион памяти доступным на чтение и запись. Функция вернет начальный адрес выделенной памяти.
Спойлер: Код: VirtualAllocEx
C: Скопировать в буфер обмена
Код:
printf(" PRESS ENTER - VirtualAllocEx! \n");
getchar();
pRemotePath = VirtualAllocEx(hProcess, NULL, size_dll_name, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pRemotePath == NULL) {
    printf("[-] VirtualAllocEx Error! Code :%d\n", GetLastError());
    return -2;
}
printf("Memory allocated! Size buffer: %d | Address pRemotePath: 0x%p\n", size_dll_name, pRemotePath);

3. Записываем путь к DLL:

После успешного выделения памяти в адресном пространстве удалённого процесса можно воспользоваться функцией WriteProcessMemory для записи данных в этот выделенный буфер. В нашем случае, туда записывается строка с именем DLL, которая была заранее подготовлена.
Спойлер: Код: WriteProcessMemory
C: Скопировать в буфер обмена
Код:
printf("  PRESS ENTER - WriteProcessMemory! \n");
getchar();
if (!WriteProcessMemory(hProcess, pRemotePath, dll_name, size_dll_name, NULL)) {
    printf("[-] WriteProcessMemory Error! Code :%d\n", GetLastError());
    VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
    return -3;
}
printf("[+] Dll path writed in buffer!\n");

4. Ищем адрес функции LoadLibraryA:

Так как LoadLibraryA экспортируется из kernel32.dll, её адрес можно получить с помощью GetModuleHandle и GetProcAddress. Это работает, потому что адрес функции LoadLibraryA будет таким же в удаленном процессе, как и в локальном процессе.
Спойлер: Код: LoadLibraryA
C: Скопировать в буфер обмена
Код:
pLoadLibraryA = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
if (pLoadLibraryA == NULL) {
    printf("[-] GetProcAddress or GetModuleHandle Error! Code :%d\n", GetLastError());
    return -4;
}
printf("[+] Address LoadLibraryA :0x%p\n", pLoadLibraryA);

5. Создаём поток в удаленном процессе:

После успешной записи пути к DLL в выделенный буфер памяти, следующий шаг — создание нового потока в удалённом процессе с помощью функции CreateRemoteThread.

На этом этапе требуется адрес функции LoadLibraryA, которая будет использоваться для загрузки нашей DLL. Этот адрес передаётся в качестве начальной точки выполнения нового потока.

В качестве аргумента для LoadLibraryA мы укажем адрес выделенного буфера, где записан путь к DLL (переменная pRemotePath). Это достигается передачей pRemotePath в параметр lpParameter функции CreateRemoteThread. Таким образом, удалённый поток выполнит вызов LoadLibraryA, загрузив указанную DLL в адресное пространство целевого процесса.
Спойлер: Код: CreateRemoteThread
C: Скопировать в буфер обмена
Код:
printf(" PRESS ENTER - CreateRemoteThread! \n");
getchar();
hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)pLoadLibraryA, pRemotePath, NULL, NULL);
if (hThread == NULL) {
    printf("[-] CreateRemoteThread Error! Code :%d\n", GetLastError());
    return -5;
}

6. Завершаем работу:

После завершения работы освобождаем ресурсы:
Спойлер: Код: Высвобождение ресурсов
C: Скопировать в буфер обмена
Код:
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
printf(" .... DLL loaded! .... \n");

printf(" PRESS ENTER - QUIT \n");
getchar();

return 0;

Спойлер: Весь исходный код
C: Скопировать в буфер обмена
Код:
#include <stdio.h>
#include <Windows.h>


int main() {
    /*
        - Создаем массив в котором хранится путь до нашего DLL
        - Размер массива сохраним в переменную size_dll_name
        - PID целевого процесса сохраним в переменной pid
        - Объявим нужные нам перенные для дальнейших действий в коде:
        ->    HANDLE hProcess         - будет хранить дескриптор удаленного процесса
        ->    LPVOID pRemotePath     - будет хранить адрес памяти куда запишем путь до нашего DLL
        ->    LPVOID pLoadLibraryA - будет хранить адрес функции LoadLibraryA
        ->    HANDLE hThread         - будет хранить дескриптор потока в удаленном процессе

   
    */
    char   dll_name[]     = "C:\\Users\\exbyroot\\source\\repos\\xss_dll_inject\\x64\\Debug\\xss_dll_inject.dll";
    int       size_dll_name = sizeof(dll_name);
    int    pid             = 6024;
    HANDLE hProcess         = NULL;
    LPVOID pRemotePath   = NULL;
    LPVOID pLoadLibraryA = NULL;
    HANDLE hThread         = NULL;

    printf("[+] OpenProcess... Process Open!\n");
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (hProcess == NULL) {
        printf("[-] OpenProcess Error! Code :%d\n", GetLastError());
        return -1;
    }

    printf(" PRESS ENTER - VirtualAllocEx! \n");
    getchar();
    pRemotePath = VirtualAllocEx(hProcess, NULL, size_dll_name, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (pRemotePath == NULL) {
        printf("[-] VirtualAllocEx Error! Code :%d\n", GetLastError());
        return -2;
    }
    printf("Memory allocated! Size buffer: %d | Address pRemotePath: 0x%p\n", size_dll_name, pRemotePath);
   
    printf("  PRESS ENTER - WriteProcessMemory! \n");
    getchar();
    if (!WriteProcessMemory(hProcess, pRemotePath, dll_name, size_dll_name, NULL)) {
        printf("[-] WriteProcessMemory Error! Code :%d\n", GetLastError());
        VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
        return -3;
    }
    printf("[+] Dll path writed in buffer!\n");


    pLoadLibraryA = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
    if (pLoadLibraryA == NULL) {
        printf("[-] GetProcAddress or GetModuleHandle Error! Code :%d\n", GetLastError());
        return -4;
    }
    printf("[+] Address LoadLibraryA :0x%p\n", pLoadLibraryA);

    printf(" PRESS ENTER - CreateRemoteThread! \n");
    getchar();
    hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)pLoadLibraryA, pRemotePath, NULL, NULL);
    if (hThread == NULL) {
        printf("[-] CreateRemoteThread Error! Code :%d\n", GetLastError());
        return -5;
    }

    WaitForSingleObject(hThread, INFINITE);
    VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
    CloseHandle(hThread);
    CloseHandle(hProcess);
    printf(" .... DLL loaded! .... \n");

    printf(" PRESS ENTER - QUIT \n");
    getchar();

    return 0;
}

Итоги...

Как видим наша DLL успешно загрузилась в память игры и вызвалась функция из DLL MessageBoxLoad();
Спойлер: Спойлер 10
10.png


А теперь, чтобы было еще понятнее взглянем визуально на все эти моменты поэтапно в отладчике x64dbg(Тык) и ProcessHacker(Тык). Итак, после запуска у нас открывается процесс и выделяется в нем память(буффер) с размером равный размеру массива, который содержит путь до DLL. Можем увидеть в x64dbg:
Спойлер: Спойлер 11
11.png


Далее идем в ProcessHacker и продолжаем следить за процессом cs2.exe в разделе потоки. Видим, что у нас создался новый поток с функцией LoadLibraryA:
Спойлер: Спойлер 12
12.png


Далее можем посмотреть раздел модули этого процесса и обнаружить наш xss_dll_inject.dll То есть наша библиотека успешно загрузилась в память:
Спойлер: Спойлер 13
13.png


Таким образом, мы создали простой метод для загрузки DLL в адресное пространство чужого процесса. Но сразу хочу сказать, что античит VAC такие действия детектит. Ибо все наши действия с процессом происходят в открытую. В этой части изначально планировалось рассказать и о других методах инжекта таких как manualmap, но подумал это заняло бы еще столько текста. О других методах будет рассказано далее в следующих частях. Научимся работать с памятью игры как раз из нашего DLL. Вообщем все, что не успели рассмотреть в этой части будет рассказано в следующей части. Если будут вопросы задавайте под постом - постараюсь ответить!

Всех с наступающими праздниками! Ваш Unseen!
 
Сверху Снизу