Last active
November 5, 2024 11:01
-
-
Save xerpi/4aaf83ca59c33190c960881e3a364627 to your computer and use it in GitHub Desktop.
PS3 NID reader
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
#!/usr/bin/env python3 | |
import sys | |
import construct | |
from collections import namedtuple | |
from elftools.elf.elffile import ELFFile | |
from elftools.elf.relocation import RelocationSection | |
from construct import Struct, Int8ub, Int16ub, Int32ub, PaddedString, CString, Array | |
ModuleImports = namedtuple("ModuleImports", "name nids") | |
ModuleExports = namedtuple("ModuleExports", "name nids") | |
ModuleInfo = namedtuple("ModuleNids", "name imports exports") | |
ELFOSABI_CELL_LV2 = 0x66 | |
ET_SCE_PPURELEXEC = 0xFFA4 | |
PT_PRX_PARAM = 0x60000002 | |
sys_prx_module_info_t = Struct( | |
"attributes" / Int16ub, | |
"version" / Int16ub, | |
"name" / PaddedString(28, "ascii"), | |
"toc" / Int32ub, | |
"exports_start" / Int32ub, | |
"exports_end" / Int32ub, | |
"imports_start" / Int32ub, | |
"imports_end" / Int32ub, | |
) | |
sys_prx_module_exports_t = Struct( | |
"size" / Int8ub, | |
"unk0" / Int8ub, | |
"version" / Int16ub, | |
"attributes" / Int16ub, | |
"num_func" / Int16ub, | |
"num_var" / Int16ub, | |
"num_tlsvar" / Int16ub, | |
"info_hash" / Int8ub, | |
"info_tlshash" / Int8ub, | |
"unk1" / Int16ub, | |
"name_addr" / Int32ub, | |
"fnid_addr" / Int32ub, | |
"fstub_addr" / Int32ub | |
) | |
sys_prx_module_imports_t = Struct( | |
"size" / Int8ub, | |
"unk0" / Int8ub, | |
"version" / Int16ub, | |
"attributes" / Int16ub, | |
"num_func" / Int16ub, | |
"num_var" / Int16ub, | |
"num_tlsvar" / Int16ub, | |
"info_hash" / Int8ub, | |
"info_tlshash" / Int8ub, | |
"unk1" / Int16ub, | |
"name_addr" / Int32ub, | |
"fnid_addr" / Int32ub, | |
"fstub_addr" / Int32ub, | |
"var_nid_table" / Int32ub, | |
"var_entry_table" / Int32ub, | |
"tls_nid_table" / Int32ub, | |
"tls_entry_table" / Int32ub | |
) | |
sys_process_prx_param_t = Struct( | |
"size" / Int32ub, | |
"magic" / Int32ub, | |
"version" / Int32ub, | |
"unk0" / Int32ub, | |
"libent_start" / Int32ub, | |
"libent_end" / Int32ub, | |
"libstub_start" / Int32ub, | |
"libstub_end" / Int32ub, | |
"ver" / Int16ub, | |
"unk1" / Int16ub, | |
"unk2" / Int32ub, | |
) | |
def usage(): | |
print("ps3nidreader by xerpi") | |
print("Usage:\n\t" + sys.argv[0] + " file.elf") | |
def read_nids_ps3_prx(f, elffile): | |
module_info_found = False | |
# First LOAD-type segment p_paddr points to the module info | |
for segment in elffile.iter_segments(): | |
if segment.header.p_type == 'PT_LOAD': | |
p_offset = segment["p_offset"] | |
f.seek(segment["p_paddr"]) | |
module_info = sys_prx_module_info_t.parse_stream(f) | |
module_info_found = True | |
break | |
if not module_info_found: | |
print("Error: can't find the module info", file=sys.stderr) | |
exit(1) | |
modinfo = ModuleInfo(module_info["name"].decode('ascii'), [], []) | |
exp_addr = p_offset + module_info["exports_start"] | |
while exp_addr < p_offset + module_info["exports_end"]: | |
f.seek(exp_addr) | |
exports = sys_prx_module_exports_t.parse_stream(f) | |
name = "" | |
if exports["name_addr"]: | |
f.seek(p_offset + exports["name_addr"]) | |
name = CString().parse_stream(f).decode('ascii') | |
f.seek(p_offset + exports["fnid_addr"]) | |
nids = Array(exports["num_func"], Int32ub).parse_stream(f) | |
modimp = ModuleExports(name, nids) | |
modinfo.exports.append(modimp) | |
exp_addr += exports["size"] | |
imp_addr = p_offset + module_info["imports_start"] | |
while imp_addr < p_offset + module_info["imports_end"]: | |
f.seek(imp_addr) | |
imports = sys_prx_module_imports_t.parse_stream(f) | |
f.seek(p_offset + imports["name_addr"]) | |
name = CString().parse_stream(f).decode('ascii') | |
f.seek(p_offset + imports["fnid_addr"]) | |
nids = Array(imports["num_func"], Int32ub).parse_stream(f) | |
modimp = ModuleImports(name, nids) | |
modinfo.imports.append(modimp) | |
imp_addr += imports["size"] | |
return modinfo | |
def read_nids_ps3_elf_bruteforce(f, elffile): | |
modinfo = ModuleInfo("", [], []) | |
# Find the exports section | |
exports_idx = -1 | |
for idx, section in enumerate(elffile.iter_sections()): | |
if section["sh_size"] > 0 and section["sh_size"] % 0x1C == 0: | |
is_exports = True | |
offset = 0 | |
while offset < section["sh_size"]: | |
f.seek(section["sh_offset"] + offset) | |
if Int8ub.parse_stream(f) != 0x1C: | |
is_exports = False | |
break | |
offset += 0x1C | |
if is_exports: | |
exports_idx = idx | |
break; | |
if exports_idx == -1: | |
print("Error: can't find the imports", file=sys.stderr) | |
exit(1) | |
# Find the imports section | |
imports_idx = -1 | |
for idx, section in enumerate(elffile.iter_sections()): | |
if section["sh_size"] > 0 and section["sh_size"] % 0x2C == 0: | |
is_imports = True | |
offset = 0 | |
while offset < section["sh_size"]: | |
f.seek(section["sh_offset"] + offset) | |
if Int8ub.parse_stream(f) != 0x2C: | |
is_imports = False | |
break | |
offset += 0x2C | |
if is_imports: | |
imports_idx = idx | |
break; | |
if imports_idx == -1: | |
print("Error: can't find the imports", file=sys.stderr) | |
exit(1) | |
exp_section = elffile.get_section(exports_idx) | |
exp_sh_offset = exp_section["sh_offset"] | |
exp_sh_addr = exp_section["sh_addr"] | |
exp_offset = 0 | |
while exp_offset < exp_section["sh_size"]: | |
f.seek(exp_sh_offset + exp_offset) | |
exports = sys_prx_module_exports_t.parse_stream(f) | |
name = "" | |
if exports["name_addr"]: | |
f.seek(exports["name_addr"] + (exp_sh_offset - exp_sh_addr)) | |
name = CString("ascii").parse_stream(f) | |
f.seek(exports["fnid_addr"] + (exp_sh_offset - exp_sh_addr)) | |
nids = Array(exports["num_func"], Int32ub).parse_stream(f) | |
modexp = ModuleExports(name, nids) | |
modinfo.exports.append(modexp) | |
exp_offset += exports["size"] | |
imp_section = elffile.get_section(imports_idx) | |
imp_sh_offset = imp_section["sh_offset"] | |
imp_sh_addr = imp_section["sh_addr"] | |
imp_offset = 0 | |
while imp_offset < imp_section["sh_size"]: | |
f.seek(imp_sh_offset + imp_offset) | |
imports = sys_prx_module_imports_t.parse_stream(f) | |
name = "" | |
if imports["name_addr"]: | |
f.seek(imports["name_addr"] + (imp_sh_offset - imp_sh_addr)) | |
name = CString("ascii").parse_stream(f) | |
f.seek(imports["fnid_addr"] + (imp_sh_offset - imp_sh_addr)) | |
nids = Array(imports["num_func"], Int32ub).parse_stream(f) | |
modimp = ModuleImports(name, nids) | |
modinfo.imports.append(modimp) | |
imp_offset += imports["size"] | |
return modinfo | |
def read_nids_ps3_elf(f, elffile): | |
prx_param_found = False | |
# PT_PRX_PARAM segment has the process PRX param | |
for segment in elffile.iter_segments(): | |
if segment.header.p_type == PT_PRX_PARAM: | |
p_offset = segment["p_offset"] | |
p_paddr = segment["p_paddr"] | |
if segment["p_offset"] == 0: | |
return read_nids_ps3_elf_bruteforce(f, elffile) | |
f.seek(segment["p_offset"]) | |
prx_param = sys_process_prx_param_t.parse_stream(f) | |
prx_param_found = True | |
break | |
if not prx_param_found: | |
print("Error: can't find the PRX param segment", file=sys.stderr) | |
exit(1) | |
modinfo = ModuleInfo("", [], []) | |
exp_addr = p_offset + (prx_param["libent_start"] - p_paddr) | |
while exp_addr < p_offset + (prx_param["libent_end"] - p_paddr): | |
f.seek(exp_addr) | |
exports = sys_prx_module_exports_t.parse_stream(f) | |
name = "" | |
if exports["name_addr"]: | |
f.seek(p_offset + (exports["name_addr"] - p_paddr)) | |
name = CString().parse_stream(f).decode('ascii') | |
f.seek(p_offset + (exports["fnid_addr"] - p_paddr)) | |
nids = Array(exports["num_func"], Int32ub).parse_stream(f) | |
modexp = ModuleExports(name, nids) | |
modinfo.exports.append(modexp) | |
exp_addr += exports["size"] | |
imp_addr = p_offset + (prx_param["libstub_start"] - p_paddr) | |
while imp_addr < p_offset + (prx_param["libstub_end"] - p_paddr): | |
f.seek(imp_addr) | |
imports = sys_prx_module_imports_t.parse_stream(f) | |
f.seek(p_offset + (imports["name_addr"] - p_paddr)) | |
name = CString().parse_stream(f).decode('ascii') | |
f.seek(p_offset + (imports["fnid_addr"] - p_paddr)) | |
nids = Array(imports["num_func"], Int32ub).parse_stream(f) | |
modimp = ModuleImports(name, nids) | |
modinfo.imports.append(modimp) | |
imp_addr += imports["size"] | |
return modinfo | |
def print_nids(modinfo): | |
print("Module name: " + modinfo.name) | |
print(" Exports:") | |
for exp in modinfo.exports: | |
if exp.name: | |
print(" " + exp.name) | |
else: | |
print(" <no name>") | |
for nid in exp.nids: | |
print(" " + "0x{:08X}".format(nid)) | |
print(" Imports:") | |
for imp in modinfo.imports: | |
print(" " + imp.name) | |
for nid in imp.nids: | |
print(" " + "0x{:08X}".format(nid)) | |
def print_nids_yaml(modinfo): | |
# We only care about exports | |
if len(modinfo.exports) == 0: | |
return | |
print("modules:") | |
print(" " + modinfo.name + ":") | |
print(" libraries:") | |
for exp in modinfo.exports: | |
if not exp.name: | |
continue; | |
print(" " + exp.name + ":") | |
print(" functions:") | |
for nid in exp.nids: | |
print(" " + modinfo.name + "_" + exp.name + "_" + "{:08X}".format(nid) + ": " + "0x{:08X}".format(nid)) | |
def process_file(filename): | |
with open(filename, 'rb') as f: | |
elffile = ELFFile(f) | |
if elffile.header.e_ident["EI_OSABI"] == ELFOSABI_CELL_LV2: | |
if elffile.header.e_type == ET_SCE_PPURELEXEC: | |
modinfo = read_nids_ps3_prx(f, elffile) | |
elif elffile.header.e_type == "ET_EXEC": | |
modinfo = read_nids_ps3_elf(f, elffile) | |
print_nids(modinfo) | |
#print_nids_yaml(modinfo) | |
else: | |
print("Error: not a PS3 PPU ELF", file=sys.stderr) | |
exit(1) | |
if __name__ == '__main__': | |
if len(sys.argv) < 2: | |
usage() | |
else: | |
process_file(sys.argv[1]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment