A story about reverse engineering and way too smart FTP client

Some time ago, I decided to swap HDD in my Original Xbox. It already has been upgraded with a 40 Gb IDE drive, but I had a spare 160 Gb SATA drive laying around and wanted to have some more storage in the system. I bought a SATA to IDE converter, swapped the drives, installed the system software, and transferred all the content back. To my utmost disappointment, savefiles for some games were corrupted. One of them was Black, where I had a decent amount of played hours. I did some basic troubleshooting but wasn’t able to find the source of the problem. At that moment it was obvious to me that the problem lay in the new hard drive.

If you’re here to know how to patch a game’s binary, jump straight to the Patching the binary yourself.

OG Xbox Savefiles 101

To prevent any tampering, all the game developers were obliged to add a digital signature to the savefiles of their games.

Additionally, developers were able to make savefiles “non-roamable”, i.e. lock them to the particular console they were created on. For achieving that, Xbox has a so-called “XboxHDKey” - a per-console value stored in the EEPROM intended specifically for the content locking. The list of such games is well known and maintained by the community. However, Black wasn’t on the list. (While writing this post, I was able to find an extended list, and even find a resigner specifically for Black. Funny enough, it wouldn’t help me with the issue I had in the first place.)

If you want to dive deeper into the topic, those pages are for you:

So, at that moment I was convinced that some additional checks were implemented in the game. I thought that probably HDD’s model and serial number were also used for the signature generating. There was only one way to know for sure.

Reverse engineering and patching game’s executable

I decided to analyze the executable binary with Ghidra. To be able to do so, I additionally grabbed ghidra-xbe, an extension for the Ghidra to support loading Xbox Executable Format (XBE) files, and xbox-includes, a set of GPL-sourced Xbox headers. Loading and analyzing the game’s executable was straightforward, but importing headers had a quirk. For some reason, Ghidra wasn’t happy with the HANDLE type, but renaming it to something different like HANDLE_MY solved the problem.

Now we are fully prepared to begin digging. Searching for the word “signature” in the program text immediately yielded some promising results:

A lot of interesting stuff

A lot of recovered (or maybe unstripped?) symbols, the most interesting of which is “XCalculateSignatureBegin”. Quick googling gave me the following function definitions from the XAPI:

HANDLE XCalculateSignatureBegin(DWORD dwFlags);
DWORD XCalculateSignatureUpdate(HANDLE hCalcSig, BYTE* pbData, ULONG cbData);
DWORD XCalculateSignatureEnd(HANDLE hCalcSig, BYTE* pbSignature);

By making an educated guess, we can assume that 1st function creates a handle object set up with some flags, 2nd one takes the binary data and its size to calculate the signature for, and the last one writes the signature into the provided address. The flags are also conveniently listed on the page mentioned above, but we’ll get to them later.

Now we can complete the function signature and take a look a look at its callers. There are 2 of them, and for the reader’s convenience, we’ll start with the 2nd one. This is how it looks after decompilation:

Looks kinda familiar…

The obtained handler is checked not to be -1, and after that 2 functions are called, whose parameters are suspiciously similar to XCalculateSignatureUpdate and XCalculateSignatureEnd. After completing their signatures the decompiled code of the function started to look like this:

Of course it is!

Now let’s take a close look. The ~Update function takes EAX as the pointer to the source data and 0x17ec as the data length. The ~End function takes EAX advanced by the same 0x17ec bytes. Most of the games stored the signature at the end of the savefile, so we can conclude that this function is responsible for adding the signature to the savefile while saving the game. You can be creative and replace this function implementation to add some custom data instead of the signature. Does anyone want to place an ad to the savefile of the 15 years old game where nobody will ever see it? No? Okay, let’s move forward then.

Now we’ll take a look at the 1st of 2 callers. Its decompiled code looks like this:

Decompiler wasn’t feeling good today

Here, the ~Update function gets called identically, but the ~End function takes a pointer to the local 20 bytes long variable as its second parameter. Perfect! The rest of decompiled code looks like total gibberish, so let’s take a look at the assembly listing:

So we’ll figure it out ourselves!

After calling the ~End function, pointers to the signature from the savefile and calculated signature get loaded to the EDI and ECI respectively, signature length gets loaded to ECX, and they got compared by the following instruction:

001f2dfd f3 a6           CMPSB.REPE ES:EDI,ESI

If the comparison is successful, i.e. the difference between signatures is 0, the following conditional jump gets performed:

001f2e01 74 05           JZ         LAB_001f2e08

The rest of the code is some compiler optimized preparation of the return value, which is not interesting. What’s interesting is to skip the code that gets executed if the check was not successful. The most straightforward thing to do is to replace the conditional jump with an unconditional one. It can be achieved by changing the instruction from JZ (opcode 74) to JMP (opcode EB). After doing so, saving the patched binary, and transferring it to the directory with the game backup on the Xbox via FTP, I was ready for testing. Unfortunately, an unexpected discovery has happened.

A terrible mistake

With patched binary, I also transferred my old savefile to the console. And I noticed something. The savefile size on my computer was 6144 bytes, while on the console it was a few bytes larger, something like 6147 bytes. I took a few seconds to digest it and then realized that I made a terrible mistake. As I figured out, FileZilla, an FTP client that I used automatically corrects line breaks while transferring text files between Windows and Unix systems, and by default, it treats files without an extension as text files. So, while I was thinking about some sophisticated anti-tampering system in the game, my FTP client was just corrupting the files.

After properly configuring FileZilla, I copied the savefile to the console, verified its size, launched the game from the vanilla executable, and it loaded the savefile normally. Just to verify whether my patch is working, I changed a few bytes in the savefile signature, copied it to the console, launched the game from the patched binary, and it also loaded the savefile normally! Yay!

It could be the end of the story, but in the process, I found something else.

Signing flags and non-roamable saves

Remember the flag that is passed into the ~Begin function? If we look back at the decompiled code, the 1 is passed in both function calls:

hCalcSig = XAPILIB::XCalculateSignatureBegin(1);

What does the 1 mean? Fortunately, the same Reddit page mentioned above gives us the answer:

DWORD XCALCSIG_FLAG_NON_ROAMABLE = 0x00000001;

So, this precisely tells us that the game incorporates non-roamable savefiles. Their signatures are tied to a particular console with mentioned above XboxHDKey and can’t be transferred to another console. But since we disabled signature checking, they now can be!

So, to recap: this patch allows you to freely move the savefiles between consoles without any additional actions like resigning. And this is some great news!

Patching the binary yourself

For obvious reasons, I can’t distribute the patched binary. However, I can show you how to patch one. During my research, I was using default.xbe file marked as region-free (game region value in the xbe certificate is 7, which means all regions) with md5 hash d21025a12520a74a2edd6c86fef9b57e. The patching is simple - open the file with a hex editor, find a byte 74 at the offset 0x1E2E01, and replace it with EB. That’s it.

If there’s something different at the aforementioned offset, you can also try looking for the following byte sequence 74240833 C0F3A65F 5E74051B C083D8FF and replace its 10th byte as described above.

Transferring savefiles without modifying the game

If you don’t have an option to use the patched binary (for example, you prefer to run games from the original discs), it’s also possible to resign the savefile. You can find a standalone resigner for Windows, its source code, and additional config for XSavSig in the feudalnate’s GitHub repository.

Wrapping up

So, what do I think about all this in the end? Was all this needed from the perspective of just playing video games? Not at all. Did I learn a lot about reverse engineering in the process? Absolutely yes! And I really enjoyed the process, it was quite interesting to dive deep and figure out how things work. And in the process, I somewhat contributed to video games preservation, which is also amazing!

Now I can confidently say that the Original Xbox helped me learn some new skills and has a special place in my life!