A few years ago, I became aware of a security issue in most Call of Duty games. Although I did not discover it myself, I thought it might be interesting to see what it could be used for.
Without going into detail, this security issue allows users playing a Call of Duty match to cause a buffer overflow on the host’s system inside a stack-allocated buffer within the game’s network handling. In consquence, this allows full remote code execution!
To use this vulnerability to exploit the game, a few things have to be taken into consideration. To exploit this vulnerability (or actually any vulnerability), you need to replicate the network protocol of the game. This turns out to be somewhat complex, so I decided not to rewrite this myself but to actually use the game as a base and to simply force it into sending malicious hand-crafted packets that exploit it.
And indeed, this method seems to work, but the problem is that you need to modify the game in order to send the packets. As Call of Duty has, just like any modern game these days, a not-so-bad anticheat mechanism (namely VAC), modifying it could result in myself getting banned from the game.
After a few other failed attempts of exploiting this vulnerability, I came up with something completely different: Why shouldn’t I use the game, without actually using the game?
The idea is still to take the game as base, but instead of hooking it, the underlying network transactions are analyzed to recreate the state of the game and to inject custom packets into the system’s network stack that look as if they were sent by the game. So you don’t modify the game itself, but rather control all the data it sends and receives. As this method doesn’t touch the game at all, it is not possible for current anti-cheat systems to detect this (it actually is possible, but I don’t think there is any anti-cheat that tries to detect that, yet).
I’m probably not the first one to come up with this idea, but I have never heard of something like this before (to be fair, I’m not very familiar with game hacking and cheating in general).
To realize this idea, I decided to go with the game Modern Warefare 2. Even though this game is pretty old, some people still play it. As this security issue exists in most Call of Duty games (even up to the latest title, World War II), I could have used any game as base, but every title released after MW2 has some kind of TLS layer underlying the network traffic which makes the analysis process much harder. Although it’s not impossible, I didn’t want to spend my time on reverse engineering it, as this should only be a proof-of-concept.
To capture the network traffic of the game, I decided to go with WinDivert. I could have used libpcap instead, but I wanted to use WinDivert, as it provides a small abstration to the network stack, because you don’t have to deal with the underlying ethernet protocol and chosing the right network interface with the correct MAC address.
As this step was done, it’s time to analyze the network traffic. The sad news is, even though I don’t have to understand the whole network protocol of the game, I still do have to replicate it partially, to be able to build custom packets. This took some time, but I finally managed to get it done, at least to a certain extent at which the game accepted my custom packets and decrypted them correctly. Looking at the Quake 3 network protocol helped a lot, as Call of Duty and Quake share a similar protocol due to using nearly the same engine.
Here is where the interesting part starts: Now that it is possible to inject packets into the game, we can start exploiting the vulnerability! With the help of Return-oriented programming it is actually possible to write code, by just having access to the stack. At first, it was necessary to rewind the stack to be able to use entire buffer to execute code, and not just the overflowing part. So to do that I had to execute shellcode that looks similar to this:
sub esp, ### retn
This piece of assembly language tells the system to rewind the stack by ### bytes. Where ### represents the length of the buffer we have access to plus the number of bytes we are overflowing.
To achieve that, it is required to write that code into the host’s system at a place that allows code execution.
Sadly, there is no space in memory with execution rights which is writable. At least there is no space I know of.
The way to go was to make the system call VirtualAlloc to allocate memory with enough rights to write and execute memory (essentially: PAGE_EXECUTE_READWRITE).
As the game itself uses VirtualAlloc to allocate memory, I looked up the address in the game’s Import Address Table.
Now to call that function, it is required to dereference the entry in the IAT and to pass all parameters accordingly, but most importantly, the return value has to be stored somewhere, as this is essentially the pointer to the memory we want to write our code to.
Using a set of ROP-Gadgets I discovered, I managed to get the game to dereference the address to the IAT in multiple steps and to call the function (which is VirtualAlloc).
Passing the parameters is easy, as we can simply push them onto the stack in reversed order (at least with x86, x64 would be a bit harder due to having a different calling convention).
Saving the return value requires to store it at a known address, so I took a pointer to some piece of data, that I was sure the game would not require anymore (I used the location at which the game stores a handle to the splash screen window, which only appears when the game starts).
Now that we have executable and writable memory, we simply have to write our shellcode to it. Note that we still have the target memory address in the eax register. Having that in mind, I found a few other interesting gadgets which could help:
pop ecx, retn
mov [eax], ecx retn
add eax, 3 retn
add eax, 1 retn
The first gadget allows to pop 4 bytes from the stack into the ecx register. The second one allows to copy the data from ecx into the memory to which eax points (eax still stores the address of the memory we allocated). As this only allows to write 4 bytes into memory, we have to increment eax by 4, to be able to write another 4 bytes. Unfortunately, I did not find a gadget that increments eax by 4, but luckily it can be increased by 3 in one and then 1 in another step (3 + 1 = 4 💪). Using that, we can devide the stack-rewinding shellcode into 4 byte blocks and write it into memory block by block.
Once this is done, it still has to be executed. To do that, we need to get our intial pointer back (which we luckily stored at the place where the game stored its splashscreen). Using
pop edx retn
we can tell the game to put the value of the splash screen address into edx, so a pointer to our memory pointer.
mov eax, [edx] retn
we can dereference it into eax which allows us to execute it using the last gadget:
No we have access to the whole buffer for executing code! Additionally, as I care about the host, I of course have to free the reserved memory again by calling VirtualFree the same way I called VirtualAlloc before.
To make my life easier, I turned this whole step of allocating memory, writing to it, executing it and freeing it, into a macro that allows to easily execute any kind of assembly code that I want.
Unfortunately, by overflowing the buffer and writing to the stack, we actually destroy the whole stack of the game, which forces it either to crash (if it executes arbitrary operations on the stack past our code), or to terminate gracefully if we tell it to do so.
Luckily, I discovered a way out! When starting a thread, every Call of Duty game stores the context of the current thread in the thread’s local storage area using setjmp. We can use that to restore the context to what it was before by loading it from the TLS and passing it to longjmp.
Now that we have full control, we can execute any code we want and restore the context to continue the execution of the game.
Using that we can for example draw hud elements, which is basically an element on the screen that every user you play with sees (e.g. text). We could also change the map or change the game configuration to make players run faster, or whatnot. A thing to note is that as we’re executing code in the process of the current match’s host, it is still possible that the host’s anti-cheat system detects this as a cheat. As we’re not doing anything in our process, but in someone else’s, we are perfectly safe. However, I did not modify any memory of the game that is protected or do anything that could let the anti-cheat system think that the host is cheating, so he should be safe as well.
With that said, we can do any kind of crazy stuff. Here you can see a demo of me changing the map, displaying the hud element You have been hacked on everyone’s screen and increasing the speed:
To sum it up, this tool allows to control the game by analyzing the network traffic and executing code on the host’s system using injected packets. It could also be used to harm the host by accessing his filesystem, or downloading an executable file and executing it or doing similar things on his computer.
To prevent others from using this tool to do this, it is not possible for me to publish the code. As the vulnerability has been patched, the code is available on GitHub. Other than that, I’m happy that it works, as I haven’t seen any kind of ‘hack’ like this before (although I’m pretty sure they exist).