Гайд Простой чит-ревилер для вашего чита

anonymous
Участник
Статус
Онлайн
Регистрация
18 Окт 2022
Сообщения
607
Реакции[?]
216
Поинты[?]
144K
всем спасибо за упоминания. очень тронут, мой дефицит внимания ликует :pogchamp:

алсо это не моя паста, но скорее всего и не сальватора
просто дело в том что топовых хвх кодеров в снг не так много и поэтому я осмелился выдвинуть такое предположение, основываясь над началом твоего мастерства в написании шеллкодов год назад:
Пожалуйста, авторизуйтесь для просмотра ссылки.
. И да людям науки хвх это интересно, без лишней озабочености конечно же, так во всём впринципе, в музыке в кино)
 
40, 40, 40 blackout XD
Read Only
Статус
Оффлайн
Регистрация
15 Янв 2020
Сообщения
454
Реакции[?]
228
Поинты[?]
24K
просто дело в том что топовых хвх кодеров в снг не так много и поэтому я осмелился выдвинуть такое предположение, основываясь над началом твоего мастерства в написании шеллкодов год назад:
Пожалуйста, авторизуйтесь для просмотра ссылки.
. И да людям науки хвх это интересно, без лишней озабочености конечно же, так во всём впринципе, в музыке в кино)
ты хорошо подумала?
 
ППХУДЕР
Начинающий
Статус
Оффлайн
Регистрация
10 Фев 2020
Сообщения
417
Реакции[?]
23
Поинты[?]
6K
Перед началом хочу выразить огромную благодарность Satoru за помощь с отображением иконок читов в табе т.к я думал что без затрагивания panoramы и js это невозможно.

В данной статье будет использована информация с репозитория
Пожалуйста, авторизуйтесь для просмотра ссылки.
(noad)

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

- Любая сетевая игра работает за счёт постоянного обмена данными между клиентом и сервером. В нашем же случае с ксго, игрок(клиент) создаёт новый "голосовой" пакет данных (экземпляр класса CCLCMsg_VoiceData и CSVCMsg_VoiceData), то есть конструирует и заполняет массив данных и в конечном итоге отправляет на сервер (CNetChan::SendNetMsg) другим игрокам. Читы делают это вручную, зачастую используя готовые функции движка игры, формируя свой "голосовой пакет" который ничем не отличается от нативного игрового кроме как будучи заполненным кастомными данными чита. Нам же нужно получить эти данные, расшифровать и сравнить нужные переменные для нашего чит ревилера.

- В добавку к этому всему, не забываем, что у каждого игрока есть разные идентификаторы и в данном случае нам понадобится идентификатор xuid_low (который так же присутствует в классах CCLCMsg_VoiceData и CSVCMsg_VoiceData), но для чего он нужен, мы узнаем немного позже.

Перейдём к написанию самого чит ревилера:

- Как любой нормальный кодер мы сразу сталкиваемся с логическими "проблемами" на которые нам нужно придумать решения, то как мы будем хранить полученные данные, как часто вызывать функции и как сделать так чтобы всё это дело было оптимизировано!

1) Хранение данных:
В моём конкретном случае, на момент написания ревилера, я использовал готовую базу чит опая (airflow) и в моём распоряжении имелась структура esp_player_t и класс c_esp, которые глобально использовались в проекте, поэтому, к счастью, мне не пришлось придумывать велосипед.

Проблема хранения решена.

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

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

Пишем хук на эту функцию, для получения "голосовых" данных:
Код:
bool __fastcall msg_voice_data(void* ecx, void* edx, c_svc_msg_voice_data* message)
{
    // получаем оригинал функции
    static auto original = hooker::get_original(&msg_voice_data);
 
    // исключаем локального игрока т.к нам нужны данные соперников ну или тиммейтов
    if ( !HACKS->local || HACKS->local->index() == message->client + 1)
        return original(ecx, edx, message);

    // перехватываем нужные нам данные для ревилера
    CHEAT_REVEALER->handle_voice(message);

    // мы получили что хотели,
    // возвращаем оригинал функции чтобы не ломать работоспособность игры
    return original(ecx, edx, message);
}
Данные получены.
Переходим к их сравнению:

Начнём со скита.

(немного коспирологии)
Глянув на код Сальватора, сразу становится понятно что он действительно один из лучших луа кодеров и бывший топовый юзер скита, становится сразу же понятно кто внёс свою лепту в его кряк, однако я был удивлён откуда у него такие знания чтобы дампить и реверсить методы скита. Смотрим в репу и видим что какой то "загадочный персонаж"(дог3д1ваюсь кто) написал ему
Пожалуйста, авторизуйтесь для просмотра ссылки.
который нам очень пригодится чтобы "детектить" юзеров скита!

Создаём заголовки
cheat_revealer.hpp:
static unsigned char skeet_shellcode[] =
{
    0x81, 0xEC, 0x4C, 0x01, 0x00, 0x00, 0x53, 0x55, 0x56, 0x8B, 0xF1, 0x89, 0x54, 0x24, 0x54, 0x33,
    0xDB, 0xC7, 0x44, 0x24, 0x10, 0x68, 0x33, 0x05, 0x97, 0x57, 0xC7, 0x44, 0x24, 0x18, 0x36, 0x06,
    0xD4, 0xEA, 0xBF, 0x00, 0x01, 0x00, 0x00, 0x8B, 0x46, 0x10, 0x8B, 0x4E, 0x14, 0x89, 0x44, 0x24,
    0x30, 0x8B, 0x46, 0x28, 0x89, 0x44, 0x24, 0x38, 0x8B, 0x46, 0x24, 0x89, 0x44, 0x24, 0x3C, 0x8B,
    0x46, 0x2C, 0x89, 0x44, 0x24, 0x40, 0x8B, 0xC3, 0xC7, 0x44, 0x24, 0x1C, 0x4F, 0xC4, 0xA4, 0x3E,
    0xC7, 0x44, 0x24, 0x20, 0x85, 0xB2, 0xAC, 0x0F, 0x89, 0x4C, 0x24, 0x34, 0x89, 0x5C, 0x24, 0x28,
    0x88, 0x44, 0x04, 0x5C, 0x40, 0x3B, 0xC7, 0x72, 0xF7, 0x8A, 0xF3, 0x8B, 0xF3, 0x8A, 0x54, 0x34,
    0x5C, 0x8B, 0xC6, 0x83, 0xE0, 0x0F, 0x8A, 0x44, 0x04, 0x14, 0x02, 0xC2, 0x02, 0xF0, 0x0F, 0xB6,
    0xCE, 0x8A, 0x44, 0x0C, 0x5C, 0x88, 0x44, 0x34, 0x5C, 0x46, 0x88, 0x54, 0x0C, 0x5C, 0x3B, 0xF7,
    0x72, 0xDB, 0x8A, 0xE3, 0x8B, 0xFB, 0xBD, 0x80, 0x00, 0x00, 0x00, 0x8A, 0xF4, 0xFE, 0xC6, 0x0F,
    0xB6, 0xF6, 0x8A, 0x54, 0x34, 0x5C, 0x02, 0xE2, 0x0F, 0xB6, 0xCC, 0x8A, 0x44, 0x0C, 0x5C, 0x88,
    0x44, 0x34, 0x5C, 0x88, 0x54, 0x0C, 0x5C, 0x83, 0xED, 0x01, 0x75, 0xE1, 0xFE, 0xC6, 0x0F, 0xB6,
    0xF6, 0x8A, 0x54, 0x34, 0x5C, 0x8A, 0xDA, 0x02, 0xDC, 0x0F, 0xB6, 0xCB, 0x8A, 0x44, 0x0C, 0x5C,
    0x88, 0x44, 0x34, 0x5C, 0x88, 0x54, 0x0C, 0x5C, 0x8A, 0x44, 0x34, 0x5C, 0x02, 0xC2, 0x0F, 0xB6,
    0xC0, 0x8A, 0x44, 0x04, 0x5C, 0x8A, 0xE3, 0x30, 0x44, 0x3C, 0x30, 0x47, 0x83, 0xFF, 0x14, 0x72,
    0xCB, 0x33, 0xFF, 0x89, 0x7C, 0x24, 0x2C, 0x8B, 0xEF, 0xC7, 0x44, 0x24, 0x24, 0x0F, 0x00, 0x00,
    0x00, 0x8B, 0x7C, 0x24, 0x24, 0xD1, 0xED, 0x89, 0x6C, 0x24, 0x48, 0x0F, 0xB7, 0x4C, 0xAC, 0x32,
    0x8B, 0xC1, 0x0F, 0xBF, 0xC9, 0x89, 0x44, 0x24, 0x54, 0x0F, 0xB7, 0x44, 0xAC, 0x34, 0xBD, 0x85,
    0x8E, 0xD5, 0x91, 0x8B, 0xD0, 0x89, 0x4C, 0x24, 0x44, 0x8B, 0xD8, 0x89, 0x54, 0x24, 0x4C, 0x8B,
    0xC1, 0x0F, 0xB7, 0xF0, 0x2B, 0xDD, 0x24, 0x0F, 0x8B, 0xD5, 0x8A, 0xC8, 0xD1, 0xC2, 0x66, 0xD3,
    0xCB, 0x8B, 0xEA, 0x66, 0x8B, 0xC3, 0xD1, 0xC5, 0x66, 0x33, 0xC6, 0x2B, 0xF2, 0x0F, 0xB7, 0xD8,
    0x8A, 0xCB, 0x80, 0xE1, 0x0F, 0x66, 0xD3, 0xCE, 0x66, 0x33, 0xF0, 0x0F, 0xB7, 0xCE, 0x0F, 0xB7,
    0xC6, 0x83, 0xEF, 0x01, 0x75, 0xCB, 0x8B, 0x7C, 0x24, 0x28, 0x8B, 0xC5, 0x2B, 0xD8, 0x89, 0x6C,
    0x24, 0x24, 0x33, 0x5C, 0x24, 0x50, 0x83, 0xC7, 0x02, 0x8B, 0x6C, 0x24, 0x48, 0xD1, 0xC0, 0x2B,
    0xC8, 0x89, 0x7C, 0x24, 0x28, 0x8B, 0x44, 0x24, 0x4C, 0x33, 0x4C, 0x24, 0x2C, 0x0F, 0xB7, 0xC0,
    0x89, 0x44, 0x24, 0x50, 0x8B, 0x44, 0x24, 0x54, 0x0F, 0xB7, 0xC0, 0x66, 0x89, 0x5C, 0xAC, 0x34,
    0x66, 0x89, 0x4C, 0xAC, 0x32, 0x89, 0x44, 0x24, 0x2C, 0x83, 0xFF, 0x09, 0x0F, 0x82, 0x45, 0xFF,
    0xFF, 0xFF, 0x8B, 0x44, 0x24, 0x30, 0x8B, 0x4C, 0x24, 0x58, 0xC1, 0xF8, 0x10, 0xC1, 0xF9, 0x10,
    0x33, 0xC1, 0xB9, 0x24, 0x24, 0x00, 0x00, 0x5F, 0x5E, 0x66, 0x3B, 0xC1, 0x5D, 0x0F, 0x94, 0xC0,
    0x5B, 0x81, 0xC4, 0x4C, 0x01, 0x00, 0x00, 0xC3
};

typedef bool(__fastcall* skeet_shellcode_t)(void*, uint32_t);

class c_cheat_revealer
{
private:
    bool is_using_gamesense(c_svc_msg_voice_data* msg, uint32_t xuid_low);
    bool is_using_fatality(uint16_t pct);
    bool is_using_evolve(uint16_t pct);
    bool is_using_onetap(uint16_t pct);
    bool is_using_pandora(uint16_t pct);
public:
    void handle_voice(c_svc_msg_voice_data* msg);
    void update_tab();
};
Создаём вспомогательные функции для сравнения полученных нами данных для будущего чит ревилера.

Краткий обзор функций благодаря которым и функционирует наш чит-ревилер:
Функции имеют тип возвращаемого значения - boolean, то-есть если нужный нам чит был найден то возвращаем true!


Рассмотрим первую функцию для обнаружения юзеров скита:
cheat_revealer.cpp:
bool c_cheat_revealer::is_using_gamesense(c_svc_msg_voice_data* msg, uint32_t xuid_low)
{
    // Declare static variables
    static unsigned char* shellcode = nullptr;
    static skeet_shellcode_t is_using_skeet_fn = nullptr;

    // Allocate memory only once
    if (!shellcode)
    {
        shellcode = reinterpret_cast<unsigned char*>(
            VirtualAlloc(nullptr, sizeof(skeet_shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE)
            );

        // Copy the shellcode to the allocated memory
        memcpy(shellcode, skeet_shellcode, sizeof(skeet_shellcode));

        // Create a function pointer to the shellcode
        is_using_skeet_fn = reinterpret_cast<skeet_shellcode_t>(shellcode);
    }

    // Call the shellcode
    bool result = is_using_skeet_fn(msg, xuid_low);

    // Note: We are not freeing the memory to keep it for future calls?

    return result;
}
Функция имеет в себе код аллоцирования памяти для загадочного шеллкода, как работает шеллкод я не знаю, но в дальнейшем поинтер на эту память вызывается с параметрами c_svc_msg_voice_data* msg (голосовая дата полученная клиентом от сервера) и uint32_t xuid_low (идентификатор игрока). В результате шеллкод возвращает тип boolean, видимо сравнивая эти два параметра с какими то данными скита внутри себя.

Остальные примеры обнаружения других читов - более простые, суть заключается в сравнении той инфы которую читы формируют и отправляют на сервер , в этой инфе (голосовые пакеты описанные выше) имеется идентификатор xuid_low и соответственно у каждого чита он разный, чтобы не дампить самому эти значения, методом поиска луашек на чит ревилер, были найдены hex значения которые юзаются для обнаружения различных читов (сравнивая переменную xuid_low из полученной инфы в SVCMsg_VoiceData хуке)

cheat_revealer.cpp:
bool c_cheat_revealer::is_using_fatality(uint16_t pct)
{
    if (pct == 0x7FFA || pct == 0x7FFB)
        return true;

    return false;
}

bool c_cheat_revealer::is_using_evolve(uint16_t pct)
{
    if (pct == 0x7FFC || pct == 0x7FFD)
        return true;

    return false;
}

bool c_cheat_revealer::is_using_onetap(uint16_t pct)
{
    if (pct == 0x57FA)
        return true;

    return false;
}

bool c_cheat_revealer::is_using_pandora(uint16_t pct)
{
    if (pct == 0x695B || pct == 0x1B39)
        return true;

    return false;
}
Собираем всё это в кучу.

Основная функция ревилера которую я написал для работы с полученной инфой из SVCMsg_VoiceData хука:
cheat_revealer.cpp:
void c_cheat_revealer::handle_voice(c_svc_msg_voice_data* msg)
{
    // исключаем использование при отжатии эксплоитов чтобы обезопасить работоспобность функции
    if (EXPLOITS->recharge.start)
        return;

    // проверяем наличие данных
    if (msg->format != 0)
        return;

    // исключаем локального игрока
    if (!HACKS->local || HACKS->engine->get_local_player() == msg->client + 1)
        return;

    // проверяем наличие данных
    if (msg->section_number == 0 && msg->sequence_bytes == 0 && msg->uncompressed_sample_offset == 0)
        return;

    // Здесь я сталкнулся с небольшой проблемой:
    // Проблема выглядела так: при частом аллоцировании и очищении памяти для шеллкода сальватора, у меня происходил краш процесса.
    // Таким способом я решил уменьшить частоту использования этого кода чтобы давать памяти время подумать.
    // Решение вышло неправильным и не сработало т.к читы в разное время отправляют те самые "голосовые" пакеты и можно легко словить рассинхрон и прозевать момент их получения клиентом и тем самым остаться с голой жопой.

    // Ограничения использования этого кода (момент неудачной оптимизации)
    //ULONGLONG time = GetTickCount64();
    //const bool should_receive = time - CHEAT_REVEALER->last_receive_ack_time >= 500; // miliseconds

    // проверяем что идентификатор игрока находится в рабочем диапазоне
    int sender_id = msg->client + 1;
    if (sender_id >= 0 && sender_id <= 63)
    {
        // создаем временный пустой массив данных
        player_info_t player_info{};

        // заполняем временно созданный массив информацией о игроке
        if (HACKS->engine->get_player_info(sender_id, &player_info))
        {
            // получаем поинтер который хранит в себе всякую информацию которую чит использует в ESP и не только..
            auto esp_info_sender = ESP->get_esp_player(sender_id);

            // здесь я придумал нехитрое решение проблемы оптимизации
            // проверку на идентификатор, чтобы постоянно не обновлять одного и того же игрока!
            // если единтификатор у игрока поменялся - проверим какой чит он использует.
            if (esp_info_sender && esp_info_sender->revealer.m_xuid_low != player_info.xuid_low)
            {
                // создаём массив и получаем "голосовой" пакет данных
                c_voice_communication_data voice_data = msg->get_data();

                // проверяем игрока на различные читы
                const auto using_skeet = is_using_gamesense(msg, player_info.xuid_low);
                const auto using_fatality = is_using_fatality(static_cast<uint16_t>(msg->xuid_low));
                const auto using_evolve = is_using_evolve(static_cast<uint16_t>(msg->xuid_low));
                const auto using_onetap = is_using_onetap(static_cast<uint16_t>(msg->xuid_low));
                const auto using_pandora = is_using_pandora(static_cast<uint16_t>(msg->xuid_low));

                // switch case would be better?
                if (using_skeet)
                {
                    esp_info_sender->revealer.update(CHEAT_GS, player_info.xuid_low);
#ifdef _DEBUG
                    printf("receiving | name: %s | xuid_low %d, found skeet user!\n", player_info.name, player_info.xuid_low);
#endif
                }
                else if (using_fatality)
                {
                    esp_info_sender->revealer.update(CHEAT_FT, player_info.xuid_low);
#ifdef _DEBUG
                    printf("receiving | name: %s | xuid_low %d, found fatality user!\n", player_info.name, player_info.xuid_low);
#endif
                }
                else if (using_evolve)
                {
                    esp_info_sender->revealer.update(CHEAT_EVOLVE, player_info.xuid_low);
#ifdef _DEBUG
                    printf("receiving | name: %s | xuid_low %d, found ev0lve user!\n", player_info.name, player_info.xuid_low);
#endif
                }
                else if (using_onetap)
                {
                    esp_info_sender->revealer.update(CHEAT_ONETAP, player_info.xuid_low);
#ifdef _DEBUG
                    printf("receiving | name: %s | xuid_low %d, found ONETAP user!\n", player_info.name, player_info.xuid_low);
#endif
                }
                else if (using_pandora)
                {
                    esp_info_sender->revealer.update(CHEAT_PANDORA, player_info.xuid_low);
#ifdef _DEBUG
                    printf("receiving | name: %s | xuid_low %d, found PANDORA user!\n", player_info.name, player_info.xuid_low);
#endif
                }
                else
                {
                    // https://lua.neverlose.cc/documentation/events#voice_message
                    // неверлуз и ещё несколько других читов(примо, спирт, никс) используют более мудрённую систему передачи/чтения данных между своими юзерами:
                    // понять что к чему можно посмотрев в их апи, и увидеть что там используются protobuf'ы с которыми у меня нет опыта работы поэтому нл я задетектить не смог!
                    // но начав дампить непонятные мне данные из голосового пакета, нашел частое hex число, дурацкая рулетка вообщем.
                    if ((uint8_t)msg->voice_data == (uint8_t)0x0B282838)
                    {
                        esp_info_sender->revealer.update(CHEAT_NL, player_info.xuid_low);            
#ifdef _DEBUG
                        const auto ctx = (c_cs_player*)HACKS->entity_list->get_client_entity(sender_id);
                        EVENT_LOGS->push_message(tfm::format("[revealer] entity: [%s] | pct: %d [0x%X] [seqb: %d | secn: %d | ucso: %d | xuid_low %d]\n", ctx->get_name(), msg->voice_data, msg->voice_data,
                            msg->sequence_bytes, msg->section_number, msg->uncompressed_sample_offset, msg->xuid_low));
#endif
                    }
                }
            }
        }
    }
}
Данные получены, функции написаны, читы других игроков мы видим!
Перейдём к визуальной части:

Увидев разные медии, можно понять что обычно используется три метода отображения чит-ревилера:
1) в табе
2) в килл листе
3) в ESP

В данной статье я покажу лишь один из предложенных методов, первый метод - отображение иконок в табе!

По началу посмотрев большинство луа скриптов, я увидел что везде юзается Panorama APi и JS , я очень расстроился и отказался от этой идеи т.к это много тяжелого кода чтобы такое реализовать, но позже одним опытным имгуи дизайнером и просто хорошим кодером мне было подсказано что можно установить одну штуку игроку: m_nPersonaDataPublicLevel = ("CCSPlayerResource", "m_nPersonaDataPublicLevel");

После чего в табе поменяется иконка, которая хранится в папке с игрой: materials/panorama/images/icons/xp/level%i.png
Заместо %i - число (уровень) который мы вручную прописываем игроку. (как пример: materials/panorama/images/icons/xp/level333.png materials/panorama/images/icons/xp/level334.png)

Ну и сам код установки "уровней" в зависимости от того какой чит использует игрок:
cheat_revealer.cpp:
void c_cheat_revealer::update_tab()
{
    if (!g_cfg.misc.cheat_revealer)
        return;

    // паттерн оффсета player_resource:
    // memory::address_t player_resource = memory::get_pattern(client_dll, CXOR("8B 3D ? ? ? ? 85 FF 0F 84 D4 02 00 00"));
    const uintptr_t ptr_resource = ** offsets::player_resource.add(0x2).cast< uintptr_t ** >();

    LISTENER_ENTITY->for_each_player([&](c_cs_player* player)
    {
        auto deref = * (std::uintptr_t *)player;
        if (deref == NULL || deref == 0x01000100)
            return;

        //if (player->is_bot())
        //  return;

        auto index = player->index();
        auto esp = ESP->get_esp_player(index);

        if (ptr_resource)
        {
            // так как контра уже не будет обновляться и хотелось поскорее протестить ревилер, я в тупую взял нужный мне оффсет для записи m_nPersonaDataPublicLevel
            // заранее извиняюсь за тупое различие типов и использование DWORD"a
            DWORD m_nPersonaDataPublicLevel = (DWORD)ptr_resource + 0x4dd4 + (index * 4);

            if (HACKS->local->index() == index)
            {
                // локальный игрок, здесь можем установить какую ту свою иконку
            }
            else
            {
                switch (esp->revealer.m_cheat)
                {
                case CHEAT_GS:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 333;
                    break;
                case CHEAT_FT:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 334;
                    break;
                case CHEAT_EVOLVE:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 336;
                    break;
                case CHEAT_ONETAP:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 338;
                    break;
                case CHEAT_PANDORA:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 339;
                    break;
                case CHEAT_NL:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 340;
                    break;
                default:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 330; // иконка знака вопроса (т.к чит не найден)
                    break;
                }
            }
        }
    }, false);
}
Вот собственно и всё, результат мы видим в табе!
Посмотреть вложение 268737

Прошу строго не судить за терминологию и код т.к я не супер профи в кодинге и первый раз писал статью.
Надеюсь новичкам это пригодится для разработки своих читов, удачи!
Годнота:seemsgood:
 
The voices are getting louder
Участник
Статус
Оффлайн
Регистрация
19 Янв 2017
Сообщения
417
Реакции[?]
344
Поинты[?]
26K
просто дело в том что топовых хвх кодеров в снг не так много и поэтому я осмелился выдвинуть такое предположение, основываясь над началом твоего мастерства в написании шеллкодов год назад:
Пожалуйста, авторизуйтесь для просмотра ссылки.
. И да людям науки хвх это интересно, без лишней озабочености конечно же, так во всём впринципе, в музыке в кино)
Не знаю о чем ты, но опять же, если ты внимательно изучил мою репозиторию и сурс, который я приложил, там есть один интересный коментарий на первой строке, но если же опять ничего не понятно, то я просто приложу это видео.

А еще, вы реально заебали Сеню трогать :rage::triumph:

 
Последнее редактирование:
Говарда
Забаненный
Статус
Оффлайн
Регистрация
21 Дек 2022
Сообщения
497
Реакции[?]
193
Поинты[?]
95K
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
где меню
 
anonymous
Участник
Статус
Онлайн
Регистрация
18 Окт 2022
Сообщения
607
Реакции[?]
216
Поинты[?]
144K
Не знаю о чем ты, но опять же, если ты внимательно изучил мою репозиторию и сурс, который я приложил, там есть один интересный коментарий на первой строке, но если же опять ничего не понятно, то я просто приложу это видео.

А еще, вы реально заебали Сеню трогать :rage::triumph:

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

+ у тебя офигенное оформление рабочей среды
короче ты крутой, отредактирую статью + извиняюсь за беспокойство!
 
Забаненный
Статус
Оффлайн
Регистрация
20 Ноя 2023
Сообщения
107
Реакции[?]
33
Поинты[?]
32K
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
Не знаю о чем ты, но опять же, если ты внимательно изучил мою репозиторию и сурс, который я приложил, там есть один интересный коментарий на первой строке, но если же опять ничего не понятно, то я просто приложу это видео.

А еще, вы реально заебали Сеню трогать :rage::triumph:

вау бро подскажи чо за тема на визуалку ?
 
HvH Legend
Пользователь
Статус
Оффлайн
Регистрация
23 Окт 2022
Сообщения
389
Реакции[?]
95
Поинты[?]
25K
Перед началом хочу выразить огромную благодарность Satoru за помощь с отображением иконок читов в табе т.к я думал что без затрагивания panoramы и js это невозможно.

В данной статье будет использована информация с репозитория
Пожалуйста, авторизуйтесь для просмотра ссылки.
(noad)

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

- Любая сетевая игра работает за счёт постоянного обмена данными между клиентом и сервером. В нашем же случае с ксго, игрок(клиент) создаёт новый "голосовой" пакет данных (экземпляр класса CCLCMsg_VoiceData и CSVCMsg_VoiceData), то есть конструирует и заполняет массив данных и в конечном итоге отправляет на сервер (CNetChan::SendNetMsg) другим игрокам. Читы делают это вручную, зачастую используя готовые функции движка игры, формируя свой "голосовой пакет" который ничем не отличается от нативного игрового кроме как будучи заполненным кастомными данными чита. Нам же нужно получить эти данные, расшифровать и сравнить нужные переменные для нашего чит ревилера.

- В добавку к этому всему, не забываем, что у каждого игрока есть разные идентификаторы и в данном случае нам понадобится идентификатор xuid_low (который так же присутствует в классах CCLCMsg_VoiceData и CSVCMsg_VoiceData), но для чего он нужен, мы узнаем немного позже.

Перейдём к написанию самого чит ревилера:

- Как любой нормальный кодер мы сразу сталкиваемся с логическими "проблемами" на которые нам нужно придумать решения, то как мы будем хранить полученные данные, как часто вызывать функции и как сделать так чтобы всё это дело было оптимизировано!

1) Хранение данных:
В моём конкретном случае, на момент написания ревилера, я использовал готовую базу чит опая (airflow) и в моём распоряжении имелась структура esp_player_t и класс c_esp, которые глобально использовались в проекте, поэтому, к счастью, мне не пришлось придумывать велосипед.

Проблема хранения решена.

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

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

Пишем хук на эту функцию, для получения "голосовых" данных:
Код:
bool __fastcall msg_voice_data(void* ecx, void* edx, c_svc_msg_voice_data* message)
{
    // получаем оригинал функции
    static auto original = hooker::get_original(&msg_voice_data);

    // исключаем локального игрока т.к нам нужны данные соперников ну или тиммейтов
    if ( !HACKS->local || HACKS->local->index() == message->client + 1)
        return original(ecx, edx, message);

    // перехватываем нужные нам данные для ревилера
    CHEAT_REVEALER->handle_voice(message);

    // мы получили что хотели,
    // возвращаем оригинал функции чтобы не ломать работоспособность игры
    return original(ecx, edx, message);
}
Данные получены.
Переходим к их сравнению:

Создаём заголовки
cheat_revealer.hpp:
static unsigned char skeet_shellcode[] =
{
    0x81, 0xEC, 0x4C, 0x01, 0x00, 0x00, 0x53, 0x55, 0x56, 0x8B, 0xF1, 0x89, 0x54, 0x24, 0x54, 0x33,
    0xDB, 0xC7, 0x44, 0x24, 0x10, 0x68, 0x33, 0x05, 0x97, 0x57, 0xC7, 0x44, 0x24, 0x18, 0x36, 0x06,
    0xD4, 0xEA, 0xBF, 0x00, 0x01, 0x00, 0x00, 0x8B, 0x46, 0x10, 0x8B, 0x4E, 0x14, 0x89, 0x44, 0x24,
    0x30, 0x8B, 0x46, 0x28, 0x89, 0x44, 0x24, 0x38, 0x8B, 0x46, 0x24, 0x89, 0x44, 0x24, 0x3C, 0x8B,
    0x46, 0x2C, 0x89, 0x44, 0x24, 0x40, 0x8B, 0xC3, 0xC7, 0x44, 0x24, 0x1C, 0x4F, 0xC4, 0xA4, 0x3E,
    0xC7, 0x44, 0x24, 0x20, 0x85, 0xB2, 0xAC, 0x0F, 0x89, 0x4C, 0x24, 0x34, 0x89, 0x5C, 0x24, 0x28,
    0x88, 0x44, 0x04, 0x5C, 0x40, 0x3B, 0xC7, 0x72, 0xF7, 0x8A, 0xF3, 0x8B, 0xF3, 0x8A, 0x54, 0x34,
    0x5C, 0x8B, 0xC6, 0x83, 0xE0, 0x0F, 0x8A, 0x44, 0x04, 0x14, 0x02, 0xC2, 0x02, 0xF0, 0x0F, 0xB6,
    0xCE, 0x8A, 0x44, 0x0C, 0x5C, 0x88, 0x44, 0x34, 0x5C, 0x46, 0x88, 0x54, 0x0C, 0x5C, 0x3B, 0xF7,
    0x72, 0xDB, 0x8A, 0xE3, 0x8B, 0xFB, 0xBD, 0x80, 0x00, 0x00, 0x00, 0x8A, 0xF4, 0xFE, 0xC6, 0x0F,
    0xB6, 0xF6, 0x8A, 0x54, 0x34, 0x5C, 0x02, 0xE2, 0x0F, 0xB6, 0xCC, 0x8A, 0x44, 0x0C, 0x5C, 0x88,
    0x44, 0x34, 0x5C, 0x88, 0x54, 0x0C, 0x5C, 0x83, 0xED, 0x01, 0x75, 0xE1, 0xFE, 0xC6, 0x0F, 0xB6,
    0xF6, 0x8A, 0x54, 0x34, 0x5C, 0x8A, 0xDA, 0x02, 0xDC, 0x0F, 0xB6, 0xCB, 0x8A, 0x44, 0x0C, 0x5C,
    0x88, 0x44, 0x34, 0x5C, 0x88, 0x54, 0x0C, 0x5C, 0x8A, 0x44, 0x34, 0x5C, 0x02, 0xC2, 0x0F, 0xB6,
    0xC0, 0x8A, 0x44, 0x04, 0x5C, 0x8A, 0xE3, 0x30, 0x44, 0x3C, 0x30, 0x47, 0x83, 0xFF, 0x14, 0x72,
    0xCB, 0x33, 0xFF, 0x89, 0x7C, 0x24, 0x2C, 0x8B, 0xEF, 0xC7, 0x44, 0x24, 0x24, 0x0F, 0x00, 0x00,
    0x00, 0x8B, 0x7C, 0x24, 0x24, 0xD1, 0xED, 0x89, 0x6C, 0x24, 0x48, 0x0F, 0xB7, 0x4C, 0xAC, 0x32,
    0x8B, 0xC1, 0x0F, 0xBF, 0xC9, 0x89, 0x44, 0x24, 0x54, 0x0F, 0xB7, 0x44, 0xAC, 0x34, 0xBD, 0x85,
    0x8E, 0xD5, 0x91, 0x8B, 0xD0, 0x89, 0x4C, 0x24, 0x44, 0x8B, 0xD8, 0x89, 0x54, 0x24, 0x4C, 0x8B,
    0xC1, 0x0F, 0xB7, 0xF0, 0x2B, 0xDD, 0x24, 0x0F, 0x8B, 0xD5, 0x8A, 0xC8, 0xD1, 0xC2, 0x66, 0xD3,
    0xCB, 0x8B, 0xEA, 0x66, 0x8B, 0xC3, 0xD1, 0xC5, 0x66, 0x33, 0xC6, 0x2B, 0xF2, 0x0F, 0xB7, 0xD8,
    0x8A, 0xCB, 0x80, 0xE1, 0x0F, 0x66, 0xD3, 0xCE, 0x66, 0x33, 0xF0, 0x0F, 0xB7, 0xCE, 0x0F, 0xB7,
    0xC6, 0x83, 0xEF, 0x01, 0x75, 0xCB, 0x8B, 0x7C, 0x24, 0x28, 0x8B, 0xC5, 0x2B, 0xD8, 0x89, 0x6C,
    0x24, 0x24, 0x33, 0x5C, 0x24, 0x50, 0x83, 0xC7, 0x02, 0x8B, 0x6C, 0x24, 0x48, 0xD1, 0xC0, 0x2B,
    0xC8, 0x89, 0x7C, 0x24, 0x28, 0x8B, 0x44, 0x24, 0x4C, 0x33, 0x4C, 0x24, 0x2C, 0x0F, 0xB7, 0xC0,
    0x89, 0x44, 0x24, 0x50, 0x8B, 0x44, 0x24, 0x54, 0x0F, 0xB7, 0xC0, 0x66, 0x89, 0x5C, 0xAC, 0x34,
    0x66, 0x89, 0x4C, 0xAC, 0x32, 0x89, 0x44, 0x24, 0x2C, 0x83, 0xFF, 0x09, 0x0F, 0x82, 0x45, 0xFF,
    0xFF, 0xFF, 0x8B, 0x44, 0x24, 0x30, 0x8B, 0x4C, 0x24, 0x58, 0xC1, 0xF8, 0x10, 0xC1, 0xF9, 0x10,
    0x33, 0xC1, 0xB9, 0x24, 0x24, 0x00, 0x00, 0x5F, 0x5E, 0x66, 0x3B, 0xC1, 0x5D, 0x0F, 0x94, 0xC0,
    0x5B, 0x81, 0xC4, 0x4C, 0x01, 0x00, 0x00, 0xC3
};

typedef bool(__fastcall* skeet_shellcode_t)(void*, uint32_t);

class c_cheat_revealer
{
private:
    bool is_using_gamesense(c_svc_msg_voice_data* msg, uint32_t xuid_low);
    bool is_using_fatality(uint16_t pct);
    bool is_using_evolve(uint16_t pct);
    bool is_using_onetap(uint16_t pct);
    bool is_using_pandora(uint16_t pct);
public:
    void handle_voice(c_svc_msg_voice_data* msg);
    void update_tab();
};
Создаём вспомогательные функции для сравнения полученных нами данных для будущего чит ревилера.

Краткий обзор функций благодаря которым и функционирует наш чит-ревилер:
Функции имеют тип возвращаемого значения - boolean, то-есть если нужный нам чит был найден то возвращаем true!


Рассмотрим первую функцию для обнаружения юзеров скита:
cheat_revealer.cpp:
bool c_cheat_revealer::is_using_gamesense(c_svc_msg_voice_data* msg, uint32_t xuid_low)
{
    // Declare static variables
    static unsigned char* shellcode = nullptr;
    static skeet_shellcode_t is_using_skeet_fn = nullptr;

    // Allocate memory only once
    if (!shellcode)
    {
        shellcode = reinterpret_cast<unsigned char*>(
            VirtualAlloc(nullptr, sizeof(skeet_shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE)
            );

        // Copy the shellcode to the allocated memory
        memcpy(shellcode, skeet_shellcode, sizeof(skeet_shellcode));

        // Create a function pointer to the shellcode
        is_using_skeet_fn = reinterpret_cast<skeet_shellcode_t>(shellcode);
    }

    // Call the shellcode
    bool result = is_using_skeet_fn(msg, xuid_low);

    // Note: We are not freeing the memory to keep it for future calls?

    return result;
}
Функция имеет в себе код аллоцирования памяти для загадочного шеллкода, как работает шеллкод я не знаю, но в дальнейшем поинтер на эту память вызывается с параметрами c_svc_msg_voice_data* msg (голосовая дата полученная клиентом от сервера) и uint32_t xuid_low (идентификатор игрока). В результате шеллкод возвращает тип boolean, видимо сравнивая эти два параметра с какими то данными скита внутри себя.

Остальные примеры обнаружения других читов - более простые, суть заключается в сравнении той инфы которую читы формируют и отправляют на сервер , в этой инфе (голосовые пакеты описанные выше) имеется идентификатор xuid_low и соответственно у каждого чита он разный, чтобы не дампить самому эти значения, методом поиска луашек на чит ревилер, были найдены hex значения которые юзаются для обнаружения различных читов (сравнивая переменную xuid_low из полученной инфы в SVCMsg_VoiceData хуке)

cheat_revealer.cpp:
bool c_cheat_revealer::is_using_fatality(uint16_t pct)
{
    if (pct == 0x7FFA || pct == 0x7FFB)
        return true;

    return false;
}

bool c_cheat_revealer::is_using_evolve(uint16_t pct)
{
    if (pct == 0x7FFC || pct == 0x7FFD)
        return true;

    return false;
}

bool c_cheat_revealer::is_using_onetap(uint16_t pct)
{
    if (pct == 0x57FA)
        return true;

    return false;
}

bool c_cheat_revealer::is_using_pandora(uint16_t pct)
{
    if (pct == 0x695B || pct == 0x1B39)
        return true;

    return false;
}
Собираем всё это в кучу.

Основная функция ревилера которую я написал для работы с полученной инфой из SVCMsg_VoiceData хука:
cheat_revealer.cpp:
void c_cheat_revealer::handle_voice(c_svc_msg_voice_data* msg)
{
    // исключаем использование при отжатии эксплоитов чтобы обезопасить работоспобность функции
    if (EXPLOITS->recharge.start)
        return;

    // проверяем наличие данных
    if (msg->format != 0)
        return;

    // исключаем локального игрока
    if (!HACKS->local || HACKS->engine->get_local_player() == msg->client + 1)
        return;

    // проверяем наличие данных
    if (msg->section_number == 0 && msg->sequence_bytes == 0 && msg->uncompressed_sample_offset == 0)
        return;

    // Здесь я сталкнулся с небольшой проблемой:
    // Проблема выглядела так: при частом аллоцировании и очищении памяти для шеллкода сальватора, у меня происходил краш процесса.
    // Таким способом я решил уменьшить частоту использования этого кода чтобы давать памяти время подумать.
    // Решение вышло неправильным и не сработало т.к читы в разное время отправляют те самые "голосовые" пакеты и можно легко словить рассинхрон и прозевать момент их получения клиентом и тем самым остаться с голой жопой.

    // Ограничения использования этого кода (момент неудачной оптимизации)
    //ULONGLONG time = GetTickCount64();
    //const bool should_receive = time - CHEAT_REVEALER->last_receive_ack_time >= 500; // miliseconds

    // проверяем что идентификатор игрока находится в рабочем диапазоне
    int sender_id = msg->client + 1;
    if (sender_id >= 0 && sender_id <= 63)
    {
        // создаем временный пустой массив данных
        player_info_t player_info{};

        // заполняем временно созданный массив информацией о игроке
        if (HACKS->engine->get_player_info(sender_id, &player_info))
        {
            // получаем поинтер который хранит в себе всякую информацию которую чит использует в ESP и не только..
            auto esp_info_sender = ESP->get_esp_player(sender_id);

            // здесь я придумал нехитрое решение проблемы оптимизации
            // проверку на идентификатор, чтобы постоянно не обновлять одного и того же игрока!
            // если единтификатор у игрока поменялся - проверим какой чит он использует.
            if (esp_info_sender && esp_info_sender->revealer.m_xuid_low != player_info.xuid_low)
            {
                // создаём массив и получаем "голосовой" пакет данных
                c_voice_communication_data voice_data = msg->get_data();

                // проверяем игрока на различные читы
                const auto using_skeet = is_using_gamesense(msg, player_info.xuid_low);
                const auto using_fatality = is_using_fatality(static_cast<uint16_t>(msg->xuid_low));
                const auto using_evolve = is_using_evolve(static_cast<uint16_t>(msg->xuid_low));
                const auto using_onetap = is_using_onetap(static_cast<uint16_t>(msg->xuid_low));
                const auto using_pandora = is_using_pandora(static_cast<uint16_t>(msg->xuid_low));

                // switch case would be better?
                if (using_skeet)
                {
                    esp_info_sender->revealer.update(CHEAT_GS, player_info.xuid_low);
#ifdef _DEBUG
                    printf("receiving | name: %s | xuid_low %d, found skeet user!\n", player_info.name, player_info.xuid_low);
#endif
                }
                else if (using_fatality)
                {
                    esp_info_sender->revealer.update(CHEAT_FT, player_info.xuid_low);
#ifdef _DEBUG
                    printf("receiving | name: %s | xuid_low %d, found fatality user!\n", player_info.name, player_info.xuid_low);
#endif
                }
                else if (using_evolve)
                {
                    esp_info_sender->revealer.update(CHEAT_EVOLVE, player_info.xuid_low);
#ifdef _DEBUG
                    printf("receiving | name: %s | xuid_low %d, found ev0lve user!\n", player_info.name, player_info.xuid_low);
#endif
                }
                else if (using_onetap)
                {
                    esp_info_sender->revealer.update(CHEAT_ONETAP, player_info.xuid_low);
#ifdef _DEBUG
                    printf("receiving | name: %s | xuid_low %d, found ONETAP user!\n", player_info.name, player_info.xuid_low);
#endif
                }
                else if (using_pandora)
                {
                    esp_info_sender->revealer.update(CHEAT_PANDORA, player_info.xuid_low);
#ifdef _DEBUG
                    printf("receiving | name: %s | xuid_low %d, found PANDORA user!\n", player_info.name, player_info.xuid_low);
#endif
                }
                else
                {
                    // https://lua.neverlose.cc/documentation/events#voice_message
                    // неверлуз и ещё несколько других читов(примо, спирт, никс) используют более мудрённую систему передачи/чтения данных между своими юзерами:
                    // понять что к чему можно посмотрев в их апи, и увидеть что там используются protobuf'ы с которыми у меня нет опыта работы поэтому нл я задетектить не смог!
                    // но начав дампить непонятные мне данные из голосового пакета, нашел частое hex число, дурацкая рулетка вообщем.
                    if ((uint8_t)msg->voice_data == (uint8_t)0x0B282838)
                    {
                        esp_info_sender->revealer.update(CHEAT_NL, player_info.xuid_low);           
#ifdef _DEBUG
                        const auto ctx = (c_cs_player*)HACKS->entity_list->get_client_entity(sender_id);
                        EVENT_LOGS->push_message(tfm::format("[revealer] entity: [%s] | pct: %d [0x%X] [seqb: %d | secn: %d | ucso: %d | xuid_low %d]\n", ctx->get_name(), msg->voice_data, msg->voice_data,
                            msg->sequence_bytes, msg->section_number, msg->uncompressed_sample_offset, msg->xuid_low));
#endif
                    }
                }
            }
        }
    }
}
Данные получены, функции написаны, читы других игроков мы видим!
Перейдём к визуальной части:

Увидев разные медии, можно понять что обычно используется три метода отображения чит-ревилера:
1) в табе
2) в килл листе
3) в ESP

В данной статье я покажу лишь один из предложенных методов, первый метод - отображение иконок в табе!

По началу посмотрев большинство луа скриптов, я увидел что везде юзается Panorama APi и JS , я очень расстроился и отказался от этой идеи т.к это много тяжелого кода чтобы такое реализовать, но позже одним опытным имгуи дизайнером и просто хорошим кодером мне было подсказано что можно установить одну штуку игроку: m_nPersonaDataPublicLevel = ("CCSPlayerResource", "m_nPersonaDataPublicLevel");

После чего в табе поменяется иконка, которая хранится в папке с игрой: materials/panorama/images/icons/xp/level%i.png
Заместо %i - число (уровень) который мы вручную прописываем игроку. (как пример: materials/panorama/images/icons/xp/level333.png materials/panorama/images/icons/xp/level334.png)

Ну и сам код установки "уровней" в зависимости от того какой чит использует игрок:
cheat_revealer.cpp:
void c_cheat_revealer::update_tab()
{
    if (!g_cfg.misc.cheat_revealer)
        return;

    // паттерн оффсета player_resource:
    // memory::address_t player_resource = memory::get_pattern(client_dll, CXOR("8B 3D ? ? ? ? 85 FF 0F 84 D4 02 00 00"));
    const uintptr_t ptr_resource = ** offsets::player_resource.add(0x2).cast< uintptr_t ** >();

    LISTENER_ENTITY->for_each_player([&](c_cs_player* player)
    {
        auto deref = * (std::uintptr_t *)player;
        if (deref == NULL || deref == 0x01000100)
            return;

        //if (player->is_bot())
        //  return;

        auto index = player->index();
        auto esp = ESP->get_esp_player(index);

        if (ptr_resource)
        {
            // так как контра уже не будет обновляться и хотелось поскорее протестить ревилер, я в тупую взял нужный мне оффсет для записи m_nPersonaDataPublicLevel
            // заранее извиняюсь за тупое различие типов и использование DWORD"a
            DWORD m_nPersonaDataPublicLevel = (DWORD)ptr_resource + 0x4dd4 + (index * 4);

            if (HACKS->local->index() == index)
            {
                // локальный игрок, здесь можем установить какую ту свою иконку
            }
            else
            {
                switch (esp->revealer.m_cheat)
                {
                case CHEAT_GS:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 333;
                    break;
                case CHEAT_FT:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 334;
                    break;
                case CHEAT_EVOLVE:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 336;
                    break;
                case CHEAT_ONETAP:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 338;
                    break;
                case CHEAT_PANDORA:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 339;
                    break;
                case CHEAT_NL:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 340;
                    break;
                default:
                    *(PINT)((DWORD)m_nPersonaDataPublicLevel) = 330; // иконка знака вопроса (т.к чит не найден)
                    break;
                }
            }
        }
    }, false);
}
Вот собственно и всё, результат мы видим в табе!
Посмотреть вложение 268737

Прошу строго не судить за терминологию и код т.к я не супер профи в кодинге и первый раз писал статью.
Надеюсь новичкам это пригодится для разработки своих читов, удачи!
что-то новое для лв 3 !
 
Сверху Снизу