Welcome back to another instalment of the ‘alternative approaches’ series! This time we’re revisiting one of the fundamentals of internal game hacking on the Source Engine: the CreateInterface function, or more specifically, the underlying list of version strings and pointers that you should be using instead.

In previous tutorials I’ve demonstrated how to get game classes by calling the exported CreateInterface function. It’s a fine method but you have to provide a full version string when calling it. Fair enough, these don’t change that often, but it’s still one more thing that’s inevitably going to stop working after a game update.

A quick look at the Source SDK 2013 code shows CreateInterface calling CreateInterfaceInternal. These functions are rather straightforward, simply looping through the s_pInterfaceRegs linked list (see class definition) and comparing the m_pName member variable against the version string provided by the user. If the interface is found, return the result of the m_CreateFn function, if not, return NULL.

Well, seems simple enough so let’s get straight into it. I’ll start off with Counter-Strike: Source, a 32-bit game. Pick any game library, open it in IDA, switch to the ‘Exports’ tab and double-click on CreateInterface.

CreateInterface disassembled and decompiled in engine.so. If you’re trying this on Windows, you’ll notice CreateInterfaceInternal is usually inlined here.

We’ll revisit this function in a moment. For now, jump to sub_5B3D70.

CreateInterfaceInternal disassembled and decompiled in engine.so. CreateInterfaceInternal with proper data types and variable names.

Looks like we’ve found the list already at 0xD0BE50! I’ll be using my Linux Basehook project for testing here. I’ve simply appended the following code to the bottom of the initialisation function, recompiled and loaded the library into the game.

Make sure to substitute the engine_address variable with the base address of engine.so in memory.

InterfaceReg* interface_list = *reinterpret_cast<InterfaceReg**>(engine_address + 0xD0BE50);

for (InterfaceReg* current = interface_list; current; current = current->m_pNext) {
  printf("%s => 0x%X\n", current->m_pName, current->m_CreateFn());
}
Excerpt of engine interface list in a Terminal window.

Right, now we can start thinking about a more permanent solution. You could always use pattern scanning, but of course, there’s another way. Interested? Of course you are. Let’s take another look at the CreateInterface function.

The address to s_pInterfaceRegs can be found in CreateInterfaceInternal. Unfortunately, we can’t get there directly since it’s not an exported symbol. On the other hand, CreateInterface is exported and we’ve already seen it doesn’t do anything but jump directly to CreateInterfaceInternal.

That means we can use dlsym to get to CreateInterface, find where it’s jumping to, go there ourselves and extract the address to s_pInterfaceRegs. Seems simple enough, doesn’t it? Let’s get started.

Here’s the disassembled CreateInterface again for reference:

.text:005B3E10 55                          push    ebp
.text:005B3E11 89 E5                       mov     ebp, esp
.text:005B3E13 5D                          pop     ebp
.text:005B3E14 E9 57 FF FF FF              jmp     CreateInterfaceInternal

The jump to CreateInterfaceInternal is 4 bytes into this function.

It’s a relative jump so we’ll need to read the displacement value after the E9 opcode.

uintptr_t jump_instruction_addr = uintptr_t(createinterface_symbol) + 4;
int32_t jump_displacement = *reinterpret_cast<int32_t*>(jump_instruction_addr + 1);

In this case, the displacement is relative to the next instruction. Since the complete jump instruction is 5 bytes long we need to ‘move’ the instruction pointer to 0x5B3E19, then add the displacement value to get our final address.

uintptr_t createinterfaceinternal_addr = (jump_instruction_addr + 5) + jump_displacement;

Alright, now we’ve got the absolute address to CreateInterfaceInternal. Let’s look at it one more time.

.text:005B3D70 55                          push    ebp
.text:005B3D71 89 E5                       mov     ebp, esp
.text:005B3D73 57                          push    edi
.text:005B3D74 56                          push    esi
.text:005B3D75 53                          push    ebx
.text:005B3D76 83 EC 1C                    sub     esp, 1Ch
.text:005B3D79 8B 1D 50 BE D0 00           mov     ebx, ds:s_pInterfaceRegs

Nice and easy. Simply read the address from the move instruction, cast to appropriate type and you’re done!

uintptr_t interface_list = *reinterpret_cast<uintptr_t*>(createinterfaceinternal_addr + 11);
InterfaceReg* interface_list = *reinterpret_cast<InterfaceReg**>(interface_list);

Now then, let’s move on to a 64-bit game – specifically Counter-Strike: Global Offensive.

s_pInterfaceRegs symbol in the Exports window of client_client.so. Valve, you’re too kind!

Oh, well isn’t that convenient? It certainly makes the implementation a lot shorter.

interfaceregs_symbol = dlsym(library_handle, "s_pInterfaceRegs");
InterfaceReg* interface_list = *reinterpret_cast<InterfaceReg**>(interfaceregs_symbol);

Some 32-bit games like Insurgency also export this symbol so it’s worth checking for. Here’s a link to a commit I recently pushed to my Linux Basehook project that includes this new implementation. The project is designed for Counter-Strike: Source but this new code has also been tested on Team Fortress 2, Garry’s Mod and Insurgency.

Last updated Friday, 17 January 2020 at 07:12 PM.