428 lines
12 KiB
C
428 lines
12 KiB
C
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "aes.h"
|
|
#include "md5.c"
|
|
|
|
#ifdef MAX_PATH
|
|
#ifndef PATH_MAX
|
|
#define PATH_MAX MAX_PATH
|
|
#endif
|
|
#else
|
|
#ifndef PATH_MAX
|
|
#define PATH_MAX (0x7FFF)
|
|
#endif
|
|
#endif
|
|
|
|
|
|
#define DEBUG 1
|
|
|
|
#define PSSE_BLOCK_SIZE (0x8000)
|
|
#define PSSE_SIG_BLOCK_SIZE (0x80000)
|
|
#define PSSE_SIG_SIZE (0x400)
|
|
|
|
#define AES_KEY_SIZE (0x10)
|
|
#define AES_IV_SIZE (0x10)
|
|
|
|
const uint8_t header_iv[AES_IV_SIZE] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}; // IV for the encrypted PSSE Header
|
|
const uint8_t header_key[AES_KEY_SIZE] = {0x4E, 0x29, 0x8B, 0x40, 0xF5, 0x31, 0xF4, 0x69, 0xD2, 0x1F, 0x75, 0xB1, 0x33, 0xC3, 0x07, 0xBE}; // Key used to decrypt the encrypted PSSE Header
|
|
|
|
const uint8_t runtime_title_key[AES_KEY_SIZE] = {0xA8, 0x69, 0x3C, 0x4D, 0xF0, 0xAE, 0xED, 0xBC, 0x9A, 0xBF, 0xD8, 0x21, 0x36, 0x92, 0x91, 0x2D}; // Header used to decrypt runtime libaries, eg. Sce.PlayStation.Core.dll.
|
|
const uint8_t header_key_psmdev[AES_KEY_SIZE] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; // Key used to decrypt the encrypted PSSE Header in PSM dev,
|
|
|
|
const char* runtime_content_id = "IP9100-NPXS10074_00-0000000000000000";
|
|
|
|
static char title_content_id[0x30];
|
|
static uint8_t title_iv[AES_IV_SIZE];
|
|
static uint8_t title_key[AES_KEY_SIZE];
|
|
|
|
// psse header
|
|
typedef struct psse_header{
|
|
char magic[0x4];
|
|
uint32_t version;
|
|
uint64_t file_size;
|
|
uint32_t psse_type;
|
|
char content_id[0x2C];
|
|
uint8_t md5_hash[0x10];
|
|
uint8_t file_name[0x20];
|
|
uint8_t file_iv[0x10];
|
|
uint8_t unk[0x600];
|
|
} psse_header;
|
|
|
|
// rif header
|
|
typedef struct ScePsmDrmLicense {
|
|
char magic[0x8];
|
|
uint32_t unk1;
|
|
uint32_t unk2;
|
|
uint64_t account_id;
|
|
uint32_t unk3;
|
|
uint32_t unk4;
|
|
uint64_t start_time;
|
|
uint64_t expiration_time;
|
|
uint8_t activation_checksum[0x20];
|
|
char content_id[0x30];
|
|
uint8_t unk5[0x80];
|
|
uint8_t unk6[0x20];
|
|
uint8_t key[0x10];
|
|
uint8_t signature[0x1D0];
|
|
uint8_t rsa_signature[0x100];
|
|
} ScePsmDrmLicense;
|
|
|
|
// psse_block_ref struct
|
|
typedef struct psse_block_ref{
|
|
uint8_t block_data[PSSE_BLOCK_SIZE];
|
|
size_t block_size;
|
|
} psse_block_ref;
|
|
|
|
typedef struct decrypted_file{
|
|
uint8_t* data;
|
|
size_t file_size;
|
|
} decrypted_file;
|
|
|
|
// Debug print buffer contents
|
|
void print_buffer(char* buffer_title, uint8_t* buffer, size_t buffer_sz){
|
|
printf("[*] %s: ", buffer_title);
|
|
for(int i = 0; i < buffer_sz; i++) {
|
|
printf("%02X", buffer[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
// Convert a path to one for whatever OS your using.
|
|
void fix_paths(char* path){
|
|
size_t sz = strlen(path);
|
|
for(int i = 0; i < sz; i++){
|
|
#ifdef _WIN32
|
|
if(path[i] == '/')
|
|
#else
|
|
if(path[i] == '\\')
|
|
#endif
|
|
{
|
|
#ifdef _WIN32
|
|
path[i] = '\\';
|
|
#else
|
|
path[i] = '/';
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert a path to one for whatever OS your using.
|
|
int is_dir(char* path){
|
|
size_t sz = strlen(path);
|
|
if(path[sz] == '/' || path[sz] == '\\')
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
// Reads title key and content id from a rif
|
|
// returns <0 on fail, 0 on success.
|
|
int read_rif(char* rif_path){
|
|
ScePsmDrmLicense rif;
|
|
|
|
FILE* rif_fd = fopen(rif_path, "rb");
|
|
if(rif_fd == NULL){
|
|
return -1;
|
|
}
|
|
fread(&rif, sizeof(ScePsmDrmLicense), 1, rif_fd);
|
|
fclose(rif_fd);
|
|
|
|
// Read important stuff.
|
|
strncpy(title_content_id, rif.content_id, sizeof(title_content_id));
|
|
memcpy(title_key, rif.key, sizeof(title_key));
|
|
|
|
if(strlen(title_content_id) != 0x24){
|
|
return -2;
|
|
}
|
|
|
|
return 0x0;
|
|
}
|
|
|
|
// Returns the total filesize of a file.
|
|
size_t get_file_size(char* file_path){
|
|
FILE* get_size_fd = fopen(file_path, "rb");
|
|
fseek(get_size_fd, 0, SEEK_END);
|
|
size_t size = ftell(get_size_fd);
|
|
fclose(get_size_fd);
|
|
return size;
|
|
}
|
|
|
|
// Calculate IV for this block.
|
|
uint8_t* roll_iv(uint64_t block_id) {
|
|
uint8_t* new_iv = (uint8_t*)malloc(sizeof(title_iv));
|
|
|
|
memset(new_iv,0x00, sizeof(title_iv));
|
|
memcpy(new_iv, &block_id, sizeof(uint64_t));
|
|
for(int i = 0; i < sizeof(title_iv); i++){
|
|
new_iv[i] = new_iv[i] ^ title_iv[i];
|
|
}
|
|
return new_iv;
|
|
}
|
|
|
|
// Decrypts a specific block inside a PSSE file.
|
|
psse_block_ref decrypt_block(FILE* psse_file, uint64_t block_id, uint64_t total_blocks, uint32_t file_size){
|
|
psse_block_ref ref;
|
|
memset(&ref, 0x00, sizeof(psse_block_ref));
|
|
|
|
uint8_t* new_iv = roll_iv(block_id);
|
|
uint64_t block_loc = block_id * PSSE_BLOCK_SIZE;
|
|
size_t total_read = PSSE_BLOCK_SIZE;
|
|
uint64_t trim_to = total_read;
|
|
|
|
if(block_id == 0){ // Skip to filedata
|
|
block_loc = sizeof(psse_header);
|
|
total_read -= sizeof(psse_header);
|
|
trim_to = total_read;
|
|
}
|
|
else if(block_loc % PSSE_SIG_BLOCK_SIZE == 0){ // Skip signature block
|
|
block_loc += PSSE_SIG_SIZE;
|
|
total_read -= PSSE_SIG_SIZE;
|
|
trim_to = total_read;
|
|
}
|
|
|
|
uint64_t rd_amt = ((block_loc - sizeof(psse_header)) - (PSSE_SIG_SIZE*(block_loc / PSSE_SIG_BLOCK_SIZE))); // Total amount of bytes read so far.
|
|
|
|
if (block_id >= total_blocks) { // Is this the last block?
|
|
total_read = file_size - rd_amt;
|
|
trim_to = total_read;
|
|
total_read += ((AES_BLOCK_SIZE) - (total_read % (AES_BLOCK_SIZE)));
|
|
}
|
|
|
|
uint8_t* block_data = malloc(total_read);
|
|
|
|
fseek(psse_file, block_loc, SEEK_SET);
|
|
fread(block_data, total_read, 0x1, psse_file);
|
|
ref.block_size = trim_to;
|
|
|
|
// Decrypt block
|
|
uint32_t aes_game_ctx[0x3C];
|
|
aes_key_setup(title_key, aes_game_ctx, 0x80);
|
|
aes_decrypt_cbc(block_data, total_read, ref.block_data, aes_game_ctx, 0x80, new_iv);
|
|
|
|
free(block_data);
|
|
free(new_iv);
|
|
return ref;
|
|
}
|
|
|
|
// returns decrypted data on success, empty filesize on error
|
|
decrypted_file decrypt_file(char* psse_file){
|
|
size_t total_filesize = get_file_size(psse_file);
|
|
uint64_t total_blocks = (total_filesize / 0x8000);
|
|
|
|
uint8_t title_key_copy[sizeof(title_key)];
|
|
|
|
uint32_t aes_header_ctx[0x3C];
|
|
|
|
FILE* psse_file_fd = fopen(psse_file, "rb");
|
|
|
|
decrypted_file plaintext_file;
|
|
memset(&plaintext_file, 0x00, sizeof(decrypted_file));
|
|
|
|
psse_header file_psse_header;
|
|
memset(&file_psse_header, 0x00, sizeof(psse_header));
|
|
fread(&file_psse_header, sizeof(psse_header), 0x1, psse_file_fd);
|
|
|
|
#ifdef DEBUG
|
|
printf("[*] Decrypting: %s\n", psse_file);
|
|
#endif
|
|
|
|
// Check magic number
|
|
if(!((strncmp(file_psse_header.magic, "PSSE", 0x4) == 0) || (strncmp(file_psse_header.magic, "PSME", 0x4) == 0))){
|
|
#ifdef DEBUG
|
|
printf("[*] %s Is not a valid PSSE file.\n", psse_file);
|
|
#endif
|
|
return plaintext_file;
|
|
}
|
|
|
|
// Chceck version
|
|
if(file_psse_header.version != 0x01){
|
|
#ifdef DEBUG
|
|
printf("[*] %s has unknown PSSE version %i.\n", psse_file, file_psse_header.version);
|
|
#endif
|
|
return plaintext_file;
|
|
}
|
|
|
|
// Check psse type
|
|
if(file_psse_header.psse_type != 0x01){
|
|
#ifdef DEBUG
|
|
printf("[*] %s has unknown PSSE type %i.\n", psse_file, file_psse_header.version);
|
|
#endif
|
|
return plaintext_file;
|
|
}
|
|
|
|
int psm_dev = 0;
|
|
|
|
// Check content id
|
|
if(strlen(file_psse_header.content_id) == 0x24) {
|
|
if(strcmp(file_psse_header.content_id, title_content_id) == 0){ // Retail PSM
|
|
aes_key_setup(header_key, aes_header_ctx, 0x80);
|
|
}
|
|
else if(strcmp(file_psse_header.content_id, runtime_content_id)){ // Runtime Libary
|
|
memcpy(title_key_copy, title_key, sizeof(title_key));
|
|
memcpy(title_key, runtime_title_key, sizeof(title_key));
|
|
aes_key_setup(header_key, aes_header_ctx, 0x80);
|
|
psm_dev = 1;
|
|
}
|
|
else{
|
|
return plaintext_file;
|
|
}
|
|
}
|
|
else { // Debug PSM
|
|
aes_key_setup(header_key_psmdev, aes_header_ctx, 0x80);
|
|
}
|
|
|
|
aes_decrypt_cbc(file_psse_header.file_iv, sizeof(title_iv), title_iv, aes_header_ctx, 0x80, header_iv);
|
|
|
|
char* plaintext = malloc(file_psse_header.file_size);
|
|
uintptr_t plaintext_ptr = 0;
|
|
|
|
MD5Context md5_ctx;
|
|
md5Init(&md5_ctx);
|
|
|
|
for(int i = 0; i <= total_blocks; i++){
|
|
psse_block_ref block_ref = decrypt_block(psse_file_fd, i, total_blocks, file_psse_header.file_size);
|
|
|
|
md5Update(&md5_ctx, block_ref.block_data, block_ref.block_size);
|
|
|
|
memcpy(plaintext+plaintext_ptr, block_ref.block_data, block_ref.block_size);
|
|
plaintext_ptr += block_ref.block_size;
|
|
}
|
|
|
|
md5Finalize(&md5_ctx);
|
|
|
|
if(memcmp(file_psse_header.md5_hash, md5_ctx.digest, 0x10) != 0){
|
|
#ifdef DEBUG
|
|
printf("[*] MD5 Hash did not match expected.\n");
|
|
print_buffer("Got MD5", md5_ctx.digest, 0x10);
|
|
print_buffer("Expected MD5", file_psse_header.md5_hash, 0x10);
|
|
#endif
|
|
return plaintext_file;
|
|
}
|
|
|
|
|
|
fclose(psse_file_fd);
|
|
|
|
plaintext_file.data = plaintext;
|
|
plaintext_file.file_size = file_psse_header.file_size;
|
|
|
|
if(psm_dev){
|
|
memcpy(title_key, title_key_copy, sizeof(title_key));
|
|
}
|
|
|
|
return plaintext_file;
|
|
}
|
|
|
|
int decrypt_all_files(char* application_folder, char* psse_list, size_t psse_list_sz){
|
|
uint64_t start_point = 0;
|
|
size_t sz = 0;
|
|
|
|
char rel_path[PATH_MAX];
|
|
for(uint64_t i = 0; i < psse_list_sz; i++){
|
|
|
|
if(psse_list[i] == '\r' || psse_list[i] == '\n') {
|
|
|
|
if (sz == 0){
|
|
goto next;
|
|
}
|
|
|
|
char* cur_filename = (char*)malloc(sz+1);
|
|
memset(cur_filename, 0x00, sz+1);
|
|
strncpy(cur_filename, (psse_list+start_point), sz);
|
|
|
|
memset(rel_path, 0x00, sizeof(rel_path));
|
|
snprintf(rel_path, sizeof(rel_path), "%s\\%s", application_folder, cur_filename);
|
|
free(cur_filename);
|
|
|
|
fix_paths(rel_path);
|
|
|
|
if(is_dir(rel_path)){
|
|
goto next;
|
|
}
|
|
|
|
decrypted_file dec_file = decrypt_file(rel_path);
|
|
|
|
if(dec_file.data == NULL){
|
|
#ifdef DEBUG
|
|
printf("[*] Decryption failed.\n");
|
|
#endif
|
|
return -3;
|
|
}
|
|
|
|
// Write decrypted file to disk.
|
|
FILE* dec_file_fd = fopen(rel_path, "wb");
|
|
if(dec_file_fd == NULL){
|
|
#ifdef DEBUG
|
|
printf("[*] Failed to open %s for writing.\n", rel_path);
|
|
#endif
|
|
return -4;
|
|
}
|
|
fwrite(dec_file.data, dec_file.file_size, 0x1, dec_file_fd);
|
|
fclose(dec_file_fd);
|
|
|
|
free(dec_file.data);
|
|
|
|
next:
|
|
start_point = i+1;
|
|
sz = 0;
|
|
continue;
|
|
}
|
|
sz++;
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
memset(title_content_id, 0x00, sizeof(title_content_id));
|
|
memset(title_iv, 0x00, sizeof(title_iv));
|
|
memset(title_key, 0x00, sizeof(title_key));
|
|
|
|
if(argc <= 1){
|
|
printf("PSSE Decryptor.\n");
|
|
printf("Created by Li / SilicaAndPina ..\n");
|
|
printf("Usage: %s <PSM_FOLDER>\n",argv[0]);
|
|
return 0;
|
|
}
|
|
|
|
char* psm_folder = argv[1];
|
|
|
|
char application_folder[PATH_MAX];
|
|
char psse_list[PATH_MAX];
|
|
char rif_file[PATH_MAX];
|
|
|
|
snprintf(application_folder, (PATH_MAX-1), "%s\\RO\\Application", psm_folder);
|
|
snprintf(psse_list, (PATH_MAX-1), "%s\\psse.list", application_folder);
|
|
snprintf(rif_file, (PATH_MAX-1), "%s\\RO\\License\\FAKE.rif", psm_folder);
|
|
|
|
fix_paths(application_folder);
|
|
fix_paths(psse_list);
|
|
fix_paths(rif_file);
|
|
|
|
if(read_rif(rif_file) < 0){
|
|
printf("[*] Unable to read RIF: %s\n", rif_file);
|
|
return -1;
|
|
}
|
|
#ifdef DEBUG
|
|
print_buffer("[*] Title Key", title_key, sizeof(title_key));
|
|
#endif
|
|
|
|
|
|
|
|
decrypted_file plaintext_psse_list = decrypt_file(psse_list);
|
|
if(plaintext_psse_list.data == NULL){
|
|
printf("[*] Decryption failed.\n");
|
|
return -2;
|
|
}
|
|
|
|
// Write decrypted psse.list.
|
|
FILE* psse_list_fd = fopen(psse_list, "wb");
|
|
fwrite(plaintext_psse_list.data, plaintext_psse_list.file_size, 0x1, psse_list_fd);
|
|
fclose(psse_list_fd);
|
|
|
|
int res = decrypt_all_files(application_folder, plaintext_psse_list.data, plaintext_psse_list.file_size);
|
|
if (res < 0){
|
|
return res;
|
|
}
|
|
free(plaintext_psse_list.data);
|
|
} |