#include "lib.h" #include "dir.h" #include "ncch.h" #include "exheader.h" #include "elf.h" #include "exefs.h" #include "romfs.h" #include "titleid.h" #include "logo_data.h" // Contains Logos const u32 MEDIA_UNIT = 0x200; // Private Prototypes int SignCFA(u8 *Signature, u8 *CFA_HDR, keys_struct *keys); int CheckCFASignature(u8 *Signature, u8 *CFA_HDR, keys_struct *keys); int SignCXI(u8 *Signature, u8 *CXI_HDR, keys_struct *keys); int CheckCXISignature(u8 *Signature, u8 *CXI_HDR, u8 *PubK); void init_NCCHSettings(ncch_settings *set); void free_NCCHSettings(ncch_settings *set); int get_NCCHSettings(ncch_settings *ncchset, user_settings *usrset); int SetBasicOptions(ncch_settings *ncchset, user_settings *usrset); int CreateInputFilePtrs(ncch_settings *ncchset, user_settings *usrset); int ImportNonCodeExeFsSections(ncch_settings *ncchset); int ImportLogo(ncch_settings *ncchset); int SetupNcch(ncch_settings *ncchset, romfs_buildctx *romfs); int FinaliseNcch(ncch_settings *ncchset); int SetCommonHeaderBasicData(ncch_settings *ncchset, ncch_hdr *hdr); bool IsValidProductCode(char *ProductCode, bool FreeProductCode); int BuildCommonHeader(ncch_settings *ncchset); int EncryptNCCHSections(ncch_settings *ncchset); int WriteNCCHSectionsToBuffer(ncch_settings *ncchset); // Code int SignCFA(u8 *Signature, u8 *CFA_HDR, keys_struct *keys) { return ctr_sig(CFA_HDR,sizeof(ncch_hdr),Signature,keys->rsa.cciCfaPub,keys->rsa.cciCfaPvt,RSA_2048_SHA256,CTR_RSA_SIGN); } int CheckCFASignature(u8 *Signature, u8 *CFA_HDR, keys_struct *keys) { return ctr_sig(CFA_HDR,sizeof(ncch_hdr),Signature,keys->rsa.cciCfaPub,NULL,RSA_2048_SHA256,CTR_RSA_VERIFY); } int SignCXI(u8 *Signature, u8 *CXI_HDR, keys_struct *keys) { return ctr_sig(CXI_HDR,sizeof(ncch_hdr),Signature,keys->rsa.cxiHdrPub,keys->rsa.cxiHdrPvt,RSA_2048_SHA256,CTR_RSA_SIGN); } int CheckCXISignature(u8 *Signature, u8 *CXI_HDR, u8 *PubK) { int result = ctr_sig(CXI_HDR,sizeof(ncch_hdr),Signature,PubK,NULL,RSA_2048_SHA256,CTR_RSA_VERIFY); return result; } // NCCH Build Functions int build_NCCH(user_settings *usrset) { int result; // Init Settings\n"); ncch_settings *ncchset = calloc(1,sizeof(ncch_settings)); if(!ncchset) { fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); return MEM_ERROR; } init_NCCHSettings(ncchset); // Get Settings\n"); result = get_NCCHSettings(ncchset,usrset); if(result) goto finish; if(!ncchset->options.IsCfa){ // CXI Specfic Sections // Build ExeFs Code Section\n"); result = BuildExeFsCode(ncchset); if(result) goto finish; // Build ExHeader\n"); result = BuildExHeader(ncchset); if(result) goto finish; } // Build ExeFs\n"); result = BuildExeFs(ncchset); if(result) goto finish; // Prepare for RomFs\n"); romfs_buildctx romfs_ctx; memset(&romfs_ctx,0,sizeof(romfs_buildctx)); result = SetupRomFs(ncchset,&romfs_ctx); if(result) goto finish; // Setup NCCH including final memory allocation\n"); result = SetupNcch(ncchset,&romfs_ctx); if(result) goto finish; // Build RomFs\n"); result = BuildRomFs(&romfs_ctx); if(result) goto finish; // Finalise NCCH (Hashes/Signatures and crypto)\n"); result = FinaliseNcch(ncchset); if(result) goto finish; finish: if(result) fprintf(stderr,"[NCCH ERROR] NCCH Build Process Failed\n"); free_NCCHSettings(ncchset); return result; } void init_NCCHSettings(ncch_settings *set) { memset(set,0,sizeof(ncch_settings)); } void free_NCCHSettings(ncch_settings *set) { if(set->componentFilePtrs.elf) fclose(set->componentFilePtrs.elf); if(set->componentFilePtrs.banner) fclose(set->componentFilePtrs.banner); if(set->componentFilePtrs.icon) fclose(set->componentFilePtrs.icon); if(set->componentFilePtrs.logo) fclose(set->componentFilePtrs.logo); if(set->componentFilePtrs.code) fclose(set->componentFilePtrs.code); if(set->componentFilePtrs.exhdr) fclose(set->componentFilePtrs.exhdr); if(set->componentFilePtrs.romfs) fclose(set->componentFilePtrs.romfs); if(set->componentFilePtrs.plainregion) fclose(set->componentFilePtrs.plainregion); if(set->exefsSections.code.size) free(set->exefsSections.code.buffer); if(set->exefsSections.banner.size) free(set->exefsSections.banner.buffer); if(set->exefsSections.icon.size) free(set->exefsSections.icon.buffer); if(set->sections.exhdr.size) free(set->sections.exhdr.buffer); if(set->sections.logo.size) free(set->sections.logo.buffer); if(set->sections.plainRegion.size) free(set->sections.plainRegion.buffer); if(set->sections.exeFs.size) free(set->sections.exeFs.buffer); memset(set,0,sizeof(ncch_settings)); free(set); } int get_NCCHSettings(ncch_settings *ncchset, user_settings *usrset) { int result = 0; ncchset->out = &usrset->common.workingFile; ncchset->rsfSet = &usrset->common.rsfSet; ncchset->keys = &usrset->common.keys; result = SetBasicOptions(ncchset,usrset); if(result) return result; result = CreateInputFilePtrs(ncchset,usrset); if(result) return result; result = ImportNonCodeExeFsSections(ncchset); if(result) return result; result = ImportLogo(ncchset); if(result) return result; return 0; } int SetBasicOptions(ncch_settings *ncchset, user_settings *usrset) { int result = 0; /* Options */ ncchset->options.mediaSize = MEDIA_UNIT; ncchset->options.IncludeExeFsLogo = usrset->ncch.includeExefsLogo; ncchset->options.CompressCode = usrset->common.rsfSet.Option.EnableCompress; ncchset->options.UseOnSD = usrset->common.rsfSet.Option.UseOnSD; ncchset->options.Encrypt = usrset->common.rsfSet.Option.EnableCrypt; ncchset->options.FreeProductCode = usrset->common.rsfSet.Option.FreeProductCode; ncchset->options.IsCfa = (usrset->ncch.ncchType == CFA); ncchset->options.IsBuildingCodeSection = (usrset->ncch.elfPath != NULL); ncchset->options.UseRomFS = ((ncchset->rsfSet->Rom.HostRoot && strlen(ncchset->rsfSet->Rom.HostRoot) > 0) || usrset->ncch.romfsPath); if(ncchset->options.IsCfa && !ncchset->options.UseRomFS){ fprintf(stderr,"[NCCH ERROR] \"Rom/HostRoot\" must be set\n"); return NCCH_BAD_YAML_SET; } return result; } int CreateInputFilePtrs(ncch_settings *ncchset, user_settings *usrset) { if(usrset->ncch.romfsPath){ ncchset->componentFilePtrs.romfsSize = GetFileSize_u64(usrset->ncch.romfsPath); ncchset->componentFilePtrs.romfs = fopen(usrset->ncch.romfsPath,"rb"); if(!ncchset->componentFilePtrs.romfs){ fprintf(stderr,"[NCCH ERROR] Failed to open RomFs file '%s'\n",usrset->ncch.romfsPath); return FAILED_TO_IMPORT_FILE; } } if(usrset->ncch.elfPath){ ncchset->componentFilePtrs.elfSize = GetFileSize_u64(usrset->ncch.elfPath); ncchset->componentFilePtrs.elf = fopen(usrset->ncch.elfPath,"rb"); if(!ncchset->componentFilePtrs.elf){ fprintf(stderr,"[NCCH ERROR] Failed to open elf file '%s'\n",usrset->ncch.elfPath); return FAILED_TO_IMPORT_FILE; } } if(usrset->ncch.bannerPath){ ncchset->componentFilePtrs.bannerSize = GetFileSize_u64(usrset->ncch.bannerPath); ncchset->componentFilePtrs.banner = fopen(usrset->ncch.bannerPath,"rb"); if(!ncchset->componentFilePtrs.banner){ fprintf(stderr,"[NCCH ERROR] Failed to open banner file '%s'\n",usrset->ncch.bannerPath); return FAILED_TO_IMPORT_FILE; } } if(usrset->ncch.iconPath){ ncchset->componentFilePtrs.iconSize = GetFileSize_u64(usrset->ncch.iconPath); ncchset->componentFilePtrs.icon = fopen(usrset->ncch.iconPath,"rb"); if(!ncchset->componentFilePtrs.icon){ fprintf(stderr,"[NCCH ERROR] Failed to open icon file '%s'\n",usrset->ncch.iconPath); return FAILED_TO_IMPORT_FILE; } } if(usrset->ncch.logoPath){ ncchset->componentFilePtrs.logoSize = GetFileSize_u64(usrset->ncch.logoPath); ncchset->componentFilePtrs.logo = fopen(usrset->ncch.logoPath,"rb"); if(!ncchset->componentFilePtrs.logo){ fprintf(stderr,"[NCCH ERROR] Failed to open logo file '%s'\n",usrset->ncch.logoPath); return FAILED_TO_IMPORT_FILE; } } if(usrset->ncch.codePath){ ncchset->componentFilePtrs.codeSize = GetFileSize_u64(usrset->ncch.codePath); ncchset->componentFilePtrs.code = fopen(usrset->ncch.codePath,"rb"); if(!ncchset->componentFilePtrs.code){ fprintf(stderr,"[NCCH ERROR] Failed to open ExeFs Code file '%s'\n",usrset->ncch.codePath); return FAILED_TO_IMPORT_FILE; } } if(usrset->ncch.exheaderPath){ ncchset->componentFilePtrs.exhdrSize = GetFileSize_u64(usrset->ncch.exheaderPath); ncchset->componentFilePtrs.exhdr = fopen(usrset->ncch.exheaderPath,"rb"); if(!ncchset->componentFilePtrs.exhdr){ fprintf(stderr,"[NCCH ERROR] Failed to open ExHeader file '%s'\n",usrset->ncch.exheaderPath); return FAILED_TO_IMPORT_FILE; } } if(usrset->ncch.plainRegionPath){ ncchset->componentFilePtrs.plainregionSize = GetFileSize_u64(usrset->ncch.plainRegionPath); ncchset->componentFilePtrs.plainregion = fopen(usrset->ncch.plainRegionPath,"rb"); if(!ncchset->componentFilePtrs.plainregion){ fprintf(stderr,"[NCCH ERROR] Failed to open PlainRegion file '%s'\n",usrset->ncch.plainRegionPath); return FAILED_TO_IMPORT_FILE; } } return 0; } int ImportNonCodeExeFsSections(ncch_settings *ncchset) { if(ncchset->componentFilePtrs.banner){ ncchset->exefsSections.banner.size = ncchset->componentFilePtrs.bannerSize; ncchset->exefsSections.banner.buffer = malloc(ncchset->exefsSections.banner.size); if(!ncchset->exefsSections.banner.buffer) { fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); return MEM_ERROR; } ReadFile_64(ncchset->exefsSections.banner.buffer,ncchset->exefsSections.banner.size,0,ncchset->componentFilePtrs.banner); } if(ncchset->componentFilePtrs.icon){ ncchset->exefsSections.icon.size = ncchset->componentFilePtrs.iconSize; ncchset->exefsSections.icon.buffer = malloc(ncchset->exefsSections.icon.size); if(!ncchset->exefsSections.icon.buffer) { fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); return MEM_ERROR; } ReadFile_64(ncchset->exefsSections.icon.buffer,ncchset->exefsSections.icon.size,0,ncchset->componentFilePtrs.icon); } return 0; } int ImportLogo(ncch_settings *ncchset) { if(ncchset->componentFilePtrs.logo){ ncchset->sections.logo.size = align(ncchset->componentFilePtrs.logoSize,ncchset->options.mediaSize); ncchset->sections.logo.buffer = malloc(ncchset->sections.logo.size); if(!ncchset->sections.logo.buffer) { fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); return MEM_ERROR; } memset(ncchset->sections.logo.buffer,0,ncchset->sections.logo.size); ReadFile_64(ncchset->sections.logo.buffer,ncchset->componentFilePtrs.logoSize,0,ncchset->componentFilePtrs.logo); } else if(ncchset->rsfSet->BasicInfo.Logo){ if(strcasecmp(ncchset->rsfSet->BasicInfo.Logo,"nintendo") == 0){ ncchset->sections.logo.size = 0x2000; ncchset->sections.logo.buffer = malloc(ncchset->sections.logo.size); if(!ncchset->sections.logo.buffer) { fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); return MEM_ERROR; } memcpy(ncchset->sections.logo.buffer,Nintendo_LZ,0x2000); } else if(strcasecmp(ncchset->rsfSet->BasicInfo.Logo,"licensed") == 0){ ncchset->sections.logo.size = 0x2000; ncchset->sections.logo.buffer = malloc(ncchset->sections.logo.size); if(!ncchset->sections.logo.buffer) { fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); return MEM_ERROR; } memcpy(ncchset->sections.logo.buffer,Nintendo_LicensedBy_LZ,0x2000); } else if(strcasecmp(ncchset->rsfSet->BasicInfo.Logo,"distributed") == 0){ ncchset->sections.logo.size = 0x2000; ncchset->sections.logo.buffer = malloc(ncchset->sections.logo.size); if(!ncchset->sections.logo.buffer) { fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); return MEM_ERROR; } memcpy(ncchset->sections.logo.buffer,Nintendo_DistributedBy_LZ,0x2000); } else if(strcasecmp(ncchset->rsfSet->BasicInfo.Logo,"ique") == 0){ ncchset->sections.logo.size = 0x2000; ncchset->sections.logo.buffer = malloc(ncchset->sections.logo.size); if(!ncchset->sections.logo.buffer) { fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); return MEM_ERROR; } memcpy(ncchset->sections.logo.buffer,iQue_with_ISBN_LZ,0x2000); } else if(strcasecmp(ncchset->rsfSet->BasicInfo.Logo,"iqueforsystem") == 0){ ncchset->sections.logo.size = 0x2000; ncchset->sections.logo.buffer = malloc(ncchset->sections.logo.size); if(!ncchset->sections.logo.buffer) { fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); return MEM_ERROR; } memcpy(ncchset->sections.logo.buffer,iQue_without_ISBN_LZ,0x2000); } else if(strcasecmp(ncchset->rsfSet->BasicInfo.Logo,"none") != 0){ fprintf(stderr,"[NCCH ERROR] Invalid logo name\n"); return NCCH_BAD_YAML_SET; } } return 0; } int SetupNcch(ncch_settings *ncchset, romfs_buildctx *romfs) { u64 ncchSize = 0; u64 exhdrSize,acexSize,logoSize,plnRgnSize,exefsSize,romfsSize; u64 exhdrOffset,acexOffset,logoOffset,plnRgnOffset,exefsOffset,romfsOffset; u32 exefsHashSize,romfsHashSize; ncchSize += 0x200; // Sig+Hdr // Sizes for NCCH hdr if(ncchset->sections.exhdr.size){ exhdrSize = ncchset->sections.exhdr.size; exhdrOffset = ncchSize; ncchSize += exhdrSize; } else exhdrSize = 0; if(ncchset->sections.acexDesc.size){ acexSize = ncchset->sections.acexDesc.size; acexOffset = ncchSize; ncchSize += acexSize; } else acexSize = 0; if(ncchset->sections.logo.size){ logoSize = ncchset->sections.logo.size; logoOffset = align(ncchSize,ncchset->options.mediaSize); ncchSize = logoOffset + logoSize; } else logoSize = 0; if(ncchset->sections.plainRegion.size){ plnRgnSize = align(ncchset->sections.plainRegion.size,ncchset->options.mediaSize); plnRgnOffset = align(ncchSize,ncchset->options.mediaSize); ncchSize = plnRgnOffset + plnRgnSize; } else plnRgnSize = 0; if(ncchset->sections.exeFs.size){ exefsHashSize = align(sizeof(exefs_hdr),ncchset->options.mediaSize); exefsSize = align(ncchset->sections.exeFs.size,ncchset->options.mediaSize); exefsOffset = align(ncchSize,ncchset->options.mediaSize); ncchSize = exefsOffset + exefsSize; } else exefsSize = 0; if(romfs->romfsSize){ romfsHashSize = align(romfs->romfsHeaderSize,ncchset->options.mediaSize); romfsSize = align(romfs->romfsSize,ncchset->options.mediaSize); //romfsOffset = align(ncchSize,0x200); // Old makerom method, SDK 2.x and prior romfsOffset = align(ncchSize,0x1000); ncchSize = romfsOffset + romfsSize; } else romfsSize = 0; // Aligning Total NCCH Size ncchSize = align(ncchSize,ncchset->options.mediaSize); u8 *ncch = calloc(1,ncchSize); if(!ncch){ fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); return MEM_ERROR; } // Setting up hdr\n"); ncch_hdr *hdr = (ncch_hdr*)(ncch+0x100); int ret = SetCommonHeaderBasicData(ncchset,hdr); if(ret != 0){ free(ncch); return ret; } u32_to_u8(hdr->ncchSize,ncchSize/ncchset->options.mediaSize,LE); // Copy already built sections to ncch\n"); if(exhdrSize){ memcpy((u8*)(ncch+exhdrOffset),ncchset->sections.exhdr.buffer,ncchset->sections.exhdr.size); free(ncchset->sections.exhdr.buffer); ncchset->sections.exhdr.buffer = NULL; u32_to_u8(hdr->exhdrSize,exhdrSize,LE); } if(acexSize){ memcpy((u8*)(ncch+acexOffset),ncchset->sections.acexDesc.buffer,ncchset->sections.acexDesc.size); free(ncchset->sections.acexDesc.buffer); ncchset->sections.acexDesc.buffer = NULL; } if(logoSize){ memcpy((u8*)(ncch+logoOffset),ncchset->sections.logo.buffer,ncchset->sections.logo.size); free(ncchset->sections.logo.buffer); ncchset->sections.logo.buffer = NULL; u32_to_u8(hdr->logoOffset,logoOffset/ncchset->options.mediaSize,LE); u32_to_u8(hdr->logoSize,logoSize/ncchset->options.mediaSize,LE); } if(plnRgnSize){ memcpy((u8*)(ncch+plnRgnOffset),ncchset->sections.plainRegion.buffer,ncchset->sections.plainRegion.size); free(ncchset->sections.plainRegion.buffer); ncchset->sections.plainRegion.buffer = NULL; u32_to_u8(hdr->plainRegionOffset,plnRgnOffset/ncchset->options.mediaSize,LE); u32_to_u8(hdr->plainRegionSize,plnRgnSize/ncchset->options.mediaSize,LE); } if(exefsSize){ memcpy((u8*)(ncch+exefsOffset),ncchset->sections.exeFs.buffer,ncchset->sections.exeFs.size); free(ncchset->sections.exeFs.buffer); ncchset->sections.exeFs.buffer = NULL; u32_to_u8(hdr->exefsOffset,exefsOffset/ncchset->options.mediaSize,LE); u32_to_u8(hdr->exefsSize,exefsSize/ncchset->options.mediaSize,LE); u32_to_u8(hdr->exefsHashSize,exefsHashSize/ncchset->options.mediaSize,LE); } // Point Romfs CTX to output buffer, if exists\n"); if(romfsSize){ romfs->output = ncch + romfsOffset; u32_to_u8(hdr->romfsOffset,romfsOffset/ncchset->options.mediaSize,LE); u32_to_u8(hdr->romfsSize,romfsSize/ncchset->options.mediaSize,LE); u32_to_u8(hdr->romfsHashSize,romfsHashSize/ncchset->options.mediaSize,LE); } ncchset->out->buffer = ncch; ncchset->out->size = ncchSize; GetNCCHStruct(&ncchset->cryptoDetails,hdr); return 0; } int FinaliseNcch(ncch_settings *ncchset) { u8 *ncch = ncchset->out->buffer; ncch_hdr *hdr = (ncch_hdr*)(ncch + 0x100); u8 *exhdr = (u8*)(ncch + ncchset->cryptoDetails.exhdrOffset); u8 *acexDesc = (u8*)(ncch + ncchset->cryptoDetails.acexOffset); u8 *logo = (u8*)(ncch + ncchset->cryptoDetails.logoOffset); u8 *exefs = (u8*)(ncch + ncchset->cryptoDetails.exefsOffset); u8 *romfs = (u8*)(ncch + ncchset->cryptoDetails.romfsOffset); // Taking Hashes\n"); if(ncchset->cryptoDetails.exhdrSize) ctr_sha(exhdr,ncchset->cryptoDetails.exhdrSize,hdr->exhdrHash,CTR_SHA_256); if(ncchset->cryptoDetails.logoSize) ctr_sha(logo,ncchset->cryptoDetails.logoSize,hdr->logoHash,CTR_SHA_256); if(ncchset->cryptoDetails.exefsHashDataSize) ctr_sha(exefs,ncchset->cryptoDetails.exefsHashDataSize,hdr->exefsHash,CTR_SHA_256); if(ncchset->cryptoDetails.romfsHashDataSize) ctr_sha(romfs,ncchset->cryptoDetails.romfsHashDataSize,hdr->romfsHash,CTR_SHA_256); // Signing NCCH\n"); int sig_result = Good; if(ncchset->options.IsCfa) sig_result = SignCFA(ncch,(u8*)hdr,ncchset->keys); else sig_result = SignCXI(ncch,(u8*)hdr,ncchset->keys); if(sig_result != Good){ fprintf(stderr,"[NCCH ERROR] Failed to sign %s header\n",ncchset->options.IsCfa ? "CFA" : "CXI"); return sig_result; } //memdump(stdout,"ncch: ",ncch,0x200); // Crypting NCCH\n"); ncch_key_type keyType = GetNCCHKeyType(hdr); if(keyType != NoKey){ SetNcchUnfixedKeys(ncchset->keys, ncch); // Getting AES Keys u8 *key0 = GetNCCHKey0(keyType, ncchset->keys); u8 *key1 = GetNCCHKey1(keyType, ncchset->keys); if(key0 == NULL || key1 == NULL){ fprintf(stderr,"[NCCH ERROR] Failed to load ncch aes key\n"); free(ncch); return -1; } /* memdump(stdout,"key0: ",key0,16); memdump(stdout,"key1: ",key1,16); */ // Crypting Exheader/AcexDesc if(ncchset->cryptoDetails.exhdrSize){ CryptNCCHSection(exhdr,ncchset->cryptoDetails.exhdrSize,0x0,&ncchset->cryptoDetails,key0,ncch_exhdr); CryptNCCHSection(acexDesc,ncchset->cryptoDetails.acexSize,ncchset->cryptoDetails.exhdrSize,&ncchset->cryptoDetails,key0,ncch_exhdr); } // Crypting ExeFs Files if(ncchset->cryptoDetails.exefsSize){ exefs_hdr *exefsHdr = (exefs_hdr*)exefs; for(int i = 0; i < MAX_EXEFS_SECTIONS; i++){ u8 *key = NULL; if(strncmp(exefsHdr->fileHdr[i].name,"icon",8) == 0 || strncmp(exefsHdr->fileHdr[i].name,"banner",8) == 0) key = key0; else key = key1; u32 offset = u8_to_u32(exefsHdr->fileHdr[i].offset,LE) + 0x200; u32 size = u8_to_u32(exefsHdr->fileHdr[i].size,LE); if(size) CryptNCCHSection((exefs+offset),align(size,ncchset->options.mediaSize),offset,&ncchset->cryptoDetails,key,ncch_exefs); } // Crypting ExeFs Header CryptNCCHSection(exefs,0x200,0x0,&ncchset->cryptoDetails,key0,ncch_exefs); } // Crypting RomFs if(ncchset->cryptoDetails.romfsSize) CryptNCCHSection(romfs,ncchset->cryptoDetails.romfsSize,0x0,&ncchset->cryptoDetails,key1,ncch_romfs); } return 0; } int SetCommonHeaderBasicData(ncch_settings *ncchset, ncch_hdr *hdr) { /* NCCH Magic */ memcpy(hdr->magic,"NCCH",4); /* NCCH Format Version */ if(!ncchset->options.IsCfa) u16_to_u8(hdr->formatVersion,0x2,LE); /* Setting ProgramId/TitleId */ u64 ProgramId = 0; int result = GetProgramID(&ProgramId,ncchset->rsfSet,false); if(result) return result; u64_to_u8(hdr->programId,ProgramId,LE); u64_to_u8(hdr->titleId,ProgramId,LE); /* Get Product Code and Maker Code */ if(ncchset->rsfSet->BasicInfo.ProductCode){ if(!IsValidProductCode((char*)ncchset->rsfSet->BasicInfo.ProductCode,ncchset->options.FreeProductCode)){ fprintf(stderr,"[NCCH ERROR] Invalid Product Code\n"); return NCCH_BAD_YAML_SET; } memcpy(hdr->productCode,ncchset->rsfSet->BasicInfo.ProductCode,strlen((char*)ncchset->rsfSet->BasicInfo.ProductCode)); } else memcpy(hdr->productCode,"CTR-P-CTAP",10); if(ncchset->rsfSet->BasicInfo.CompanyCode){ if(strlen((char*)ncchset->rsfSet->BasicInfo.CompanyCode) != 2){ fprintf(stderr,"[NCCH ERROR] CompanyCode length must be 2\n"); return NCCH_BAD_YAML_SET; } memcpy(hdr->makerCode,ncchset->rsfSet->BasicInfo.CompanyCode,2); } else memcpy(hdr->makerCode,"00",2); // Setting Encryption Settings if(!ncchset->options.Encrypt) hdr->flags[OtherFlag] = (NoCrypto|FixedCryptoKey); else if(ncchset->keys->aes.ncchKeyX0){ hdr->flags[OtherFlag] = UnFixedCryptoKey; if(ncchset->keys->aes.ncchKeyX1 && !ncchset->options.IsCfa) hdr->flags[SecureCrypto2] = 1; } else{ hdr->flags[OtherFlag] = FixedCryptoKey; u8 *key = GetNCCHKey0(GetNCCHKeyType(hdr),ncchset->keys); if(!key){ // for detecting absense of fixed aes keys hdr->flags[OtherFlag] = (NoCrypto|FixedCryptoKey); fprintf(stderr,"[NCCH WARNING] NCCH AES Key could not be loaded, NCCH will not be encrypted\n"); } } /* Set ContentUnitSize */ hdr->flags[ContentUnitSize] = 0; // 0x200 /* Setting ContentPlatform */ hdr->flags[ContentPlatform] = 1; // CTR /* Setting OtherFlag */ if(!ncchset->options.UseRomFS) hdr->flags[OtherFlag] |= NoMountRomFs; /* Setting ContentType */ hdr->flags[ContentType] = 0; if(ncchset->options.UseRomFS) hdr->flags[ContentType] |= content_Data; if(!ncchset->options.IsCfa) hdr->flags[ContentType] |= content_Executable; if(ncchset->rsfSet->BasicInfo.ContentType){ if(strcmp(ncchset->rsfSet->BasicInfo.ContentType,"Application") == 0) hdr->flags[ContentType] |= 0; else if(strcmp(ncchset->rsfSet->BasicInfo.ContentType,"SystemUpdate") == 0) hdr->flags[ContentType] |= content_SystemUpdate; else if(strcmp(ncchset->rsfSet->BasicInfo.ContentType,"Manual") == 0) hdr->flags[ContentType] |= content_Manual; else if(strcmp(ncchset->rsfSet->BasicInfo.ContentType,"Child") == 0) hdr->flags[ContentType] |= content_Child; else if(strcmp(ncchset->rsfSet->BasicInfo.ContentType,"Trial") == 0) hdr->flags[ContentType] |= content_Trial; else{ fprintf(stderr,"[NCCH ERROR] Invalid ContentType '%s'\n",ncchset->rsfSet->BasicInfo.ContentType); return NCCH_BAD_YAML_SET; } } return 0; } bool IsValidProductCode(char *ProductCode, bool FreeProductCode) { if(strlen(ProductCode) > 16) return false; if(FreeProductCode) return true; if(strlen(ProductCode) < 10) return false; if(strncmp(ProductCode,"CTR-",4) != 0) return false; if(ProductCode[5] != '-') return false; if(!isdigit(ProductCode[4]) && !isupper(ProductCode[4])) return false; for(int i = 6; i < 10; i++){ if(!isdigit(ProductCode[i]) && !isupper(ProductCode[i])) return false; } return true; } // NCCH Read Functions int VerifyNCCH(u8 *ncch, keys_struct *keys, bool CheckHash, bool SuppressOutput) { // Setup u8 Hash[0x20]; u8 *hdr_sig = ncch; ncch_hdr* hdr = GetNCCH_CommonHDR(NULL,NULL,ncch); ncch_struct *ncch_ctx = calloc(1,sizeof(ncch_struct)); if(!ncch_ctx){ fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); return MEM_ERROR; } GetNCCHStruct(ncch_ctx,hdr); ncch_key_type keyType = GetNCCHKeyType(hdr); u8 *key0 = NULL; u8 *key1 = NULL; if(keyType != NoKey){ //memdump(stdout,"ncch: ",ncch,0x200); SetNcchUnfixedKeys(keys,ncch); if(GetNCCHKey0(keyType,keys) == NULL){ if(!SuppressOutput) fprintf(stderr,"[NCCH ERROR] Failed to load ncch aes key.\n"); return UNABLE_TO_LOAD_NCCH_KEY; } key0 = GetNCCHKey0(keyType,keys); key1 = GetNCCHKey1(keyType,keys); } //memdump(stdout,"key0: ",key0,16); //memdump(stdout,"key1: ",key1,16); if(IsCfa(hdr)){ if(CheckCFASignature(hdr_sig,(u8*)hdr,keys) != Good && !keys->rsa.isFalseSign){ if(!SuppressOutput) fprintf(stderr,"[NCCH ERROR] CFA Sigcheck Failed\n"); free(ncch_ctx); return NCCH_HDR_SIG_BAD; } if(!ncch_ctx->romfsSize){ if(!SuppressOutput) fprintf(stderr,"[NCCH ERROR] CFA is corrupt\n"); free(ncch_ctx); return NO_ROMFS_IN_CFA; } } else{ // IsCxi // Checking for necessary sections if(!ncch_ctx->exhdrSize){ if(!SuppressOutput) fprintf(stderr,"[NCCH ERROR] CXI is corrupt\n"); free(ncch_ctx); return NO_EXHEADER_IN_CXI; } if(!ncch_ctx->exefsSize){ if(!SuppressOutput) fprintf(stderr,"[NCCH ERROR] CXI is corrupt\n"); free(ncch_ctx); return NO_EXEFS_IN_CXI; } // Get ExHeader/AcexDesc extended_hdr *exHdr = malloc(ncch_ctx->exhdrSize); if(!exHdr){ fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); free(ncch_ctx); return MEM_ERROR; } memcpy(exHdr,ncch+ncch_ctx->exhdrOffset,ncch_ctx->exhdrSize); if(key0 != NULL) CryptNCCHSection((u8*)exHdr,ncch_ctx->exhdrSize,0,ncch_ctx,key0,ncch_exhdr); // Checking Exheader Hash to see if decryption was sucessful ctr_sha(exHdr,0x400,Hash,CTR_SHA_256); if(memcmp(Hash,hdr->exhdrHash,0x20) != 0){ //memdump(stdout,"Expected Hash: ",hdr->extended_header_sha_256_hash,0x20); //memdump(stdout,"Actual Hash: ",Hash,0x20); //memdump(stdout,"Exheader: ",(u8*)exHdr,0x400); if(!SuppressOutput) { fprintf(stderr,"[NCCH ERROR] ExHeader Hashcheck Failed\n"); fprintf(stderr,"[NCCH ERROR] CXI is corrupt\n"); } free(ncch_ctx); free(exHdr); return ExHeader_Hashfail; } free(exHdr); // Checking RSA Sigs access_descriptor *acexDesc = malloc(ncch_ctx->acexSize); if(!acexDesc){ fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); free(ncch_ctx); free(exHdr); return MEM_ERROR; } memcpy(acexDesc,ncch+ncch_ctx->acexOffset,ncch_ctx->acexSize); if(key0 != NULL) CryptNCCHSection((u8*)acexDesc,ncch_ctx->acexSize,ncch_ctx->exhdrOffset,ncch_ctx,key0,ncch_exhdr); if(CheckAccessDescSignature(acexDesc,keys) != 0 && !keys->rsa.isFalseSign){ if(!SuppressOutput) fprintf(stderr,"[NCCH ERROR] AccessDesc Sigcheck Failed\n"); free(ncch_ctx); free(acexDesc); return ACCESSDESC_SIG_BAD; } u8 *hdr_pubk = GetAcexNcchPubKey(acexDesc); if(CheckCXISignature(hdr_sig,(u8*)hdr,hdr_pubk) != 0 && !keys->rsa.isFalseSign){ if(!SuppressOutput) fprintf(stderr,"[NCCH ERROR] CXI Header Sigcheck Failed\n"); free(ncch_ctx); free(acexDesc); return NCCH_HDR_SIG_BAD; } } if(!CheckHash) return 0; /* Checking ExeFs Hash, if present */ if(ncch_ctx->exefsSize) { u8 *ExeFs = malloc(ncch_ctx->exefsHashDataSize); if(!ExeFs){ fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); free(ncch_ctx); return MEM_ERROR; } memcpy(ExeFs,ncch+ncch_ctx->exefsOffset,ncch_ctx->exefsHashDataSize); if(key0 != NULL) CryptNCCHSection(ExeFs,ncch_ctx->exefsHashDataSize,0,ncch_ctx,key0,ncch_exefs); ctr_sha(ExeFs,ncch_ctx->exefsHashDataSize,Hash,CTR_SHA_256); free(ExeFs); if(memcmp(Hash,hdr->exefsHash,0x20) != 0){ if(!SuppressOutput) fprintf(stderr,"[NCCH ERROR] ExeFs Hashcheck Failed\n"); free(ncch_ctx); return ExeFs_Hashfail; } } /* Checking RomFs hash, if present */ if(ncch_ctx->romfsSize){ u8 *RomFs = malloc(ncch_ctx->romfsHashDataSize); if(!RomFs){ fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); free(ncch_ctx); return MEM_ERROR; } memcpy(RomFs,ncch+ncch_ctx->romfsOffset,ncch_ctx->romfsHashDataSize); if(key1 != NULL) CryptNCCHSection(RomFs,ncch_ctx->romfsHashDataSize,0,ncch_ctx,key1,ncch_romfs); ctr_sha(RomFs,ncch_ctx->romfsHashDataSize,Hash,CTR_SHA_256); free(RomFs); if(memcmp(Hash,hdr->romfsHash,0x20) != 0){ if(!SuppressOutput) fprintf(stderr,"[NCCH ERROR] RomFs Hashcheck Failed\n"); free(ncch_ctx); return ExeFs_Hashfail; } } /* Checking the Logo Hash, if present */ if(ncch_ctx->logoSize){ u8 *logo = (ncch+ncch_ctx->logoOffset); ctr_sha(logo,ncch_ctx->logoSize,Hash,CTR_SHA_256); if(memcmp(Hash,hdr->logoHash,0x20) != 0){ if(!SuppressOutput) fprintf(stderr,"[NCCH ERROR] Logo Hashcheck Failed\n"); free(ncch_ctx); return Logo_Hashfail; } } free(ncch_ctx); return 0; } u8* RetargetNCCH(FILE *fp, u64 size, u8 *TitleId, u8 *ProgramId, keys_struct *keys) { u8 *ncch = calloc(1,size); if(!ncch){ fprintf(stderr,"[NCCH ERROR] Not enough memory\n"); return NULL; } ReadFile_64(ncch,size,0,fp); // Importing if(ModifyNcchIds(ncch,TitleId, ProgramId, keys) != 0){ free(ncch); return NULL; } return ncch; } int ModifyNcchIds(u8 *ncch, u8 *titleId, u8 *programId, keys_struct *keys) { if(!IsNCCH(NULL,ncch)){ //free(ncch); return -1; } ncch_hdr *hdr = NULL; hdr = GetNCCH_CommonHDR(NULL,NULL,ncch); if(/*keys->rsa.requiresPresignedDesc && */!IsCfa(hdr)){ fprintf(stderr,"[NCCH ERROR] CXI's ID cannot be modified without the ability to resign the AccessDesc\n"); // Not yet yet, requires AccessDesc Privk, may implement anyway later //free(ncch); return -1; } bool titleIdMatches = titleId == NULL? true : memcmp(titleId,hdr->titleId,8) == 0; bool programIdMatches = programId == NULL? true : memcmp(programId,hdr->programId,8) == 0; if(titleIdMatches && programIdMatches) return 0;// if no modification is required don't do anything if(titleIdMatches){ // If TitleID Same, no crypto required, just resign. memcpy(hdr->programId,programId,8); SignCFA(ncch,(u8*)hdr,keys); return 0; } ncch_key_type keytype = GetNCCHKeyType(hdr); ncch_struct ncch_struct; u8 *key = NULL; u8 *romfs = NULL; //Decrypting if necessary if(keytype != NoKey){ GetNCCHStruct(&ncch_struct,hdr); romfs = (ncch+ncch_struct.romfsOffset); SetNcchUnfixedKeys(keys, ncch); // For Secure Crypto key = GetNCCHKey1(keytype,keys); if(key == NULL){ fprintf(stderr,"[NCCH ERROR] Failed to load ncch aes key\n"); //free(ncch); return -1; } CryptNCCHSection(romfs,ncch_struct.romfsSize,0,&ncch_struct,key,ncch_romfs); } // Editing data and resigning if(titleId) memcpy(hdr->titleId,titleId,8); if(programId) memcpy(hdr->programId,programId,8); SignCFA(ncch,(u8*)hdr,keys); //Checking New Key Type keytype = GetNCCHKeyType(hdr); // Re-encrypting if necessary if(keytype != NoKey){ GetNCCHStruct(&ncch_struct,hdr); romfs = (ncch+ncch_struct.romfsOffset); SetNcchUnfixedKeys(keys, ncch); // For Secure Crypto key = GetNCCHKey1(keytype,keys); if(key == NULL){ fprintf(stderr,"[NCCH ERROR] Failed to load ncch aes key\n"); //free(ncch); return -1; } CryptNCCHSection(romfs,ncch_struct.romfsSize,0,&ncch_struct,key,ncch_romfs); } return 0; } ncch_hdr* GetNCCH_CommonHDR(void *out, FILE *fp, u8 *buf) { if(!fp && !buf) return NULL; if(fp){ if(!out) return NULL; ReadFile_64(out,0x100,0x100,fp); return (ncch_hdr*)out; } else{ return (ncch_hdr*)(buf+0x100); } } bool IsNCCH(FILE *fp, u8 *buf) { if(!fp && !buf) return false; ncch_hdr *ncchHDR = NULL; bool result; if(fp) { ncchHDR = malloc(sizeof(ncch_hdr)); GetNCCH_CommonHDR(ncchHDR,fp,NULL); result = (memcmp(ncchHDR->magic,"NCCH",4) == 0); free(ncchHDR); } else { ncchHDR = GetNCCH_CommonHDR(ncchHDR,NULL,buf); result = (memcmp(ncchHDR->magic,"NCCH",4) == 0); } return result; } bool IsCfa(ncch_hdr* hdr) { return (((hdr->flags[ContentType] & content_Data) == content_Data) && ((hdr->flags[ContentType] & content_Executable) != content_Executable)); } u32 GetNCCH_MediaUnitSize(ncch_hdr* hdr) { u16 formatVersion = u8_to_u16(hdr->formatVersion,LE); u32 ret = 0; if (formatVersion == 1) ret = 1; else if (formatVersion == 2 || formatVersion == 0) ret = 1 << (hdr->flags[ContentUnitSize] + 9); return ret; //return 0x200*pow(2,hdr->flags[ContentUnitSize]); } u32 GetNCCH_MediaSize(ncch_hdr* hdr) { return u8_to_u32(hdr->ncchSize,LE); } ncch_key_type GetNCCHKeyType(ncch_hdr* hdr) { // Non-Secure Key Options if((hdr->flags[OtherFlag] & NoCrypto) == NoCrypto) return NoKey; if((hdr->flags[OtherFlag] & FixedCryptoKey) == FixedCryptoKey){ if((hdr->programId[4] & 0x10) == 0x10) return KeyIsSystemFixed; else return KeyIsNormalFixed; } // Secure Key Options if(hdr->flags[SecureCrypto2]) return KeyIsUnFixed2; return KeyIsUnFixed; } u8* GetNCCHKey0(ncch_key_type keytype, keys_struct *keys) { switch(keytype){ case NoKey: return NULL; case KeyIsNormalFixed: return keys->aes.normalKey; case KeyIsSystemFixed: return keys->aes.systemFixedKey; case KeyIsUnFixed: case KeyIsUnFixed2: if(keys->aes.ncchKeyX0) return keys->aes.unFixedKey0; else return NULL; } return NULL; } u8* GetNCCHKey1(ncch_key_type keytype, keys_struct *keys) { switch(keytype){ case NoKey: return NULL; case KeyIsNormalFixed: return keys->aes.normalKey; case KeyIsSystemFixed: return keys->aes.systemFixedKey; case KeyIsUnFixed: if(keys->aes.ncchKeyX0) return keys->aes.unFixedKey0; else return NULL; case KeyIsUnFixed2: if(keys->aes.ncchKeyX1) return keys->aes.unFixedKey1; else return NULL; } return NULL; } int GetNCCHStruct(ncch_struct *ctx, ncch_hdr *header) { memcpy(ctx->titleId,header->titleId,8); memcpy(ctx->programId,header->programId,8); u32 media_unit = GetNCCH_MediaUnitSize(header); ctx->formatVersion = u8_to_u16(header->formatVersion,LE); if(!IsCfa(header)){ ctx->exhdrOffset = 0x200; ctx->exhdrSize = u8_to_u32(header->exhdrSize,LE); ctx->acexOffset = (ctx->exhdrOffset + ctx->exhdrSize); ctx->acexSize = sizeof(access_descriptor); ctx->plainRegionOffset = (u64)(u8_to_u32(header->plainRegionOffset,LE)*media_unit); ctx->plainRegionSize = (u64)(u8_to_u32(header->plainRegionSize,LE)*media_unit); } ctx->logoOffset = (u64)(u8_to_u32(header->logoOffset,LE)*media_unit); ctx->logoSize = (u64)(u8_to_u32(header->logoSize,LE)*media_unit); ctx->exefsOffset = (u64)(u8_to_u32(header->exefsOffset,LE)*media_unit); ctx->exefsSize = (u64)(u8_to_u32(header->exefsSize,LE)*media_unit); ctx->exefsHashDataSize = (u64)(u8_to_u32(header->exefsHashSize,LE)*media_unit); ctx->romfsOffset = (u64) (u8_to_u32(header->romfsOffset,LE)*media_unit); ctx->romfsSize = (u64) (u8_to_u32(header->romfsSize,LE)*media_unit); ctx->romfsHashDataSize = (u64)(u8_to_u32(header->romfsHashSize,LE)*media_unit); return 0; } void CryptNCCHSection(u8 *buffer, u64 size, u64 src_pos, ncch_struct *ctx, u8 key[16], u8 type) { if(type < 1 || type > 3) return; u8 counter[0x10]; ncch_get_counter(ctx,counter,type); ctr_aes_context aes_ctx; memset(&aes_ctx,0x0,sizeof(ctr_aes_context)); ctr_init_counter(&aes_ctx, key, counter); if(src_pos > 0){ u32 carry = 0; carry = align(src_pos,0x10); carry /= 0x10; ctr_add_counter(&aes_ctx,carry); } ctr_crypt_counter(&aes_ctx, buffer, buffer, size); return; } void ncch_get_counter(ncch_struct *ctx, u8 counter[16], u8 type) { u8 *titleId = ctx->titleId; u32 i; u32 x = 0; memset(counter, 0, 16); if (ctx->formatVersion == 2 || ctx->formatVersion == 0) { for(i=0; i<8; i++) counter[i] = titleId[7-i]; counter[8] = type; } else if (ctx->formatVersion == 1) { switch(type){ case ncch_exhdr : x = ctx->exhdrOffset; break; case ncch_exefs : x = ctx->exefsOffset; break; case ncch_romfs : x = ctx->romfsOffset; break; } for(i=0; i<8; i++) counter[i] = titleId[i]; for(i=0; i<4; i++) counter[12+i] = x>>((3-i)*8); } //memdump(stdout,"CTR: ",counter,16); }