Как Создавать Читы для CS:GO (1 часть)

L$DK$K
Пользователь
Статус
Оффлайн
Регистрация
14 Дек 2018
Сообщения
279
Реакции[?]
57
Поинты[?]
0
Данное нечто переведено с форума unknowncheats.com (noad)
Ссылка на оригинал:
Пожалуйста, авторизуйтесь для просмотра ссылки.

Тема старая (там последний UPD был в 2018 году)
Не бейте за кривость перевода. + я вообще не шарю в этом лол

Глава 1. Объяснение про CS:GO
Counter Strike: Global Offensive (он же CS:GO), как многие из вас знают, это шутер от первого лица с большим акцентом на соревновательный аспект. Игра требует определенного набора навыков и умений, чтобы убить ваших врагов, и когда дело доходит до мастерства мы все это знаем... пошли читерить! CS:GO-это, наверное, одна из самых (если не самая) полная игра читеров и создатели (Valve + Hidden Path Entertainment) ничего не могут для нее сделать. На протяжении различных обновлений игры они выпустили несколько способов и обходных путей, чтобы попытаться остановить читеров (всегда терпящих неудачу), но в конце концов мы все знаем, что они не остановят читеров по очевидным финансовым причинам. Там почти нет защиты по сравнению с другими платными играми (ну, не поймите меня неправильно), и само сообщество полно читеров.

Но мы здесь не для того, чтобы говорить о том, как полно читеров CS:GO, мы здесь для того, чтобы узнать о том, как сделать чит! Этот форум полон ссылок, учебников, источников о том, как сделать определенные вещи для CS:GO, на самом деле я поставлю в конце каждой главы небольшой список ссылочных ссылок, которые я действительно рекомендую прочитать, чтобы глубже понять концепцию, объясненную в этой главе.

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

Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.


Кратко Про Движок Source
CS:GO работает на движке Source, действительно известном игровом движке, используемом для создания многих игр, которые мы все знаем и любим (яркий пример-Half Life или Left 4 Dead).
Source хорошо известен своей великолепной физической системой (которую мы видели в действии в HL), но в целом это отличный движок для создания игр. Еще одним большим преимуществом является его гибкость, которая значительно облегчает жизнь моддеров.

Она широко документирована в Интернете и здесь, и это (только частично) открытый исходный код. Это позволяет нам получить чертовски много информации о самой игре без необходимости полностью менять ее (не поймите меня неправильно, вам все равно нужно будет менять ее в определенных ситуациях).
Поскольку CS:GO построен на Source Engine, большая часть того, что мы будем делать, чтобы изменить игру (чтобы получить преимущество), связана с ней, особенно когда мы собираемся внутренние

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

Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.


Типы Читов

Untitled.png

Обычно (не только для CS:GO) существует 2 типа читов: и внешние.
Разница между этими двумя типами огромна:

External чит-это EXE-файл, который способен через ДЕСКРИПТОР (думайте, что это как мост) к процессу CS:GO читать и записывать память. Читая и записывая определенные области памяти, мы можем создавать свои читы.
Основным недостатком external чита является то, что мы не синхронизированы с потоком игры, что может привести к "неожиданным" результатам (ярким примером являются внешние скинченджеры, так как они не синхронизированы с потоком игры, вы не достигнете согласованности, особенно при попытке изменить модель ножа).
Мы также вынуждены читать и писать через определенные функции (мы увидим позже), которые трудно вызвать.

Internal чит-это DLL который вводится непосредственно в CS:GO и способен непосредственно читать и записывать память CS:GO через необработанные указатели. Внутренне мы также можем получать интерфейсы и вызывать виртуальные функции, что значительно облегчает нашу жизнь. Мы также можем подключить некоторые функции, чтобы как бы "перенаправить" их и заставить делать то, что мы хотим.

До сих пор существует открытая дискуссия о том, какой тип читов является наиболее палевным.

Существует еще один тип чита, называемый "Internal/External" , который в основном представляет собой DLL, которая вводится в любой 32-битный процесс и оттуда работает как обычный внешний.
VAC, Ring0 и Ring3 (необязательно)
Основной античит, который использует CS:GO, - это Valve Anti-Cheat (он же VAC), который должен блокировать читеров, но мы все знаем, что он с треском проваливается. В принципе, VAC-это шутка. Вам не нужно слишком сильно бояться его, так как он не забанит вас, если вы не используете публичный чит. VAC использует сканирование сигнатур для обнаружения читов. Когда чит обнаруживается VAC, это означает, что VAC обнаружил его подпись (которая является уникальным идентификатором для чего-то) и сохранил её в своей базе данных. Когда вы используете чит с обнаруженной подписью, вы трахаетесь (помечены).
Баны VAC полностью автоматические и могут занять много времени, чтобы запретить вам играть, потому что он должен знать на 100%, что вы обманываете, чтобы избежать ложных срабатываний.

Вы должны знать о Ring0 и Ring3, чтобы получить представление о том, как VAC-это настоящая шутка. В основном, процессоры Intel (x86, но и другие) Для защиты критических ресурсов, таких как IO, память, порты и т. Д., Предоставляют некоторые уровни привилегий (0-большинство привилегий и 3-последнее), а также UserMode и KernelMode.

VAC работает в Ring3. В принципе, если вы сделаете чит Ring0, то все будет хорошо. Но если вы способны сделать обман Ring0, то ты профи в создании читов.

Ссылки на этот раздел:
Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.


Глава 2:External Читы: Отправная Точка.

На данный момент мы сосредоточимся на external читах, которые вы теперь знаете, что они делают (ref: типы читов)
Написание внешних читов служит действительно хорошей отправной точкой в этом секторе, потому что внешние читы гораздо более документированы, чем внутренние, и их гораздо легче кодировать и понимать.
Однако, как только вы действительно хорошо овладеете этим типом чита, вам следует подумать о переключении на внутренний, который (имхо) довольно лучше. И все же вам нужен правильные знания+.

Как я уже объяснял, целью External читов является чтение и запись памяти CS:GO с целью ее редактирования, получения информации и получения огромного преимущества над нашими врагами в игре.
External чит, однако, не вынужден управлять памятью CS:GO. Например, вы можете написать действительно хорошую систему управления отдачей (она же RCS) без необходимости читать или писать что-либо. Тем не менее, наиболее последовательные результаты могут быть достигнуты только при чтении/записи памяти. Например, вы можете сделать скрипт bhop, который спамит пробел без чтения или записи какого-либо сектора памяти, но все равно прыжки не будут последовательными, и результат может быть дерьмовым. С другой стороны, читая память, чтобы точно знать, когда прыгать, вы можете достичь желаемой последовательности.
В конце концов, все дело в последовательности.

Кратко Про WinAPI

Я неоднократно заявлял, что нам нужно управлять памятью CS:GO, чтобы достичь определенных результатов с помощью наших внешних читов. Но как? Ответ получил название: Windows Application Programming Interface! (он же WinAPI)

ОС Windows позволяет нам использовать WinAPI, который (в основном говоря) представляет собой набор функций и структур C, реализованных в Dynamic Link Libraries (aka DLL). Не поймите меня неправильно, вы все еще можете смешивать свой стиль кода C++ с использованием WinAPI.
Есть, как вы, возможно, знаете, три основные группы API: Kernel, Graphics Device Interface (он же GDI) и Пользователь. Теперь, когда мы знаем, что мы будем использовать в основном в наших внешних устройствах, мы можем начать говорить о практическом управлении памятью CS:GO!

Управление Памяти CS:GO: RPM и WPM
Мы будем использовать много вещей от по WinAPI, чтобы получить результаты, которые мы хотим, и точно те функции, которые мы собираемся использовать наиболее актуальные функции для чтения и записи памяти процесса.
Эти две функции ReadProcessMemory and WriteProcessMemory (aka RPM and WPM).
Давайте посмотрим, как мы работаем с этими двумя функциями, начну с чтения.
Опираясь на странице MSDN об / мин, прототип функции:

Код:
BOOL WINAPI ReadProcessMemory(
  _In_  HANDLE  hProcess,
  _In_  LPCVOID lpBaseAddress,
  _Out_ LPVOID  lpBuffer,
  _In_  SIZE_T  nSize,
  _Out_ SIZE_T  *lpNumberOfBytesRead
);
"_In_ HANDLE hProcess"

Первый аргумент, hProcess, является фактическим
Пожалуйста, авторизуйтесь для просмотра ссылки.
процесса, из которого мы должны считывать память. Помните, что я сказал в 1.2? Во внешних чит-кодах мы используем ДЕСКРИПТОР (например, мост) для чтения и записи памяти.

Наиболее часто используемый способ заключается в цикле через весь процесс через
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.
), используя вспомогательную структуру
Пожалуйста, авторизуйтесь для просмотра ссылки.
, а затем в цикле сравнения текущего имени процесса с именем процесса, который мы ищем, если имена совпадают, то мы используем функцию WinAPI
Пожалуйста, авторизуйтесь для просмотра ссылки.
чтобы открыть дескриптор этого процесса. Обратите внимание, что для того, чтобы иметь возможность читать и писать, мы поставим флаг
Пожалуйста, авторизуйтесь для просмотра ссылки.
, чтобы получить полные привилегии.

ПОМНИ! HANDLE всегда должна быть закрыта через
Пожалуйста, авторизуйтесь для просмотра ссылки.
, как только она больше не используется!


Но это, конечно, не единственный путь. Например, если мы используем управляемые языки, такие как C# или VB.NET-есть гораздо более простые способы. Однако я объяснил самый распространенный из них.

Другие аргументы уже хорошо объяснены на странице MSDN, и позже мы увидим, как мы будем использовать второй аргумент с базовыми адресами модулей и смещениями.

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

Оборотов и слов в минуту-это не единственный способ для чтения и записи памяти процесса, это всего лишь наиболее распространенным, так как он хорошо документированы и реализованы уже само по WinAPI. CS:GO не обнаруживает WPM и RPM, так как многие законные процессы используют их постоянно, а античит (как я уже говорил) хочет избежать ложных срабатываний.

Получение информации о модуле CS:GO

Одна из самых полезных вещей, которые мы можем сделать в памяти CS:GO, - это захват информации о конкретном модуле (DLL, например client.dll или engine.dll, но и другие). Когда мы перейдем к смещениям, вы увидите, как важно знать некоторые вещи о различных модулях CS:GO. Но как мы можем ухватить эту полезную информацию?
Как всегда, есть несколько методов, один из которых действительно похож на метод, который я уже объяснял ранее об открытии дескриптора процесса. Единственное отличие заключается в том , что вместо использования
Пожалуйста, авторизуйтесь для просмотра ссылки.
и связанных с ним функций, таких как
Пожалуйста, авторизуйтесь для просмотра ссылки.
и
Пожалуйста, авторизуйтесь для просмотра ссылки.
, вы должны использовать
Пожалуйста, авторизуйтесь для просмотра ссылки.
чтобы хранить информацию, а
Пожалуйста, авторизуйтесь для просмотра ссылки.
/next цикл через модуль list.

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

Теперь, у нас есть информация, что мы хотим, мы можем использовать базовый адрес модуля для доступа к определенным вещам (используя смещение) в этом модуле (как мы видим, например, захват локального игрока, здоровье игрока или местного значения).
Еще одна полезная вещь, которую нужно знать, - это размер модуля, мы увидим его важность, когда поговорим о сканировании паттернов.


Построение класса управления памятью

Теперь, когда вы знаете, что нам нужно сделать, чтобы весело провести время в памяти CS:GO, вы можете сделать еще один шаг вперед и начать кодировать, чтобы сделать все проще. Наша цель-создать класс управления памятью (он же MMC), который позволит нам подключаться к определенному процессу и захватывать модули. Он также будет содержать несколько полезных оберток шаблонов для WPM и RPM, так что все будет проще.

Не, поймите меня правильно, вы на самом деле не нуждаетесь в таком классе, вы можете просто идти дальше и использовать сырые RPM/WPM, или строить функции,не используя класс, но имхо настройки с помощью ООП поможет нам.
В этом руководстве я буду использовать C++ в качестве примера языка, но это не проблема, так как можно сделать по-настоящему же самое (с некоторыми небольшими изменениями :P), используя другие языки, такие как C#.

Так, пойдем дальше, этот класс будет содержать некоторые члены, офк.

Код:
class CMemoryManager
{
private: // Private members: We are going to setup some getters later on!
    HANDLE m_hProcess; // The HANDLE to the process to attach
    DWORD m_dwProcessId; // The Process Id of the process to attach
    std::vector<MODULEENTRY32> m_Modules; // std::vector containing all the modules we grab from the process
Как вы можете видеть, у нас есть m_hProcess, который в основном является ДЕСКРИПТОРОМ процесса для присоединения, m_dwProcessId, который является идентификатором процесса для присоединения, и базовый std::вектор структур MODULEENTRY32, который будет содержать все модули, которые мы берем из присоединенного процесса. Мы используем вектор std::, так что легче отодвинуть новые элементы (модули), не слишком беспокоясь о фиксированных размерах.
Теперь давайте сделаем публичные функции:

C++:
 // Attach to a process based on strProcessName
    // Returns true on success, false on failure
    bool Attach(const std::string& strProcessName)
    {
        // First of all we create a snapshot handle specific for processes
        // (notice the usage of TH32CS_SNAPPROCESS) so we are able to call Process32First/Next
        // Remeber to close it when you don't use it anymore!
        HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
        // Check if the snapshot created is valid
        if (hSnapshot == INVALID_HANDLE_VALUE) return false;
    
        // Create the helper struct that will contain all the infos about the current process
        // while we loop through all the running processes
        PROCESSENTRY32 ProcEntry;
        // Remember to set the dwSize member of ProcEntry to sizeof(PROCESSENTRY32)
        ProcEntry.dwSize = sizeof(PROCESSENTRY32);
    
        // Call Process32First
        if (Process32First(hSnapshot, &ProcEntry))
        {
            // Notice that you have to enable Multi-Byte character set in order
            // to avoid converting everything.
            // strcmp is not the only way to compare 2 strings ofc, work with your imagination
            if (!strcmp(ProcEntry.szExeFile, strProcessName.c_str()))
            {
                // If we are here it means that the process has been found, we can
                // open an handle to it and return it
                // But first of all we have to close the snapshot handle!
                CloseHandle(hSnapshot);
                // Open an handle and set the m_hProcess member using OpenProcess
                // (Notice the usage of PROCESS_ALL_ACCESS flag in order to grant read/write privileges)
                m_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcEntry.th32ProcessID);
                // Store the process id into m_dwProcessId
                m_dwProcessId = ProcEntry.th32ProcessID);
                // Return true meaning success
                return true;
            }
        }
        else
        {
            // If the Process32First call failed, it means that there is no
            // process running in the first place, we can return directly.
            CloseHandle(hSnapshot);
            return false;
        }
    
        // If we are here it means that the Process32First call returned TRUE, but the first process
        // wasn't the process that we were searching for. Now we can loop through the processes
        // using Process32Next
            while (Process32Next(hSnapshot, &ProcEntry))
            {
            // We do the same check we did for Process32First
            if (!strcmp(ProcEntry.szExeFile, strProcessName.c_str()))
            {
                // If we are here it means that the process has been found, we can
                // open an handle to it and set the m_hProcess member using OpenProcess
                // (Notice the usage of PROCESS_ALL_ACCESS flag in order to grant read/write privileges)
                m_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcEntry.th32ProcessID);
                // Store the process id into m_dwProcessId
                m_dwProcessId = ProcEntry.th32ProcessID);
                // Return true meaning success
                return true;
            }
        }
        // Continue loop while the Process32Next call returns TRUE meaning that there are still processes to check
    
        // If we are here it means that the process has not been found or that there are no processes to scan for anymore.
        // We can close the snapshot handle and return false.
        CloseHandle(hSnapshot);
        return false;
    }
Поскольку я использую C++ и WinAPI, я использую точно такой же алгоритм, показанный в предыдущих разделах, с некоторыми небольшими изменениями возвращаемых значений и комментариев.
То же самое можно сделать с функцией захвата нового модуля из процесса:

C++:
// Grabs a module and adds it to m_Modules if found based on strModuleName
    // Returns true on success, false on failure
    bool GrabModule(const std::string& strModuleName)
    {
        // First of all we create a snapshot handle specific for modules
        // (notice the usage of TH32CS_SNAPMODULE) so we are able to call Module32First/Next
        // Remeber to close it when you don't use it anymore!
        HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, m_dwProcessId);
        // Check if the snapshot created is valid
        if (hSnapshot == INVALID_HANDLE_VALUE) return false;
    
        // Create the helper struct that will contain all the infos about the current module
        // while we loop through all the loaded modules
        MODULEENTRY32 ModEntry;
        // Remember to set the dwSize member of ModEntry to sizeof(MODULEENTRY32)
        ModEntry.dwSize = sizeof(MODULEENTRY32);
    
        // Call Module32First
        if (Module32First(hSnapshot, &ModEntry))
        {
            // Notice that you have to enable Multi-Byte character set in order
            // to avoid converting everything.
            // strcmp is not the only way to compare 2 strings ofc, work with your imagination
            if (!strcmp(ModEntry.szModule, strModuleName.c_str()))
            {
                // If we are here it means that the module has been found, we can add the module to the vector
                // But first of all we have to close the snapshot handle!
                CloseHandle(hSnapshot);
                // Add ModEntry to the m_Modules vector
                m_Modules.push_back(ModEntry); // You can add a check here to see if the module is already there ;)
                // Return true meaning success
                return true;
            }
        }
        else
        {
            // If the Process32First call failed, it means that there is no
            // process running in the first place, we can return directly.
            CloseHandle(hSnapshot);
            return false;
        }
    
        // If we are here it means that the Module32First call returned TRUE, but the first module
        // wasn't the module that we were searching for. Now we can loop through the modules
        // using Module32Next
            while (Module32Next(hSnapshot, &ModEntry))
        {
            // We do the same check we did for Module32First
            if (!strcmp(ModEntry.szModule, strModuleName.c_str()))
            {
                // If we are here it means that the module has been found, we can add the module to the vector
                // But first of all we have to close the snapshot handle!
                CloseHandle(hSnapshot);
                // Add ModEntry to the m_Modules vector
                m_Modules.push_back(ModEntry); // You can add a check here to see if the module is already there ;)
                // Return true meaning success
                return true;
            }
        }
        // Continue loop while the Module32Next call returns TRUE meaning that there are still modules to check
    
        // If we are here it means that the module has not been found or that there are no modules to scan for anymore.
        // We can close the snapshot handle and return false.
        CloseHandle(hSnapshot);
        return false;
    }
Как вы можете видеть, это тот же самый алгоритм с некоторыми небольшими изменениями! Опять же, это не единственный способ сделать это, в зависимости от того, что вы используете.

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

C++:
   // Default constructor: won't attach to any process
    CMemoryManager()
    {
        // Init members
        m_hProcess = INVALID_HANDLE_VALUE;
        m_dwProcessId = 0;
        // Just for safety, I clear out the modules vector
        m_Modules.clear();
    }
    // This constructor will attach to a specific process (default CS:GO)
    CMemoryManager(const std::string& strProcessName = "csgo.exe")
    {
        // Init members
        m_hProcess = INVALID_HANDLE_VALUE;
        m_dwProcessId = 0;
        // Just for safety, I clear out the modules vector
        m_Modules.clear();
        // Attach and throw if the function failed so we can manage the fail
        if (!Attach(strProcessName))
            throw;
    }
Теперь мы можем начать писать наши обертки RPM/WPM, я пока буду попроще, но вы можете пойти дальше, добавив такие вещи, как чтение фиксированного размера памяти,возврат указателя на него и так далее... просто используйте свое воображение!

C++:
// RPM/WPM wrappers
  
    // Read a value from memory and put it in Value
    // Returns true on success, false on failure
    template <class T>
    inline bool Read(DWORD dwAddress, T& Value)
    {
        return ReadProcessMemory(m_hProcess, reinterpret_cast<LPVOID>(dwAddress), Value, sizeof(T), NULL) == TRUE;
    }
    // Write a Value in memory
    // Returns true on success, false on failure
    template <class T>
    inline bool Write(DWORD dwAddress, const T& Value)
    {
        return WriteProcessMemory(m_hProcess, reinterpret_cast<LPVOID>(dwAddress), Value, sizeof(T), NULL) == TRUE;
    }
Здесь нет ничего особенного, как вы можете видеть, это просто функции, которые обертывают RPM и WPM с помощью шаблонов.
Наконец, мы можем поставить несколько геттеров, чтобы завершить класс.

C++:
// Getters
HANDLE GetHandle() { return m_hProcess; }
DWORD GetProcId() { return m_dwProcessId; }
std::vector<MODULEENTRY32> GetModules() { return m_Modules; }
Это вообще нуждается в комментариях?


Конец Первой Части
 
Сверху Снизу