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

Unreal Engine 3 Reversing with SDK gen.


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

Итак, начнем с самого начала
Когда-то очень давно я начал играть в игру под названием Rocket League
Импровизированая игра в футбол, где катаются машинки, со своей физикой и пр.
Проиграв около года, все надоело и я решил посмотреть что есть у этой игры: 
Как оказалось апи для какой-либо статистики открытого нет (Имею ввиду то, которое поддерживается официальными разработчиками)
Ну раз нет апи, решил сделать что-то элементарное, например найти структуру игррка (в данном случае машинки)
На данный момент у игры нет античита, хотя он тут и не нужен вовсе

Вообщем загорелся идеей, начал пытаться найти эту структуру
Долгое время ковырялся, ковырялся, ничего не получалось
То ли дело во мне, то ли грабли не едут
Посмотрел движок игры, оказалось Unreal Engine 3
На UE3 вроде как бесполезно что-то искать руками, т.к поинтер будет с громадным количеством оффсетов и почти наверняка невалидным
Посмотрел как реверсят игрушки на UE, и что-то не въехал с первого раза (даже с 5 не понял :DD)

А суть была в том, что в Unreal Engine 3 можно хукнуть метод который передает все внутреигровые ф-ии которые использует в режиме реального времени
Прочитал много статеек, посмотрел примеры и понял что хукают метод ProcessEvent
Делают это с помощью дампа игровых классов, которые можно использовать для написания своего чита

Собственно вот и дампер Тык
Там уже есть заготовки для некоторые UE3, UE4 игр
Нужная нам также имеется, осталось только сдампить

Для дампа необходимо знать сигнатурки 
ProcessEvent
GlobalNames
GlobalObjects
Собственно вот и скомпилированный дампер
pFOQ0HS.png

Сам дампер это длл которая инжектится в игру, ну а сама длл и вытягивает все нужные классы для дальнейшего написания


Теперь к написанию...

Создаем проект, подключаем к проекту sdk которое сгенерировалось на диске С
Подключаем все нужные инклуды и пр.

Точка входа для нашей длл
 

BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD ul_reason_for_call,
	LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		AllocConsole();
		freopen("output.log", "w", stdout);
		printf("Dll Initialized\n");
		DisableThreadLibraryCalls(hModule);
		CreateThread(0, 0, MainThread, hModule, 0, 0); // creates our thread 
		break;

	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

 

В длл создаем поток который будет хукать ProcessEvent и использовать внутреигровые функции
 

DWORD WINAPI MainThread(LPVOID param) // our main thread
{
	StartupApp();
	printf("Inited\n");
	ProcessEvent = (tProcessEvent)DetourFunction((BYTE*)ProcessEvent, (BYTE*)hkProcessEvent);
	return false;
}


StartupApp метод который по сигнатуркам ищет адрес GNames, GObjects
 

bool InitializeObjectsStore() {
	auto address = FindPattern(GetModuleHandleW(nullptr), reinterpret_cast<const unsigned char*>(Signture), Mask);
	if (address == -1) { return false; }
	UObject::GObjects = reinterpret_cast<decltype(UObject::GObjects)>(*reinterpret_cast<uint32_t*>(address + 1));
	return UObject::GObjects != nullptr;
}

bool InitializeNamesStore() {
	auto address = FindPattern(GetModuleHandleW(nullptr), reinterpret_cast<const unsigned char*>(Signture), Mask);
	if (address == -1) { return false; }
	FName::GNames = reinterpret_cast<decltype(FName::GNames)>(*reinterpret_cast<uint32_t*>(address + 2));
	return FName::GNames != nullptr;
}


ProcessEvent это наш хук который мы будем использовать (Как происходит хук гуглим, detours lib в помощь)

Теперь к основной части, наш метод hProcessEvent
Не забываем указывать Typedef 

void __fastcall hkProcessEvent(UObject *pObject, void *edx, UFunction *pFunction, void *pParms, void *pResult);
typedef void(__thiscall *tProcessEvent)(class UObject *, class UFunction *, void *, void *);
tProcessEvent ProcessEvent = (tProcessEvent)FindPE();


 

void __fastcall hkProcessEvent(UObject *pObject, void *edx, UFunction *pFunction, void *pParms, void *pResult)
{
	if (pFunction)
	{
		//Get Player*
		if (strcmp(pFunction->GetFullName().c_str(), "Function Engine.PlayerController.PlayerTick") == 0)
			pPlayerController = (APlayerController*)pObject;

		//Get Ball*
		if (strcmp(pFunction->GetFullName().c_str(), "Function TAGame.Ball_TA.OnRigidBodyCollision") == 0)
			pBall = (ABall_TA*)pObject;

		//Function TAGame.CarComponent_Boost_TA.EventBoostAmountChanged
		if (strcmp(pFunction->GetFullName().c_str(), "Function TAGame.CarComponent_Boost_TA.EventBoostAmountChanged") == 0)
		{
			Boost = ((ACarComponent_Boost_TA_EventBoostAmountChanged_Params*)(pParms))->Boost;
		}


		if (strcmp(pFunction->GetFullName().c_str(), "Function Engine.Interaction.PostRender") == 0)
		{
			Canvas = ((UInteraction_PostRender_Params*)(pParms))->Canvas;
			myPostRender(((UInteraction_PostRender_Params*)(pParms))->Canvas, pPlayerController, Boost);
		}



	}
	ProcessEvent(pObject, pFunction, pParms, pResult);
}


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

В данном случае я получаю Указатели на Игрока, Мяч, UCanvas (Который рисует все что возможно в игре) и эвент EventBoostAmountChanged,
который срабатывает когда кто-то использует на карте буст

 Перейдем к методу myPostRender который при срабатывании рисует в игре количество буста у игрока и также линию от игрока к мячику

 

void myPostRender(UCanvas* Canvas, APlayerController* player, ACarComponent_Boost_TA* boost)
{

	//Проверка на валидность входных данных
	if (!Canvas || !player || player->bDeleteMe || !boost || Boost->bDeleteMe || boost == NULL)
		return;


	try
	{
		if (boost)
		{
			
			std::map<std::string, ACarComponent_Boost_TA*>::iterator it;
			//Есть ли игрок уже в map
			if (BoostMap.count(boost->Owner->GetFullName()) > 0)
			{
				//Обновляем игрока в map
				it = BoostMap.find(boost->Owner->GetFullName());
				BoostMap.erase(it);
				BoostMap.insert(std::pair<std::string, ACarComponent_Boost_TA*>(boost->Owner->GetFullName(), boost));
			}
			else
			{
				BoostMap.insert(std::pair<std::string, ACarComponent_Boost_TA*>(boost->Owner->GetFullName(), boost));
			}
		}

		std::map<std::string, ACarComponent_Boost_TA*>::iterator i;
		for (i = BoostMap.begin(); i != BoostMap.end(); ++i)
		{
			//Опять проверяем на валидность 
			if (!i->second->Owner || i->second->Owner->bDeleteMe || !i->second || i->second->bDeleteMe || i->second == NULL || i->second->Owner == NULL || !Canvas)
			{
				//Удаляем если не валиден
				BoostMap.erase(i);
				continue;
			}
			
			//Переводим 3D Координаты игрока через Видовую матрицу Canvas->Project в 2D
			FVector PlayerLoc = Canvas->Project(i->second->Owner->Location);


			if (i->second->Owner->GetTeamNum() == 0) //Blue Team
			{
				//Проверка на то, видем ли мы на экране точку в который нужно рисовать
				if (PlayerLoc.Z > 0)
					DrawTextToScreen(Canvas, string("Boost Amount: ") + std::to_string((int)(i->second->CurrentBoostAmount * 100)), PlayerLoc.X + 10, PlayerLoc.Y - 30, BLUE);
				
			}
			else {							//Orange Team
				if (PlayerLoc.Z > 0)
					DrawTextToScreen(Canvas, string("Boost Amount: ") + std::to_string((int)(i->second->CurrentBoostAmount * 100)), PlayerLoc.X + 10, PlayerLoc.Y - 30, ORANGE);
				
			}

		}
	}
	catch(std::exception& e)
	{
		printf("Exception %s", e.what());
	}
}


Итог: Разобраться в этом сразу конечно очень сложно, но методом проб и ошибок у вас должно получиться

Ах, да, забыл показать конечный результат 
Вот...
Отрисовку линий я убрал в коде, но там нет ничего сложного
Теперь мы видим количество буста у тиммейтов и противников, что вполне неплохо
Также можно придумать множество реализаций SDK

GPoSrT4.jpg

  • Понравилось 3
  • Плюс 3
Ссылка на комментарий
Поделиться на другие сайты

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

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

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