mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-13 10:18:40 +12:00
Updated the Crypto Engine:
- Fixed several bugs in unedat; - Improved EDAT/SDAT file decryption.
This commit is contained in:
parent
eada1fe12c
commit
6d6c1a9672
16 changed files with 1190 additions and 295 deletions
|
@ -1,5 +1,4 @@
|
|||
#include "stdafx.h"
|
||||
#include "utils.h"
|
||||
#include "key_vault.h"
|
||||
#include "unedat.h"
|
||||
#include "Utilities/Log.h"
|
||||
|
@ -7,7 +6,7 @@
|
|||
|
||||
void generate_key(int crypto_mode, int version, unsigned char *key_final, unsigned char *iv_final, unsigned char *key, unsigned char *iv)
|
||||
{
|
||||
int mode = (int) (crypto_mode & 0xF0000000);
|
||||
int mode = (int)(crypto_mode & 0xF0000000);
|
||||
switch (mode) {
|
||||
case 0x10000000:
|
||||
// Encrypted ERK.
|
||||
|
@ -32,7 +31,7 @@ void generate_key(int crypto_mode, int version, unsigned char *key_final, unsign
|
|||
|
||||
void generate_hash(int hash_mode, int version, unsigned char *hash_final, unsigned char *hash)
|
||||
{
|
||||
int mode = (int) (hash_mode & 0xF0000000);
|
||||
int mode = (int)(hash_mode & 0xF0000000);
|
||||
switch (mode) {
|
||||
case 0x10000000:
|
||||
// Encrypted HASH.
|
||||
|
@ -52,7 +51,7 @@ void generate_hash(int hash_mode, int version, unsigned char *hash_final, unsign
|
|||
};
|
||||
}
|
||||
|
||||
bool crypto(int hash_mode, int crypto_mode, int version, unsigned char *in, unsigned char *out, int length, unsigned char *key, unsigned char *iv, unsigned char *hash, unsigned char *test_hash)
|
||||
bool decrypt(int hash_mode, int crypto_mode, int version, unsigned char *in, unsigned char *out, int length, unsigned char *key, unsigned char *iv, unsigned char *hash, unsigned char *test_hash)
|
||||
{
|
||||
// Setup buffers for key, iv and hash.
|
||||
unsigned char key_final[0x10] = {};
|
||||
|
@ -77,29 +76,30 @@ bool crypto(int hash_mode, int crypto_mode, int version, unsigned char *in, unsi
|
|||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Unknown crypto algorithm!\n");
|
||||
LOG_ERROR(LOADER, "EDAT: Unknown crypto algorithm!");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if ((hash_mode & 0xFF) == 0x01) // 0x14 SHA1-HMAC
|
||||
{
|
||||
return hmac_hash_compare(hash_final_14, 0x14, in, length, test_hash);
|
||||
return hmac_hash_compare(hash_final_14, 0x14, in, length, test_hash, 0x14);
|
||||
}
|
||||
else if ((hash_mode & 0xFF) == 0x02) // 0x10 AES-CMAC
|
||||
{
|
||||
return cmac_hash_compare(hash_final_10, 0x10, in, length, test_hash);
|
||||
return cmac_hash_compare(hash_final_10, 0x10, in, length, test_hash, 0x10);
|
||||
}
|
||||
else if ((hash_mode & 0xFF) == 0x04) //0x10 SHA1-HMAC
|
||||
{
|
||||
return hmac_hash_compare(hash_final_10, 0x10, in, length, test_hash);
|
||||
return hmac_hash_compare(hash_final_10, 0x10, in, length, test_hash, 0x10);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Unknown hashing algorithm!\n");
|
||||
LOG_ERROR(LOADER, "EDAT: Unknown hashing algorithm!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// EDAT/SDAT functions.
|
||||
unsigned char* dec_section(unsigned char* metadata)
|
||||
{
|
||||
unsigned char *dec = new unsigned char[0x10];
|
||||
|
@ -135,11 +135,11 @@ unsigned char* get_block_key(int block, NPD_HEADER *npd)
|
|||
return dest_key;
|
||||
}
|
||||
|
||||
// EDAT/SDAT functions.
|
||||
int decrypt_data(rFile *in, rFile *out, EDAT_SDAT_HEADER *edat, NPD_HEADER *npd, unsigned char* crypt_key, bool verbose)
|
||||
// EDAT/SDAT decryption.
|
||||
int decrypt_data(rFile *in, rFile *out, EDAT_HEADER *edat, NPD_HEADER *npd, unsigned char* crypt_key, bool verbose)
|
||||
{
|
||||
// Get metadata info and setup buffers.
|
||||
int block_num = (int) ((edat->file_size + edat->block_size - 1) / edat->block_size);
|
||||
int block_num = (int)((edat->file_size + edat->block_size - 1) / edat->block_size);
|
||||
int metadata_section_size = ((edat->flags & EDAT_COMPRESSED_FLAG) != 0 || (edat->flags & EDAT_FLAG_0x20) != 0) ? 0x20 : 0x10;
|
||||
int metadata_offset = 0x100;
|
||||
|
||||
|
@ -148,78 +148,103 @@ int decrypt_data(rFile *in, rFile *out, EDAT_SDAT_HEADER *edat, NPD_HEADER *npd,
|
|||
unsigned char *b_key;
|
||||
unsigned char *iv;
|
||||
|
||||
unsigned char hash[0x10];
|
||||
unsigned char key_result[0x10];
|
||||
unsigned char hash_result[0x14];
|
||||
memset(hash, 0, 0x10);
|
||||
memset(key_result, 0, 0x10);
|
||||
memset(hash_result, 0, 0x14);
|
||||
|
||||
unsigned long long offset = 0;
|
||||
unsigned long long metadata_sec_offset = 0;
|
||||
int length = 0;
|
||||
int compression_end = 0;
|
||||
unsigned char empty_iv[0x10] = {};
|
||||
|
||||
// Decrypt the metadata.
|
||||
for (int i = 0; i < block_num; i++)
|
||||
int i;
|
||||
for (i = 0; i < block_num; i++)
|
||||
{
|
||||
in->Seek(metadata_offset + i * metadata_section_size);
|
||||
unsigned char hash_result[0x10];
|
||||
long offset;
|
||||
int length = 0;
|
||||
int compression_end = 0;
|
||||
memset(hash_result, 0, 0x14);
|
||||
|
||||
if ((edat->flags & EDAT_FLAG_0x04) != 0)
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Flag 0x04 is not yet supported");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((edat->flags & EDAT_COMPRESSED_FLAG) != 0)
|
||||
{
|
||||
unsigned char metadata[0x20];
|
||||
in->Read(metadata, 0x20);
|
||||
|
||||
// If the data is compressed, decrypt the metadata.
|
||||
unsigned char *result = dec_section(metadata);
|
||||
offset = ((swap32(*(int*)&result[0]) << 4) | (swap32(*(int*)&result[4])));
|
||||
length = swap32(*(int*)&result[8]);
|
||||
compression_end = swap32(*(int*)&result[12]);
|
||||
delete[] result;
|
||||
metadata_sec_offset = metadata_offset + (unsigned long long) i * metadata_section_size;
|
||||
in->Seek(metadata_sec_offset);
|
||||
|
||||
unsigned char metadata[0x20];
|
||||
memset(metadata, 0, 0x20);
|
||||
in->Read(metadata, 0x20);
|
||||
|
||||
// If the data is compressed, decrypt the metadata.
|
||||
// NOTE: For NPD version 1 the metadata is not encrypted.
|
||||
if (npd->version <= 1)
|
||||
{
|
||||
offset = swap64(*(unsigned long long*)&metadata[0x10]);
|
||||
length = swap32(*(int*)&metadata[0x18]);
|
||||
compression_end = swap32(*(int*)&metadata[0x1C]);
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned char *result = dec_section(metadata);
|
||||
offset = swap64(*(unsigned long long*)&result[0]);
|
||||
length = swap32(*(int*)&result[8]);
|
||||
compression_end = swap32(*(int*)&result[12]);
|
||||
delete[] result;
|
||||
}
|
||||
|
||||
memcpy(hash_result, metadata, 0x10);
|
||||
}
|
||||
else if ((edat->flags & EDAT_FLAG_0x20) != 0)
|
||||
{
|
||||
// If FLAG 0x20, the metadata precedes each data block.
|
||||
in->Seek(metadata_offset + i * metadata_section_size + length);
|
||||
metadata_sec_offset = metadata_offset + (unsigned long long) i * (metadata_section_size + length);
|
||||
in->Seek(metadata_sec_offset);
|
||||
|
||||
unsigned char metadata[0x20];
|
||||
memset(metadata, 0, 0x20);
|
||||
in->Read(metadata, 0x20);
|
||||
memcpy(hash_result, metadata, 0x14);
|
||||
|
||||
// If FLAG 0x20 is set, apply custom xor.
|
||||
for (int j = 0; j < 0x10; j++) {
|
||||
hash_result[j] = (unsigned char)(metadata[j] ^ metadata[j+0x10]);
|
||||
}
|
||||
int j;
|
||||
for (j = 0; j < 0x10; j++)
|
||||
hash_result[j] = (unsigned char)(metadata[j] ^ metadata[j + 0x10]);
|
||||
|
||||
offset = metadata_offset + i * edat->block_size + (i + 1) * metadata_section_size;
|
||||
offset = metadata_sec_offset + 0x20;
|
||||
length = edat->block_size;
|
||||
|
||||
if ((i == (block_num - 1)) && (edat->file_size % edat->block_size))
|
||||
length = (int) (edat->file_size % edat->block_size);
|
||||
length = (int)(edat->file_size % edat->block_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
metadata_sec_offset = metadata_offset + (unsigned long long) i * metadata_section_size;
|
||||
in->Seek(metadata_sec_offset);
|
||||
|
||||
in->Read(hash_result, 0x10);
|
||||
offset = metadata_offset + i * edat->block_size + block_num * metadata_section_size;
|
||||
offset = metadata_offset + (unsigned long long) i * edat->block_size + (unsigned long long) block_num * metadata_section_size;
|
||||
length = edat->block_size;
|
||||
|
||||
|
||||
if ((i == (block_num - 1)) && (edat->file_size % edat->block_size))
|
||||
length = (int) (edat->file_size % edat->block_size);
|
||||
length = (int)(edat->file_size % edat->block_size);
|
||||
}
|
||||
|
||||
// Locate the real data.
|
||||
int pad_length = length;
|
||||
length = (int) ((pad_length + 0xF) & 0xFFFFFFF0);
|
||||
in->Seek(offset);
|
||||
length = (int)((pad_length + 0xF) & 0xFFFFFFF0);
|
||||
|
||||
// Setup buffers for decryption and read the data.
|
||||
enc_data = new unsigned char[length];
|
||||
dec_data = new unsigned char[length];
|
||||
unsigned char key_result[0x10];
|
||||
unsigned char hash[0x10];
|
||||
memset(enc_data, 0, length);
|
||||
memset(dec_data, 0, length);
|
||||
memset(hash, 0, 0x10);
|
||||
memset(key_result, 0, 0x10);
|
||||
|
||||
in->Seek(offset);
|
||||
in->Read(enc_data, length);
|
||||
|
||||
|
||||
// Generate a key for the current block.
|
||||
b_key = get_block_key(i, npd);
|
||||
|
||||
|
@ -247,7 +272,7 @@ int decrypt_data(rFile *in, rFile *out, EDAT_SDAT_HEADER *edat, NPD_HEADER *npd,
|
|||
hash_mode |= 0x10000000;
|
||||
}
|
||||
|
||||
if ((edat->flags & EDAT_DEBUG_DATA_FLAG) != 0)
|
||||
if ((edat->flags & EDAT_DEBUG_DATA_FLAG) != 0)
|
||||
{
|
||||
// Reset the flags.
|
||||
crypto_mode |= 0x01000000;
|
||||
|
@ -260,7 +285,13 @@ int decrypt_data(rFile *in, rFile *out, EDAT_SDAT_HEADER *edat, NPD_HEADER *npd,
|
|||
// IV is null if NPD version is 1 or 0.
|
||||
iv = (npd->version <= 1) ? empty_iv : npd->digest;
|
||||
// Call main crypto routine on this data block.
|
||||
crypto(hash_mode, crypto_mode, (npd->version == 4), enc_data, dec_data, length, key_result, iv, hash, hash_result);
|
||||
if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), enc_data, dec_data, length, key_result, iv, hash, hash_result))
|
||||
{
|
||||
if (verbose)
|
||||
LOG_WARNING(LOADER, "EDAT: Block at offset 0x%llx has invalid hash!", offset);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply additional compression if needed and write the decrypted data.
|
||||
|
@ -271,28 +302,28 @@ int decrypt_data(rFile *in, rFile *out, EDAT_SDAT_HEADER *edat, NPD_HEADER *npd,
|
|||
memset(decomp_data, 0, decomp_size);
|
||||
|
||||
if (verbose)
|
||||
LOG_NOTICE(LOADER, "EDAT: Decompressing...\n");
|
||||
|
||||
int res = lz_decompress(decomp_data, dec_data, decomp_size);
|
||||
LOG_NOTICE(LOADER, "EDAT: Decompressing data...");
|
||||
|
||||
int res = decompress(decomp_data, dec_data, decomp_size);
|
||||
out->Write(decomp_data, res);
|
||||
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
LOG_NOTICE(LOADER, "EDAT: Compressed block size: %d\n", pad_length);
|
||||
LOG_NOTICE(LOADER, "EDAT: Decompressed block size: %d\n", res);
|
||||
LOG_NOTICE(LOADER, "EDAT: Compressed block size: %d", pad_length);
|
||||
LOG_NOTICE(LOADER, "EDAT: Decompressed block size: %d", res);
|
||||
}
|
||||
|
||||
edat->file_size -= res;
|
||||
|
||||
if (edat->file_size == 0)
|
||||
if (edat->file_size == 0)
|
||||
{
|
||||
if (res < 0)
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Decompression failed!\n");
|
||||
LOG_ERROR(LOADER, "EDAT: Decompression failed!");
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
LOG_SUCCESS(LOADER, "EDAT: Data successfully decompressed!\n");
|
||||
LOG_NOTICE(LOADER, "EDAT: Successfully decompressed!");
|
||||
}
|
||||
|
||||
delete[] decomp_data;
|
||||
|
@ -309,158 +340,262 @@ int decrypt_data(rFile *in, rFile *out, EDAT_SDAT_HEADER *edat, NPD_HEADER *npd,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool check_flags(EDAT_SDAT_HEADER *edat, NPD_HEADER *npd)
|
||||
int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, rFile *f, bool verbose)
|
||||
{
|
||||
if (edat == nullptr || npd == nullptr)
|
||||
return false;
|
||||
f->Seek(0);
|
||||
unsigned char header[0xA0];
|
||||
unsigned char empty_header[0xA0];
|
||||
unsigned char header_hash[0x10];
|
||||
unsigned char metadata_hash[0x10];
|
||||
memset(header, 0, 0xA0);
|
||||
memset(empty_header, 0, 0xA0);
|
||||
memset(header_hash, 0, 0x10);
|
||||
memset(metadata_hash, 0, 0x10);
|
||||
|
||||
if (npd->version == 0 || npd->version == 1)
|
||||
// Check NPD version and flags.
|
||||
if ((npd->version == 0) || (npd->version == 1))
|
||||
{
|
||||
if (edat->flags & 0x7EFFFFFE)
|
||||
if (edat->flags & 0x7EFFFFFE)
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Bad header flags!\n");
|
||||
return false;
|
||||
LOG_ERROR(LOADER, "EDAT: Bad header flags!");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (npd->version == 2)
|
||||
else if (npd->version == 2)
|
||||
{
|
||||
if (edat->flags & 0x7EFFFFE0)
|
||||
if (edat->flags & 0x7EFFFFE0)
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Bad header flags!\n");
|
||||
return false;
|
||||
LOG_ERROR(LOADER, "EDAT: Bad header flags!");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (npd->version == 3 || npd->version == 4)
|
||||
else if ((npd->version == 3) || (npd->version == 4))
|
||||
{
|
||||
if (edat->flags & 0x7EFFFFC0)
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Bad header flags!\n");
|
||||
return false;
|
||||
LOG_ERROR(LOADER, "EDAT: Bad header flags!");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (npd->version > 4)
|
||||
else
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Unknown version - %d\n", npd->version);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int check_data(unsigned char *key, EDAT_SDAT_HEADER *edat, NPD_HEADER *npd, rFile *f, bool verbose)
|
||||
{
|
||||
f->Seek(0);
|
||||
unsigned char *header = new unsigned char[0xA0];
|
||||
unsigned char *tmp = new unsigned char[0xA0];
|
||||
unsigned char *hash_result = new unsigned char[0x10];
|
||||
|
||||
// Check NPD version and EDAT flags.
|
||||
if (!check_flags(edat, npd))
|
||||
{
|
||||
delete[] header;
|
||||
delete[] tmp;
|
||||
delete[] hash_result;
|
||||
|
||||
LOG_ERROR(LOADER, "EDAT: Unknown version!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Read in the file header.
|
||||
f->Read(header, 0xA0);
|
||||
f->Read(hash_result, 0x10);
|
||||
|
||||
// Read in the header and metadata section hashes.
|
||||
f->Seek(0x90);
|
||||
f->Read(metadata_hash, 0x10);
|
||||
f->Read(header_hash, 0x10);
|
||||
|
||||
// Setup the hashing mode and the crypto mode used in the file.
|
||||
int crypto_mode = 0x1;
|
||||
int hash_mode = ((edat->flags & EDAT_ENCRYPTED_KEY_FLAG) == 0) ? 0x00000002 : 0x10000002;
|
||||
if ((edat->flags & EDAT_DEBUG_DATA_FLAG) != 0)
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: DEBUG data detected!\n");
|
||||
hash_mode |= 0x01000000;
|
||||
|
||||
if (verbose)
|
||||
LOG_WARNING(LOADER, "EDAT: DEBUG data detected!");
|
||||
}
|
||||
|
||||
// Setup header key and iv buffers.
|
||||
unsigned char header_key[0x10] = {};
|
||||
unsigned char header_iv[0x10] = {};
|
||||
unsigned char header_key[0x10];
|
||||
unsigned char header_iv[0x10];
|
||||
memset(header_key, 0, 0x10);
|
||||
memset(header_iv, 0, 0x10);
|
||||
|
||||
// Test the header hash (located at offset 0xA0).
|
||||
if (!crypto(hash_mode, crypto_mode, (npd->version == 4), header, tmp, 0xA0, header_key, header_iv, key, hash_result))
|
||||
if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), header, empty_header, 0xA0, header_key, header_iv, key, header_hash))
|
||||
{
|
||||
if (verbose)
|
||||
LOG_WARNING(LOADER, "EDAT: Header hash is invalid!\n");
|
||||
LOG_WARNING(LOADER, "EDAT: Header hash is invalid!");
|
||||
|
||||
// If the header hash test fails and the data is not DEBUG, then RAP/RIF/KLIC key is invalid.
|
||||
if ((edat->flags & EDAT_DEBUG_DATA_FLAG) != EDAT_DEBUG_DATA_FLAG)
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: RAP/RIF/KLIC key is invalid!");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the metadata info.
|
||||
int metadata_section_size = 0x10;
|
||||
int metadata_section_size = ((edat->flags & EDAT_COMPRESSED_FLAG) != 0 || (edat->flags & EDAT_FLAG_0x20) != 0) ? 0x20 : 0x10;
|
||||
if (((edat->flags & EDAT_COMPRESSED_FLAG) != 0))
|
||||
{
|
||||
LOG_WARNING(LOADER, "EDAT: COMPRESSED data detected!\n");
|
||||
metadata_section_size = 0x20;
|
||||
if (verbose)
|
||||
LOG_WARNING(LOADER, "EDAT: COMPRESSED data detected!");
|
||||
}
|
||||
|
||||
int block_num = (int) ((edat->file_size + edat->block_size - 1) / edat->block_size);
|
||||
int bytes_read = 0;
|
||||
int block_num = (int)((edat->file_size + edat->block_size - 1) / edat->block_size);
|
||||
int metadata_offset = 0x100;
|
||||
int metadata_size = metadata_section_size * block_num;
|
||||
long long metadata_section_offset = metadata_offset;
|
||||
|
||||
long bytes_read = 0;
|
||||
long bytes_to_read = metadata_size;
|
||||
unsigned char *metadata = new unsigned char[metadata_size];
|
||||
unsigned char *empty_metadata = new unsigned char[metadata_size];
|
||||
|
||||
long bytes_to_read = metadata_section_size * block_num;
|
||||
while (bytes_to_read > 0)
|
||||
{
|
||||
// Locate the metadata blocks.
|
||||
int block_size = (0x3C00 > bytes_to_read) ? (int) bytes_to_read : 0x3C00; // 0x3C00 is the maximum block size.
|
||||
f->Seek(metadata_offset + bytes_read);
|
||||
unsigned char *data = new unsigned char[block_size];
|
||||
f->Seek(metadata_section_offset);
|
||||
|
||||
// Read in the metadata.
|
||||
tmp = new unsigned char[block_size];
|
||||
f->Read(data, block_size);
|
||||
|
||||
// Check the generated hash against the metadata hash located at offset 0x90 in the header.
|
||||
memset(hash_result, 0, 0x10);
|
||||
f->Seek(0x90);
|
||||
f->Read(hash_result, 0x10);
|
||||
|
||||
// Generate the hash for this block.
|
||||
if (!crypto(hash_mode, crypto_mode, (npd->version == 4), data, tmp, block_size, header_key, header_iv, key, hash_result))
|
||||
{
|
||||
if (verbose)
|
||||
LOG_WARNING(LOADER, "EDAT: Metadata hash from block 0x%08x is invalid!\n", metadata_offset + bytes_read);
|
||||
}
|
||||
f->Read(metadata + bytes_read, metadata_section_size);
|
||||
|
||||
// Adjust sizes.
|
||||
bytes_read += block_size;
|
||||
bytes_to_read -= block_size;
|
||||
bytes_read += metadata_section_size;
|
||||
bytes_to_read -= metadata_section_size;
|
||||
|
||||
delete[] data;
|
||||
if (((edat->flags & EDAT_FLAG_0x20) != 0)) // Metadata block before each data block.
|
||||
metadata_section_offset += (metadata_section_size + edat->block_size);
|
||||
else
|
||||
metadata_section_offset += metadata_section_size;
|
||||
}
|
||||
|
||||
// Test the metadata section hash (located at offset 0x90).
|
||||
if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), metadata, empty_metadata, metadata_size, header_key, header_iv, key, metadata_hash))
|
||||
{
|
||||
if (verbose)
|
||||
LOG_WARNING(LOADER, "EDAT: Metadata section hash is invalid!");
|
||||
}
|
||||
|
||||
// Checking ECDSA signatures.
|
||||
if ((edat->flags & EDAT_DEBUG_DATA_FLAG) == 0)
|
||||
{
|
||||
LOG_NOTICE(LOADER, "EDAT: Checking signatures...");
|
||||
|
||||
// Setup buffers.
|
||||
unsigned char metadata_signature[0x28];
|
||||
unsigned char header_signature[0x28];
|
||||
unsigned char signature_hash[20];
|
||||
unsigned char signature_r[0x15];
|
||||
unsigned char signature_s[0x15];
|
||||
unsigned char zero_buf[0x15];
|
||||
memset(metadata_signature, 0, 0x28);
|
||||
memset(header_signature, 0, 0x28);
|
||||
memset(signature_hash, 0, 20);
|
||||
memset(signature_r, 0, 0x15);
|
||||
memset(signature_s, 0, 0x15);
|
||||
memset(zero_buf, 0, 0x15);
|
||||
|
||||
// Setup ECDSA curve and public key.
|
||||
ecdsa_set_curve(VSH_CURVE_P, VSH_CURVE_A, VSH_CURVE_B, VSH_CURVE_N, VSH_CURVE_GX, VSH_CURVE_GY);
|
||||
ecdsa_set_pub(VSH_PUB);
|
||||
|
||||
|
||||
// Read in the metadata and header signatures.
|
||||
f->Seek(0xB0);
|
||||
f->Read(metadata_signature, 0x28);
|
||||
f->Seek(0xD8);
|
||||
f->Read(header_signature, 0x28);
|
||||
|
||||
// Checking metadata signature.
|
||||
// Setup signature r and s.
|
||||
memcpy(signature_r + 01, metadata_signature, 0x14);
|
||||
memcpy(signature_s + 01, metadata_signature + 0x14, 0x14);
|
||||
if ((!memcmp(signature_r, zero_buf, 0x15)) || (!memcmp(signature_s, zero_buf, 0x15)))
|
||||
LOG_WARNING(LOADER, "EDAT: Metadata signature is invalid!");
|
||||
else
|
||||
{
|
||||
// Setup signature hash.
|
||||
if ((edat->flags & EDAT_FLAG_0x20) != 0) //Sony failed again, they used buffer from 0x100 with half size of real metadata.
|
||||
{
|
||||
int metadata_buf_size = block_num * 0x10;
|
||||
unsigned char *metadata_buf = new unsigned char[metadata_buf_size];
|
||||
f->Seek(metadata_offset);
|
||||
f->Read(metadata_buf, metadata_buf_size);
|
||||
sha1(metadata_buf, metadata_buf_size, signature_hash);
|
||||
delete[] metadata_buf;
|
||||
}
|
||||
else
|
||||
sha1(metadata, metadata_size, signature_hash);
|
||||
|
||||
if (!ecdsa_verify(signature_hash, signature_r, signature_s))
|
||||
{
|
||||
LOG_WARNING(LOADER, "EDAT: Metadata signature is invalid!");
|
||||
if (((unsigned long long)edat->block_size * block_num) > 0x100000000)
|
||||
LOG_WARNING(LOADER, "EDAT: *Due to large file size, metadata signature status may be incorrect!");
|
||||
}
|
||||
else
|
||||
LOG_NOTICE(LOADER, "EDAT: Metadata signature is valid!");
|
||||
}
|
||||
|
||||
|
||||
// Checking header signature.
|
||||
// Setup header signature r and s.
|
||||
memset(signature_r, 0, 0x15);
|
||||
memset(signature_s, 0, 0x15);
|
||||
memcpy(signature_r + 01, header_signature, 0x14);
|
||||
memcpy(signature_s + 01, header_signature + 0x14, 0x14);
|
||||
|
||||
if ((!memcmp(signature_r, zero_buf, 0x15)) || (!memcmp(signature_s, zero_buf, 0x15)))
|
||||
LOG_WARNING(LOADER, "EDAT: Header signature is invalid!");
|
||||
else
|
||||
{
|
||||
// Setup header signature hash.
|
||||
memset(signature_hash, 0, 20);
|
||||
unsigned char *header_buf = new unsigned char[0xD8];
|
||||
f->Seek(0x00);
|
||||
f->Read(header_buf, 0xD8);
|
||||
sha1(header_buf, 0xD8, signature_hash );
|
||||
delete[] header_buf;
|
||||
|
||||
if (ecdsa_verify(signature_hash, signature_r, signature_s))
|
||||
LOG_NOTICE(LOADER, "EDAT: Header signature is valid!");
|
||||
else
|
||||
LOG_WARNING(LOADER, "EDAT: Header signature is invalid!");
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
delete[] header;
|
||||
delete[] tmp;
|
||||
delete[] hash_result;
|
||||
delete[] metadata;
|
||||
delete[] empty_metadata;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void validate_data(const char* file_name, unsigned char *klicensee, NPD_HEADER *npd, bool verbose)
|
||||
int validate_npd_hashes(const char* file_name, unsigned char *klicensee, NPD_HEADER *npd, bool verbose)
|
||||
{
|
||||
int title_hash_result = 0;
|
||||
int dev_hash_result = 0;
|
||||
|
||||
int file_name_length = (int)strlen(file_name);
|
||||
int file_name_length = (int) strlen(file_name);
|
||||
unsigned char *buf = new unsigned char[0x30 + file_name_length];
|
||||
unsigned char dev[0x60];
|
||||
unsigned char key[0x10];
|
||||
memset(dev, 0, 0x60);
|
||||
memset(key, 0, 0x10);
|
||||
|
||||
// Build the buffer (content_id + file_name).
|
||||
// Build the title buffer (content_id + file_name).
|
||||
memcpy(buf, npd->content_id, 0x30);
|
||||
memcpy(buf + 0x30, file_name, file_name_length);
|
||||
|
||||
// Hash with NP_OMAC_KEY_3 and compare with title_hash.
|
||||
title_hash_result = cmac_hash_compare(NP_OMAC_KEY_3, 0x10, buf, 0x30 + file_name_length, npd->title_hash);
|
||||
// Build the dev buffer (first 0x60 bytes of NPD header in big-endian).
|
||||
memcpy(dev, npd, 0x60);
|
||||
|
||||
// Fix endianness.
|
||||
int version = swap32(npd->version);
|
||||
int license = swap32(npd->license);
|
||||
int type = swap32(npd->type);
|
||||
memcpy(dev + 0x4, &version, 4);
|
||||
memcpy(dev + 0x8, &license, 4);
|
||||
memcpy(dev + 0xC, &type, 4);
|
||||
|
||||
// Hash with NPDRM_OMAC_KEY_3 and compare with title_hash.
|
||||
title_hash_result = cmac_hash_compare(NP_OMAC_KEY_3, 0x10, buf, 0x30 + file_name_length, npd->title_hash, 0x10);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
if (title_hash_result)
|
||||
LOG_SUCCESS(LOADER, "EDAT: NPD title hash is valid!\n");
|
||||
else
|
||||
LOG_WARNING(LOADER, "EDAT: NPD title hash is invalid!\n");
|
||||
LOG_NOTICE(LOADER, "EDAT: NPD title hash is valid!");
|
||||
else
|
||||
LOG_WARNING(LOADER, "EDAT: NPD title hash is invalid!");
|
||||
}
|
||||
|
||||
// Check for an empty dev_hash (can't validate if devklic is NULL);
|
||||
|
@ -477,40 +612,45 @@ void validate_data(const char* file_name, unsigned char *klicensee, NPD_HEADER *
|
|||
if (isDevklicEmpty)
|
||||
{
|
||||
if (verbose)
|
||||
LOG_WARNING(LOADER, "EDAT: NPD dev hash is empty!\n");
|
||||
LOG_WARNING(LOADER, "EDAT: NPD dev hash is empty!");
|
||||
|
||||
// Allow empty dev hash.
|
||||
dev_hash_result = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generate klicensee xor key.
|
||||
xor_(key, klicensee, NP_OMAC_KEY_2, 0x10);
|
||||
xor(key, klicensee, NP_OMAC_KEY_2, 0x10);
|
||||
|
||||
// Hash with generated key and compare with dev_hash.
|
||||
dev_hash_result = cmac_hash_compare(key, 0x10, (unsigned char *)npd, 0x60, npd->dev_hash);
|
||||
|
||||
dev_hash_result = cmac_hash_compare(key, 0x10, dev, 0x60, npd->dev_hash, 0x10);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
if (dev_hash_result)
|
||||
LOG_SUCCESS(LOADER, "EDAT: NPD dev hash is valid!\n");
|
||||
else
|
||||
LOG_WARNING(LOADER, "EDAT: NPD dev hash is invalid!\n");
|
||||
LOG_NOTICE(LOADER, "EDAT: NPD dev hash is valid!");
|
||||
else
|
||||
LOG_WARNING(LOADER, "EDAT: NPD dev hash is invalid!");
|
||||
}
|
||||
}
|
||||
|
||||
delete[] buf;
|
||||
|
||||
return (title_hash_result && dev_hash_result);
|
||||
}
|
||||
|
||||
bool extract_data(rFile *input, rFile *output, const char* input_file_name, unsigned char* devklic, unsigned char* rifkey, bool verbose)
|
||||
{
|
||||
// Setup NPD and EDAT/SDAT structs.
|
||||
NPD_HEADER *NPD = new NPD_HEADER();
|
||||
EDAT_SDAT_HEADER *EDAT = new EDAT_SDAT_HEADER();
|
||||
EDAT_HEADER *EDAT = new EDAT_HEADER();
|
||||
|
||||
// Read in the NPD and EDAT/SDAT headers.
|
||||
char npd_header[0x80];
|
||||
char edat_header[0x10];
|
||||
input->Read(npd_header, 0x80);
|
||||
input->Read(edat_header, 0x10);
|
||||
|
||||
input->Read(npd_header, sizeof(npd_header));
|
||||
input->Read(edat_header, sizeof(edat_header));
|
||||
|
||||
memcpy(NPD->magic, npd_header, 4);
|
||||
NPD->version = swap32(*(int*)&npd_header[4]);
|
||||
NPD->license = swap32(*(int*)&npd_header[8]);
|
||||
|
@ -523,7 +663,7 @@ bool extract_data(rFile *input, rFile *output, const char* input_file_name, unsi
|
|||
NPD->unk2 = swap64(*(u64*)&npd_header[120]);
|
||||
|
||||
unsigned char npd_magic[4] = {0x4E, 0x50, 0x44, 0x00}; //NPD0
|
||||
if(memcmp(NPD->magic, npd_magic, 4))
|
||||
if (memcmp(NPD->magic, npd_magic, 4))
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: %s has invalid NPD header or already decrypted.", input_file_name);
|
||||
delete NPD;
|
||||
|
@ -537,41 +677,65 @@ bool extract_data(rFile *input, rFile *output, const char* input_file_name, unsi
|
|||
|
||||
if (verbose)
|
||||
{
|
||||
LOG_NOTICE(LOADER, "NPD HEADER\n");
|
||||
LOG_NOTICE(LOADER, "NPD version: %d\n", NPD->version);
|
||||
LOG_NOTICE(LOADER, "NPD license: %d\n", NPD->license);
|
||||
LOG_NOTICE(LOADER, "NPD type: %d\n", NPD->type);
|
||||
LOG_NOTICE(LOADER, "\n");
|
||||
LOG_NOTICE(LOADER, "EDAT HEADER\n");
|
||||
LOG_NOTICE(LOADER, "EDAT flags: 0x%08X\n", EDAT->flags);
|
||||
LOG_NOTICE(LOADER, "EDAT block size: 0x%08X\n", EDAT->block_size);
|
||||
LOG_NOTICE(LOADER, "EDAT file size: 0x%08X\n", EDAT->file_size);
|
||||
LOG_NOTICE(LOADER, "\n");
|
||||
LOG_NOTICE(LOADER, "NPD HEADER");
|
||||
LOG_NOTICE(LOADER, "NPD version: %d", NPD->version);
|
||||
LOG_NOTICE(LOADER, "NPD license: %d", NPD->license);
|
||||
LOG_NOTICE(LOADER, "NPD type: %d", NPD->type);
|
||||
}
|
||||
|
||||
// Set decryption key.
|
||||
unsigned char key[0x10];
|
||||
memset(key, 0, 0x10);
|
||||
|
||||
if((EDAT->flags & SDAT_FLAG) == SDAT_FLAG)
|
||||
// Check EDAT/SDAT flag.
|
||||
if ((EDAT->flags & SDAT_FLAG) == SDAT_FLAG)
|
||||
{
|
||||
LOG_WARNING(LOADER, "EDAT: SDAT detected!\n");
|
||||
xor_(key, NPD->dev_hash, SDAT_KEY, 0x10);
|
||||
if (verbose)
|
||||
{
|
||||
LOG_NOTICE(LOADER, "SDAT HEADER");
|
||||
LOG_NOTICE(LOADER, "SDAT flags: 0x%08X", EDAT->flags);
|
||||
LOG_NOTICE(LOADER, "SDAT block size: 0x%08X", EDAT->block_size);
|
||||
LOG_NOTICE(LOADER, "SDAT file size: 0x%08X", EDAT->file_size);
|
||||
}
|
||||
|
||||
// Generate SDAT key.
|
||||
xor(key, NPD->dev_hash, SDAT_KEY, 0x10);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Perform header validation (optional step).
|
||||
validate_data(input_file_name, devklic, NPD, verbose);
|
||||
if (verbose)
|
||||
{
|
||||
LOG_NOTICE(LOADER, "EDAT HEADER");
|
||||
LOG_NOTICE(LOADER, "EDAT flags: 0x%08X", EDAT->flags);
|
||||
LOG_NOTICE(LOADER, "EDAT block size: 0x%08X", EDAT->block_size);
|
||||
LOG_NOTICE(LOADER, "EDAT file size: 0x%08X", EDAT->file_size);
|
||||
}
|
||||
|
||||
if ((NPD->license & 0x3) == 0x3) // Type 3: Use supplied devklic.
|
||||
// Perform header validation (EDAT only).
|
||||
char real_file_name[MAX_PATH];
|
||||
extract_file_name(input_file_name, real_file_name);
|
||||
if (!validate_npd_hashes(real_file_name, devklic, NPD, verbose))
|
||||
{
|
||||
// Ignore header validation in DEBUG data.
|
||||
if ((EDAT->flags & EDAT_DEBUG_DATA_FLAG) != EDAT_DEBUG_DATA_FLAG)
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: NPD hash validation failed!");
|
||||
delete NPD;
|
||||
delete EDAT;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Select EDAT key.
|
||||
if ((NPD->license & 0x3) == 0x3) // Type 3: Use supplied devklic.
|
||||
memcpy(key, devklic, 0x10);
|
||||
else if ((NPD->license & 0x2) == 0x2) // Type 2: Use key from RAP file (RIF key).
|
||||
{
|
||||
else if ((NPD->license & 0x2) == 0x2) // Type 2: Use key from RAP file (RIF key).
|
||||
{
|
||||
memcpy(key, rifkey, 0x10);
|
||||
|
||||
// Make sure we don't have an empty RIF key.
|
||||
int i, test = 0;
|
||||
for(i = 0; i < 0x10; i++)
|
||||
for (i = 0; i < 0x10; i++)
|
||||
{
|
||||
if (key[i] != 0)
|
||||
{
|
||||
|
@ -579,30 +743,65 @@ bool extract_data(rFile *input, rFile *output, const char* input_file_name, unsi
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!test)
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: A valid RAP file is needed!");
|
||||
LOG_ERROR(LOADER, "EDAT: A valid RAP file is needed for this EDAT file!");
|
||||
delete NPD;
|
||||
delete EDAT;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if ((NPD->license & 0x1) == 0x1) // Type 1: Use network activation.
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Network license not supported!");
|
||||
delete NPD;
|
||||
delete EDAT;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
int i;
|
||||
LOG_NOTICE(LOADER, "DEVKLIC: ");
|
||||
for (i = 0; i < 0x10; i++)
|
||||
LOG_NOTICE(LOADER, "%02X", devklic[i]);
|
||||
|
||||
LOG_NOTICE(LOADER, "RIF KEY: ");
|
||||
for (i = 0; i < 0x10; i++)
|
||||
LOG_NOTICE(LOADER, "%02X", rifkey[i]);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_NOTICE(LOADER, "EDAT: Parsing data...\n");
|
||||
if (verbose)
|
||||
{
|
||||
int i;
|
||||
LOG_NOTICE(LOADER, "DECRYPTION KEY: ");
|
||||
for (i = 0; i < 0x10; i++)
|
||||
LOG_NOTICE(LOADER, "%02X", key[i]);
|
||||
}
|
||||
|
||||
LOG_NOTICE(LOADER, "EDAT: Parsing data...");
|
||||
if (check_data(key, EDAT, NPD, input, verbose))
|
||||
LOG_ERROR(LOADER, "EDAT: Data parsing failed!\n");
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Data parsing failed!");
|
||||
delete NPD;
|
||||
delete EDAT;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
LOG_SUCCESS(LOADER, "EDAT: Data successfully parsed!\n");
|
||||
LOG_NOTICE(LOADER, "EDAT: Data successfully parsed!");
|
||||
|
||||
printf("\n");
|
||||
|
||||
LOG_NOTICE(LOADER, "EDAT: Decrypting data...\n");
|
||||
LOG_NOTICE(LOADER, "EDAT: Decrypting data...");
|
||||
if (decrypt_data(input, output, EDAT, NPD, key, verbose))
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Data decryption failed!");
|
||||
delete NPD;
|
||||
delete EDAT;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
LOG_SUCCESS(LOADER, "EDAT: Data successfully decrypted!");
|
||||
LOG_NOTICE(LOADER, "EDAT: Data successfully decrypted!");
|
||||
|
||||
delete NPD;
|
||||
delete EDAT;
|
||||
|
@ -655,21 +854,21 @@ int DecryptEDAT(const std::string& input_file_name, const std::string& output_fi
|
|||
memcpy(devklic, custom_klic, 0x10);
|
||||
else
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Invalid custom klic!\n");
|
||||
LOG_ERROR(LOADER, "EDAT: Invalid custom klic!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(LOADER, "EDAT: Invalid mode!\n");
|
||||
LOG_ERROR(LOADER, "EDAT: Invalid mode!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check the input/output files.
|
||||
if (!input.IsOpened() || !output.IsOpened())
|
||||
{
|
||||
LOG_ERROR(LOADER, "EDAT: Failed to open files!\n");
|
||||
LOG_ERROR(LOADER, "EDAT: Failed to open files!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -692,11 +891,11 @@ int DecryptEDAT(const std::string& input_file_name, const std::string& output_fi
|
|||
input.Close();
|
||||
output.Close();
|
||||
rRemoveFile(output_file_name);
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
input.Close();
|
||||
output.Close();
|
||||
return 0;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue