RCE Endeavors 😅

July 5, 2022

Creating an Aimbot: Introduction (1/4)

Filed under: Game Hacking,Programming,Reverse Engineering — admin @ 9:20 PM

This series of posts will go over what it takes to build an aimbot – a program that automatically aims at enemies – for an FPS game. The game that we will be building the aimbot for is Half-Life 2, running on the Source engine. The aimbot will run from within the game’s process and use reverse engineered internal game functions to perform its functionality, as opposed to something that runs externally and scans the screen.

The core of the series will be broken down into two parts: first we will take a look at the Source SDK and use it as a guide to reverse engineer the Half-Life 2 executable, then we will use the knowledge that was gained by reversing to build the bot. By the end of the series, we will have written an aimbot that snaps the view angle to the nearest enemy.

A link to each post is provided below:

June 27, 2022

Vandelay: An Importer/Exporter for BigTable

Filed under: Programming — admin @ 1:05 PM

I’m proud to announce the release of Vandelay, an open source project that I’ve started to perform full backups of BigTable instance schemas and row data. Looking forward to continued development on it, and support for other database types in the future!

December 1, 2021

Reverse Engineering REST APIs: Conclusion (12/12)

Filed under: Game Hacking,Programming,Reverse Engineering — admin @ 9:16 AM

Table of Contents:

Reverse engineering the REST APIs that power the Age of Empires IV multiplayer lobby system is complete at this point.

Throughout the series, we took different approaches at achieving this goal. Initially, we started off using third-party tools that allowed us to set up a reverse proxy and route the game’s traffic through it. From this, we were able to see the decrypted request and response content. We were fortunate that the game did not use certificate pinning, otherwise this technique would have been rendered useless. The series then took a turn towards debugging the game and getting some basic information from the game’s strings dump. From this, it was found that the game used an OpenSSL feature that allowed the generated keys to be written out to a file specified by the SSLKEYLOGFILE environment variable. We were able to import the key information from this file and decrypt captured request and response data in Wireshark.

The series then got more technical as the game was reverse engineered. By attaching a debugger and setting breakpoints on the Winsock send and recv functions, we were able to walk the call stack backwards until we had a point in the code where we had access to the plaintext data. The functions responsible for encrypting and decrypting the data were reverse engineered and their prototypes were extracted. Using these prototypes, we were able to create and set our hook functions. From within these hook functions, we had direct access to the plaintext request and response data. In the example source code, we logged the data out to console, or a file, but we can do whatever we want with it.

Hopefully this series has been helpful in describing what goes in to reverse engineering a processes network communication. In the best case scenario, it can be a trivial task that can be accomplished with the use of third-party tools. In the more complex scenario, it is a very technical process that involves a deep dive into the code and a strong understanding of reverse engineering at the assembly level, dynamic analysis via a debugger, and an understanding of what to look for. I hope that readers of this series have learned something and walk away with a better understanding of what it takes to reverse engineer software.

Reverse Engineering REST APIs: Ingress – Monitoring (11/12)

Filed under: Game Hacking,Programming,Reverse Engineering — admin @ 9:13 AM

Table of Contents:

This post will cover the home stretch of the series: hooking the response decrypt function and showing the complete request-response flow. The technique to do this is the same as what was done to output the request data, so the code snippets here will be more brief. As before, we will start with a signature scan of the process memory for the response decrypt function.

void* FindDecryptPacketAddress(void* baseAddress)
{
    std::array<unsigned char, 39> signature = {
        0x48, 0x89, 0x5C, 0x24, 0x08,                        /* mov qword ptr ss:[rsp+8],rbx            */
        0x48, 0x89, 0x6C, 0x24, 0x10,                        /* mov qword ptr ss:[rsp+10],rbp           */
        0x48, 0x89, 0x74, 0x24, 0x18,                        /* mov qword ptr ss:[rsp+18],rsi           */
        0x57,                                                /* push rdi                                */
        0x48, 0x81, 0xEC, 0x20, 0x01, 0x00, 0x00,            /* sub rsp,120                             */
        0x48, 0x63, 0xC2,                                    /* movsxd rax,edx                          */
        0x49, 0x8B, 0xD9,                                    /* mov rbx,r9                              */
        0x49, 0x8B, 0xF8,                                    /* mov rdi,r8                              */
        0x48, 0x8B, 0xF1,                                    /* mov rsi,rcx                             */
        0x48, 0x8D, 0x2C, 0x40                               /* lea rbp,qword ptr ds:[rax+rax*2]        */
    };

    return PerformSignatureScan(baseAddress, signature);
}

Here we take the bytes that make up the instructions in the response decrypt function that was found in the previous post. We then scan the process memory for these instructions and return the address at which they were found. After finding this address, we place a hook on the function

__declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID reserved)
{
    static HookEngine hookEngine{};
    static HMODULE baseAddress{ GetModuleHandle(NULL) };
    static void* targetSendAddress{ FindSendPacketAddress(baseAddress) };
    static void* targetDecryptAddress{ FindDecryptPacketAddress(baseAddress) };

    if (dwReason == DLL_PROCESS_ATTACH) {
        // Some code omitted here ...
        (void)hookEngine.Hook(targetSendAddress, GameSendPacketHook);
        (void)hookEngine.Hook(targetDecryptAddress, GameDecryptPacketHook);
    }

    if (dwReason == DLL_PROCESS_DETACH) {
        (void)hookEngine.Unhook(targetSendAddress, GameSendPacketHook);
        (void)hookEngine.Unhook(targetDecryptAddress, GameDecryptPacketHook);
    }

    return TRUE;
}

We can define our hooks to simply output the request and response data

int WINAPI GameDecryptPacketHook(void* unknown, int alwaysZero, char* decryptBuffer,
	size_t decryptBufferMaxSize, char* errorFlag)
{
	auto original{ (GameDecryptPacketFnc)HookEngine::GetOriginalAddressFromHook(GameDecryptPacketHook) };
	int result{};
	if (original != nullptr) {
		result = original(unknown, alwaysZero, decryptBuffer, decryptBufferMaxSize, errorFlag);
	}

	while (result == -1) {
		std::cerr << "Decrypt failed... retrying..." << std::endl;
		result = original(unknown, alwaysZero, decryptBuffer, decryptBufferMaxSize, errorFlag);
	}

	auto output{ MakePrintableAscii(decryptBuffer, result) };
	for (const auto& line : output) {
		std::cerr << std::format("Decrypted Response: {}", line)
			<< std::endl;
	}

	return result;
}

int WINAPI GameSendPacketHook(void* unknown, SOCKET socket, const char* buffer, int length, int* sentSize)
{
	auto output{ MakePrintableAscii(buffer, length) };
	auto [ipAddress, port] { GetPeerInfo(socket) };
	for (const auto& line : output) {
		std::cerr << std::format("[{}:{}] - Data: {}", ipAddress, port, line)
			<< std::endl;
	}

	auto original{ (GameSendPacketFnc)HookEngine::GetOriginalAddressFromHook(GameSendPacketHook) };
	int result{};
	if (original != nullptr) {
		result = original(unknown, socket, buffer, length, sentSize);
	}

	return result;
}

Since we are hooking a function that calls SSL_read, we can add some additional logic to avoid hitting the error-handling code that we did not reverse engineer in the previous post. Per the documentation on SSL_read, we can retry the call if the function returns -1, hence the addition of the while loop in GameDecryptPacketHook.

Lets see this in action: launch Age of Empires IV and inject the DLL containing these hooks into the process. After the console is created, perform some actions in-game to cause a request to be sent out.

From this we can see that the hooks are working correctly: each request, and its corresponding response, is shown. As we did before, we can choose to do whatever we want to the data: log it, modify it, prevent it from reaching the caller, and so on. At this point we have fully achieved what we set out to do; the request and response data, which we saw as being encrypted when inspecting the network traffic, is now clearly visible. We have successfully reverse engineered the REST APIs that make the multiplayer lobby system function!

November 30, 2021

Reverse Engineering REST APIs: Ingress – Reversing the Response Decrypt Function (10/12)

Filed under: Game Hacking,Programming,Reverse Engineering — admin @ 9:05 AM

Table of Contents:

We last left off in the middle of a function where we had a pointer to where decrypted response data would be written in to. The plan was to take a closer look at this function and see what it does; if we can hook into it and manipulate the decrypted buffer then our mission of reverse engineering the request-response flow is complete. Fortunately, this function is pretty simple — the assembly listing has been reproduced below:

00007FF7BFB0FAC4 | 48:895C24 08                   | mov qword ptr ss:[rsp+8],rbx                      |
00007FF7BFB0FAC9 | 48:896C24 10                   | mov qword ptr ss:[rsp+10],rbp                     |
00007FF7BFB0FACE | 48:897424 18                   | mov qword ptr ss:[rsp+18],rsi                     |
00007FF7BFB0FAD3 | 57                             | push rdi                                          |
00007FF7BFB0FAD4 | 48:81EC 20010000               | sub rsp,120                                       |
00007FF7BFB0FADB | 48:63C2                        | movsxd rax,edx                                    |
00007FF7BFB0FADE | 49:8BD9                        | mov rbx,r9                                        |
00007FF7BFB0FAE1 | 49:8BF8                        | mov rdi,r8                                        |
00007FF7BFB0FAE4 | 48:8BF1                        | mov rsi,rcx                                       |
00007FF7BFB0FAE7 | 48:8D2C40                      | lea rbp,qword ptr ds:[rax+rax*2]                  |
00007FF7BFB0FAEB | E8 0093A501                    | call reliccardinal.7FF7C1568DF0                   |
00007FF7BFB0FAF0 | 48:8B8CEE E0020000             | mov rcx,qword ptr ds:[rsi+rbp*8+2E0]              |
00007FF7BFB0FAF8 | B8 FFFFFF7F                    | mov eax,7FFFFFFF                                  |
00007FF7BFB0FAFD | 48:3BD8                        | cmp rbx,rax                                       |
00007FF7BFB0FB00 | 48:8BD7                        | mov rdx,rdi                                       |
00007FF7BFB0FB03 | 0F47D8                         | cmova ebx,eax                                     |
00007FF7BFB0FB06 | 48:8B49 08                     | mov rcx,qword ptr ds:[rcx+8]                      |
00007FF7BFB0FB0A | 44:8BC3                        | mov r8d,ebx                                       |
00007FF7BFB0FB0D | E8 1E9DB801                    | call reliccardinal.7FF7C1699830                   |
00007FF7BFB0FB12 | 48:63F8                        | movsxd rdi,eax                                    |
00007FF7BFB0FB15 | 85C0                           | test eax,eax                                      |
00007FF7BFB0FB17 | 7E 1C                          | jle reliccardinal.7FF7BFB0FB35                    |
00007FF7BFB0FB19 | 48:8BC7                        | mov rax,rdi                                       |
00007FF7BFB0FB1C | 4C:8D9C24 20010000             | lea r11,qword ptr ss:[rsp+120]                    |
00007FF7BFB0FB24 | 49:8B5B 10                     | mov rbx,qword ptr ds:[r11+10]                     |
00007FF7BFB0FB28 | 49:8B6B 18                     | mov rbp,qword ptr ds:[r11+18]                     |
00007FF7BFB0FB2C | 49:8B73 20                     | mov rsi,qword ptr ds:[r11+20]                     |
00007FF7BFB0FB30 | 49:8BE3                        | mov rsp,r11                                       |
00007FF7BFB0FB33 | 5F                             | pop rdi                                           |
00007FF7BFB0FB34 | C3                             | ret                                               |

We can begin by setting a breakpoint at the top of this function and stepping. After doing this, we can see that the buffer gets decrypted after the call to reliccardinal.7FF7C1699830 is executed, as shown below:

00007FF7BFB0FB0D | E8 1E9DB801                    | call reliccardinal.7FF7C1699830                   |
00007FF7BFB0FB12 | 48:63F8                        | movsxd rdi,eax                                    | rdi:"HTTP/1.1 200 OK\r\nDate: Wed, 24 Nov 2021 14:24:03 GMT\r\nContent-Type: application/json;charset=utf-8\r\nTransfer-Encoding: chunked\r\nConnection: keep-alive\r\nRequest-Context: appId=X\r\nCache-Control: no-store, no-cache, must-revalidate, max-age=0\r\nRequest-Path: /game/advertisement/findAdvertisements\r\n\r\n67ed\r\n[0,[[10132504,0,\"{\\\"templateName\\\":\\\"GameSession\\\",\\\"name\\\":\\\"X\\\",\\\"scid\\\":\\\"0000

If the result of this call is less than or equal to zero, then we jump to reliccardinal.7FF7BFB0FB35. If we look around the instructions at this address, we notice some debug strings mentioning OpenSSL:

00007FF65657FBF4 | FF15 26E9AA05                  | call qword ptr ds:[<&WSAGetLastError>]            |
00007FF65657FBFA | 48:8B0E                        | mov rcx,qword ptr ds:[rsi]                        |
00007FF65657FBFD | 48:8D15 9CD0B102               | lea rdx,qword ptr ds:[7FF65909CCA0]               | 00007FF65909CCA0:"OpenSSL SSL_read: %s, errno %d"
00007FF65657FC04 | 44:8BC8                        | mov r9d,eax                                       |
00007FF65657FC07 | 4C:8BC3                        | mov r8,rbx                                        |
00007FF65657FC0A | E8 B1C20502                    | call reliccardinal.7FF6585DBEC0                   |

This logic is very similar to what we saw when we were encrypting the request data, though now we are on the read path instead of the write path. Whereas before we had a debug string referencing SSL_write, now we have one referencing SSL_read. Based on stepping through the code, the documentation of SSL_read, and the debug strings, we can make a reasonable assumption that reliccardinal.7FF7C1699830 is SSL_read. As in the request decryption code, [RCX+0x8] is passed as the first parameter, followed by a pointer to the decrypt buffer, and the buffer size. From the previous reverse engineering session regarding the request encryption flow, we found that [RCX+0x8] holds the SSL opaque pointer.

This function that we are looking at seems like a reasonable place to hook. The logic here is very similar to the encryption function that was hooked earlier in this series, and we can verify that this function only gets called when we have made a REST request. The last thing that we need to do is to define the function prototype. We can derive most of the prototype based on what we’ve seen already. To start with, we see four arguments to this function being moved to different registers at the top


00007FF6541AFADB | 48:63C2                        | movsxd rax,edx                                    |
00007FF6541AFADE | 49:8BD9                        | mov rbx,r9                                        |
00007FF6541AFAE1 | 49:8BF8                        | mov rdi,r8                                        |
00007FF6541AFAE4 | 48:8BF1                        | mov rsi,rcx                                       |

We can see these registers being passed a little lower to the call to SSL_read:

00007FF6541AFB00 | 48:8BD7                        | mov rdx,rdi                                       |
00007FF6541AFB03 | 0F47D8                         | cmova ebx,eax                                     |
00007FF6541AFB06 | 48:8B49 08                     | mov rcx,qword ptr ds:[rcx+8]                      |
00007FF6541AFB0A | 44:8BC3                        | mov r8d,ebx                                       |
00007FF6541AFB0D | E8 1E9DB801                    | call reliccardinal.7FF655D39830                   |

From this, we can deduce that RCX holds the structure that wraps the SSL and HTTP request info logic, R8 holds the pointer to the decrypt buffer, and R9 holds the size of the decrypt buffer. This might be unclear from the partial assembly listings, but if you start with the call to SSL_read and work backwards from where the arguments come from, it becomes more clearly visible. This gets us to identifying three arguments, but there are more.

At the top, we move the second argument into RAX. This argument is then used in two places:

00007FF6541AFAE7 | 48:8D2C40                      | lea rbp,qword ptr ds:[rax+rax*2]                  |
00007FF6541AFAEB | E8 0093A501                    | call reliccardinal.7FF655C08DF0                   |
00007FF6541AFAF0 | 48:8B8CEE E0020000             | mov rcx,qword ptr ds:[rsi+rbp*8+2E0]              |
...
00007FF6541AFB35 | 48:8B8CEE E0020000             | mov rcx,qword ptr ds:[rsi+rbp*8+2E0]              |
...

It is used as an index into the structure passed in the first parameter. From debugging, we notice that its value is always zero, and we can just leave this argument alone since we aren’t interested in reverse engineering the SSL/HTTP request wrapper internals.

The last argument is an out parameter. If we look through the path where SSL_read returns less than or equal to 0, we can see it being set:

00007FF6541AFB64 | 48:8B8424 50010000             | mov rax,qword ptr ss:[rsp+150]                    |
00007FF6541AFB6C | C700 51000000                  | mov dword ptr ds:[rax],51                         | 51:'Q'
...
00007FF65657FC0F | 48:8B8424 50010000             | mov rax,qword ptr ss:[rsp+150]                    |
00007FF65657FC17 | C700 38000000                  | mov dword ptr ds:[rax],38                         | 38:'8'

Given that this is on the error path, we will also choose to ignore it. Since we are placing a hook, we can just forward the result of this back to the caller without having to worry about performing any modifications on it. At this point we have all of the arguments to the function. We can define the prototype as

using GameDecryptPacketFnc = int (WINAPI*)(void* unknown, int alwaysZero, char* decryptBuffer, size_t decryptBufferSize, char* errorFlag);

We are now done reverse engineering the decryption routine. We’ve found that it is a suitable function to hook in order to get at the decrypted response buffer. From within our hook, we can choose to inspect or modify this buffer before returning it to the caller. The next post will quickly cover how to do this, and wrap up the series.

« Newer PostsOlder Posts »

Powered by WordPress