Scripting Engines inside Games? You can hack them too! (AngelScript)

Introduction to Games and Scripting

Multiple games delegate many portions of the game into a scripting engine: sounds, effects, animations, objects properties, events, etc. There are a lot of alternatives when we talk about scripting, but the most popular are Lua, Squirrel, and AngelScript. The idea of this post won’t be to cheat a game but to learn how scripting works and how we could reverse scripting libraries to manipulate the normal behavior of the game and run our own scripts in the game context.

We are going to choose a popular Engine Game, analyze how it works, and then we are going to reverse the engine in order to run our custom scripts in the game context, being able to manipulate what happens inside the game.

TL;DR

Today we are going to see AngelScript. We are going to analyze HPL Engine from Frictional Games and see how they do to run their scripts on the game context. After that, we are going to hook the primary function to run AngelScript and retrieve key object reference that we need to later run our own scripts.

HPL Engine

As the official website says HPL is a cross-platform 3D game engine used mostly by Frictional Games, written in C++ and with three versions released so far. Frictional Games have multiple games: Amnesia and Penumbra are two of the most well known.  Both of them have multiple releases that tell different histories, but what they make them interesting is that they all use the same game engine 🙂

As I said there are three versions so far. Frictional Games decided to release the source code of the first version, which is awesome and I love this idea. This will make reversing quite easier to be honest. Even when we are not using the same version, some portions of the “core” may remain similar or equal and will help us to understand how it works.

If you read the official overview you will notice something really interesting:

They have a sort of Scripting on scenario objects and events. Awesome!! This is what we are looking for, some scripting that we could reverse, and use it to modify the normal behavior of the game and take advantage of this.

Oh, before I forget, yep, the engine name comes from the popular writer Howard Phillips Lovecraft. A genius.

Analyzing the public source code

Based on the fact that we want to learn how the external scripting is integrated with the game, I started with some search on their github. After a while, by using the word “script” I found what I wanted:

cSqScript::cSqScript(const tString& asName,asIScriptEngine *apScriptEngine,
							cScriptOutput *apScriptOutput, int alHandle)
							: iScript(asName)
							{
								mpScriptEngine = apScriptEngine;
								mpScriptOutput = apScriptOutput;
								mlHandle = alHandle;

								mpContext = mpScriptEngine->CreateContext();

								//Create a unique module name
								msModuleName = "Module_"+cString::ToString(cMath::RandRectl(0,1000000))+
									"_"+cString::ToString(mlHandle);
							}
//-----------------------------------------------------------------------
bool cSqScript::Run(const tString& asFuncLine)
{
	mpScriptEngine->ExecuteString(msModuleName.c_str(), asFuncLine.c_str());
	return true;
}
//-----------------------------------------------------------------------
bool cSqScript::Run(int alHandle)
{
	mpContext->Prepare(alHandle);
	/* Set all the args here */
	mpContext->Execute();
	return true;
}
https://github.com/FrictionalGames/HPL1Engine/blob/5e441bbb247a7473e75cc0f05ca8a5b62c6ec64c/sources/impl/SqScript.cpp

I have omitted some portions of the code to keep it simple. But we can see here that cSqSCript is what we are looking for. The first method cSqScript is its constructor. It receives two references to variables of an unknown type for us: asIScriptEngine and cScriptOutput.

A quick search in Google shows us that both types belong to AngelScript: asIScriptEngine. Perfect, we are even closer. We have a portion of code that may be managing the script engine as the type variable says, and we also have the documentation of AngelScript which list us all the properties and method of this class.

Let’s analyze how this engine works. The first thing we notice is that in the constructor, it takes the engine and it creates a context for the scripts to run, which it gets stored on mpContext.

mpScriptEngine = apScriptEngine;
mpContext = mpScriptEngine->CreateContext();

If we look at the second Run method, the one that receives an int handle as a parameter. We will see that it uses this mpContext together with the handle to “Prepare” and “Execute” the script. But, what results more interesting is that we have another Run method that receives a string and directly “execute” this string by calling a method called ExecuteString:

mpScriptEngine->ExecuteString(msModuleName.c_str(), asFuncLine.c_str());

I think that with all this information we can grab any of the games and start looking for similar behaviors inside the binaries and dll of the game.

Analyzing statically the binary files

I decided to choose Amnesia: A Machine for Pigs because I played this game before and I knew part of the story. This particular game makes use of HPL2. When you run the game you will notice that the main binary is aamfp.exe.

Let’s open it with IDA and try to find something related to the script engine we want to hack. After analyzing the binary for a while and searching for functions and strings I came across the following function:

I started to analyze this function trying to guess why the heck they have this weird string there. Clearly, it is a declaration of a function. Perhaps they need to create some sort of function syntax in order to execute code on the engine, and they are creating a function in run time which is being parsed by the engine?

Lucky but late, I found out that this ExecuteString is a helper provided by AngelScript developers here. It would have been really helpful to find it before completely reversing the function, but hey! Better late than never 🙂

This is the result of reversing the function and then validating with some similar code found in the documentation:

int __cdecl fn_ExecuteString(int *engine, void *code, int *mod, int *ctx)
{
  int *execMod; // eax@2
  void *v5; // ecx@4
  int r; // esi@6
  int result; // eax@9
  int *execCtx; // esi@11
  int v9; // eax@18
  int v10; // edx@18
  int func; // [sp+14h] [bp-34h]@4
  int *pEngine; // [sp+18h] [bp-30h]@1
  void *funcCode; // [sp+1Ch] [bp-2Ch]@1
  int v14; // [sp+2Ch] [bp-1Ch]@1
  unsigned int v15; // [sp+30h] [bp-18h]@1
  int v16; // [sp+44h] [bp-4h]@1

  pEngine = engine;
  v15 = 15;
  v14 = 0;
  LOBYTE(funcCode) = 0;
  sub_404910((int)&funcCode, "void ExecuteString() {\n", 0x17u);
  v16 = 0;
  sub_40B070((int)&funcCode, code, strlen((const char *)code));
  sub_40B070((int)&funcCode, "\n;}", 3u);
  if ( mod )
    execMod = mod;
  else
    execMod = (int *)(*(int (__stdcall **)(_DWORD, signed int))(*pEngine + 192))("ExecuteString", 2);// asIScriptModule *execMod = mod ? mod : engine->GetModule("ExecuteString", asGM_ALWAYS_CREATE);
                                                // 
  v5 = funcCode;
  func = 0;
  if ( v15 < 0x10 )
    v5 = &funcCode;
  r = (*(int (__thiscall **)(int *, _DWORD, void *, signed int, _DWORD, int *))(*execMod + 20))(// int r = execMod->CompileFunction("ExecuteString", funcCode.c_str(), -1, 0, &func);
        execMod,
        "ExecuteString",
        v5,
        -1,
        0,
        &func);
  if ( r >= 0 )
  {
    if ( ctx )
      execCtx = ctx;
    else
      execCtx = (int *)(*(int (**)(void))(*pEngine + 220))();// asIScriptContext *execCtx = ctx ? ctx : engine->CreateContext();
    pEngine = (int *)(*(int (__thiscall **)(int *, int))(*execCtx + 12))(execCtx, func);// r = execCtx->Prepare(func->GetId());
    if ( (signed int)pEngine >= 0 )             // if( r < 0 )
    {
      v9 = (*(int (__thiscall **)(int *))(*execCtx + 20))(execCtx);
      v10 = *(_DWORD *)func;
      pEngine = (int *)v9;
      (*(void (**)(void))(v10 + 8))();
      if ( !ctx )
        (*(void (__thiscall **)(int *))(*execCtx + 4))(execCtx);
      if ( v15 >= 0x10 )
        operator delete(funcCode);
    }
    else
    {
      (*(void (**)(void))(*(_DWORD *)func + 8))();// func->Release();
      if ( !ctx )
        (*(void (__thiscall **)(int *))(*execCtx + 4))(execCtx);// execCtx->Release();
      if ( v15 >= 0x10 )
        operator delete(funcCode);
    }
    result = (int)pEngine;
  }
  else
  {
    if ( v15 >= 0x10 )
      operator delete(funcCode);
    result = r;
  }
  return result;
}

What they are doing is to craft a string which contains the code to be executed and then, they compile this function and create a context if it doesn’t already exist one. After that, they just execute this just compiled code into the context of the game.

Now that we have identified our function, and we understand what it does, we need to hook it. If we can hook ExecuteString and store a copy of the arguments we will be able to control the engine.

Hooking ExecuteString

If we are able to hook ExecuteString, we can get a valid reference of asIScriptEngine. The main idea is the following:

  1. Use typedef to define ExecuteString in our code
  2. Create a new ExecuteString that we will call ExecuteStringHook, which will store the pointer to the engine.
  3. Find the original address of ExecuteString
  4. Detour the function so it points to ours
  5. Once we got the pointer to the engine we can call ExecuteString sending the reference to the engine and our own code.

Define ExecuteString using typedef

If we pay attention to IDA, it will tell us what calling convention it is using and of course, we can manually confirm it. IDA identified that __cdecl is being used:

the code is heavily commented because I reversed part of the it

We can see two characteristic things about __cdecl calling convention: arguments are being pushed into the stack from right to left: and the caller is poping/clearing the arguments from the stack with add esp,10h.

 

Based on what we have just seen, defining the function in our code is quite simple:

typedef int(__cdecl* ExecuteString)(int *, const char *, int *, int *);
ExecuteString fn_origExecuteString = NULL;

Pay careful attention so everything matches: return value type, arguments, etc.

Our new ExecuteStringHook

bool bExecuteString = false;
int *g_engine = NULL;
char *g_code = NULL;
int *g_mod = NULL;
int *g_ctx = NULL;

int __cdecl  ExecuteStringHook(int *engine, const char *code, int *mod, int *ctx)
{
	const char &c = *code;
	if (!bExecuteString)
	{
		g_engine = engine;
		g_mod = mod;
		g_ctx = ctx;
		std::cout << "[+] engine Addr: " << std::hex << engine << std::endl;
		std::cout << "[+] code Addr: " << &c << std::endl;
		std::cout << "[+] mod Addr: " << std::hex << mod << std::endl;
		std::cout << "[+] ctx Addr: " << std::hex << ctx << std::endl;
		bExecuteString = true;
	}
	std::cout << "[+] code Addr: " << &c << std::endl;
	return fn_origExecuteString(engine, code, mod, ctx);
}

We have done this before, we need to define again a function, in this case, with the same calling convention. I said “in this case” because depending on the calling convention used, we may have to do so nasty things to properly hook the function without losing any argument and without breaking the stack.

We have a boolean variable to make sure that we initialize each variable only once and then one variable for each argument. After our function is called we will store each argument so we can make use of them later.

Find the original address of ExecuteString

Easy Peasy guys! We grab the base address of aamfp.exe and then we add the offset to the original ExecuteString:

void GetExecuteString()
{
	//DWORD_PTR hDxgi = (DWORD_PTR)GetModuleHandle("aamfp.exe");
	DWORD_PTR hDxgi = (DWORD_PTR)GetModuleHandle(NULL);
	std::cout << "[+] Base Addr: " << std::hex << hDxgi << std::endl;
	#if defined(ENV64BIT)
		std::cout << "[-] ExecuteString x64 not implemented."
	#elif defined (ENV32BIT)
		std::cout << "[+] ExecuteString Addr: " << std::hex << hDxgi + 0x2025f0 << std::endl;
		//Pig Machine
		fn_origExecuteString = (ExecuteString)((DWORD_PTR)hDxgi + 0x1FA8A0);
		//Descent
		//fn_origExecuteString = (ExecuteString)((DWORD_PTR)hDxgi + 0x2025f0);
	#endif
	std::cout << "[+] ExecuteString Addr: " << std::hex << fn_origExecuteString << std::endl;
}

Doing GetModuleHandle(NULL) or GetModuleHandle("aamfp.exe") will have the same result and both are valid. The game is 32 bits so we don’t need to calculate either obtain its offset, but I like to be cautious and I add it just in case, for the future. 0x2025f0 is the offset of ExecuteString inside of the binary. Finally, we just store the address on fn_origExecuteString, which we previously defined.

Detour the function so it points to ours

We have done this several times, here we go:

void detourExecuteString()
{
	std::cout << "[+] Calling ExecuteString Detour" << std::endl;
	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread());
	// Detours the original fn_origExecuteString with our ExecuteStringHook
	DetourAttach(&(LPVOID&)fn_origExecuteString, (PBYTE)ExecuteStringHook);
	DetourTransactionCommit();
}

Executing our own scripts

const char  new_code[] = "TeleportPlayer('IntroStart_4');";
fn_origExecuteString(g_engine, new_code, g_mod, g_ctx);

In this case, we are using an already existing function I found in the game to teleport the player back to some position. Yep teleport it! 😀 Awesome, this kind of things sometimes are harder to implement if you have to manually analyze the memory, and reverse the code in order to manipulate your current position.

How did I find this teleport function?

That was quite easy, each map is an HPS file, which contains all the AngelScript functions that the game uses to create all the map scenes. Not always will be like this, but if you have already hooked the method that the game uses to execute AngelScript code, you will able to record and display these functions so you can search them inside the game files later.

In the last screenshot, you can see all the calls to ExecuteScript made by the game.

Is it possible to evolve this code to hook any game?

I gave it a try and analyzed commons ways to find a generic hook for AngelScript as we have seen with DirectX, however, AngelScript does not provide .lib or .dll files and each game include this external library in a different way. It is possible to download the SDK here, where the developers state that the library is not distributed as a binary.

There are multiple ways to include AngelScript in your project [1]: Include the source inside your project; compile a static library and link into the project; compile a Dynamically loaded library with an import library; or  load the dll manually. This possibility to compile, configure and customize AngelScript is what makes harder to find a universal hook for the library.

I’m still trying different methods to achieve this, but none of them seems to work. If you have a better approach I would love to know about it 😉

Conclusion

We cansay that having a valid pointer to the engine is the main goal when hooking AngelScript. However, how we get this reference will depend on the game and how it is integrated with AngelScript. Anyways, I assume that most of the games will implement something similar to what we have seen today in this post. With our reference to the engine, we can do a lot of things. Some interesting examples I found inside the maps are:

TeleportPlayer("temple_intro_1");
SetPlayerActive(false);
ShowPlayerCrossHairIcons(false);
SetPlayerJumpDisabled(true);
SetPlayerCrouching(true);
SetPlayerCrouchDisabled(true);
TeleportPlayer("MainStart");
SetPlayerActive(true);
SetPlayerMoveSpeedMul(0);
SetPlayerLookSpeedMul(0);
SetPlayerRunSpeedMul(0.4f);
TeleportPlayer("BedStart");

Of course, we could create our own code and execute our own instructions.