diff --git a/source/common/bootfirm.h b/source/common/bootfirm.h new file mode 100644 index 0000000..b1dc441 --- /dev/null +++ b/source/common/bootfirm.h @@ -0,0 +1,4 @@ +#pragma once +#include "common.h" + +void __attribute__((noreturn)) BootFirm(void *firm, char *path); diff --git a/source/common/bootfirm.s b/source/common/bootfirm.s new file mode 100644 index 0000000..10eaf38 --- /dev/null +++ b/source/common/bootfirm.s @@ -0,0 +1,151 @@ +.section .text +.arm +.align 4 + +.equ ARG_MAGIC, 0xBEEF +.equ MPCORE_LD, 0x27FFFB00 +.equ STUB_LOC, 0x27FFFC00 +.equ FBPTR_LOC, 0x23FFFE00 +.equ ARGV_LOC, 0x23FFFE20 + +.cpu mpcore +MPCore_stub: + mov r0, #0x20000000 + mov r1, #0 + str r1, [r0, #-4] + .Lcheckmpcentry: + ldr r1, [r0, #-4] + cmp r1, #0 + beq .Lcheckmpcentry + bx r1 +.pool +MPCore_stub_end: + +@ Assume these functions ALWAYS clobber R0-R3 and R12 and don't use the stack +.equ MEMCPY, 0xFFFF0374 @ void memcpy32(void *src, void *dest, u32 size) +.equ INV_DC, 0xFFFF07F0 @ void invalidate_dcache(void) +.equ WB_DC, 0xFFFF07FC @ void writeback_dcache(void) +.equ WBINV_DC, 0xFFFF0830 @ void writeback_invalidate_dcache(void) +.equ INV_IC, 0xFFFF0AB4 @ void invalidate_icache(void) +.equ INITCP15, 0xFFFF0C58 @ void reset_cp15(void) + +.cpu arm946e-s +@ void BootFirm_stub(void *firm, char *path) +@ r0-r8: scratch registers +@ r9: FIRM path +@ r10: FIRM header +@ r11: current section header +BootFirm_stub: + mov r10, r0 + add r11, r0, #0x40 + mov r9, r1 + + mov r4, #4 + .LBootFirm_stub_copysect: + @ Fetch source, destination and length + ldmia r11, {r0-r2} + + cmp r2, #0 @ If section is unused/zerolength, don't even bother + addne r0, r10 @ Fix source address + + ldrne r3, =MEMCPY + blxne r3 + + subs r4, #1 + addne r11, #0x30 @ Advance to the next section + bne .LBootFirm_stub_copysect + + @ Boot state + @ CPSR: + @ ARM, Supervisor, IRQ/FIQs disabled + @ Flags are undefined + msr cpsr_c, #0xD3 + + @ CP15: + @ MPU and Caches are off + @ TCMs are on (location/configuration is undefined) + @ Alternative exception vectors are enabled (0xFFFF0000) + ldr r3, =WBINV_DC + ldr r4, =INV_IC + ldr r5, =INITCP15 + blx r3 + blx r4 + blx r5 + + @ Registers: + @ R0 = 0x00000002 + @ R1 = 0x23FFFE10 + @ R2 = 0x0000BEEF + @ R3-R14 are undefined + + mov r0, #2 + ldr r1, =ARGV_LOC + ldr r2, =ARG_MAGIC + + + @ Setup argv + str r9, [r1, #0x00] @ FIRM path / argv[0] + + ldr r3, =FBPTR_LOC + str r3, [r1, #0x04] @ Framebuffers / argv[1] + + @ Fetch FIRM entrypoints + ldr r3, [r10, #0x08] @ ARM11 entrypoint + ldr r4, [r10, #0x0C] @ ARM9 entrypoint + + @ Set the ARM11 entrypoint + mov r5, #0x20000000 + str r3, [r5, #-4] + + @ Branch to the ARM9 entrypoint + bx r4 + +.pool +BootFirm_stub_end: + +@ void BootFirm(void *firm, char *path) +@ BootFirm_stub wrapper +@ No checks are performed on the data +.global BootFirm +.type BootFirm, %function +BootFirm: + mov r10, r0 + + @ Copy the FIRM path somewhere safe + ldr r0, =(ARGV_LOC+8) + mov r11, r0 + blx strcpy + + @ Relocate the MPCore stub binary + ldr r4, =MPCORE_LD + adr r1, MPCore_stub + adr r2, MPCore_stub_end + sub r2, r1 + mov r0, r4 + blx memcpy + + @ Make the ARM11 run the stub, wait until its done + mov r1, #0x20000000 + mov r0, r4 + str r0, [r1, #-4] + .Lwaitforsi: + ldr r0, [r1, #-4] + cmp r0, #0 + bne .Lwaitforsi + + @ Relocate BootFirm + ldr r4, =STUB_LOC + adr r5, BootFirm_stub + adr r6, BootFirm_stub_end + sub r7, r6, r5 + + mov r0, r4 + mov r1, r5 + mov r2, r7 + blx memcpy + + mov r0, r10 + mov r1, r11 + + bx r4 + b . diff --git a/source/common/chainload.h b/source/common/chainload.h index 7b2dc1d..123cfa3 100644 --- a/source/common/chainload.h +++ b/source/common/chainload.h @@ -4,4 +4,5 @@ #define PAYLOAD_MAX_SIZE 0xFFFE0 -void Chainload(u8 *source, size_t size); +void __attribute__((noreturn)) Chainload(u8 *source, size_t size); +void __attribute__((noreturn)) BootFirm(FirmHeader *firm, char *path); diff --git a/source/game/firm.c b/source/game/firm.c index c04ccff..7512d26 100644 --- a/source/game/firm.c +++ b/source/game/firm.c @@ -9,6 +9,14 @@ // 0 -> pre 9.5 / 1 -> 9.5 / 2 -> post 9.5 #define A9L_CRYPTO_TYPE(hdr) ((hdr->k9l[3] == 0xFF) ? 0 : (hdr->k9l[3] == '1') ? 1 : 2) +// valid addresses for FIRM section loading, pairs of start / end address, provided by Wolfvak +#define FIRM_VALID_ADDRESS \ + 0x08006000, 0x08800000, \ + 0x18000000, 0x18600000, \ + 0x1FFF0000, 0x1FFFFFFC, \ + 0x20000000, 0x23FFFE00, \ + 0x24000000, 0x27FFFB00 + u32 ValidateFirmHeader(FirmHeader* header, u32 data_size) { u8 magic[] = { FIRM_MAGIC }; if (memcmp(header->magic, magic, sizeof(magic)) != 0) @@ -21,6 +29,7 @@ u32 ValidateFirmHeader(FirmHeader* header, u32 data_size) { FirmSectionHeader* section = header->sections + i; if (!section->size) continue; if (section->offset < firm_size) return 1; + if ((section->offset % 512) || (section->address % 512) || (section->size % 512)) return 1; if ((header->entry_arm11 >= section->address) && (header->entry_arm11 < section->address + section->size)) section_arm11 = i; @@ -50,6 +59,36 @@ u32 ValidateFirmA9LHeader(FirmA9LHeader* header) { return sha_cmp((IS_DEVKIT) ? enckeyX0x15devhash : enckeyX0x15hash, header->keyX0x15, 0x10, SHA256_MODE); } +u32 ValidateFirm(void* firm, u32 firm_size) { + FirmHeader* header = (FirmHeader*) firm; + + // validate firm header + if (ValidateFirmHeader(header, firm_size) != 0) + return 1; + + // hash verify all available sections and check load address + for (u32 i = 0; i < 4; i++) { + u32 valid_address[] = { FIRM_VALID_ADDRESS }; + FirmSectionHeader* section = header->sections + i; + if (!section->size) continue; + if (sha_cmp(section->hash, (u8*) firm + section->offset, section->size, SHA256_MODE) != 0) { + return 1; + } + bool is_valid_address = false; + for (u32 a = 0; a < sizeof(valid_address) / (2*sizeof(u32)); a++) { + if ((valid_address[2*a] >= section->address) && (valid_address[(2*a)+1] <= section->address + section->size)) + is_valid_address = true; + } + if (!is_valid_address) return 1; + } + + // section for arm9 entrypoint not found? + if (!FindFirmArm9Section(header)) + return 1; + + return 0; +} + FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm) { for (u32 i = 0; i < 4; i++) { FirmSectionHeader* section = firm->sections + i; diff --git a/source/game/firm.h b/source/game/firm.h index da53cf5..8d812be 100644 --- a/source/game/firm.h +++ b/source/game/firm.h @@ -43,6 +43,7 @@ typedef struct { u32 ValidateFirmHeader(FirmHeader* header, u32 data_size); u32 ValidateFirmA9LHeader(FirmA9LHeader* header); +u32 ValidateFirm(void* firm, u32 firm_size); FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm); u32 GetArm9BinarySize(FirmA9LHeader* a9l); diff --git a/source/godmode.c b/source/godmode.c index 9b85857..5d6e05a 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -1417,11 +1417,15 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur } return 0; } else if ((user_select == boot)) { - size_t payload_size = FileGetSize(curr_entry->path); - if (ShowUnlockSequence(3, "%s (%dkB)\nBoot FIRM via boot9strap?\n(requires boot9strap nightly)", pathstr, payload_size / 1024)) { - u32 flags = OVERWRITE_ALL; - if (PathMoveCopy("0:/bootonce.firm", curr_entry->path, &flags, false)) - Reboot(); + size_t firm_size = FileGetSize(curr_entry->path); + if (firm_size > TEMP_BUFFER_SIZE) { + ShowPrompt(false, "FIRM too big, can't launch"); // unlikely + } else if (ShowUnlockSequence(3, "%s (%dkB)\nBoot FIRM via chainloader?", pathstr, firm_size / 1024)) { + if ((FileGetData(curr_entry->path, TEMP_BUFFER, firm_size, 0) == firm_size) && + (ValidateFirm(TEMP_BUFFER, firm_size) != 0)) { + BootFirm((FirmHeader*)(void*)TEMP_BUFFER, curr_entry->path); + while(1); + } else ShowPrompt(false, "Not a vaild FIRM, can't launch"); } return 0; } else if ((user_select == script)) {