Difference between revisions of "Wii Savegame Parser"
(First revision of the script; Nothing encryptionwise figured out so far) |
m (Added Mac Address Information) |
||
Line 23: | Line 23: | ||
==Program ID== | ==Program ID== | ||
− | After another fixed magic LONG (0x00010000) the savegame's parent program's ID is stored in another LONG. | + | After another fixed magic LONG (0x00010000) the savegame's parent program's ID is stored in another LONG. Then follows the Wii's MAC address in another LONG. Another fixed LONG (0xF5550000) completes another 16 byte block. |
==First Hash== | ==First Hash== | ||
Line 78: | Line 78: | ||
readLong('Magic',0x00010000); | readLong('Magic',0x00010000); | ||
readLong('PrgID'); | readLong('PrgID'); | ||
− | readLong(' | + | readLong('MacAdd'); |
readLong('Magic',0xF5550000); | readLong('Magic',0xF5550000); | ||
readBlock('Hash1',16,'hash1'); | readBlock('Hash1',16,'hash1'); |
Revision as of 20:20, 24 January 2007
Overview
The following Perl script can decompile a Wii savedata file which was copied from the Wii to an SD card. These are stored in files called data.bin. The script only decompiles the data.bin file. It does not decrypt the files contained therein to plaintext nor does it encrypt the plaintext again and reassemble the data.bin file. So it cannot use it to create data.bin's that can be copied over to the Wii again.
Structure of data.bin files
All multiple byte numbers (e.g. LONG = 4 bytes) are encoded in big endian format. While some information in the data.bin is plaintext and can therefore be used by the script below to parse the bin-file. The two encrypted blocks of data are the header and the actual savegame files stored in the data bin. LONGs or BYTEs that are fixed and therefore have he same value in each data.bin file are called magics.
Header
The header is one big block of encrypted data at the beginning of the data.bin. It consists of 64 byte blocks and ends in front of the first 64 byte block starting with 0x00000070 followed by 0x42B0001.
Basic File Information
The ID number of the Wii the data.bin was encoded on follows after that, then a LONG with the number of files contained in the data.bin. This concludes the first 16 bytes after the header.
Data Size
The next 16 bytes contain one LONG with the size of the block of encrypted file data, two zero LONGs and one LONG describing the number of bytes from the end of the header (including 0x00000070) up to the end of file. A 64 byte of zeros follows the data size information.
Program ID
After another fixed magic LONG (0x00010000) the savegame's parent program's ID is stored in another LONG. Then follows the Wii's MAC address in another LONG. Another fixed LONG (0xF5550000) completes another 16 byte block.
First Hash
A 16 byte hash of unknown purpose.
Files
The data.bin contains as many files as indicated by the number of files in the Basic File Information. Each file starts with a file header begining with a magic LONG 0x03ADF17E and then a LONG describing the individual file size in bytes. Three BYTE magics 0x34, 0x00, 0x01 are followed by a zero-terminated string containing the file's name (e.g."zeldaTp.dat"). A block of of 117 minus the string's length artificially increses each single file header's size to 128 bytes. Then follows the actual encrypted file content followed by a block of randon data to force the file data to fit into complete 64 byte blocks.
Certificate Information
After the file data the certificates that were (probably) used to encrypted and should be used to decrypt the data are given. A 60 byte hash is followed by two LONG magics (0x00000000, 0x00010002) and another 60 byte hash. Then follows a 64 byte block of zeros and a 64 byte zero-terminated string ("Root-CA00000001-MS00000001") patched with zeros. Another magic (0x00000002) is followed by a string containing the Wii's ID in ASCII ("NG0???????"). This is followed by a 64 byte hash and a 60 byte block of zeros. Then follows a magic (0x00010002), a 60 byte hash, a 64 byte block of zeros and another 64 byte string ("Root-CA00000001-MS00000002-NG0???????") followed by a 64 byte block of zeros. A 0x00000002 magic is followed by another string ("AP0000000100000002") followed by 64 zeros. Then follows a 0x00000000 magic and a 60 byte hash which is finally concluded by 60 zeros bytes till the end of file.
Scirpt
This Perl script will decompile a data.bin file displaying the values of the LONGs and BYTEs and sizes of hashes, strings and data blocks. The hashes and data blocks will furthermore be saved in single binary files with according suffixes.
#!/usr/bin/perl #----------------------------------- # Wii Savegame Parser # written by Lockhool # for #wiidev @ EFnet #----------------------------------- use strict; use Fcntl; sub readLong($;$); sub readByte($;$); sub readString($); sub readUpto($$); sub readBlock($$;$); sub readEof(); my $file_in=shift; die("\n Usage: ./wiiparse.pl <datafile>\n\n") unless(sysopen(IN,$file_in,O_RDONLY)); my $add=0; my $in; readUpto('Header',0x00000070); readLong('Magic',0x426B0001); readLong('WiiID'); my $numfiles = readLong('NumFiles'); readLong('FileDataLen'); readLong('Magic',0x00000000); readLong('Magic',0x00000000); readLong('PostHeadLen'); readBlock('Zeros',64); readLong('Magic',0x00010000); readLong('PrgID'); readLong('MacAdd'); readLong('Magic',0xF5550000); readBlock('Hash1',16,'hash1'); for(1..$numfiles){ readLong('Magic'.$_,0x03ADF17E); my $filesize = readLong('Filesize'.$_); readByte('Magic'.$_,0x34); readByte('Magic'.$_,0x00); readByte('Magic'.$_,0x01); my $strlen = readString('Filename'.$_); readBlock('StrFiller'.$_,117-$strlen,'f'.$_.'sfill'); readBlock('Filedata'.$_,$filesize,'f'.$_.'data'); readBlock('DataFiller'.$_,$filesize % 64,'f'.$_.'dfill'); } readBlock('Hash2',60,'hash2'); readLong('Magic',0x00000000); readLong('Magic',0x00010002); readBlock('Hash3',60,'hash3'); readBlock('Zeros',64); my $strlen = readString('RootCA'); readBlock('Zeros',64-$strlen); readLong('Magic',0x00000002); my $strlen = readString('NG'); readBlock('Zeros',64-$strlen); readBlock('Hash4',64,'hash4'); readBlock('Zeros',60); readLong('Magic',0x00010002); readBlock('Hash5',60,'hash5'); readBlock('Zeros',64); my $strlen = readString('RootCA-MS-NG'); readBlock('Zeros',64-$strlen); readLong('Magic',0x00000002); my $strlen = readString('AP'); readBlock('Zeros',64-$strlen); readLong('Magic',0x00000000); readBlock('Hash6',60,'hash6'); readEof; close(IN); sub readLong($;$){ printf("% 12u : ",$add); my $name=shift; my $val=shift; die("!! '$name' premature EOF !!\n") unless(4==sysread(IN,$in,4)); $in=unpack("N",$in); $add+=4; printf(" LONG '$name' 0x%08X (%u)\n",$in,$in); if(defined $val){print(' 'x17); if($val==$in){print('==');}else{print('!=')} printf(" 0x%08X (%u)\n",$val,$val); } return($in); } sub readByte($;$){ printf("% 12u : ",$add); my $name=shift; my $val=shift; die("!! '$name' premature EOF !!\n") unless(1==sysread(IN,$in,1)); $in=ord($in); $add+=1; printf(" BYTE '$name' 0x%02X (%u)\n",$in,$in); if(defined $val){print(' 'x17); if($val==$in){print('==');}else{print('!=')} printf(" 0x%02X (%u)\n",$val,$val); } return($in); } sub readBlock($$;$){ printf("% 12u : ",$add); my $name=shift; my $cnt=shift; my $file=shift; if(defined $file){$file=$file_in.'.'.$file} my $size=$cnt; if(defined $file){ die("!! Can't open $file !!\n") unless(sysopen(OUT,$file,O_WRONLY|O_CREAT));} while($cnt!=0){ die("!! '$name' premature EOF !!\n") unless(1==sysread(IN,$in,1)); if(defined $file){syswrite(OUT,$in,1);} $add++; $cnt--;} if(defined $file){close(OUT);} printf(" BLOCK '$name' ends after %u bytes\n",$size); return($size); } sub readString($){ printf("% 12u : ",$add); my $name=shift; my $upto=shift; my $size=0; my $string=''; my $in; while(1){ die("!! '$name' premature EOF !!\n") unless(1==sysread(IN,$in,1)); $add+=1; $size+=1; $string=$string.unpack('a',$in); if(ord($in)==0){last;} } printf("STRING '$name' ends after %u bytes\n",$size); print(' 'x15 ."\"$string\"\n"); return($size); } sub readEof(){ printf("% 12u",$add); my $name=shift; my $upto=shift; my $size=0; my $in; while(1){ last unless(0!=sysread(IN,$in,1)); $add++; $size++;} printf(" - %u : EOF reached after %u bytes\n",$add,$size); return($size); } sub readUpto($$){ printf("% 12u : ",$add); my $name=shift; my $upto=shift; my $size=0; my $in; while(1){ die("!! '$name' premature EOF !!\n") unless(4==sysread(IN,$in,4)); $add+=4; $size+=4; if($upto==unpack("N",$in)){last;}} printf(" UPTO '$name' ends after %u bytes\n",$size); return($size); }