Перейти к содержанию

Unreal Engine 4 Reversing


Рекомендуемые сообщения

Предисловие:

Я хотел сделать эту статью сообщением под статьей пользователя roma912, но потом решил вынести её отдельно, так как здесь мы будем описывать новую версию движка и более конкретно описывать те или иные действия. Использовать мы будем тот-же SDK GEN, что и в его статье, но здесь мы рассмотрим ситуацию, когда игр, нам нужных для разбора нет в пресетах (как чаще всего и бывает), и будем искать всеми нужные GNames и GObjects, ручками.

Для этого нам понадобятся инструменты. Из обязательных: GHIDRA/IDA, Cheat Engine, ReClass.
Опционально: исходный код Unreal Engine конкретной версии, как его получить, смотрите здесь --
https://github.com/EpicGames/Signup, также сам движок подходящий по версии с вашей игрой.

Что есть звери эти?

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

Важно понимать, что в Unreal Engine 4 используется в основном язык C++, так что этот движок образный «ООП на стероидах». В нем используется сложная система наследования, практически каждый тип данных заменен на свой, с добавлением приставки “U”, такие как «UField», «UObject», «UArray», именно поэтому в нем будет разобраться не просто, даже имея исходный код на руках.

Начнем с GObjects.
Что это такое? Судя по названию, можно предположить, что это глобальная переменная, в которой хранятся все объекты.  Это отчасти верно, однако даже поиск по исходному коду движка этого названия нам ничего не даст.
Нужно подумать, объектом какого класса может быть эта переменная.

Достаточно длительный разбор исходного кода движка приводит нас к следующей картине: в файле UObjectArray.h есть класс с названием «class COREUOBJECT_API FUObjectArray».
Звучит интересно. Смотрим в его приватные поля и видим

Спойлер

typedef FFixedUObjectArray TUObjectArray;

	// note these variables are left with the Obj prefix so they can be related to the historical GObj versions

	/** First index into objects array taken into account for GC.							*/
	int32 ObjFirstGCIndex;
	/** Index pointing to last object created in range disregarded for GC.					*/
	int32 ObjLastNonGCIndex;
	/** Maximum number of objects in the disregard for GC Pool */
	int32 MaxObjectsNotConsideredByGC;

	/** If true this is the intial load and we should load objects int the disregarded for GC range.	*/
	bool OpenForDisregardForGC;
	/** Array of all live objects.											*/
	TUObjectArray ObjObjects;
	/** Synchronization object for all live objects.											*/
	FCriticalSection ObjObjectsCritical;
	/** Available object indices.											*/
	TLockFreePointerListUnordered<int32, PLATFORM_CACHE_LINE_SIZE> ObjAvailableList;
#if UE_GC_TRACK_OBJ_AVAILABLE
	/** Available object index count.										*/
	FThreadSafeCounter ObjAvailableCount;
#endif
	/**
	 * Array of things to notify when a UObjectBase is created
	 */
	TArray<FUObjectCreateListener* > UObjectCreateListeners;
	/**
	 * Array of things to notify when a UObjectBase is destroyed
	 */
	TArray<FUObjectDeleteListener* > UObjectDeleteListeners;
#if THREADSAFE_UOBJECTS
	FCriticalSection UObjectDeleteListenersCritical;
#endif

	/** Current master serial number **/
	FThreadSafeCounter	MasterSerialNumber;

 

Интересующий нас объект называется ObjObjects. Теперь посмотрим его класс.
Объявление его класса находится в том же файле выше. Не забываем про typedef и ищем класс «FFixedUObjectArray». В его приватных полях находим 
Спойлер

/** Static master table to chunks of pointers **/
        FUObjectItem* Objects;
        /** Number of elements we currently have **/
        int32 MaxElements;
        /** Current number of UObject slots */
        int32 NumElements;

 

Это то, что нужно! В итоге логика выглядит так:
FUObjectArray.ObjObjects->Objects

В файле UObjectHash.cpp находим использование этого класса в играх

Спойлер

 


// Global UObject array instance
FUObjectArray GUObjectArray;

 

Как видно, это статическая переменная, содержащая в себе класс «массив», который в свою очередь хранит в себе указатель на массив всех объектов в игре. Такие дела.
Примечание: на многих источниках этот объект указывают как TArray<Object*>, что не совсем корректно для более поздних движков.

GNames.

Не буду долго распинаться, поэтому скажу в общих чертах:
GNames так же статичная переменная, используемая для хранения имен всех объектов в игре.
Имеет тип TNameEntryArray, что является typedef’ом от TStaticIndirectArrayThreadSafeRead<FNameEntry, 4 * 1024 * 1024, 16384>

Больший интерес представляет сам класс FNameEntry, который и является классом хранения имени. Выглядит он так:

Спойлер

class FNameEntry {

               public:

                       std::int32_t m_index = 0;

                       unsigned char _pad_0xC_0[0xC] = {};

 

                       union {

                               char m_ansi[1024];

                               wchar_t m_wide[1024];

                       };

 

Именно m_ansi нас и интересует.

Розыскные мероприятия.

Мы знаем, что переменные GUObjectArray и Names, статические, значит, для целей их розыска и поимки, мы можем использовать GHIRA/IDA. Дизассемблируем программу, ждем конца автоанализа.

Сейчас я расскажу, как действовать, если вы решили пойти по хитрому пути и скачать движок Unreal Engine версии, совпадающей с исследуемой игрой.
 Заходим в
EGS-> Unreal Engine -> Библиотека.
Потому нажимаем на + возле Версии
Unreal Engine

2138613011_.png.3c9f29120d9542f8aba1f45efeb39426.png

Выбираете версию движка, нажимаете установить.

ВАЖНЫЙ МОМЕНТ!

2067763915_.png.33c0aa6b426693513949d5ea2162d213.png

Заходите в настройки установки и отмечаете галочкой эту строку

362724655_.png.1efe423b7173fc1b8b7b69611ae4a0d4.png

Для того, чтобы потом можно было генерировать .pdb.

Когда скачали, запускайте и создавайте новый проект. Желательно выбирать жанр, который совпадает с вашей исследуемой игрой.

Ждем загрузки…

2086839758_.thumb.png.4263aed304967b675ab573ca09b55461.png
Далее кликаем File -> Package Project -> Build Configuration -> Shipping

 407138670_1.thumb.png.29210e95d46fb8fed9f12e9f42550c95.png

Далее File -> Package Project -> Packaging Settings

И отмечаем галочку «Include Debug Files»

330545848_2.thumb.png.9bd4f407877df5ffcba22b8ec3db1be9.png

1626954791_.thumb.png.61fee43a5c7289f625e5a9a099665578.png

И, время компилировать!
File -> Package Project -> Windows -> и выбираем разрядность игры, которую исследуем.

Зачем это нужно?

Открываем GHIDRA/IDA, пихаем туда нашу скомпилированную игру и грузим .pdb
ВНИМАНИЕ: В ГИДРЕ ОТКАЗЫВАЕМСЯ ОТ АВТОАНАЛИЗА И ЗАГРУЖАЕМ .PDB ДО НЕГО!

Теперь у нас есть листинг, где мы можем найти все подписанные функции движка. Выглядит это так:

759658866_1.thumb.png.f316669a179e6e12172fd724932dded3.png

Вот, собственно, и всё. Теперь можно просто по паттерну искать функции в нашей исследуемой игре.

Для поиска GNames нам понадобиться метод FName::GetNames()
Для GObjects GUObjectArray()

1513869002_2.thumb.png.0edc90afa6adf3fb9388ed3fd66f1fc4.png

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

Зачем ReClass?

К сожалению, мы с вами будем работать через SDK генератор, который обновлялся последний раз пять лет назад. А вот Epic Games обновляют движок игры примерно раз в полгода. Поэтому, нам нужно будет ручками реверсить классы в движке, и заносить результаты в отдельный файл. Так же никто не отменял, что некоторые компании изменяют движок под свои нужны и могут использовать кастомные типы данных. Поэтому, всё же, нужно будет знать объявление классов для того, чтобы SDK генератор работал правильно.
Брать эти объявления можно из исходников движка, а через ReClass подтверждать, что они указаны верно для конкретно вашей игры.

Послесловие.

Все сказанное выше актуально только для Unreal Engine версии ниже 4.22, так как в версиях выше для GNames используется класс FNameEntryPool, который работает совершенно по-другому.

Спасибо за чтение и удачи в исследованиях!

Изменено пользователем KRYPTOPUNK
  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.