Since as early as March, I've been working non-stop on hacking and reverse engineering the Switch alongside extremely talented hackers/developers such as plutoo, derrek, yellows8 and SciresM.
Together we have achieved incredible milestones and I'm really glad all our hard work eventually paid off.
That said, let's move on to the reason for this post: the Wii U.
If you are one of those few
Back then, they demonstrated their own exploitation path for taking down both the PPC and ARM processors on the Wii U, which was something that had already been publicly achieved using different bugs/exploits. This was also achieved much earlier by the hacking group fail0verflow which showcased their findings during the 30th edition of the CCC (back in 2013).
It was a very cool talk all around, but the main highlight for Wii U hacking fans was something derrek brought up: boot1.
This particular piece of the Wii U's boot chain was never obtained up until derrek and his team (plutoo, yellows8, smealum and naehrwert) successfully launched an hardware based attack that resulted in dumping the boot1 key.
The setup for this attack remained private, but the overall exploitation process was explained during the talk and is also documented here: http://wiiubrew.org/wiki/Wii_U_System_Flaws#boot0
That was the last nail in the Wii U's coffin... or was it? Naturally, after obtaining the boot1 binary, derrek and his team began looking for vulnerabilities in it. While most of the usually critical stuff (signature handling, file parsing, etc.) was found to be safely implemented, derrek, plutoo, yellows8, naehrwert and shuffle2 did find one potential bug. However, due to lack of motivation and time, it remained just a theory.
I even wrote a blog post about all this where I mistakenly assumed some things that weren't true. Then I issued another blog post apologizing for said assumptions... Those were confusing times. :P
However, that's what got me to chat with derrek and get to know him better.
After some discussions about boot1, derrek agreed on sharing with me the potential bug that was mentioned during CCC. His team was already getting ready for the Switch release and they had little to no time left to work on trying to exploit this bug, so I offered my help.
A few days later I found a way to exploit this bug and achieved boot1 code execution! Neat, huh?
So, without further ado, I present you a writeup on the mythical boot1hax. :D
NOTE: Obtaining the boot1 binary in the first place is out of scope for the purposes of this writeup.
The Bug
The bug itself is really simple.After some hours reverse engineering the boot1 binary and using the information derrek had shared with me, finding the bug was straightforward.
However, to understand it, we have to look into a specific IOSU process: IOS-MCP.
Wait, IOS-MCP? That loads AFTER boot1, what could possibly relate them?
Turns out, IOS-MCP manages something that plenty of people have already looked into (and maybe even guessed it's purpose), but couldn't exactly undestand what it does.
There is a range of physical memory mapped in IOSU that appears to serve no clear purpose: 0x10000000 to 0x100FFFFF (see http://wiiubrew.org/wiki/Physical_Memory).
The typical layout of this region is as follows:
Breaking it down, we have a pattern filling the first 0x400 bytes followed by NULL bytes up until this PRSH/PRST structure:
This structure is created by IOS-MCP to keep track of the addresses and sizes of several memory regions. It is encapsulated with a header (PRSH) and a footer (PRST) and contains an array of structures describing memory regions.
In the latest firmware version, only 7 regions are registered in this structure. Here's an example parsed from my console:
While most of these regions contain nothing particularly interesting, there is one exception: boot_info.
This region stores data passed along from boot0 and boot1 to IOS-MCP! An example from my console:
As an example, the last 8 fields contain the time spent on each boot0/boot1 stage and this data is printed on crash logs.
What derrek and his team found out by looking at the boot1 binary is that this structure is also passed back to boot1!
How? Well, right before a reset is asserted, IOS-MCP encrypts the entire 0x10000400 to 0x10008000 range with the Starbuck ancast key. When the console reboots, RAM contents are not cleared and boot1 will decrypt this range and parse the PRSH/PRST struct looking for the boot_info region.
Even though this might look like a weird way to pass data back and forth across the boot chain, this process is actually properly implemented and boot1's code for parsing the PRSH/PRST structure is sound. But there is one exception...
On coldboot, where the RAM contents are cleared, it's boot1 that creates boot_info for the fist time at the hardcoded address 0x10008000 and inserts this information into the PRSH/PRST structure. However, on a warmboot, boot1 decrypts and parses the already existing PRSH/PRST structure in RAM which might have been changed by IOS-MCP. This means that boot1 actually locates the boot_info section inside the PRSH/PRST structure and uses the pointer stored there to read/write the actual data.
This shouldn't be a problem, but they forgot to validate the pointer to boot_info data, which means that if IOS-MCP changes it, boot1 will attempt to read/write the boot_info data from any address we want (instead of 0x10008000)!
What derrek and his team were able to verify is that changing the pointer for boot_info to an address within boot1's stack region would crash boot1. A plausible exploitation path here would be to take advantage of whatever boot1 writes into boot_info to overwrite some LR address stored in boot1's stack and gain code execution.
Unfortunately, this is very impractical. Turns out, the way boot_info is parsed and modified by boot1 is very, very restricted.
The Exploit
So, we have this weird small structure that boot1 uses to communicate with IOSU (and vice versa) and we can control the pointer for said structure to force boot1 to read it from anywhere we want. The only way to tell if this can be exploited or not is to know exactly why this boot_info structure exists and how boot1 handles it, so I'm going to cheat and jump straight to a breakdown of how it's done:As you can see, there's not much going on. The PRSH/PRST structure is decrypted from RAM and boot1 tries to locate boot_info inside it. If it fails, a new boot_info entry is created and inserted into the PRSH/PRST structure, but the pointer for it's data will be hardcoded to 0x10008000, thus causing any subsequent reads/writes to occur in a perfectly safe address range. However, if it does find a preexisting boot_info entry, boot1 will fetch it's data from the pointer stored inside the PRSH/PRST structure and this is what we are interested in causing.
The attack plan is simple:
- Use any exploit we want and escalate to IOS-MCP (or even better, to the IOSU's kernel);
- Craft/modify the PRSH/PRST structure in memory using a modified boot_info pointer;
- Encrypt the PRSH/PRST structure with the Starbuck ancast key and boot_info IV (stored at 0x050677C0 in IOS-MCP);
- Force a reboot.
As expected, we can force boot1 to take a modified boot_info pointer and start reading the data from anywhere we want. Now comes the real challenge: where should we point to?
We must focus on the boot_info fields that are always modified by boot1, but we also need to take into account how boot1 tells if boot_info already exists or not. This happens in sub_D40AF10 and it goes like this:
- Each PRSH/PRST section is parsed and it's name is compared against the string "boot_info";
- If the boot_info section is found, it's size is checked and it must be 0x58;
- Finally, the boot_info_04 field must have bit 0x80 (big endian) set.
This last check is very important since boot1 only accepts a preexisting boot_info structure if the word at offset 0x04 has that specific bit set.
It's now clear that we can achieve a semi-arbitrary write in boot1 by abusing this particular bug. All we need is to change the boot_info pointer inside our crafted PRSH/PRST into something that resembles a valid boot_info structure from boot1's point of view. This would then result in boot1 updating those boot_info fields listed before and, therefore, write data to an arbitrary address.
Let's leave the "boot_info_04 field must have bit 0x80 set" aside for a while and focus on which fields might be useful to write into boot1's address space:
- boot_info_08: this is only modified by boot1 when a specific RTC event has occurred.
- boot_info_38 to boot_info_54: these are used to store the time spent on the various boot0/boot1 stages.
- boot_info_0C: this is increased by 1 each time a warmboot occurs (gets set back to 0 on a coldboot).
To be more precise, there are indeed a few more fields that are modified by boot1, but these are always set to either 0 or -1 in a way that doesn't make it really practical to choose them.
I began by focusing on the time related fields, but these just turned out to be too volatile for a reliable memory corruption. Also, boot_info_0C is not really useful either since it keeps changing on each warmboot.
What about boot_info_08? For this field to be read/written the RTC must have the SLEEP_EN flag set. Luckily, we can just force this flag to be set by calling the system call ios_shutdown(1) (see http://wiiubrew.org/wiki/Syscalls) which also happens to trigger a system reset!
Still, boot_info_08 will be overwritten with rtc_events |= (boot_info_08 & 0x101E). From blind tests, I could tell the final value that got written was always 0x0020XXXX, which means I could write a NULL byte followed by 0x20 and whatever was in the lower bits of boot_info_08. This is far from optimal... :\
Took me about 2 afternoons of reading boot1's binary until I finally found the perfect place to corrupt:
This particular snippet is the epilogue of sub_D40AC30, which runs immediately after boot_info_08 is modified. Doesn't look particularly interesting, but let's check the hex:
Jackpot! Since we are running way before MMU is set up and all that, we can do unaligned memory reads/writes just fine, so, if I change the boot_info pointer to 0x0D40AC6D, boot1 will see the following structure:
- boot_info_00: 0x00BC0E46
- boot_info_04: 0x93469D47
- boot_info_08: 0x080000XX
Since boot_info_04 has bit 0x80 (big endian) set, boot1 will overwrite boot_info_08 with 0x0020XXXX. Why is this important? Because we now have mutated the instruction BX R1 into BX R0 and R0 is 0.
This means boot1's execution will fall into NULL. Normally this isn't very exciting, but in the Wii U's case the physical address range 0x00000000 to 0x01FFFFFF maps to MEM1 (which is frequently used for graphics).
This memory range is not cleared on a warmboot either so, as long as boot0 and boot1 leave it alone, we can actually plant our payload there and have arbitrary code execution going!
It's known that boot0 doesn't touch any relevant RAM regions, but what about boot1? Well, boot1 actually accesses the three RAM regions (MEM0, MEM1 and MEM2):
- MEM0 is fully cleared by boot1 as soon as it starts;
- MEM2 is left untouched, with the exception of the first 0x400 bytes at address 0x10000000 which are filled with a binary pattern for testing RAM self-refresh;
- MEM1 is also left untouched, with the exception of a single word (0x20008000) being written at address 0x0000000C for unknown reasons.
As long as our payload starts right away with a jump over address 0x0000000C, we are good!
So, I cook up a small payload to copy boot1 from SRAM into some unused MEM2 region, patch the corruption I just caused, jump back to where execution fell to NULL and let boot1 finish. Now I just need to escalate into IOS-MCP or IOSU's kernel and fish out the binary!
As a bonus, this particular method allows me to hijack execution before the 2 mysterious OTP blocks get locked (see http://wiiubrew.org/wiki/Hardware/OTP) so I can easily piggyback on boot1's OTP reading functions and get them too!
Sadly, these blocks are never used and were likely locked out as a preemptive measure so a future update could begin to use them instead of some other key material (especially since the 2 blocks are not per-console).
And there you have it, boot1 code execution from a RAM based attack!
Along with this writeup, I'll be publicly documenting boot1 over at http://wiiubrew.org and I'm releasing a patch for my long forgotten project hexFW that gives you the option to dump your console's boot1 and unlocked OTP: https://github.com/hexkyz/hexFW/commit/f52f85f683dfcef0544f8ddb3643cef5cfa2ee86
NOTE: This does not include the boot1 AES key, since that one is long gone by the time we are running code in boot1!
What about CFW? Well this attack on it's own doesn't really help you there.
In order to get a custom firmware running straight from boot1 another kind of attack is preferred, specially something that actually survives a coldboot. :\
Remember that this only works due to RAM not being cleared on a warmboot so it's impossible to achieve persistence this way.
However... There's one plausible vector that could be used to create a much safer alternative to current methods.
Leveraging this bug from the vWii environment, for example, could grant a nice boot(ish) time CFW by combining some form of contenthax in a way that entering vWii mode would launch the boot1hax payload, reset the console and send you right into a CFW. The total time spent on this would be minimal and it would create a dual-boot environment where you could hold down the "B" button on boot to jump into CFW or do nothing to land on the vanilla OS. That is, of course, if you wouldn't mind sacrificing your vWii channel for a while (it would then be possible to restore it from within the CFW environment, so that's not really an issue).
I've been looking into this for quite some time with derrek, but the Switch has been taking most of our time so I kept postponing this project endlessly. Regardless, I still plan on picking this up one last time during the start of this new year, but derrek and I agreed on sharing this anyway so others can also get the chance to research boot1 and hopefully find some new bugs in the process.
All this has been kept under wraps for quite a while for a very good reason: it's insanely easy to patch. Now that the Wii U reached it's EoL and the Switch is the new kid on the block, it seems appropriate to end (for good) the Wii U cycle while homebrew on the Switch is just beginning to flourish.
Retail plaintext boot1 (v8377) SHA-256: 5013BFABC578CBA08843D9A0F650171942A696CBC54DF12E754D9E0978FCD3B1
I hope you enjoyed reading this as much as I did writing it. :)
Stay safe and have fun!