From 01b7584fbaccdd895d8462669852dd49c4e14dfa Mon Sep 17 00:00:00 2001 From: Bluzume <39113159+KuromeSan@users.noreply.github.com> Date: Tue, 5 Oct 2021 01:33:59 +1300 Subject: [PATCH] Add MD5 Verification, and support for PSM Dev PSSE --- decrypt.py | 273 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 205 insertions(+), 68 deletions(-) diff --git a/decrypt.py b/decrypt.py index 30674e0..ed73777 100644 --- a/decrypt.py +++ b/decrypt.py @@ -3,22 +3,22 @@ import binascii import sys import struct import math +import hashlib + from Crypto.Cipher import AES +from cryptography.hazmat.primitives.serialization import pkcs12 +from cryptography.hazmat.primitives.asymmetric import padding + game_key = b"" content_id = b"" original_iv = b"" -def IvRoll(location): - iv_next = location - iv = bytearray(original_iv) - new_iv = bytearray(iv_next.to_bytes(0x10, "little")) - for i in range(0,0x10): - new_iv[i] ^= iv[i] - - return bytes(new_iv) - -def ReadGameKey(filename): +psm_dev = False +file_size = 0 +total_blocks = 0 + +def ReadGameKey(filename): # Read from FAKE.RIF global game_key global content_id @@ -30,36 +30,121 @@ def ReadGameKey(filename): riffd.close() - -def GetBlock(fd, blockNo): +def ReadPublisherKey(p12, khapp): # used for PSM Developer Assistant (debug PSSE) global game_key - current_iv = IvRoll(blockNo) - blockId = blockNo - blockNo = blockNo * 0x8000 + + print("Reading PRIVATE KEY from "+p12) + pkcs12_file = open(p12, 'rb').read() + passphrase = b"password" + private_key, certificate, additional_certificates = pkcs12.load_key_and_certificates(pkcs12_file, passphrase) # Parse PKCS12 file + + print("Reading: HKAPP "+khapp) + hkfd = open(khapp, "rb") + offset_by = 0 + + magic = hkfd.read(0x4) # is this an appkey or a keyring? + if magic == b"PAKR": + offset_by = 0x48 # Probably bad parsing, but whatever + elif magic == b"PSHK": + offset_by = 0 + else: + print("Invalid khapp!") + + + hkfd.seek(0x200+offset_by, 0) + enc = hkfd.read(0x100) + dec = private_key.decrypt(enc, padding.PKCS1v15()) # RSA Decrypt + + key = dec[0xC0:0xD0] + iv = dec[0xD0:0xE0] + + hkfd.seek(0x80+offset_by, 0) + ebuffer = hkfd.read(0x240) + cipher = AES.new(key, AES.MODE_CBC, iv) + dec = cipher.decrypt(ebuffer) # AES Decrypt + + # Dont know what these are for. + something1 = dec[0x00:0x10] + something2 = dec[0xA0:0xB0] + + game_key = dec[0x60:0x70] + + hkfd.close() + + print("Game Key read from Publisher License Files: "+binascii.hexlify(game_key).decode("UTF-8")) + +def IvRoll(location): + iv_next = location + iv = bytearray(original_iv) + new_iv = bytearray(iv_next.to_bytes(0x10, "little")) + for i in range(0,0x10): + new_iv[i] ^= iv[i] + + return bytes(new_iv) + + +def GetBlock(fd, block_id): + global game_key + global file_size + global total_blocks + current_iv = IvRoll(block_id) + block_loc = block_id * 0x8000 total_read = 0x8000 - if blockId == 0: # Skip to filedata - blockNo = 0x680 - total_read -= 0x680 - elif blockNo % 0x80000 == 0: # Skip signature block - blockNo += 0x400 + trim_to = total_read + + + if block_id == 0: # Skip to filedata + block_loc = 0x680 + total_read -= 0x680 + trim_to = total_read + elif block_loc % 0x80000 == 0: # Skip signature block + block_loc += 0x400 total_read -= 0x400 - fd.seek(blockNo, 0) + trim_to = total_read + + rd_amt = ((block_loc - 0x680) - (0x400*math.floor(block_loc / 0x80000))) # Total amount of file read so far. + + if block_id >= total_blocks-1: # Is this the last block? + total_read = file_size - rd_amt + trim_to = total_read + total_read += (0x10-(total_read % 0x10)) + + if block_id > total_blocks-1: + return b"" + if total_read <= 0: + return b"" + + fd.seek(block_loc, 0) file_data = fd.read(total_read) cipher = AES.new(game_key, AES.MODE_CBC, current_iv) - return cipher.decrypt(file_data) + return cipher.decrypt(file_data)[:trim_to] + def DecryptFile(input_filename): global game_key global content_id global original_iv + global file_size + global total_blocks + global psm_dev + + iv = b"\x00\x01\x02\x03\04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" # Header IV, used to find the real IV + key = b"\x4E\x29\x8B\x40\xF5\x31\xF4\x69\xD2\x1F\x75\xB1\x33\xC3\x07\xBE" # Header key for PSM Runtime and used in all retail games. + psm_dev_key = b"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF" # Header key for PSM Dev, used for all files encrypted with psm_encryptor.dll + runtime_game_key = b"\xA8\x69\x3C\x4D\xF0\xAE\xED\xBC\x9A\xBF\xD8\x21\x36\x92\x91\x2D" # key for Sce.PlayStation.Core.dll + + total_blocks = math.floor(os.path.getsize(input_filename) / 0x8000)+1 + fd = open(input_filename, "rb") + # Check magic is "PSSE" or "PSME" header = fd.read(4) if header != b"PSSE" and header != b"PSME": print("Not a valid PSSE File") exit() - + + # Check PSSE Version is 0x1 (not sure what it would be otherwise) fd.seek(0x4, 0) version = struct.unpack('i', fd.read(4))[0] if version != 0x01: @@ -67,83 +152,135 @@ def DecryptFile(input_filename): exit() fd.seek(0x8, 0) - size = struct.unpack('i', fd.read(4))[0] - totalBlocks = math.ceil(size/0x8000)+1 + file_size = struct.unpack('Q', fd.read(8))[0] + + fd.seek(0x10, 0) + psse_type = struct.unpack('i', fd.read(4))[0] + if psse_type != 0x1: + print("Unknown PSSE Type: "+str(psse_type)) + exit() fd.seek(0x14, 0) read_content_id = fd.read(0x24) - if read_content_id == b"IP9100-NPXS10074_00-0000000000000000": #Runtime Files - game_key = b"\xA8\x69\x3C\x4D\xF0\xAE\xED\xBC\x9A\xBF\xD8\x21\x36\x92\x91\x2D" + if read_content_id == b"IP9100-NPXS10074_00-0000000000000000": # Sce.PlayStation.Core.dll + game_key = runtime_game_key + elif read_content_id == (b"\x00"*0x24): # PSM developer assistant doenst include a Content ID + psm_dev = True elif content_id != read_content_id: print("Content ID Mismatch! Expected: "+content_id.decode("UTF-8")+" but got"+read_content_id.decode("UTF-8")) - - #not sure what this part is for + if game_key == b"": + print("Unknown game key! if its debug PSSE, please provide publisher key (p12) and app seed (krng/hkapp) as seperate arguments") + exit() + + fd.seek(0x40, 0) + file_md5 = fd.read(0x10) + + # Supposadly this contains the filename. #fd.seek(0x50, 0) #enc1 = fd.read(0x20) - - #iv - fd.seek(0x70, 0) - enc2 = fd.read(0x10) - - iv = b"\x00\x01\x02\x03\04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" - key = b"\x4E\x29\x8B\x40\xF5\x31\xF4\x69\xD2\x1F\x75\xB1\x33\xC3\x07\xBE" #cipher = AES.new(key, AES.MODE_CBC, iv) #dec1 = cipher.decrypt(enc1) - cipher = AES.new(key, AES.MODE_CBC, iv) - original_iv = cipher.decrypt(enc2) - total_file_data = b"" - for i in range(0,totalBlocks): - total_file_data += GetBlock(fd, i) + # Determine file IV + fd.seek(0x70, 0) + enc = fd.read(0x10) + + if psm_dev: + key = psm_dev_key + cipher = AES.new(key, AES.MODE_CBC, iv) + original_iv = cipher.decrypt(enc) + + md5 = hashlib.md5() + total_file_data = b"" + + for i in range(0, total_blocks): + file_block = GetBlock(fd, i) + md5.update(file_block) + total_file_data += file_block + fd.close() - - return total_file_data[:size] # trim to sz + total_file_data = total_file_data[:file_size] # trim to file_size + + got_md5 = md5.digest() + if file_md5 != got_md5: # Verify decrypted file. + print("MD5 Mismatch, Expected: "+binascii.hexlify(file_md5).decode("UTF-8")+" got: "+binascii.hexlify(got_md5).decode("UTF-8")) + exit() + + return total_file_data if len(sys.argv) <= 1: - print("PSSE Decryptor by SilicaAndPina") - print("Usage: decrypt.py ") - print("Where the game folder contains \"RO/License/FAKE.RIF\" and \"RO/Application/psse.list\"") + print("PSSE Decryptor by SilicaAndPina v2") + print("(for retail games) ") + print("(for PSM Dev Packages) ") + print("") + print("For retail games, you require the RO/License/FAKE.RIF file") + print("And for PSM Dev Packages, you need there original publisher key and keyring.") + print("") + print("This program expects the game files to be located in PSM_GAME_FOLDER/RO/Application") + print("(just PSM_GAME_FOLDER/Application for PSM Developer Packages)") exit() file = sys.argv[1] +if len(sys.argv) >= 4: + psm_dev = True + pkcs12name = sys.argv[2] + khappname = sys.argv[3] + print("Reading Publisher Key and App Key") + + if not os.path.exists(pkcs12name): + print("Publisher Key not found") + exit() + + if not os.path.exists(khappname): + print("App key file not found.") + exit() + + ReadPublisherKey(pkcs12name, khappname) + if os.path.isfile(file): print("Decrypting: "+file) data = DecryptFile(file) open(file, "wb").write(data) exit() - -licenseFile = os.path.normpath(file+"/RO/License/FAKE.RIF") -if not os.path.exists(licenseFile): - print("Cannot find license "+licenseFile) - exit() - -ReadGameKey(licenseFile) -print("Reading Game key from FAKE.RIF: "+binascii.hexlify(game_key).decode("UTF-8")) -psseList = os.path.normpath(file+"/RO/Application/psse.list") -if not os.path.exists(psseList): - print("Cannot find "+psseList) +ro_path = b"/RO/Application/" +if psm_dev: # I guess for android this would also be true. + ro_path = b"/Application/" + +if not psm_dev: # When using PSM Dev, there are no licenses, instead keys are stored in krng or khapp file... + license_file = os.path.normpath(file+"/RO/License/FAKE.RIF") + if not os.path.exists(license_file): + print("Cannot find license "+license_file) + exit() + + ReadGameKey(license_file) + print("Reading Game key from FAKE.RIF: "+binascii.hexlify(game_key).decode("UTF-8")) + +psse_list = os.path.normpath(file.encode("UTF-8")+ro_path+b"psse.list") + +if not os.path.exists(psse_list): + print("Cannot find "+psse_list.decode("UTF-8")) exit() -FileData = DecryptFile(psseList) -FilesList = FileData.replace(b"\r", b"").split(b"\n") +file_data = DecryptFile(psse_list) +file_list = file_data.replace(b"\r", b"").split(b"\n") -for File in FilesList: - if File == b"": +for psse_file in file_list: + if psse_file == b"": continue path = file.encode("UTF-8") - FilePath = os.path.normpath(path+b"/RO/Application/"+File) - print((b"Decrypting: "+FilePath).decode("UTF-8")) - if os.path.exists(FilePath): - DecryptedData = DecryptFile(FilePath) - open(FilePath, "wb").write(DecryptedData) + file_path = os.path.normpath(path+ro_path+psse_file) + print((b"Decrypting: "+file_path).decode("UTF-8")) + if os.path.exists(file_path): + decrypted_data = DecryptFile(file_path) + open(file_path, "wb").write(decrypted_data) else: print("Error: File not Found") -open(psseList, "wb").write(FileData) -print("Done") +open(psse_list, "wb").write(file_data) +print("Done") \ No newline at end of file