GameCube Save Exploits - F-Zero GX

While I previously explained the steps on how to go from already having code execution up to running homebrew files, I did not yet write down how to actually find an exploit and then use it to get to code execution, so I decided to do that today with f-zero gx. While I did find the exploit myself and made use of it, I was not actually the first to discover it, you see back at the speedrunning event SGDQ2018, there was actually a demonstration for it, see this video at 11:30

Also at the end of the demonstration my name was actually listed in the credits which was really cool to see, and while I was curious as to why and what they all did, instead of immediately asking them directly about it, I instead wanted to find it out how the exploit works for myself just to see if now that I've seen it demonstrated I could reproduce it as well.
Now having the knowledge that it has something to do with replays, I did a quick search to see if there was any public info on replay save files already and sure enough, there was this really detailed breakdown of it:
https://github.com/yoshifan/fzerogx-docs/blob/master/file_formats/gci_replay.md
and also an actual graphical interface for editing the replay save files directly:
https://github.com/yoshifan/fgx-re
While reading through that documentation file I did notice that it also contains data for a custom machine, basically in f-zero gx you can create your own machine to race in, so now I decided to head into dolphin, make my own custom machine and do a quick race on the first track of the game to have a good base replay file to mess around with probably all of the important data that hopefully would lead me to break the game in some way.
So now I just had to export that save from dolphin and open it in that graphical interface:
saveopen.jpg
Those sure are a lot of numbers, scrolling down on the side there will just give you an endless string of more numbers by the way. Oh and also I had to run this GUI in a windows 7 vm cause I had trouble getting it to open on my main system ;)
So, now I had a lot of numbers in front of me which I could just mess around with, make them really big and see if any of them would cause some weird things to happen to the game. This all in all took me quite a few hours because really it was a LOT to go through, but then finally, after changing the "Body ID" way at the bottom from its initial value of 09 00 00 00 to a large number like this:
bodyid.jpg
Dolphin suddenly came up with THIS when trying to access the replay:
dolphinwarning.jpg
Thats a great start, an invalid read! Now I just looked at the machine code of the PC (position code) given here by dolphin and found this:
strcpy.jpg
you can see from the top right of that image that I also immediately knew which function this was and gave it its name here, it is strcpy! That really was the best case to run into as I'll explain in a bit, I did also take some time to capture the exact moment it had that invalid read in dolphin just to see, re-creating this for this blog entry took me 4 minutes of clicking through breakpoints to hit this invalid read again because of how often strcpy gets used, but here are some values of interest:
dolphininvread1.jpg

r1 and r3 got me very excited, you see, strcpy has the job of taking a string from one memory position (r4) and copy it over to another memory position (r3), in this case r3 clearly is based on the stack pointer (r1), the stack pointer is used for various purposes like this but you see, it also is used to store critical values such as where to jump next to in code, and strcpy has no limit as to how much it can copy, so when you gain control of strcpy and give it a very, very long string you created you can break the intended maximum length that the stack pointer has prepared for and just overwrite things like the positions it jumps to next in code!
In normal operation, everything works as intended because of course the allocated size on the stack was chosen to be big enough to support all the strings that can be copied by the game developer, but once you can break that limit on a console like a gamecube or wii, you can pretty much do what you want!
and now for the most important thing of this breakpoint is this value:
dolphininvread3.jpg

The LR (load register) essentially is the address that is responsible for calling this strcpy, so now all we really have to do is look at its code and see what happened, I just gave it the name of copy_machine_id_string, dont ask me how I come up with these names, I just do :P
copy_machine_id_string.jpg
so as you can see, the value of r4, the invalid memory position causing that invalid read we are looking for, is actually the value of r3 which was handed over by whatever called copy_machine_id_string and then moved to r4 by 8030A8DC, we will look at that in just a second, I just want to point out that whatever is in r4 (that for now caused in invalid read) gets copied onto a pretty small bit of the stack (r1) as you can see at 8030A8E0, if we manage to somehow make that string 0x4C bytes long, we would be able to modify the address the code jumps to when its done, that address gets saved at 8030A8CC, just keep that in mind for later.
Now I wanted to know what function called copy_machine_id_string, well this in dolphin was again just a simple case of setting a breakpoint and it led us to this bit of code I called get_machine_id_name:
get_machine_id_name.jpg
as you can see I named r6 to machine_id_name_arr, basically every of those "Body ID" values corresponds to some name in that array, and the most interesting thing is that this array gets copied from r6 (a static memory position) over to r7 by the code from 8030952C to 8030953C, and you can see at 80309520 that r7 is actually based off r1, so it is a value on stack!
That array is rather short compared to the possible values you can set "Body ID" to, which as I marked at 80309514 that the id given can cause an overflow, also here you can see the body id is getting moved from r3 to r29, you'll see that this is the indeed the body id shortly.
After that array got copied over, the code then goes on to move the body id from r29 to r3 in 80309540, the line below it actually moves the language used which is in r5 over to r0 (I used the US release here so this is actually 0), then it multiplies the body id (r3) by 0x18, it then loads up that freshly copied array address from r1, the stack, into a separate register, r28, adds the language (r0) on top of the multiplied body id (r3) and stores its value in r27. Lastly, it basically just adds r27 and r28 together and loads whatever is in that combined address into r3 to finally hand over to copy_machine_id_string, the function responsible for calling the strcpy that caused the invalid read. What a long bit of code to explain!
Well, if you remember that dolphin breakpoint from earlier, we can actually look at r27, r28 and r29 now to confirm what I just explained is correct:
dolphininvread2.jpg

So this just confirms that indeed, r27 is just r29 (the body id 0xFF) multiplied by 0x18:
hexmulti.jpg

With all this, we now know that we can essentially control a load from anywhere between 801B67B0 (r28) and 801B7F98 (which is just 0x801B67B0+0x17E8, the biggest body id we can set), well if we quickly scroll through that area of memory in dolphin, we suddenly find a familiar bit of memory:
gcileftover.jpg

if you dont recognize it anymore, scroll back up to the part where we first made it crash with changing the body id in the save, seem familiar now? ;) Theres also a reason I specifically marked out 801B7670 here as you'll find out later.
What luck, it seems like on the stack is still some leftover bit from the actual replay file being loaded and we can actually reach it by just setting the body id to some value of choice! Now at this point, I finally went ahead and wrote my own little tool to get ready to inject some custom code into a replay file which is located right here:
https://github.com/FIX94/fzero-hx-gc/blob/master/fzero_injldr/main.c
This bit of code includes r28 for all the regions, here you can see it for the US version we've been looking at so far, 801B67B0:
https://github.com/FIX94/fzero-hx-gc/blob/master/fzero_injldr/main.c#L60
followed by a machine id that will give us a very nice position we can modify in the end I'll explain later:
https://github.com/FIX94/fzero-hx-gc/blob/master/fzero_injldr/main.c#L61
and also of course that multiplication of the machine id by 0x18 we talked about:
https://github.com/FIX94/fzero-hx-gc/blob/master/fzero_injldr/main.c#L93
Now, how exactly did I come to choose that particular machine id? Well, let us have a quick look at the save file in the graphical interface again, but this time scrolled up slightly:
pixdata.jpg
you notice how over body id and pilot id there is that giant space called "pixel data"? Well, that turns out to be 8192 bytes long as described in the save document from the start, and because its just that, pixels, we can really fill it with whatever we want, making it ideal for code. Also calculating where that bit is in memory is pretty easy, I marked out 801B7670 before which is at the pilot id, so now we just have to subtract the 8192 bytes from that and we get an address of 801B5670, thats also the one included in my little tool:
https://github.com/FIX94/fzero-hx-gc/blob/master/fzero_injldr/main.c#L59
So now if we take r28 again, 801B67B0, and take the machine id I chose in my tool, 0x9C, and multiply that by 0x18 and adding it onto 801B67B0, we get 801B7650, which you may notice is just barely within the space of pixel data which ends at 801B7670, its as if I planned for that to happen ;)
Right, now what exactly do we even write into that position? Well, remember that our goal is to overflow a string to jump to our own code, so we first write in an address for our string into the bit of pixel data we just set up for:
https://github.com/FIX94/fzero-hx-gc/blob/master/fzero_injldr/main.c#L138-L147
You may notice that those are 6 writes total, remember how I earlier talked about language also being a thing added to that read address? Well, this just makes sure we cover all possible languages that can get added to the address. We wrote in the address emblem_arr_start into this position, which as you saw earlier is the start of the pixel data, so now next up we make up a string that is 0x4C bytes long which as I explained earlier makes it possible for us to modify where the code jumps to next:
https://github.com/FIX94/fzero-hx-gc/blob/master/fzero_injldr/main.c#L148-L150
and now of course we copy in the code position we want to jump to right behind that string:
https://github.com/FIX94/fzero-hx-gc/blob/master/fzero_injldr/main.c#L151-L153
which I chose to be the pixel data+0x50 bytes.
All I now have to do is copy the common loader I wrote about in the previous blog entry into the pixel data+0x50:
https://github.com/FIX94/fzero-hx-gc/blob/master/fzero_injldr/main.c#L173-L174
and the rest of that tool code now just writes our prepared pixel data and our body id of choice over into a replay save file, makes sure its checksum is valid so the game can read it, gives it a nice name and banner and that is it!
Oh and what I mean by nice name and banner, its just how it shows up in whatever memory card editor you then use to import the save, I like to always edit things like that to stand out a bit ;)
memcard.jpg

Right, of course if you want to see it in action here you go:


That is pretty much everything covered, make sure to also read my previous blog entry if you didnt already to see how that common loader works to get a full picture of the exploit, thanks for reading.
  • Like
Reactions: 6 people

Comments

There are no comments to display.

Blog entry information

Author
FIX94
Views
819
Last update

More entries in Personal Blogs

More entries from FIX94

General chit-chat
Help Users
  • No one is chatting at the moment.
    K3Nv2 @ K3Nv2: I really don't want to buy this fap tab...