Friday, May 18, 2012

Writing a Savegame Exploit for Wii From Start to Finish

In this post I'm going to go into great detail of writing a savegame exploit for Wii from start to finish.If this does interest you, then by all means please continue reading after the break.

Note that this exploit was never finished, but I'm releasing my notes on it in case anyone might find it useful.

Preface

Writing this article has been rolling around in my head for almost six months now. Since Wii homebrew currently lacks an exploit which works on every system menu version without the need for a game it is absolutely necessary to have as many options available to users in order to eliminate the stress and pain of hunting for a specific game. I've learned a great deal about the process of writing a savegame exploit for Wii by talking with svpe, AerialX, megazig, Treeki, and many others and in the past made a bit of progress poking around with their assistance. I've really, really wanted to go through and do a completely original new savegame exploit for quite some time now meanwhile being able to share my knowledge and insight I've gained on the process. So I finally decided to drop DOP-Mii, DeSmuME, and my other projects for awhile to follow through on this idea. Enter: This article. Along with trying to make this as insightful and useful as possible, I really hope that everyone enjoys reading it as much as I did writing it.

Picking a Target Game

The easiest way to exploit a savegame is to do so by creating a buffer overflow. This is done by taking a string stored in the file and giving it more characters than it's maximum allowed amount. So when picking a game to create an exploit for you should choose one that you know stores strings in order to minimize your risk of wasting your time for nothing. My target game is going to be Silent Hill: Shattered Memories not only because it's a cool and fairly popular game, but because I know the savefile stores at least one string which the game will not let me increase the length of infinitely (the player's name).

Obtaining an ISO

The easiest way to obtain an ISO of your target game is to run emukidid's CleanRip which will rip an ISO from your disc right to your attached USB Hard Drive.

Obtaining the main.dol

After getting the ISO we need to get the main.dol, the main executable, from it. To do this open the ISO with WiiScrubber and extract it.



Obtaining an Unencrypted Savegame

The first step to getting a savegame is to, well, play the game. I recommend you put at least several hours of play time in so you fill up your savefile with lots of interesting content.

Unfortunately, you can't just drop a savegame onto your SD Card and then presume to play with it. The savefile is encrypted when it is moved to the SD Card so any changes you make to it will cause it to not be recognized by the Wii. Luckily, savegames are stored unencrypted on the Wii's NAND.You can use my Savegame Manager MOD tool to copy an unencrypted savegame  from the Wii's NAND to your SD Card. You can put it back onto your Wii by using Savegame Manager MOD to transfer it back.

There is a ton of readable plaintext including literally about a hundred strings in the Silent Hill Shattered Memories save file. I could read the names of the players I entered and I could see names of pictures and other items I had acquired as well as the names of events I had chosen and places I had been. I tried editing the strings and using the edited savefile. Silent Hill spewed out an error that the data was corrupt and could not be read. This meant that there was a checksum present in the savefile. A checksum is a value calculated by running data present in the savefile through an algorithm. When Silent Hill loads a savefile it runs the checksum algorithm and then looks at the checksum in the savefile and sees if the checksums match eachother. If they match, Silent Hill runs the savefile; If not, it spits out an error about the data being corrupt.  Despite needing to figure out how the checksum is calculated, this is a very encouraging sign. If you're familiar with hashing, you can think of a checksum has the hash produced by hashing a certain section (or all) of the data.

Reversing the main.dol in IDA Pro into PPC Assembly

To uncover how checksums are calculated in Silent Hill we'll use IDA Pro to reverse the main.dol into PPC assembly and study the assembly to uncover the checksum algorithm. In order to use IDA Pro to reverse the main.dol you're going to need the DOL Loader and Gekko CPU extension installed. There are copies of them here but if those aren't correct for your version of IDA or you can't compile with the IDA SDK then come on over to #wiidev on EFNET and someone should be able to help you out. If necessary, you should be able to use versions of them lower than your version of IDA Pro and they will work. Place the DOL Loader in IDA/loaders and place the Gekko CPU extension in IDA/plugins .

Now once everything is setup properly it's time to reverse the main.dol into PPC assembly with IDA. Start IDA Pro and select to disassemble a new file. Goto the file dropdown menu and select to load a file. Load the main.dol and make sure it is recognized as Gamecube DOL and finally select Ok to the prompts to load it as one. Once it is loaded successfully by IDA, you should see something like this:


Now that the main.dol has been reversed into PPC assembly there are a number of techniques we can use to locate where the checksum is calculated. One such method would be searching for text with Read, or Write in it. Personally, I think the most accurate and easiest way is to use Megazig's Wii Tools.

Using Megazig's Wii Tools

In order to use them you first have to compile them. If you're on Linux and you have GCC installed then just use make. If you're on Windows you can compile them under cygwin. I don't use Mac operating systems so you're on your own if you do. But it shouldn't be even remotely difficult.

Once compiled we'll run these two commands and examine their output:

./mega_info_creator --dol nand.mega main.dol
./mega_info_creator --dol nand2.mega main.dol

This is the output I received:

NANDRead at 0x80244280
36 nandIsInitialized 80245f90
60 ISFS_Read 802418b0
64 nandConvertErrorCode 80245fb0
NANDWrite at 0x80244280
36 nandIsInitialized 80245f90
60 ISFS_Write 802418b0
64 nandConvertErrorCode 80245fb0
nandComposePerm at 0x80244730
NANDPrivateGetStatus at 0x80244a00
28 nandIsInitialized 80245f90
60 nandGetStatus 80244820
64 nandConvertErrorCode 80245fb0
NANDOpenAsync at 0x80244d30
16 _savegpr_27 801a6870
40 nandIsInitialized 80245f90
88 nandOpen 80244b00
92 nandConvertErrorCode 80245fb0
100 _restgpr_27 801a68bc
NANDPrivateOpenAsync at 0x80244db0
16 _savegpr_27 801a6870
40 nandIsInitialized 80245f90
88 nandOpen 80244b00
92 nandConvertErrorCode 80245fb0
100 _restgpr_27 801a68bc
nandOpenCallback at 0x80244e30
80 nandConvertErrorCode 80245fb0
NANDClose at 0x80244eb0
20 nandIsInitialized 80245f90
64 ISFS_Close 80241a50
84 nandConvertErrorCode 80245fb0
nandCloseCallback at 0x80245af0
52 nandConvertErrorCode 80245fb0
nandRemoveTailToken at 0x80245b50
80 strlen 801a622c
136 strncpy 8019d624
nandGetHeadToken at 0x80245c30
16 _savegpr_27 801a6870
72 strncpy 8019d624
116 strcpy 8019d564
144 strncpy 8019d624
176 strlen 801a622c
192 _restgpr_27 801a68bc
nandCallback at 0x802464b0
20 nandConvertErrorCode 80245fb0
nandGetTypeCallback at 0x80246820
80 nandConvertErrorCode 80245fb0
nandCalcUsage at 0x80246990
80 ISFS_GetUsage 80241220

NANDRead at 0x80244280
36 nandIsInitialized 80245f90
60 ISFS_Read 802418b0
64 nandConvertErrorCode 80245fb0
NANDReadAsync at 0x802442f0
16 _savegpr_27 801a6870
40 nandIsInitialized 80245f90
64 nandCallback
84 nandCallback
88 ISFS_ReadAsync 802418d0
92 nandConvertErrorCode 80245fb0
100 _restgpr_27 801a68bc
NANDWrite at 0x80244280
36 nandIsInitialized 80245f90
60 ISFS_Write 802418b0
64 nandConvertErrorCode 80245fb0
NANDWriteAsync at 0x802442f0
16 _savegpr_27 801a6870
40 nandIsInitialized 80245f90
64 nandCallback
84 nandCallback
88 ISFS_WriteAsync 802418d0
92 nandConvertErrorCode 80245fb0
100 _restgpr_27 801a68bc
NANDPrivateMove at 0x80244a00
28 nandIsInitialized 80245f90
60 nandMove 80244820
64 nandConvertErrorCode 80245fb0
nandComposePerm at 0x80244730
NANDPrivateGetStatus at 0x80244a00
28 nandIsInitialized 80245f90
60 nandGetStatus 80244820
64 nandConvertErrorCode 80245fb0
NANDOpenAsync at 0x80244d30
16 _savegpr_27 801a6870
40 nandIsInitialized 80245f90
88 nandOpen 80244b00
92 nandConvertErrorCode 80245fb0
100 _restgpr_27 801a68bc
NANDPrivateOpenAsync at 0x80244db0
16 _savegpr_27 801a6870
40 nandIsInitialized 80245f90
88 nandOpen 80244b00
92 nandConvertErrorCode 80245fb0
100 _restgpr_27 801a68bc
nandOpenCallback at 0x80244e30
80 nandConvertErrorCode 80245fb0
NANDClose at 0x80244eb0
20 nandIsInitialized 80245f90
64 ISFS_Close 80241a50
84 nandConvertErrorCode 80245fb0
NANDCloseAsync at 0x80244f20
36 nandIsInitialized 80245f90
80 nandCloseCallback
92 nandCloseCallback
100 ISFS_CloseAsync 80241a60
104 nandConvertErrorCode 80245fb0
nandCloseCallback at 0x80245af0
52 nandConvertErrorCode 80245fb0
nandRemoveTailToken at 0x80245b50
80 strlen 801a622c
136 strncpy 8019d624
nandGetHeadToken at 0x80245c30
16 _savegpr_27 801a6870
72 strncpy 8019d624
116 strcpy 8019d564
144 strncpy 8019d624
176 strlen 801a622c
192 _restgpr_27 801a68bc
nandGetRelativeName at 0x80245d10
32 strcmp 8019d6e0
52 strcpy 8019d564
64 strlen 801a622c
124 strcpy 8019d564
nandConvertPath at 0x80245db0
40 strlen 801a622c
60 strcpy 8019d564
80 nandGetHeadToken 80245c30
92 strcmp 8019d6e0
132 strcmp 8019d6e0
152 nandRemoveTailToken 80245b50
196 strcmp 8019d6e0
224 sprintf 8019be14
252 sprintf 8019be14
284 strcpy 8019d564
nandOnShutdown at 0x802463d0
16 _savegpr_26 801a686c
44 OSGetTime 8021c000
48 nandShutdownCallback
64 nandShutdownCallback
68 ISFS_ShutdownAsync 80241af0
104 OSGetTime 8021c000
136 __div2i 801a69c0
184 _restgpr_26 801a68b8
nandCallback at 0x802464b0
20 nandConvertErrorCode 80245fb0
nandGetTypeCallback at 0x80246820
80 nandConvertErrorCode 80245fb0
NANDInitBanner at 0x802468b0
56 memset 80006284
84 wcscmp 801a1bd0
108 wcsncpy 801a1b40
128 wcsncpy 801a1b40
140 wcscmp 801a1bd0
164 wcsncpy 801a1b40
184 wcsncpy 801a1b40
nandCalcUsage at 0x80246990
80 ISFS_GetUsage 80241220

Using this output we can now see where Write and Read calls are made to the NAND. Since the checksum algorithm will have to be used just before and just after these calls are made respectively we can go back to the disassembly in IDA with a pretty clear place of where to look for the checksum.

Back to IDA Pro

The first thing I do with my dissassembly after getting info from Megazig's Wii Tools is to rename functions to give me a better sense of what's going on. For example, I jump to address 80244280 click sub_80244820, press I on my keyboard, and then rename it to NANDRead. IDA will rename every call to this subroutine NANDRead making it easy for me to remember what to search for to find each call. To make things easier on me, I ran mega_info_creator with many more .mega files and I renamed a lot of subroutines.

The savefile format used for Silent Hill is extremely confusing; There are a ton of plaintext strings embedded with lots of garbage looking data. Presumably this garbage data is somehow encrypted to further prevent modification. I'm not particularly interested in it though, all I'm really interested in is being able to edit strings. I'm still relatively new to reverse engineering Wii software so eventually I called upon the help of veteran reverse engineer Treeki.

Treeki noticed a handful of interesting pieces of data starting at address 803E0320:

.data6:803E0320         .long aEcmd_idle        # "eCmd_Idle"
.data6:803E0324         .long aEcmd_checkcard   # "eCmd_CheckCard"
.data6:803E0328         .long aEcmd_openfile    # "eCmd_OpenFile"
.data6:803E032C         .long aEcmd_closefile   # "eCmd_CloseFile"
.data6:803E0330         .long aEcmd_readfile    # "eCmd_ReadFile"
.data6:803E0334         .long aEcmd_createfil   # "eCmd_CreateFile"
.data6:803E0338         .long aEcmd_writefile   # "eCmd_WriteFile"
.data6:803E033C         .long aEcmd_seekfile    # "eCmd_SeekFile"
.data6:803E0340         .long aEcmd_deletefil   # "eCmd_DeleteFile"
.data6:803E0344         .long aEcmd_checkcrc    # "eCmd_CheckCRC"
.data6:803E0348         .long aEcmd_checkspac   # "eCmd_CheckSpace"
.data6:803E034C         .long aEcmd_createdir   # "eCmd_CreateDir"
.data6:803E0350         .long aEcmd_deletedir   # "eCmd_DeleteDir"
.data6:803E0354         .long aEcmd_msgbox      # "eCmd_MsgBox"
.data6:803E0358         .long aEcmd_confirmsa   # "eCmd_ConfirmSave"
.data6:803E035C         .long aEcmd_complete    # "eCmd_Complete"
.data6:803E0360         .long aEcmd_completes   # "eCmd_CompleteSuccess"


Of particularly interesting note to Treeki was eCmd_CheckCRC. The Wii has a 32-Bit  processor and CRC32 is a widely documented generic checksum algorithm. After doing a lot of cross referencing and tracing we found sub_801B570C. sub_801B570C calculates a CRC block of data we were able to figure out what the size of the first CRC block would be in bytes and where in the block the checksum would be placed. We quickly tried this command in Python:

import zlib
print(hex(zlib.cr32(open('C:/Store.000','rb').read()[0x10:0x760]))) 

It returned: 0xd14d6ff8 as correctly being the checksum being generated in that CRC block which was correctly verified with a hex editor. We also were able to correctly calculate the checksum in the PDSS CRC block (where a lot of strings are stored). So with two checksum algorithms uncovered which were used over a lot of strings it was finally time to move on from IDA Pro.

Using the Checksum

Once you have the checksum algorithm and the range of data it's used for, the basic idea is to edit your savedata and then recalculate and change the checksum. Prior to this project I was under the misconception that once you had a checksum algorithm all you needed was to mess around inside a hex editor with it. This is actually impossible to do in most cases and instead you'll need to write your own code to use the checksum. Due to it's simplicity, portability, and powerful nature I decided to write my code in Python. I drafted this up:

#!/usr/bin/python
#Silent Hill: Shattered Memories (Wii) Savefile Checksum Calculator and Editor
#Written by Arikado and Treeki

import zlib
import struct

#Get the checksum for the first CRC block of data
checksum = zlib.crc32(open('Store.000','rb').read()[0x10:0x760])
print(hex(checksum))
print("\n")

#Modify the savefile with the calculated checksum
f = open('Store.000','ab')
f.seek(0x0C,1)
f.write(struct.pack('>I', checksum))
f.close()

#Get the checksum for the second CRC block of data (PDSS section)
checksum = zlib.crc32(open('Store.000','rb').read()[0x770:0x770+0x4C10])
print(hex(checksum))
print("\n")

#Modify the savefile with the calculated checksum
f = open('Store.000','ab')
f.seek(0x768,1)
f.write(struct.pack('>I', checksum))
f.close()

#End

I tested this on my savefile and it was successful. Then I changed the name of my player to a name with an equal number of characters (for testing purposes), ran the program and tested the new savefile ... and ... it failed miserably. What I mean by "failed miserably" is that Silent Hill detected that the save file was modified and refused to load it. My guess was that there is yet another checksum that is calculated based on the content of the entire savefile or at least a large portion of it. This is exactly why reverse engineering is so frustrating, so much fun, and why so few people do it.

Using the Dolphin Emulator

With enough time and patience you can essentially figure out any file format or any other interesting information from a program through reverse engineering it. In IDA I graphed out every place that calls the CRC block producing function and proceeded to reverse engineer each one to figure out how it worked. It was obvious that even more backtracing would have to be done of the functions that called the CRC block producing function. tl;dr I had about at least week worth of work ahead of me before I would really get anywhere. I don't really have that kind of time or patience and probably neither does Treeki.

When I develop Wii homebrew I use my USB Gecko to log debug output onto a terminal in my PC. Wii developers do the same thing with their development units using a function of the SDK called OS_Report. The Dolphin emulator is able to log these messages sent via OS_Report. Treeki came up with the brilliant idea to add OS_Report logging to the main.dol so we could see where every CRC block is made and where every checksum is written in it. Going on my hypothesis, this would reveal what checksums are protecting the checksums we already reversed. If not, then it was back to IDA for probably at least another week of reverse engineering to find out what was making those checksums.

Treeki also found that there was a function used to send debug messages by the developers to OS_Report. This function was empty though so it was gutted out for retail compilation. The debug messages were still there, they just weren't being sent to OS_Report by the function. Because this might help us reverse the savefile and because Treeki and I are both interested in how developers think, we filled the function back in.

Identifying and Exploiting a Vulnerability

blah blah blah

Testing the Vulnerability

This section may be a bit redundant but I wanted to throw it in just to ensure absolute clarity among all readers. Testing the vulnerability is a trivial exercise done to determine if it's possible to turn the hacked savegame into a complete exploit. To test it, simply reinstall the modified savegame back into the Wii with Savegame Manager MOD. Next run the game you're working with and see if the savegame crashes the Wii. If it does, you've successfully discovered a vulnerability which can be used to create a savegame exploit. If not, you'll have to either work with the savedata a little more or give up and try a different game.

Adding a DOL Loader

blah blah blah

Modifying the Exploit to Work for Other Regions

blah blah blah

Thinking Outside the Box

There are a number of ways this article can help out fellow hackers that I want to bring to attention before closing off.

Silent Hill: Shattered Memories was also released on the PSP and PS2. I know the PSP in particular is always looking for new savegames to exploit. Perhaps some of the information in this article can be useful to PSP hackers; I certainly think it's at least worth a look. Pushing this train of thought even further, there are also other Silent Hill games on the PSP, PS2, PS3, and XBOX 360 that may be worth looking into.

Konami and developer [NAME] have released other games for the Wii. What are the chances that they use the same generic CRC32 checksum algorithm for those games too? What if there are even more striking similarities in the savefiles of other games they have released? In order to further increase the number of savegame exploits available for Wii, it should definitely be considered being investigated.

Release

Download and Source Code: http://code.google.com/p/INEEDANAME
Twitter: twitter.com/OArikadoO
IRC: #arikadosblog on EFNET


Additional Reading
Acknowledgments
  • Team Twiizers (particularly svpe and bushing for always answering my questions)
  • Megazig
  • Treeki (probably couldn't have done it without you)
  • AerialX
  • trap15
  • HACKERCHANNEL

1 comment:

  1. Very nice. How does it actually run the exploit code though?

    ReplyDelete