├── LICENSE ├── README.md └── asp_loader.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Specter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IDA ASP Loader 2 | Simple loader plugin for IDA to load AMD-SP or PSP firmware binaries. Will try to load bootloader blobs unpacked by [PSPTool](https://github.com/PSPReverse/PSPTool). 3 | 4 | ## Installation 5 | Copy repo contents or script into `[ida root]/loaders`. 6 | 7 | ## Notes 8 | - Load addresses are currently hardcoded as there's no easy way to dynamically deduce them. It's possible a given binary doesn't load at a correct address (open an issue) 9 | - PSP files have some different magics, known ones are supported but there may be some binaries that have currently unsupported magics and won't be recognized (open an issue) 10 | 11 | ## Future Work 12 | - [ ] Rework configuration from hardcoding to config files for better maintainability 13 | - [ ] Add syscall annotation 14 | 15 | ## License 16 | This plugin is licensed under a [MIT](LICENSE) license. 17 | 18 | ## Resources 19 | - [https://github.com/PSPReverse](https://github.com/PSPReverse) 20 | - [https://doc.coreboot.org/soc/amd/psp_integration.html](https://doc.coreboot.org/soc/amd/psp_integration.html) 21 | -------------------------------------------------------------------------------- /asp_loader.py: -------------------------------------------------------------------------------- 1 | ''' 2 | IDA AMD PSP/ASP binary loader 3 | @SpecterDev 4 | ''' 5 | import binascii 6 | import ida_idp 7 | import idaapi 8 | import idc 9 | import struct 10 | 11 | PSP_MAGIC_GENERIC = 0x24505331 # $PS1 12 | PSP_MAGIC_ABL_VARIANT_A = 0x00424157 # [N]BAW 13 | PSP_MAGIC_ABL_VARIANT_A_MASK = 0xFF000000 14 | PSP_MAGIC_ABL_VARIANT_A_SHIFT = 24 15 | PSP_MAGIC_ABL_VARIANT_B = 0x41570042 # AW[N]B 16 | PSP_MAGIC_ABL_VARIANT_B_MASK = 0x0000FF00 17 | PSP_MAGIC_ABL_VARIANT_B_SHIFT = 8 18 | 19 | def swap32(i): 20 | return struct.unpack("I", i))[0] 21 | 22 | class PSPFile: 23 | def __init__(self, f): 24 | self.offset = f.tell() 25 | 26 | # 0x00-0x10 27 | f.seek(0x00) 28 | self.pad1 = struct.unpack("<16s", f.read(0x10)) 29 | 30 | # 0x10-0x20 31 | f.seek(0x10) 32 | (self.magic, self.body_size, self.is_encrypted, self.pad2) = struct.unpack("> PSP_MAGIC_ABL_VARIANT_B_SHIFT 59 | 60 | if (self.magic & ~PSP_MAGIC_ABL_VARIANT_A_MASK) == PSP_MAGIC_ABL_VARIANT_A: 61 | return (self.magic & PSP_MAGIC_ABL_VARIANT_A_MASK) >> PSP_MAGIC_ABL_VARIANT_A_SHIFT 62 | 63 | if (self.magic & ~PSP_MAGIC_ABL_VARIANT_B_MASK) == PSP_MAGIC_ABL_VARIANT_B: 64 | return (self.magic & PSP_MAGIC_ABL_VARIANT_B_MASK) >> PSP_MAGIC_ABL_VARIANT_B_SHIFT 65 | 66 | def is_abl0(self): 67 | abl_num = self.get_abl_num() 68 | 69 | if abl_num == 0x30: 70 | return True 71 | return False 72 | 73 | def is_abln(self): 74 | abl_num = self.get_abl_num() 75 | 76 | if abl_num >= 0x31 and abl_num <= 0x37: 77 | return True 78 | return False 79 | 80 | def create_header_struct(address): 81 | pspStructId = idc.add_struc(-1, "PSPFileHeader", 0) 82 | 83 | idc.add_struc_member(pspStructId, "pad1", 0x00, idc.FF_BYTE, -1, 0x10) 84 | idc.add_struc_member(pspStructId, "magic", 0x10, idc.FF_STRLIT, -1, 0x04) 85 | idc.add_struc_member(pspStructId, "body_size", 0x14, idc.FF_DWORD, -1, 0x04) 86 | idc.add_struc_member(pspStructId, "is_encrypted", 0x18, idc.FF_DWORD, -1, 0x04) 87 | idc.add_struc_member(pspStructId, "pad2", 0x1C, idc.FF_BYTE, -1, 0x4) 88 | idc.add_struc_member(pspStructId, "iv", 0x20, idc.FF_BYTE, -1, 0x10) 89 | idc.add_struc_member(pspStructId, "is_signed", 0x30, idc.FF_DWORD, -1, 0x04) 90 | idc.add_struc_member(pspStructId, "pad3", 0x34, idc.FF_BYTE, -1, 0x04) 91 | idc.add_struc_member(pspStructId, "signature_footprint", 0x38, idc.FF_BYTE, -1, 0x10) 92 | idc.add_struc_member(pspStructId, "is_compressed", 0x48, idc.FF_DWORD, -1, 0x04) 93 | idc.add_struc_member(pspStructId, "pad4", 0x4C, idc.FF_BYTE, -1, 0x04) 94 | idc.add_struc_member(pspStructId, "uncompressed_size", 0x50, idc.FF_DWORD, -1, 0x04) 95 | idc.add_struc_member(pspStructId, "zlib_size", 0x54, idc.FF_DWORD, -1, 0x04) 96 | idc.add_struc_member(pspStructId, "pad5", 0x58, idc.FF_BYTE, -1, 0x08) 97 | idc.add_struc_member(pspStructId, "version", 0x60, idc.FF_DWORD, -1, 0x04) 98 | idc.add_struc_member(pspStructId, "pad6", 0x64, idc.FF_BYTE, -1, 0x04) 99 | idc.add_struc_member(pspStructId, "load_addr", 0x68, idc.FF_DWORD, -1, 0x04) 100 | idc.add_struc_member(pspStructId, "rom_size", 0x6C, idc.FF_DWORD, -1, 0x04) 101 | idc.add_struc_member(pspStructId, "pad7", 0x70, idc.FF_BYTE, -1, 0x10) 102 | idc.add_struc_member(pspStructId, "wrapped_key", 0x80, idc.FF_BYTE, -1, 0x10) 103 | idc.add_struc_member(pspStructId, "pad8", 0x90, idc.FF_BYTE, -1, 0x10) 104 | idc.add_struc_member(pspStructId, "abl_id", 0xA0, idc.FF_BYTE, -1, 0x04) 105 | 106 | idaapi.create_struct(address, 0x90, pspStructId) 107 | 108 | def load_file(f, neflags, format): 109 | print('# ASP Loader') 110 | pspFile = PSPFile(f) 111 | 112 | idaapi.set_processor_type("arm", ida_idp.SETPROC_LOADER) 113 | idaapi.get_inf_structure().lflags |= idaapi.LFLG_PC_FLAT 114 | 115 | # Default addr of 0 (used for PSP_FW_BOOT_LOADER) 116 | address = 0x0 117 | 118 | # ABL0 standard addr is 0x15000, which seems to be the case when load addr is 0x100 119 | if pspFile.is_abl0(): 120 | if pspFile.load_addr == 0x100: 121 | address = 0x15100 122 | else: 123 | # Exceptions, currently all we have is 4700s 124 | address = 0x54000 125 | 126 | # ABLN standard addr is 0x16200, which seems to be the case when load addr is 0x100 127 | if pspFile.is_abln(): 128 | if pspFile.load_addr == 0x100: 129 | address = 0x16200 130 | else: 131 | # Exceptions, currently all we have is 4700s 132 | address = 0x57000 # for c09+ 133 | 134 | end = address + 0x100 135 | 136 | print('# Creating header segment') 137 | idaapi.add_segm(0x0, address, end, 'HEADER', 'CONST', 0x0) 138 | f.seek(0x0) 139 | f.file2base(0x0, address, end, 0) 140 | 141 | create_header_struct(address) 142 | 143 | print('# Creating ROM Segment...') 144 | address += 0x100 145 | end = address + f.size() - 0x100 146 | 147 | idaapi.add_segm(0x0, address, end, 'ROM', 'CODE', 0x0) 148 | f.seek(0x100) 149 | f.file2base(0x100, address, end, 0) 150 | 151 | idaapi.add_entry(address, address, "start", 1) 152 | return 1 153 | 154 | def accept_file(f, n): 155 | retval = 0 156 | 157 | print('# Checking for ASP') 158 | 159 | f.seek(0) 160 | pspFile = PSPFile(f) 161 | 162 | if pspFile.magic == PSP_MAGIC_GENERIC or (pspFile.magic & ~PSP_MAGIC_ABL_VARIANT_A_MASK) == PSP_MAGIC_ABL_VARIANT_A or (pspFile.magic & ~PSP_MAGIC_ABL_VARIANT_B_MASK) == PSP_MAGIC_ABL_VARIANT_B: 163 | retval = "AMD-SP Firmware Blob" 164 | magicStr = bytearray.fromhex('{:x}'.format(pspFile.magic)).decode() 165 | 166 | print('# PSP File found!') 167 | print('# Magic: {:s}'.format(magicStr)) 168 | print('# Version: {:02x}'.format(pspFile.version)) 169 | print('# Is encrypted: {}'.format(pspFile.is_encrypted)) 170 | print('# Is signed: {}'.format(pspFile.is_signed)) 171 | print('# Is compressed: {}'.format(pspFile.is_compressed)) 172 | print('# Load Address: 0x{:02x}'.format(pspFile.load_addr)) 173 | print('# ROM size: 0x{:02x}'.format(pspFile.rom_size)) 174 | print('# Body size: 0x{:02x}'.format(pspFile.body_size)) 175 | print('# Zlib size: 0x{:02x}'.format(pspFile.zlib_size)) 176 | print('# Uncompressed size: 0x{:02x}'.format(pspFile.uncompressed_size)) 177 | print('# Wrapped key: {}'.format(binascii.hexlify(pspFile.wrapped_key, ' ').decode())) 178 | print('# IV: {}'.format(binascii.hexlify(pspFile.iv, ' ').decode())) 179 | print('# Signature footprint: {}'.format(binascii.hexlify(pspFile.signature_footprint, ' ').decode())) 180 | print('# ABL ID: {}'.format(pspFile.abl_id)) 181 | 182 | return retval 183 | --------------------------------------------------------------------------------