diff --git a/lib/libnx/include/nx/XciHeader.h b/lib/libnx/include/nx/XciHeader.h new file mode 100644 index 0000000..4253e52 --- /dev/null +++ b/lib/libnx/include/nx/XciHeader.h @@ -0,0 +1,134 @@ +#pragma once +#include +#include +#include +#include + +namespace nx +{ + class XciHeader : + public fnd::ISerialiseableBinary + { + public: + XciHeader(); + XciHeader(const XciHeader& other); + XciHeader(const byte_t* bytes, size_t len); + + bool operator==(const XciHeader& other) const; + bool operator!=(const XciHeader& other) const; + void operator=(const XciHeader& other); + + // to be used after export + const byte_t* getBytes() const; + size_t getSize() const; + + // export/import binary + void exportBinary(); + void importBinary(const byte_t* bytes, size_t len); + + // variables + void clear(); + uint32_t getRomAreaStartPage() const; + void setRomAreaStartPage(uint32_t startPage); + uint32_t getBackupAreaStartPage() const; + void setBackupAreaStartPage(uint32_t startPage); + byte_t getKekIndex() const; + void setKekIndex(byte_t kekIndex); + byte_t getTitleKeyDecIndex() const; + void setTitleKeyDecIndex(byte_t index); + byte_t getRomSizeType() const; + void setRomSizeType(byte_t romSizeType); + byte_t getCardHeaderVersion() const; + void setCardHeaderVersion(byte_t version); + byte_t getFlags() const; + void setFlags(byte_t flags); + uint64_t getPackageId() const; + void setPackageId(uint64_t id); + uint32_t getValidDataEndPage() const; + void setValidDataEndPage(uint32_t page); + const crypto::aes::sAesIvCtr& getAesCbcIv() const; + void setAesCbcIv(const crypto::aes::sAesIvCtr& iv); + uint64_t getPartitionFsAddress() const; + void setPartitionFsAddress(uint64_t address); + uint64_t getPartitionFsSize() const; + void setPartitionFsSize(uint64_t size); + const crypto::sha::sSha256Hash& getPartitionFsHash() const; + void setPartitionFsHash(const crypto::sha::sSha256Hash& hash); + const crypto::sha::sSha256Hash& getInitialDataHash() const; + void setInitialDataHash(const crypto::sha::sSha256Hash& hash); + uint32_t getSelSec() const; + void setSelSec(uint32_t sel_sec); + uint32_t getSelT1Key() const; + void setSelT1Key(uint32_t sel_t1_key); + uint32_t getSelKey() const; + void setSelKey(uint32_t sel_key); + uint32_t getLimAreaPage() const; + void setLimAreaPage(uint32_t page); + + uint32_t getFwVerMajor() const; + void setFwVerMajor(uint32_t ver); + uint32_t getFwVerMinor() const; + void setFwVerMinor(uint32_t ver); + uint32_t getAccCtrl1() const; + void setAccCtrl1(uint32_t acc_ctrl_1); + uint32_t getWait1TimeRead() const; + void setWait1TimeRead(uint32_t seconds); + uint32_t getWait2TimeRead() const; + void setWait2TimeRead(uint32_t seconds); + uint32_t getWait1TimeWrite() const; + void setWait1TimeWrite(uint32_t seconds); + uint32_t getWait2TimeWrite() const; + void setWait2TimeWrite(uint32_t seconds); + uint32_t getFwMode() const; + void setFwMode(uint32_t fw_mode); + uint32_t getUppVersion() const; + void setUppVersion(uint32_t version); + const byte_t* getUppHash() const; + void setUppHash(const byte_t* hash); + uint64_t getUppId() const; + void setUppId(uint64_t id); + + private: + const std::string kModuleName = "XCI_HEADER"; + + // binary + fnd::MemoryBlob mBinaryBlob; + + // data + uint32_t mRomAreaStartPage; + uint32_t mBackupAreaStartPage; + byte_t mKekIndex; + byte_t mTitleKeyDecIndex; + byte_t mRomSize; + byte_t mCardHeaderVersion; + byte_t mFlags; + uint64_t mPackageId; + uint32_t mValidDataEndPage; + crypto::aes::sAesIvCtr mAesCbcIv; + uint64_t mPartitionFsHeaderAddress; + uint64_t mPartitionFsHeaderSize; + crypto::sha::sSha256Hash mPartitionFsHeaderHash; + crypto::sha::sSha256Hash mInitialDataHash; + uint32_t mSelSec; + uint32_t mSelT1Key; + uint32_t mSelKey; + uint32_t mLimAreaPage; + + // Encrypted Data + uint32_t mFwVersion[2]; + uint32_t mAccCtrl1; + uint32_t mWait1TimeRead; + uint32_t mWait2TimeRead; + uint32_t mWait1TimeWrite; + uint32_t mWait2TimeWrite; + uint32_t mFwMode; + uint32_t mUppVersion; + byte_t mUppHash[8]; + uint64_t mUppId; + + // helpers + bool isEqual(const XciHeader& other) const; + void copyFrom(const XciHeader& other); + }; + +} \ No newline at end of file diff --git a/lib/libnx/include/nx/XciUtils.h b/lib/libnx/include/nx/XciUtils.h new file mode 100644 index 0000000..0482bb1 --- /dev/null +++ b/lib/libnx/include/nx/XciUtils.h @@ -0,0 +1,13 @@ +#pragma once +#include + +namespace nx +{ + class XciUtils + { + public: + static inline uint64_t blockToAddr(uint32_t block) { return ((uint64_t)block) << 9; } + static void getXciHeaderAesIv(const nx::sXciHeader* hdr, byte_t* iv); + static void decryptXciHeader(const byte_t* src, byte_t* dst, const byte_t* key); + }; +} \ No newline at end of file diff --git a/lib/libnx/include/nx/xci.h b/lib/libnx/include/nx/xci.h index 47d3c1e..849a033 100644 --- a/lib/libnx/include/nx/xci.h +++ b/lib/libnx/include/nx/xci.h @@ -1,8 +1,10 @@ +#pragma once #include #include #include #include #include +#include #include namespace nx @@ -13,6 +15,7 @@ namespace nx static const uint32_t kHeaderEncOffset = 0x90; static const uint32_t kHeaderEncSize = 0x70; static const uint32_t kPageSize = 0x200; + static const uint32_t kUppHashLen = 8; static const uint32_t kCardKeyAreaPageCount = 8; static const uint32_t kCardHeaderPageCount = 1; static const uint32_t kReservedAreaPageCount = 55; @@ -21,6 +24,12 @@ namespace nx static const uint32_t kNormalAreaStartPageAddress = kReservedAreaPageCount + kCertAreaPageCount + kCardHeaderPageCount + kCardKeyAreaPageCount; + enum KekIndex + { + KEK_XCIE, + KEK_XCIR + }; + enum RomSize { ROM_SIZE_1GB = 0xFA, @@ -63,12 +72,12 @@ namespace nx byte_t flags; le_uint64_t package_id; le_uint32_t valid_data_end_page; - byte_t reserved_01[4]; - byte_t encryption_iv[16]; + byte_t reserved_00[4]; + crypto::aes::sAesIvCtr aescbc_iv; le_uint64_t partition_fs_header_address; le_uint64_t partition_fs_header_size; - byte_t partition_fs_header_hash[0x20]; - byte_t initial_data_hash[0x20]; + crypto::sha::sSha256Hash partition_fs_header_hash; + crypto::sha::sSha256Hash initial_data_hash; le_uint32_t sel_sec; le_uint32_t sel_t1_key; le_uint32_t sel_key; @@ -82,13 +91,19 @@ namespace nx le_uint32_t wait_2_time_write; le_uint32_t fw_mode; le_uint32_t upp_version; - byte_t reserved_03[0x4]; - byte_t upp_hash[8]; + byte_t reserved_01[0x4]; + byte_t upp_hash[xci::kUppHashLen]; le_uint64_t upp_id; - byte_t reserved_04[0x38]; + byte_t reserved_02[0x38]; // END ENCRYPTION }; + struct sXciHeaderPage + { + byte_t signature[crypto::rsa::kRsa2048Size]; + sXciHeader header; + }; // sizeof() = 512 (1 page) + struct sInitialData { byte_t key_source[16]; // { package_id[8], zeros[8]} diff --git a/lib/libnx/source/XciHeader.cpp b/lib/libnx/source/XciHeader.cpp new file mode 100644 index 0000000..7bd95be --- /dev/null +++ b/lib/libnx/source/XciHeader.cpp @@ -0,0 +1,499 @@ +#include + +bool nx::XciHeader::isEqual(const XciHeader& other) const +{ + return ( mRomAreaStartPage == other.mRomAreaStartPage \ + & mBackupAreaStartPage == other.mBackupAreaStartPage \ + & mKekIndex == other.mKekIndex \ + & mTitleKeyDecIndex == other.mTitleKeyDecIndex \ + & mRomSize == other.mRomSize \ + & mCardHeaderVersion == other.mCardHeaderVersion \ + & mFlags == other.mFlags \ + & mPackageId == other.mPackageId \ + & mValidDataEndPage == other.mValidDataEndPage \ + & mAesCbcIv == other.mAesCbcIv \ + & mPartitionFsHeaderAddress == other.mPartitionFsHeaderAddress \ + & mPartitionFsHeaderSize == other.mPartitionFsHeaderSize \ + & mPartitionFsHeaderHash == other.mPartitionFsHeaderHash \ + & mInitialDataHash == other.mInitialDataHash \ + & mSelSec == other.mSelSec \ + & mSelT1Key == other.mSelT1Key \ + & mSelKey == other.mSelKey \ + & mLimAreaPage == other.mLimAreaPage \ + & mFwVersion[0] == other.mFwVersion[0] \ + & mFwVersion[1] == other.mFwVersion[1] \ + & mAccCtrl1 == other.mAccCtrl1 \ + & mWait1TimeRead == other.mWait1TimeRead \ + & mWait2TimeRead == other.mWait2TimeRead \ + & mWait1TimeWrite == other.mWait1TimeWrite \ + & mWait2TimeWrite == other.mWait2TimeWrite \ + & mFwMode == other.mFwMode \ + & mUppVersion == other.mUppVersion \ + & memcmp(mUppHash, other.mUppHash, xci::kUppHashLen) \ + & mUppId == other.mUppId); +} + +void nx::XciHeader::copyFrom(const XciHeader& other) +{ + mRomAreaStartPage = other.mRomAreaStartPage; + mBackupAreaStartPage = other.mBackupAreaStartPage; + mKekIndex = other.mKekIndex; + mTitleKeyDecIndex = other.mTitleKeyDecIndex; + mRomSize = other.mRomSize; + mCardHeaderVersion = other.mCardHeaderVersion; + mFlags = other.mFlags; + mPackageId = other.mPackageId; + mValidDataEndPage = other.mValidDataEndPage; + mAesCbcIv = other.mAesCbcIv; + mPartitionFsHeaderAddress = other.mPartitionFsHeaderAddress; + mPartitionFsHeaderSize = other.mPartitionFsHeaderSize; + mPartitionFsHeaderHash = other.mPartitionFsHeaderHash; + mInitialDataHash = other.mInitialDataHash; + mSelSec = other.mSelSec; + mSelT1Key = other.mSelT1Key; + mSelKey = other.mSelKey; + mLimAreaPage = other.mLimAreaPage; + mFwVersion[0] = other.mFwVersion[0]; + mFwVersion[1] = other.mFwVersion[1]; + mAccCtrl1 = other.mAccCtrl1; + mWait1TimeRead = other.mWait1TimeRead; + mWait2TimeRead = other.mWait2TimeRead; + mWait1TimeWrite = other.mWait1TimeWrite; + mWait2TimeWrite = other.mWait2TimeWrite; + mFwMode = other.mFwMode; + mUppVersion = other.mUppVersion; + memcpy(mUppHash, other.mUppHash, xci::kUppHashLen); + mUppId = other.mUppId; +} + +nx::XciHeader::XciHeader() +{ + +} + +nx::XciHeader::XciHeader(const XciHeader& other) +{ + importBinary(other.getBytes(), other.getSize()); +} +nx::XciHeader::XciHeader(const byte_t* bytes, size_t len) +{ + importBinary(bytes, len); +} + +bool nx::XciHeader::operator==(const XciHeader& other) const +{ + return isEqual(other); +} +bool nx::XciHeader::operator!=(const XciHeader& other) const +{ + return isEqual(other) == false; +} +void nx::XciHeader::operator=(const XciHeader& other) +{ + copyFrom(other); +} + +// to be used after export +const byte_t* nx::XciHeader::getBytes() const +{ + return mBinaryBlob.getBytes(); +} + +size_t nx::XciHeader::getSize() const +{ + return mBinaryBlob.getSize(); +} + +// export/import binary +void nx::XciHeader::exportBinary() +{ + fnd::Exception(kModuleName, "exportBinary() not implemented"); +} + +void nx::XciHeader::importBinary(const byte_t* bytes, size_t len) +{ + // check input data size + if (len < sizeof(sXciHeader)) + { + throw fnd::Exception(kModuleName, "XCI header size is too small"); + } + + // clear internal members + clear(); + + // allocate internal local binary copy + mBinaryBlob.alloc(sizeof(sXciHeader)); + memcpy(mBinaryBlob.getBytes(), bytes, mBinaryBlob.getSize()); + + // get sXciHeader ptr + const nx::sXciHeader* hdr = (const nx::sXciHeader*)mBinaryBlob.getBytes(); + + // check XCI signature + if (std::string(hdr->signature, 4) != xci::kXciSig) + { + throw fnd::Exception(kModuleName, "XCI header corrupt"); + } + + mRomAreaStartPage = hdr->rom_area_start_page.get(); + mBackupAreaStartPage = hdr->backup_area_start_page.get(); + mKekIndex = hdr->key_flag & 7; + mTitleKeyDecIndex = (hdr->key_flag >> 4) & 7; + mRomSize = hdr->rom_size; + mCardHeaderVersion = hdr->card_header_version; + mFlags = hdr->flags; + mPackageId = hdr->package_id.get(); + mValidDataEndPage = hdr->valid_data_end_page.get(); + for (size_t i = 0; i < crypto::aes::kAesBlockSize; i++) + mAesCbcIv.iv[i] = hdr->aescbc_iv.iv[15-i]; + mPartitionFsHeaderAddress = hdr->partition_fs_header_address.get(); + mPartitionFsHeaderSize = hdr->partition_fs_header_size.get(); + mPartitionFsHeaderHash = hdr->partition_fs_header_hash; + mInitialDataHash = hdr->initial_data_hash; + mSelSec = hdr->sel_sec.get(); + mSelT1Key = hdr->sel_t1_key.get(); + mSelKey = hdr->sel_key.get(); + mLimAreaPage = hdr->lim_area.get(); + + // if decrypted + if (hdr->reserved_02[sizeof(hdr->reserved_02)-1] == 0x00 && hdr->reserved_02[sizeof(hdr->reserved_02)-2] == 0x00) + { + mFwVersion[xci::FWVER_MAJOR] = hdr->fw_version[xci::FWVER_MAJOR].get(); + mFwVersion[xci::FWVER_MINOR] = hdr->fw_version[xci::FWVER_MINOR].get(); + mAccCtrl1 = hdr->acc_ctrl_1.get(); + mWait1TimeRead = hdr->wait_1_time_read.get(); + mWait2TimeRead = hdr->wait_2_time_read.get(); + mWait1TimeWrite = hdr->wait_1_time_write.get(); + mWait2TimeWrite = hdr->wait_2_time_write.get(); + mFwMode = hdr->fw_mode.get(); + mUppVersion = hdr->upp_version.get(); + memcpy(mUppHash, hdr->upp_hash, xci::kUppHashLen); + mUppId = hdr->upp_id.get(); + } + +} + +// variables +void nx::XciHeader::clear() +{ + mRomAreaStartPage = 0; + mBackupAreaStartPage = 0; + mKekIndex = 0; + mTitleKeyDecIndex = 0; + mRomSize = 0; + mCardHeaderVersion = 0; + mFlags = 0; + mPackageId = 0; + mValidDataEndPage = 0; + memset(mAesCbcIv.iv, 0, sizeof(mAesCbcIv)); + mPartitionFsHeaderAddress = 0; + mPartitionFsHeaderSize = 0; + memset(mPartitionFsHeaderHash.bytes, 0, sizeof(mPartitionFsHeaderHash)); + memset(mInitialDataHash.bytes, 0, sizeof(mInitialDataHash)); + mSelSec = 0; + mSelT1Key = 0; + mSelKey = 0; + mLimAreaPage = 0; + mFwVersion[0] = 0; + mFwVersion[1] = 0; + mAccCtrl1 = 0; + mWait1TimeRead = 0; + mWait2TimeRead = 0; + mWait1TimeWrite = 0; + mWait2TimeWrite = 0; + mFwMode = 0; + mUppVersion = 0; + memset(mUppHash, 0, xci::kUppHashLen); + mUppId = 0; +} + +uint32_t nx::XciHeader::getRomAreaStartPage() const +{ + return mRomAreaStartPage; +} + +void nx::XciHeader::setRomAreaStartPage(uint32_t startPage) +{ + mRomAreaStartPage = startPage; +} + +uint32_t nx::XciHeader::getBackupAreaStartPage() const +{ + return mBackupAreaStartPage; +} + +void nx::XciHeader::setBackupAreaStartPage(uint32_t startPage) +{ + mBackupAreaStartPage = startPage; +} + +byte_t nx::XciHeader::getKekIndex() const +{ + return mKekIndex; +} + +void nx::XciHeader::setKekIndex(byte_t kekIndex) +{ + mKekIndex = kekIndex; +} + +byte_t nx::XciHeader::getTitleKeyDecIndex() const +{ + return mTitleKeyDecIndex; +} + +void nx::XciHeader::setTitleKeyDecIndex(byte_t index) +{ + mTitleKeyDecIndex = index; +} + +byte_t nx::XciHeader::getRomSizeType() const +{ + return mRomSize; +} + +void nx::XciHeader::setRomSizeType(byte_t romSizeType) +{ + mRomSize = romSizeType; +} + +byte_t nx::XciHeader::getCardHeaderVersion() const +{ + return mCardHeaderVersion; +} + +void nx::XciHeader::setCardHeaderVersion(byte_t version) +{ + mCardHeaderVersion = version; +} + +byte_t nx::XciHeader::getFlags() const +{ + return mFlags; +} + +void nx::XciHeader::setFlags(byte_t flags) +{ + mFlags = flags; +} + +uint64_t nx::XciHeader::getPackageId() const +{ + return mPackageId; +} + +void nx::XciHeader::setPackageId(uint64_t id) +{ + mPackageId = id; +} + +uint32_t nx::XciHeader::getValidDataEndPage() const +{ + return mValidDataEndPage; +} + +void nx::XciHeader::setValidDataEndPage(uint32_t page) +{ + mValidDataEndPage = page; +} + +const crypto::aes::sAesIvCtr& nx::XciHeader::getAesCbcIv() const +{ + return mAesCbcIv; +} + +void nx::XciHeader::setAesCbcIv(const crypto::aes::sAesIvCtr& iv) +{ + mAesCbcIv = iv; +} + +uint64_t nx::XciHeader::getPartitionFsAddress() const +{ + return mPartitionFsHeaderAddress; +} + +void nx::XciHeader::setPartitionFsAddress(uint64_t address) +{ + mPartitionFsHeaderAddress = address; +} + +uint64_t nx::XciHeader::getPartitionFsSize() const +{ + return mPartitionFsHeaderSize; +} + +void nx::XciHeader::setPartitionFsSize(uint64_t size) +{ + mPartitionFsHeaderSize = size; +} + +const crypto::sha::sSha256Hash& nx::XciHeader::getPartitionFsHash() const +{ + return mPartitionFsHeaderHash; +} + +void nx::XciHeader::setPartitionFsHash(const crypto::sha::sSha256Hash& hash) +{ + mPartitionFsHeaderHash = hash; +} + +const crypto::sha::sSha256Hash& nx::XciHeader::getInitialDataHash() const +{ + return mInitialDataHash; +} + +void nx::XciHeader::setInitialDataHash(const crypto::sha::sSha256Hash& hash) +{ + mInitialDataHash = hash; +} + +uint32_t nx::XciHeader::getSelSec() const +{ + return mSelSec; +} + +void nx::XciHeader::setSelSec(uint32_t sel_sec) +{ + mSelSec = sel_sec; +} + +uint32_t nx::XciHeader::getSelT1Key() const +{ + return mSelT1Key; +} + +void nx::XciHeader::setSelT1Key(uint32_t sel_t1_key) +{ + mSelT1Key = sel_t1_key; +} + +uint32_t nx::XciHeader::getSelKey() const +{ + return mSelKey; +} + +void nx::XciHeader::setSelKey(uint32_t sel_key) +{ + mSelKey = sel_key; +} + +uint32_t nx::XciHeader::getLimAreaPage() const +{ + return mLimAreaPage; +} + +void nx::XciHeader::setLimAreaPage(uint32_t page) +{ + mLimAreaPage = page; +} + + +uint32_t nx::XciHeader::getFwVerMajor() const +{ + return mFwVersion[xci::FWVER_MAJOR]; +} + +void nx::XciHeader::setFwVerMajor(uint32_t ver) +{ + mFwVersion[xci::FWVER_MAJOR] = ver; +} + +uint32_t nx::XciHeader::getFwVerMinor() const +{ + return mFwVersion[xci::FWVER_MINOR]; +} + +void nx::XciHeader::setFwVerMinor(uint32_t ver) +{ + mFwVersion[xci::FWVER_MINOR] = ver; +} + +uint32_t nx::XciHeader::getAccCtrl1() const +{ + return mAccCtrl1; +} + +void nx::XciHeader::setAccCtrl1(uint32_t acc_ctrl_1) +{ + mAccCtrl1 = acc_ctrl_1; +} + +uint32_t nx::XciHeader::getWait1TimeRead() const +{ + return mWait1TimeRead; +} + +void nx::XciHeader::setWait1TimeRead(uint32_t seconds) +{ + mWait1TimeRead = seconds; +} + +uint32_t nx::XciHeader::getWait2TimeRead() const +{ + return mWait2TimeRead; +} + +void nx::XciHeader::setWait2TimeRead(uint32_t seconds) +{ + mWait2TimeRead = seconds; +} + +uint32_t nx::XciHeader::getWait1TimeWrite() const +{ + return mWait1TimeWrite; +} + +void nx::XciHeader::setWait1TimeWrite(uint32_t seconds) +{ + mWait1TimeWrite = seconds; +} + +uint32_t nx::XciHeader::getWait2TimeWrite() const +{ + return mWait2TimeWrite; +} + +void nx::XciHeader::setWait2TimeWrite(uint32_t seconds) +{ + mWait2TimeWrite = seconds; +} + +uint32_t nx::XciHeader::getFwMode() const +{ + return mFwMode; +} + +void nx::XciHeader::setFwMode(uint32_t fw_mode) +{ + mFwMode = fw_mode; +} + +uint32_t nx::XciHeader::getUppVersion() const +{ + return mUppVersion; +} + +void nx::XciHeader::setUppVersion(uint32_t version) +{ + mUppVersion = version; +} + +const byte_t* nx::XciHeader::getUppHash() const +{ + return mUppHash; +} + +void nx::XciHeader::setUppHash(const byte_t* hash) +{ + memcpy(mUppHash, hash, xci::kUppHashLen); +} + +uint64_t nx::XciHeader::getUppId() const +{ + return mUppId; +} + +void nx::XciHeader::setUppId(uint64_t id) +{ + mUppId = id; +} + diff --git a/lib/libnx/source/XciUtils.cpp b/lib/libnx/source/XciUtils.cpp new file mode 100644 index 0000000..756725b --- /dev/null +++ b/lib/libnx/source/XciUtils.cpp @@ -0,0 +1,22 @@ +#include + +void nx::XciUtils::getXciHeaderAesIv(const nx::sXciHeader* hdr, byte_t* iv) +{ + for (size_t i = 0; i < 16; i++) + { + iv[15-i] = hdr->aescbc_iv.iv[i]; + } +} + +void nx::XciUtils::decryptXciHeader(const byte_t* src, byte_t* dst, const byte_t* key) +{ + byte_t iv[crypto::aes::kAesBlockSize]; + + getXciHeaderAesIv((const nx::sXciHeader*)src, iv); + + // copy plain + memcpy(dst, src, nx::xci::kHeaderEncOffset); + + // decrypt encrypted data + crypto::aes::AesCbcDecrypt(src + nx::xci::kHeaderEncOffset, nx::xci::kHeaderEncSize, key, iv, dst + nx::xci::kHeaderEncOffset); +} \ No newline at end of file