FIRM chainloader: offer on-the-fly decryption of encrypted FIRMs

This commit is contained in:
d0k3 2017-09-29 03:14:06 +02:00
parent 5f1814b599
commit b6409ec045
6 changed files with 54 additions and 17 deletions

View File

@ -264,3 +264,24 @@ u32 DecryptFirmSequential(void* data, u32 offset, u32 size) {
return (a9lptr) ? DecryptFirm(data, offset, size, firmptr, a9lptr) : 0;
}
u32 DecryptFirmFull(void* data, u32 size) {
// this expects the full FIRM being in memory
FirmHeader* firm = (FirmHeader*) data;
FirmSectionHeader* arm9s = FindFirmArm9Section(firm);
if (ValidateFirmHeader(firm, size) != 0) return 1; // not a proper firm
if (!arm9s) return 0; // no ARM9 section -> not encrypted -> done
FirmA9LHeader* a9l = (FirmA9LHeader*)(void*) ((u8*) data + arm9s->offset);
if (ValidateFirmA9LHeader(a9l) != 0) return 0; // no ARM9bin -> not encrypted -> done
// decrypt FIRM and ARM9loader header
if ((DecryptFirm(data, 0, size, firm, a9l) != 0) || (DecryptA9LHeader(a9l) != 0))
return 1;
// fix ARM9 section SHA and ARM9 entrypoint
sha_quick(arm9s->hash, (u8*) data + arm9s->offset, arm9s->size, SHA256_MODE);
firm->entry_arm9 = ARM9ENTRY_FIX(firm);
return 0;
}

View File

@ -7,11 +7,20 @@
#define FIRM_MAX_SIZE 0x400000 // 4MB, due to FIRM partition size
#define ARM11NCCH_OFFSET 0, 0x2A000, 0x2B000, 0x2C000
#define ARM9BIN_OFFSET 0x800
// ARM9 entrypoint after decryption - TWL/AGB and NATIVE/SAFE_MODE
// see: https://github.com/AuroraWright/Luma3DS/blob/master/source/firm.c#L349
// and: https://github.com/AuroraWright/Luma3DS/blob/master/source/firm.c#L424
// and: https://github.com/AuroraWright/Luma3DS/blob/master/source/firm.c#L463
#define ARM9ENTRY_FIX(firm) (((firm)->sections[3].offset) ? 0x801301C : 0x0801B01C)
#define FIRM_NDMA_CPY 0
#define FIRM_XDMA_CPY 1
#define FIRM_CPU_MEMCPY 2
#define IsInstallableFirm(firm, firm_size) (ValidateFirm(firm, firm_size, true) == 0)
#define IsBootableFirm(firm, firm_size) (ValidateFirm(firm, firm_size, false) == 0)
// see: https://www.3dbrew.org/wiki/FIRM#Firmware_Section_Headers
typedef struct {
u32 offset;
@ -58,3 +67,4 @@ u32 DecryptA9LHeader(FirmA9LHeader* header);
u32 DecryptFirm(void* data, u32 offset, u32 size, FirmHeader* firm, FirmA9LHeader* a9l);
u32 DecryptArm9Binary(void* data, u32 offset, u32 size, FirmA9LHeader* a9l);
u32 DecryptFirmSequential(void* data, u32 offset, u32 size);
u32 DecryptFirmFull(void* data, u32 size);

View File

@ -768,19 +768,28 @@ u32 BootFirmHandler(const char* bootpath, bool verbose, bool delete) {
if (verbose && !ShowUnlockSequence(3, "%s (%dkB)\nBoot FIRM via chainloader?", pathstr, firm_size / 1024))
return 1;
if ((FileGetData(bootpath, TEMP_BUFFER, firm_size, 0) != firm_size) ||
(ValidateFirm(TEMP_BUFFER, firm_size, false) != 0)) {
if (verbose) ShowPrompt(false, "%s\nNot a bootable FIRM", pathstr);
void* firm = (void*) TEMP_BUFFER;
if ((FileGetData(bootpath, firm, firm_size, 0) != firm_size) ||
!IsBootableFirm(firm, firm_size)) {
if (verbose) ShowPrompt(false, "%s\nNot a bootable FIRM.", pathstr);
return 1;
}
// encrypted firm handling
FirmSectionHeader* arm9s = FindFirmArm9Section(firm);
FirmA9LHeader* a9l = (FirmA9LHeader*)(void*) ((u8*) firm + arm9s->offset);
if (verbose && (ValidateFirmA9LHeader(a9l) == 0) &&
ShowPrompt(true, "%s\nFIRM is encrypted.\n \nDecrypt before boot?", pathstr) &&
(DecryptFirmFull(firm, firm_size) != 0))
return 1;
// unsupported location handling
char fixpath[256] = { 0 };
if (verbose && (*bootpath != '0') && (*bootpath != '1')) {
const char* optionstr[2] = { "Make a copy at " OUTPUT_PATH "/temp.firm", "Try to boot anyways" };
u32 user_select = ShowSelectPrompt(2, optionstr, "%s\nWarning: Trying to boot from\nan unsupported location.", pathstr);
u32 user_select = ShowSelectPrompt(2, optionstr, "%s\nWarning: Trying to boot from an\nunsupported location.", pathstr);
if (user_select == 1) {
FileSetData(OUTPUT_PATH "/temp.firm", TEMP_BUFFER, firm_size, 0, true);
FileSetData(OUTPUT_PATH "/temp.firm", firm, firm_size, 0, true);
bootpath = OUTPUT_PATH "/temp.firm";
} else if (!user_select) bootpath = "";
}
@ -793,7 +802,7 @@ u32 BootFirmHandler(const char* bootpath, bool verbose, bool delete) {
// boot the FIRM (if we got a proper fixpath)
if (*fixpath) {
if (delete) PathDelete(bootpath);
BootFirm((FirmHeader*)(void*)TEMP_BUFFER, fixpath);
BootFirm((FirmHeader*) firm, fixpath);
while(1);
}
@ -836,8 +845,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
bool xorpadable = (FTYPE_XORPAD(filetype));
bool keyinitable = (FTYPE_KEYINIT(filetype));
bool scriptable = (FTYPE_SCRIPT(filetype));
bool bootable = (FTYPE_BOOTABLE(filetype) && !(drvtype & DRV_VIRTUAL));
bool installable = (FTYPE_INSTALLABLE(filetype) && !(drvtype & DRV_VIRTUAL));
bool bootable = (FTYPE_BOOTABLE(filetype));
bool installable = (FTYPE_INSTALLABLE(filetype));
bool agbexportable = (FTPYE_AGBSAVE(filetype));
bool agbimportable = (FTPYE_AGBSAVE(filetype) && (drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND));
bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || transferable || hsinjectable || restorable || xorpadable || ebackupable || ncsdfixable || extrcodeable || keyinitable || bootable || scriptable || installable || agbexportable || agbimportable;
@ -1738,7 +1747,7 @@ u32 GodMode(bool is_b9s) {
// bootloader handler
if (bootloader) {
const char* bootfirm_paths[] = { BOOTFIRM_PATHS };
if (ValidateFirm(firm_in_mem, FIRM_MAX_SIZE, false) == 0) BootFirm(firm_in_mem, "0:/bootonce.firm");
if (IsBootableFirm(firm_in_mem, FIRM_MAX_SIZE)) BootFirm(firm_in_mem, "0:/bootonce.firm");
for (u32 i = 0; i < sizeof(bootfirm_paths) / sizeof(char*); i++) {
BootFirmHandler(bootfirm_paths[i], false, (BOOTFIRM_TEMPS >> i) & 0x1);
}

View File

@ -908,10 +908,7 @@ u32 DecryptFirmFile(const char* orig, const char* dest) {
// write back FIRM header
fvx_lseek(&file, 0);
memcpy(firm.dec_magic, dec_magic, sizeof(dec_magic));
// see: https://github.com/AuroraWright/Luma3DS/blob/master/source/firm.c#L349
// and: https://github.com/AuroraWright/Luma3DS/blob/master/source/firm.c#L424
// and: https://github.com/AuroraWright/Luma3DS/blob/master/source/firm.c#L463
firm.entry_arm9 = (firm.sections[3].offset) ? 0x801301C : 0x0801B01C;
firm.entry_arm9 = ARM9ENTRY_FIX(&firm);
if (fvx_write(&file, &firm, sizeof(FirmHeader), &btr) != FR_OK) {
fvx_close(&file);
return 1;

View File

@ -452,8 +452,9 @@ u32 SafeInstallFirm(const char* path, u32 slots) {
u8* firm = (u8*) TEMP_BUFFER;
UINT firm_size;
if ((fvx_qread(path, firm, 0, TEMP_BUFFER_SIZE, &firm_size) != FR_OK) ||
!firm_size || (ValidateFirm(firm, firm_size, true) != 0)) {
ShowPrompt(false, "%s\nFIRM load/verify error.", pathstr);
!firm_size || !IsInstallableFirm(firm, firm_size)) {
ShowPrompt(false, IsBootableFirm(firm, firm_size) ?
"%s\nNot a installable FIRM." : "%s\nFIRM load/verify error.", pathstr);
return 1;
}

View File

@ -723,8 +723,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
}
else if (id == CMD_ID_BOOT) {
size_t firm_size = FileGetData(argv[0], TEMP_BUFFER, TEMP_BUFFER_SIZE, 0);
ret = firm_size && (firm_size < TEMP_BUFFER_SIZE) &&
(ValidateFirm(TEMP_BUFFER, firm_size, false) == 0);
ret = firm_size && IsBootableFirm(TEMP_BUFFER, firm_size);
if (ret) {
char fixpath[256] = { 0 };
if ((*argv[0] == '0') || (*argv[0] == '1'))