Last active
April 3, 2023 17:13
-
-
Save iMoD1998/09e160dce858fcad899251b676c32e2b to your computer and use it in GitHub Desktop.
IDA PS3 LV2 Dump Analyzer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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