From 5c68a03ae49176837a5f1d43d8dd780361c84b08 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 20 May 2017 18:33:36 +0200 Subject: [PATCH 1/3] VolumeWiiCrypted: Cache TMDs and tickets --- Source/Core/DiscIO/Volume.cpp | 3 + Source/Core/DiscIO/Volume.h | 10 ++- Source/Core/DiscIO/VolumeWad.cpp | 2 +- Source/Core/DiscIO/VolumeWad.h | 2 +- Source/Core/DiscIO/VolumeWiiCrypted.cpp | 87 +++++++++++++++---------- Source/Core/DiscIO/VolumeWiiCrypted.h | 8 ++- 6 files changed, 69 insertions(+), 43 deletions(-) diff --git a/Source/Core/DiscIO/Volume.cpp b/Source/Core/DiscIO/Volume.cpp index d91272a329..874db7279a 100644 --- a/Source/Core/DiscIO/Volume.cpp +++ b/Source/Core/DiscIO/Volume.cpp @@ -32,6 +32,9 @@ static const unsigned int WII_BANNER_HEIGHT = 64; static const unsigned int WII_BANNER_SIZE = WII_BANNER_WIDTH * WII_BANNER_HEIGHT * 2; static const unsigned int WII_BANNER_OFFSET = 0xA0; +const IOS::ES::TicketReader IVolume::INVALID_TICKET{}; +const IOS::ES::TMDReader IVolume::INVALID_TMD{}; + std::vector IVolume::GetWiiBanner(int* width, int* height, u64 title_id) { *width = 0; diff --git a/Source/Core/DiscIO/Volume.h b/Source/Core/DiscIO/Volume.h index 1d98d69c46..f8de832c30 100644 --- a/Source/Core/DiscIO/Volume.h +++ b/Source/Core/DiscIO/Volume.h @@ -55,8 +55,11 @@ public: virtual Partition GetGamePartition() const { return PARTITION_NONE; } bool GetTitleID(u64* buffer) const { return GetTitleID(buffer, GetGamePartition()); } virtual bool GetTitleID(u64* buffer, const Partition& partition) const { return false; } - virtual IOS::ES::TicketReader GetTicket(const Partition& partition) const { return {}; } - virtual IOS::ES::TMDReader GetTMD(const Partition& partition) const { return {}; } + virtual const IOS::ES::TicketReader& GetTicket(const Partition& partition) const + { + return INVALID_TICKET; + } + virtual const IOS::ES::TMDReader& GetTMD(const Partition& partition) const { return INVALID_TMD; } std::string GetGameID() const { return GetGameID(GetGamePartition()); } virtual std::string GetGameID(const Partition& partition) const = 0; std::string GetMakerID() const { return GetMakerID(GetGamePartition()); } @@ -111,6 +114,9 @@ protected: static const size_t NAME_STRING_LENGTH = 42; static const size_t NAME_BYTES_LENGTH = NAME_STRING_LENGTH * sizeof(u16); static const size_t NAMES_TOTAL_BYTES = NAME_BYTES_LENGTH * NUMBER_OF_LANGUAGES; + + static const IOS::ES::TicketReader INVALID_TICKET; + static const IOS::ES::TMDReader INVALID_TMD; }; std::unique_ptr CreateVolumeFromFilename(const std::string& filename); diff --git a/Source/Core/DiscIO/VolumeWad.cpp b/Source/Core/DiscIO/VolumeWad.cpp index b401a3787e..f170abdd47 100644 --- a/Source/Core/DiscIO/VolumeWad.cpp +++ b/Source/Core/DiscIO/VolumeWad.cpp @@ -83,7 +83,7 @@ Country CVolumeWAD::GetCountry(const Partition& partition) const return CountrySwitch(country_code); } -IOS::ES::TMDReader CVolumeWAD::GetTMD(const Partition& partition) const +const IOS::ES::TMDReader& CVolumeWAD::GetTMD(const Partition& partition) const { return m_tmd; } diff --git a/Source/Core/DiscIO/VolumeWad.h b/Source/Core/DiscIO/VolumeWad.h index ecaef672fe..2de1d9df8e 100644 --- a/Source/Core/DiscIO/VolumeWad.h +++ b/Source/Core/DiscIO/VolumeWad.h @@ -34,7 +34,7 @@ public: bool Read(u64 offset, u64 length, u8* buffer, const Partition& partition = PARTITION_NONE) const override; bool GetTitleID(u64* buffer, const Partition& partition = PARTITION_NONE) const override; - IOS::ES::TMDReader GetTMD(const Partition& partition = PARTITION_NONE) const override; + const IOS::ES::TMDReader& GetTMD(const Partition& partition = PARTITION_NONE) const override; std::string GetGameID(const Partition& partition = PARTITION_NONE) const override; std::string GetMakerID(const Partition& partition = PARTITION_NONE) const override; u16 GetRevision(const Partition& partition = PARTITION_NONE) const override; diff --git a/Source/Core/DiscIO/VolumeWiiCrypted.cpp b/Source/Core/DiscIO/VolumeWiiCrypted.cpp index b77294640e..30c90de2f5 100644 --- a/Source/Core/DiscIO/VolumeWiiCrypted.cpp +++ b/Source/Core/DiscIO/VolumeWiiCrypted.cpp @@ -35,7 +35,7 @@ CVolumeWiiCrypted::CVolumeWiiCrypted(std::unique_ptr reader) { _assert_(m_pReader); - // Get decryption keys for all partitions + // Get tickets, TMDs, and decryption keys for all partitions for (u32 partition_group = 0; partition_group < 4; ++partition_group) { u32 number_of_partitions; @@ -49,10 +49,12 @@ CVolumeWiiCrypted::CVolumeWiiCrypted(std::unique_ptr reader) for (u32 i = 0; i < number_of_partitions; i++) { + // Read the partition offset if (!m_pReader->ReadSwapped(partition_table_offset + (i * 8), &read_buffer)) continue; const u64 partition_offset = (u64)read_buffer << 2; + // Set m_game_partition if this is the game partition if (m_game_partition == PARTITION_NONE) { u32 partition_type; @@ -63,6 +65,36 @@ CVolumeWiiCrypted::CVolumeWiiCrypted(std::unique_ptr reader) m_game_partition = Partition(partition_offset); } + // Read ticket + std::vector ticket_buffer(sizeof(IOS::ES::Ticket)); + if (!m_pReader->Read(partition_offset, ticket_buffer.size(), ticket_buffer.data())) + continue; + IOS::ES::TicketReader ticket{std::move(ticket_buffer)}; + + // Read TMD + u32 tmd_size = 0; + u32 tmd_address = 0; + if (!m_pReader->ReadSwapped(partition_offset + 0x2a4, &tmd_size)) + continue; + if (!m_pReader->ReadSwapped(partition_offset + 0x2a8, &tmd_address)) + continue; + tmd_address <<= 2; + if (tmd_size > 1024 * 1024 * 4) + { + // The size is checked so that a malicious or corrupt ISO + // can't force Dolphin to allocate up to 4 GiB of memory. + // 4 MiB should be much bigger than the size of TMDs and much smaller + // than the amount of RAM in a computer that can run Dolphin. + PanicAlert("TMD > 4 MiB"); + continue; + } + std::vector tmd_buffer(tmd_size); + if (!m_pReader->Read(partition_offset + tmd_address, tmd_size, tmd_buffer.data())) + continue; + IOS::ES::TMDReader tmd{std::move(tmd_buffer)}; + + // All of the code below is for getting the decryption key + u8 sub_key[16]; if (!m_pReader->Read(partition_offset + 0x1bf, 16, sub_key)) continue; @@ -108,7 +140,13 @@ CVolumeWiiCrypted::CVolumeWiiCrypted(std::unique_ptr reader) std::unique_ptr partition_AES_context = std::make_unique(); mbedtls_aes_setkey_dec(partition_AES_context.get(), volume_key, 128); - m_partitions[Partition(partition_offset)] = std::move(partition_AES_context); + + // We've read everything. Time to store it! (The reason we don't store anything + // earlier is because we want to be able to skip adding the partition if an error occurs.) + const Partition partition(partition_offset); + m_partition_keys[partition] = std::move(partition_AES_context); + m_partition_tickets[partition] = std::move(ticket); + m_partition_tmds[partition] = std::move(tmd); } } } @@ -124,8 +162,8 @@ bool CVolumeWiiCrypted::Read(u64 _ReadOffset, u64 _Length, u8* _pBuffer, return m_pReader->Read(_ReadOffset, _Length, _pBuffer); // Get the decryption key for the partition - auto it = m_partitions.find(partition); - if (it == m_partitions.end()) + auto it = m_partition_keys.find(partition); + if (it == m_partition_keys.end()) return false; mbedtls_aes_context* aes_context = it->second.get(); @@ -174,7 +212,7 @@ bool CVolumeWiiCrypted::Read(u64 _ReadOffset, u64 _Length, u8* _pBuffer, std::vector CVolumeWiiCrypted::GetPartitions() const { std::vector partitions; - for (const auto& pair : m_partitions) + for (const auto& pair : m_partition_keys) partitions.push_back(pair.first); return partitions; } @@ -189,39 +227,16 @@ bool CVolumeWiiCrypted::GetTitleID(u64* buffer, const Partition& partition) cons return m_pReader->ReadSwapped(partition.offset + 0x1DC, buffer); } -IOS::ES::TicketReader CVolumeWiiCrypted::GetTicket(const Partition& partition) const +const IOS::ES::TicketReader& CVolumeWiiCrypted::GetTicket(const Partition& partition) const { - std::vector buffer(0x2a4); - m_pReader->Read(partition.offset, buffer.size(), buffer.data()); - return IOS::ES::TicketReader{std::move(buffer)}; + auto it = m_partition_tickets.find(partition); + return it != m_partition_tickets.end() ? it->second : INVALID_TICKET; } -IOS::ES::TMDReader CVolumeWiiCrypted::GetTMD(const Partition& partition) const +const IOS::ES::TMDReader& CVolumeWiiCrypted::GetTMD(const Partition& partition) const { - u32 tmd_size = 0; - u32 tmd_address = 0; - - if (!m_pReader->ReadSwapped(partition.offset + 0x2a4, &tmd_size)) - return {}; - if (!m_pReader->ReadSwapped(partition.offset + 0x2a8, &tmd_address)) - return {}; - tmd_address <<= 2; - - if (tmd_size > 1024 * 1024 * 4) - { - // The size is checked so that a malicious or corrupt ISO - // can't force Dolphin to allocate up to 4 GiB of memory. - // 4 MiB should be much bigger than the size of TMDs and much smaller - // than the amount of RAM in a computer that can run Dolphin. - PanicAlert("TMD > 4 MiB"); - tmd_size = 1024 * 1024 * 4; - } - - std::vector buffer(tmd_size); - if (!m_pReader->Read(partition.offset + tmd_address, tmd_size, buffer.data())) - return {}; - - return IOS::ES::TMDReader{std::move(buffer)}; + auto it = m_partition_tmds.find(partition); + return it != m_partition_tmds.end() ? it->second : INVALID_TMD; } u64 CVolumeWiiCrypted::PartitionOffsetToRawOffset(u64 offset, const Partition& partition) @@ -368,8 +383,8 @@ u64 CVolumeWiiCrypted::GetRawSize() const bool CVolumeWiiCrypted::CheckIntegrity(const Partition& partition) const { // Get the decryption key for the partition - auto it = m_partitions.find(partition); - if (it == m_partitions.end()) + auto it = m_partition_keys.find(partition); + if (it == m_partition_keys.end()) return false; mbedtls_aes_context* aes_context = it->second.get(); diff --git a/Source/Core/DiscIO/VolumeWiiCrypted.h b/Source/Core/DiscIO/VolumeWiiCrypted.h index 88befabcb6..c0816c9e44 100644 --- a/Source/Core/DiscIO/VolumeWiiCrypted.h +++ b/Source/Core/DiscIO/VolumeWiiCrypted.h @@ -34,8 +34,8 @@ public: std::vector GetPartitions() const override; Partition GetGamePartition() const override; bool GetTitleID(u64* buffer, const Partition& partition) const override; - IOS::ES::TicketReader GetTicket(const Partition& partition) const override; - IOS::ES::TMDReader GetTMD(const Partition& partition) const override; + const IOS::ES::TicketReader& GetTicket(const Partition& partition) const override; + const IOS::ES::TMDReader& GetTMD(const Partition& partition) const override; std::string GetGameID(const Partition& partition) const override; std::string GetMakerID(const Partition& partition) const override; u16 GetRevision(const Partition& partition) const override; @@ -64,7 +64,9 @@ public: private: std::unique_ptr m_pReader; - std::map> m_partitions; + std::map> m_partition_keys; + std::map m_partition_tickets; + std::map m_partition_tmds; Partition m_game_partition; mutable u64 m_last_decrypted_block; From 26f5b53ecba0f27020d469b8d07767a9529a8d8a Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 20 May 2017 18:58:12 +0200 Subject: [PATCH 2/3] VolumeWiiCrypted: Get title IDs from TicketReader --- Source/Core/DiscIO/VolumeWiiCrypted.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Core/DiscIO/VolumeWiiCrypted.cpp b/Source/Core/DiscIO/VolumeWiiCrypted.cpp index 30c90de2f5..a9345b4b94 100644 --- a/Source/Core/DiscIO/VolumeWiiCrypted.cpp +++ b/Source/Core/DiscIO/VolumeWiiCrypted.cpp @@ -224,7 +224,11 @@ Partition CVolumeWiiCrypted::GetGamePartition() const bool CVolumeWiiCrypted::GetTitleID(u64* buffer, const Partition& partition) const { - return m_pReader->ReadSwapped(partition.offset + 0x1DC, buffer); + const IOS::ES::TicketReader& ticket = GetTicket(partition); + if (!ticket.IsValid()) + return false; + *buffer = ticket.GetTitleId(); + return true; } const IOS::ES::TicketReader& CVolumeWiiCrypted::GetTicket(const Partition& partition) const From 1575020c3a84f8d59ddb9591e858829958a8af43 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 20 May 2017 19:41:54 +0200 Subject: [PATCH 3/3] VolumeWiiCrypted: Get title keys from TicketReader --- Source/Core/Core/IOS/ES/Formats.cpp | 13 +++++- Source/Core/Core/IOS/ES/Formats.h | 1 + Source/Core/Core/IOS/IOSC.cpp | 31 ++++++++++---- Source/Core/Core/IOS/IOSC.h | 15 +++++-- Source/Core/DiscIO/VolumeWiiCrypted.cpp | 55 ++++--------------------- 5 files changed, 55 insertions(+), 60 deletions(-) diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp index dce527519f..9749f939d6 100644 --- a/Source/Core/Core/IOS/ES/Formats.cpp +++ b/Source/Core/Core/IOS/ES/Formats.cpp @@ -279,6 +279,13 @@ std::vector TicketReader::GetRawTicketView(u32 ticket_num) const return view; } +std::string TicketReader::GetIssuer() const +{ + const char* bytes = + reinterpret_cast(m_bytes.data() + offsetof(Ticket, signature_issuer)); + return std::string(bytes, strnlen(bytes, sizeof(Ticket::signature_issuer))); +} + u32 TicketReader::GetDeviceId() const { return Common::swap32(m_bytes.data() + offsetof(Ticket, device_id)); @@ -303,8 +310,12 @@ std::vector TicketReader::GetTitleKey() const GetTitleId(), index); } + const bool is_rvt = (GetIssuer() == "Root-CA00000002-XS00000006"); + const HLE::IOSC::ConsoleType console_type = + is_rvt ? HLE::IOSC::ConsoleType::RVT : HLE::IOSC::ConsoleType::Retail; + std::vector key(16); - HLE::IOSC iosc; + HLE::IOSC iosc(console_type); iosc.Decrypt(common_key_handle, iv, &m_bytes[offsetof(Ticket, title_key)], 16, key.data(), HLE::PID_ES); return key; diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h index 6337aeaf32..ab1e006b3f 100644 --- a/Source/Core/Core/IOS/ES/Formats.h +++ b/Source/Core/Core/IOS/ES/Formats.h @@ -189,6 +189,7 @@ public: // more than just one ticket and generate ticket views for them, so we implement it too. std::vector GetRawTicketView(u32 ticket_num) const; + std::string GetIssuer() const; u32 GetDeviceId() const; u64 GetTitleId() const; std::vector GetTitleKey() const; diff --git a/Source/Core/Core/IOS/IOSC.cpp b/Source/Core/Core/IOS/IOSC.cpp index 6157a7b6a9..ce47785ba9 100644 --- a/Source/Core/Core/IOS/IOSC.cpp +++ b/Source/Core/Core/IOS/IOSC.cpp @@ -19,9 +19,9 @@ namespace IOS { namespace HLE { -IOSC::IOSC() +IOSC::IOSC(ConsoleType console_type) { - LoadDefaultEntries(); + LoadDefaultEntries(console_type); } IOSC::~IOSC() = default; @@ -167,7 +167,7 @@ ReturnCode IOSC::SetOwnership(Handle handle, u32 new_owner, u32 pid) return IPC_SUCCESS; } -void IOSC::LoadDefaultEntries() +void IOSC::LoadDefaultEntries(ConsoleType console_type) { // TODO: add support for loading and writing to a BootMii / SEEPROM and OTP dump. @@ -181,11 +181,26 @@ void IOSC::LoadDefaultEntries() m_key_entries[HANDLE_FS_KEY] = {TYPE_SECRET_KEY, SUBTYPE_AES128, std::vector(16), 5}; m_key_entries[HANDLE_FS_MAC] = {TYPE_SECRET_KEY, SUBTYPE_MAC, std::vector(20), 5}; - m_key_entries[HANDLE_COMMON_KEY] = {TYPE_SECRET_KEY, - SUBTYPE_AES128, - {{0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4, 0x48, 0xd9, - 0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7}}, - 3}; + switch (console_type) + { + case ConsoleType::Retail: + m_key_entries[HANDLE_COMMON_KEY] = {TYPE_SECRET_KEY, + SUBTYPE_AES128, + {{0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4, 0x48, + 0xd9, 0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7}}, + 3}; + break; + case ConsoleType::RVT: + m_key_entries[HANDLE_COMMON_KEY] = {TYPE_SECRET_KEY, + SUBTYPE_AES128, + {{0xa1, 0x60, 0x4a, 0x6a, 0x71, 0x23, 0xb5, 0x29, 0xae, + 0x8b, 0xec, 0x32, 0xc8, 0x16, 0xfc, 0xaa}}, + 3}; + break; + default: + _assert_msg_(IOS, false, "Unknown console type"); + break; + } // Unimplemented. m_key_entries[HANDLE_PRNG_KEY] = {TYPE_SECRET_KEY, SUBTYPE_AES128, std::vector(16), 3}; diff --git a/Source/Core/Core/IOS/IOSC.h b/Source/Core/Core/IOS/IOSC.h index 5433251ba7..7b3bc2d86f 100644 --- a/Source/Core/Core/IOS/IOSC.h +++ b/Source/Core/Core/IOS/IOSC.h @@ -24,10 +24,14 @@ enum ReturnCode : s32; class IOSC final { public: - IOSC(); - ~IOSC(); - using Handle = u32; + + enum class ConsoleType + { + Retail, + RVT, + }; + // We use the same default key handle IDs as the actual IOSC because there are ioctlvs // that accept arbitrary key handles from the PPC, so the IDs must match. // More information on default handles: https://wiibrew.org/wiki/IOS/Syscalls @@ -75,6 +79,9 @@ public: SUBTYPE_VERSION = 6 }; + IOSC(ConsoleType console_type = ConsoleType::Retail); + ~IOSC(); + // Create an object for use with the other functions that operate on objects. ReturnCode CreateObject(Handle* handle, ObjectType type, ObjectSubType subtype, u32 pid); // Delete an object. Built-in objects cannot be deleted. @@ -116,7 +123,7 @@ private: // The Wii's IOSC is limited to 32 entries, including 12 built-in entries. using KeyEntries = std::array; - void LoadDefaultEntries(); + void LoadDefaultEntries(ConsoleType console_type); KeyEntries::iterator FindFreeEntry(); Handle GetHandleFromIterator(KeyEntries::iterator iterator) const; bool HasOwnership(Handle handle, u32 pid) const; diff --git a/Source/Core/DiscIO/VolumeWiiCrypted.cpp b/Source/Core/DiscIO/VolumeWiiCrypted.cpp index a9345b4b94..c2826c6515 100644 --- a/Source/Core/DiscIO/VolumeWiiCrypted.cpp +++ b/Source/Core/DiscIO/VolumeWiiCrypted.cpp @@ -70,6 +70,8 @@ CVolumeWiiCrypted::CVolumeWiiCrypted(std::unique_ptr reader) if (!m_pReader->Read(partition_offset, ticket_buffer.size(), ticket_buffer.data())) continue; IOS::ES::TicketReader ticket{std::move(ticket_buffer)}; + if (!ticket.IsValid()) + continue; // Read TMD u32 tmd_size = 0; @@ -93,58 +95,17 @@ CVolumeWiiCrypted::CVolumeWiiCrypted(std::unique_ptr reader) continue; IOS::ES::TMDReader tmd{std::move(tmd_buffer)}; - // All of the code below is for getting the decryption key - - u8 sub_key[16]; - if (!m_pReader->Read(partition_offset + 0x1bf, 16, sub_key)) + // Get the decryption key + const std::vector key = ticket.GetTitleKey(); + if (key.size() != 16) continue; - - u8 iv[16]; - memset(iv, 0, 16); - if (!m_pReader->Read(partition_offset + 0x44c, 8, iv)) - continue; - - static const u8 common_key_standard[16] = {0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4, - 0x48, 0xd9, 0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7}; - static const u8 common_key_korean[16] = {0x63, 0xb8, 0x2b, 0xb4, 0xf4, 0x61, 0x4e, 0x2e, - 0x13, 0xf2, 0xfe, 0xfb, 0xba, 0x4c, 0x9b, 0x7e}; - static const u8 common_key_rvt[16] = {0xa1, 0x60, 0x4a, 0x6a, 0x71, 0x23, 0xb5, 0x29, - 0xae, 0x8b, 0xec, 0x32, 0xc8, 0x16, 0xfc, 0xaa}; - static const char issuer_rvt[] = "Root-CA00000002-XS00000006"; - - const u8* common_key; - - u8 issuer[sizeof(issuer_rvt)]; - if (!m_pReader->Read(partition_offset + 0x140, sizeof(issuer), issuer)) - continue; - - if (!memcmp(issuer, issuer_rvt, sizeof(issuer_rvt))) - { - // RVT issuer. Use the RVT (debug) master key. - common_key = common_key_rvt; - } - else - { - u8 key_number = 0; - if (!m_pReader->ReadSwapped(partition_offset + 0x1f1, &key_number)) - continue; - common_key = (key_number == 1) ? common_key_korean : common_key_standard; - } - - mbedtls_aes_context aes_context; - mbedtls_aes_setkey_dec(&aes_context, common_key, 128); - - u8 volume_key[16]; - mbedtls_aes_crypt_cbc(&aes_context, MBEDTLS_AES_DECRYPT, 16, iv, sub_key, volume_key); - - std::unique_ptr partition_AES_context = - std::make_unique(); - mbedtls_aes_setkey_dec(partition_AES_context.get(), volume_key, 128); + std::unique_ptr aes_context = std::make_unique(); + mbedtls_aes_setkey_dec(aes_context.get(), key.data(), 128); // We've read everything. Time to store it! (The reason we don't store anything // earlier is because we want to be able to skip adding the partition if an error occurs.) const Partition partition(partition_offset); - m_partition_keys[partition] = std::move(partition_AES_context); + m_partition_keys[partition] = std::move(aes_context); m_partition_tickets[partition] = std::move(ticket); m_partition_tmds[partition] = std::move(tmd); }