From 40b3b45dd6b0c426066178fb4cae6dea706e14a2 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sun, 17 Jun 2018 22:16:20 +0800 Subject: [PATCH] [nstool] Add heuristics for detecting CNMT and NACP without -t. --- programs/nstool/source/UserSettings.cpp | 117 ++++++++++++++++++++---- programs/nstool/source/UserSettings.h | 4 + 2 files changed, 105 insertions(+), 16 deletions(-) diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index 4087558..00216de 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include #include @@ -688,7 +690,7 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str) FileType UserSettings::determineFileTypeFromFile(const std::string& path) { - static const size_t kMaxReadSize = 0x1000; + static const size_t kMaxReadSize = 0x4000; FileType file_type = FILE_INVALID; fnd::SimpleFile file; fnd::MemoryBlob scratch; @@ -702,16 +704,7 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path) // close file file.close(); - //fnd::SimpleTextOutput::hxdStyleDump(scratch.getBytes(), scratch.getSize()); - - // prepare decrypted NCA data - byte_t nca_raw[nx::nca::kHeaderSize]; - nx::sNcaHeader* nca_header = (nx::sNcaHeader*)(nca_raw + nx::NcaUtils::sectorToOffset(1)); - if (scratch.getSize() >= nx::nca::kHeaderSize) - { - nx::NcaUtils::decryptNcaHeader(scratch.getBytes(), nca_raw, mKeyset.nca.header_key); - } // _QUICK_CAST resolves to a pointer of type 'st' located at scratch.getBytes() + 'oft' #define _QUICK_CAST(st, oft) ((st*)(scratch.getBytes() + (oft))) @@ -729,15 +722,18 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path) // test romfs else if (_ASSERT_SIZE(sizeof(nx::sRomfsHeader)) && _QUICK_CAST(nx::sRomfsHeader, 0)->header_size.get() == sizeof(nx::sRomfsHeader) && _QUICK_CAST(nx::sRomfsHeader, 0)->sections[1].offset.get() == (_QUICK_CAST(nx::sRomfsHeader, 0)->sections[0].offset.get() + _QUICK_CAST(nx::sRomfsHeader, 0)->sections[0].size.get())) file_type = FILE_ROMFS; - // test nca2 - else if (_ASSERT_SIZE(nx::nca::kHeaderSize) && nca_header->signature.get() == nx::nca::kNca2Sig) - file_type = FILE_NCA; - // test nca3 - else if (_ASSERT_SIZE(nx::nca::kHeaderSize) && nca_header->signature.get() == nx::nca::kNca3Sig) - file_type = FILE_NCA; // test npdm else if (_ASSERT_SIZE(sizeof(nx::sNpdmHeader)) && _QUICK_CAST(nx::sNpdmHeader, 0)->signature.get() == nx::npdm::kNpdmStructSig) file_type = FILE_NPDM; + // test nca + else if (determineValidNcaFromSample(scratch)) + file_type = FILE_NCA; + // test cnmt + else if (determineValidCnmtFromSample(scratch)) + file_type = FILE_CNMT; + // test nacp + else if (determineValidNacpFromSample(scratch)) + file_type = FILE_NACP; // test nso else if (_ASSERT_SIZE(sizeof(nx::sNsoHeader)) && _QUICK_CAST(nx::sNsoHeader, 0)->signature.get() == nx::nso::kNsoSig) file_type = FILE_NSO; @@ -757,6 +753,95 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path) return file_type; } +bool UserSettings::determineValidNcaFromSample(const fnd::MemoryBlob& sample) const +{ + // prepare decrypted NCA data + byte_t nca_raw[nx::nca::kHeaderSize]; + nx::sNcaHeader* nca_header = (nx::sNcaHeader*)(nca_raw + nx::NcaUtils::sectorToOffset(1)); + + if (sample.getSize() < nx::nca::kHeaderSize) + return false; + + nx::NcaUtils::decryptNcaHeader(sample.getBytes(), nca_raw, mKeyset.nca.header_key); + + if (nca_header->signature.get() != nx::nca::kNca2Sig && nca_header->signature.get() != nx::nca::kNca3Sig) + return false; + + return true; +} + +bool UserSettings::determineValidCnmtFromSample(const fnd::MemoryBlob& sample) const +{ + if (sample.getSize() < sizeof(nx::sContentMetaHeader)) + return false; + + const nx::sContentMetaHeader* data = (const nx::sContentMetaHeader*)sample.getBytes(); + + size_t minimum_size = sizeof(nx::sContentMetaHeader) + data->exhdr_size.get() + data->content_count.get() * sizeof(nx::sContentInfo) + data->content_meta_count.get() * sizeof(nx::sContentMetaInfo) + nx::cnmt::kDigestLen; + + if (sample.getSize() < minimum_size) + return false; + + if (data->type == nx::cnmt::METATYPE_APPLICATION) + { + const nx::sApplicationMetaExtendedHeader* meta = (const nx::sApplicationMetaExtendedHeader*)(sample.getBytes() + sizeof(nx::sContentMetaHeader)); + if ((meta->patch_id.get() & data->id.get()) != data->id.get()) + return false; + } + else if (data->type == nx::cnmt::METATYPE_PATCH) + { + const nx::sPatchMetaExtendedHeader* meta = (const nx::sPatchMetaExtendedHeader*)(sample.getBytes() + sizeof(nx::sContentMetaHeader)); + if ((meta->application_id.get() & data->id.get()) != meta->application_id.get()) + return false; + + minimum_size += meta->extended_data_size.get(); + } + else if (data->type == nx::cnmt::METATYPE_ADD_ON_CONTENT) + { + const nx::sAddOnContentMetaExtendedHeader* meta = (const nx::sAddOnContentMetaExtendedHeader*)(sample.getBytes() + sizeof(nx::sContentMetaHeader)); + if ((meta->application_id.get() & data->id.get()) != meta->application_id.get()) + return false; + } + else if (data->type == nx::cnmt::METATYPE_DELTA) + { + const nx::sDeltaMetaExtendedHeader* meta = (const nx::sDeltaMetaExtendedHeader*)(sample.getBytes() + sizeof(nx::sContentMetaHeader)); + if ((meta->application_id.get() & data->id.get()) != meta->application_id.get()) + return false; + + minimum_size += meta->extended_data_size.get(); + } + + if (sample.getSize() != minimum_size) + return false; + + return true; +} + +bool UserSettings::determineValidNacpFromSample(const fnd::MemoryBlob& sample) const +{ + if (sample.getSize() != sizeof(nx::sApplicationControlProperty)) + return false; + + const nx::sApplicationControlProperty* data = (const nx::sApplicationControlProperty*)sample.getBytes(); + + if (data->logo_type > nx::nacp::LOGO_Nintendo) + return false; + + if (data->display_version[0] == 0) + return false; + + if (data->user_account_save_data_size.get() == 0 && data->user_account_save_data_journal_size.get() != 0) + return false; + + if (data->user_account_save_data_journal_size.get() == 0 && data->user_account_save_data_size.get() != 0) + return false; + + if (data->supported_language_flag.get() == 0) + return false; + + return true; +} + nx::npdm::InstructionType UserSettings::getInstructionTypeFromString(const std::string & type_str) { std::string str = type_str; diff --git a/programs/nstool/source/UserSettings.h b/programs/nstool/source/UserSettings.h index c8d28f9..cb82b5e 100644 --- a/programs/nstool/source/UserSettings.h +++ b/programs/nstool/source/UserSettings.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include "nstool.h" @@ -101,5 +102,8 @@ private: void decodeHexStringToBytes(const std::string& name, const std::string& str, byte_t* out, size_t out_len); FileType getFileTypeFromString(const std::string& type_str); FileType determineFileTypeFromFile(const std::string& path); + bool determineValidNcaFromSample(const fnd::MemoryBlob& sample) const; + bool determineValidCnmtFromSample(const fnd::MemoryBlob& sample) const; + bool determineValidNacpFromSample(const fnd::MemoryBlob& sample) const; nx::npdm::InstructionType getInstructionTypeFromString(const std::string& type_str); }; \ No newline at end of file