Super Mario Galaxy savefile

From WiiBrew
Jump to navigation Jump to search

The game Super Mario Galaxy saves player data into a file called GameData.bin stored within the game's savegame.

The GameData.bin file consists of a header, followed by an index and some data sections.

Header

Start End Length Description
0x000 0x003 4 Checksum
0x004 0x007 4 ?? Version = 2
0x008 0x00B 4 number of entries
0x00C 0x00F 4 size of GameData.bin

The checksum is calculated as follows, with buf pointing to offset 0x04 in the file, and len being the length of GameData.bin minus 0x04:

uint32_t generate_checksum(void *buf, int len)
{
    uint16_t *data = (uint16_t*)buf;
    uint32_t c1 = 0, c2 = 0;

    for (int i = 0; i < len / sizeof(uint16_t); i++)
    {
        c1 = (c1 + data[i]) & 0xFFFF;
        c2 = (c2 + ~data[i]) & 0xFFFF;
    }

    return (c2 & 0xFFFF) | ((c1 & 0xFFFF) << 16);
}

Index

The index has as many entries as specified in the header. Index entries are stored consecutively. The format of an index entry is specified in the following table.

Start End Length Description
0x000 0x00B 12 entry name, null padded (i.e. 'mario1', 'luigi1', ...)
0x00C 0x00F 4 offset of entry data in GameData.bin

Data

There are at least 4 types of data entries:

  • Player data is stored in PLAY data entries
    • 'mario%1d' and 'luigi%1d' entries have a length of 0xF80 bytes
  • Configuration data is stored in CONF data entries
    • 'config%1d' entries have a length of 0x60 bytes
  • System configuration is stored in SYSC data entries (there is usually only one SYSC data entry)
    • 'sysconf' entries have a length of 0x80 bytes


Start End Length Description
0x000 0x003 4 ?? entry type code (0x01010000=SYSC, 0x01030000=CONF, 0x01060000=PLAY)
0x004 0x007 4 entry type name ('SYSC', 'CONF', 'PLAY')
... ... ... ...


Vulnerabilities

Super Mario Galaxy (and its sequel, Super Mario Galaxy 2) are known to have an unexploitable vulnerability in parsing GameData.bin. The entire file is loaded into memory at once, and after being verified for integrity (checksum, version, etc.), its index entries are extracted. The game keeps an in-memory template of an empty savefile, with the index entries being copied into the template. A function is used (on both GameData.bin and the template) to get the data pointer and length of an entry.

This function calculates the length of an index entry by subtracting the current entry's offset from the next entry's offset. Unfortunately, the length from GameData.bin is not used for the memcpy() into the template: the length from the template is used. However, once the memcpy() completes, the game checks if the amount of data specified by GameData.bin is less than the amount copied. This is done by checking whether template_size - reported_size > 0. It is possible to make reported_size negative (>= 0x80000000), which would cause that check to return true. If true, it memset()s that amount of 0 bytes after the copied data.

Unfortunately, the layout of the heap and the fact that it is all 0 bytes makes this vulnerability apparently useless.