Пожалуйста, обратите внимание, что пользователь заблокирован
Всем привет! Сегодня мы будем писать распрыжку для кс!
В качестве языка хотел выбрать православный паскаль, но потом подумал что будет лучше на плюсах.
Приступим!
Писать будем для CSS, так как именно в этой версии кс распрыжка приобрела не то чтобы популярность, а стала неотъемлемым атрибутом настоящих тащеров! Если вы конечно помните легенду бхопа Phoon и его мувики! После которых каждый школьник качал себе бхоп и ставил ник Phoon, брал дигл и заходил раскурачивать сервер с аимом!
Чтобы не было несовпадений в билде кс и не накачать троянцев, рекомендую всем купить себе для тестов акк в Steam. Цена вопроса 100 рублей максимум. Я буду ломать именно последний стимовский билд. Так как игра по идее не обновляется, вы сможете все повторить и все будет как на скринах.
Поэтому как тренировочный полигон она идеально подходит.
Обязательно пропишем в параметры запуска следующие параметры:
Код:
-insecure -console
Консоль позволит нам получить доступ к некоторым командам, которые немного упростят нам задачу в ходе отладки.
А режим "insecure" защитит вашу учетную запись от VAC, так как мы будем ковыряться такими инструментами как Cheat Engine и x64dbg.
Когда мы пишем хак, нам нужно понимать что мы вообще от него хотим, и уже в соответствии с этим подобрать нужные "инженерные" решения, если их так можно назвать.
Что такое распрыжка? Она же бхоп.
Это быстрый способ перемещения по карте, при котором вы двигаетесь прыжками, а каждый следующий прыжок выполняется сразу же в момент касания с поверхностью. В основном эта штука популярна и существует только в играх на движке Source и является не багом, а "фичей", которая прочно вошла в геймплей.
Разумеется, все это можно автоматизировать, чтобы не делать руками.
Итого наша задача примерно такова:
1) Найти в игре данные, которые позволят нам определить положение игрока в пространстве (состояние в прыжке / на земле);
2) Выбрать подходящий способ инициации прыжка (эмуляция нажатия / вызов функции);
3) Написать модуль для работы с памятью процесса, а также модуль для сканирования подгружаемых модулей клиента.
Начнем с x64dbg - отладчика.
С OllyDbg не получится, так как она что-то совсем плохо дружит с десяткой.
Качаем с официального сайта.
Запускаем в режиме x32, так как CSS это 32-битное приложение.
Запускаем экземпляр CS, сразу рекомендую поставить оконный - 4:3 и 800x600, чтобы было удобно параллельно работать в отладчиках.
Далее аттачимся к процессу CSS - "hl2.exe".
Прожимаем до талого стрелку которую я выделил (Run), так как необходимо проскочить точки останова без которых игра не запуститься. Если вы попали в меню, значит все хорошо, прожимаем до конца несколько раз.
Затем выбираем в столбце (Module) слева модуль client.dll - выделен цифрой 2.
Все дело в том, что Source Engine имеет модульную структуру.
Каждый модуль отвечает за отдельную область.
В нашем случае нас интересует client.dll - он отвечает за графическую составляющую (рендер), а также предсказания (рандом) и ввод данных.
Кликаем дважды по нему.
В появившемся окне (раздел CPU) прожимаем ПКМ -> Search For -> Current Module -> String References.
Должны увидеть примерно следующее:
Мы выгрузили строки модуля, эта инфа поможет нам найти смещения (оффсеты). Да и в принципе познакомиться с теми или иными функциями и разведать что этот модуль умеет. В условиях, когда Source SDK открыт перелопачивать все вручную смысла нет.
Поэтому сразу подскажу вам для экономии времени (можете изучить Source SDK или поковырять либу самостоятельно, там много интересного того, что не задокументировано в SDK).
Нас интересуют следующие переменные (зачем именно они поймете позже, когда объясню методику):
Код:
m_iHealth;
m_iTeamNum;
m_fFlags;
Нажатием на самые первые пути в открывшемся окне в столбике "Disassembly" (отдельно для каждой из искомых переменных) открываем инспектор, где чуть выше самим переменных мы можем увидеть смещение - самый первый "push".
Например, push 9c у m_iTeamNum;
Запишем найденные значения смещения:
Код:
m_iHealth - push 94
m_fFlags - push 350
m_iTeamNum - push 9c
Больше нам x64dbg не понадобится.
Закрываем! Перезапускаем игру. Запускаем Cheat Engine!
Аттачимся к процессу!
Снова создаем сервер и подключаемся.
Первое что необходимо сделать, найти показатель здоровья.
(Что он несет, зачем нам эти непонятные флаги до этого, а теперь еще и значение здоровья искать?).
Не волнуйтесь, скоро все станет ясно! А пока доверьтесь
Чтобы не прыгать с моста / подрывать себя гранатой - открываем консоль (нажатием на тильду "~") и прописываем команды:
Sass:
sv_cheats 1 // Включить читы
hurtme // нанести себе 10 единиц урона
И жмем Next Scan (Я случайно прожал hurtme пару лишних раз, так как Cheat Engine подвис).
Наш результат не очень радует... Целая пачка адресов, вроде бы все верные, так как при нанесении урона они все правильным образом изменяются. Хм.. А как же найти правильный адрес?
Тут нам на помощь приходит тайное знание.
Если вы обратили внимание, то когда мы искали строку m_iHealth, рядом с ней "по соседству" "лежала" m_iTeamNum.
Что же это значит? Во-первых, m_iTeamNum, как вы могли догадаться - показывает на какой стороне играет пользователь (контры / терры / спектры). И как мы могли понять, точно также как и m_iHealth находится в базовой сущности.
Давайте посмотрим, насколько сильно "далеко" от m_iHealth лежит m_iTeamNum.
Вернемся к нашим смещениям: push 94 и push 9c. Совсем рядышком!
Откроем каждый из предложенных вариантов в Cheat Engine с помощью комбинации Cntrl + B (Browse This Memory Region) и обратим наше внимание на "03" в первой строчке. Как раз находится справа на величину смещения от левого края.
А что же это может быть, хмм?
А это как раз таки здесь расположился m_iTeamNum.
А значит, что именно этот вариант находится в базовой сущности.
Его и добавим в наш список двойным пкмом!
Давайте проверим нашу гипотезу, не ошиблись ли мы?
Попробуем сменить сторону.
Значит, точно то что нам нужно.
Выбираем нашу переменную и жмем ПКМ в Cheat Engine -> Pointer Scan For This Address -> Количество потоков и глубину по 1.
В этом случае этого достаточно.
Остается 5. Какой из них наш?
Внимательно смотрим на значение смещения (Offset).
Если вспомните, то у нас push 94. Поэтому выбираем первый указатель с соответствующим значением.
Прожимаем дважды ЛКМ и заносим в таблицу.
Дважды жмем по нему ЛКМ в таблице и открываем, смотрим на значение: "client.dll"+004C88E8
А теперь запоминаем:
Так как процесс разворачивается в случайном месте в оперативной памяти, то адреса каждый раз разные.
Но относительно модуля "client.dll" базовая сущность всегда находится на одном месте, также как и переменные внутри нее относится фиксированно в одних и тех же местах на величину смещения.
В нашем случае базовой сущностью будет 004C88E8 или 0x4C88E8.
Сохраним отдельно как Base Player.
Код:
PLAYER_BASE = 0x4C88E8;
Согласно документации Source SDK, а также здравому смыслу (если бы у нас SDK не было, то можно было бы предположить).
Что есть некие состояния. В данном случае они известны.
FL_ONGROUND и FL_INJUMP.
На земле и в прыжке.
Довольно легко, но путем большого количества итераций по типу прыжок / сканирование вычисляются (на тот случай, если захотите сделать bhop на игру, где нет открытой инфы и придется рыть самому).
Как именно это сделать мы чуть позже разберем на другом примере.
А пока немного почитерим:
Открываем консоль и прописываем команду: cl_pdump 1
Она выдаст нам всю информацию о различных структурах, сущностях и переменных прямо на экран:
Открываем Cheat Engine, делаем First Scan на 257, а также через Settings -> Hotkeys -> Назначаем горячую клавишу на "Next Scan -> Exact Value". После сканирования на 257, устанавливаем значение 256 в поле ввода, после этого выполняем прыжок и в момент прыжка прожимаем наш хоткей. Итого мы должны получить 5 вариантов:
Обращаем внимание на столбик слева, и смотрим чтобы нужный вариант оказался в адресном пространстве неподалеку с базовой сущностью. Соответственно, первые 4 варианта находятся совсем на других страницах памяти (0C7), а нам нужен вариант со страницы 390 (смотрите Address в Pointerscan Result который мы нашли ранее).
Добавляем нужный вариант в таблицу двойным кликом. Отложим это значение.
А как же нам понять, в какой момент на стороне клиента инициируется прыжок?
Давайте попробуем отследить, куда идет запись в память при нажатии клавиш.
Конечно, можно было воспользоваться отладчиком и посмотреть на уровне инструкций, как пришлось бы делать в более продвинутых играх. Но в нашем случае на движке Source при управлении и вводе все нажатия можно эмулировать через консоль. Для этого пропишем в консоль следующие команды: +jump и -jump. Если вы когда-нибудь играли в CS 1.6 и делали бинды, то вы сразу должны были меня понять. (Вспоминайте, -duck / +duck, даблдакеры!)
Как вы могли заметить, после ввода команды +jump происходит прыжок. Не забываем сбросить состояние командой -jump.
Теперь откроем Cheat Engine и выберем First Scan -> Unknown Initial Value - неизвестное значение.
А также сделаем хоткей на Next Scan - Changed Value, чтобы отследить изменяющиеся значения.
Хмм.. Но ведь так будет менять не только состояние прыжка, но еще и углы обзора и все такое прочее... А что же тогда делать?
Попробуй сделать хоткеи на Increased и Decreased Value, но не простые, а Increased / Decreased By.
Ведь это не углы, а показатель состояния, а значит он может принимать значение Int или boolean, но точно никак не double и не float. По логике вещей, при +jump оно должно расти, а при -jump - падать.
Устанавливаем Increased by 1 на +, а также Decreased by 1 на -.
Понадобится очень много итераций, но в результате мы находим адрес:
Потрясающе! Мы нашли статический адрес!
5 при +jump, и 4 при -jump, так как теперь мы то уже знаем эти значения.
Копируем наш Offset: client.dll+4F5D24 то есть 0x4F5D24.
Это и будет FORCE_JUMP (инициация прыжка).
А теперь пришло время все это закодить!
Создаем проект в Visual Studio. В нашем случае это будет Empty Project (пустой). В настройках не забываем включить многобайтовую кодировку.
Создаем следующие файлы:
Код:
main.cpp
memtool.cpp
memtool.h
В настройках проекта обязательно устанавливаем x86, так как наша игрулька тоже x86. Меняем сразу Debug на Release.
Назову наш проект XSSHOP
В первую очередь создадим основной класс для управления процессами:
C++:
#pragma once
#include <Windows.h> // Подключение библиотеки для работы с API Windows
#include <TlHelp32.h> // Подключение библиотеки для создания снимков процессов и работы с ними
#include <string> // Подключение библиотеки для работы со строками
#include <stdexcept> // Подключение библиотеки для работы с исключениями
// Класс MemTool предоставляет инструменты для работы с процессами и модулями в Windows
class MemTool {
public:
PROCESSENTRY32 gameProcess{}; // Структура для хранения информации о процессе игры
HANDLE processHandle = nullptr; // Дескриптор процесса
HWND gameWindow = nullptr; // Дескриптор окна игры
DWORD clientModuleBase = 0; // Базовый адрес модуля клиента
// Поиск ID процесса по имени
DWORD findProcessIdByName(const std::string& processName);
// Поиск ID главного потока процесса
DWORD findMainThreadId(DWORD processId);
// Получение базового адреса модуля
DWORD getModuleBaseAddress(const std::string& moduleName, DWORD processId);
// Повышение привилегий для доступа к другим процессам
void elevateAccess();
// Инициализация MemTool (поиск процесса игры, открытие процесса и получение базового адреса модуля)
void initialize();
private:
// Создание снимка процессов, потоков или модулей
HANDLE createProcessSnapshot(DWORD flags, DWORD processId = 0);
};
// Экземпляр класса MemTool, доступный в других модулях программы
extern MemTool memToolInstance;
Далее реализуем методы класса для доступа к процессам и модулям:
C++:
#include "memtool.h"
// Экземпляр класса MemTool
MemTool memToolInstance;
// Функция для создания снимка процесса
HANDLE MemTool::createProcessSnapshot(DWORD flags, DWORD processId) {
HANDLE snapshot = CreateToolhelp32Snapshot(flags, processId);
if (snapshot == INVALID_HANDLE_VALUE) {
throw std::runtime_error("Failed to create snapshot");
}
return snapshot;
}
// Функция для поиска ID процесса по имени
DWORD MemTool::findProcessIdByName(const std::string& processName) {
auto snapshot = createProcessSnapshot(TH32CS_SNAPPROCESS); // Создание снимка процессов
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(snapshot, &entry)) { // Итерация по процессам в снимке
do {
if (_stricmp(entry.szExeFile, processName.c_str()) == 0) { // Сравнение имен процессов
CloseHandle(snapshot);
return entry.th32ProcessID; // Возврат ID процесса
}
} while (Process32Next(snapshot, &entry));
}
CloseHandle(snapshot);
return 0; // Возврат 0, если процесс не найден
}
// Функция для поиска ID главного потока процесса
DWORD MemTool::findMainThreadId(DWORD processId) {
auto snapshot = createProcessSnapshot(TH32CS_SNAPTHREAD); // Создание снимка потоков
THREADENTRY32 entry;
entry.dwSize = sizeof(THREADENTRY32);
if (Thread32First(snapshot, &entry)) { // Итерация по потокам в снимке
do {
if (entry.th32OwnerProcessID == processId) { // Проверка соответствия ID процесса
CloseHandle(snapshot);
return entry.th32ThreadID; // Возврат ID потока
}
} while (Thread32Next(snapshot, &entry));
}
CloseHandle(snapshot);
return 0; // Возврат 0, если поток не найден
}
// Функция для получения базового адреса модуля
DWORD MemTool::getModuleBaseAddress(const std::string& moduleName, DWORD processId) {
auto snapshot = createProcessSnapshot(TH32CS_SNAPMODULE, processId); // Создание снимка модулей процесса
MODULEENTRY32 entry;
entry.dwSize = sizeof(MODULEENTRY32);
if (Module32First(snapshot, &entry)) { // Итерация по модулям в снимке
do {
if (_stricmp(entry.szModule, moduleName.c_str()) == 0) { // Сравнение имен модулей
CloseHandle(snapshot);
return reinterpret_cast<DWORD>(entry.modBaseAddr); // Возврат базового адреса модуля
}
} while (Module32Next(snapshot, &entry));
}
CloseHandle(snapshot);
return 0; // Возврат 0, если модуль не найден
}
// Функция для повышения привилегий
void MemTool::elevateAccess() {
HANDLE tokenHandle = nullptr;
TOKEN_PRIVILEGES privileges{};
LUID luid;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &tokenHandle)) { // Открытие токена процесса
if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) { // Получение LUID для привилегии отладки
privileges.PrivilegeCount = 1;
privileges.Privileges[0].Luid = luid;
privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(tokenHandle, FALSE, &privileges, sizeof(privileges), NULL, NULL); // Повышение привилегий
}
CloseHandle(tokenHandle);
}
}
// Функция инициализации MemTool
void MemTool::initialize() {
elevateAccess(); // Повышение привилегий
gameProcess.th32ProcessID = findProcessIdByName("hl2.exe"); // Поиск ID процесса игры
processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, gameProcess.th32ProcessID); // Открытие процесса с полными правами
if (processHandle == nullptr) {
throw std::runtime_error("Failed to open process"); // Ошибка, если не удалось открыть процесс
}
clientModuleBase = getModuleBaseAddress("client.dll", gameProcess.th32ProcessID); // Получение базового адреса модуля клиента
gameWindow = FindWindow(NULL, "Counter-Strike Source"); // Поиск окна игры
}
В основной программе мы создаем объект memToolInstance, инициализируем его и начинаем основной цикл, в котором проверяем состояние клавиши пробела для выполнения прыжка. Если клавиша пробела нажата, программа проверяет, находится ли игрок на земле, и если да, то выполняет прыжок.
C++:
#include <Windows.h> // Подключение библиотеки для работы с API Windows
#include <iostream> // Подключение библиотеки для работы с вводом/выводом в консоль
#include "memtool.h" // Подключение собственного заголовочного файла, где описаны функции для работы с процессами
// Константы, задающие смещения в памяти
constexpr DWORD PLAYER_BASE_OFFSET = 0x4C88E8; // Смещение для адреса базового игрока
constexpr DWORD JUMP_ADDRESS = 0x4F5D24; // Адрес для выполнения прыжка
constexpr DWORD JUMP_OFFSET = 0x350; // Смещение для флагов (состояния игрока)
// Константы, задающие значения, связанные с управлением
constexpr int FL_ONGROUND = 257; // Флаг, обозначающий, что игрок на земле
constexpr int SPACE = 0x20; // Код клавиши пробел
constexpr int DELETE_KEY = 0x2E; // Код клавиши Delete
// Структура для хранения информации о игроке
struct Player {
DWORD localPlayer = 0; // Адрес локального игрока в памяти
int flags = 0; // Флаги, определяющие текущее состояние игрока
// Обновление данных игрока путём чтения памяти процесса игры
void updatePlayerData(HANDLE processHandle, DWORD clientModuleBase) {
// Чтение адреса локального игрока
ReadProcessMemory(processHandle, reinterpret_cast<LPCVOID>(clientModuleBase + PLAYER_BASE_OFFSET), &localPlayer, sizeof(DWORD), nullptr);
// Чтение флагов состояния игрока
ReadProcessMemory(processHandle, reinterpret_cast<LPCVOID>(localPlayer + JUMP_OFFSET), &flags, sizeof(int), nullptr);
}
} player;
// Функция для выполнения прыжка
void bHop(HANDLE processHandle, DWORD clientModuleBase) {
if (GetAsyncKeyState(SPACE) & 0x8000) { // Проверка, нажата ли клавиша пробел
const bool jump = player.flags == FL_ONGROUND; // Проверка, на земле ли игрок
// Запись команды на прыжок в память процесса игры
WriteProcessMemory(processHandle, reinterpret_cast<LPVOID>(clientModuleBase + JUMP_ADDRESS), &jump, sizeof(jump), nullptr);
}
}
int main() {
try {
// Инициализация инструментов работы с процессом (например, открытие процесса игры)
memToolInstance.initialize();
std::cout << "XSSHOP v0.1" << std::endl;
// Основной цикл программы, выполняющийся до нажатия клавиши Delete
while (!(GetAsyncKeyState(DELETE_KEY) & 0x8000)) {
player.updatePlayerData(memToolInstance.processHandle, memToolInstance.clientModuleBase); // Обновление данных игрока
bHop(memToolInstance.processHandle, memToolInstance.clientModuleBase); // Выполнение прыжка при необходимости
}
std::cin.get(); // Ожидание ввода пользователя (чтобы программа не завершалась сразу)
}
catch (const std::exception& e) {
// Обработка исключений и вывод ошибки, если что-то пошло не так
std::cerr << "Error: " << e.what() << std::endl;
}
return 0; // Завершение программы
}
Специально для xss.pro от ZakonUlits.
Вложения
Последнее редактирование: