Skip to content

Instantly share code, notes, and snippets.

@iMoD1998
Last active April 3, 2023 17:13
Show Gist options
  • Save iMoD1998/09e160dce858fcad899251b676c32e2b to your computer and use it in GitHub Desktop.
Save iMoD1998/09e160dce858fcad899251b676c32e2b to your computer and use it in GitHub Desktop.
IDA PS3 LV2 Dump Analyzer
import idautils
import ida_bytes
import ida_funcs
import ida_ida
import ida_kernwin
import ida_search
import ida_idp
import idaapi
import idc
import pprint
import json
#
# PPC ABI states that TOC register points to 32KB (0x8000) after the first TOC entry
# to allow for 64KB range in a single load.
#
# http://cdn.openpowerfoundation.org/wp-content/uploads/resources/leabi/content/dbdoclet.50655241_66700.html
# https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html
#
PPC_TOC_START_OFFSET = 0x8000
PPC_OPD64_OFF_FUNC = 0x00
PPC_OPD64_OFF_TOC = 0x08
PPC_OPD64_OFF_ENV = 0x10
PPC_OPD64_SIZE = 0x18
class PPC64OPDEntry:
def __init__(self, address):
self.address = address
self.function_address = ida_bytes.get_qword( address + PPC_OPD64_OFF_FUNC )
self.toc_address = ida_bytes.get_qword( address + PPC_OPD64_OFF_TOC )
self.env_address = ida_bytes.get_qword( address + PPC_OPD64_OFF_ENV )
def __str__(self):
return "%X { %X %X %X }" % ( self.address, self.function_address, self.toc_address, self.env_address )
#
# Returns OPD entry object for address.
#
def ppc_get_opd(address):
return PPC64OPDEntry(address)
#
# Adds structurs such as OPDEntry.
#
def ppc_add_types():
OPDTypeTID = idc.add_struc( ida_idaapi.BADADDR, "OPDEntry", False )
OPDTypeSPTR = ida_struct.get_struc_id( "OPDEntry" )
idc.add_struc_member( OPDTypeSPTR, "Function", 0x00, ida_bytes.FF_QWORD | ida_bytes.FF_DATA | ida_bytes.FF_0OFF, 0, 8 )
idc.add_struc_member( OPDTypeSPTR, "TOC", 0x08, ida_bytes.FF_QWORD | ida_bytes.FF_DATA | ida_bytes.FF_0OFF, 0, 8 )
idc.add_struc_member( OPDTypeSPTR, "Enviroment", 0x10, ida_bytes.FF_QWORD | ida_bytes.FF_DATA | ida_bytes.FF_0OFF, 0, 8 )
LV2_BASE_ADDR = 0x8000000000000000
LV2_KERN_INFO = LV2_BASE_ADDR + 0x3000
def lv2_read_toc():
return ida_bytes.get_qword( LV2_KERN_INFO )
#
# Returns the address of beginning of the .toc segment.
#
def lv2_get_toc_start():
return lv2_read_toc() - PPC_TOC_START_OFFSET
#
# Returns the address of the end of the OPD segment.
#
def lv2_get_opd_seg_end():
'''
TOC segment starts directly after OPD segment.
'''
return lv2_get_toc_start()
#
# Returns the address of the start of the OPD segment.
#
def lv2_get_opd_seg_start():
return lv2_get_opd_first()
#
# Returns the address of the last OPD entry + 1.
#
def lv2_get_opd_last():
'''
TOC segment starts directly after last OPD entry and aligned by 0x10 (16).
'''
last_entry = lv2_get_opd_seg_end()
'''
OPD segment ending address might be alignment padded by 10.
'''
if ppc_get_opd( last_entry - 0x18 ).toc_address != lv2_read_toc():
last_entry -= 8
return last_entry
#
# Returns the address of the first OPD entry.
#
def lv2_get_opd_first():
current_entry_address = lv2_get_opd_last() - PPC_OPD64_SIZE
current_entry = ppc_get_opd( current_entry_address )
while current_entry.toc_address == lv2_read_toc():
current_entry_address -= PPC_OPD64_SIZE
current_entry = ppc_get_opd( current_entry_address )
return current_entry_address + PPC_OPD64_SIZE
#
# Lables each interrupt handler.
#
def lv2_make_interrupt_handlers():
#
# Interrupt handlers offsets and names.
# offset, name
#
interrupt_handlers = [
[ 0x0100, "__vector_SystemReset" ],
[ 0x0200, "__vector_MachineCheck" ],
[ 0x0300, "__vector_DataStorage" ],
[ 0x0380, "__vector_DataSegment" ],
[ 0x0400, "__vector_TextStorage" ],
[ 0x0480, "__vector_TextSegment" ],
[ 0x0500, "__vector_External" ],
[ 0x0600, "__vector_Alignment" ],
[ 0x0700, "__vector_Program" ],
[ 0x0800, "__vector_FP_unavail" ],
[ 0x0900, "__vector_Decrementer" ],
[ 0x0C00, "__vector_Syscall" ],
[ 0x0D00, "__vector_Trace" ],
[ 0x0F00, "__vector_PMM" ],
[ 0x0F20, "__vector_VMX_unavail" ],
[ 0x1700, "__vector_VMX_assist" ]
]
for handler in interrupt_handlers:
handler_address = LV2_BASE_ADDR + handler[ 0 ]
handler_name = handler[ 1 ]
ida_funcs.add_func( handler_address )
idc.set_name( handler_address, handler_name, idaapi.SN_CHECK )
def lv2_get_opd_entries():
return [ ppc_get_opd( address ) for address in range( lv2_get_opd_first(), lv2_get_opd_last(), PPC_OPD64_SIZE ) ]
def lv2_make_functions():
for opd in lv2_get_opd_entries():
print( "OPD %X -> %X %X %X" % ( opd.address, opd.function_address, opd.toc_address, opd.env_address ) )
ida_funcs.add_func( opd.function_address )
idc.del_items( opd.address, 0, 0x18 )
idc.create_struct( opd.address, -1, "OPDEntry" )
#
# Returns if an address matches the layout for the system call table.
#
def lv2_is_syscall_table( address, invalid_entry ):
#
# This is pretty ghetto, but simpily it checks if specific system call indexes are not implmented.
#
return ( ida_bytes.get_qword( address + ( 0 * 8 ) ) == invalid_entry and
ida_bytes.get_qword( address + ( 1 * 8 ) ) != invalid_entry and
ida_bytes.get_qword( address + ( 2 * 8 ) ) != invalid_entry and
ida_bytes.get_qword( address + ( 16 * 8 ) ) == invalid_entry and
ida_bytes.get_qword( address + ( 17 * 8 ) ) == invalid_entry and
ida_bytes.get_qword( address + ( 20 * 8 ) ) == invalid_entry and
ida_bytes.get_qword( address + ( 32 * 8 ) ) == invalid_entry and
ida_bytes.get_qword( address + ( 33 * 8 ) ) == invalid_entry and
ida_bytes.get_qword( address + ( 34 * 8 ) ) == invalid_entry and
ida_bytes.get_qword( address + ( 36 * 8 ) ) == invalid_entry and
ida_bytes.get_qword( address + ( 39 * 8 ) ) == invalid_entry and
ida_bytes.get_qword( address + ( 40 * 8 ) ) == invalid_entry and
ida_bytes.get_qword( address + ( 42 * 8 ) ) == invalid_entry )
#
# Returns address to system call table or None if error.
#
def lv2_find_system_call_table():
opd_entries = lv2_get_opd_entries()
current_search_address = ida_ida.inf_get_min_ea()
#
# Search for functions that return 0xFFFFFFFF80010003LL (ENOTIMPLMENTED) as candidates for invalid_entry.
#
while True:
current_search_address = ida_search.find_binary( current_search_address, ida_ida.inf_get_max_ea(), "3C 60 80 01 60 63 00 03 4E 80 00 20", 16, ida_search.SEARCH_DOWN )
if current_search_address == ida_idaapi.BADADDR:
break
print( "Found invalid syscall entry candidate: %X" % current_search_address )
#
# Find associated OPD for this candidate.
#
invalid_entry_opds = [ opd for opd in opd_entries if opd.function_address == current_search_address ]
#
# Search each OPD for the candidate.
#
for opd in invalid_entry_opds:
opd_xref_search = lv2_get_toc_start()
#
# Find xrefs to OPD address.
#
while True:
opd_xref = ida_search.find_binary( opd_xref_search, ida_ida.inf_get_max_ea(), "%X" % opd.address, 16, ida_search.SEARCH_DOWN )
if opd_xref == ida_idaapi.BADADDR:
break
#
# Check if this xref matches the layout of thes syscall table.
#
if lv2_is_syscall_table( opd_xref, opd.address ):
print( "%x is systemcall table" % opd_xref )
return opd_xref
opd_xref_search = opd_xref + 1
current_search_address += 1
return None
lv2_system_call_names = {
0: "not_implemented",
1: "sys_process_getpid",
2: "sys_process_wait_for_child",
4: "sys_process_get_status",
5: "sys_process_detach_child",
12: "sys_process_get_number_of_object",
13: "sys_process_get_id",
14: "sys_process_is_spu_lock_line_reservation_address",
18: "sys_process_getppid",
19: "sys_process_kill",
21: "_sys_process_spawn",
23: "sys_process_wait_for_child2",
25: "sys_process_get_sdk_version",
43: "sys_ppu_thread_yield",
44: "sys_ppu_thread_join",
45: "sys_ppu_thread_detach",
46: "sys_ppu_thread_get_join_state",
47: "sys_ppu_thread_set_priority",
48: "sys_ppu_thread_get_priority",
49: "sys_ppu_thread_get_stack_information",
56: "sys_ppu_thread_rename",
57: "sys_ppu_thread_recover_page_fault",
60: "sys_trace_create",
61: "sys_trace_start",
62: "sys_trace_stop",
63: "sys_trace_update_top_index",
64: "sys_trace_destroy",
65: "sys_trace_drain",
66: "sys_trace_attach_process",
67: "sys_trace_allocate_buffer",
68: "sys_trace_free_buffer",
69: "sys_trace_create2",
70: "sys_timer_create",
71: "sys_timer_destroy",
72: "sys_timer_get_information",
73: "_sys_timer_start",
74: "sys_timer_stop",
75: "sys_timer_connect_event_queue",
76: "sys_timer_disconnect_event_queue",
80: "sys_interrupt_tag_create",
81: "sys_interrupt_tag_destroy",
84: "_sys_interrupt_thread_establish",
85: "sys_event_flag_wait",
86: "sys_event_flag_trywait",
87: "sys_event_flag_set",
88: "sys_interrupt_thread_eoi",
89: "_sys_interrupt_thread_disestablish",
90: "sys_semaphore_create",
91: "sys_semaphore_destroy",
92: "sys_semaphore_wait",
93: "sys_semaphore_trywait",
94: "sys_semaphore_post",
100: "sys_mutex_create",
101: "sys_mutex_destroy",
102: "sys_mutex_lock",
103: "sys_mutex_trylock",
104: "sys_mutex_unlock",
105: "sys_cond_create",
106: "sys_cond_destroy",
107: "sys_cond_wait",
108: "sys_cond_signal",
109: "sys_cond_signal_all",
110: "sys_cond_signal_to",
114: "sys_semaphore_get_value",
120: "sys_rwlock_create",
121: "sys_rwlock_destroy",
122: "sys_rwlock_rlock",
123: "sys_rwlock_tryrlock",
124: "sys_rwlock_runlock",
125: "sys_rwlock_wlock",
126: "sys_rwlock_trywlock",
127: "sys_rwlock_wunlock",
128: "sys_event_queue_create",
129: "sys_event_queue_destroy",
130: "sys_event_queue_receive",
131: "sys_event_queue_tryreceive",
133: "sys_event_queue_drain",
134: "sys_event_port_create",
135: "sys_event_port_destroy",
136: "sys_event_port_connect_local",
137: "sys_event_port_disconnect",
138: "sys_event_port_send",
140: "sys_event_port_connect_ipc",
141: "sys_timer_usleep",
142: "sys_timer_sleep",
145: "sys_time_get_current_time",
147: "sys_time_get_timebase_frequency",
150: "sys_raw_spu_create_interrupt_tag",
151: "sys_raw_spu_set_int_mask",
152: "sys_raw_spu_get_int_mask",
153: "sys_raw_spu_set_int_stat",
154: "sys_raw_spu_get_int_stat",
156: "sys_spu_image_open",
160: "sys_raw_spu_create",
161: "sys_raw_spu_destroy",
163: "sys_raw_spu_read_puint_mb",
165: "sys_spu_thread_get_exit_status",
166: "sys_spu_thread_set_argument",
167: "sys_spu_thread_group_start_on_exit",
169: "sys_spu_initialize",
170: "sys_spu_thread_group_create",
171: "sys_spu_thread_group_destroy",
172: "sys_spu_thread_initialize",
173: "sys_spu_thread_group_start",
174: "sys_spu_thread_group_suspend",
175: "sys_spu_thread_group_resume",
176: "sys_spu_thread_group_yield",
177: "sys_spu_thread_group_terminate",
178: "sys_spu_thread_group_join",
179: "sys_spu_thread_group_set_priority",
180: "sys_spu_thread_group_get_priority",
181: "sys_spu_thread_write_ls",
182: "sys_spu_thread_read_ls",
184: "sys_spu_thread_write_snr",
185: "sys_spu_thread_group_connect_event",
186: "sys_spu_thread_group_disconnect_event",
187: "sys_spu_thread_set_spu_cfg",
188: "sys_spu_thread_get_spu_cfg",
190: "sys_spu_thread_write_spu_mb",
191: "sys_spu_thread_connect_event",
192: "sys_spu_thread_disconnect_event",
193: "sys_spu_thread_bind_queue",
194: "sys_spu_thread_unbind_queue",
196: "sys_raw_spu_set_spu_cfg",
197: "sys_raw_spu_get_spu_cfg",
198: "sys_spu_thread_recover_page_fault",
199: "sys_raw_spu_recover_page_fault",
251: "sys_spu_thread_group_connect_event_all_threads",
252: "sys_spu_thread_group_disconnect_event_all_threads",
260: "sys_spu_image_open_by_fd",
327: "sys_mmapper_enable_page_fault_notification",
329: "sys_mmapper_free_shared_memory",
330: "sys_mmapper_allocate_address",
331: "sys_mmapper_free_address",
332: "sys_mmapper_allocate_shared_memory",
333: "sys_mmapper_set_shared_memory_flag",
334: "sys_mmapper_map_shared_memory",
335: "sys_mmapper_unmap_shared_memory",
336: "sys_mmapper_change_address_access_right",
337: "sys_mmapper_search_and_map",
338: "sys_mmapper_get_shared_memory_attribute",
341: "sys_memory_container_create",
342: "sys_memory_container_destroy",
343: "sys_memory_container_get_size",
348: "sys_memory_allocate",
349: "sys_memory_free",
350: "sys_memory_allocate_from_container",
351: "sys_memory_get_page_attribute",
352: "sys_memory_get_user_memory_size",
402: "sys_tty_read",
403: "sys_tty_write",
450: "sys_overlay_load_module",
451: "sys_overlay_unload_module",
452: "sys_overlay_get_module_list",
453: "sys_overlay_get_module_info",
454: "sys_overlay_load_module_by_fd",
455: "sys_overlay_get_module_info2",
456: "sys_overlay_get_sdk_version",
457: "sys_overlay_get_module_dbg_info",
461: "_sys_prx_get_module_id_by_address",
463: "_sys_prx_load_module_by_fd",
464: "_sys_prx_load_module_on_memcontainer_by_fd",
480: "_sys_prx_load_module",
481: "_sys_prx_start_module",
482: "_sys_prx_stop_module",
483: "_sys_prx_unload_module",
484: "_sys_prx_register_module",
485: "_sys_prx_query_module",
486: "_sys_prx_register_library",
487: "_sys_prx_unregister_library",
488: "_sys_prx_link_library",
489: "_sys_prx_unlink_library",
490: "_sys_prx_query_library",
494: "_sys_prx_get_module_list",
495: "_sys_prx_get_module_info",
496: "_sys_prx_get_module_id_by_name",
497: "_sys_prx_load_module_on_memcontainer",
498: "_sys_prx_start",
499: "_sys_prx_stop",
600: "sys_storage_open",
601: "sys_storage_close",
602: "sys_storage_read",
603: "sys_storage_write",
604: "sys_storage_send_device_command",
605: "sys_storage_async_configure",
606: "sys_storage_async_read",
607: "sys_storage_async_write",
608: "sys_storage_async_cancel",
609: "sys_storage_get_device_info",
610: "sys_storage_get_device_config",
611: "sys_storage_report_devices",
612: "sys_storage_configure_medium_event",
613: "sys_storage_set_medium_polling_interval",
614: "sys_storage_create_region",
615: "sys_storage_delete_region",
616: "sys_storage_execute_device_command",
617: "sys_storage_get_region_acl",
618: "sys_storage_set_region_acl",
619: "sys_storage_async_send_device_command",
624: "sys_io_buffer_create",
625: "sys_io_buffer_destroy",
626: "sys_io_buffer_allocate",
627: "sys_io_buffer_free",
630: "sys_gpio_set",
631: "sys_gpio_get",
633: "sys_fsw_connect_event",
634: "sys_fsw_disconnect_event",
666: "sys_rsx_device_open",
667: "sys_rsx_device_close",
668: "sys_rsx_memory_allocate",
669: "sys_rsx_memory_free",
670: "sys_rsx_context_allocate",
671: "sys_rsx_context_free",
672: "sys_rsx_context_iomap",
673: "sys_rsx_context_iounmap",
674: "sys_rsx_context_attribute",
675: "sys_rsx_device_map",
676: "sys_rsx_device_unmap",
677: "sys_rsx_attribute",
801: "sys_fs_open",
802: "sys_fs_read",
803: "sys_fs_write",
804: "sys_fs_close",
805: "sys_fs_opendir",
806: "sys_fs_readdir",
807: "sys_fs_closedir",
808: "sys_fs_stat",
809: "sys_fs_fstat",
810: "sys_fs_link",
811: "sys_fs_mkdir",
812: "sys_fs_rename",
813: "sys_fs_rmdir",
814: "sys_fs_unlink",
815: "sys_fs_utime",
818: "sys_fs_lseek",
820: "sys_fs_fsync",
831: "sys_fs_truncate",
832: "sys_fs_ftruncate",
834: "sys_fs_chmod",
837: "sys_fs_mount",
872: "sys_ss_get_open_psid",
873: "sys_ss_get_cache_of_product_mode",
880: "sys_deci3_open",
881: "sys_deci3_create_event_path",
882: "sys_deci3_close",
883: "sys_deci3_send",
884: "sys_deci3_receive",
}
lv2_syscall_not_implmented_bytes = "3C 60 80 01 60 63 00 03 4E 80 00 20"
#
# Labels all the system call handlers.
#
def lv2_make_system_calls():
system_call_table = lv2_find_system_call_table()
if system_call_table is None:
ida_kernwin.warning("Unable to find system call table!!\nSkipping system call labeling.")
return
for syscall_index in range( 0, 1024 ):
system_call_entry_address = system_call_table + ( syscall_index * 8 )
system_call_entry_opd_address = ida_bytes.get_qword( system_call_entry_address )
system_call_entry_opd = ppc_get_opd( system_call_entry_opd_address )
system_call_handler_address = system_call_entry_opd.function_address
not_implmented_bytes = bytes.fromhex( lv2_syscall_not_implmented_bytes )
system_call_name = "invalid_entry"
if ida_bytes.get_bytes( system_call_handler_address, len( not_implmented_bytes ) ) == not_implmented_bytes:
print( ".syscall_invalid -> %X" % ( system_call_handler_address ) )
elif syscall_index in lv2_system_call_names:
system_call_name = ".syscall_%s" % ( lv2_system_call_names[ syscall_index ] )
idc.set_name( system_call_handler_address, system_call_name, idaapi.SN_CHECK )
else:
system_call_name = ".syscall_%i" % ( syscall_index )
idc.set_name( system_call_handler_address, system_call_name, idaapi.SN_CHECK )
print( "%s -> %X" % ( system_call_name, system_call_handler_address ) )
def lv2_init():
if ida_ida.inf_get_min_ea() != LV2_BASE_ADDR:
dialog_result = ida_kernwin.ask_yn( ida_kernwin.ASKBTN_BTN2, "HIDECANCEL\nLV2 base address incorrect!!\nWould you like to rebase to 0x8000000000000000?\nNice cock!" )
if dialog_result == ida_kernwin.ASKBTN_BTN1:
ida_segment.rebase_program( LV2_BASE_ADDR - ida_ida.inf_get_min_ea(), ida_segment.MSF_NOFIX )
else:
return False
return True
def lv2_dump_analyze():
if lv2_init() == False:
return 0
print( ".TOC: %X" % lv2_read_toc() )
print( ".OPD.end: %X" % lv2_get_opd_seg_end() )
print( ".OPD.start: %X" % lv2_get_opd_seg_start() )
print( "First OPD Entry: %X" % lv2_get_opd_first() )
print( "Last OPD Entry: %X" % lv2_get_opd_last() )
ppc_add_types()
#
# Enable EABI for correct TOC handling.
#
ida_typeinf.set_compiler_id( ida_typeinf.COMP_GNU, "sysv-eabi" )
#
# Add segments.
#
#ida_segment.add_segm( 0, 0x8000000000000000, 0x8000000000003020, ".core", "CODE", ida_segment.ADDSEG_NOSREG )
#ida_segment.add_segm( 8, lv2_get_opd_seg_start(), lv2_get_opd_seg_end(), ".opd", "DATA", ida_segment.ADDSEG_NOSREG )
#
# Set TOC address.
#
idc.process_config_line( "PPC_TOC=0x%X" % lv2_read_toc() )
lv2_make_interrupt_handlers()
lv2_make_functions()
lv2_make_system_calls()
lv2_dump_analyze()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment