Espresso boot ROM
The Espresso includes a special boot ROM that checks all software it boots. It only boots software in the form of an Ancast Image.
Initialization
The Espresso boot ROM runs from address 0x00000100 where the PPC reset vector is located. It begins by setting up SPRs, mapping memory and jumping to the main routine:
// Clear MSR, TLB and TBU MSR = 0 TBL = 0 TBU = 0 // Set HID0_BHT, HID0_BTIC, HID0_SGE and HID0_SPD HID0 = 0x2A4 // Set HID4_SBE HID4 |= 0x2000000 // Set a flag in BCR when in vWii mode if ((SCR & 0x8000000) != 0 && (SCR & 0x4000000) != 0) BCR = ((BCR & 0x1FFFFFFF) | 0x20000000) __isync(); // Clear all DBATs/IBATs DBAT0U = 0 DBAT1U = 0 DBAT2U = 0 DBAT3U = 0 DBAT4U = 0 DBAT5U = 0 DBAT6U = 0 DBAT7U = 0 IBAT0U = 0 IBAT1U = 0 IBAT2U = 0 IBAT3U = 0 IBAT4U = 0 IBAT5U = 0 IBAT6U = 0 IBAT7U = 0 __isync(); // Clear all SRs SR0 = 0 SR1 = 0 SR2 = 0 SR3 = 0 SR4 = 0 SR5 = 0 SR6 = 0 SR7 = 0 SR8 = 0 SR9 = 0 SR10 = 0 SR11 = 0 SR12 = 0 SR13 = 0 SR14 = 0 SR15 = 0 __sync(); // Flush L2CR with a dummy read L2CR = L2CR __sync(); // Set L2CR_L2I L2CR |= 0x200000 // Wait for L2CR_L2IP to be cleared while ((L2CR & 0x1) != 0); // Clear L2CR_L2I L2CR &= 0xFFDFFFFF // Wait for L2CR_L2IP to be cleared while ((L2CR & 0x1) != 0); // Set HID0_DCFI and HID0_ICFI HID0 |= 0xC00 __isync(); // Configure DBAT0 // Physical address is 0 // Virtual address is 0 // Mapped as read only, cache inhibit, user valid and supervisor valid DBAT0L = 0x21 DBAT0U = 0x3 __isync(); // Configure IBAT0 // Physical address is 0 // Virtual address is 0 // Mapped as read only, cache inhibit, user valid and supervisor valid IBAT0L = 0x21 IBAT0U = 0x3 __isync(); // Configure DBAT1 // Physical address is 0xC320000 // Virtual address is 0xC320000 // Mapped as read only, cache inhibit, user valid and supervisor valid DBAT1L = 0xC320021 DBAT1U = 0xC320003 __isync(); // Configure DBAT2 // Physical address is 0xE0000000 // Virtual address is 0xE0000000 // Mapped as read write, user valid and supervisor valid DBAT2L = 0xE0000002 DBAT2U = 0xE0000003 __isync(); // Configure DBAT4 // Physical address is 0x16C0000 // Virtual address is 0x20000 // Mapped as read write, user valid and supervisor valid DBAT4L = 0x16C0002 DBAT4U = 0x20003 __isync(); // Configure IBAT4 // Physical address is 0x16C0000 // Virtual address is 0x20000 // Mapped as read write, user valid and supervisor valid IBAT0L = 0x16C0002 IBAT0U = 0x20003 __isync(); // Clear DBAT6 DBAT6L = 0 DBAT6U = 0 __isync(); // Configure DBAT7 // Physical address is 0x16E0000 // Virtual address is 0xC16E0000 // Mapped as read write, guarded, cache inhibit, user valid and supervisor valid DBAT7L = 0x16E002A DBAT7U = 0xC16E0003 __isync(); // Set MSR_DR and MSR_IR MSR |= 0x30 __isync(); // Clear L2CR_L2TS, L2CR_L2I, L2CR_L2DO and set L2CR_L2E L2CR = ((L2CR | 0x80000000) & 0xFF9BFFFF) // Set HID0_ICE and HID0_DCE HID0 |= 0xC000 // Set HID2_LCE HID2 |= 0x10000000 u32 index = 0xE0000000; u32 size = 0x200; // Set to zero data cache for the 0xE0000000 region while (size > 0) { __dcbzl(0, index); index += 0x20; size -= 0x1; } // Configure DBAT5 // Physical address is 0 // Virtual address is 0xF0000000 // Mapped as read write, user valid and supervisor valid DBAT5L = 0x2 DBAT5U = 0xF0000003 __isync(); index = 0xF0000100; size = 0x7; // Test cache flushing while (size > 0) { *(u32 *)index = 0x48000000; __dcbf(0, index); __isync(); __sync(); if (*(u32 *)index != 0x48000000) { // Deadlock sub_E0(); } index += 0x100; size -= 0x1; } // Clear DBAT5 DBAT5L = 0 DBAT5U = 0 __isync(); SRR0 = 0x758 SRR1 = MSR // Set LR to a deadlock LR = sub_E0 // Execution will jump to 0x758 return;
Main
This is the main function of the Espresso boot ROM. It is located at address 0x00000758 (where the initialization routine jumps to) and is responsible for initializing registers and memory, processing the PPC ancast image and running it.
*(u32 *)0xC16FFFFC = 0; *(u32 *)0xC16FFFF8 = 0; *(u32 *)0xC16FFFF4 = 0; *(u32 *)0xC16FFFF0 = 0; // Clear all GPRs and set the TOC and SDA addresses InitializeRegisters(); // Initialize and map virtual memory InitializeMemory(); // Load, verify and decrypt the ancast image u32 entry_addr = LoadImg(); // Cleanup and jump to the loaded image Finish(entry_addr); // Execution will jump to a deadlock return;
InitializeMemory
The Espresso boot ROM copies and clears several memory regions.
// Copy memory regions: // Source address 0xB0C, destination address 0x20000, size 0x2D58 // Source address 0x3864, destination address 0xE0000000, size 0x6A0 // Source address 0x3F04, destination address 0xE00006A0, size 0xD0 // Source address 0x3FD4, destination address 0xE0000780, size 0xC for (int i = 0; mem_copy_array[i].size > 0; i++) { u32 src_addr = mem_copy_array[i].src_addr; u32 dst_addr = mem_copy_array[i].dst_addr; u32 size = mem_copy_array[i].size; if (src_addr != dst_addr) { memcpy(dst_addr, src_addr, size); if (dst_addr == 0x20000) { // Set to zero data cache for the 0x24000 region sub_5A4(); // Invalidate instruction cache for the given range sub_604(dst_addr, size); if (size) { if (memcmp(dst_addr, src_addr, size)) { *(u32 *)0xC16FFFFC = 0x1000000; while (1); } } } } } // Clear memory regions: // Address 0xE00007A0, size 0x18 // Address 0xE00007C0, size 0x2E4 for (int i = 0; mem_clear_array[i].size > 0; i++) { memset(mem_clear_array[i].addr, 0, mem_clear_array[i].size); } return;
LoadImg
The Espresso boot ROM loads, verifies and decrypts the PPC ancast image.
u8 aes_key[0x10] = {0}; u32 aes_key_addr = 0; u32 ecdsa_key_addr = 0; u32 img_addr = 0; u32 img_state = 0; ImgHeader img_header; u8 img_hash[0x14] = {0}; u32 is_evaluation = 0; u32 time_fw_verify_start = 0; u32 time_fw_verify_end = 0; u32 time_fw_decrypt_start = 0; u32 time_fw_decrypt_end = 0; // Does nothing, just returns sub_784(); // Check if SCR bits 30 and 31 are set if ((GetSCR() & 0xF0000000) == 0xC0000000) { // Set an error if bit 26 is not set if ((GetSCR() & 0x2000000) == 0) { SetState(0x11000000); // Deadlock sub_20000(); } // Use a secret static key memcpy(aes_key, aes_static_key, 0x10); aes_key_addr = &aes_key; SetImgState(0x80); // Use production ECDSA key ecdsa_key_addr = &ecdsa_prod_key; SetImgState(0x20000); } else { // Read the fuse type from eFuses u32 fuse_type = *(u32 *)(efuse_addr + 0x3C); // Set SCR bit 28 SetSCR(GetSCR() | 0x10000000); if ((fuse_type & 0x4000000) != 0) { if (IsVWii()) { // Copy the vWii AES key from eFuses memcpy(aes_key, efuse_addr + 0x10, 0x10); SetImgState(2); } else { // Copy the WiiU AES key from eFuses memcpy(aes_key, efuse_addr, 0x10); SetImgState(1); } aes_key_addr = &aes_key; // Read the fuse type from eFuses fuse_type = *(u32 *)(efuse_addr + 0x3C); if ((fuse_type & 0x18000000) == 0x8000000) { // Use development ECDSA key ecdsa_key_addr = &ecdsa_dev_key; SetImgState(0x10000); } else if ((fuse_type & 0x18000000) == 0x10000000) { // Use production ECDSA key ecdsa_key_addr = &ecdsa_prod_key; SetImgState(0x20000); } else { SetState(0x12000000); // Deadlock sub_20000(); } } else { aes_key_addr = 0; ecdsa_key_addr = 0; is_evaluation = 1; } } if (IsVWii()) { img_addr = 0x1330000; if ((GetHID1() & 0x8000000) != 0) SetImgState(0x400); else SetImgState(0x200); } else { img_addr = 0x8000000; SetImgState(0x100); } if (aes_key_addr) { // Get the current time time_fw_verify_start = GetTB(); // Load the ancast image over DMA DmaLoad(dma_addr, img_addr, 0); DmaBarrier(0); // Copy the ancast header memcpy(&img_header, dma_addr, 0x100); // Verify the ancast header's ECDSA signature if (!EcdsaVerify(ecdsa_key_addr, (u8 *)&img_header.reserved4, 0x60, img_header.signature)) { SetState(0x21000000); // Deadlock sub_20000(); } // Ensure 2 bytes at offset 0xA0 are set to 0 if (img_header.reserved4 != 0) { SetState(0x22000000); // Deadlock sub_20000(); } // Ensure bytes at offsets 0xA2 and 0xA3 are set to 0 if ((img_header.reserved5 != 0) || (img_header.reserved6 != 0)) { SetState(0x23000000); // Deadlock sub_20000(); } // Ensure 0x3C bytes at offset 0xC4 are set to 0 for (int i = 0; i < 0x3C; i++) { if (img_header.reserved7[i] != 0) { SetState(0x24000000); // Deadlock sub_20000(); } } if ((img_state & 0xFF) == 0x80) { if (img_header.targetDevice != 0x14) { SetState(0x29000000); // Deadlock sub_20000(); } } else { if (img_header.targetDevice == 0x11) { if ((img_state & 0xFF00) != 0x100) { SetState(0x25000000); // Deadlock sub_20000(); } } else if (img_header.targetDevice == 0x12) { if ((img_state & 0xFF00) != 0x400) { SetState(0x25000000); // Deadlock sub_20000(); } } else if (img_header.targetDevice == 0x13) { if ((img_state & 0xFF00) != 0x200) { SetState(0x25000000); // Deadlock sub_20000(); } } else { SetState(0x25000000); // Deadlock sub_20000(); } } if (img_header.consoleType == 1) { if ((img_state & 0xFF0000) != 0x10000) { SetState(0x26000000); // Deadlock sub_20000(); } } else if (img_header.consoleType == 2) { if ((img_state & 0xFF0000) != 0x20000) { SetState(0x26000000); // Deadlock sub_20000(); } } else { SetState(0x26000000); // Deadlock sub_20000(); } u32 img_blk_count = (img_header.imgSize >> 0xC); if (!img_blk_count) { SetState(0x27000000); // Deadlock sub_20000(); } else { u32 img_offset = img_addr + 0x100; // Initialize the SHA1 context Sha1Init(&sha1_ctx); // Load the ancast image and update the SHA1 context for (int i = 0; i < img_blk_count; i++) { DmaLoad(dma_addr + ((i << 12) & 0x1000), img_offset, 0); Sha1Update(&sha1_ctx, dma_addr + ((i << 12) & 0x1000), 0x1000); DmaBarrier(0); img_offset += 0x1000; } // Calculate the ancast image's hash Sha1Final(&sha1_ctx, img_hash); // Compare the calculated hash with the expected one if (memcmp(img_header.imgHash, img_hash, 0x14)) { SetState(0x28000000); // Deadlock sub_20000(); } else { // Get the current time time_fw_verify_end = GetTB(); // Save the time spent verifying the image *(u32 *)0xC16FFFF8 = time_fw_verify_end - time_fw_verify_start; if (!img_blk_count) { // Deadlock sub_20000(); } else { // Get the current time time_fw_decrypt_start = GetTB(); // Initialize the AES context AesCtxIni(&aes_ctx, aes_key_addr, 0x10, aes_iv, 0); img_offset = img_addr + 0x100; // Load, decrypt and store the ancast image for (int i = 0; i < img_blk_count; i++) { DmaLoad(dma_addr + ((i << 12) & 0x1000), img_offset, 0); AesDecrypt(&aes_ctx, dma_addr + ((i << 12) & 0x1000), 0x1000, dma_addr + ((i << 12) & 0x1000)); DmaStore(img_offset, dma_addr + ((i << 12) & 0x1000), 0); DmaBarrier(0); img_offset += 0x1000; } // Does nothing, just returns sub_20950(); // Get the current time time_fw_decrypt_end = GetTB(); // Save the time spent decrypting the image *(u32 *)0xC16FFFF4 = time_fw_decrypt_end - time_fw_decrypt_start; } } } } return img_addr + 0x100;
Finish
The Espresso boot ROM cleans up and jumps to the entrypoint address returned by LoadImg minus 4 bytes.
// Load state from MSR u32 state = MSR if (!is_evaluation) SCR &= 0xCFFFFFFF // Clear 0x20000 region memset(0x20000, 0, 0x4000); // Set to zero data cache for the 0x24000 region sub_5A4(); // Clear 0xE0000000 region memset(0xE0000000, 0, 0x4000); // Invalidate cache for the 0xE0000000 region and clear HID2_LCE sub_638(); // Clear DBAT1, DBAT2, DBAT4 and IBAT4 sub_660(); if (!IsVWii()) { // Configure DBAT3 and IBAT3 for WiiU mode // Physical address is 0x8000000 // Virtual address is 0x8000000 // Mapped as read only, user valid, supervisor valid and block size is 2MB sub_788(); if (entry_addr != 0x8000100) { *(u32 *)0xC16FFFFC |= 0x31000000; while (1); } else state |= 0x40; } else { // Configure DBAT3 and IBAT3 for vWii mode // Physical address is 0x1000000 // Virtual address is 0x1000000 // Mapped as read only, user valid, supervisor valid and block size is 8MB sub_7B4(); if (entry_addr != 0x1330100) { *(u32 *)0xC16FFFFC |= 0x31000000; while (1); } } // Store TB *(u32 *)0xC16FFFF0 = TB // Clear TBL and TBU sub_1E8(); // Clear L2CR_L2TS and do a global invalidate sub_69C(); // Clear HID0_DCE and HID0_ICE HID0 &= 0xFFFF3FFF __isync(); // Set L2CR_L2E and clear L2CR_L2I sub_7E0(); // Set HID0_DCE and HID0_ICE HID0 |= 0xFFFF3FFF __isync(); // Set HID0_DCFI and HID0_ICFI HID0 |= 0xC00 __isync(); __sync(); // Write the bootrom disable instruction and flush the cache u32 index = ((entry_addr - 0x4) & 0xFFFFFFE0); *(u32 *)(entry_addr - 0x4) = 0x7C73EBA6; __dcbf(0, index); __isync(); __sync(); if (*(u32 *)(entry_addr - 0x4) != 0x7C73EBA6) { *(u32 *)0xC16FFFFC |= 0x31000000; while (1); } *(u32 *)0xC16FFFFC |= 0x80000000; SRR0 = (entry_addr - 0x4) SRR1 = state // Copy SCR to R3 as it will be used by the bootrom disable instruction R3 = ((SCR | 0x80000000) & 0xBFFFFFFF) // Execution will jump to entry_addr - 4 return;
BootInfo
This is a structure mapped by the Espresso boot ROM to address 0xC16FFFF0.
Offset | Size | Description |
---|---|---|
0x0 | 0x4 | BootMainTime |
0x4 | 0x4 | BootDecryptTime |
0x8 | 0x4 | BootVerifyTime |
0xC | 0x4 | BootState |