• Добро пожаловать на сайт - wlux.net!

    FAQ по форуму

    1. Все сообщения до группы местный проходят модерацию от 1 минуты до 24 часа

    2. Сообщения учитываються в следующих разделах: Читать

    3.Что-бы скачать вложение нужно 2 сообщения.

    4.Личные переписки работают только с Администрацией форума

    5. Запрещено: Просить скрытый текст , спам, реклама, скам, ддос, кардинг и другая чернуха, нарушать любые законы РФ/СНГ = бан аккаунта

    6. Внимание! Мы не удаляем аккаунты с форума! Будьте внимательны ДО регистрации! Как удалить аккаунт на форуме?!

    5.Не понимаю, как и что тут работает у вас?!Как создавать темы, писать сообщения, как получать реакции. Почему не засчитывает сообщения. Все ответы здесь

This is a mobile optimized page that loads fast, if you want to load the real page, click this text.

Гайд Пишем свой первый чит на Unreal Engine 5

Оффлайн

wlux.net

Где волчьи уши, там волчьи зубы.
Команда форума
LV
7
 
20.06.2022
23 849
218
36
Награды
10
Пол
Муж.

Репутация:

  • Автор темы
  • Администратор
  • Модератор
  • Команда форума
  • #1
Собственно в мои руки попала довольно не новая игра, но одна из немногих игр на Unreal Engine 5 - POLYGON. Игра Free 2 Play, а в качестве анти-чита используется бесплатная версия Easy Anti Cheat. Так как статью я пишу об Internal чите, для инжекта вам может потребоваться kernel-mode инжектор, но так как там бесплатный EAC вы можете использовать банальный face-injector (

Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

). Для тестов он вам годится

Мельком пробежавшись по дампу игры понял что разницы с Unreal Engine 4 в плане чит-дева не особо много, но всё же отличия есть

Собственно небольшое оглавление данной статьи:
  1. Основные программы для работы
  2. Делаем дамп игры
  3. Настраиваем IDA Pro для работы с дампом
  4. Объяснение по основным офсетам
  5. Сбор офсетов
  6. Пишем первый код
Конечно будут ещё подпункты в статьях, но дописывать их не хочу

1. Основные программы для работы
  1. Microsoft Visual Studio ( ссылка:

    Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

    ) - Собственно самая важная программа, в которой мы и будем писать код. В статье я буду использовать MSVS 2022. При установке вам нужно будет выбрать "Разработка классических приложений на C++"
  2. x64dbg ( ссылка:

    Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

    ) - Программа для отладки. На первое время нам понадобится из его функционала только Scylla ( при желании вы можете скачать её отдельно

    Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

    ), для того чтоб сдампить саму игру.
  3. ReClass ( ссылка:

    Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

    ) - Программа для реверса структур, нам оно понадобится для создания классов
  4. IDA Pro ( ссылкa:

    Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

    ) - Софт для анализа дампа игры, в котором мы и будем собирать офсеты или тут:
Скачать IDA_8.3 pro -

2. Делаем дамп игры
После того как Вы скачали весь нужный софт из 1 "главы" (буду называть это так), нам нужно сделать дамп игры.
Так как игра у нас защищена кернел анти читом, просто так сдампить не получится. Конечно Вы можете немного извратиться и скачать KsDumper ( ссылка:

Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

) для дампа игры под анти читом. Но с огромной вероятностью Вас либо не пустит в игру, либо же Вам забанит аккаунт.
Но пока что мы обойдёмся x64dbg'ом и его плагином Scylla.
Чтобы сдампить игру, нам надо запустить её без анти чита. Переходим в стим, нажимаем ПКМ на нашу игру, наводим на "Управление" и кликаем "Посмотреть локальные файлы". Мы попадаем в папку с игрой, и дальше идём по пути - POLYGON\Binaries\Win64. Ищем там бинарник POLYGON-Win64-Shipping.exe и запускаем его.

После того как игра прогрузилась до ошибки инициализации, запускаем x64dbg
В x64dbg переходим во вкладку Plugins и выбираем Scylla:


В открывшемся окне, в поле "Attach to an active process" выбираем наш процесс игры. В данной ситуации это "POLYGON-Win64-Shipping.exe"


Далее кликаем "IAT Autosearch" и немного ждём ( На слабых пк может и не немного ). В появившемся окошке Information кликаем да


В следующем окошке кликаем "ОК" и в окне Scylla нажимаем на "Get Imports"
После чего нам надо удалить инвалидные импорты ( помечены красным крестиком ) из полученного списка. Делается это нажатием на ПКМ по нему и нажатием "Delete Tree Node"

Сохраняем наш дамп в удобное для вас место, переходим в IDA Pro и закидываем туда дамп. Если вам попросит подгрузить длл просто нажимаете отмена, она нам ни к чему.

3. Настраиваем IDA Pro для работы с дампом
Настройку IDA Pro можно проводить в любой момент, но в идеала дождаться пока дамп полностью проанализируется. Понять что дамп полностью проанализировался можно по левому нижнему углу, там должно быть написано idle


Далее мы нажимаем Shift + F12 для того чтоб сгенерировать строки. После того как они сгенерируется, в любом месте вкладки Strings кликаем ПКМ, выбираем Setup


Там мы выбираем в табличке "Allowed Strings Types" - C-Style, Unicode C-Style


На этом этапе для сбора основных офсетов нам хватит этих настроек

4. Объяснение по основным офсетам
Для данной статьи я сделал такую простую диаграмму где идёт зависимости офсетов


Пояснения по цветам там есть, и оно должно быть для вас более-менее понятным

Сами классы ( офсеты ):
UWorld - Основной класс. По документации Unreal Engine этот класс отвечает за сам наш игровой мир, и всё что в нём хранится
ULevel - Наша игровая локация, и всё что на неё хранится
AActor - Все наши акторы на карте ( например игроки, деревья, лут, звуки, свет и тд и тп )
USceneComponent - Параметры наших акторов на определённой сцене
GetObjectName - Получение имени акторов
APawn - Базовый класс всех акторов
RelativeLocation - Позиция наших акторов на сцене

UGameInstance - Тут хранятся все параметры
ULocalPlayer - Из названия понятно что это наш локальный игрок
APlayerController - Класс управления нашим актором
APawn - В данном случае базовый класс нашего локального актора
ProjectWorldToScreen - Преобразование Vector3 координат в Vector2 координаты для вывода есп на экран

Собственно этого нам хватит для написания базового есп. Переходим теперь к сбору этих офсетов

5. Сбор офсетов
Теперь перейдём к самому сбору офсетов. Поиск их будет происходить в основном по строкам, я приведу пример строк по которым 100% можно что либо найти, так же вы сможете попробовать поискать свои, так как Unreal Engine опен сурс движок.

Начнём мы с UWorld. Для того чтоб найти его, в поиск по строкам вставляете следующую строку: "No world was found for object (%s) passed in to UEngine::GetWorldFromContextObject()."
Переходим к нему 2 раза кликнув, потом нажимаем X дабы открыть Xref'ы


Переходим к функции и декомпилируем её нажатием F5


Обращаем внимание на 44-45 строки
if ( !v15 ) return qword_7FF7A2139140
7FF7A2139140 - Аддрес UWorld'a. Из него нам надо получить сам офсет. Сделать это можно перейдя в IDA Output и ввести простенькую команду
0x7FF7A2139140-get_imagebase(). Второе значение с буквой H в конце, это и есть наш офсет. По итогу выходит 0x7E49140. Записываем его в какой нибудь блокнот на пк, и идём искать всё остальное


Далее на очереди у нас UGameInstance. Его мы будем искать по строке "InWorld->GetGameInstance() is null"
Переходим к строчке, нажимаем xref и переходим к функции. Тут можно даже не декомпилировать её, тут её прекрасно видно 0x190 - Наш офсет. Записываем и бежим дальше


Теперь ищем офсетик ULevel. Ищется по строке "RequestLevel: World is pending kill %s". Переходим xref'aем и декомпилируем


Обращаем внимание на данные строки:
pseudocode:
 if ( (*(v25 + 8) & 0x60000000) != 0 )
    {
      if ( byte_7FF7A2120658 >= 6u )
      {
        v47 = sub_7FF79C7262E0(&v68, &v111);
        if ( *(v47 + 8) )
          v28 = *v47;
        v86 = v28;
        sub_7FF79FEDAAB0(&v71, &byte_7FF7A2120658, aRequestlevelWo, &v86);
        v19 = v111;
        goto LABEL_158;
      }
      return 0;
    }
    v46 = *(v25 + 48);
    if ( v46 != *(a1 + 344) )
    {
      *(v25 + 274) = *(a2 + 274);
      *(v46 + 184) = a2;
      sub_7FF79EF24080(a1, *(v25 + 48), v29);
    }
    return 1;

Тут "v46 = *(v25 + 48);" где 48 наш офсет (функцию можно представить в виде: ULevel = ( UWorld + 0x30 ) ). кликаем по нему мышкой, и нажимаем на клавиатуре H ( или же русская Р ). Выходит 0x30 - это наш офсет ULevel

Теперь у нас ULocalPlayer. Ищется по строчке: "UGameInstance::RemovePlayer: Removed player %s with ControllerId %i at index %i (%i remaining players)"
Переходим декомпилируем


Тут где мы видим a1 + 56 (Функцию можно представить в виде UGameInstance + 0x38) - 56 наш офсет. Точно так же кликаем H на него и получаем 0x38. Это и есть наш офсет. Сохраняем идём дальше

Дальше у нас APlayerController
Ищем по строчке - "Failed to find local player for controller id %d". Переходим декомпилируем и смотрим


Тут у нас строка v8 = *(v7 + 48); - где 48 наш офсет ( можно представить в виде: APlayerController = (ULocalPlayer + 0x30) ). Кликаем H и получаем офсет 0x30 - Это наш APlayerController

AActor: ищем по стрчоке "Num Actors: %i".
Переходим к функции декомпилируем



Тут переходим на функцию которая находится в v10 ( Эта функция является GetActorCount ).


Листаем в самый низ, и видим строчку "v1 += *(v8 + 160);" ( представить её можно в виде ActorCount = (ULevel + 0xA0) ) тут 160 мы преобразуем в HEX и получаем 0xA0. Это ActorCount офсет. Чтобы получить нам класс AActor нужно из 0xA0 вычесть 0x8. Получается 0xA0 - 0x8 = 0x98. 0x98 это наш офсет класса AACtor

Дальше у нас идут функции, начнём пожалуй с WorldToScreen
Ищем по строчке "ProcessServerTravel: %s". Переходим к строке, и листаем вниз на 2 функции.
Наш WorldToScreen должен выглядеть так:


Копируем всё что после sub_ и считаем в IDC путём 0x7FF79EDEC1A0-get_imagebase(). На выходе получаем офсет 0x4AFC1A0. Это собственно и есть наш world to screen

Теперь ищем GetObjectName. Для его поиска в Strings пишем GetObjectName. Нажимаем X и переходим ко второму xref'y


Потом переходим на функцию под нашей строчкой


декомпилируем эту функцию и внимательно смотрим


Там где v8 = sub_7FF79EED2720(v10, v7); всё что после sub_ это наш аддрес функции. Считаем по тому же пути что и world to screen ( 0x7FF79EED2720-get_imagebase() ) получаем офсет 0x4BE2720 это наш офсет GetObjectName

На этом наш поиск офсетов закончен. Теперь мы можем перейти к написанию кода

6. Пишем код
Для написания кода вам понадобится база чита. Объективно хукнуть Present и Resize и подключить имгуи. Вам этого хватит.
Я для этого буду использовать свою готовую базу ( просить её не надо, я всё равно не дам )

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


2 раза кликаем по красному значению ( Это значение является base address )


и прибавляем к этому значению наш офсет UWorld


Нажимаем Enter и кликаем ПКМ по офсету 0000 выбираем там Change Type и выбираем Pointer


После того как кликнули нажимаем на 2 стрелочки и выбираем Class Instance


Далее путём клика пкм по появившемуся классу добавляем ему 2 раза 256 байт


У вас должно выйти примерно такое:


Теперь вы знаете как создавать классы в реклассе. Но это не конец. Во 1х где строчка класс переименовывайте в UWorld так как мы находимся в нём
Дальше смотрим на офсет 0030. Тут у нас находится ULevel. Делаем класс и добавляем 3 раза 64 байта или 1 раз 256 как удобнее. Понять что вы находитесь в верном классе, можно по офсету 0058. Там обычно пишется имя карты на которой мы находимся


Но это нам не особо надо, мы переходим к нашему офсету 0x98 (0098) - AActor. Делаем из него класс. Если вы хотите можете поменять значение офсета 00A0 на uint32. Это будет количество акторов которые находятся в данный момент на карте. А 00A4 - Максимальное количество акторов на карте

но они нам особо не нужны

Дальше спускаемся до 0190 в UWorld к нашему классу UGameInstance. Делаем из него класс и добавляем 64 байта этого нам хватит. Тут же делаем класс по офсету 0038 - Это наш ULocalPlayer. У вас должно получится так:


Тут мы можем сделать поинтер на 0030 офсете, это наш APlayerController, но как вы можете заметить он стоит nullptr. Это объясняется тем, что как такового нет игрока за которого мы можем играть, но так как нам в любом случае этот офсет нужен, делаем из него класс не обращая что он не валидный

По итогу у вас должно выйти вот так:


Этого нам собственно хватит на данном этапе. Вверху кликаем Project -> Generate C++ code
Нам выведет это:


Копируем всё что идёт от class UWorld и до самого конца. Первые 2 класса можно не затрагивать. Теперь переходим в MSVS

Как я и сказал вам нужна будет база с хуком и какой нибудь рисовкой ( Как пример можно использовать ( главное не используйте это на постоянной основе, с вероятностью 99% вы получите бан ):

Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

)

Вставляем наш код и немного редактируем его. Во первых надо задать все имена для классов. Например:
Было
UWorld class:
class UWorld
{
public:
    char pad_0000[ 48 ]; //0x0000
    class ULevel* N000006CC; //0x0030
    char pad_0038[ 344 ]; //0x0038
    class UGameInstance* N000006F8; //0x0190
    char pad_0198[ 112 ]; //0x0198
}; //Size: 0x0208

Стало
UWorld class:
class UWorld
{
public:
    char pad_0000[ 48 ]; //0x0000
    class ULevel* mylevel; //0x0030
    char pad_0038[ 344 ]; //0x0038
    class UGameInstance* owninggameinstance; //0x0190
    char pad_0198[ 112 ]; //0x0198
}; //Size: 0x0208

И так далее. Суть вы должны понять, ибо это не сложно. Ставите так как удобно вам

Далее нам потребуется SDK Unreal Engine'a. А конкретно функции Vector2, 3; FString; TArray. Я был бы нелюдем если бы не дал эти структурки вам:

Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.



копируем и вставляем над нашими классами.

Теперь нам надо изменить 2 офсета в классах. А конкретно
ULevel -> AActor и UGameInstance -> ULocalPlayer
Их надо поместить в TArray.

TArray < class AActor *> actors;
TArray< class ULocalPlayer* > localplayer;

Выйти у вас должно так:

C++:
class ULevel
{
public:
    char pad_0000[ 152 ]; //0x0000
    TArray< class AActor* > actors; //0x0098
}; //Size: 0x00C8

class UGameInstance
{
public:
    char pad_0000[ 56 ]; //0x0000
    TArray< class ULocalPlayer* > localplayer; //0x0038
}; //Size: 0x0048

Теперь работа с классами у нас на этом закончена. Переходим к функциям. Я вынес их в отдельный файл для удобства

Для начала напишем функцию WorldToScreen. У неё должно быть 3 аргумента ( 4 вообще если считать бул, но он в движке стоит по дефолту и изменять нам его не обязательно ) аргументы у неё это APlayerController - наше управление игроком, Vector3 - позиция актора в мире, Vector2 - позиция на экране . Сама функция имеет тип bool. По итогу объявление функции должно иметь такой вид:
engine functions:
bool world_to_screen( APlayerController*, Vector3, Vector2* );

Теперь давайте её определим. Мы знаем нужные нам аргументы и офсет функции.
Пишем такой код:
WorldToScreen function:
    bool world_to_screen( APlayerController* controller, Vector3 world_pos, Vector2* screen_pos )
    {
        auto base = ( uintptr_t ) GetModuleHandle( L"POLYGON-Win64-Shipping.exe" ); // Получаем базовый аддрес приложения

        using worldtoscreen = bool( * )( APlayerController*, Vector3, Vector2* ); // Объявляем тип функции

        const auto WorldToScreen = ( worldtoscreen ) ( base + 0x4AFC1A0 ); // Инициализируем функцию читая её офсет

        return WorldToScreen( controller, world_pos, screen_pos ); // Вызываем функцию
    }

В идеале сделать чек на валид, но до этого мы дойдём потом

Теперь делаем функцию GetObjectName. Функция имеет 1 аргумент AActor - наш актор имя которого мы получаем. Тип функции FString ( Наша кастомная структурка )
Объявляем её:
GetObjectName:
FString get_object_name( AActor* );

Определяем
GetObjectName function:
    FString get_object_name( AActor* actor )
    {
        auto base = ( uintptr_t ) GetModuleHandle( L"POLYGON-Win64-Shipping.exe" ); // Получаем базовый аддрес приложения

        using getobjectname = FString( * )( AActor* ); // Объявляем тип функции

        const auto GetObjectName = ( getobjectname ) ( base + 0x4BE2720 ); // Инициализируем функцию читая её офсет

        return GetObjectName( actor ); // Вызываем функцию
    }

Готово! Мы объявили 2 функции которые нашли, и теперь можем спокойно их использовать.

Единственное что надо немного дописать класс AActor и добавить ещё 1. Так как не хочу запаривать вам голову реверсом. Эти 2 офсета ищутся в реклассе или иде. Но я просто дам вам готовые классы

classes:
class AActor
{
public:
    char pad_0000[ 0x190 ]; //0x0000
    class USceneComponent* rootcomponent;
}; //Size: 0x0008

class USceneComponent
{
public:
    char pad_0000[ 0x138 ];
    Vector3 relativelocation;
};

Так мы сможем получить позицию наших акторов.

Теперь мы можем перейти к написанию самого кода ЕСП. Я решил расписать всё это в виде комментариев так как тут и так много текста. Так что держите и анализируйте:
Simple ESP:
bool execute( )
    {
        auto base_address = ( uintptr_t ) GetModuleHandle( L"POLYGON-Win64-Shipping.exe" ); // Получаем базовый аддрес приложения
    
        auto myworld = *( UWorld** ) ( base_address + 0x7E49140 ); // Инициализируем класс UWorld
        if ( !myworld ) return false; // Проверяем на валид наш класс

        auto level = myworld->mylevel; // Получаем класс ULevel из под класса UWorld
        if ( !level ) return false; // Проверяем на валид наш класс

        auto gameinstance = myworld->owninggameinstance; // Получаем класс UGameInstance из под класса UWorld
        if ( !gameinstance ) return false; // Проверяем на валид наш класс

        auto localplayer = gameinstance->localplayer[ 0 ];// Получаем класс ULocalPlayer из под класса UGameInstance. Важно что ULocalPlayer находится в TArray. По этому надо указать [ 0 ]
        if ( !localplayer ) return false; // Проверяем на валид наш класс

        auto playercontroller = localplayer->playercontroller; // Получаем класс APlayerController из под класса ULocalPlayer
        if ( !playercontroller ) return false; // Проверяем на валид наш класс

        auto actors = level->actors; // Получаем класс AActor из под класса ULevel

        for ( auto i = 0; i < actors.Num( ); i++ ) // Запускаем цикл перебора акторов. Где actors.Num( ) - количество акторов
        {
            if ( !actors.IsValidIndex( i ) ) continue; // Проверяем индекс на валид. Тут вызывается проверка что если индекс < количества акторов, то возвращается false

            auto actor = actors[ i ]; // Получаем текущего актора ( в цикле )
            if ( !actor ) continue; // проверяем этого актора на валид

            auto rootcomponent = actor->rootcomponent; // Получаем USceneComponent для текущего актора
            if ( !rootcomponent ) continue; // Проверяем USceneComponent на валид

            auto world_position = rootcomponent->relativelocation; // Получаем позицию актора в мире

            auto actor_name = engine::get_object_name( actor ); // Объявляем и вызываем функцию actor_name где будут хранится имена акторов
            if ( !actor_name.IsValid( ) ) continue; // Проверяем имя актора на валид

            Vector2 screen_position{}; // Инициализируем переменную позиции на экране
            if ( engine::world_to_screen( playercontroller, world_position, &screen_position ) ) // вызываем WorldToScreen ( 1 аргумент наш playercontroller, 2 аргумент позиция актора в мире, 3 аргумент позиция на экране в которую мы записываем значения
            {
                // Желательно держать WorldToScreen в if ( ) { } чтобы не было лишней нагрузки на наш пк. Пока актор находится в пределах видимости экрана w2s возвращает true, иначе вернёт false.
                // Соответственно рисовать мы будем только тогда, когда w2s будет true

                // Так как значение FString при вызове .c_str() отдаётся в виде wchar_t, нам надо записать его в char
                char buffer[ 2048 ];
                ImFormatString( buffer, IM_ARRAYSIZE( buffer ), "%ws", actor_name.c_str( ) );

                // Рисуем наших акторов
                ImGui::GetOverlayDrawList( )->AddText( ImVec2( screen_position.x, screen_position.y ), IM_COL32( 255, 255, 255, 255 ), buffer );
            }

        }
    }

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

Вложения

  • Далее путём клика пкм по появившемуся классу добавляем ему 2 раза 256 байт.png
    134,7 КБ · Просмотры: 13
Оффлайн

Ltpondohva

Местный
Участник
LV
2
 
31.01.2024
37
0
29
Награды
3
33

Репутация:

огонь, авторские курсы подоспели
 
Оффлайн

FatalBlow

Участник
LV
3
 
11.05.2024
12
0
26
Награды
3
38

Репутация:

спасибо за мануал, очень подробно и доступно!
 
B Оффлайн

begemot2010

Участник
LV
3
 
25.01.2024
9
0
24
Награды
3
27

Репутация:

всем привет, сори не по теме, не нашел ветку нужно, я новичок
скажите плиз есть норм софт для pubgm?
 
Оффлайн

wlux.net

Где волчьи уши, там волчьи зубы.
Команда форума
LV
7
 
20.06.2022
23 849
218
36
Награды
10
Пол
Муж.

Репутация:

  • Автор темы
  • Администратор
  • Модератор
  • Команда форума
  • #5
H Оффлайн

HKZ

Участник
LV
0
 
08.07.2024
2
0
5
29

Репутация:

отличный мануал, я просто в шоке что это за циферки буковки
 
А Оффлайн

Ауоки

Участник
LV
3
 
09.06.2023
30
0
30
Награды
4

Репутация:

Спасибо за подробность объяснения!
 
Оффлайн

sergeyxxre

Участник
LV
0
 
13.10.2024
2
0
7
22

Репутация:

привет, а как можно еще найти дампы? игра не со стим и античит там АСЕ, при попытке залезть в нее x64dbg'ом пишет мол хакерские инструменты, а если и получается то при выводе OEP пишет что-то типа not found, помоги плз
 

Поиск по форуму

Данный сайт использует cookie. Вы должны принять их для продолжения использования. Узнать больше....