/* * Copyright (c) 2011-2013 by naehrwert * This file is released under the GPLv2. */ #include #include #include #include #ifdef _WIN32 #include #include "getopt.h" #else #include #include #define _access access #define _strdup strdup #endif #include "types.h" #include "config.h" #include "aes.c" #include "getopt.c" #include "sha1.c" #include "zlib.h" #include "util.h" #include "keys.h" #include "sce.h" #include "np.h" #include "self.h" #include "rvk.h" /*! Verbose mode. */ BOOL _verbose = FALSE; /*! Raw mode. */ BOOL _raw = FALSE; /*! List keys. */ static BOOL _list_keys = FALSE; /*! Parameters. */ scetool::s8 *_template = NULL; scetool::s8 *_file_type = NULL; scetool::s8 *_compress_data = NULL; scetool::s8 *_skip_sections = NULL; scetool::s8 *_key_rev = NULL; scetool::s8 *_meta_info = NULL; scetool::s8 *_keyset = NULL; scetool::s8 *_auth_id = NULL; scetool::s8 *_vendor_id = NULL; scetool::s8 *_self_type = NULL; scetool::s8 *_app_version = NULL; scetool::s8 *_fw_version = NULL; scetool::s8 *_add_shdrs = NULL; scetool::s8 *_ctrl_flags = NULL; scetool::s8 *_cap_flags = NULL; #ifdef CONFIG_CUSTOM_INDIV_SEED scetool::s8 *_indiv_seed = NULL; #endif scetool::s8 *_license_type = NULL; scetool::s8 *_app_type = NULL; scetool::s8 *_content_id = NULL; scetool::s8 *_klicensee = NULL; scetool::s8 *_real_fname = NULL; scetool::s8 *_add_sig = NULL; #include //FILE: LIST.CPP list_t *list_create() { list_t *res; if((res = (list_t *)malloc(sizeof(list_t))) == NULL) return NULL; res->head = NULL; res->count = 0; return res; } void list_destroy(list_t *l) { if(l == NULL) return; lnode_t *iter = l->head, *tmp; while(iter != NULL) { tmp = iter; iter = iter->next; free(tmp); } free(l); } BOOL list_push(list_t *l, void *value) { if(l == NULL) return FALSE; lnode_t *_new; //Allocate new node. if((_new = (lnode_t *)malloc(sizeof(lnode_t))) == NULL) return FALSE; //Insert. _new->value = value; _new->next = l->head; l->head = _new; l->count++; return TRUE; } BOOL list_add_back(list_t *l, void *value) { if(l == NULL) return FALSE; lnode_t *n, *_new; //Allocate new node. if((_new = (lnode_t *)malloc(sizeof(lnode_t))) == NULL) return FALSE; _new->value = value; _new->next = NULL; if(l->head == NULL) l->head = _new; else { //Move to the list end. for(n = l->head; n->next != NULL; n = n->next); //Add. n->next = _new; l->count++; } return TRUE; } BOOL list_remove_node(list_t *l, lnode_t *node) { if(l == NULL) return FALSE; lnode_t *iter; if(l->head == node) { l->head = l->head->next; free(node); l->count--; return TRUE; } iter = l->head; while(iter->next != NULL) { if(iter->next == node) { iter->next = iter->next->next; free(node); l->count--; return TRUE; } iter = iter->next; } return FALSE; } //// //FILE: UTIL.CPP scetool::u64 _x_to_u64(const scetool::s8 *hex) { scetool::u64 t = 0, res = 0; scetool::u32 len = strlen(hex); char c; while(len--) { c = *hex++; if(c >= '0' && c <= '9') t = c - '0'; else if(c >= 'a' && c <= 'f') t = c - 'a' + 10; else if(c >= 'A' && c <= 'F') t = c - 'A' + 10; else t = 0; res |= t << (len * 4); } return res; } scetool::u8 *_x_to_u8_buffer(const scetool::s8 *hex) { scetool::u32 len = strlen(hex); scetool::s8 xtmp[3] = {0, 0, 0}; //Must be aligned to 2. if(len % 2 != 0) return NULL; scetool::u8 *res = (scetool::u8 *)malloc(sizeof(scetool::u8) * len); scetool::u8 *ptr = res; while(len--) { xtmp[0] = *hex++; xtmp[1] = *hex++; *ptr++ = (scetool::u8)_x_to_u64(xtmp); } return res; } scetool::u8 *_read_buffer(const scetool::s8 *file, scetool::u32 *length) { FILE *fp; scetool::u32 size; if((fp = fopen(file, "rb")) == NULL) return NULL; fseek(fp, 0, SEEK_END); size = ftell(fp); fseek(fp, 0, SEEK_SET); scetool::u8 *buffer = (scetool::u8 *)malloc(sizeof(scetool::u8) * size); fread(buffer, sizeof(scetool::u8), size, fp); if(length != NULL) *length = size; fclose(fp); return buffer; } int _write_buffer(const scetool::s8 *file, scetool::u8 *buffer, scetool::u32 length) { FILE *fp; if((fp = fopen(file, "wb")) == NULL) return 0; /**/ while(length > 0) { scetool::u32 wrlen = 1024; if(length < 1024) wrlen = length; fwrite(buffer, sizeof(scetool::u8), wrlen, fp); length -= wrlen; buffer += 1024; } /**/ //fwrite(buffer, sizeof(scetool::u8), length, fp); fclose(fp); return 1; } void _zlib_inflate(scetool::u8 *in, scetool::u64 len_in, scetool::u8 *out, scetool::u64 len_out) { z_stream s; memset(&s, 0, sizeof(z_stream)); s.zalloc = Z_NULL; s.zfree = Z_NULL; s.opaque = Z_NULL; inflateInit(&s); s.avail_in = len_in; s.next_in = in; s.avail_out = len_out; s.next_out = out; inflate(&s, Z_FINISH); inflateEnd(&s); } void *_memdup(void *ptr, scetool::u32 size) { void *res = malloc(size); if(res != NULL) memcpy(res, ptr, size); return res; } void _print_align(FILE *fp, const scetool::s8 *str, scetool::s32 align, scetool::s32 len) { scetool::s32 i, tmp; tmp = align - len; if(tmp < 0) tmp = 0; for(i = 0; i < tmp; i++) fputs(str, fp); } const scetool::s8 *_get_name(id_to_name_t *tab, scetool::u64 id) { scetool::u32 i = 0; while(!(tab[i].name == NULL && tab[i].id == 0)) { if(tab[i].id == id) return tab[i].name; i++; } return NULL; } //FILE: SCE.CPP #include "sce_inlines.h" sce_buffer_ctxt_t *sce_create_ctxt_from_buffer(scetool::u8 *scebuffer) { sce_buffer_ctxt_t *res; if((res = (sce_buffer_ctxt_t *)malloc(sizeof(sce_buffer_ctxt_t))) == NULL) return NULL; memset(res, 0, sizeof(sce_buffer_ctxt_t)); res->scebuffer = scebuffer; res->mdec = FALSE; //Set pointer to SCE header. res->sceh = (sce_header_t *)scebuffer; _es_sce_header(res->sceh); //Set pointers to file type specific headers. switch(res->sceh->header_type) { case SCE_HEADER_TYPE_SELF: { //SELF header. res->self.selfh = (self_header_t *)(res->scebuffer + sizeof(sce_header_t)); _es_self_header(res->self.selfh); //Application info. res->self.ai = (app_info_t *)(res->scebuffer + res->self.selfh->app_info_offset); _es_app_info(res->self.ai); //Section infos. res->self.si = (section_info_t *)(res->scebuffer + res->self.selfh->section_info_offset); //SCE version. if(res->self.selfh->sce_version_offset != NULL) { res->self.sv = (sce_version_t *)(res->scebuffer + res->self.selfh->sce_version_offset); _es_sce_version(res->self.sv); } else res->self.sv = 0; //Get pointers to all control infos. scetool::u32 len = (scetool::u32)res->self.selfh->control_info_size; if(len > 0) { scetool::u8 *ptr = res->scebuffer + res->self.selfh->control_info_offset; res->self.cis = list_create(); while(len > 0) { control_info_t *tci = (control_info_t *)ptr; _es_control_info(tci); ptr += tci->size; len -= tci->size; list_add_back(res->self.cis, tci); } } else res->self.cis = NULL; } break; case SCE_HEADER_TYPE_RVK: //TODO break; case SCE_HEADER_TYPE_PKG: //TODO break; case SCE_HEADER_TYPE_SPP: //TODO break; default: free(res); return NULL; break; } //Set pointers to metadata headers. res->metai = (metadata_info_t *)(scebuffer + sizeof(sce_header_t) + res->sceh->metadata_offset); res->metah = (metadata_header_t *)((scetool::u8 *)res->metai + sizeof(metadata_info_t)); res->metash = (metadata_section_header_t *)((scetool::u8 *)res->metah + sizeof(metadata_header_t)); return res; } static scetool::s8 _sce_tmp_vstr[16]; scetool::s8 *sce_version_to_str(scetool::u64 version) { scetool::u32 v = version >> 32; sprintf(_sce_tmp_vstr, "%02X.%02X", (v & 0xFFFF0000) >> 16, v & 0x0000FFFF); return _sce_tmp_vstr; } BOOL sce_decrypt_header(sce_buffer_ctxt_t *ctxt, scetool::u8 *metadata_info, scetool::u8 *keyset) { scetool::u32 i; size_t nc_off; scetool::u8 sblk[0x10], iv[0x10]; keyset_t *ks; aes_context aes_ctxt; //Check if provided metadata info should be used. if(metadata_info == NULL) { //Check if a keyset is provided. if(keyset == NULL) { //Try to find keyset. if((ks = keyset_find(ctxt)) == NULL) return FALSE; _LOG_VERBOSE("Using keyset [%s 0x%04X %s]\n", ks->name, ks->key_revision, sce_version_to_str(ks->version)); } else { //Use the provided keyset. ks = keyset_from_buffer(keyset); } //Remove NPDRM layer. if(ctxt->sceh->header_type == SCE_HEADER_TYPE_SELF && ctxt->self.ai->self_type == SELF_TYPE_NPDRM) if(np_decrypt_npdrm(ctxt) == FALSE) return FALSE; //Decrypt metadata info. aes_setkey_dec(&aes_ctxt, ks->erk, KEYBITS(ks->erklen)); memcpy(iv, ks->riv, 0x10); //!!! aes_crypt_cbc(&aes_ctxt, AES_DECRYPT, sizeof(metadata_info_t), iv, (scetool::u8 *)ctxt->metai, (scetool::u8 *)ctxt->metai); } else { //Copy provided metadata info over SELF metadata. memcpy((scetool::u8 *)ctxt->metai, metadata_info, sizeof(metadata_info)); } if(ctxt->metai->key_pad[0] != 0x00 || ctxt->metai->iv_pad[0] != 0x00) return FALSE; //Decrypt metadata header, metadata section headers and keys. nc_off = 0; aes_setkey_enc(&aes_ctxt, ctxt->metai->key, METADATA_INFO_KEYBITS); aes_crypt_ctr(&aes_ctxt, ctxt->sceh->header_len - (sizeof(sce_header_t) + ctxt->sceh->metadata_offset + sizeof(metadata_info_t)), &nc_off, ctxt->metai->iv, sblk, (scetool::u8 *)ctxt->metah, (scetool::u8 *)ctxt->metah); //Fixup headers. _es_metadata_header(ctxt->metah); for(i = 0; i < ctxt->metah->section_count; i++) _es_metadata_section_header(&ctxt->metash[i]); //Metadata decrypted. ctxt->mdec = TRUE; //Set start of SCE file keys. ctxt->keys = (scetool::u8 *)ctxt->metash + sizeof(metadata_section_header_t) * ctxt->metah->section_count; ctxt->keys_len = ctxt->metah->key_count * 0x10; //Set SELF only headers. if(ctxt->sceh->header_type == SCE_HEADER_TYPE_SELF) { //Get pointers to all optional headers. ctxt->self.ohs = list_create(); opt_header_t *oh = (opt_header_t *)(ctxt->keys + ctxt->metah->key_count * 0x10); _es_opt_header(oh); list_add_back(ctxt->self.ohs, oh); while(oh->next != 0) { oh = (opt_header_t *)((scetool::u8 *)oh + oh->size); _es_opt_header(oh); list_add_back(ctxt->self.ohs, oh); } //Signature. ctxt->sig = (signature_t *)((scetool::u8 *)oh + oh->size); } else ctxt->sig = (signature_t *)(ctxt->keys + ctxt->metah->key_count * 0x10); return TRUE; } BOOL sce_decrypt_data(sce_buffer_ctxt_t *ctxt) { scetool::u32 i; aes_context aes_ctxt; //Decrypt sections. for(i = 0; i < ctxt->metah->section_count; i++) { size_t nc_off = 0; scetool::u8 buf[16]; scetool::u8 iv[16]; //Only decrypt encrypted sections. if(ctxt->metash[i].encrypted == METADATA_SECTION_ENCRYPTED) { if(ctxt->metash[i].key_index > ctxt->metah->key_count - 1 || ctxt->metash[i].iv_index > ctxt->metah->key_count) printf("[*] Warning: Skipped decryption of section %03d (marked encrypted but key/iv index out of range)\n", i); else { memcpy(iv, ctxt->keys + ctxt->metash[i].iv_index * 0x10, 0x10); aes_setkey_enc(&aes_ctxt, ctxt->keys + ctxt->metash[i].key_index * 0x10, 128); scetool::u8 *ptr = ctxt->scebuffer + ctxt->metash[i].data_offset; aes_crypt_ctr(&aes_ctxt, ctxt->metash[i].data_size, &nc_off, iv, buf, ptr, ptr); } } } return TRUE; } //FILE: SELF.CPP #include "elf_inlines.h" //TODO: maybe implement better. BOOL self_write_to_elf(sce_buffer_ctxt_t *ctxt, const scetool::s8 *elf_out) { FILE *fp; scetool::u32 i, self_type; const scetool::u8 *eident; //Check for SELF. if(ctxt->sceh->header_type != SCE_HEADER_TYPE_SELF) return FALSE; if((fp = fopen(elf_out, "wb")) == NULL) return FALSE; self_type = ctxt->self.ai->self_type; eident = ctxt->scebuffer + ctxt->self.selfh->elf_offset; //SPU is 32 bit. if(self_type == SELF_TYPE_LDR || self_type == SELF_TYPE_ISO || eident[EI_CLASS] == ELFCLASs32) { #ifdef CONFIG_DUMP_INDIV_SEED /* //Print individuals seed. if(self_type == SELF_TYPE_ISO) { scetool::u8 *indiv_seed = (scetool::u8 *)ctxt->self.ish + sizeof(iseed_header_t); scetool::s8 ifile[256]; sprintf(ifile, "%s.indiv_seed.bin", elf_out); FILE *ifp = fopen(ifile, "wb"); fwrite(indiv_seed, sizeof(scetool::u8), ctxt->self.ish->size - sizeof(iseed_header_t), ifp); fclose(ifp); } */ #endif //32 bit ELF. Elf32_Ehdr ceh, *eh = (Elf32_Ehdr *)(ctxt->scebuffer + ctxt->self.selfh->elf_offset); _copy_es_elf32_ehdr(&ceh, eh); //Write ELF header. fwrite(eh, sizeof(Elf32_Ehdr), 1, fp); //Write program headers. Elf32_Phdr *ph = (Elf32_Phdr *)(ctxt->scebuffer + ctxt->self.selfh->phdr_offset); fwrite(ph, sizeof(Elf32_Phdr), ceh.e_phnum, fp); //Write program data. metadata_section_header_t *msh = ctxt->metash; for(i = 0; i < ctxt->metah->section_count; i++) { if(msh[i].type == METADATA_SECTION_TYPE_PHDR) { _es_elf32_phdr(&ph[msh[i].index]); fseek(fp, ph[msh[i].index].p_offset, SEEK_SET); fwrite(ctxt->scebuffer + msh[i].data_offset, sizeof(scetool::u8), msh[i].data_size, fp); } } //Write section headers. if(ctxt->self.selfh->shdr_offset != 0) { Elf32_Shdr *sh = (Elf32_Shdr *)(ctxt->scebuffer + ctxt->self.selfh->shdr_offset); fseek(fp, ceh.e_shoff, SEEK_SET); fwrite(sh, sizeof(Elf32_Shdr), ceh.e_shnum, fp); } } else { //64 bit ELF. Elf64_Ehdr ceh, *eh = (Elf64_Ehdr *)(ctxt->scebuffer + ctxt->self.selfh->elf_offset); _copy_es_elf64_ehdr(&ceh, eh); //Write ELF header. fwrite(eh, sizeof(Elf64_Ehdr), 1, fp); //Write program headers. Elf64_Phdr *ph = (Elf64_Phdr *)(ctxt->scebuffer + ctxt->self.selfh->phdr_offset); fwrite(ph, sizeof(Elf64_Phdr), ceh.e_phnum, fp); //Write program data. metadata_section_header_t *msh = ctxt->metash; for(i = 0; i < ctxt->metah->section_count; i++) { if(msh[i].type == METADATA_SECTION_TYPE_PHDR) { if(msh[i].compressed == METADATA_SECTION_COMPRESSED) { _es_elf64_phdr(&ph[msh[i].index]); scetool::u8 *data = (scetool::u8 *)malloc(ph[msh[i].index].p_filesz); _zlib_inflate(ctxt->scebuffer + msh[i].data_offset, msh[i].data_size, data, ph[msh[i].index].p_filesz); fseek(fp, ph[msh[i].index].p_offset, SEEK_SET); fwrite(data, sizeof(scetool::u8), ph[msh[i].index].p_filesz, fp); free(data); } else { _es_elf64_phdr(&ph[msh[i].index]); fseek(fp, ph[msh[i].index].p_offset, SEEK_SET); fwrite(ctxt->scebuffer + msh[i].data_offset, sizeof(scetool::u8), msh[i].data_size, fp); } } } //Write section headers. if(ctxt->self.selfh->shdr_offset != 0) { Elf64_Shdr *sh = (Elf64_Shdr *)(ctxt->scebuffer + ctxt->self.selfh->shdr_offset); fseek(fp, ceh.e_shoff, SEEK_SET); fwrite(sh, sizeof(Elf64_Shdr), ceh.e_shnum, fp); } } fclose(fp); return TRUE; } //FILE: NP.CPP /*! klicensee key. */ static scetool::u8 *_klicensee_key; static ci_data_npdrm_t *_sce_find_ci_npdrm(sce_buffer_ctxt_t *ctxt) { if(ctxt->self.cis != NULL) { LIST_FOREACH(iter, ctxt->self.cis) { control_info_t *ci = (control_info_t *)iter->value; if(ci->type == CONTROL_INFO_TYPE_NPDRM) { ci_data_npdrm_t *np = (ci_data_npdrm_t *)((scetool::u8 *)ci + sizeof(control_info_t)); //Fixup. _es_ci_data_npdrm(np); return np; } } } return NULL; } void np_set_klicensee(scetool::u8 *klicensee) { _klicensee_key = klicensee; } BOOL np_decrypt_npdrm(sce_buffer_ctxt_t *ctxt) { aes_context aes_ctxt; keyset_t *ks_np_klic_free, *ks_klic_key; scetool::u8 npdrm_key[0x10]; scetool::u8 npdrm_iv[0x10]; ci_data_npdrm_t *np; if((np = _sce_find_ci_npdrm(ctxt)) == NULL) return FALSE; //Try to find keysets. ks_klic_key = keyset_find_by_name(CONFIG_NP_KLIC_KEY_KNAME); if(ks_klic_key == NULL) return FALSE; if(_klicensee_key != NULL) memcpy(npdrm_key, _klicensee_key, 0x10); else if(np->license_type == NP_LICENSE_FREE) { ks_np_klic_free = keyset_find_by_name(CONFIG_NP_KLIC_FREE_KNAME); if(ks_np_klic_free == NULL) return FALSE; memcpy(npdrm_key, ks_np_klic_free->erk, 0x10); } else if(np->license_type == NP_LICENSE_LOCAL) { if ((klicensee_by_content_id((scetool::s8 *)np->content_id, npdrm_key)) == FALSE) return FALSE; } else return FALSE; aes_setkey_dec(&aes_ctxt, ks_klic_key->erk, METADATA_INFO_KEYBITS); aes_crypt_ecb(&aes_ctxt, AES_DECRYPT, npdrm_key, npdrm_key); memset(npdrm_iv, 0, 0x10); aes_setkey_dec(&aes_ctxt, npdrm_key, METADATA_INFO_KEYBITS); aes_crypt_cbc(&aes_ctxt, AES_DECRYPT, sizeof(metadata_info_t), npdrm_iv, (scetool::u8 *)ctxt->metai, (scetool::u8 *)ctxt->metai); return TRUE; } //FILE: TABLES.CPP /*! SCE header types. */ id_to_name_t _sce_header_types[] = { {SCE_HEADER_TYPE_SELF, "SELF"}, {SCE_HEADER_TYPE_RVK, "RVK"}, {SCE_HEADER_TYPE_PKG, "PKG"}, {SCE_HEADER_TYPE_SPP, "SPP"}, {0, NULL} }; /*! SELF types. */ id_to_name_t _self_types[] = { {SELF_TYPE_LV0, "lv0"}, {SELF_TYPE_LV1, "lv1"}, {SELF_TYPE_LV2, "lv2"}, {SELF_TYPE_APP, "Application"}, {SELF_TYPE_ISO, "Isolated SPU Module"}, {SELF_TYPE_LDR, "Secure Loader"}, {SELF_TYPE_UNK_7, "Unknown 7"}, {SELF_TYPE_NPDRM, "NPDRM Application"}, {0, NULL} }; /*! Key types. */ id_to_name_t _key_types[] = { {KEYTYPE_SELF, "SELF"}, {KEYTYPE_RVK, "RVK"}, {KEYTYPE_PKG, "PKG"}, {KEYTYPE_SPP, "SPP"}, {KEYTYPE_OTHER, "OTHER"}, {0, NULL} }; //FILE: KEYS.CPP /*! Loaded keysets. */ list_t *_keysets; /*! Loaded curves. */ curve_t *_curves; /*! Loaded VSH curves. */ vsh_curve_t *_vsh_curves; static scetool::u8 rap_init_key[0x10] = { 0x86, 0x9F, 0x77, 0x45, 0xC1, 0x3F, 0xD8, 0x90, 0xCC, 0xF2, 0x91, 0x88, 0xE3, 0xCC, 0x3E, 0xDF }; static scetool::u8 rap_pbox[0x10] = { 0x0C, 0x03, 0x06, 0x04, 0x01, 0x0B, 0x0F, 0x08, 0x02, 0x07, 0x00, 0x05, 0x0A, 0x0E, 0x0D, 0x09 }; static scetool::u8 rap_e1[0x10] = { 0xA9, 0x3E, 0x1F, 0xD6, 0x7C, 0x55, 0xA3, 0x29, 0xB7, 0x5F, 0xDD, 0xA6, 0x2A, 0x95, 0xC7, 0xA5 }; static scetool::u8 rap_e2[0x10] = { 0x67, 0xD4, 0x5D, 0xA3, 0x29, 0x6D, 0x00, 0x6A, 0x4E, 0x7C, 0x53, 0x7B, 0xF5, 0x53, 0x8C, 0x74 }; static act_dat_t *act_dat_load() { scetool::s8 *ps3 = NULL, path[256]; act_dat_t *act_dat; scetool::u32 len = 0; if((ps3 = getenv(CONFIG_ENV_PS3)) != NULL) if(_access(ps3, 0) != 0) ps3 = NULL; if(ps3 != NULL) { sprintf(path, "%s/%s", ps3, CONFIG_ACT_DAT_FILE); if(_access(path, 0) != 0) sprintf(path, "%s/%s", CONFIG_ACT_DAT_PATH, CONFIG_ACT_DAT_FILE); } else sprintf(path, "%s/%s", CONFIG_ACT_DAT_PATH, CONFIG_ACT_DAT_FILE); act_dat = (act_dat_t *)_read_buffer(path, &len); if(act_dat == NULL) return NULL; if(len != ACT_DAT_LENGTH) { free(act_dat); return NULL; } return act_dat; } static rif_t *rif_load(const scetool::s8 *content_id) { scetool::s8 *ps3 = NULL, path[256]; rif_t *rif; scetool::u32 len = 0; if((ps3 = getenv(CONFIG_ENV_PS3)) != NULL) if(_access(ps3, 0) != 0) ps3 = NULL; if(ps3 != NULL) { sprintf(path, "%s/%s%s", ps3, content_id, CONFIG_RIF_FILE_EXT); if(_access(path, 0) != 0) sprintf(path, "%s/%s%s", CONFIG_RIF_PATH, content_id, CONFIG_RIF_FILE_EXT); } else sprintf(path, "%s/%s%s", CONFIG_RIF_PATH, content_id, CONFIG_RIF_FILE_EXT); rif = (rif_t *)_read_buffer(path, &len); if(rif == NULL) return NULL; if(len < RIF_LENGTH) { free(rif); return NULL; } return rif; } static scetool::u8 *rap_load(const scetool::s8 *content_id) { scetool::s8 *ps3 = NULL, path[256]; scetool::u8 *rap; scetool::u32 len = 0; if((ps3 = getenv(CONFIG_ENV_PS3)) != NULL) if(_access(ps3, 0) != 0) ps3 = NULL; if(ps3 != NULL) { sprintf(path, "%s/%s%s", ps3, content_id, CONFIG_RAP_FILE_EXT); if(_access(path, 0) != 0) sprintf(path, "%s/%s%s", CONFIG_RAP_PATH, content_id, CONFIG_RAP_FILE_EXT); } else sprintf(path, "%s/%s%s", CONFIG_RAP_PATH, content_id, CONFIG_RAP_FILE_EXT); rap = (scetool::u8 *)_read_buffer(path, &len); if(rap == NULL) return NULL; if(len != RAP_LENGTH) { free(rap); return NULL; } return rap; } static BOOL rap_to_klicensee(const scetool::s8 *content_id, scetool::u8 *klicensee) { scetool::u8 *rap; aes_context aes_ctxt; int round_num; int i; rap = rap_load(content_id); if(rap == NULL) return FALSE; aes_setkey_dec(&aes_ctxt, rap_init_key, RAP_KEYBITS); aes_crypt_ecb(&aes_ctxt, AES_DECRYPT, rap, rap); for (round_num = 0; round_num < 5; ++round_num) { for (i = 0; i < 16; ++i) { int p = rap_pbox[i]; rap[p] ^= rap_e1[p]; } for (i = 15; i >= 1; --i) { int p = rap_pbox[i]; int pp = rap_pbox[i - 1]; rap[p] ^= rap[pp]; } int o = 0; for (i = 0; i < 16; ++i) { int p = rap_pbox[i]; scetool::u8 kc = rap[p] - o; scetool::u8 ec2 = rap_e2[p]; if (o != 1 || kc != 0xFF) { o = kc < ec2 ? 1 : 0; rap[p] = kc - ec2; } else if (kc == 0xFF) rap[p] = kc - ec2; else rap[p] = kc; } } memcpy(klicensee, rap, RAP_LENGTH); free(rap); return TRUE; } static scetool::u8 *idps_load() { scetool::s8 *ps3 = NULL, path[256]; scetool::u8 *idps; scetool::u32 len = 0; if((ps3 = getenv(CONFIG_ENV_PS3)) != NULL) if(_access(ps3, 0) != 0) ps3 = NULL; if(ps3 != NULL) { sprintf(path, "%s/%s", ps3, CONFIG_IDPS_FILE); if(_access(path, 0) != 0) sprintf(path, "%s/%s", CONFIG_IDPS_PATH, CONFIG_IDPS_FILE); } else sprintf(path, "%s/%s", CONFIG_IDPS_PATH, CONFIG_IDPS_FILE); idps = (scetool::u8 *)_read_buffer(path, &len); if(idps == NULL) return NULL; if(len != IDPS_LENGTH) { free(idps); return NULL; } return idps; } BOOL klicensee_by_content_id(const scetool::s8 *content_id, scetool::u8 *klicensee) { aes_context aes_ctxt; if(rap_to_klicensee(content_id, klicensee) == FALSE) { keyset_t *ks_np_idps_const, *ks_np_rif_key; rif_t *rif; scetool::u8 idps_const[0x10]; scetool::u8 act_dat_key[0x10]; scetool::u32 act_dat_key_index; scetool::u8 *idps; act_dat_t *act_dat; if((idps = idps_load()) == NULL) { printf("[*] Error: Could not load IDPS.\n"); return FALSE; } else _LOG_VERBOSE("IDPS loaded.\n"); if((act_dat = act_dat_load()) == NULL) { printf("[*] Error: Could not load act.dat.\n"); return FALSE; } else _LOG_VERBOSE("act.dat loaded.\n"); ks_np_idps_const = keyset_find_by_name(CONFIG_NP_IDPS_CONST_KNAME); if(ks_np_idps_const == NULL) return FALSE; memcpy(idps_const, ks_np_idps_const->erk, 0x10); ks_np_rif_key = keyset_find_by_name(CONFIG_NP_RIF_KEY_KNAME); if(ks_np_rif_key == NULL) return FALSE; rif = rif_load(content_id); if(rif == NULL) { printf("[*] Error: Could not obtain klicensee for '%s'.\n", content_id); return FALSE; } aes_setkey_dec(&aes_ctxt, ks_np_rif_key->erk, RIF_KEYBITS); aes_crypt_ecb(&aes_ctxt, AES_DECRYPT, rif->act_key_index, rif->act_key_index); act_dat_key_index = _Es32(*(scetool::u32 *)(rif->act_key_index + 12)); if(act_dat_key_index > 127) { printf("[*] Error: act.dat key index out of bounds.\n"); return FALSE; } memcpy(act_dat_key, act_dat->primary_key_table + act_dat_key_index * BITS2BYTES(ACT_DAT_KEYBITS), BITS2BYTES(ACT_DAT_KEYBITS)); aes_setkey_enc(&aes_ctxt, idps, IDPS_KEYBITS); aes_crypt_ecb(&aes_ctxt, AES_ENCRYPT, idps_const, idps_const); aes_setkey_dec(&aes_ctxt, idps_const, IDPS_KEYBITS); aes_crypt_ecb(&aes_ctxt, AES_DECRYPT, act_dat_key, act_dat_key); aes_setkey_dec(&aes_ctxt, act_dat_key, ACT_DAT_KEYBITS); aes_crypt_ecb(&aes_ctxt, AES_DECRYPT, rif->klicensee, klicensee); free(rif); _LOG_VERBOSE("klicensee decrypted.\n"); } else _LOG_VERBOSE("klicensee converted from %s.rap.\n", content_id); return TRUE; } keyset_t *keyset_from_buffer(scetool::u8 *keyset) { keyset_t *ks; if((ks = (keyset_t *)malloc(sizeof(keyset_t))) == NULL) return NULL; ks->erk = (scetool::u8 *)_memdup(keyset, 0x20); ks->erklen = 0x20; ks->riv = (scetool::u8 *)_memdup(keyset + 0x20, 0x10); ks->rivlen = 0x10; ks->pub = (scetool::u8 *)_memdup(keyset + 0x20 + 0x10, 0x28); ks->priv = (scetool::u8 *)_memdup(keyset + 0x20 + 0x10 + 0x28, 0x15); ks->ctype = (scetool::u8)*(keyset + 0x20 + 0x10 + 0x28 + 0x15); return ks; } keyset_t *keyset_find_by_name(const scetool::s8 *name) { LIST_FOREACH(iter, _keysets) { keyset_t *ks = (keyset_t *)iter->value; if(strcmp(ks->name, name) == 0) return ks; } printf("[*] Error: Could not find keyset '%s'.\n", name); return NULL; } static keyset_t *_keyset_find_for_self(scetool::u32 self_type, scetool::u16 key_revision, scetool::u64 version) { LIST_FOREACH(iter, _keysets) { keyset_t *ks = (keyset_t *)iter->value; if(ks->self_type == self_type) { switch(self_type) { case SELF_TYPE_LV0: return ks; break; case SELF_TYPE_LV1: if(version <= ks->version) return ks; break; case SELF_TYPE_LV2: if(version <= ks->version) return ks; break; case SELF_TYPE_APP: if(key_revision == ks->key_revision) return ks; break; case SELF_TYPE_ISO: if(version <= ks->version && key_revision == ks->key_revision) return ks; break; case SELF_TYPE_LDR: return ks; break; case SELF_TYPE_NPDRM: if(key_revision == ks->key_revision) return ks; break; } } } return NULL; } static keyset_t *_keyset_find_for_rvk(scetool::u32 key_revision) { LIST_FOREACH(iter, _keysets) { keyset_t *ks = (keyset_t *)iter->value; if(ks->type == KEYTYPE_RVK && key_revision <= ks->key_revision) return ks; } return NULL; } static keyset_t *_keyset_find_for_pkg(scetool::u16 key_revision) { LIST_FOREACH(iter, _keysets) { keyset_t *ks = (keyset_t *)iter->value; if(ks->type == KEYTYPE_PKG && key_revision <= ks->key_revision) return ks; } return NULL; } static keyset_t *_keyset_find_for_spp(scetool::u16 key_revision) { LIST_FOREACH(iter, _keysets) { keyset_t *ks = (keyset_t *)iter->value; if(ks->type == KEYTYPE_SPP && key_revision <= ks->key_revision) return ks; } return NULL; } keyset_t *keyset_find(sce_buffer_ctxt_t *ctxt) { keyset_t *res = NULL; switch(ctxt->sceh->header_type) { case SCE_HEADER_TYPE_SELF: res = _keyset_find_for_self(ctxt->self.ai->self_type, ctxt->sceh->key_revision, ctxt->self.ai->version); break; case SCE_HEADER_TYPE_RVK: res = _keyset_find_for_rvk(ctxt->sceh->key_revision); break; case SCE_HEADER_TYPE_PKG: res = _keyset_find_for_pkg(ctxt->sceh->key_revision); break; case SCE_HEADER_TYPE_SPP: res = _keyset_find_for_spp(ctxt->sceh->key_revision); break; } if(res == NULL) printf("[*] Error: Could not find keyset for %s.\n", _get_name(_sce_header_types, ctxt->sceh->header_type)); return res; } static void _fill_property(keyset_t *ks, scetool::s8 *prop, scetool::s8 *value) { if(strcmp(prop, "type") == 0) { if(strcmp(value, "SELF") == 0) ks->type = KEYTYPE_SELF; else if(strcmp(value, "RVK") == 0) ks->type = KEYTYPE_RVK; else if(strcmp(value, "PKG") == 0) ks->type = KEYTYPE_PKG; else if(strcmp(value, "SPP") == 0) ks->type = KEYTYPE_SPP; else if(strcmp(value, "OTHER") == 0) ks->type = KEYTYPE_OTHER; else printf("[*] Error: Unknown type '%s'.\n", value); } else if(strcmp(prop, "revision") == 0) ks->key_revision = (scetool::u16)_x_to_u64(value); else if(strcmp(prop, "version") == 0) ks->version = _x_to_u64(value); else if(strcmp(prop, "self_type") == 0) { if(strcmp(value, "LV0") == 0) ks->self_type = SELF_TYPE_LV0; else if(strcmp(value, "LV1") == 0) ks->self_type = SELF_TYPE_LV1; else if(strcmp(value, "LV2") == 0) ks->self_type = SELF_TYPE_LV2; else if(strcmp(value, "APP") == 0) ks->self_type = SELF_TYPE_APP; else if(strcmp(value, "ISO") == 0) ks->self_type = SELF_TYPE_ISO; else if(strcmp(value, "LDR") == 0) ks->self_type = SELF_TYPE_LDR; else if(strcmp(value, "UNK_7") == 0) ks->self_type = SELF_TYPE_UNK_7; else if(strcmp(value, "NPDRM") == 0) ks->self_type = SELF_TYPE_NPDRM; else printf("[*] Error: unknown SELF type '%s'.\n", value); } else if(strcmp(prop, "erk") == 0 || strcmp(prop, "key") == 0) { ks->erk = _x_to_u8_buffer(value); ks->erklen = strlen(value) / 2; } else if(strcmp(prop, "riv") == 0) { ks->riv = _x_to_u8_buffer(value); ks->rivlen = strlen(value) / 2; } else if(strcmp(prop, "pub") == 0) ks->pub = _x_to_u8_buffer(value); else if(strcmp(prop, "priv") == 0) ks->priv = _x_to_u8_buffer(value); else if(strcmp(prop, "ctype") == 0) ks->ctype = (scetool::u8)_x_to_u64(value); else printf("[*] Error: Unknown keyfile property '%s'.\n", prop); } BOOL curves_load(const scetool::s8 *cfile) { scetool::u32 len = 0; _curves = (curve_t *)_read_buffer(cfile, &len); if(_curves == NULL) return FALSE; if(len != CURVES_LENGTH) { free(_curves); return FALSE; } return TRUE; } BOOL vsh_curves_load(const scetool::s8 *cfile) { scetool::u32 len = 0; _vsh_curves = (vsh_curve_t *)_read_buffer(cfile, &len); if(_vsh_curves == NULL) return FALSE; if(len != VSH_CURVES_LENGTH) { free(_vsh_curves); return FALSE; } return TRUE; } static scetool::s64 _compare_keysets(keyset_t *ks1, keyset_t *ks2) { scetool::s64 res; if((res = (scetool::s64)ks1->version - (scetool::s64)ks2->version) == 0) res = (scetool::s64)ks1->key_revision - (scetool::s64)ks2->key_revision; return res; } static void _sort_keysets() { scetool::u32 i, to = _keysets->count; lnode_t *max; list_t *tmp = list_create(); for(i = 0; i < to; i++) { max = _keysets->head; LIST_FOREACH(iter, _keysets) { if(_compare_keysets((keyset_t *)max->value, (keyset_t *)iter->value) < 0) max = iter; } list_push(tmp, max->value); list_remove_node(_keysets, max); } list_destroy(_keysets); _keysets = tmp; } #define LINEBUFSIZE 512 BOOL keys_load(const scetool::s8 *kfile) { scetool::u32 i = 0, lblen; FILE *fp; scetool::s8 lbuf[LINEBUFSIZE]; keyset_t *cks = NULL; if((_keysets = list_create()) == NULL) return FALSE; if((fp = fopen(kfile, "r")) == NULL) { list_destroy(_keysets); return FALSE; } do { //Get next line. lbuf[0] = 0; fgets(lbuf, LINEBUFSIZE, fp); lblen = strlen(lbuf); //Don't parse empty lines (ignore '\n') and comment lines (starting with '#'). if(lblen > 1 && lbuf[0] != '#') { //Remove '\n'. lbuf[lblen-1] = 0; //Check for keyset entry. if(lblen > 2 && lbuf[0] == '[') { if(cks != NULL) { //Add to keyset list. list_push(_keysets, cks); cks = NULL; } //Find name end. for(i = 0; lbuf[i] != ']' && lbuf[i] != '\n' && i < lblen; i++); lbuf[i] = 0; //Allocate keyset and fill name. cks = (keyset_t *)malloc(sizeof(keyset_t)); memset(cks, 0, sizeof(keyset_t)); cks->name = _strdup(&lbuf[1]); } else if(cks != NULL) { //Find property name end. for(i = 0; lbuf[i] != '=' && lbuf[i] != '\n' && i < lblen; i++); lbuf[i] = 0; //Fill property. _fill_property(cks, &lbuf[0], &lbuf[i+1]); } } } while(!feof(fp)); //Add last keyset to keyset list. if(cks != NULL) list_push(_keysets, cks); //Sort keysets. _sort_keysets(); return TRUE; } #undef LINEBUFSIZE bool frontend_decrypt(scetool::s8 *file_in, scetool::s8 *file_out) { scetool::u8 *buf = _read_buffer(file_in, NULL); if(buf != NULL) { sce_buffer_ctxt_t *ctxt = sce_create_ctxt_from_buffer(buf); if(ctxt != NULL) { scetool::u8 *meta_info = NULL; if(_meta_info != NULL) { if(strlen(_meta_info) != 0x40*2) { ConLog.Error("scetool: Metadata info needs to be 64 bytes.\n"); return false; } meta_info = _x_to_u8_buffer(_meta_info); } scetool::u8 *keyset = NULL; if(_keyset != NULL) { if(strlen(_keyset) != (0x20 + 0x10 + 0x15 + 0x28 + 0x01)*2) { ConLog.Error("scetool: Keyset has a wrong length"); return false; } keyset = _x_to_u8_buffer(_keyset); } if(sce_decrypt_header(ctxt, meta_info, keyset)) { if(sce_decrypt_data(ctxt)) { if(ctxt->sceh->header_type == SCE_HEADER_TYPE_SELF) { if(self_write_to_elf(ctxt, file_out) == TRUE) ConLog.Write("scetool: ELF written to %s", file_out); else ConLog.Error("scetool: Could not write ELF"); } else if(ctxt->sceh->header_type == SCE_HEADER_TYPE_RVK) { if(_write_buffer(file_out, ctxt->scebuffer + ctxt->metash[0].data_offset, ctxt->metash[0].data_size + ctxt->metash[1].data_size)) ConLog.Write("scetool: RVK written to %s", file_out); else ConLog.Error("scetool: Could not write RVK"); } else if(ctxt->sceh->header_type == SCE_HEADER_TYPE_PKG) { /*if(_write_buffer(file_out, ctxt->scebuffer + ctxt->metash[0].data_offset, ctxt->metash[0].data_size + ctxt->metash[1].data_size + ctxt->metash[2].data_size)) printf("[*] PKG written to %s.\n", file_out); else printf("[*] Error: Could not write PKG.\n");*/ ConLog.Warning("scetool: Not yet supported"); } else if(ctxt->sceh->header_type == SCE_HEADER_TYPE_SPP) { if(_write_buffer(file_out, ctxt->scebuffer + ctxt->metash[0].data_offset, ctxt->metash[0].data_size + ctxt->metash[1].data_size)) ConLog.Write("scetool: SPP written to %s", file_out); else ConLog.Error("scetool: Could not write SPP"); } } else ConLog.Error("scetool: Could not decrypt data"); } else ConLog.Error("scetool: Could not decrypt header"); free(ctxt); } else ConLog.Error("scetool: Could not process %s", file_in); free(buf); } else ConLog.Error("scetool: Could not load %s", file_in); return true; } bool scetool_decrypt(scetool::s8 *_file_in, scetool::s8 *_file_out) { scetool::s8 *ps3 = NULL, path[256]; //Try to get path from env:PS3. if((ps3 = getenv(CONFIG_ENV_PS3)) != NULL) if(_access(ps3, 0) != 0) ps3 = NULL; //Load keysets. if(ps3 != NULL) { sprintf(path, "%s/%s", ps3, CONFIG_KEYS_FILE); if(_access(path, 0) != 0) sprintf(path, "%s/%s", CONFIG_KEYS_PATH, CONFIG_KEYS_FILE); } else sprintf(path, "%s/%s", CONFIG_KEYS_PATH, CONFIG_KEYS_FILE); if(!keys_load(path)) { ConLog.Error("scetool: Could not load keys"); return false; } //Load curves. if(ps3 != NULL) { sprintf(path, "%s/%s", ps3, CONFIG_CURVES_FILE); if(_access(path, 0) != 0) sprintf(path, "%s/%s", CONFIG_CURVES_PATH, CONFIG_CURVES_FILE); } else sprintf(path, "%s/%s", CONFIG_CURVES_PATH, CONFIG_CURVES_FILE); if(!curves_load(path)) { ConLog.Error("scetool: Could not load loader curves"); return false; } //Load curves. if(ps3 != NULL) { sprintf(path, "%s/%s", ps3, CONFIG_VSH_CURVES_FILE); if(_access(path, 0) != 0) sprintf(path, "%s/%s", CONFIG_VSH_CURVES_PATH, CONFIG_VSH_CURVES_FILE); } else sprintf(path, "%s/%s", CONFIG_VSH_CURVES_PATH, CONFIG_VSH_CURVES_FILE); if(!vsh_curves_load(path)) { ConLog.Error("scetool: Could not load vsh curves"); return false; } //Set klicensee. if(_klicensee != NULL) { if(strlen(_klicensee) != 0x10*2) { ConLog.Error("scetool: klicensee needs to be 16 bytes."); return false; } np_set_klicensee(_x_to_u8_buffer(_klicensee)); } return frontend_decrypt(_file_in, _file_out);; }