Tony Hawk’s Pro Strcpy
Back in 2016 I really wanted to improve my exploit development skills and find some new bugs to hack the original Xbox. For many years people could download a hacked game save for games like 007 Agent Under Fire, Splinter Cell, or Mechassault and use it to hack their console. These game save hacks worked by exploiting trivial buffer overflows that would give the attacker code execution on the console and run unsigned code (code not authorized by Microsoft). From there you’d typically install some hacked OS files that would allow your console to run homebrew and pirated games. Being into computer security I knew all the academics of how exploit techniques like memory corruption and ROP worked but had no experience actually writing an exploit that used any of these techniques. I wasn’t going to try and write an exploit for a modern game console or PC without getting some elementary level experience under my belt first, which is why I turned to older gaming consoles.
Devices from the early-mid 2000s (or y2k devices as I like to call them) are a great platform for anyone wanting to learn more about how computer hardware works, exploit development, software development, etc. They have a low barrier to entry because they don’t have any of the security mitigations a more modern device will have and a lot of information on the inner workings have been thoroughly researched and documented. However, there’s still a lot of things yet to be discovered or learned about these consoles and any seasoned exploit developer can have a lot of fun treating these as “CTF” devices while in search of an easier way to hack the console.
Part 1: Dropping in
I started with the game save approach and began looking through the game saves I had from a backup of one of my consoles. The first game save I happen to open was for Tony Hawk’s Pro Skater 4. It was a custom park made with the “Create-A-Park” feature which was like a mini level editor players could use to create their own skate parks. Looking at the save file in a hex editor something immediately stuck out to me:
The “Create-A-Park” feature allows you to create what’s known as a “gap”, which is a term in skateboarding used to describe an area between two platforms you jump over. THPS4 allows you to name the gap so when a player successfully clears the gap the name will appear in screen with a point value. It added additional depth to the feature back in the early 2000s when the idea of creating your own level as a feature of a console game was still pretty novel. But for me it meant I had a starting point for bug hunting. This custom string had a max length of 31 characters (+ a null terminator), and presumably this would be run through some sort of string copy function. If I was lucky it would be strcpy (opposed to something like strncpy) and I might be able to use it as a memory corruption primitive.
I crafted a malicious save file by changing the string to some really long repeating ‘0x41’ character sequence and copied it back to my Xbox. For testing I’d be using an already hacked Xbox console that had full debugging capabilities which would allow me to step through individual CPU instructions and investigate memory contents. Upon loading the game save the console crashed and looking at the CPU state I could see the instruction pointer was set to 0x41414141 which meant the gap name string was likely being copied to the stack using strcpy. After finding the address of where the strcpy was happening I opened it in IDA for easier analysis:
The disassembly is a little hard to follow because the parameters for the inline strcpy call have been optimized but here’s the relevant pseudo code for the function that loads the save file data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
struct save_file_gap_data { ... char gap_name[32] ... }; struct gap_description { ... char gap_name[32] ... }; void read_park_file() { ... // Loop through all the gaps in the park file. for (int i = 0; i < park_header->gap_count; i++) { gap_description gapDesc; save_file_gap_data* pGapData = (save_file_gap_data*)pParkDataPtr; ... // Copy the gap name locally. strcpy(gapDesc.gap_name, pGapData->gap_name); ... // Register the gap data parsed from the save file. sub_EA520(&gapDesc); // Next gap. pParkDataPtr += sizeof(save_file_gap_data); } ... } |
As you can see the function loops through each gap in the save file and parses some information including the name which is copied to the gapDesc variable on the stack. There’s no bounds checking on the string so strcpy will continue to copy data until it reaches a null terminator. By overflowing the gap name string we can overwrite stack data including the return address for the function. This version of the game is conveniently compiled without stack cookies so we’re clear to trash as much data as we want. However, even once we control the return address not all memory is executable so we’ll need to do a little more work to get full code execution.
None of the gen 6 game consoles (Xbox, Playstation 2, Gamecube, Dreamcast) have any form of hardware data execution prevention (DEP) mitigations that would prevent regions of memory from being executable . However, Xbox does have some “soft DEP” that was used in later versions of the console and games but it can’t be applied to arbitrary regions of memory, it has to be a single contiguous region of memory. Basically, memory is only executable up to a certain address and everything thereafter is non-executable. This is achieved by changing the code segment selector address which defines the region of executable memory on the Pentium 3 processor. Later versions of the Xbox kernel will limit this region so not all memory is executable, but this really only prevents heap data from being executable and still leaves plenty of regions of RWX memory for us to use.
Stick the landing
The stack region and heap allocation containing the save file data are beyond the limit of the cs selector register, so we can’t execute code off either of these regions. However, all memory for the game executable including read-write data segments are in the executable region of memory, so all we need to do is find a way to copy some code to this area and we can execute it. This is where a ROP chain would prove useful but I actually found another way to achieve the same result that was a bit easier.
In addition to naming the gaps in the park file the game also lets you name the park itself. The park name string is stored in the header of the save file and the game must load this information after the player selects the save file so the name can be displayed in the UI. This header data is 136 bytes long and gets copied to a struct in the data segment of the executable which is in the executable region of memory. We can’t modify all of the header data as some fields need to be valid for the game to parse the file correctly. But there’s a small amount of data we can modify and it’s large enough to put a small memcpy stub there to copy our full payload to an executable region of memory and jump to it. Putting this all together we end up with the following:
- After loading the save file but before the player presses “Start Game” the first 136 bytes of the save file are copied to the data segment of the game executable. This 136 bytes includes our shell code copy stub which will copy the full payload to an executable region of memory once we trigger the overflow.
- After the player presses “Start Game” the save file will be loaded in full and our maliciously crafted gap name will be copied to the stack. The buffer is crafted specifically so that we overwrite the return address with the data segment address containing our shell code copy stub.
- Once the park loading function returns it’ll jump to our shell code copy stub and copy a larger shell code payload from the save file buffer to some location in the game executable’s data segment that’s in the executable code region.
- After copying the full payload the copy stub will jump to it giving us full arbitrary code execution.
Now that we have full code execution on the console the next step is to disable signature enforcement and launch an unsigned executable, most likely one that could be used to further install softmod files on the console for a persistent hack. I’m not going to cover all the details of the security and OS on the Xbox or any other console mentioned in this post, but I will provide a brief overview of the steps used to patch the OS and launch additional unsigned code. I based this payload on the ones used in the existing softmod installer save files for 007 Agent Under Fire. The steps to patch out signature enforcement are as follows:
- Resolve the addresses of some kernel functions and data, the most notable being the address of the RSA public key used for signature validation of executable files.
- Disable write protection in the machine state register and patch the RSA public key with the “habibi” key.
- Launch a secondary executable file bundled in with the save game files, typically a softmod installer, though I used my classic “nyan cat” executable during all my testing. This executable must be signed with the habibi RSA key.
So what’s the “habibi” key?
The habibi key pair is an RSA key that was generated by (presumably) the original Xbox Linux hacking group in the early 2000s. I haven’t been able to track down the entire back story to this key pair but this is what I’ve been able to conclude based on researching it. Rather than patching the RSA signature check out of the kernel which would allow for piracy the Xbox Linux group decided to instead replace the RSA public key with their own. This would allow them to sign their linux loader executable and run it on a console along with the game save hack but prevent people from using their game save to run pirated content (in theory). However, this didn’t really work since shortly after they released their exploit to run linux other hacking groups released additional exploit files to install a persistent hack on the console which completely removed the signature enforcement checks.
The more interesting thing about the habibi key is that the public key modulus only has a 4 byte difference compared to the Microsoft RSA public key. For reference the MS key is a 2048 bit RSA key. I’ve asked a few people how this might be possible and the answer I got is “if you change the exponent to something small like 3 you easily factor out a similar key”. This should require that the exponent of the public key is also patched to “3”. However, none of the shell code payloads that use the habibi key ever change the exponent used by the RSA signature verification routine. Presumably it’s still performing the validation using the exponent 65537 so I’m not entirely sure how this works. Perhaps someone more knowledgeable could shed some light on it.
So why use the habibi key instead of just patching the signature validation function to always return true? I initially wanted to do this but to make the exploit compatible with every kernel version released on the original Xbox would require I pattern match an instruction pattern with high probability for collisions, and I didn’t want to spend a bunch of time writing logic to decode instructions to additional validation of matches. Using the habibi key was easier because it only requires a 4 byte patch to memory I can easily find without pattern matching.
With everything together I was able to load my hacked save file, get full code execution on the console, and run unsigned code. I was happy with the results but I wanted more so I looked at other iterations of the games in the Tony Hawk’s Pro Skater series to see how far back this bug went and how many versions of the game I could exploit with it.
Variant analysis
There’s a number of games in the Tony Hawk series and most of them have the Create-A-Park feature, so surely there’s more that are also exploitable. I worked backwards from THPS4 to see what the earliest version was that had the Create-A-Park feature and allowed you to use a custom name for gaps and found it was Tony Hawk’s Pro Skater 3.
Tony Hawk’s Pro Skater 3
I booted up THPS3 and created a custom park, fuzzed the gap name string and loaded the save again. However, this time the console didn’t crash when loading the save. I was able to spawn in and skate around, but as soon as I chose the “quit game” option the console crashed. Investigating the crash site revealed that the gap name string wasn’t being copied to the stack and was instead being copied to the heap. The overflow had overwritten some data in the next heap allocation and the console crashed when trying to free it. This piqued my interest because I was hoping to get experience with several different exploit techniques and not just rewriting the same strcpy bug several times over.
After investigating the crash some more I was able to determine that the game was using a custom memory allocator and by overflowing the gap name I was overwriting the allocation header for the next allocation in memory. When this next allocation was free’d the game would pull some pointers from the allocation header that lead to a vtable containing a function pointer for a cleanup routine.
This would be easy to exploit, however, unlike THPS4 this version of the game didn’t copy the header of the save file into the data segment, and the save file data was in non-executable heap memory. I’d need to find a way to get my shell code payload into executable memory and this was the perfect place to use a ROP chain. By overwriting the pAllocOwner pointer in the next allocation header I can control where the cleanup function pointer is loaded from. This can be set to the address of some instructions that would change the stack pointer to point to the malicious save data memory containing a ROP chain.
The ROP chain only needed to be a few gadgets long to copy the full shell code payload into the executable region of memory and jump to it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
; ROP gadget 0: 0x1BD19F -> stack pivot push ecx ; address of our fake heap allocation header pop esp ; esp is now set to the fake heap allocation header address pop esi ; esi now contains the address of the vtable retn 0Ch ; ROP gadget 1: 0x45F69 -> load memcpy parameters pop esi ; load the src address of the shell code pop edi ; load the data segment address for the shell code to be copied to pop ecx ; load the length of the shell code in dwords retn ; ROP gadget 2: 0x19C4C1 -> perform memcpy rep movsd ; copy the shell code from the heap to the data segment pop edi ; load the data segment address for the shell code so we can jump to it pop esi retn ; ROP gadget 3: 0x1902DD -> jump to the shell code call edi ; jump to the shell code ... |
This is actually the ROP chain pictured in my twitter profile banner (assuming I haven’t changed the picture since writing this post). The full payload was a generic “hack xbox kernel” payload I made for the THPS4 save exploit that would patch the RSA public key for executables to the habibi key and launch an unsigned executable. Putting everything together I had another save game exploit done, loading the hacked save file and then quitting the game would trigger the exploit and launch my nyan cat executable.
Tony Hawk’s Underground 1 & 2
THUG started out with the same routine, create the park file, fuzz the gap name, and load the modified save. However, instead of getting some sort of crash for an access violation I received a full bug check and the following message in my debugger:
1 2 3 |
Buffer overrun detected! A buffer overrun has been detected which has corrupted the program's internal state. The program cannot safely continue execution and must now be terminated. |
This means the game was compiled with stack cookies and my buffer overflow corrupted the cookie which caused the game to halt. Looking through the executable I found the save file loading function and confirmed the stack cookie check was there:
A stack cookie is a random value generated when the executable first starts that gets placed on the stack of functions that perform certain copy operations to stack variables. The cookie sits before the return address (or immediately following the variable used in the copy operations) and before the function jumps to the return address it’ll check the stack cookie matches the expected value and if not it’ll throw an exception (or in this case bug check the console). This can thwart stack overflows as the only way to overwrite the return address requires you know the cookie value and include it as part of the overflow data (so it appears unmodified when validated). This was surprising to see but I wasn’t deterred and simply took this as another challenge.
I spent some time analyzing the function to see if there were any variables I could corrupt after the gap name buffer but before the stack cookie, and there were. However, these variables were useless from an exploitation perspective as they would immediately get overwritten by the game code after the gap name strcpy call. I looked further to see if there was any code I could use to cause an exception and possibly use SEH exploitation. Unfortunately there’s no way for me to cause an exception in the remaining code before the stack cookie check, and even if there was there’s no exception handler registered at this moment that would walk the SEH chain. After spending a bit of time brainstorming I decided to give up on this one and move on.
Checking Tony Hawk’s Underground 2 I saw the same thing: the game was compiled with stack cookies and there’s no variables on the stack I could use for exploitation purposes and there’s no way for me to leak the stack cookie value. I don’t wanna say these games can’t be used for exploitation as I know there are other strcpy bugs in the game that can be used for exploitation and are on the heap. But you definitely can’t use the gap name string in save files for exploitation on Xbox. Interestingly enough the Playstation 2 version, PC version, (and most likely the Gamecube version) are not compiled with stack cookies and can be exploited using the gap name string buffer.
Tony Hawk’s American Wasteland
Next up is Tony Hawk’s American wasteland, which I was expecting to not be expoitable after seeing THUG1 and 2 compiled with stack cookies. However, to my surprise THAW was not compiled with stack cookies and was vulnerable to the gap name string buffer overflow. The exploit is more or less identical to the one for THPS4 so I won’t bore you with redundant details.
Part 2: Remote code execution
Now that I had 3 new save game exploits for the Xbox on hand I wanted to go further. It’s 2016 and while finding a game that can be used for soft-modding the console with a save game hack is easy, finding the memory card you need to load the hacked save file is not. It’s also not common to find adapters that let you use a usb stick or other common storage device as a memory card on the console, and hacking up a controller is also not ideal. I wanted to try and find a new type of exploit with lower barrier to entry that didn’t require a memory card. The attack surface I had my eye on was the ability to play a multiplayer LAN game with a park made in the Create-A-Park editor. This meant that the game is sending the save file over the network and loading it on the client’s console, so it should be possible to craft a special save file that could be used over the network to hack the console of anyone who joins your match.
To start out I created a special setup for the host by modifying the game executable to fix the strcpy bug which would prevent the host from hacking themself. I also added a new code segment to the executable so that I’d have plenty of space for any additional functions I’d need to write. Next I setup a network match between my “host” and another console using a hacked save file that would just change the LED color to orange to signal the payload ran. Unlike loading the save locally, loading it over the network doesn’t copy the save file header data to the data segment which breaks the shell code copy stub, but that’s not a big issue because we can use a ROP chain similar to the THPS3 exploit to perform the shell code copy.
After crafting the save file and setting up a network game I had the client console join expecting it to receive the park file, get compromised, and change the console’s LED color. However, what actually happened is that the client connected to the host and spawned the player in, allowing them to skate around. Not only did the payload not run but we didn’t even trigger the buffer overflow. This was odd so I did some poking around in the memory of the client console and saw that the memory containing the park file sent by the host did not match my exploit file. I could see some of the exploit data in memory but the gap name been trashed and null terminated…
To figure out what was going on I placed some memory breakpoints on the memory containing the park file data on the host and waited for them to get hit. What I found was that the host would load the park file, then re-save it in memory, and send that to the client. In the process it would trash the exploit data which prevented it from running on the client console. Not entirely sure why it did this but I was able to just NOP the function call out and then everything worked as expected. The client would receive the hacked park file, get compromised, and change the LED color.
The next obstacle was figuring out how to obtain the secondary payload executable on the client side. When running the save exploit locally the secondary payload is bundled into the folder for the game save. However, running the exploit over the network the client won’t have access to this file so I’d have to come up with a way for them to obtain it. Initially I tossed around ideas like loading it off a burned CD but I didn’t want people to have to burn a disc as less and less people have CD burners nowadays. I also considered loading the executable from a local network address which would only require the client run a python script on a computer on the same network as the console. However, the Xbox winsock implementation uses secure socket connections by default which meant that my python script would have to recreate all the Xbox security stuff on top of the IP frames, and I didn’t want to do all that.
I decided to try and find the game’s net code and see if I could use it to send the executable to the client using some sort of “out of band” messages on the already established connection. I figured this might require quite a bit of shell code to do so I started by doing more research on the park save file to see how much space I had to work with. If it turned out I didn’t have enough space in the park file for all the shell coded needed then I’d be wasting time.
What happens if I google this?
I started searching the internet to see if anyone else had explored the park save files and if there might be some notes or something I could use as preliminary research material. After a few google searches I wasn’t able to find anything useful. So I played a game I like to call “what happens if I google this?”. Any time I’m reverse engineering something and come across a magic number or debug string I’ll put it into google and see what comes up. Over the years I’ve found a number of really interesting things such as the exact source file for the obscure encryption algorithm used in Call of Duty Black Ops, Microsoft patents with C-structs and developer comments describing how some encryption key ROM chips worked, and this time I’d hit one of my best finds. In the function that loads the park file there’s a reference to the string “Sk4Ed_Dead”. I put this string into google to see what would pop up, hoping that I’d find some sort of forum post referencing it. What I found instead was a GitHub repository called “thug”:
I thought that maybe this was some sort of homebrew tool for modding the game, but as I started to explore the repository I quickly realized that this was actually the source code for the entire game of Tony Hawk’s Underground. Someone had dumped it onto the internet in the form of this GitHub repository. This was an incredible find, even if it wasn’t for THPS4 the code base should be similar enough that I could use it to figure out how the game’s networking code worked and write the hooks I need to send the secondary payload to the client. You might be thinking, but wait, isn’t that cheating? And sure I guess you could say that, but in my opinion everything is fair in exploitation. My goal was to hack the console at all costs, I didn’t care about having a “clean room” exploit implementation. I already had RCE working and knew with a bit of work I could have the payload transfer using the connection established with the host. The only difference having this source code makes is how long it’ll take me to find the net code functions I need, from a few days down to a few hours. But now my goal has slightly changed.
Rather than just get the exploit working over the network I wanted to make it as robust as I could.
I wanted to make an exploit that would silently get RCE on the console of anyone who joined my match, hack their console to run unsigned code, and silently transfer another executable to their console while they played the game. Nowadays with RCE being a common attack vector this doesn’t sound that crazy. But imagine it’s ~2005 and a hack like this came to light, how crazy would it be to see headlines talking about hackers remotely hacking Xbox gaming consoles through online play, with the exploit being so seamless that the victim has no idea it happened until it’s too late. So I decided to switch from console hacker to threat actor and see how much fun I could have with this.
I cloned the repo and began looking through the code base. I was able to find the park file loading code and saw the exact line of code with the strcpy bug. Curious if this code was for the final version of the game I spent an hour or so recreating the Visual Studio project files for it and after fixing a few compiler errors I was able to successfully compile the code and run it with assets from the final version of the game. The code didn’t appear to be final but very very close to it. I spent some time looking through the network code and got an understanding of how messages were sent back and forth from host <–> client. After that I tracked down the relevant functions and variables for THPS4 in IDA and had everything I needed to start writing the hooks.
Restoring execution
Now that I decided to make this exploit as robust as possible I’d need to find a way to restore execution back to the game while the secondary payload transferred. This would require some changes to the ROP chain to save the old stack pointer before performing the stack pivot, and then restoring it later on after the shell code finishes executing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
; Gadget 0 - save old stack pointer push esp and al, 8 mov [ecx], edx pop esi ; esi now contains the old stack pointer retn 4 ; Gadget 1 - stack pivot pop esp ; Change esp to point to our ROP gadget data ret ; Gadget 2 - get address to save stack pointer to pop eax ; Hack_OldStackPointer ret ; Gadget 3 - save old stack pointer for later mov [eax], esi ; Save old stack pointer pop esi ret ; Gadget 4 - load destination address for shell code copy pop ecx ; ShellCodeCopyDstAddress ret ; Gadget 5 - load source address for shell code copy pop eax ; shell_code_start ret ; Gadget 6 - copy shell code to executable memory pop edx ; size of shell code / 4 sub ecx, eax push esi loc_1117E4: mov esi, [eax] mov [ecx+eax], esi add eax, 4 dec edx jnz short loc_1117E4 pop esi retn ; Jump to our shell code |
Asynchronous file transfer
The game’s networking system works by registering a set of message handler functions and corresponding message IDs, when the game receives a message it’ll call the handler function for the message ID received. By registering some unused message IDs I can setup some simple file transfer messages to send the secondary payload to the client. I came up with some simple message exchanges that look like this:
- When the client connects to the host they’ll start the file transfer with a MSG_ID_PAYLOAD_REQUEST message to the host.
- The host will respond with a MSG_ID_PAYLOAD_DATA message that contains a sub message ID and associated value. The possible sub message IDs are:
- PAYLOAD_MSG_ID_START: the value is the size of the secondary payload.
- PAYLOAD_MSG_ID_DATA: when sent from the client to the host the value is the offset of the next block of data to send, when sent from host to client the value is the size of the payload data attached.
- PAYLOAD_MSG_ID_END: indicates this is the last chunk of data for the secondary payload (file transfer complete).
- Once the client receives the PAYLOAD_MSG_ID_END message the file transfer is complete.
After coding up the file transfer message handlers for both the host and client and working out the bugs I could finally see the debug spew that my payload was transferring to the client. I waited anxiously for the transfer to complete, but rather than it completing successfully my client console crashed trying to dereference a null pointer.
A memory leak 15 years in the making
Upon investigating further I found that a memory allocator for network data was returning NULL and causing a the null pointer dereference. I started looking through the source code for the net code and I could see exactly where the buffers for my network messages should be getting free’d. It didn’t make sense that the game was out of memory. I eventually found some debug prints left in the game that I enabled to get a print out of memory statistics if a memory allocation fails. Looking at the output I could see the memory pool for “networking” had 0 free bytes. But this didn’t make sense, I could see exactly where the buffers for the network messages should be getting free’d. I initially thought this might be caused by the custom memory allocator the game was using that overrides the global new/delete operators. However, after investigating some more I realized that the code I was looking at for THUG actually contained a bug fix that was introduced some time after THPS4 had shipped. The bug? This memory leak.
For legal reasons I’m not going to show the real source code but I’ve written a pseudo code-esq version of the code that contains the issue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
class stream_desc { char* p_data; // Buffer holding message data ... ~stream_desc() { delete[] p_data; } }; class stream_link { stream_desc* p_desc; }; int handle_stream_message(...) { ... stream_link* p_link = ...; stream_desc* p_desc = p_link->p_desc; ... // Copy message data to the descriptor object. p_desc->p_data = new char[...]; memcpy(p_desc->p_data, ...); ... // Dispatch the message handler to process the message data. dispatch_message(p_desc); ... // Cleanup temporary resources. delete p_desc; delete p_link; ... } |
The problem is on line 39 which doesn’t exist in THPS4, meaning ~stream_desc()
isn’t going to be called and p_data won’t be free’d. This memory will be leaked and this happens for every message I send with payload data until the memory pool for networking data is exhausted and the next allocation request returns NULL. This memory leak has existed for years and it would normally never be an issue unless you sat in a multiplayer game long enough for the network pool to be exhausted (or, you know, decided to send some exploit files through the game’s net code…).
Not to worry though, because we can fix this by simply hooking the client code and free’ing the p_desc->p_data
buffer. Yes, as part of this exploit I’m going to hot patch a 15 year old bug in the game so I can hack the client’s console. The fix is very simple, I just hooked the handle_stream_message
function and called the correct free function for the p_data
buffer. Here’s the pseudo code for the fix:
1 2 3 4 5 6 7 8 9 10 |
int handle_stream_message(...) { ... // Cleanup temporary resources. mem_delete(g_memory_manager, p_desc->p_data); delete p_link; ... } |
The final result
With the memory leak hot patch code in place I booted up the host and client console, let the client connect to the match and waited for the payload transfer to finish. The hot patch worked, the file transfer completed successfully and the client ran my nyan-cat executable. I now had full RCE and asynchronous file transfer to the client all while they continued to play the game. I ran one last test using a tunneling app to remotely hack a friends console who lived across the country. After a few minutes of setting everything up I waited for the file transfer to complete and boom, his console booted the nyan-cat executable.
Here’s a recap of all the steps required to perform this exploit from start to finish:
- The client connects to the malicious host console and receives the “Hack Xbox” park file over the network.
- The client parses the park file which triggers the buffer overflow bug and overwrites the return address on the stack to kick off the ROP chain.
- The ROP chain will copy the full shell code payload into an executable section of memory and jump to it.
- The shell code will do the following before restoring execution back to the game and spawning the player in the match:
- Register new network message handlers for my custom message IDs to facilitate the file transfer in the background.
- Hot patch the memory leak bug in the net code.
- Send a MSG_ID_PAYLOAD_REQUEST message to the host console to initiate the file transfer.
- While the player skates around in-game the host will send an executable file to the client that gets saved locally on the client’s HDD.
- Once the file transfer is completed the shell code will patch the client’s kernel to use the habibi key and boot the secondary executable that was sent.
The entire process is so seamlessly smooth that without changing the LED color an unsuspecting player would have no idea anything was going on until their console suddenly booted another application. If I was a real threat actor I could easily get persistent code execution on the console and do whatever I want, snoop around the person’s network, create a botnet, or just brick their console entirely. Luckily I’m just here to try and prove my skills as a console hacker. With this exploit completed I was just pretty satisfied and felt I had achieved my original goals. However, with everything I achieved I was still doing all of this on a console that doesn’t have DEP or any real security mitigations that would make this exploit difficult. So it was time to move on to another target, one I had wanted to find a new exploit on for a long time…
Part 3: The first Xbox 360 software-only exploit
I’ve spent many years reverse engineering the software on the Xbox 360 in hopes I might one day find a bug that could be used to hack the console on newer kernel versions. The Xbox 360 hypervisor is probably the most secure piece of code Microsoft has ever written. There’s only ever been 1 software bug found in it that I largely suspect was due to a compiler bug and not the result of a developer making changes to the hypervisor code. That bug existed in the system call handler and was only present in the 4548 version kernel, which has not been usable since early in the console’s lifecycle. While I have found a few bugs in the Xbox 360 hypervisor I have not found any that are exploitable or could be chained together to get code execution.
I checked the version of Tony Hawk’s American Wasteland for Xbox 360 and confirmed it was vulnerable to the gap name buffer overflow attack. I really wanted to exploit the console on a kernel version newer than 4548 (even obtaining a console that can boot this kernel version is difficult in modern times) but without a new hypervisor bug that hope was dead in the water. However, there was one thing I could do with the Tony Hawk strcpy bug and that was develop the first software only exploit for the console, even if it only worked on the 4548 version kernel.
Lets take a step back to 2006 for a moment…
The Xbox 360 has recently been released and the Xbox hacking community is in full swing trying to hack the console. Very little is known about the inner workings of the console due to all executable code being encrypted. This means hackers didn’t have the ability to reverse engineer any code to try and find bugs or learn about how the console software worked. However, December 2006 an exploit would be revealed at 23C3 that showed the game King Kong being used to boot linux. Shortly after the first decrypted hypervisor and kernel image would appear on the internet for people to start reverse engineering, and eventually tools would be developed to decrypt all code that could be found on the console or on game discs. I remember reading a lot of misconceptions that the Xbox 360 hypervisor would prevent buffer overflow attacks and that the console wouldn’t be able to be hacked using game save exploits like the original Xbox was. This however, was incorrect, the Xbox 360 hypervisor doesn’t do anything that prevents stack buffer overflows.
Later on the details of the King Kong hack would be disclosed for anyone to use. It worked by modifying the shader files the game used to perform some arbitrary writes to kernel memory that would kick off a ROP chain and exploit the system call handler bug. This required you opened the console and flash modified firmware to the dvd drive in order to play the modified King Kong disc you would need to create (the shader files are on disc). I always wondered if there was another entry point that could have been used instead of having to open the console and modify the dvd drive (which ultimately led to an unstoppable wave of piracy in the years to come). And there was: using a game save bug to kick off the ROP chain.
However, this is also a “chicken and egg” situation as the game save files are RSA signed using a key pair that is unique to your console. Every Xbox 360 console has a “key store” that contains a number of cryptographic keys used by the console, one of which is used to sign game saves. This prevents people from modifying save files unless you have the RSA private key from a console’s key store, any console’s key store. How does a save file signed with my console’s private RSA key successfully validate on another Xbox 360 console? Because the RSA public key is embedded into the save file header, allowing another console to use it to validate the integrity of the save file. This public key is actually signed by another RSA key pair that only Microsoft has, so you can’t just put any public key in the save file header and have it validate. But as long as you had the decrypted key store for any Xbox 360 console you could resign a modified save file for use on anyone else’s console. This would require you first hack the console in order to get the decrypted key store, and you might be wondering how the first group of hackers were able to find the system call handler bug in the first place if all executable code is encrypted?
The crux of all Xbox 360 hacking started with hackers obtain Xbox 360 development consoles. These are special consoles used by developers to make and debug games. Once hackers were able to obtain these consoles (or more importantly, the SDK used along side these consoles) it would reveal a ton of information on how to decrypt executable code allowing them to start reverse engineering the boot chain, hypervisor, and game code, and eventually hack the console. Had these developer consoles and software not leaked the console most likely would have never been hacked as all executable code was encrypted (even when in RAM) from an external point of view. No ability to inspect code = no ability to find bugs.
The system call handler bug
I’m not gonna go into great detail about the overall security architecture of the Xbox 360 as there’s way too much to cover that’s not relevant to this post. But I will provide an overview of the system call handler bug as understanding that is essential to understanding the exploit payload.
The Xbox 360 has two main modes of execution: hypervisor real mode which is the most privileged mode and kernel mode which is less privileged and where the rest of the OS and games run. The CPU will use 64-bit physical addresses when in real mode and 32-bit virtual addresses when in kernel mode. The hypervisor doesn’t actually provide any virtualization functionality that you’d expect when you hear the word “hypervisor”. It’s more akin to a micro-kernel or “security supervisor” as it facilitates all security related operations on the console (such as code integrity validation) and assignment of executable memory. There’s no way to run any code on the console without it going through the hypervisor to be validated and have the memory pages marked as executable.
The CPU also has a cryptography unit on-die that sits next to the L2 cache and is responsible for encrypting and hashing memory. This prevents an attacker from sniffing or modifying RAM externally but also helps to thwart certain types of memory corruption bugs. When in kernel mode you can only see the cipher text of the hypervisor pages as they’re encrypted + hashed and not mapped in a way that would allow successful decryption of the memory from kernel mode. Trying to overwrite the cipher text from kernel mode will cause the hashing checks to fail and accessing that memory from real mode (hypervisor context) will trigger an exception and halt the console. Basically, you can’t read or write hypervisor memory from kernel mode or the console will halt.
When in real mode the upper 32-bits of a physical address are used by the crypto unit to control encryption and hashing of data (I’ll refer to them as the “protection bits”). There’s a special address mask, 0x80000000.00000000
, that can be applied to a 64-bit physical address that performs a memory access while ignoring encryption and hashing. This can be used to read or write memory in a non-protected way as validation of the memory is skipped. Any place kernel mode can provide a physical memory address to the hypervisor it’s imperative that the upper 32 bits are cleared to ensure that kernel mode code can’t provide the protection bits or else it could be used to read/write protected memory.
Looking at the normal implementation of the system call handler we can see how it’s supposed to work. Register r0 contains the system call ordinal provided by kernel mode. The slwi (shift left word immediate) instruction will shift the system call ordinal left by 2 (multiplying it by 4) and discard the upper 32 bits of the result (truncating the 64 bit result to 32 bits). This offset is used to index into the system call function table to get the function address for the specified system call ordinal. Since the ordinal has to be between 0 and the highest system call ordinal it’s not possible to get offset to point anywhere except within the system call function table.
Looking at the implementation of the system call handler on 4548 we can see there’s a slightly different instruction pattern, the slwi instruction has been replaced with sldi (shift left double immediate). This instruction operates on 64 bits, not 32, which means we can control the upper 32 bits of r0 which will get used when indexing the system call function table. Now I did say that the system call ordinal will be checked to make sure it’s within [0, max ordinal), but that comparison operates on the lower 32 bits of r0. The upper 32 bits will not be considered for the comparison. So by setting r0 to a value such as 0x20000000.0000003F
it will pass the ordinal range check and produce an offset of 0x80000000.000000FC
which will allow us to access unprotected memory (ignoring encryption and hashing) when indexing the system call function table.
I highly suspect that this change was due to a compiler bug…
The system call handler routine would be one of the first pieces of code written for the hypervisor as it’s a CPU exception vector and required for kernel mode code to run. By the time the console launched this code would have been well matured and there wouldn’t be any reason to change it, especially a few updates into the console’s lifecycle. Having worked at Microsoft for 7 years I can recount 3 occasions where we received an MSRC bug report for some bug that cropped up in the Windows kernel. Upon investigating the causes it was determined that recent compiler work had cause certain instruction patterns emitted by the compiler to change, and the new instruction patterns led to security bugs in kernel code. I highly suspect that this is the case for the 4548 system call handler bug as well. I don’t think there’s any reason for this code to have been changed by a developer especially in a way that would change how this array is indexed.
So how can this be exploited? From kernel mode we have a view of encrypted hypervisor memory and we can overwrite it. Normally this would cause the console to halt the next time the hypervisor tried to read that memory, but because we can get the system call handler to read a function pointer while controlling the upper 32 bits of the address we can set the upper most bit so a read ignoring encryption and hashing is performed. This will let the hypervisor read the value we overwrote and not fault. We can set the function pointer to point to a convenient instruction sequence that’ll get us code execution and by executing a system call with a maliciously crafted ordinal we can get the hypervisor to jump to our code. Here’s the steps required to exploit this bug:
- Load some shell code into memory and get the physical address of the allocation.
- Change a kernel memory manager variable that controls the 64kb page mappings. This will expose the encrypted view of hypervisor memory into an address range we can write to.
- Overwrite a system call function pointer in the encrypted view of hypervisor memory to point to the address of a convenient instruction sequence. For this we choose the instruction sequence
mtctr r4; bctr
which will jump to the address contained in r4 which we have full control over going into the system call handler. - Set r0 to contain the ordinal of the system call function pointer we overwrote in step 2 and set the upper 32 bits such that shifting them left by 2 will set the upper most bit in the register, ex:
. Set r4 such that it contains the physical address of our shell code OR’d with the0x20000000.0000003F
0x80000000.00000000
mask (our shell code sits in unprotected memory). - Execute the system call instruction which will switch into real mode and let the hypervisor dispatch the system call. It’ll perform a 32-bit comparison on the malicious system call ordinal and a 64-bit shift to calculate the array offset which will read the function pointer we overwrote without faulting. The hypervisor will jump to this address and execute the
mtctr r4; bctr
instruction sequence and jump to our shell code in unprotected memory. When in real mode the page protections are ignored so the hypervisor won’t fault trying to execute non-executable memory.
So how can we do this using the strcpy bug in Tony Hawk’s American Wasteland?
Collect E X P L O I T while maintaining a ROP chain
To build the ROP chain I modified the (now archived) Ida-Sploiter IDA plugin to add support for PowerPC architecture. This plugin would help me find ROP gadgets based on given search criteria for specific instructions or registers being used. The entire exploit took 24 ROP gadgets to perform and achieve full hypervisor code execution where I then patch out the code integrity checks and launch a secondary executable bundled in with the game save. I’m not going to detail the ROP chain here as it’s really long and boring (I’ve also thoroughly documented it on the GitHub repository), but I’ll provide a brief overview of all the steps for the full exploit:
- Using the strcpy bug we overflow the gap name buffer on the stack and overwrite the return address to point to the first ROP gadget.
- The first ROP gadget changes the stack pointer to point to the ROP chain data contained in the save game buffer in memory.
- Call MmAllocatePhysicalMemoryEx to allocate a block of physical memory for our hypervisor shell code.
- Call memcpy and copy the hypervisor shell code into the buffer allocated in step 3.
- Call MmGetPhysicalAddress to get the physical address of our shell code buffer (this is what we pass to the hypervisor) and save it for later.
- Change the kernel memory manager variable that controls the 64kb page mappings to map in the encrypted view of hypervisor memory for write access.
- Overwrite the hypervisor system call function address in the encrypted memory view to point to the
mtctr r4; bctr
instruction sequence. - Execute the syscall instruction using the malicious system call ordinal and physical address of our hypervisor shell code obtained in step 5.
- The hypervisor will load the function pointer we overwrote in step 7, execute the
mtctr r4; bctr
instruction sequence and jump to our hypervisor shell code. - Now we have full hypervisor code exec. I change the LED color to signal the exploit was successful, then patch out the RSA signature checks on executable files and return from the system call interrupt.
- We’re back in the ROP chain in kernel mode. Next we map a folder on the HDD that contains the secondary payload by calling ObCreateSymbolicLink.
- Finally we call XLaunchNewImage and launch our unsigned secondary payload.
And there you have it, the first software only exploit for the Xbox 360. It’s kind of ironic that this worked out almost exactly the same as the save game exploits for the original Xbox: performing a stack buffer overflow from a strcpy call on data contained in a save game file you can copy to your console using a memory card. You can use the strcpy bug to get ROP execution on any Xbox 360 OS version, but you’ll only be able to get full hypervisor code execution on the 4548 kernel version. If a new hypervisor bug is discovered this can easily be paired with it to work on newer kernel versions. I still have some hope that there might be an exploitable bug that would get you hypervisor code execution on a new kernel version. But I highly suspect it would be some kind of CPU or MMU bug rather than a bug in the hypervisor code.
Part 3: Hack the planet
Fast forward to present day (2024) and I finally got around to cleaning up and releasing all these Tony Hawk exploits. However, since I’m most likely retiring from game console hacking after this I wanted to drop an absolute banger of a release so I ported the exploit to some other game consoles that are vulnerable to it. This bug exists in 5 different iterations of the Tony Hawk video game series across numerous game consoles and handhelds. No one is safe from Tony Hawk’s Pro Strcpy. Since you’re probably tired of me talking about the same strcpy bug over and over I’m only going to provide some brief details of which games for which platforms I ported the exploit to and how it may or may not make hacking those consoles easier.
Playstation 2
I ported the Tony Hawk Pro Skater 4 network RCE exploit to the Playstation 2 version of the game. Using PCSX2 (or another console) you only need the THPS4 disc and you can hack your console over the network. The exploit will send uLaunchElf over the network and launch it when the transfer completes, from there you can load the FreeMcBoot/FreeHDBoot installer off some other media (like a usb stick). I originally wanted to just send the FreeMcBoot installer but it’s not a single file and the PS2 doesn’t have any persistent storage attached to it by default (unlike Xbox with the built-in HDD).
The save game exploits are not useful on the PS2 because if you have a way to copy files to a memory card you can just install FreeMcBoot and be done. I don’t know if this network exploit will make it any easier to hack the console since you can already buy a FreeMcBoot memory card off Amazon for $15 USD with next day prime shipping or just use FreeHDBoot on a phat console with the network adapter. So I think it’s safe to say anyone who wants to hack their PS2 most likely will not need this exploit. Oh well, hack the planet.
GameCube
I ported the Tony Hawk Pro Skater 4 save game exploit to the GameCube version of the game, but did not port the network RCE or any other version of the save game exploits. What I didn’t realize going into this was that it’s non-trivial to copy files to the GameCube memory card and that people have been buying memory cards with pre-hacked save files on them off of Ebay for upwards of $50 USD. This is honestly pretty lame and I tried to think of another way to lower the barrier to entry for this console but the GameCube versions of the Tony Hawk games don’t have network support so even if you bought the network adapter (which apparently no one has because only like 4 games supported it) you wouldn’t be able to use the network exploit anyway. It looks like there’s already plenty of game save exploits available for the console, and since there isn’t a persistent software hack for the console most people end up going with a modchip anyway. Oh well, hack the planet.
Windows
I created a game save exploit for THUG PRO, the community patch for Tony Hawk’s Underground, and I even reported the bug to them 7 years ago but they weren’t interested in fixing it at the time. Since the exploit doesn’t provide any value to Windows I opted not to release it (it wasn’t full stack anyway) but I would like to provide a word of warning to anyone playing any of the Tony Hawk games on PC. They all have the same strcpy bug in them, they’re all exploitable, and that’s not the only strcpy bug that can be exploited over network play either. I highly recommend playing those games while forcing ASLR on the executable, and don’t ever run them as Administrator.
Conclusion
So there you have it, who would have thought one strcpy bug could be used to hack so many different platforms and even achieve RCE on some of them? Bugs aside the Tony Hawk skateboarding games were some of my favorite growing up. Those games are what got me into skateboarding and provided years of entertainment. Neversoft was one of my favorite game studios and a place I would’ve loved to work at. There’s some behind the scenes footage of a bunch of Neversoft developers who were probably in their late 20s-early 30s and had never stepped on a skateboard before have a contest to see who could do a kickflip off a big wooden conference table at the studio. I remember thinking that was the coolest thing I had ever seen and wanted to work there ever since. Unfortunately they shut down but at least the legacy of the Tony Hawk video game series will live on as some of the greatest games of the 2000s and the best way to hack your old gaming consoles 😉
Full source code and patched game save files are available on my GitHub.
RSA keys are a product of two numbers. Those numbers don’t _need_ to be prime, although it sure would make it harder to break if they were.
If you add 2 to a public key, it’s probably very easy to factor the new valid key that you obtained. If you loop over 2^31 options of even numbers to add, you’ll quickly find something breakable but not trivially breakable.
> The problem lies in the custom delete operator. When you call delete on a C++ class pointer the standard runtime implementation will invoke the destructor for the class, in this case ~stream_desc() would be invoked and p_data would be free’d. However, the custom delete operator written by the developers does not invoke the destructor for the object being deleted
This is wrong. The delete operator never calls the destructor, the delete **expression** does – and calls the delete operator afterwards.
I suspect the actual issue is that they did not provide a custom
operator delete[]
, so thedelete[]
in~stream_desc()
calls the defaultdelete[]
which frees the memory, but does not reset the pool handle.I debugged the game and the dtor for stream_desc is never invoked. In the disassembly you can clearly see where the custom delete operator is called and there’s no invocation of the object dtor before it.
Taking a second look at this I think what happened is the code I saw for Tony Hawk’s Underground was a bug fix that was introduced after THPS4 had shipped. The custom memory allocator side tracked me and led me to the right conclusion for the wrong reasons.
Good job, man. You did a great job, if someone knew about this bug in 2005 tony hawk games would sell like hotcakes.