[nx|ncatool] Take out NCA constants and structures to separate file. Add support for NCA key area index 5.

This commit is contained in:
jakcron 2018-04-07 16:01:11 +08:00
parent dec7c50cc4
commit fd9261b789
4 changed files with 352 additions and 373 deletions

View file

@ -1,10 +1,7 @@
#pragma once #pragma once
#include <string> #include <nx/nca.h>
#include <fnd/types.h>
#include <fnd/MemoryBlob.h> #include <fnd/MemoryBlob.h>
#include <fnd/List.h> #include <fnd/List.h>
#include <crypto/aes.h>
#include <crypto/sha.h>
#include <fnd/ISerialiseableBinary.h> #include <fnd/ISerialiseableBinary.h>
namespace nx namespace nx
@ -19,37 +16,16 @@ namespace nx
NCA3_FORMAT NCA3_FORMAT
}; };
enum DistributionType struct sPartition
{
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
{ {
byte_t index;
uint64_t offset; uint64_t offset;
uint64_t size; uint64_t size;
crypto::sha::sSha256Hash hash; crypto::sha::sSha256Hash hash;
const sSection& operator=(const sSection& other) const sPartition& operator=(const sPartition& other)
{ {
index = other.index;
offset = other.offset; offset = other.offset;
size = other.size; size = other.size;
hash = other.hash; hash = other.hash;
@ -57,21 +33,20 @@ namespace nx
return *this; 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) \ && (size == other.size) \
&& (hash == other.hash); && (hash == other.hash);
} }
bool operator!=(const sSection& other) const bool operator!=(const sPartition& other) const
{ {
return !operator==(other); return !operator==(other);
} }
}; };
static const size_t kBlockSize = 0x200;
NcaHeader(); NcaHeader();
NcaHeader(const NcaHeader& other); NcaHeader(const NcaHeader& other);
NcaHeader(const byte_t* bytes, size_t len); NcaHeader(const byte_t* bytes, size_t len);
@ -92,85 +67,56 @@ namespace nx
void clear(); void clear();
FormatVersion getFormatVersion() const; FormatVersion getFormatVersion() const;
void setFormatVersion(FormatVersion ver); void setFormatVersion(FormatVersion ver);
DistributionType getDistributionType() const; nca::DistributionType getDistributionType() const;
void setDistributionType(DistributionType type); void setDistributionType(nca::DistributionType type);
ContentType getContentType() const; nca::ContentType getContentType() const;
void setContentType(ContentType type); void setContentType(nca::ContentType type);
byte_t getCryptoType() const; byte_t getKeyGeneration() const;
void setCryptoType(byte_t type); void setKeyGeneration(byte_t gen);
byte_t getKaekIndex() const; byte_t getKaekIndex() const;
void setKaekIndex(byte_t index); void setKaekIndex(byte_t index);
uint64_t getNcaSize() const; uint64_t getContentSize() const;
void setNcaSize(uint64_t size); void setContentSize(uint64_t size);
uint64_t getProgramId() const; uint64_t getProgramId() const;
void setProgramId(uint64_t program_id); void setProgramId(uint64_t program_id);
uint32_t getContentIndex() const; uint32_t getContentIndex() const;
void setContentIndex(uint32_t index); void setContentIndex(uint32_t index);
uint32_t getSdkAddonVersion() const; uint32_t getSdkAddonVersion() const;
void setSdkAddonVersion(uint32_t version); void setSdkAddonVersion(uint32_t version);
const fnd::List<sSection>& getSections() const; const byte_t* getRightsId() const;
void addSection(const sSection& section); void setRightsId(const byte_t* rights_id);
const fnd::List<sPartition>& getPartitions() const;
void setPartitions(const fnd::List<sPartition>& partitions);
const fnd::List<crypto::aes::sAes128Key>& getEncAesKeys() const; const fnd::List<crypto::aes::sAes128Key>& getEncAesKeys() const;
void addEncAesKey(const crypto::aes::sAes128Key& key); void setEncAesKeys(const fnd::List<crypto::aes::sAes128Key>& keys);
private: private:
const std::string kModuleName = "NCA_HEADER"; const std::string kModuleName = "NCA_HEADER";
const std::string kNca2Sig = "NCA2";
const std::string kNca3Sig = "NCA3"; //static const uint32_t kDefaultSdkAddonVersion = 721920;
static const size_t kSectionNum = 4;
static const size_t kAesKeyNum = 4;
static const uint32_t kDefaultSdkAddonVersion = 721920;
enum ProgramPartitionId enum ProgramPartitionId
{ {
SECTION_CODE, PARTITION_CODE,
SECTION_DATA, PARTITION_DATA,
SECTION_LOGO, 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 // binary
fnd::MemoryBlob mBinaryBlob; fnd::MemoryBlob mBinaryBlob;
// data // data
FormatVersion mFormatVersion; FormatVersion mFormatVersion;
DistributionType mDistributionType; nca::DistributionType mDistributionType;
ContentType mContentType; nca::ContentType mContentType;
byte_t mCryptoType; byte_t mKeyGeneration;
byte_t mKaekIndex; byte_t mKaekIndex;
uint64_t mNcaSize; uint64_t mContentSize;
uint64_t mProgramId; uint64_t mProgramId;
uint32_t mContentIndex; uint32_t mContentIndex;
uint32_t mSdkAddonVersion; uint32_t mSdkAddonVersion;
fnd::List<sSection> mSections; byte_t mRightsId[nca::kRightsIdLen];
fnd::List<sPartition> mPartitions;
fnd::List<crypto::aes::sAes128Key> mEncAesKeys; fnd::List<crypto::aes::sAes128Key> mEncAesKeys;
uint64_t blockNumToSize(uint32_t block_num) const; uint64_t blockNumToSize(uint32_t block_num) const;

141
lib/libnx/include/nx/nca.h Normal file
View file

@ -0,0 +1,141 @@
#include <string>
#include <fnd/types.h>
#include <crypto/aes.h>
#include <crypto/sha.h>
#include <fnd/ISerialiseableBinary.h>
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)
}

View file

@ -12,38 +12,49 @@ void NcaHeader::exportBinary()
switch(mFormatVersion) switch(mFormatVersion)
{ {
case (NCA2_FORMAT): case (NCA2_FORMAT):
strncpy(hdr->signature, kNca2Sig.c_str(), 4); strncpy(hdr->signature, nca::kNca2Sig.c_str(), 4);
break; break;
case (NCA3_FORMAT): case (NCA3_FORMAT):
strncpy(hdr->signature, kNca3Sig.c_str(), 4); strncpy(hdr->signature, nca::kNca3Sig.c_str(), 4);
break; break;
default: default:
throw fnd::Exception(kModuleName, "Unsupported format version"); throw fnd::Exception(kModuleName, "Unsupported format version");
} }
hdr->distribution_type = mDistributionType; hdr->distribution_type = mDistributionType;
hdr->content_type = mContentType; 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->key_area_encryption_key_index = mKaekIndex;
hdr->nca_size = mNcaSize; hdr->content_size = mContentSize;
hdr->program_id = mProgramId; hdr->program_id = mProgramId;
hdr->content_index = mContentIndex; hdr->content_index = mContentIndex;
hdr->sdk_addon_version = mSdkAddonVersion; 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 // TODO: properly reconstruct NCA layout? atm in hands of user
for (size_t i = 0; i < mPartitions.getSize(); i++)
for (size_t i = 0; i < mSections.getSize(); i++)
{ {
// determine section index // determine partition index
byte_t section = mSections.getSize() - 1 - i; byte_t idx = mPartitions[i].index;
hdr->section[section].start = sizeToBlockNum(mSections[i].offset); if (mPartitions[i].index >= nca::kPartitionNum || hdr->partition[idx].enabled) continue;
hdr->section[section].end = (sizeToBlockNum(mSections[i].offset) + sizeToBlockNum(mSections[i].size));
hdr->section[section].enabled = true; hdr->partition[idx].start = sizeToBlockNum(mPartitions[i].offset);
hdr->section_hash[section] = mSections[i].hash; 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]; 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(); 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; 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; mFormatVersion = NCA3_FORMAT;
} }
@ -76,28 +87,26 @@ void NcaHeader::importBinary(const byte_t * bytes, size_t len)
throw fnd::Exception(kModuleName, "NCA header corrupt"); throw fnd::Exception(kModuleName, "NCA header corrupt");
} }
mDistributionType = (DistributionType)hdr->distribution_type; mDistributionType = (nca::DistributionType)hdr->distribution_type;
mContentType = (ContentType)hdr->content_type; mContentType = (nca::ContentType)hdr->content_type;
mCryptoType = MAX(hdr->crypto_type, hdr->crypto_type_2); mKeyGeneration = MAX(hdr->key_generation, hdr->key_generation_2);
mKaekIndex = hdr->key_area_encryption_key_index; mKaekIndex = hdr->key_area_encryption_key_index;
mNcaSize = *hdr->nca_size; mContentSize = *hdr->content_size;
mProgramId = *hdr->program_id; mProgramId = *hdr->program_id;
mContentIndex = *hdr->content_index; mContentIndex = *hdr->content_index;
mSdkAddonVersion = *hdr->sdk_addon_version; 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 // 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 // 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]); 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() void nx::NcaHeader::clear()
{ {
mFormatVersion = NCA3_FORMAT; mFormatVersion = NCA3_FORMAT;
mDistributionType = DIST_DOWNLOAD; mDistributionType = nca::DIST_DOWNLOAD;
mContentType = TYPE_PROGRAM; mContentType = nca::TYPE_PROGRAM;
mCryptoType = 0; mKeyGeneration = 0;
mKaekIndex = 0; mKaekIndex = 0;
mNcaSize = 0; mContentSize = 0;
mProgramId = 0; mProgramId = 0;
mContentIndex = 0; mContentIndex = 0;
mSdkAddonVersion = 0; mSdkAddonVersion = 0;
mSections.clear();
mPartitions.clear();
mEncAesKeys.clear(); mEncAesKeys.clear();
} }
@ -128,34 +138,34 @@ void nx::NcaHeader::setFormatVersion(FormatVersion version)
mFormatVersion = version; mFormatVersion = version;
} }
nx::NcaHeader::DistributionType nx::NcaHeader::getDistributionType() const nx::nca::DistributionType nx::NcaHeader::getDistributionType() const
{ {
return mDistributionType; return mDistributionType;
} }
void nx::NcaHeader::setDistributionType(DistributionType type) void nx::NcaHeader::setDistributionType(nca::DistributionType type)
{ {
mDistributionType = type; mDistributionType = type;
} }
nx::NcaHeader::ContentType nx::NcaHeader::getContentType() const nx::nca::ContentType nx::NcaHeader::getContentType() const
{ {
return mContentType; return mContentType;
} }
void nx::NcaHeader::setContentType(ContentType type) void nx::NcaHeader::setContentType(nca::ContentType type)
{ {
mContentType = 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 byte_t nx::NcaHeader::getKaekIndex() const
@ -168,14 +178,14 @@ void nx::NcaHeader::setKaekIndex(byte_t index)
mKaekIndex = 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 uint64_t NcaHeader::getProgramId() const
@ -208,18 +218,28 @@ void nx::NcaHeader::setSdkAddonVersion(uint32_t version)
mSdkAddonVersion = version; mSdkAddonVersion = version;
} }
const fnd::List<NcaHeader::sSection>& 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);
{ }
throw fnd::Exception(kModuleName, "Too many NCA sections");
const fnd::List<NcaHeader::sPartition>& NcaHeader::getPartitions() const
{
return mPartitions;
}
void NcaHeader::setPartitions(const fnd::List<NcaHeader::sPartition>& partitions)
{
mPartitions = partitions;
if (mPartitions.getSize() >= nca::kPartitionNum)
{
throw fnd::Exception(kModuleName, "Too many NCA partitions");
} }
mSections.addElement(section);
} }
const fnd::List<crypto::aes::sAes128Key>& NcaHeader::getEncAesKeys() const const fnd::List<crypto::aes::sAes128Key>& NcaHeader::getEncAesKeys() const
@ -227,37 +247,32 @@ const fnd::List<crypto::aes::sAes128Key>& NcaHeader::getEncAesKeys() const
return mEncAesKeys; return mEncAesKeys;
} }
void NcaHeader::addEncAesKey(const crypto::aes::sAes128Key & key) void NcaHeader::setEncAesKeys(const fnd::List<crypto::aes::sAes128Key>& keys)
{ {
if (mEncAesKeys.getSize() >= kAesKeyNum) mEncAesKeys = keys;
{
throw fnd::Exception(kModuleName, "Too many NCA aes keys");
}
mEncAesKeys.addElement(key);
} }
uint64_t NcaHeader::blockNumToSize(uint32_t block_num) const 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 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 bool NcaHeader::isEqual(const NcaHeader & other) const
{ {
return (mDistributionType == other.mDistributionType) \ return (mDistributionType == other.mDistributionType) \
&& (mContentType == other.mContentType) \ && (mContentType == other.mContentType) \
&& (mCryptoType == other.mCryptoType) \ && (mKeyGeneration == other.mKeyGeneration) \
&& (mKaekIndex == other.mKaekIndex) \ && (mKaekIndex == other.mKaekIndex) \
&& (mNcaSize == other.mNcaSize) \ && (mContentSize == other.mContentSize) \
&& (mProgramId == other.mProgramId) \ && (mProgramId == other.mProgramId) \
&& (mContentIndex == other.mContentIndex) \ && (mContentIndex == other.mContentIndex) \
&& (mSdkAddonVersion == other.mSdkAddonVersion) \ && (mSdkAddonVersion == other.mSdkAddonVersion) \
&& (mSections == other.mSections) \ && (mPartitions == other.mPartitions) \
&& (mEncAesKeys == other.mEncAesKeys); && (mEncAesKeys == other.mEncAesKeys);
} }
@ -272,13 +287,13 @@ void NcaHeader::copyFrom(const NcaHeader & other)
mBinaryBlob.clear(); mBinaryBlob.clear();
mDistributionType = other.mDistributionType; mDistributionType = other.mDistributionType;
mContentType = other.mContentType; mContentType = other.mContentType;
mCryptoType = other.mCryptoType; mKeyGeneration = other.mKeyGeneration;
mKaekIndex = other.mKaekIndex; mKaekIndex = other.mKaekIndex;
mNcaSize = other.mNcaSize; mContentSize = other.mContentSize;
mProgramId = other.mProgramId; mProgramId = other.mProgramId;
mContentIndex = other.mContentIndex; mContentIndex = other.mContentIndex;
mSdkAddonVersion = other.mSdkAddonVersion; mSdkAddonVersion = other.mSdkAddonVersion;
mSections = other.mSections; mPartitions = other.mPartitions;
mEncAesKeys = other.mEncAesKeys; mEncAesKeys = other.mEncAesKeys;
} }
} }

View file

@ -2,8 +2,10 @@
#include <crypto/aes.h> #include <crypto/aes.h>
#include <fnd/io.h> #include <fnd/io.h>
#include <fnd/MemoryBlob.h> #include <fnd/MemoryBlob.h>
#include <fnd/SimpleTextOutput.h>
#include <nx/NXCrypto.h> #include <nx/NXCrypto.h>
#include <nx/NcaHeader.h> #include <nx/NcaHeader.h>
#include <nx/PfsHeader.h>
#include <inttypes.h> #include <inttypes.h>
#ifdef _WIN32 #ifdef _WIN32
#include <direct.h> #include <direct.h>
@ -11,8 +13,6 @@
#include <sys/stat.h> #include <sys/stat.h>
#endif #endif
const size_t kNcaSectorSize = nx::NcaHeader::kBlockSize;
std::string kFormatVersionStr[] std::string kFormatVersionStr[]
{ {
"NCA2", "NCA2",
@ -40,7 +40,7 @@ std::string kEncryptionTypeStr[]
"None", "None",
"AesXts", "AesXts",
"AesCtr", "AesCtr",
"BKTR" "AesCtrEx"
}; };
std::string kHashTypeStr[] std::string kHashTypeStr[]
@ -64,16 +64,6 @@ std::string kKaekIndexStr[]
"System" "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 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] } { crypto::aes::nx::prod::nca_header_key[0], crypto::aes::nx::prod::nca_header_key[1] }
}; };
#pragma pack(push,1) inline size_t sectorToOffset(size_t sector_index) { return sector_index * nx::nca::kSectorSize; }
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; }
void initNcaCtr(byte_t ctr[crypto::aes::kAesBlockSize], uint32_t generation) 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) void decryptNcaHeader(byte_t header[nx::nca::kHeaderSize], const byte_t* key[2])
{
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])
{ {
byte_t tweak[crypto::aes::kAesBlockSize]; byte_t tweak[crypto::aes::kAesBlockSize];
// decrypt main header // decrypt main header
byte_t raw_hdr[kNcaSectorSize]; byte_t raw_hdr[nx::nca::kSectorSize];
nx::NcaHeader hdr; nx::NcaHeader hdr;
crypto::aes::AesXtsMakeTweak(tweak, 1); crypto::aes::AesXtsMakeTweak(tweak, 1);
crypto::aes::AesXtsDecryptSector(header + sectorToOffset(1), kNcaSectorSize, key[0], key[1], tweak, raw_hdr); crypto::aes::AesXtsDecryptSector(header + sectorToOffset(1), nx::nca::kSectorSize, key[0], key[1], tweak, raw_hdr);
hdr.importBinary(raw_hdr, kNcaSectorSize); hdr.importBinary(raw_hdr, nx::nca::kSectorSize);
bool useNca2SectorIndex = hdr.getFormatVersion() == nx::NcaHeader::NCA2_FORMAT;
// decrypt whole header // 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::AesXtsMakeTweak(tweak, (i > 1 && useNca2SectorIndex)? 0 : i);
crypto::aes::AesXtsDecryptSector(header + sectorToOffset(i), kNcaSectorSize, key[0], key[1], tweak, header + sectorToOffset(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 testNcaHeaderKey(const byte_t* header_src, const byte_t* key[2])
{ {
bool validKey = false; bool validKey = false;
byte_t header_dec[kNcaSectorSize]; byte_t header_dec[nx::nca::kSectorSize];
byte_t tweak[crypto::aes::kAesBlockSize]; byte_t tweak[crypto::aes::kAesBlockSize];
// try key // try key
crypto::aes::AesXtsMakeTweak(tweak, 1); crypto::aes::AesXtsMakeTweak(tweak, 1);
crypto::aes::AesXtsDecryptSector(header_src + sectorToOffset(1), kNcaSectorSize, key[0], key[1], tweak, header_dec); crypto::aes::AesXtsDecryptSector(header_src + sectorToOffset(1), nx::nca::kSectorSize, key[0], key[1], tweak, header_dec);
if (memcmp(header_dec, kNcaMagic[0], 4) == 0 || memcmp(header_dec, kNcaMagic[1], 4) == 0) if (memcmp(header_dec, nx::nca::kNca2Sig.c_str(), 4) == 0 || memcmp(header_dec, nx::nca::kNca3Sig.c_str(), 4) == 0)
{ {
validKey = true; validKey = true;
} }
@ -233,6 +139,86 @@ KeysetType getKeysetFromNcaHeader(const byte_t* header_src)
throw fnd::Exception("Failed to determine NCA header key"); 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) int main(int argc, char** argv)
{ {
if (argc < 2) if (argc < 2)
@ -244,122 +230,13 @@ int main(int argc, char** argv)
try try
{ {
fnd::MemoryBlob nca; fnd::MemoryBlob nca;
fnd::io::readFile(argv[1], nca); fnd::io::readFile(argv[1], 0x0, nx::nca::kHeaderSize, nca);
KeysetType keyset = getKeysetFromNcaHeader(nca.getBytes()); KeysetType keyset = getKeysetFromNcaHeader(nca.getBytes());
decryptNcaHeader(nca.getBytes(), kNcaHeaderKey[keyset]); decryptNcaHeader(nca.getBytes(), kNcaHeaderKey[keyset]);
//dumpHxdStyleSector(nca.getBytes(), 0xc00);
// nca test printHeader(nca.getBytes());
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
}
} catch (const fnd::Exception& e) } catch (const fnd::Exception& e)
{ {
printf("%s\n",e.what()); printf("%s\n",e.what());