diff --git a/lib/libnx/include/nx/NcaHeader.h b/lib/libnx/include/nx/NcaHeader.h index ea2d74b..43688ed 100644 --- a/lib/libnx/include/nx/NcaHeader.h +++ b/lib/libnx/include/nx/NcaHeader.h @@ -1,10 +1,7 @@ #pragma once -#include -#include +#include #include #include -#include -#include #include namespace nx @@ -19,37 +16,16 @@ namespace nx NCA3_FORMAT }; - enum DistributionType - { - DIST_DOWNLOAD, - DIST_GAME_CARD - }; - - enum ContentType - { - TYPE_PROGRAM, - TYPE_META, - TYPE_CONTROL, - TYPE_MANUAL, - TYPE_DATA, - }; - - enum KeyBankIndex - { - KEY_AESXTS_0, - KEY_AESXTS_1, - KEY_AESCTR, - KEY_UNUSED_3, - }; - - struct sSection + struct sPartition { + byte_t index; uint64_t offset; uint64_t size; crypto::sha::sSha256Hash hash; - const sSection& operator=(const sSection& other) + const sPartition& operator=(const sPartition& other) { + index = other.index; offset = other.offset; size = other.size; hash = other.hash; @@ -57,21 +33,20 @@ namespace nx return *this; } - bool operator==(const sSection& other) const + bool operator==(const sPartition& other) const { - return (offset == other.offset) \ + return (index == other.index) \ + && (offset == other.offset) \ && (size == other.size) \ && (hash == other.hash); } - bool operator!=(const sSection& other) const + bool operator!=(const sPartition& other) const { return !operator==(other); } }; - static const size_t kBlockSize = 0x200; - NcaHeader(); NcaHeader(const NcaHeader& other); NcaHeader(const byte_t* bytes, size_t len); @@ -92,85 +67,56 @@ namespace nx void clear(); FormatVersion getFormatVersion() const; void setFormatVersion(FormatVersion ver); - DistributionType getDistributionType() const; - void setDistributionType(DistributionType type); - ContentType getContentType() const; - void setContentType(ContentType type); - byte_t getCryptoType() const; - void setCryptoType(byte_t type); + nca::DistributionType getDistributionType() const; + void setDistributionType(nca::DistributionType type); + nca::ContentType getContentType() const; + void setContentType(nca::ContentType type); + byte_t getKeyGeneration() const; + void setKeyGeneration(byte_t gen); byte_t getKaekIndex() const; void setKaekIndex(byte_t index); - uint64_t getNcaSize() const; - void setNcaSize(uint64_t size); + uint64_t getContentSize() const; + void setContentSize(uint64_t size); uint64_t getProgramId() const; void setProgramId(uint64_t program_id); uint32_t getContentIndex() const; void setContentIndex(uint32_t index); uint32_t getSdkAddonVersion() const; void setSdkAddonVersion(uint32_t version); - const fnd::List& getSections() const; - void addSection(const sSection& section); + const byte_t* getRightsId() const; + void setRightsId(const byte_t* rights_id); + const fnd::List& getPartitions() const; + void setPartitions(const fnd::List& partitions); const fnd::List& getEncAesKeys() const; - void addEncAesKey(const crypto::aes::sAes128Key& key); + void setEncAesKeys(const fnd::List& keys); private: const std::string kModuleName = "NCA_HEADER"; - const std::string kNca2Sig = "NCA2"; - const std::string kNca3Sig = "NCA3"; - static const size_t kSectionNum = 4; - static const size_t kAesKeyNum = 4; - static const uint32_t kDefaultSdkAddonVersion = 721920; + + //static const uint32_t kDefaultSdkAddonVersion = 721920; enum ProgramPartitionId { - SECTION_CODE, - SECTION_DATA, - SECTION_LOGO, + PARTITION_CODE, + PARTITION_DATA, + PARTITION_LOGO, }; -#pragma pack (push, 1) - - struct sNcaHeader - { - char signature[4]; - byte_t distribution_type; - byte_t content_type; - byte_t crypto_type; // KeyGeneration - byte_t key_area_encryption_key_index; - le_uint64_t nca_size; - le_uint64_t program_id; - le_uint32_t content_index; - le_uint32_t sdk_addon_version; - byte_t crypto_type_2; - byte_t reserved_2[0xf]; - byte_t rights_id[0x10]; - struct sNcaSection - { - le_uint32_t start; // block units - le_uint32_t end; // block units - byte_t enabled; - byte_t reserved[7]; - } section[kSectionNum]; - crypto::sha::sSha256Hash section_hash[kSectionNum]; - crypto::aes::sAes128Key enc_aes_key[kAesKeyNum]; - - }; -#pragma pack (pop) - // binary fnd::MemoryBlob mBinaryBlob; // data FormatVersion mFormatVersion; - DistributionType mDistributionType; - ContentType mContentType; - byte_t mCryptoType; + nca::DistributionType mDistributionType; + nca::ContentType mContentType; + byte_t mKeyGeneration; byte_t mKaekIndex; - uint64_t mNcaSize; + uint64_t mContentSize; uint64_t mProgramId; uint32_t mContentIndex; uint32_t mSdkAddonVersion; - fnd::List mSections; + byte_t mRightsId[nca::kRightsIdLen]; + fnd::List mPartitions; fnd::List mEncAesKeys; uint64_t blockNumToSize(uint32_t block_num) const; diff --git a/lib/libnx/include/nx/nca.h b/lib/libnx/include/nx/nca.h new file mode 100644 index 0000000..07595fd --- /dev/null +++ b/lib/libnx/include/nx/nca.h @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include + +namespace nx +{ + namespace nca + { + const std::string kNca2Sig = "NCA2"; + const std::string kNca3Sig = "NCA3"; + static const size_t kSectorSize = 0x200; + static const size_t kPartitionNum = 4; + static const size_t kHeaderSectorNum = 6; + static const size_t kHeaderSize = kSectorSize * kHeaderSectorNum; + static const size_t kAesKeyNum = 16; + static const size_t kRightsIdLen = 0x10; + + enum DistributionType + { + DIST_DOWNLOAD, + DIST_GAME_CARD + }; + + enum ContentType + { + TYPE_PROGRAM, + TYPE_META, + TYPE_CONTROL, + TYPE_MANUAL, + TYPE_DATA, + }; + + enum KeyBankIndex + { + KEY_AESXTS_0, + KEY_AESXTS_1, + KEY_AESCTR, + KEY_UNUSED_3, + KEY_AESCTR_HW + }; + + enum KeyAreaEncryptionKeyIndex + { + KAEK_IDX_APPLICATION, + KAEK_IDX_OCEAN, + KAEK_IDX_SYSTEM + }; + + enum FormatType + { + FORMAT_ROMFS, + FORMAT_PFS0 + }; + + enum HashType + { + HASH_AUTO, + HASH_UNK1, + HASH_HIERARCHICAL_SHA256, + HASH_HIERARCHICAL_INTERGRITY + }; + + enum EncryptionType + { + CRYPT_AUTO, + CRYPT_NONE, + CRYPT_AESXTS, + CRYPT_AESCTR, + CRYPT_BKTR + }; + } + +#pragma pack(push,1) + struct sNcaHeader + { + char signature[4]; + byte_t distribution_type; + byte_t content_type; + byte_t key_generation; // KeyGeneration + byte_t key_area_encryption_key_index; + le_uint64_t content_size; + le_uint64_t program_id; + le_uint32_t content_index; + le_uint32_t sdk_addon_version; + byte_t key_generation_2; + byte_t reserved_2[0xf]; + byte_t rights_id[nca::kRightsIdLen]; + struct sNcaSection + { + le_uint32_t start; // block units + le_uint32_t end; // block units + byte_t enabled; + byte_t reserved[7]; + } partition[nca::kPartitionNum]; + crypto::sha::sSha256Hash partition_hash[nca::kPartitionNum]; + crypto::aes::sAes128Key enc_aes_key[nca::kAesKeyNum]; + }; + + struct sNcaFsHeader + { + le_uint16_t version; // usually 0x0002 + byte_t format_type; // RomFs(0x00), PartitionFs(0x01) + byte_t hash_type; // HashTypeAuto(0x00), HashTypeHierarchicalSha256(0x02), HashTypeHierarchicalIntegrity(0x03).RomFs uses (0x03) this is forced, PartitionFs uses (0x02). + byte_t encryption_type; // EncryptionTypeAuto(0x00), EncryptionTypeNone(0x01), EncryptionTypeAesCtr(0x03) + byte_t reserved[3]; + }; + + struct sPfsSuperBlock + { + byte_t master_hash[0x20]; + le_uint32_t hash_block_size; + le_uint32_t unk_0x02; + struct sLayout + { + le_uint64_t offset; + le_uint64_t size; + } hash_data, hash_target; + }; + + static const size_t kMaxIvfcLevel = 4; + + struct sIvfcHeader + { + le_uint32_t magic; + le_uint32_t id; + le_uint32_t master_hash_size; + le_uint32_t level_num; + struct sIvfcLevelHeader + { + uint64_t logical_offset; + uint64_t hash_data_size; + uint32_t block_size; + byte_t reserved[4]; + } level_header[kMaxIvfcLevel]; + byte_t unk_0xA0[0x20]; + byte_t master_hash[0x20]; + }; +#pragma pack(pop) +} diff --git a/lib/libnx/source/NcaHeader.cpp b/lib/libnx/source/NcaHeader.cpp index 46e7227..f8dd2f2 100644 --- a/lib/libnx/source/NcaHeader.cpp +++ b/lib/libnx/source/NcaHeader.cpp @@ -12,38 +12,49 @@ void NcaHeader::exportBinary() switch(mFormatVersion) { case (NCA2_FORMAT): - strncpy(hdr->signature, kNca2Sig.c_str(), 4); + strncpy(hdr->signature, nca::kNca2Sig.c_str(), 4); break; case (NCA3_FORMAT): - strncpy(hdr->signature, kNca3Sig.c_str(), 4); + strncpy(hdr->signature, nca::kNca3Sig.c_str(), 4); break; default: throw fnd::Exception(kModuleName, "Unsupported format version"); } hdr->distribution_type = mDistributionType; hdr->content_type = mContentType; - hdr->crypto_type = mCryptoType; + if (mKeyGeneration > 2) + { + hdr->key_generation = 2; + hdr->key_generation_2 = mKeyGeneration; + } + else + { + hdr->key_generation = mKeyGeneration; + hdr->key_generation_2 = 0; + } + hdr->key_area_encryption_key_index = mKaekIndex; - hdr->nca_size = mNcaSize; + hdr->content_size = mContentSize; hdr->program_id = mProgramId; hdr->content_index = mContentIndex; hdr->sdk_addon_version = mSdkAddonVersion; - hdr->crypto_type_2 = 0; + memcpy(hdr->rights_id, mRightsId, nca::kRightsIdLen); // TODO: properly reconstruct NCA layout? atm in hands of user - - for (size_t i = 0; i < mSections.getSize(); i++) + for (size_t i = 0; i < mPartitions.getSize(); i++) { - // determine section index - byte_t section = mSections.getSize() - 1 - i; + // determine partition index + byte_t idx = mPartitions[i].index; - hdr->section[section].start = sizeToBlockNum(mSections[i].offset); - hdr->section[section].end = (sizeToBlockNum(mSections[i].offset) + sizeToBlockNum(mSections[i].size)); - hdr->section[section].enabled = true; - hdr->section_hash[section] = mSections[i].hash; + if (mPartitions[i].index >= nca::kPartitionNum || hdr->partition[idx].enabled) continue; + + hdr->partition[idx].start = sizeToBlockNum(mPartitions[i].offset); + hdr->partition[idx].end = (sizeToBlockNum(mPartitions[i].offset) + sizeToBlockNum(mPartitions[i].size)); + hdr->partition[idx].enabled = true; + hdr->partition_hash[idx] = mPartitions[i].hash; } - for (size_t i = 0; i < kAesKeyNum; i++) + for (size_t i = 0; i < nca::kAesKeyNum; i++) { hdr->enc_aes_key[i] = mEncAesKeys[i]; } @@ -63,11 +74,11 @@ void NcaHeader::importBinary(const byte_t * bytes, size_t len) sNcaHeader* hdr = (sNcaHeader*)mBinaryBlob.getBytes(); - if (memcmp(hdr->signature, kNca2Sig.c_str(), 4) == 0) + if (memcmp(hdr->signature, nca::kNca2Sig.c_str(), 4) == 0) { mFormatVersion = NCA2_FORMAT; } - else if (memcmp(hdr->signature, kNca3Sig.c_str(), 4) == 0) + else if (memcmp(hdr->signature, nca::kNca3Sig.c_str(), 4) == 0) { mFormatVersion = NCA3_FORMAT; } @@ -76,28 +87,26 @@ void NcaHeader::importBinary(const byte_t * bytes, size_t len) throw fnd::Exception(kModuleName, "NCA header corrupt"); } - mDistributionType = (DistributionType)hdr->distribution_type; - mContentType = (ContentType)hdr->content_type; - mCryptoType = MAX(hdr->crypto_type, hdr->crypto_type_2); + mDistributionType = (nca::DistributionType)hdr->distribution_type; + mContentType = (nca::ContentType)hdr->content_type; + mKeyGeneration = MAX(hdr->key_generation, hdr->key_generation_2); mKaekIndex = hdr->key_area_encryption_key_index; - mNcaSize = *hdr->nca_size; + mContentSize = *hdr->content_size; mProgramId = *hdr->program_id; mContentIndex = *hdr->content_index; mSdkAddonVersion = *hdr->sdk_addon_version; + memcpy(mRightsId, hdr->rights_id, nca::kRightsIdLen); - for (size_t i = 0; i < kSectionNum; i++) + for (size_t i = 0; i < nca::kPartitionNum; i++) { - // determine section index - byte_t section = kSectionNum - 1 - i; - // skip sections that don't exist - if (*hdr->section[section].start == 0 && *hdr->section[section].end == 0) continue; + if (hdr->partition[i].enabled == 0) continue; // add high level struct - mSections.addElement({ blockNumToSize(*hdr->section[section].start), blockNumToSize(hdr->section[section].end.get() - hdr->section[section].start.get()), hdr->section_hash[section] }); + mPartitions.addElement({(byte_t)i, blockNumToSize(hdr->partition[i].start.get()), blockNumToSize(hdr->partition[i].end.get() - hdr->partition[i].start.get()), hdr->partition_hash[i] }); } - for (size_t i = 0; i < kAesKeyNum; i++) + for (size_t i = 0; i < nca::kAesKeyNum; i++) { mEncAesKeys.addElement(hdr->enc_aes_key[i]); } @@ -106,15 +115,16 @@ void NcaHeader::importBinary(const byte_t * bytes, size_t len) void nx::NcaHeader::clear() { mFormatVersion = NCA3_FORMAT; - mDistributionType = DIST_DOWNLOAD; - mContentType = TYPE_PROGRAM; - mCryptoType = 0; + mDistributionType = nca::DIST_DOWNLOAD; + mContentType = nca::TYPE_PROGRAM; + mKeyGeneration = 0; mKaekIndex = 0; - mNcaSize = 0; + mContentSize = 0; mProgramId = 0; mContentIndex = 0; mSdkAddonVersion = 0; - mSections.clear(); + + mPartitions.clear(); mEncAesKeys.clear(); } @@ -128,34 +138,34 @@ void nx::NcaHeader::setFormatVersion(FormatVersion version) mFormatVersion = version; } -nx::NcaHeader::DistributionType nx::NcaHeader::getDistributionType() const +nx::nca::DistributionType nx::NcaHeader::getDistributionType() const { return mDistributionType; } -void nx::NcaHeader::setDistributionType(DistributionType type) +void nx::NcaHeader::setDistributionType(nca::DistributionType type) { mDistributionType = type; } -nx::NcaHeader::ContentType nx::NcaHeader::getContentType() const +nx::nca::ContentType nx::NcaHeader::getContentType() const { return mContentType; } -void nx::NcaHeader::setContentType(ContentType type) +void nx::NcaHeader::setContentType(nca::ContentType type) { mContentType = type; } -byte_t nx::NcaHeader::getCryptoType() const +byte_t nx::NcaHeader::getKeyGeneration() const { - return mCryptoType; + return mKeyGeneration; } -void nx::NcaHeader::setCryptoType(byte_t type) +void nx::NcaHeader::setKeyGeneration(byte_t gen) { - mCryptoType = type; + mKeyGeneration = gen; } byte_t nx::NcaHeader::getKaekIndex() const @@ -168,14 +178,14 @@ void nx::NcaHeader::setKaekIndex(byte_t index) mKaekIndex = index; } -uint64_t NcaHeader::getNcaSize() const +uint64_t NcaHeader::getContentSize() const { - return mNcaSize; + return mContentSize; } -void NcaHeader::setNcaSize(uint64_t size) +void NcaHeader::setContentSize(uint64_t size) { - mNcaSize = size; + mContentSize = size; } uint64_t NcaHeader::getProgramId() const @@ -208,18 +218,28 @@ void nx::NcaHeader::setSdkAddonVersion(uint32_t version) mSdkAddonVersion = version; } -const fnd::List& NcaHeader::getSections() const +const byte_t* nx::NcaHeader::getRightsId() const { - return mSections; + return mRightsId; } -void NcaHeader::addSection(const sSection & section) +void nx::NcaHeader::setRightsId(const byte_t* rights_id) { - if (mSections.getSize() >= kSectionNum) + memcpy(mRightsId, rights_id, nca::kRightsIdLen); +} + +const fnd::List& NcaHeader::getPartitions() const +{ + return mPartitions; +} + +void NcaHeader::setPartitions(const fnd::List& partitions) +{ + mPartitions = partitions; + if (mPartitions.getSize() >= nca::kPartitionNum) { - throw fnd::Exception(kModuleName, "Too many NCA sections"); + throw fnd::Exception(kModuleName, "Too many NCA partitions"); } - mSections.addElement(section); } const fnd::List& NcaHeader::getEncAesKeys() const @@ -227,37 +247,32 @@ const fnd::List& NcaHeader::getEncAesKeys() const return mEncAesKeys; } -void NcaHeader::addEncAesKey(const crypto::aes::sAes128Key & key) +void NcaHeader::setEncAesKeys(const fnd::List& keys) { - if (mEncAesKeys.getSize() >= kAesKeyNum) - { - throw fnd::Exception(kModuleName, "Too many NCA aes keys"); - } - - mEncAesKeys.addElement(key); + mEncAesKeys = keys; } uint64_t NcaHeader::blockNumToSize(uint32_t block_num) const { - return block_num*kBlockSize; + return block_num*nca::kSectorSize; } uint32_t NcaHeader::sizeToBlockNum(uint64_t real_size) const { - return align(real_size, kBlockSize)/kBlockSize; + return align(real_size, nca::kSectorSize) / nca::kSectorSize; } bool NcaHeader::isEqual(const NcaHeader & other) const { return (mDistributionType == other.mDistributionType) \ && (mContentType == other.mContentType) \ - && (mCryptoType == other.mCryptoType) \ + && (mKeyGeneration == other.mKeyGeneration) \ && (mKaekIndex == other.mKaekIndex) \ - && (mNcaSize == other.mNcaSize) \ + && (mContentSize == other.mContentSize) \ && (mProgramId == other.mProgramId) \ && (mContentIndex == other.mContentIndex) \ && (mSdkAddonVersion == other.mSdkAddonVersion) \ - && (mSections == other.mSections) \ + && (mPartitions == other.mPartitions) \ && (mEncAesKeys == other.mEncAesKeys); } @@ -272,13 +287,13 @@ void NcaHeader::copyFrom(const NcaHeader & other) mBinaryBlob.clear(); mDistributionType = other.mDistributionType; mContentType = other.mContentType; - mCryptoType = other.mCryptoType; + mKeyGeneration = other.mKeyGeneration; mKaekIndex = other.mKaekIndex; - mNcaSize = other.mNcaSize; + mContentSize = other.mContentSize; mProgramId = other.mProgramId; mContentIndex = other.mContentIndex; mSdkAddonVersion = other.mSdkAddonVersion; - mSections = other.mSections; + mPartitions = other.mPartitions; mEncAesKeys = other.mEncAesKeys; } } diff --git a/programs/ncatool/source/main.cpp b/programs/ncatool/source/main.cpp index d9e19b8..55b5feb 100644 --- a/programs/ncatool/source/main.cpp +++ b/programs/ncatool/source/main.cpp @@ -2,8 +2,10 @@ #include #include #include +#include #include #include +#include #include #ifdef _WIN32 #include @@ -11,8 +13,6 @@ #include #endif -const size_t kNcaSectorSize = nx::NcaHeader::kBlockSize; - std::string kFormatVersionStr[] { "NCA2", @@ -40,7 +40,7 @@ std::string kEncryptionTypeStr[] "None", "AesXts", "AesCtr", - "BKTR" + "AesCtrEx" }; std::string kHashTypeStr[] @@ -64,16 +64,6 @@ std::string kKaekIndexStr[] "System" }; -enum EncryptionType -{ - CRYPT_AUTO, - CRYPT_NONE, - CRYPT_AESXTS, - CRYPT_AESCTR, - CRYPT_BKTR -}; - -static const byte_t kNcaMagic[2][4] = {{'N','C','A','2'}, {'N','C','A','3'}}; enum KeysetType { @@ -87,18 +77,7 @@ static const byte_t* kNcaHeaderKey[2][2] = { crypto::aes::nx::prod::nca_header_key[0], crypto::aes::nx::prod::nca_header_key[1] } }; -#pragma pack(push,1) -struct sNcaFsHeader -{ - le_uint16_t version; // usually 0x0002 - byte_t format_type; // RomFs(0x00), PartitionFs(0x01) - byte_t hash_type; // HashTypeAuto(0x00), HashTypeHierarchicalSha256(0x02), HashTypeHierarchicalIntegrity(0x03).RomFs uses (0x03) this is forced, PartitionFs uses (0x02). - byte_t encryption_type; // EncryptionTypeAuto(0x00), EncryptionTypeNone(0x01), EncryptionTypeAesCtr(0x03) - byte_t reserved[3]; -}; -#pragma pack(pop) - -inline size_t sectorToOffset(size_t sector_index) { return sector_index * kNcaSectorSize; } +inline size_t sectorToOffset(size_t sector_index) { return sector_index * nx::nca::kSectorSize; } void initNcaCtr(byte_t ctr[crypto::aes::kAesBlockSize], uint32_t generation) { @@ -109,110 +88,37 @@ void initNcaCtr(byte_t ctr[crypto::aes::kAesBlockSize], uint32_t generation) } } -void hexDump(const byte_t* data, size_t len) -{ - for (size_t i = 0; i < len; i++) - { - printf("%02X", data[i]); - } -} - -void xorData(const byte_t* a, const byte_t* b, byte_t* out, size_t len) -{ - for (size_t i = 0; i < len; i++) - { - out[i] = a[i] ^ b[i]; - } -} - -void decryptNcaHeader(byte_t header[0xc00], const byte_t* key[2]) +void decryptNcaHeader(byte_t header[nx::nca::kHeaderSize], const byte_t* key[2]) { byte_t tweak[crypto::aes::kAesBlockSize]; // decrypt main header - byte_t raw_hdr[kNcaSectorSize]; + byte_t raw_hdr[nx::nca::kSectorSize]; nx::NcaHeader hdr; crypto::aes::AesXtsMakeTweak(tweak, 1); - crypto::aes::AesXtsDecryptSector(header + sectorToOffset(1), kNcaSectorSize, key[0], key[1], tweak, raw_hdr); - hdr.importBinary(raw_hdr, kNcaSectorSize); + crypto::aes::AesXtsDecryptSector(header + sectorToOffset(1), nx::nca::kSectorSize, key[0], key[1], tweak, raw_hdr); + hdr.importBinary(raw_hdr, nx::nca::kSectorSize); + + bool useNca2SectorIndex = hdr.getFormatVersion() == nx::NcaHeader::NCA2_FORMAT; // decrypt whole header - for (size_t i = 0; i < 6; i++) + for (size_t i = 0; i < nx::nca::kHeaderSectorNum; i++) { - crypto::aes::AesXtsMakeTweak(tweak, (i > 1 && hdr.getFormatVersion() == nx::NcaHeader::NCA2_FORMAT)? 0 : i); - crypto::aes::AesXtsDecryptSector(header + sectorToOffset(i), kNcaSectorSize, key[0], key[1], tweak, header + sectorToOffset(i)); + crypto::aes::AesXtsMakeTweak(tweak, (i > 1 && useNca2SectorIndex)? 0 : i); + crypto::aes::AesXtsDecryptSector(header + sectorToOffset(i), nx::nca::kSectorSize, key[0], key[1], tweak, header + sectorToOffset(i)); } } -void decryptNcaSectorXts(const fnd::MemoryBlob& nca, byte_t out[kNcaSectorSize], size_t sector, const byte_t* key[2]) -{ - byte_t tweak[crypto::aes::kAesBlockSize]; - crypto::aes::AesXtsMakeTweak(tweak, sector); - crypto::aes::AesXtsDecryptSector(nca.getBytes() + sectorToOffset(sector), kNcaSectorSize, key[0], key[1], tweak, out); -} - -void decryptNcaSectorCtr(const fnd::MemoryBlob& nca, byte_t out[kNcaSectorSize], size_t sector, const byte_t* key) -{ - byte_t ctr[crypto::aes::kAesBlockSize]; - initNcaCtr(ctr, 0); - crypto::aes::AesIncrementCounter(ctr, (sector*kNcaSectorSize)/crypto::aes::kAesBlockSize, ctr); - crypto::aes::AesCtr(nca.getBytes() + sector*kNcaSectorSize, kNcaSectorSize, key, ctr, out); -} - -void dumpNcaSector(byte_t out[kNcaSectorSize]) -{ - for (size_t j = 0; j < kNcaSectorSize / crypto::aes::kAesBlockSize; j++) - { - hexDump(out + j * crypto::aes::kAesBlockSize, crypto::aes::kAesBlockSize); - printf("\n"); - } -} - -void dumpHxdStyleSector(byte_t* out, size_t len) -{ - // iterate over 0x10 blocks - for (size_t i = 0; i < (len / crypto::aes::kAesBlockSize); i++) - { - // for block i print each byte - for (size_t j = 0; j < crypto::aes::kAesBlockSize; j++) - { - printf("%02X ", out[i*crypto::aes::kAesBlockSize + j]); - } - printf(" "); - for (size_t j = 0; j < crypto::aes::kAesBlockSize; j++) - { - printf("%c", isalnum(out[i*crypto::aes::kAesBlockSize + j]) ? out[i*crypto::aes::kAesBlockSize + j] : '.'); - } - printf("\n"); - } - - /* - for (size_t i = 0; i < len % crypto::aes::kAesBlockSize; i++) - { - printf("%02X ", out[(len / crypto::aes::kAesBlockSize)*crypto::aes::kAesBlockSize + i]); - } - for (size_t i = 0; i < crypto::aes::kAesBlockSize - (len % crypto::aes::kAesBlockSize); i++) - { - printf(" "); - } - for (size_t i = 0; i < len % crypto::aes::kAesBlockSize; i++) - { - printf("%c", out[(len / crypto::aes::kAesBlockSize)*crypto::aes::kAesBlockSize + i]); - } - */ -} - - bool testNcaHeaderKey(const byte_t* header_src, const byte_t* key[2]) { bool validKey = false; - byte_t header_dec[kNcaSectorSize]; + byte_t header_dec[nx::nca::kSectorSize]; byte_t tweak[crypto::aes::kAesBlockSize]; // try key crypto::aes::AesXtsMakeTweak(tweak, 1); - crypto::aes::AesXtsDecryptSector(header_src + sectorToOffset(1), kNcaSectorSize, key[0], key[1], tweak, header_dec); - if (memcmp(header_dec, kNcaMagic[0], 4) == 0 || memcmp(header_dec, kNcaMagic[1], 4) == 0) + crypto::aes::AesXtsDecryptSector(header_src + sectorToOffset(1), nx::nca::kSectorSize, key[0], key[1], tweak, header_dec); + if (memcmp(header_dec, nx::nca::kNca2Sig.c_str(), 4) == 0 || memcmp(header_dec, nx::nca::kNca3Sig.c_str(), 4) == 0) { validKey = true; } @@ -233,6 +139,86 @@ KeysetType getKeysetFromNcaHeader(const byte_t* header_src) throw fnd::Exception("Failed to determine NCA header key"); } +void printHeader(const byte_t* header) +{ + nx::NcaHeader hdr; + hdr.importBinary(header + sectorToOffset(1), nx::nca::kSectorSize); + + printf("[NCA Header]\n"); + printf(" Format Type: %s\n", kFormatVersionStr[hdr.getFormatVersion()].c_str()); + printf(" Dist. Type: %s\n", kDistributionTypeStr[hdr.getDistributionType()].c_str()); + printf(" Content Type: %s\n", kContentTypeStr[hdr.getContentType()].c_str()); + printf(" Key Generation: %d\n", hdr.getKeyGeneration()); + printf(" Kaek Index: %s (%d)\n", kKaekIndexStr[hdr.getKaekIndex()].c_str(), hdr.getKaekIndex()); + printf(" Size: 0x%" PRIx64 "\n", hdr.getContentSize()); + printf(" ProgID: 0x%016" PRIx64 "\n", hdr.getProgramId()); + printf(" Content Index: %" PRIu32 "\n", hdr.getContentIndex()); + uint32_t ver = hdr.getSdkAddonVersion(); + printf(" SdkAddon Ver.: v%d.%d.%d (v%" PRIu32 ")\n", (ver>>24 & 0xff),(ver>>16 & 0xff),(ver>>8 & 0xff), ver); + printf(" RightsId: "); + fnd::SimpleTextOutput::hexDump(hdr.getRightsId(), 0x10); + printf("\n"); + printf(" Encrypted Key Area:\n"); + crypto::aes::sAes128Key zero_key; + memset(zero_key.key, 0, sizeof(zero_key)); + for (size_t i = 0; i < hdr.getEncAesKeys().getSize(); i++) + { + if (hdr.getEncAesKeys()[i] != zero_key) + { + printf(" %2lu: ", i); + fnd::SimpleTextOutput::hexDump(hdr.getEncAesKeys()[i].key, crypto::aes::kAes128KeySize); + printf("\n"); + } + } + + printf(" Sections:\n"); + for (size_t i = 0; i < hdr.getPartitions().getSize(); i++) + { + const nx::NcaHeader::sPartition& partition = hdr.getPartitions()[i]; + printf(" %lu:\n", i); + //printf(" Start Blk: %" PRId32 "\n", partition.start_blk); + //printf(" End Blk: %" PRId32 "\n", partition.end_blk); + printf(" Index: %d\n", partition.index); + printf(" Offset: 0x%" PRIx64 "\n", partition.offset); + printf(" Size: 0x%" PRIx64 "\n", partition.size); + + + size_t sector_index = 2 + partition.index; + + crypto::sha::sSha256Hash ncaFsHeaderHash; + crypto::sha::Sha256(header + sectorToOffset(sector_index), nx::nca::kSectorSize, ncaFsHeaderHash.bytes); + if (partition.hash.compare(ncaFsHeaderHash) == false) + { + throw fnd::Exception("ncatool", "NcaFsHeader has bad sha256 hash"); + } + + const nx::sNcaFsHeader* fsHdr = (const nx::sNcaFsHeader*)(header + sectorToOffset(sector_index)); + printf(" FsHeader:\n"); + printf(" Version: 0x%d\n", fsHdr->version.get()); + printf(" Format Type: %s\n", kFormatTypeStr[fsHdr->format_type].c_str()); + printf(" Hash Type: %s\n", kHashTypeStr[fsHdr->hash_type].c_str()); + printf(" Enc. Type: %s\n", kEncryptionTypeStr[fsHdr->encryption_type].c_str()); + if (fsHdr->format_type == nx::nca::FORMAT_ROMFS) + { + + } + else if (fsHdr->format_type == nx::nca::FORMAT_PFS0) + { + const nx::sPfsSuperBlock* pfs0 = (const nx::sPfsSuperBlock*)(header + sectorToOffset(sector_index) + sizeof(nx::sNcaFsHeader)); + printf(" PFS0 SuperBlock:\n"); + printf(" Master Hash: \n"); + printf(" HashBlockSize: 0x%x\n", pfs0->hash_block_size.get()); + printf(" Unknown: 0x%x\n", pfs0->unk_0x02.get()); + printf(" HashDataOffset: 0x%" PRIx64 "\n", pfs0->hash_data.offset.get()); + printf(" HashDataSize: 0x%" PRIx64 "\n", pfs0->hash_data.size.get()); + printf(" HashTargetOffset: 0x%" PRIx64 "\n", pfs0->hash_target.offset.get()); + printf(" HashTargetSize: 0x%" PRIx64 "\n", pfs0->hash_target.size.get()); + + } + + } +} + int main(int argc, char** argv) { if (argc < 2) @@ -244,122 +230,13 @@ int main(int argc, char** argv) try { fnd::MemoryBlob nca; - fnd::io::readFile(argv[1], nca); + fnd::io::readFile(argv[1], 0x0, nx::nca::kHeaderSize, nca); KeysetType keyset = getKeysetFromNcaHeader(nca.getBytes()); decryptNcaHeader(nca.getBytes(), kNcaHeaderKey[keyset]); - //dumpHxdStyleSector(nca.getBytes(), 0xc00); - // nca test - if (argc == 2 || argc == 3) - { - //decryptNcaSectorXts(nca, sector, 1, crypto::aes::nx::dev::nca_header_key[0], crypto::aes::nx::dev::nca_header_key[1]); - - nx::NcaHeader hdr; - hdr.importBinary(nca.getBytes() + sectorToOffset(1), kNcaSectorSize); - - printf("[NCA Header]\n"); - printf(" Format Type: %s\n", kFormatVersionStr[hdr.getFormatVersion()].c_str()); - printf(" Dist. Type: %s\n", kDistributionTypeStr[hdr.getDistributionType()].c_str()); - printf(" Type: %s\n", kContentTypeStr[hdr.getContentType()].c_str()); - printf(" Crypto Type: %d\n", hdr.getCryptoType()); - printf(" Kaek Index: %s (%d)\n", kKaekIndexStr[hdr.getKaekIndex()].c_str(), hdr.getKaekIndex()); - printf(" Size: 0x%" PRIx64 "\n", hdr.getNcaSize()); - printf(" ProgID: 0x%016" PRIx64 "\n", hdr.getProgramId()); - printf(" Content. Idx: %" PRIu32 "\n", hdr.getContentIndex()); - uint32_t ver = hdr.getSdkAddonVersion(); - printf(" SdkAddon Ver.: v%d.%d.%d.%d (v%" PRIu32 ")\n", (ver>>24 & 0xff),(ver>>16 & 0xff),(ver>>8 & 0xff),(ver>>0 & 0xff), ver); - printf(" Encrypted Key Area:\n"); - for (size_t i = 0; i < hdr.getEncAesKeys().getSize(); i++) - { - printf(" %lu: ", i); - hexDump(hdr.getEncAesKeys()[i].key, crypto::aes::kAes128KeySize); - printf("\n"); - /* - byte_t key[crypto::aes::kAes128KeySize]; - crypto::aes::AesEcbDecrypt(hdr.getEncAesKeys()[i].key, crypto::aes::kAes128KeySize, crypto::aes::nx::dev::key_area_encryption_key_0, key); - printf(" dec: ", i); - hexDump(key, crypto::aes::kAes128KeySize); - printf("\n"); - */ - } - - printf(" Sections:\n"); - for (size_t i = 0; i < hdr.getSections().getSize(); i++) - { - const nx::NcaHeader::sSection& section = hdr.getSections()[i]; - printf(" %lu:\n", i); - //printf(" Start Blk: %" PRId32 "\n", section.start_blk); - //printf(" End Blk: %" PRId32 "\n", section.end_blk); - printf(" Offset: 0x%" PRIx64 "\n", section.offset); - printf(" Size: 0x%" PRIx64 "\n", section.size); - - - size_t sector_index = 1 + (hdr.getSections().getSize() - i); - - byte_t hash[crypto::sha::kSha256HashLen]; - crypto::sha::Sha256(nca.getBytes() + sectorToOffset(sector_index), kNcaSectorSize, hash); - if (section.hash.compare(hash) == false) - { - throw fnd::Exception("ncatool", "NcaFsHeader has bad sha256 hash"); - } - - const sNcaFsHeader* fsHdr = (const sNcaFsHeader*)(nca.getBytes() + sectorToOffset(sector_index)); - printf(" FsHeader:\n"); - printf(" Version: 0x%d\n", fsHdr->version.get()); - printf(" Format Type: %s\n", kFormatTypeStr[fsHdr->format_type].c_str()); - printf(" Hash Type: %s\n", kHashTypeStr[fsHdr->hash_type].c_str()); - printf(" Enc. Type: %s\n", kEncryptionTypeStr[fsHdr->encryption_type].c_str()); - /* - printf(" Hash: "); - hexDump(section.hash.bytes, crypto::sha::kSha256HashLen); - printf("\n"); - byte_t hash[crypto::sha::kSha256HashLen]; - crypto::sha::Sha256(nca.getBytes() + sectorToOffset(sector_index), kNcaSectorSize, hash); - printf(" Hash: "); - hexDump(hash, crypto::sha::kSha256HashLen); - printf("\n"); - */ - //dumpHxdStyleSector(nca.getBytes() + sectorToOffset(sector_index), 0x10); - - } - - - - -#ifdef USE_OLD_CODE - if (argc == 3) - { -#ifdef _WIN32 - _mkdir(argv[2]); -#else - mkdir(argv[2], S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); -#endif - - for (size_t i = 0; i < hdr.getSections().getSize(); i++) - { - const nx::NcaHeader::sSection& section = hdr.getSections()[i]; -#ifdef _WIN32 - fnd::io::writeFile(std::string(argv[2]) + "\\" + std::to_string(i) + ".bin" , nca.getBytes() + section.offset, section.size); -#else - fnd::io::writeFile(std::string(argv[2]) + "/" + std::to_string(i) + ".bin", nca.getBytes() + section.offset, section.size); -#endif - } - } - } - if (argc == 4) - { - printf("decrypt test\n"); - byte_t sect[kNcaSectorSize];; - for (size_t i = 0; i < 6; i++) - { - decryptNcaSectorXts(nca, sect, i, crypto::aes::nx::dev::nca_header_key[0], crypto::aes::nx::dev::nca_header_key[1]); - dumpNcaSector(sect); - } - } -#endif - } + printHeader(nca.getBytes()); } catch (const fnd::Exception& e) { printf("%s\n",e.what());