First some background info, followed by a story then some code.
A lot of people know the wii contains a SEEPROM but the purpose of it doesn't seem to be very well understood. It's similar to the OTP area but it's writable; IOS actually writes to it every time it starts. It contains a few things that might (but typically aren't) need to be updated at some point (the MS and CA key IDs, the boot2 version, the NAND generation), some things which are static but don't fit in the OTP (NG specific data, korean key) and one thing that is updated very often (PRNG seed). The korean key is particularly interesting because its presence is the only thing that is different (at a low-level) between a regular wii and a korean wii - it's the cause of the 003 error (we'll come back to this later).
Even though the physical SEEPROM is apparently an embedded part of hollywood/starlet it seems to be functionally identical to an atmel AT93C56 with the ORG pin tied to VCC (16 bit word size). This chip uses a three wire serial interface, which means you talk to it by setting the data input line to the bit value you want to send then toggle the clock line and check the data out line (if you care about the output). These lines along with the chip select line are all exposed via GPIOs which are easy enough to use from Broadway if AHBPROT has been disabled to give access to the right registers. Normally however only starlet/the IOS kernel can talk to the SEEPROM and it normally only happens during IOS startup; the kernel fetches all the required info into RAM and the other parts of IOS can only access/change it via various syscalls (so even if you find an exploit in an IOS module, you won't be able to access the SEEPROM from user mode). There are other very important pieces of information from the atmel spec, such as:
- 16-bit word size, addresses are not in bytes
- self-timed write cycle, so if broadway or starlet crash in the middle of a write it doesn't matter
- no separate erase cycle needed before writing
- command/write timings
Back in 2008 when the korean wiis first appeared, the existing IOSes knew nothing about the existence of the korean key. The first one to acknowledge it was IOS37v2070 which was also the first IOS to have a fixed signature check, DLC support and quite a lot of other security fixes - before it appeared IOS was trivial to exploit because no input validation was performed, it was simple to overwrite memory and hijack code execution. But unfortunately the korean key support introduced a major flaw into their shiny new "secure" IOS. If you've ever compared DLC files from different wiis for songs downloaded in Rock Band 2 or Guitar Hero World Tour, you might notice they're almost identical. They're not actually meant to be; they're supposedly encrypted with the PRNG key which is different for every console but instead are encrypted with a key made up of all zeroes (a NULL key). I figured this out a long time ago and it was what allowed the early versions of RawkSD to create .bin files for custom songs on a PC that had no knowledge about the wii they were going to be used with. It is also what caused those same .bin files to crash when the game was played using wanky's cIOS - it used a different IOS base that used the real PRNG key instead of a NULL key. This IOS code shows the actual cause of the bug:
The read_seeprom_words function takes three arguments: the offset (in words) of the data to read from the seeprom, the destination in memory where the data will be copied to and the word_count tells how many words to copy. Remember each SEEPROM word is 16-bits (2 bytes) so this code is reading 16*2 = 32 bytes while the korean key is only 16 bytes long. The 16 bytes following the korean key in the SEEPROM are empty but the area in memory that they are being loaded into (16 bytes after dest) is where the console's PRNG key has already been loaded into - so it gets overwritten with zeroes and that is what is used to encrypt/decrypt the songs.
The funny thing is that ninty eventually noticed this mistake and changed word_count to 8 in all future IOSes but that introduced a new problem: thousands of DLC songs had already been downloaded and saved to people's SD cards using the NULL key, fixing the bug would require all those songs to be redownloaded. So they added a hack in the ES module: whenever DLC from Guitar Hero World Tour or Rock Band 2 is loaded from the SD card a NULL key is explicitly used to encrypt/decrypt it instead of the PRNG key. Thus preserving their security breach for future generations.
Getting back to the pesky 003 error: if it's caused by the presence of the korean key why not just erase it from the SEEPROM? This is pretty easy since code to read the seeprom already exists (from MINI) and the missing commands are in the atmel documentation:
A lot of people know the wii contains a SEEPROM but the purpose of it doesn't seem to be very well understood. It's similar to the OTP area but it's writable; IOS actually writes to it every time it starts. It contains a few things that might (but typically aren't) need to be updated at some point (the MS and CA key IDs, the boot2 version, the NAND generation), some things which are static but don't fit in the OTP (NG specific data, korean key) and one thing that is updated very often (PRNG seed). The korean key is particularly interesting because its presence is the only thing that is different (at a low-level) between a regular wii and a korean wii - it's the cause of the 003 error (we'll come back to this later).
Even though the physical SEEPROM is apparently an embedded part of hollywood/starlet it seems to be functionally identical to an atmel AT93C56 with the ORG pin tied to VCC (16 bit word size). This chip uses a three wire serial interface, which means you talk to it by setting the data input line to the bit value you want to send then toggle the clock line and check the data out line (if you care about the output). These lines along with the chip select line are all exposed via GPIOs which are easy enough to use from Broadway if AHBPROT has been disabled to give access to the right registers. Normally however only starlet/the IOS kernel can talk to the SEEPROM and it normally only happens during IOS startup; the kernel fetches all the required info into RAM and the other parts of IOS can only access/change it via various syscalls (so even if you find an exploit in an IOS module, you won't be able to access the SEEPROM from user mode). There are other very important pieces of information from the atmel spec, such as:
- 16-bit word size, addresses are not in bytes
- self-timed write cycle, so if broadway or starlet crash in the middle of a write it doesn't matter
- no separate erase cycle needed before writing
- command/write timings
Back in 2008 when the korean wiis first appeared, the existing IOSes knew nothing about the existence of the korean key. The first one to acknowledge it was IOS37v2070 which was also the first IOS to have a fixed signature check, DLC support and quite a lot of other security fixes - before it appeared IOS was trivial to exploit because no input validation was performed, it was simple to overwrite memory and hijack code execution. But unfortunately the korean key support introduced a major flaw into their shiny new "secure" IOS. If you've ever compared DLC files from different wiis for songs downloaded in Rock Band 2 or Guitar Hero World Tour, you might notice they're almost identical. They're not actually meant to be; they're supposedly encrypted with the PRNG key which is different for every console but instead are encrypted with a key made up of all zeroes (a NULL key). I figured this out a long time ago and it was what allowed the early versions of RawkSD to create .bin files for custom songs on a PC that had no knowledge about the wii they were going to be used with. It is also what caused those same .bin files to crash when the game was played using wanky's cIOS - it used a different IOS base that used the real PRNG key instead of a NULL key. This IOS code shows the actual cause of the bug:
Code:
LOAD:FFFF1D20 read_korean_key ; CODE XREF: load_all_keys+7Cj
LOAD:FFFF1D20 ; DATA XREF: LOAD:off_13A7976Co
LOAD:FFFF1D20 PUSH {R4,R5,LR}
LOAD:FFFF1D22 ADDS R4, R0, #0
LOAD:FFFF1D24 BL os_disable_interrupts_thunk
LOAD:FFFF1D28 ADDS R5, R0, #0
LOAD:FFFF1D2A BL init_otp_data
LOAD:FFFF1D2E CMP R0, #0
LOAD:FFFF1D30 BEQ loc_FFFF1D48
LOAD:FFFF1D32 MOVS R0, #0x3A ; offset
LOAD:FFFF1D34 ADDS R1, R4, #0 ; dest
LOAD:FFFF1D36 MOVS R2, #0x10 ; word_count
LOAD:FFFF1D38 BL read_seeprom_words
LOAD:FFFF1D3C
LOAD:FFFF1D3C loc_FFFF1D3C ; CODE XREF: read_korean_key+32j
LOAD:FFFF1D3C ADDS R0, R5, #0
LOAD:FFFF1D3E BL os_enable_interrupts
LOAD:FFFF1D42 POP {R4,R5}
LOAD:FFFF1D44 POP {R0}
LOAD:FFFF1D46 BX R0
LOAD:FFFF1D48 ; ---------------------------------------------------------------------------
LOAD:FFFF1D48
LOAD:FFFF1D48 loc_FFFF1D48 ; CODE XREF: read_korean_key+10j
LOAD:FFFF1D48 ADDS R0, R4, #0
LOAD:FFFF1D4A LDR R1, =default_korean_key
LOAD:FFFF1D4C MOVS R2, #0x10
LOAD:FFFF1D4E BL memcpy_2
LOAD:FFFF1D52 B loc_FFFF1D3C
LOAD:FFFF1D52 ; End of function read_korean_key
The funny thing is that ninty eventually noticed this mistake and changed word_count to 8 in all future IOSes but that introduced a new problem: thousands of DLC songs had already been downloaded and saved to people's SD cards using the NULL key, fixing the bug would require all those songs to be redownloaded. So they added a hack in the ES module: whenever DLC from Guitar Hero World Tour or Rock Band 2 is loaded from the SD card a NULL key is explicitly used to encrypt/decrypt it instead of the PRNG key. Thus preserving their security breach for future generations.
Getting back to the pesky 003 error: if it's caused by the presence of the korean key why not just erase it from the SEEPROM? This is pretty easy since code to read the seeprom already exists (from MINI) and the missing commands are in the atmel documentation:
Code:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
#define HW_GPIO1OUT 0x0D8000E0
#define HW_GPIO1IN 0x0D8000E8
enum {
GP_EEP_CS = 0x000400,
GP_EEP_CLK = 0x000800,
GP_EEP_MOSI = 0x001000,
GP_EEP_MISO = 0x002000
};
#define eeprom_delay() usleep(5)
static void seeprom_send_bits(int b, int bits) {
while (bits--) {
if (b & (1 >=1;
// don't interrupt us, this is srs bsns
_CPU_ISR_Disable(level);
mask32(HW_GPIO1OUT, GP_EEP_CLK, 0);
mask32(HW_GPIO1OUT, GP_EEP_CS, 0);
eeprom_delay();
// EWEN - Enable programming commands
mask32(HW_GPIO1OUT, 0, GP_EEP_CS);
seeprom_send_bits(0x4FF, 11);
mask32(HW_GPIO1OUT, GP_EEP_CS, 0);
eeprom_delay();
for (i = 0; i < size; i++) {
send = (ptr[0]