VolumeVerifier: Check hashes in Wii partitions

This commit is contained in:
JosJuice
2019-03-26 17:22:18 +01:00
parent 84cbd5150f
commit 4fd2d8e8c4
7 changed files with 184 additions and 116 deletions

View File

@ -109,6 +109,19 @@ VolumeWii::VolumeWii(std::unique_ptr<BlobReader> reader)
return cert_chain;
};
auto get_h3_table = [this, partition]() -> std::vector<u8> {
if (!m_encrypted)
return {};
const std::optional<u64> h3_table_offset =
ReadSwappedAndShifted(partition.offset + 0x2b4, PARTITION_NONE);
if (!h3_table_offset)
return {};
std::vector<u8> h3_table(H3_TABLE_SIZE);
if (!m_reader->Read(partition.offset + *h3_table_offset, H3_TABLE_SIZE, h3_table.data()))
return {};
return h3_table;
};
auto get_key = [this, partition]() -> std::unique_ptr<mbedtls_aes_context> {
const IOS::ES::TicketReader& ticket = *m_partitions[partition].ticket;
if (!ticket.IsValid())
@ -133,6 +146,7 @@ VolumeWii::VolumeWii(std::unique_ptr<BlobReader> reader)
Common::Lazy<IOS::ES::TicketReader>(get_ticket),
Common::Lazy<IOS::ES::TMDReader>(get_tmd),
Common::Lazy<std::vector<u8>>(get_cert_chain),
Common::Lazy<std::vector<u8>>(get_h3_table),
Common::Lazy<std::unique_ptr<FileSystem>>(get_file_system),
Common::Lazy<u64>(get_data_offset), *partition_type});
}
@ -409,82 +423,84 @@ u64 VolumeWii::GetRawSize() const
return m_reader->GetRawSize();
}
bool VolumeWii::CheckIntegrity(const Partition& partition) const
bool VolumeWii::CheckH3TableIntegrity(const Partition& partition) const
{
if (!m_encrypted)
return false;
// Get the decryption key for the partition
auto it = m_partitions.find(partition);
if (it == m_partitions.end())
return false;
const PartitionDetails& partition_details = it->second;
const std::vector<u8>& h3_table = *partition_details.h3_table;
if (h3_table.size() != H3_TABLE_SIZE)
return false;
const IOS::ES::TMDReader& tmd = *partition_details.tmd;
if (!tmd.IsValid())
return false;
const std::vector<IOS::ES::Content> contents = tmd.GetContents();
if (contents.size() != 1)
return false;
std::array<u8, 20> h3_table_sha1;
mbedtls_sha1(h3_table.data(), h3_table.size(), h3_table_sha1.data());
return h3_table_sha1 == contents[0].sha1;
}
bool VolumeWii::CheckBlockIntegrity(u64 block_index, const Partition& partition) const
{
auto it = m_partitions.find(partition);
if (it == m_partitions.end())
return false;
const PartitionDetails& partition_details = it->second;
constexpr size_t SHA1_SIZE = 20;
if (block_index / 64 * SHA1_SIZE >= partition_details.h3_table->size())
return false;
mbedtls_aes_context* aes_context = partition_details.key->get();
if (!aes_context)
return false;
// Get partition data size
const auto part_data_size = ReadSwappedAndShifted(partition.offset + 0x2BC, PARTITION_NONE);
if (!part_data_size)
const u64 cluster_offset =
partition.offset + *partition_details.data_offset + block_index * BLOCK_TOTAL_SIZE;
// Read and decrypt the cluster metadata
u8 cluster_metadata_crypted[BLOCK_HEADER_SIZE];
u8 cluster_metadata[BLOCK_HEADER_SIZE];
u8 iv[16] = {0};
if (!m_reader->Read(cluster_offset, BLOCK_HEADER_SIZE, cluster_metadata_crypted))
return false;
mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, BLOCK_HEADER_SIZE, iv,
cluster_metadata_crypted, cluster_metadata);
u8 cluster_data[BLOCK_DATA_SIZE];
if (!Read(block_index * BLOCK_DATA_SIZE, BLOCK_DATA_SIZE, cluster_data, partition))
return false;
const u32 num_clusters = static_cast<u32>(part_data_size.value() / 0x8000);
for (u32 cluster_id = 0; cluster_id < num_clusters; ++cluster_id)
for (u32 hash_index = 0; hash_index < 31; ++hash_index)
{
const u64 cluster_offset =
partition.offset + *partition_details.data_offset + static_cast<u64>(cluster_id) * 0x8000;
// Read and decrypt the cluster metadata
u8 cluster_metadata_crypted[0x400];
u8 cluster_metadata[0x400];
u8 iv[16] = {0};
if (!m_reader->Read(cluster_offset, sizeof(cluster_metadata_crypted), cluster_metadata_crypted))
{
WARN_LOG(DISCIO, "Integrity Check: fail at cluster %d: could not read metadata", cluster_id);
u8 h0_hash[SHA1_SIZE];
mbedtls_sha1(cluster_data + hash_index * 0x400, 0x400, h0_hash);
if (memcmp(h0_hash, cluster_metadata + hash_index * SHA1_SIZE, SHA1_SIZE))
return false;
}
mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, sizeof(cluster_metadata), iv,
cluster_metadata_crypted, cluster_metadata);
// Some clusters have invalid data and metadata because they aren't
// meant to be read by the game (for example, holes between files). To
// try to avoid reporting errors because of these clusters, we check
// the 0x00 paddings in the metadata.
//
// This may cause some false negatives though: some bad clusters may be
// skipped because they are *too* bad and are not even recognized as
// valid clusters. To be improved.
const u8* pad_begin = cluster_metadata + 0x26C;
const u8* pad_end = pad_begin + 0x14;
const bool meaningless = std::any_of(pad_begin, pad_end, [](u8 val) { return val != 0; });
if (meaningless)
continue;
u8 cluster_data[0x7C00];
if (!Read(cluster_id * sizeof(cluster_data), sizeof(cluster_data), cluster_data, partition))
{
WARN_LOG(DISCIO, "Integrity Check: fail at cluster %d: could not read data", cluster_id);
return false;
}
for (u32 hash_id = 0; hash_id < 31; ++hash_id)
{
u8 hash[20];
mbedtls_sha1(cluster_data + hash_id * sizeof(cluster_metadata), sizeof(cluster_metadata),
hash);
// Note that we do not use strncmp here
if (memcmp(hash, cluster_metadata + hash_id * sizeof(hash), sizeof(hash)))
{
WARN_LOG(DISCIO, "Integrity Check: fail at cluster %d: hash %d is invalid", cluster_id,
hash_id);
return false;
}
}
}
u8 h1_hash[SHA1_SIZE];
mbedtls_sha1(cluster_metadata, SHA1_SIZE * 31, h1_hash);
if (memcmp(h1_hash, cluster_metadata + 0x280 + (block_index % 8) * SHA1_SIZE, SHA1_SIZE))
return false;
u8 h2_hash[SHA1_SIZE];
mbedtls_sha1(cluster_metadata + 0x280, SHA1_SIZE * 8, h2_hash);
if (memcmp(h2_hash, cluster_metadata + 0x340 + (block_index / 8 % 8) * SHA1_SIZE, SHA1_SIZE))
return false;
u8 h3_hash[SHA1_SIZE];
mbedtls_sha1(cluster_metadata + 0x340, SHA1_SIZE * 8, h3_hash);
if (memcmp(h3_hash, partition_details.h3_table->data() + block_index / 64 * SHA1_SIZE, SHA1_SIZE))
return false;
return true;
}