#!/usr/bin/env python ''' PS4 Kernel Loader by SocraticBliss (R) Major Thanks to... # aerosoul # balika011 # Znullptr # Pablo (kozarovv) # ChendoChap # xyz # CelesteBlue # kiwidogg # motoharu # noname120 # flatz # Team Reswitched Extra Special Thanks for telling me my program sucks... # zecoxao ps4_kernel.py: IDA loader for reading Sony PlayStation(R) 4 Kernel files ''' from idaapi import * from idc import * import idaapi import idc import operator import struct class Binary: __slots__ = ('EI_MAGIC', 'EI_CLASS', 'EI_DATA', 'EI_VERSION', 'EI_OSABI', 'EI_PADDING', 'EI_ABIVERSION', 'EI_SIZE', 'E_TYPE', 'E_MACHINE', 'E_VERSION', 'E_START_ADDR', 'E_PHT_OFFSET', 'E_SHT_OFFSET', 'E_FLAGS', 'E_SIZE', 'E_PHT_SIZE', 'E_PHT_COUNT', 'E_SHT_SIZE', 'E_SHT_COUNT', 'E_SHT_INDEX', 'E_SEGMENTS', 'E_SECTIONS') # Elf Types ET_NONE = 0x0 ET_REL = 0x1 ET_EXEC = 0x2 ET_DYN = 0x3 ET_CORE = 0x4 ET_SCE_EXEC = 0xfe00 ET_SCE_REPLAY_EXEC = 0xfe01 ET_SCE_RELEXEC = 0xfe04 ET_SCE_STUBLIB = 0xfe0c ET_SCE_DYNEXEC = 0xfe10 ET_SCE_DYNAMIC = 0xfe18 ET_LOPROC = 0xff00 ET_HIPROC = 0xffff # Elf Architecture EM_X86_64 = 0x3E def __init__(self, f): f.seek(0) self.EI_MAGIC = struct.unpack('4s', f.read(4))[0] self.EI_CLASS = struct.unpack(' 7: return idc.get_struc_id(name) entry = idc.add_struc(BADADDR, name, False) for (member, comment, size) in members: flags = idaapi.get_flags_by_size(size) if member in ['function', 'offset', 'd_name']: idc.add_struc_member(entry, member, location, flags + FF_0OFF, BADADDR, size, BADADDR, 0, REF_OFF64) else: idc.add_struc_member(entry, member, location, flags, BADADDR, size) idc.set_member_cmt(entry, location, comment, False) location += size return entry def type(self): return { Segment.PT_LOAD : 'CODE' if self.flags() == (SEGPERM_EXEC | SEGPERM_READ) else 'DATA', Segment.PT_DYNAMIC : 'DATA', Segment.PT_INTERP : 'CONST', Segment.PT_NOTE : 'CONST', Segment.PT_PHDR : 'CONST', Segment.PT_TLS : 'BSS', Segment.PT_SCE_DYNLIBDATA : 'CONST', Segment.PT_SCE_PROCPARAM : 'DATA', Segment.PT_SCE_MODULEPARAM : 'CONST', Segment.PT_SCE_RELRO : 'DATA', Segment.PT_GNU_EH_FRAME : 'CONST', Segment.PT_SCE_COMMENT : 'CONST', Segment.PT_SCE_LIBVERSION : 'CONST', }.get(self.TYPE, 'UNK') class Section: __slots__ = ('NAME', 'TYPE', 'FLAGS', 'MEM_ADDR', 'OFFSET', 'FILE_SIZE', 'LINK', 'INFO', 'ALIGNMENT', 'FSE_SIZE') def __init__(self, f): self.NAME = struct.unpack('> 48 self.VERSION_MINOR = (self.VALUE >> 40) & 0xF self.VERSION_MAJOR = (self.VALUE >> 32) & 0xF self.INDEX = self.VALUE & 0xFFF if self.TAG in [Dynamic.DT_SCE_NEEDED_MODULE, Dynamic.DT_SCE_MODULE_INFO]: if self.INDEX not in modules: modules[self.INDEX] = 0 return '%s | %#x | MID:%#x Version:%i.%i | %#x' % \ (self.tag(), self.VALUE, self.ID, self.VERSION_MAJOR, self.VERSION_MINOR, self.INDEX) elif self.TAG in [Dynamic.DT_SCE_IMPORT_LIB, Dynamic.DT_SCE_EXPORT_LIB]: if self.INDEX not in modules: modules[self.INDEX] = 0 return '%s | %#x | LID:%#x Version:%i | %#x' % \ (self.tag(), self.VALUE, self.ID, self.VERSION_MAJOR, self.INDEX) elif self.TAG == Dynamic.DT_SCE_MODULE_ATTR: return '%s | %#x | %s' % (self.tag(), self.VALUE, self.mod_attribute()) elif self.TAG in [Dynamic.DT_SCE_IMPORT_LIB_ATTR, Dynamic.DT_SCE_EXPORT_LIB_ATTR]: return '%s | %#x | LID:%#x Attributes:%s' % \ (self.tag(), self.VALUE, self.ID, self.lib_attribute()) elif self.TAG == Dynamic.DT_SCE_ORIGINAL_FILENAME: stubs[self.INDEX] = 0 return '%s | %#x' % (self.tag(), self.VALUE) class Relocation: __slots__ = ('OFFSET', 'INDEX', 'INFO', 'ADDEND', 'RELSTR') # PS4 (X86_64) Relocation Codes (40) (R_X86_64_NONE, R_X86_64_64, R_X86_64_PC32, R_X86_64_GOT32, R_X86_64_PLT32, R_X86_64_COPY, R_X86_64_GLOB_DAT, R_X86_64_JUMP_SLOT, R_X86_64_RELATIVE, R_X86_64_GOTPCREL, R_X86_64_32, R_X86_64_32S, R_X86_64_16, R_X86_64_PC16, R_X86_64_8, R_X86_64_PC8, R_X86_64_DTPMOD64, R_X86_64_DTPOFF64, R_X86_64_TPOFF64, R_X86_64_TLSGD, R_X86_64_TLSLD, R_X86_64_DTPOFF32, R_X86_64_GOTTPOFF, R_X86_64_TPOFF32, R_X86_64_PC64, R_X86_64_GOTOFF64, R_X86_64_GOTPC32, R_X86_64_GOT64, R_X86_64_GOTPCREL64, R_X86_64_GOTPC64, R_X86_64_GOTPLT64, R_X86_64_PLTOFF64, R_X86_64_SIZE32, R_X86_64_SIZE64, R_X86_64_GOTPC32_TLSDESC, R_X86_64_TLSDESC_CALL, R_X86_64_TLSDESC, R_X86_64_IRELATIVE, R_X86_64_RELATIVE64) = range(0x27) R_X86_64_ORBIS_GOTPCREL_LOAD = 0x28 def __init__(self, f): self.OFFSET = struct.unpack(' 0: idc.set_name(self.VALUE, function, SN_NOCHECK | SN_NOWARN | SN_FORCE) # PROGRAM START # Open File Dialog... def accept_file(f, n): ps4 = Binary(f) # Non-Symbol Kernels if ps4.E_START_ADDR > 0xFFFFFFFF82200000 and ps4.E_SEGMENTS[0].FILE_SIZE != 0x118: return { 'format': 'PS4 - Kernel', 'options': ACCEPT_FIRST } return 0 # Chendo's cdevsw con-struct-or def chendo(address, end, search, struct): while address < end: address = idaapi.find_binary(address, end, search, 0x10, SEARCH_DOWN) idaapi.del_items(address, 0xB0, 0) idaapi.create_struct(address, 0xB0, struct) address += 0x8 # Kiwidog's __stack_chk_fail def kiwidog(address, end, search): magic = idaapi.find_binary(address, end, search, 0x0, SEARCH_DOWN) function = idaapi.get_func(idaapi.get_first_dref_to(magic)) idaapi.set_name(function.start_ea, '__stack_chk_fail', SN_NOCHECK | SN_NOWARN | SN_FORCE) function.flags |= FUNC_NORET idaapi.update_func(function) # Pablo's IDC def pablo(mode, address, end, search): while address < end: address = idaapi.find_binary(address, end, search, 0x10, SEARCH_DOWN) if address > idaapi.get_segm_by_name('CODE').end_ea: offset = address - 0x3 if idaapi.is_unknown(idaapi.getFlags(offset)): if idaapi.get_qword(offset) <= end: idaapi.create_data(offset, FF_QWORD, 0x8, BADNODE) address = offset + 4 else: address += mode idaapi.del_items(address, 0) idaapi.create_insn(address) idaapi.add_func(address, BADADDR) address += 1 # Znullptr's Syscalls def znullptr(address, end, search, struct): magic = idaapi.find_binary(address, end, search, 0x10, idc.SEARCH_DOWN) pattern = '%02X %02X %02X %02X FF FF FF FF' % (magic & 0xFF, ((magic >> 0x8) & 0xFF), ((magic >> 0x10) & 0xFF), ((magic >> 0x18) & 0xFF)) sysvec = idaapi.find_binary(address, cvar.inf.max_ea, pattern, 0x10, idc.SEARCH_UP) - 0x60 idaapi.set_name(sysvec, 'sysentvec', SN_NOCHECK | SN_NOWARN | SN_FORCE) sysent = idaapi.get_qword(sysvec + 0x8) idaapi.set_name(sysent, 'sv_table', SN_NOCHECK | SN_NOWARN | SN_FORCE) sysnames = idaapi.get_qword(sysvec + 0xD0) idaapi.set_name(sysnames, 'sv_syscallnames', SN_NOCHECK | SN_NOWARN | SN_FORCE) # Get the list of syscalls offset = idaapi.find_binary(address, cvar.inf.max_ea, '73 79 73 63 61 6C 6C 00 65 78 69 74 00', 0x10, SEARCH_DOWN) numsyscalls = idaapi.get_qword(sysvec) for entry in range(numsyscalls): initial = sysnames + (entry * 0x8) idc.create_data(initial, FF_QWORD, 0x8, BADNODE) offset = idaapi.get_qword(initial) length = idaapi.get_max_strlit_length(offset, STRTYPE_C) name = idaapi.get_strlit_contents(offset, length, STRTYPE_C).decode() # Skip if 'obs_{' in name: name = '%i' % entry elif '#' in name: name = name.split('#')[1] elif '.' in name: name = name.split('.')[1] sysentoffset = sysent + 0x8 + (entry * 0x30) idaapi.del_items(sysentoffset - 0x8, 0x30, 0) idaapi.create_struct(sysentoffset - 0x8, 0x30, struct) idc.set_cmt(sysentoffset - 0x8, '#%i' % entry, False) # Rename the functions function = idaapi.get_qword(sysentoffset) idaapi.set_name(function, 'sys_' + name, SN_NOCHECK | SN_NOWARN | SN_FORCE) idc.apply_type(function, idc.parse_decl('__int64 sys_%s(struct thread* td, struct default_uap* uap);' % name, 0), TINFO_DEFINITE) # Load Input Binary... def load_file(f, neflags, format): print('# PS4 Kernel Loader') ps4 = Binary(f) # PS4 Processor, Compiler, Library bitness = ps4.procomp('metapc', CM_N64 | CM_M_NN | CM_CC_FASTCALL, 'gnulnx_x64') # Segment Loading... for segm in ps4.E_SEGMENTS: # Process Loadable Segments... if segm.name() in ['CODE', 'DATA', 'SCE_RELRO']: address = segm.MEM_ADDR size = segm.MEM_SIZE # Dumped Kernel Fix-ups if segm.name() in ['DATA', 'SCE_RELRO'] and idaapi.get_segm_by_name('CODE').start_ea != 0xFFFFFFFF82200000: offset = address - idaapi.get_segm_by_name('CODE').start_ea dumped = segm.MEM_SIZE else: offset = segm.OFFSET dumped = segm.FILE_SIZE print('# Creating %s Segment...' % segm.name()) f.file2base(offset, address, address + dumped, FILEREG_PATCHABLE) idaapi.add_segm(0, address, address + size, segm.name(), segm.type(), ADDSEG_NOTRUNC | ADDSEG_FILLGAP) # Processor Specific Segment Details idc.set_segm_addressing(address, bitness) idc.set_segm_alignment(address, segm.alignment()) idc.set_segm_attr(address, SEGATTR_PERM, segm.flags()) # Process Dynamic Segment... elif segm.name() == 'DYNAMIC': code = idaapi.get_segm_by_name('CODE') data = idaapi.get_segm_by_name('DATA') relro = idaapi.get_segm_by_name('SCE_RELRO') # ------------------------------------------------------------------------------------------------------------ # Dynamic Tag Entry Structure members = [('tag', 'Tag', 0x8), ('value', 'Value', 0x8)] struct = segm.struct('Tag', members) # Dynamic Tag Table stubs = {} modules = {} location = segm.MEM_ADDR # Dumps are offset by a small amount if code.start_ea != 0xFFFFFFFF82200000: dumped = code.start_ea - 0xFFFFFFFF82200000 else: dumped = 0 f.seek(location - code.start_ea) for entry in range(int(segm.MEM_SIZE / 0x10)): idaapi.create_struct(location + (entry * 0x10), 0x10, struct) idc.set_cmt(location + (entry * 0x10), Dynamic(f).process(dumped, stubs, modules), False) # ------------------------------------------------------------------------------------------------------------ # Hash Entry Structure members = [('bucket', 'Bucket', 0x2), ('chain', 'Chain', 0x2), ('buckets', 'Buckets', 0x2), ('chains', 'Chains', 0x2)] struct = segm.struct('Hash', members) # Hash Table try: location = Dynamic.HASHTAB size = Dynamic.HASHTABSZ except: location = Dynamic.HASH size = Dynamic.SYMTAB - location f.seek(location - code.start_ea) for entry in range(int(size / 0x8)): idaapi.create_struct(location + (entry * 0x8), 0x8, struct) # -------------------------------------------------------------------------------------------------------- # Relocation Entry Structure (with specific addends) members = [('offset', 'Offset (String Index)', 0x8), ('info', 'Info (Symbol Index : Relocation Code)', 0x8), ('addend', 'AddEnd', 0x8)] struct = segm.struct('Relocation', members) # Relocation Table (with specific addends) location = Dynamic.RELATAB f.seek(location - code.start_ea) for entry in range(int(Dynamic.RELATABSZ / 0x18)): idaapi.create_struct(location + (entry * 0x18), 0x18, struct) idc.set_cmt(location + (entry * 0x18), Relocation(f).process(dumped, code.end_ea), False) # Initialization Function idc.add_entry(Dynamic.INIT, Dynamic.INIT, '.init', True) address = code.start_ea # ELF Header Structure members = [('File format', 0x4), ('File class', 0x1), ('Data encoding', 0x1), ('File version', 0x1), ('OS/ABI', 0x1), ('ABI version', 0x1), ('Padding', 0x7), ('File type', 0x2), ('Machine', 0x2), ('File version', 0x4), ('Entry point', 0x8), ('PHT file offset', 0x8), ('SHT file offset', 0x8), ('Processor-specific flags', 0x4), ('ELF header size', 0x2), ('PHT entry size', 0x2), ('Number of entries in PHT', 0x2), ('SHT entry size', 0x2), ('Number of entries in SHT', 0x2), ('SHT entry index for string table\n', 0x2)] for (comment, size) in members: flags = idaapi.get_flags_by_size(size) idc.create_data(address, flags if flags != 0 else FF_STRLIT, size, BADNODE) idc.set_cmt(address, comment, False) address += size for index, entry in enumerate(ps4.E_SEGMENTS): # ELF Program Header Structure members = [('Type: %s' % entry.name(), 0x4), ('Flags', 0x4), ('File offset', 0x8), ('Virtual address', 0x8), ('Physical address', 0x8), ('Size in file image', 0x8), ('Size in memory image', 0x8), ('Alignment\n', 0x8)] for (comment, size) in members: flags = idaapi.get_flags_by_size(size) idc.create_data(address, flags if flags != 0 else FF_STRLIT, size, BADNODE) idc.set_cmt(address, comment, False) address += size # Start Function idc.add_entry(ps4.E_START_ADDR, ps4.E_START_ADDR, 'start', True) # Xfast_syscall address = idaapi.find_binary(code.start_ea, code.end_ea, '0F 01 F8 65 48 89 24 25 A8 02 00 00 65 48 8B 24', 0x10, SEARCH_DOWN) idaapi.del_items(address, 0) idaapi.create_insn(address) idaapi.add_func(address, BADADDR) idaapi.set_name(address, 'Xfast_syscall', SN_NOCHECK | SN_NOWARN | SN_FORCE) # -------------------------------------------------------------------------------------------------------- # Znullptr's syscalls print('# Processing Znullptr\'s Syscalls...') # Syscall Entry Structure members = [('narg', 'Number of Arguments', 0x4), ('_pad', 'Padding', 0x4), ('function', 'Function', 0x8), ('auevent', 'Augmented Event?', 0x2), ('_pad1', 'Padding', 0x2), ('_pad2', 'Padding', 0x4), ('trace_args_func', 'Trace Arguments Function', 0x8), ('entry', 'Entry', 0x4), ('return', 'Return', 0x4), ('flags', 'Flags', 0x4), ('thrcnt', 'Thread Count?', 0x4)] struct = segm.struct('Syscall', members) znullptr(code.start_ea, code.end_ea, '4F 52 42 49 53 20 6B 65 72 6E 65 6C 20 53 45 4C 46', struct) # -------------------------------------------------------------------------------------------------------- # Chendo's cdevsw con-struct-or print('# Processing Chendo\'s Structures...') # cdevsw Entry Structure members = [('d_version', 'Version', 0x4), ('d_flags', 'Flags', 0x4), ('d_name', 'Name', 0x8), ('d_open', 'Open', 0x8), ('d_fdopen', 'File Descriptor Open', 0x8), ('d_close', 'Close', 0x8), ('d_read', 'Read', 0x8), ('d_write', 'Write', 0x8), ('d_ioctl', 'Input/Ouput Control', 0x8), ('d_poll', 'Poll', 0x8), ('d_mmap', 'Memory Mapping', 0x8), ('d_strategy', 'Strategy', 0x8), ('d_dump', 'Dump', 0x8), ('d_kqfilter', 'KQFilter', 0x8), ('d_purge', 'Purge', 0x8), ('d_mmap_single', 'Single Memory Mapping', 0x8), ('d_spare0', 'Spare0', 0x8), ('d_spare1', 'Spare1', 0x8), ('d_spare2', 'Spare2', 0x8), ('d_spare3', 'Spare3', 0x8), ('d_spare4', 'Spare4', 0x8), ('d_spare5', 'Spare5', 0x8), ('d_spare6', 'Spare6', 0x4), ('d_spare7', 'Spare7', 0x4)] struct = segm.struct('cdevsw', members) chendo(data.start_ea, data.end_ea, '09 20 12 17', struct) # -------------------------------------------------------------------------------------------------------- # Pablo's IDC try: print('# Processing Pablo\'s Push IDC...') # Script 1) Push it real good... # Default patterns set pablo(0, code.start_ea, 0x10, '55 48 89') pablo(2, code.start_ea, code.end_ea, '90 90 55 48 ??') pablo(2, code.start_ea, code.end_ea, 'C3 90 55 48 ??') pablo(2, code.start_ea, code.end_ea, '66 90 55 48 ??') pablo(2, code.start_ea, code.end_ea, 'C9 C3 55 48 ??') pablo(2, code.start_ea, code.end_ea, '0F 0B 55 48 ??') pablo(2, code.start_ea, code.end_ea, 'EB ?? 55 48 ??') pablo(2, code.start_ea, code.end_ea, '5D C3 55 48 ??') pablo(2, code.start_ea, code.end_ea, '5B C3 55 48 ??') pablo(2, code.start_ea, code.end_ea, '90 90 55 41 ?? 41 ??') pablo(2, code.start_ea, code.end_ea, '66 90 48 81 EC ?? 00 00 00') pablo(2, code.start_ea, code.end_ea, '0F 0B 48 89 9D ?? ?? FF FF 49 89') pablo(2, code.start_ea, code.end_ea, '90 90 53 4C 8B 54 24 20') pablo(2, code.start_ea, code.end_ea, '90 90 55 41 56 53') pablo(2, code.start_ea, code.end_ea, '90 90 53 48 89') pablo(2, code.start_ea, code.end_ea, '90 90 41 ?? 41 ??') pablo(3, code.start_ea, code.end_ea, '0F 0B 90 55 48 ??') pablo(3, code.start_ea, code.end_ea, 'EB ?? 90 55 48 ??') pablo(3, code.start_ea, code.end_ea, '41 5F C3 55 48 ??') pablo(3, code.start_ea, code.end_ea, '41 5C C3 55 48 ??') pablo(3, code.start_ea, code.end_ea, '31 C0 C3 55 48 ??') pablo(3, code.start_ea, code.end_ea, '41 5D C3 55 48 ??') pablo(3, code.start_ea, code.end_ea, '41 5E C3 55 48 ??') pablo(3, code.start_ea, code.end_ea, '66 66 90 55 48 ??') pablo(3, code.start_ea, code.end_ea, '0F 1F 00 55 48 ??') pablo(3, code.start_ea, code.end_ea, '41 ?? C3 53 48') pablo(3, code.start_ea, code.end_ea, '0F 1F 00 48 81 EC ?? 00 00 00') pablo(4, code.start_ea, code.end_ea, '0F 1F 40 00 55 48 ??') pablo(4, code.start_ea, code.end_ea, '0F 1F 40 00 48 81 EC ?? 00 00 00') pablo(5, code.start_ea, code.end_ea, 'E9 ?? ?? ?? ?? 55 48 ??') pablo(5, code.start_ea, code.end_ea, 'E8 ?? ?? ?? ?? 55 48 ??') pablo(5, code.start_ea, code.end_ea, '48 83 C4 ?? C3 55 48 ??') pablo(5, code.start_ea, code.end_ea, '0F 1F 44 00 00 55 48 ??') pablo(5, code.start_ea, code.end_ea, '0F 1F 44 00 00 48 81 EC ?? 00 00 00') pablo(6, code.start_ea, code.end_ea, 'E9 ?? ?? ?? ?? 90 55 48 ??') pablo(6, code.start_ea, code.end_ea, 'E8 ?? ?? ?? ?? 90 55 48 ??') pablo(6, code.start_ea, code.end_ea, '66 0F 1F 44 00 00 55 48 ??') pablo(7, code.start_ea, code.end_ea, '0F 1F 80 00 00 00 00 55 48 ??') pablo(8, code.start_ea, code.end_ea, '0F 1F 84 00 00 00 00 00 55 48 ??') pablo(8, code.start_ea, code.end_ea, 'C3 0F 1F 80 00 00 00 00 48') pablo(8, code.start_ea, code.end_ea, '0F 1F 84 00 00 00 00 00 53 48 83 EC') # Special cases patterns set pablo(13, code.start_ea, code.end_ea, 'C3 90 90 90 90 90 90 90 90 90 90 90 90 48') pablo(13, code.start_ea, code.end_ea, 'C3 90 90 90 90 90 90 90 90 90 90 90 90 55') pablo(17, code.start_ea, code.end_ea, 'E9 ?? ?? ?? ?? 90 90 90 90 90 90 90 90 90 90 90 90 48') pablo(19, code.start_ea, code.end_ea, 'E9 ?? ?? ?? ?? 90 90 90 90 90 90 90 90 90 90 90 90 90 90 48') pablo(19, code.start_ea, code.end_ea, 'E8 ?? ?? ?? ?? 90 90 90 90 90 90 90 90 90 90 90 90 90 90 48') pablo(20, code.start_ea, code.end_ea, 'E9 ?? ?? ?? ?? 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 48') pablo(20, code.start_ea, code.end_ea, 'E9 ?? ?? ?? ?? 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 48') # Script 2) Fix-up Dumped Data Pointers... if dumped: print('# Processing Pablo\'s Dumped Data Pointers IDC...') pablo(0, data.start_ea, data.end_ea, '?? FF FF FF FF') except: pass # -------------------------------------------------------------------------------------------------------- # Kiwidog's __stack_chk_fail print('# Processing Kiwidog\'s Stack Functions...') kiwidog(code.start_ea, code.end_ea, '73 74 61 63 6B 20 6F 76 65 72 66 6C 6F 77 20 64 65 74 65 63 74 65 64 3B') print('# Done!') return 1 # PROGRAM END