Yep, I felt like writing about yet another save exploit, this time about the latest one I released:
Why you wonder? Well, it took me quite a while to find a viable entry and I can actually write about every step properly and not just in vague terms as I sometimes had to before.
In case you dont know, these gamecube save exploits load a homebrew executable of your choice from memory card so you can have homebrew without any modchips or something like that, just a game which has an exploit, a way to install a hacked save like a hacked wii and that is it.
First off, why did I decide to look at ghost recon 2? About little over two years ago, I looked at the original splinter cell and found a good exploit in that, and for some reason I decided not to look at the other splinter cell titles afterwards and then a couple days ago I finally did - leading to me finding a very simple entry in pandora tomorrow, that one is so simple in fact that its not even worth writing about
I then looked at the other splinter cell titles and while I did get them to crash, I did not immediately see any useful point from where I can do something, so I moved on to other ubisoft titles, and in ghost recon 1 I was able to make a proof of concept exploit, but it was really unstable because that exploit involves corrupting the allocated game memory to then hopefully overwrite parts of code, but that game memory moves around constantly, so I was not sure if I can make it consistent, leading me to move on.
Now I then fired up ghost recon 2, made a save, looked at it in a hex editor and it seemed very simple, in fact I made a second save where I slightly changed the name of the save file because I did not see some immediate save hash I have to fix up when I want to edit the save which I usually have to do first, reverse engineer the game save hash function, but in this case I looked at it and there was nothing, no verification hash!
Normally I spend quite a bit of time to make some test saves and often they turn out to not seemingly have any nice exploits in them which is a bit frustrating, but in this case for probably the first time I could just edit the save in a hex editor and not worry about any hash fixups, the game just takes it! This already told me that this game most likely can be broken pretty well if the save file alone is this fragile.Warning: Spoilers inside!
Well then I decided to just add one more character to the name and see how the game reacts to it, saved that, fired up dolphin, clicked a bit around in the menu and when I hit change name it already came up with a problem, that was quick.Warning: Spoilers inside!
So we have a PC (position counter) which tells us where in code something went wrong and I went looking at that bit of code:Warning: Spoilers inside!
As you can see, I left a comment right there about that first bit of the invalid address always being forced to 01, the RAM of the gamecube though is always in the 80... range so this crash was not so useful, I decided to start looking how that address exactly came to be changed anyways by using memory breakpoints of whatever initially wrote that invalid value int 0x18(r31) which thanks to dolphins debug mode we can see r31 in the debug register tab when the crash happens:Warning: Spoilers inside!
so I then in dolphins debug memory tab I went right to that point and indeed, there is our invalid value:
So, setting a memory breakpoint at 813da424, it led me to this particular bit of code:
as you can see I named the function here again, its strcpy, the function we use pretty much always to cause these gamecube exploits to work! Now where did that 01 come from though, our edited save did not have a 01 at the end of it after the "RI", so again thanks to dolphins memory tab I could look at what r4 (the source memory) is and set a breakpoint on that.Warning: Spoilers inside!
So well, it led me to the function that gets called right when it loads the save the first time, and the copied name is actually a fixed length:
which gets copied to 0x1B0 of r30 here, and then the little 01 gets set right at the end of the load function:Warning: Spoilers inside!
Well that just gets copied from 0x10D of the save, so I go ahead in my hex editor, change that to 80 (start of ram) like this:Warning: Spoilers inside!
as you can see in the bottom right its 0x10D from the name, save that, load up dolphin again and...!Warning: Spoilers inside!
wait WHAT? how did that happen! Well I ended up setting yet another memory breakpoint from where that 0x10D(r31) got set which led me to this little code bit:Warning: Spoilers inside!
Ouch! So the first possible entry I thought I had turns out to be not possible because it actually verifies that that particular byte has to be 01 and nothing else, if it isnt, it displays loading failed! Now, at this point it seems like the "change name" option wont be a possible entry, thats a bit sad, but because of how obviously broken the game seems to be at points I decided to not give up and just fill the save with some more garbage values to see if anything else is coded up questionably.Warning: Spoilers inside!
I did also return the 80 back to a 01 so it would load at all, and well, it did load, I clicked around and suddenly yet another crash popped up at me when clicking on edit profile:Warning: Spoilers inside!
well I then went in yet again, starting to read what happened, looked at the code in detail and the registers that caused this, ending me up with this code bit:Warning: Spoilers inside!
As you can see from my comments, it basically ends up taking a bit of the value we just edited in, and uses that as an index to another address to load from, I did look at what that address gets used for and did not see anything down that function tree that could lead to any actually useful exploit, yet again. I even tried putting in a "valid" wrong value into that bit we have full control of and nothing happened, it did not end up helping us. So of course I had to continue filling up the save with a little more garbage, and this time I did not have to do a whole lot to break it at all, just add a tiny bit more:Warning: Spoilers inside!
get back into the game, click around more options, and now when I hit "load profile" I get hit with you guessed it, a 3rd crash!Warning: Spoilers inside!
So for a 3rd time now I went and looked at another failed piece of code, and suddenly everything turned to get very exciting:Warning: Spoilers inside!
suddenly, we are in control of a string that gets copied onto the stack, which if big enough will overflow the stack and suddenly lets us modify where the code should jump to next! So finally on crash number 3, we actually seem to encounter an actually useful crash for us, thats how it often goes for me, I do find crashes in lots of games but they dont seem to lead anywhere so finding one that does is always great.Warning: Spoilers inside!
So now knowing how the formula works I first set a breakpoint in dolphin right at 802B5E9C to see what r3 actually gets loaded with before our controlled value gets added onto it, leading to this:
This indeed shows that r4 is a value we chose and r3 is some address in gamecube ram, great! Now, to see where I wanted to redirect that to I dumped the entire gamecube ram in dolphin and in a hex editor looked for a good spot that we have control over, I tried many different things but in the end chose to go with the save name left over on stack by some previous function:
So how do we get from 80de12b8 to 8053c414 or 8053c418? Well, you saw me comment on the math above, now I just went ahead and just tried different values until I had a possible value we can use to make this happen:Warning: Spoilers inside!
So at this point as a proof of concept I did lots of work with a hex editor to verify that this crash is usable which it was, but instead of recreating that for this blog I'll just point to my released code from this point as it will be better to see whats going on. As you can see, with a custom value of FFF46FDE we end up pointing right to the name on stack:Warning: Spoilers inside!
Now I changed that name to an address to point to a string in memory we choose to be big enough to change the code position that function returns to, the value I found best for that is right at 80DE2768 because it has a large chunk of the save stored there that we can just freely edit:
You can see from an earlier picture of the save that yep, thats indeed how the save looks too.Warning: Spoilers inside!
So I just change the save "name" to that memory position:
And fill it with a string large enough to overflow up to the point where we can edit where the code jumps to:
You may notice that that string is pretty packed with much more than just some code address to jump to, thats because I put the actual loader code much further down into the save which ends up being in a very high memory position that I dont trust being consistent, so I instead went for a custom bit of machine code that basically searches for that loader code and then jumps to it.
To make all this happen, a few conditions have to be met. First, I need some bit of code that actually jumps to what is essentially code on stack, since we overflowed the stack thats where our code is right now, for that I chose 802BFE24:
Now here is that bit of relevant code:
r28 was chosen by us on the stack we overflow, and I did choose that to be the exact location on stack where this function will execute code from:Warning: Spoilers inside!
I know that location because I set a breakpoint right at that location in dolphin and just looked at the stack memory. Now what does that ICBlockInvalidate do? Well, you see because it just copied our code over as a string, it is in the processor data cache. Code however is in the processor instruction cache, so that function basically writes the data cache to main ram and prepares the instruction cache to read from it, thats why it was so important to choose this exact function to jump to. Now this bit only makes it possible to execute 0x28 bytes of code which is not quite enough for a search, so we will extend that in the code we actually place there. Also that stw instruction also overwrites part of our code with garbage so we will have to work around that too.
Lets quickly talk about the massive limitations this code has to have to work at all.
You see, strcpy sees a "string" to end when it finds a byte with a value of 0x00, so in order for our overflow string to copy code over to the stack, that code has to have absolutely no 0x00 byte or it would get cut off short. This turns out to be not a very easy task, I mean just look at the bytes of the code above I just showed you:
I think you see the problem immediately So yeah, this is the bit of machine code I came up with:Warning: Spoilers inside!
Even if you cant read machine code like this you can probably see how weird it looks just compared to everything else you've seen so far, this is because I had to look at each instruction carefully and make it so it contains no 0x00 bytes when compiled which wasnt easy but hey, I did it in the end:
So you know how I said 0x28 bytes werent enough? Well, this first bit of code basically flushes out the data cache for a big area of 0x140 bytes, which is more than enough for us:Warning: Spoilers inside!
From there, we jump over that corrupted instruction I talked about earlier:
And then this entire rest of the code:
Basically searches for a "magic word instruction" that I chose, "mfmsr r4", which in bytes is 7C8000A6 which you can see in that code above basically checking every 4 bytes if it finds that exact set of bytes, and when it does, it finally jumps to it. If you wonder where it jumps to, it is of course the common loader which I wrote a previous blog entry about. That common loader then finally loads a homebrew executable from memory card and executes it, which I do of course have a short clip of:
After all that I of course had to go through the steps to find the memory locations again for the PAL version and also for the different languages you can boot the game in, which went pretty smooth now that I knew exactly what was going on.
Man this entry got longer than I initially expected, just like all the other entries I wrote so far, never expect them to get this long, so if you made it this far, thank you for reading.
You need to be logged in to comment