D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Всем привет!
Сегодня у меня возникла идея начать писать XSS Game Hacking Core, далее - GHC, - некий нож мультитул в мире читов.
В основе идеи лежит модульная структура, при которой можно легко использовать наработки при взломе любых других игр.
Пока четкой структуры нет, но я вижу это так:
База, обрастающая модулями, которые могут пригодиться в любых играх, такими как: модуль сканирования паттернов, дампер, несколько видов хуков, типичные алгоритмы работы читерской логики по типу ESP, модули отрисовки, кастомный LLVM-обфускатор с блекджеком и дамами
В ходе цикла по геймхакингу мы обкатаем учебные техники, которые не могут идти в продакшен, так как будут палиться, но помогут понять логику работы читов, чтобы перейти на более высокий уровень абстракции. В ходе этих же тестов обкатаем и идею с GHC, который будет удобным средством для развертывания инструмента в нашем "тестовом полигоне".
В сегодняшней статьей мы рассмотрим такую штуку как AutoShot, все на базе той же CS:S из прошлого урока.
Перед началом прочтения статьи обязательно к прочтению все статьи других мемберов форума, где подробно разбираются моменты, которые будут опущены либо разобраны минимально в последующих статьях с целью экономия нашего времени, да и просто разжевывать какие-то детали каждый раз будет слишком накладно и ресурсозатратно.
Спойлер: Поэтому изучаем
[0x1]Легкий Геймхакинг: Шаг за шагом
[0x2]Легкий Геймхакинг: Шаг за шагом
[0x3]Легкий Геймхакинг: Шаг за шагом.
[0x4]Легкий Геймхакинг: Шаг за шагом.
[0x5]Легкий Геймхакинг: Шаг за шагом.
[0x6]Легкий Геймхакинг: Шаг за шагом.
[0x7]Легкий Геймхакинг: Шаг за шагом.
[0x8]Легкий Геймхакинг: Шаг за шагом.
Ломаем игры и зарабатываем как Michael Donnelly ч. 1
Ломаем игры и зарабатываем как Michael Donnelly ч. 2
Ломаем игры и зарабатываем как Michael Donnelly ч. 3
Чит своими руками. Обходим простой античит и исследуем игру на Unity
Античит Valve (VAC) — процесс развития и методы борьбы
Распрыжка на плюсах для чайников
Начинаем!
Во-первых, запускаем экземпляр КС и Cheat Engine.
Напоминаю, что я использую Steam версию! В NoSteam оффсеты будут отличаться!
Аттачимся, самостоятельно находим m_iHealth и Player_Base.
Спойлер: m_iHealth и Player_Base
Можете (так и нужно сделать) найти указатели, сохранить их в таблицу, которую будете использовать при дальнейших запусках, чтобы не тратить время.
Код: Скопировать в буфер обмена
Не забываем, что выбираем тот вариант при поиске m_iHealth, который имеет рядом лежащий m_iTeamNum, как это понять см. в прошлом уроке.
Что нам нужно реализовать?
AutoShot - функция в читах, которая производит автоматический выстрел по противнику.
Первое, что приходит в голову:
Алгоритм работы должен быть таков, что при наведении на противника (центр перекрестия совпадает с моделью противника) происходит выстрел.
Но происходит он не сам по себе, мы его в этот момент инициируем.
Какие у нас есть пути для этого?
1) Кажется самым простым (но, возможно, медленным) - эмуляция нажатия клавиши мыши.
2) Запись значения в память переменной, отвечающей за состояние ведения огня, во всех играх есть что-то типо "OnFire".
3) Перехват функции, которая работает с этим состоянием и вызов ее в нужный момент времени (способ "на хуках").
Мы будем использовать второй способ, так как он самый простой и идеально подходит для нашей образовательной демонстрации. Но позже также разберем и способы на хуках, и эмуляцию, а также преимущества и недостатки каждого из них.
Формируем задачу:
1) Найти:
- способ инициации выстрела
- способ который позволит нам понять, когда "Enemy In Cross".
2) Определить:
- Действительно ли перед нами Enemy? Вспоминаем что у нас есть m_iTeamNum.
3) Запустить поток выполнения, цикл которого будет непрерывно проверять эти состоянии и в случае чего инициировать огонь.
Давайте приступим:
После нахождения указателя к m_iHealth (pointerscan result), попробуем изучить структуру данных.
Для этого выбираем первый из 4 найденных указателей (выглядит он как на скриншоте ниже, проверьте, что вы работаете именно с ним).
Он не должен отличаться у вас, если вы установили Steam-версию игры.
Спойлер: 1 pointer
Выбираем его ПКМ и открываем в памяти с помощью "Browse This Memory Region".
Спойлер: После нажатия должно получится так:
После этого открываем "Tools" и выбираем "Dissect Data Structures".
Спойлер: Откроется следующее окно:
Объявим новую структуру, нажав на "Structures" -> "Define Data Structure".
Параметры при выборе не трогаем, оставляем без изменений.
Спойлер: Наша структура данных:
Что мы здесь видим?
Если вы посмотрите на начало - то мы находимся как раз у m_iHealth.
Проверим прописав "hurtme" в консоль.
Спойлер: Все верно!
Все верно, значение изменилось, что же дальше?
Как помните из прошлого урока, то спектаторы это 1, террористы это 2, а CT это 3.
Поменяем команду и проверим, изменится ли значение m_iTeamNum и есть ли оно в этой структуре?
Спойлер: Нашли m_iTeamNum
Как видите слева в поле 0008 значение поменялось с 2 -> 3.
Мы нашли структуру которая хранит информацию о нашем локальном игроке.
А как же найти информацию о других игроках?
Возьмем наш следующий по списку указатель ("client.dll"+004D5AE4)
Открываем структуру по полученному адресу и видим следующую картину:
Спойлер: Entity_List?
Развернем оба указателя и увидим, что каждый из них содержит информацию о здоровье, а также принадлежность к команде (и все остальные неизвестные нам атрибуты).
А значит, что мы наткнулись на Entity_List (на сервере должен быть хотя бы 1 бот чтобы проверить).
Чтобы подтвердить или опровергнуть правильность ваших действий, можете нанести себе/боту урон, чтобы отразились изменения в памяти.
Спойлер: HP и Team Number
Если вы обратите внимание, то указатели находятся друг от друга на расстоянии 0x10, что будет являться глубиной при работе с ними в цикле, т.е. когда мы будем по кругу сканировать всех игроков на сервере.
Назовем это глубиной сканирования:
Код: Скопировать в буфер обмена
То есть наши "враги" в памяти в 10 байтах друг от друга.
Что нам нужно еще?
Теперь нам нужно что-то, что является InCross или его аналогом.
Но когда вы наводите перекрестием на союзника, то на экране отображается его имя.
Может начать копать в эту сторону!
Давайте открым CE, встанем рядом с нашим ботом (союзником).
Пропишите для этого sv_cheats 1 и bot_stop 1.
Если нужно добавить бота, пропишите bot_add_ct.
Автобаланс в командах должен предварительно быть выключен в настройках сервера, чтобы его не перекинуло за T-сторону.
Что дальше? Сканируем на значение "0" (Exact), при этом смотри в сторону/в пол от бота, чтобы не триггерить состояние.
Наводим мышкой на нашего бота, затем сканируем на Next Scan с помощью Between Value между 1 и 10 (знаем, что значение выше 0, но не факт, что это единица, весь состояний может быть много, так как Entity это не обязательно игрок, это может быть бегающая по карте курица, или катающаяся Бочка, в общем-то любой объект имеющий физику/прочность/какие-то иные показатели).
Затем снова наводим на игрока, и снова сканируем Exact.
Проводим несколько итераций до тех пор, пока количество значений не будет допустимо удобным для ручного анализа.
У меня получилось так:
Спойлер: InCross
Если мы уведем прицел в сторону, то получим значение "0".
Я предполагаю, что искомое значение в данном случае самое последнее. Почему?
Потому что оно лежать в Entity List, а значит находится примерно в том же адресном пространстве.
Смотрим на начало адреса, последнее начинается на 3...
Остальные явно лежат в каких-то других страницах памяти, и не смотря на то что они могут принимать верное значение состояния, отвечать они могут за совсем иные элементы, хотя также могут относиться к InCross. Например, могут быть связаны с визуальным отображением имени/пр. функциями, что относится уже к HUD и другим элементам, которые нам не интересны, так как мы работаем с клиентом! (client.dll).
Добавим его в наш список двойным кликом.
Сразу не отходя от него вычислим смещение.
Открываем колькулятор и отнимаем наш условный InCross от EntityList (отнимаем между собой адреса!)
Спойлер: InCross Address - EntityList Addres
У меня получилось 14F0.
Адреса у вас будут другие, так как каждый раз процесс/модули будут грузиться в случайные страницы RAM! (Random Acces Memory, не забываем!)
Но оффсет получится такой же, так как внутри модуля все относительно на тех же самых местах.
Не забываем также указать для Entity List отображение HEX через ПКМ, иначе у вас будет не адрес, а значение (числовое), и вы не сможете ничего отнять либо получите непредвиденный результат.
Код: Скопировать в буфер обмена
Теперь мы знаем:
- Шаг между игроками в памяти;
- Где найти состояние InCross и чему оно равно при наведении;
- Указатель на Entity List
- Смещение для iTeamNum, с помощью которого мы можем выполнить проверку на команду (чтобы не было Friendly Fire, либо сделать дополнительную настройку, например, для игры на DM-серверах, где мы будет отключать эту проверку) (Сделать это можно только вручную (смену режима), так как в CSS официально нет режима DM).
Но как же нам выполнять наш цикл?
Сколько игроков нам считать?
Например, сейчас у нас два игрока.
Но потенциально их может быть и 32, то есть столько, сколько вместит сервер.
А что, если какие-то из них мертвы, или отключились от сервера?
В таком случае нам нужно узнать еще и сколько игроков прямо сейчас, что называется, isAlive.
Как это сделать?
Давайте посмотрим, где может храниться это значение.
Нужно нам это для того, чтобы не выполнять огромное количество пустых операций по пустым ячейкам памяти.
Ведь за это время может измениться и состояние InCross, и тогда наш AutoShoot просто не будет работать как следует, либо будет работать с задержками/нестабильно.
Например, сейчас я нахожусь на сервере с 1 ботом.
Выполняем First Scan на сканирование кол-ва игроков (2 в моем случае)
Затем убираем мусор с помощью Unchaned Value -> добавляем еще бота с помощью bot_add.
Проверяем значение Exact -> 3 -> bot_add -> 4.
Спойлер: Поиск значения игроков
У меня получилось так. Перезапустив сервер и добавив больше игроков, я получил следующие значения, при этом
некоторые из них на -1 отличаются от реального, а какие-то на +2, но тем не менее при добавлении/кике бота, они синхронно увеличиваются/уменьшаются на +/- 1.
Что бы это могло значить и где найти верное решение?
Спойлер: Хммм
Обращаем внимание, что у нас есть 4 статичных адреса адреса, это хорошо, так как не нужно будет искать указатели.
Делаем рестарт сервера, перезаходим. Было 9 ботов + 1 я. Все значения показывали "10". После добавления еще одного бота - снова аномалия.
Спойлер: Результат после перезахода.
Нам подойдут второй и пятый варианты, так как они отражают реальное число.
Перезапустим игру, посмотрим, какие из адресов останутся "живыми".
Магия! Остался только - 1:
Спойлер: server.dll + Player Numbers
Так что Cheat Engine тоже может иногда на@бывать нас со статичными адресами.
Оставляем его как PlayerNum.
А как же теперь нам инициировать выстрел, ведь все остальное у нас уже есть.
Вспоминаем, как в прошлый раз эмулировали прыжок через консоль (с помощью +/-jump).
Также имитируем и атаку (+/-attack). Откуда я это взял?
Это дефолтные бинды в любой CS, будь то 1.6/CSS/CSGO/CS2, просмотреть весь перечень команд вы можете в открытом SDK или в конфигах игры, где биндятся настройки управления.
Переходим к сканированию.
Запустим пустой сервер.
Берем любую пушку и открываем консоль:
Выполняем поиск First Scan - Unknown Initial Value (сканируем всю память).
Далее - Unchanged (Next Scan) - т.к. мы не делали выстрел, а значит состояние ForceFire не поменялось.
+Attack в консоль -> Changed Value
-Attack -> снова сканируем Changed.
Несколько раз Unchanged чтобы отсортировать фоновый мусор.
Далее я решил немного "считерить", так как результатов было слишком много.
Добавлял Increased/Decreased на +/-, так как предполагал, что значение становится больше/меньше соответственно, а также на последних этапах добавил к ним "by 1", чтобы еще быстрее отфильтровать.
Итераций вышло примерно столько:
Спойлер: Консоль с итерациями.
В CE мы получаем такую картину:
Спойлер: Адреса
Затем делаем +/-Attack и смотрим, как меняются значения.
К счастью, единственно правильным адресом стал последний.
Состояние False = 4, состояние ForceAttack(true) = 5.
Запишем +4F5D30 как FORCE_ATTACK.
Заметим, что у нас также добавилась новая библиотека (модуль) - server.dll.
Поэтому их также придется подключить к проекту.
Приступим к написанию собственно софта!
Получил от вас такой фидбэк по прошлому уроку:
Поэтому постараюсь алгоритмическую сторону разобрать подробнее.
Но все таки необходимы хотя бы базовые знания C++ (хотя бы посмотрите C++ для чайников за 30 минут на ютубе).
Сначала познакомлю вас с общим кодом, а затем разберемся с отдельными функциями и принципами работы.
В ходе написания статьи меня ударил перфекционист, поэтому специально для вас вылизал код в соответствии с Source SDK, а также избавился от server.dll и ограничился только engine.dll и client.dll.
Спойлер: main.cpp
C++: Скопировать в буфер обмена
Спойлер: GameProcessManager.h
C++: Скопировать в буфер обмена
Спойлер: GameProcessManager
C++: Скопировать в буфер обмена
В первую очередь рассмотрим GameProcessManager.cpp:
Это основной файл, с которого хак начинает работу.
Он делает "слепки" процессов и потоков, чтобы найти их ID, чтобы дальше найти базовые адреса модулей.
Для чего это делается? Все потому, что каждый раз ваша программа запускается в случайном участке RAM, а значит необходимо находить эти адресы, чтобы в дальнейшем ими оперировать и высчитывать нужные адреса с помощью оффсетов.
Метод initialize запускает весь процесс -> открытие процесса -> получение базовых адресов модулей и нахождение окна игры.
GameProcessManager.h: этот файл — заголовочный файл класса GameProcessManager. Он объявляет функции для работы с процессами и модулями, а также он включает функцию для повышения привилегий и инициализации объекта
А теперь подробнее пробежимся по главному алгоритму! main.cpp
За исключением модуля бхопа, так как его мы разобрали в прошлом уроке, а здесь он достался хаку в наследство =)
Программа выполняет следующие шаги:
Инициализация: в main вызывается initialize из gameProcessManagerInstance, что открывает процесс игры, находит модули и окно игры.
Основной цикл: запускается цикл, который продолжается до тех пор, пока не будет нажата клавиша "Delete".
Обновление информации о игроке: внутри цикла вызывается метод update структуры PlayerInfo, чтобы получить данные о текущем игроке.
Обновление информации о сущностях: Для каждой из 32 сущностей вызывается метод update структуры EntityInfo, чтобы получить базовый адрес сущности и её команду.
autoShoot:
Тут без 100 грамм не разобрать
Прикладываю блок-схему:
C++: Скопировать в буфер обмена
Данную версию XSS GHC можно использовать в любых играх (но только в учебных целях, так как метод WPM небезопасен!)
Достаточно поменять название окна, модулей.
Добавить оффсеты.
Типичные функции работы с памятью здесь есть, такие как чтение и запись.
Принцип везде один.
Надеюсь, что вам понравилось!
Специально для XSS.is от ZakonUlits!
Сегодня у меня возникла идея начать писать XSS Game Hacking Core, далее - GHC, - некий нож мультитул в мире читов.
В основе идеи лежит модульная структура, при которой можно легко использовать наработки при взломе любых других игр.
Пока четкой структуры нет, но я вижу это так:
База, обрастающая модулями, которые могут пригодиться в любых играх, такими как: модуль сканирования паттернов, дампер, несколько видов хуков, типичные алгоритмы работы читерской логики по типу ESP, модули отрисовки, кастомный LLVM-обфускатор с блекджеком и дамами
В ходе цикла по геймхакингу мы обкатаем учебные техники, которые не могут идти в продакшен, так как будут палиться, но помогут понять логику работы читов, чтобы перейти на более высокий уровень абстракции. В ходе этих же тестов обкатаем и идею с GHC, который будет удобным средством для развертывания инструмента в нашем "тестовом полигоне".
В сегодняшней статьей мы рассмотрим такую штуку как AutoShot, все на базе той же CS:S из прошлого урока.
Перед началом прочтения статьи обязательно к прочтению все статьи других мемберов форума, где подробно разбираются моменты, которые будут опущены либо разобраны минимально в последующих статьях с целью экономия нашего времени, да и просто разжевывать какие-то детали каждый раз будет слишком накладно и ресурсозатратно.
Спойлер: Поэтому изучаем
[0x1]Легкий Геймхакинг: Шаг за шагом
[0x2]Легкий Геймхакинг: Шаг за шагом
[0x3]Легкий Геймхакинг: Шаг за шагом.
[0x4]Легкий Геймхакинг: Шаг за шагом.
[0x5]Легкий Геймхакинг: Шаг за шагом.
[0x6]Легкий Геймхакинг: Шаг за шагом.
[0x7]Легкий Геймхакинг: Шаг за шагом.
[0x8]Легкий Геймхакинг: Шаг за шагом.
Ломаем игры и зарабатываем как Michael Donnelly ч. 1
Ломаем игры и зарабатываем как Michael Donnelly ч. 2
Ломаем игры и зарабатываем как Michael Donnelly ч. 3
Чит своими руками. Обходим простой античит и исследуем игру на Unity
Античит Valve (VAC) — процесс развития и методы борьбы
Распрыжка на плюсах для чайников
Начинаем!
Во-первых, запускаем экземпляр КС и Cheat Engine.
Напоминаю, что я использую Steam версию! В NoSteam оффсеты будут отличаться!
Аттачимся, самостоятельно находим m_iHealth и Player_Base.
Спойлер: m_iHealth и Player_Base
Можете (так и нужно сделать) найти указатели, сохранить их в таблицу, которую будете использовать при дальнейших запусках, чтобы не тратить время.
Код: Скопировать в буфер обмена
Код:
Player_Base = "client.dll" + 0x4C88E8 // Offset 0x4C88E8
m_iHealth = Player_Base + 0x94 // Offset 0x94
Не забываем, что выбираем тот вариант при поиске m_iHealth, который имеет рядом лежащий m_iTeamNum, как это понять см. в прошлом уроке.
Что нам нужно реализовать?
AutoShot - функция в читах, которая производит автоматический выстрел по противнику.
Первое, что приходит в голову:
Алгоритм работы должен быть таков, что при наведении на противника (центр перекрестия совпадает с моделью противника) происходит выстрел.
Но происходит он не сам по себе, мы его в этот момент инициируем.
Какие у нас есть пути для этого?
1) Кажется самым простым (но, возможно, медленным) - эмуляция нажатия клавиши мыши.
2) Запись значения в память переменной, отвечающей за состояние ведения огня, во всех играх есть что-то типо "OnFire".
3) Перехват функции, которая работает с этим состоянием и вызов ее в нужный момент времени (способ "на хуках").
Мы будем использовать второй способ, так как он самый простой и идеально подходит для нашей образовательной демонстрации. Но позже также разберем и способы на хуках, и эмуляцию, а также преимущества и недостатки каждого из них.
Формируем задачу:
1) Найти:
- способ инициации выстрела
- способ который позволит нам понять, когда "Enemy In Cross".
2) Определить:
- Действительно ли перед нами Enemy? Вспоминаем что у нас есть m_iTeamNum.
3) Запустить поток выполнения, цикл которого будет непрерывно проверять эти состоянии и в случае чего инициировать огонь.
Давайте приступим:
После нахождения указателя к m_iHealth (pointerscan result), попробуем изучить структуру данных.
Для этого выбираем первый из 4 найденных указателей (выглядит он как на скриншоте ниже, проверьте, что вы работаете именно с ним).
Он не должен отличаться у вас, если вы установили Steam-версию игры.
Спойлер: 1 pointer
Выбираем его ПКМ и открываем в памяти с помощью "Browse This Memory Region".
Спойлер: После нажатия должно получится так:
После этого открываем "Tools" и выбираем "Dissect Data Structures".
Спойлер: Откроется следующее окно:
Объявим новую структуру, нажав на "Structures" -> "Define Data Structure".
Параметры при выборе не трогаем, оставляем без изменений.
Спойлер: Наша структура данных:
Что мы здесь видим?
Если вы посмотрите на начало - то мы находимся как раз у m_iHealth.
Проверим прописав "hurtme" в консоль.
Спойлер: Все верно!
Все верно, значение изменилось, что же дальше?
Как помните из прошлого урока, то спектаторы это 1, террористы это 2, а CT это 3.
Поменяем команду и проверим, изменится ли значение m_iTeamNum и есть ли оно в этой структуре?
Спойлер: Нашли m_iTeamNum
Как видите слева в поле 0008 значение поменялось с 2 -> 3.
Мы нашли структуру которая хранит информацию о нашем локальном игроке.
А как же найти информацию о других игроках?
Возьмем наш следующий по списку указатель ("client.dll"+004D5AE4)
Открываем структуру по полученному адресу и видим следующую картину:
Спойлер: Entity_List?
Развернем оба указателя и увидим, что каждый из них содержит информацию о здоровье, а также принадлежность к команде (и все остальные неизвестные нам атрибуты).
А значит, что мы наткнулись на Entity_List (на сервере должен быть хотя бы 1 бот чтобы проверить).
Чтобы подтвердить или опровергнуть правильность ваших действий, можете нанести себе/боту урон, чтобы отразились изменения в памяти.
Спойлер: HP и Team Number
Если вы обратите внимание, то указатели находятся друг от друга на расстоянии 0x10, что будет являться глубиной при работе с ними в цикле, т.е. когда мы будем по кругу сканировать всех игроков на сервере.
Назовем это глубиной сканирования:
Код: Скопировать в буфер обмена
EntityScanDeep = 0x10;
То есть наши "враги" в памяти в 10 байтах друг от друга.
Что нам нужно еще?
Теперь нам нужно что-то, что является InCross или его аналогом.
Но когда вы наводите перекрестием на союзника, то на экране отображается его имя.
Может начать копать в эту сторону!
Давайте открым CE, встанем рядом с нашим ботом (союзником).
Пропишите для этого sv_cheats 1 и bot_stop 1.
Если нужно добавить бота, пропишите bot_add_ct.
Автобаланс в командах должен предварительно быть выключен в настройках сервера, чтобы его не перекинуло за T-сторону.
Что дальше? Сканируем на значение "0" (Exact), при этом смотри в сторону/в пол от бота, чтобы не триггерить состояние.
Наводим мышкой на нашего бота, затем сканируем на Next Scan с помощью Between Value между 1 и 10 (знаем, что значение выше 0, но не факт, что это единица, весь состояний может быть много, так как Entity это не обязательно игрок, это может быть бегающая по карте курица, или катающаяся Бочка, в общем-то любой объект имеющий физику/прочность/какие-то иные показатели).
Затем снова наводим на игрока, и снова сканируем Exact.
Проводим несколько итераций до тех пор, пока количество значений не будет допустимо удобным для ручного анализа.
У меня получилось так:
Спойлер: InCross
Если мы уведем прицел в сторону, то получим значение "0".
Я предполагаю, что искомое значение в данном случае самое последнее. Почему?
Потому что оно лежать в Entity List, а значит находится примерно в том же адресном пространстве.
Смотрим на начало адреса, последнее начинается на 3...
Остальные явно лежат в каких-то других страницах памяти, и не смотря на то что они могут принимать верное значение состояния, отвечать они могут за совсем иные элементы, хотя также могут относиться к InCross. Например, могут быть связаны с визуальным отображением имени/пр. функциями, что относится уже к HUD и другим элементам, которые нам не интересны, так как мы работаем с клиентом! (client.dll).
Добавим его в наш список двойным кликом.
Сразу не отходя от него вычислим смещение.
Открываем колькулятор и отнимаем наш условный InCross от EntityList (отнимаем между собой адреса!)
Спойлер: InCross Address - EntityList Addres
У меня получилось 14F0.
Адреса у вас будут другие, так как каждый раз процесс/модули будут грузиться в случайные страницы RAM! (Random Acces Memory, не забываем!)
Но оффсет получится такой же, так как внутри модуля все относительно на тех же самых местах.
Не забываем также указать для Entity List отображение HEX через ПКМ, иначе у вас будет не адрес, а значение (числовое), и вы не сможете ничего отнять либо получите непредвиденный результат.
Код: Скопировать в буфер обмена
InCrossOffset = 0x14F0;
Теперь мы знаем:
- Шаг между игроками в памяти;
- Где найти состояние InCross и чему оно равно при наведении;
- Указатель на Entity List
- Смещение для iTeamNum, с помощью которого мы можем выполнить проверку на команду (чтобы не было Friendly Fire, либо сделать дополнительную настройку, например, для игры на DM-серверах, где мы будет отключать эту проверку) (Сделать это можно только вручную (смену режима), так как в CSS официально нет режима DM).
Но как же нам выполнять наш цикл?
Сколько игроков нам считать?
Например, сейчас у нас два игрока.
Но потенциально их может быть и 32, то есть столько, сколько вместит сервер.
А что, если какие-то из них мертвы, или отключились от сервера?
В таком случае нам нужно узнать еще и сколько игроков прямо сейчас, что называется, isAlive.
Как это сделать?
Давайте посмотрим, где может храниться это значение.
Нужно нам это для того, чтобы не выполнять огромное количество пустых операций по пустым ячейкам памяти.
Ведь за это время может измениться и состояние InCross, и тогда наш AutoShoot просто не будет работать как следует, либо будет работать с задержками/нестабильно.
Например, сейчас я нахожусь на сервере с 1 ботом.
Выполняем First Scan на сканирование кол-ва игроков (2 в моем случае)
Затем убираем мусор с помощью Unchaned Value -> добавляем еще бота с помощью bot_add.
Проверяем значение Exact -> 3 -> bot_add -> 4.
Спойлер: Поиск значения игроков
У меня получилось так. Перезапустив сервер и добавив больше игроков, я получил следующие значения, при этом
некоторые из них на -1 отличаются от реального, а какие-то на +2, но тем не менее при добавлении/кике бота, они синхронно увеличиваются/уменьшаются на +/- 1.
Что бы это могло значить и где найти верное решение?
Спойлер: Хммм
Обращаем внимание, что у нас есть 4 статичных адреса адреса, это хорошо, так как не нужно будет искать указатели.
Делаем рестарт сервера, перезаходим. Было 9 ботов + 1 я. Все значения показывали "10". После добавления еще одного бота - снова аномалия.
Спойлер: Результат после перезахода.
Нам подойдут второй и пятый варианты, так как они отражают реальное число.
Перезапустим игру, посмотрим, какие из адресов останутся "живыми".
Магия! Остался только - 1:
Спойлер: server.dll + Player Numbers
Так что Cheat Engine тоже может иногда на@бывать нас со статичными адресами.
Оставляем его как PlayerNum.
А как же теперь нам инициировать выстрел, ведь все остальное у нас уже есть.
Вспоминаем, как в прошлый раз эмулировали прыжок через консоль (с помощью +/-jump).
Также имитируем и атаку (+/-attack). Откуда я это взял?
Это дефолтные бинды в любой CS, будь то 1.6/CSS/CSGO/CS2, просмотреть весь перечень команд вы можете в открытом SDK или в конфигах игры, где биндятся настройки управления.
Переходим к сканированию.
Запустим пустой сервер.
Берем любую пушку и открываем консоль:
Выполняем поиск First Scan - Unknown Initial Value (сканируем всю память).
Далее - Unchanged (Next Scan) - т.к. мы не делали выстрел, а значит состояние ForceFire не поменялось.
+Attack в консоль -> Changed Value
-Attack -> снова сканируем Changed.
Несколько раз Unchanged чтобы отсортировать фоновый мусор.
Далее я решил немного "считерить", так как результатов было слишком много.
Добавлял Increased/Decreased на +/-, так как предполагал, что значение становится больше/меньше соответственно, а также на последних этапах добавил к ним "by 1", чтобы еще быстрее отфильтровать.
Итераций вышло примерно столько:
Спойлер: Консоль с итерациями.
В CE мы получаем такую картину:
Спойлер: Адреса
Затем делаем +/-Attack и смотрим, как меняются значения.
К счастью, единственно правильным адресом стал последний.
Состояние False = 4, состояние ForceAttack(true) = 5.
Запишем +4F5D30 как FORCE_ATTACK.
Заметим, что у нас также добавилась новая библиотека (модуль) - server.dll.
Поэтому их также придется подключить к проекту.
Приступим к написанию собственно софта!
Получил от вас такой фидбэк по прошлому уроку:
Поэтому постараюсь алгоритмическую сторону разобрать подробнее.
Но все таки необходимы хотя бы базовые знания C++ (хотя бы посмотрите C++ для чайников за 30 минут на ютубе).
Сначала познакомлю вас с общим кодом, а затем разберемся с отдельными функциями и принципами работы.
В ходе написания статьи меня ударил перфекционист, поэтому специально для вас вылизал код в соответствии с Source SDK, а также избавился от server.dll и ограничился только engine.dll и client.dll.
Спойлер: main.cpp
C++: Скопировать в буфер обмена
Код:
#include <Windows.h>
#include <iostream>
#include "GameProcessManager.h"
// Константы триггер-бота
constexpr DWORD m_dwLocalPlayer = 0x4C88E8;
constexpr DWORD m_dwForceAttack = 0x4F5D30;
constexpr DWORD m_iTeamNum = 0x9C;
constexpr DWORD m_iCrosshairId = 0x14F0;
constexpr DWORD m_dwEntityList = 0x4D5AE4;
constexpr DWORD m_dwEntityDistance = 0x10;
constexpr int attackOn = 5; // В игре +Attack
constexpr int attackOff = 4; // В игре -Attack
constexpr int FL_ONGROUND = 257;
constexpr int KEY_SPACE = 0x20;
constexpr int KEY_DELETE = 0x2E;
constexpr DWORD m_dwForceJump = 0x4F5D24; // Адрес для выполнения прыжка
constexpr DWORD m_fFlags = 0x350; // Смещение для проверки флага
bool isShooting = false;
struct PlayerInfo {
DWORD playerBaseAddress = 0;
int playerTeam = 0;
int targetEntityId = 0;
int flags = 0;
void update(HANDLE processHandle, DWORD clientModuleBase) {
ReadProcessMemory(processHandle, reinterpret_cast<LPCVOID>(clientModuleBase + m_dwLocalPlayer), &playerBaseAddress, sizeof(DWORD), nullptr);
ReadProcessMemory(processHandle, reinterpret_cast<LPCVOID>(playerBaseAddress + m_iTeamNum), &playerTeam, sizeof(int), nullptr);
ReadProcessMemory(processHandle, reinterpret_cast<LPCVOID>(playerBaseAddress + m_iCrosshairId), &targetEntityId, sizeof(int), nullptr);
ReadProcessMemory(processHandle, reinterpret_cast<LPCVOID>(playerBaseAddress + m_fFlags), &flags, sizeof(int), nullptr);
}
} player;
struct EntityInfo {
DWORD entityBaseAddress = 0;
int entityTeam = 0;
void update(HANDLE processHandle, DWORD clientModuleBase, int index) {
ReadProcessMemory(processHandle, reinterpret_cast<LPCVOID>(clientModuleBase + m_dwEntityList + (index * m_dwEntityDistance)), &entityBaseAddress, sizeof(DWORD), nullptr);
ReadProcessMemory(processHandle, reinterpret_cast<LPCVOID>(entityBaseAddress + m_iTeamNum), &entityTeam, sizeof(int), nullptr);
}
} entities[32];
void bHop(HANDLE processHandle, DWORD clientModuleBase) {
if (GetAsyncKeyState(KEY_SPACE) & 0x8000) {
const bool jump = player.flags == FL_ONGROUND;
WriteProcessMemory(processHandle, reinterpret_cast<LPVOID>(clientModuleBase + m_dwForceJump), &jump, sizeof(jump), nullptr);
}
}
void autoShoot(HANDLE processHandle, DWORD clientModuleBase) {
if (!isShooting) {
WriteProcessMemory(processHandle, reinterpret_cast<LPVOID>(clientModuleBase + m_dwForceAttack), &attackOff, sizeof(int), nullptr);
isShooting = !isShooting;
}
if (player.targetEntityId == 0)
return;
if (entities[player.targetEntityId - 1].entityTeam == player.playerTeam)
return;
if (player.targetEntityId >= 32)
return;
if (isShooting) {
WriteProcessMemory(processHandle, reinterpret_cast<LPVOID>(clientModuleBase + m_dwForceAttack), &attackOn, sizeof(int), nullptr);
isShooting = !isShooting;
}
}
int main() {
try {
gameProcessManagerInstance.initialize();
std::cout << "gameProcessManagerInstance Initialized" << std::endl;
while (!(GetAsyncKeyState(KEY_DELETE) & 0x8000)) {
player.update(gameProcessManagerInstance.handle, gameProcessManagerInstance.clientModuleBaseAddress);
for (int i = 0; i < 32; i++) {
entities[i].update(gameProcessManagerInstance.handle, gameProcessManagerInstance.clientModuleBaseAddress, i);
}
autoShoot(gameProcessManagerInstance.handle, gameProcessManagerInstance.clientModuleBaseAddress);
bHop(gameProcessManagerInstance.handle, gameProcessManagerInstance.clientModuleBaseAddress);
}
std::cin.get(); // Ожидание ввода пользователя (чтобы программа не завершалась сразу)
}
catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
C++: Скопировать в буфер обмена
Код:
#pragma once
#include <Windows.h>
#include <TlHelp32.h>
#include <string>
#include <stdexcept>
class GameProcessManager {
public:
PROCESSENTRY32 gameProcess{};
HANDLE handle = nullptr;
HWND windowHandle = nullptr;
DWORD clientModuleBaseAddress = 0;
DWORD engineModuleBaseAddress = 0;
DWORD getProcessIdByName(const std::wstring& processName);
DWORD getMainThreadId(DWORD processId);
DWORD getModuleBaseAddress(const std::wstring& moduleName, DWORD processId);
void elevatePrivileges();
void initialize();
private:
HANDLE createSnapshot(DWORD flags, DWORD processId = 0);
};
// Экземпляр класса
extern GameProcessManager gameProcessManagerInstance;
C++: Скопировать в буфер обмена
Код:
#include "GameProcessManager.h"
#include <iostream>
// Экземпляр класса
GameProcessManager gameProcessManagerInstance;
// Функция для создания снимка процесса
HANDLE GameProcessManager::createSnapshot(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 GameProcessManager::getProcessIdByName(const std::wstring& processName) {
auto snapshot = createSnapshot(TH32CS_SNAPPROCESS);
PROCESSENTRY32 entry{ sizeof(PROCESSENTRY32) };
if (Process32First(snapshot, &entry)) {
do {
if (_wcsicmp(entry.szExeFile, processName.c_str()) == 0) {
CloseHandle(snapshot);
return entry.th32ProcessID;
}
} while (Process32Next(snapshot, &entry));
}
CloseHandle(snapshot);
return 0;
}
// Функция для поиска ID главного потока процесса
DWORD GameProcessManager::getMainThreadId(DWORD processId) {
auto snapshot = createSnapshot(TH32CS_SNAPTHREAD);
THREADENTRY32 entry{ sizeof(THREADENTRY32) };
if (Thread32First(snapshot, &entry)) {
do {
if (entry.th32OwnerProcessID == processId) {
CloseHandle(snapshot);
return entry.th32ThreadID;
}
} while (Thread32Next(snapshot, &entry));
}
CloseHandle(snapshot);
return 0;
}
// Функция для получения базового адреса модуля
DWORD GameProcessManager::getModuleBaseAddress(const std::wstring& moduleName, DWORD processId) {
auto snapshot = createSnapshot(TH32CS_SNAPMODULE, processId);
MODULEENTRY32 entry{ sizeof(MODULEENTRY32) };
if (Module32First(snapshot, &entry)) {
do {
if (_wcsicmp(entry.szModule, moduleName.c_str()) == 0) {
CloseHandle(snapshot);
return reinterpret_cast<DWORD>(entry.modBaseAddr);
}
} while (Module32Next(snapshot, &entry));
}
CloseHandle(snapshot);
return 0;
}
// Функция для повышения привилегий
void GameProcessManager::elevatePrivileges() {
HANDLE tokenHandle = nullptr;
TOKEN_PRIVILEGES privileges{};
LUID luid;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &tokenHandle)) {
if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &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);
}
}
// Функция инициализации GameProcessManager
void GameProcessManager::initialize() {
elevatePrivileges();
gameProcess.th32ProcessID = getProcessIdByName(L"hl2.exe");
if (gameProcess.th32ProcessID == 0) {
throw std::runtime_error("Failed to find game process.");
}
handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, gameProcess.th32ProcessID);
if (handle == nullptr) {
throw std::runtime_error("Failed to open process.");
}
clientModuleBaseAddress = getModuleBaseAddress(L"client.dll", gameProcess.th32ProcessID);
engineModuleBaseAddress = getModuleBaseAddress(L"engine.dll", gameProcess.th32ProcessID);
if (clientModuleBaseAddress == 0 || engineModuleBaseAddress == 0) {
throw std::runtime_error("Failed to get client or engine module base address.");
}
windowHandle = FindWindowW(nullptr, L"Counter-Strike Source");
if (windowHandle == nullptr) {
throw std::runtime_error("Failed to find game window.");
}
}
В первую очередь рассмотрим GameProcessManager.cpp:
Это основной файл, с которого хак начинает работу.
Он делает "слепки" процессов и потоков, чтобы найти их ID, чтобы дальше найти базовые адреса модулей.
Для чего это делается? Все потому, что каждый раз ваша программа запускается в случайном участке RAM, а значит необходимо находить эти адресы, чтобы в дальнейшем ими оперировать и высчитывать нужные адреса с помощью оффсетов.
Метод initialize запускает весь процесс -> открытие процесса -> получение базовых адресов модулей и нахождение окна игры.
GameProcessManager.h: этот файл — заголовочный файл класса GameProcessManager. Он объявляет функции для работы с процессами и модулями, а также он включает функцию для повышения привилегий и инициализации объекта
А теперь подробнее пробежимся по главному алгоритму! main.cpp
За исключением модуля бхопа, так как его мы разобрали в прошлом уроке, а здесь он достался хаку в наследство =)
Программа выполняет следующие шаги:
Инициализация: в main вызывается initialize из gameProcessManagerInstance, что открывает процесс игры, находит модули и окно игры.
Основной цикл: запускается цикл, который продолжается до тех пор, пока не будет нажата клавиша "Delete".
Обновление информации о игроке: внутри цикла вызывается метод update структуры PlayerInfo, чтобы получить данные о текущем игроке.
Обновление информации о сущностях: Для каждой из 32 сущностей вызывается метод update структуры EntityInfo, чтобы получить базовый адрес сущности и её команду.
autoShoot:
Тут без 100 грамм не разобрать
Прикладываю блок-схему:
C++: Скопировать в буфер обмена
Код:
+-----------------------------+
| Начало функции |
+-----------------------------+
|
v
+-----------------------------+
| Проверка if (!isShooting) |
| Выполнить: |
| WriteProcessMemory |
| (выключить стрельбу) |
| Установить isShooting = true|
+-----------------------------+
|
v
+-----------------------------+
| Проверка if (targetEntityId == 0) |
| Если цель отсутствует, |
| выйти из функции. |
+-----------------------------+
|
v
+-----------------------------+
| Проверка if (entityTeam == playerTeam) |
| Если цель в той же команде, |
| выйти из функции. |
+-----------------------------+
|
v
+-----------------------------+
| Проверка if (targetEntityId >= 32) |
| Если индекс цели вне допустимого диапазона, |
| выйти из функции. |
+-----------------------------+
|
v
+-----------------------------+
| Проверка if (isShooting) |
| Выполнить: |
| WriteProcessMemory |
| (включить стрельбу) |
| Установить isShooting = false |
+-----------------------------+
|
v
+-----------------------------+
| Конец функции |
+-----------------------------+
Данную версию XSS GHC можно использовать в любых играх (но только в учебных целях, так как метод WPM небезопасен!)
Достаточно поменять название окна, модулей.
Добавить оффсеты.
Типичные функции работы с памятью здесь есть, такие как чтение и запись.
Принцип везде один.
Надеюсь, что вам понравилось!
Специально для XSS.is от ZakonUlits!