├── LICENSE ├── README.md ├── __init__.py ├── bn_view_abl.py ├── bn_view_bootloader.py ├── data └── syscalls.json ├── plugin.json ├── psp_file.py ├── psp_types.py └── svc_annotate.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 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 | # AMD-SP/PSP Loader 2 | Author: **dayzerosec** 3 | 4 | _Loader for AMD-SP or PSP firmware binaries._ 5 | 6 | ![](https://i.imgur.com/MH9C1hu.png) 7 | 8 | ## Description 9 | 10 | Binary Ninja loader for AMD Secure Processor (SP) / Platform Security Processor (PSP) firmware binaries. It will try to load AGESA Bootloader (ABL) and Bootloader blobs and will setup the correct load addresses. 11 | 12 | The ABL loader will also optionally annotate syscalls using the dictionary in [./data/syscalls.json](./data/syscalls.json). 13 | 14 | ## Installation 15 | 16 | To install this plugin, go to Binary Ninja's plugin directory (can be found by going to Tools -> "Open Plugin Folder"), and run the following command: 17 | 18 | ``` 19 | git clone https://github.com/dayzerosec/AMD-SP-Loader 20 | ``` 21 | 22 | Note you'll probably need to restart Binary Ninja for the plugin to load. 23 | 24 | ## Usage 25 | 26 | This loader is intended to be used with binaries extracted via [PSPTool](https://github.com/PSPReverse/PSPTool), as this loader will **not extract firmware from UEFI or perform any decompression before loading**. 27 | 28 | Simply load an `ABL*` or `PSP_FW_BOOTLOADER_*` binary to use the loader. Your view name on the top left of the disassembly pane should have an `AMD-SP` prefix. If your particular firmware blob doesn't load and/or loads at an incorrect address, please file an issue. 29 | 30 | ## Future Work / Places for Contribution 31 | 32 | - [ ] Currently load addresses are static, perhaps this should be reworked to dynamically determine it via parsing entrypoint instructions? 33 | - [ ] Add loaders for other firmwares 34 | - [ ] SMU (xtensa) 35 | - [ ] Trusted OS (tOS) 36 | - [ ] Boot time trustlets 37 | - [ ] Reverse and add more syscalls to the annotation dictionary 38 | - [ ] Update args of existing syscalls 39 | - [ ] Improve annotations to fix-up syscalls in HLIL 40 | 41 | ## Notes 42 | 43 | - The loaders make some assumptions on the load address and such, so its possible a particular binary differs and won't load properly (open an issue). 44 | - Syscall annotations that are prefixed with a `_` are unofficial/guessed. 45 | 46 | ## Minimum Version 47 | 48 | This plugin requires the following minimum version of Binary Ninja: 49 | * release - 3.2.3814 50 | 51 | ## Resources 52 | 53 | - [https://github.com/PSPReverse](https://github.com/PSPReverse) 54 | - [https://doc.coreboot.org/soc/amd/psp_integration.html](https://doc.coreboot.org/soc/amd/psp_integration.html) 55 | - [https://github.com/sameershaik/coreboot_beagle-xM/blob/main/src/vendorcode/amd/fsp/cezanne/include/bl_uapp/bl_syscall_public.h](https://github.com/sameershaik/coreboot_beagle-xM/blob/main/src/vendorcode/amd/fsp/cezanne/include/bl_uapp/bl_syscall_public.h) 56 | 57 | ## License 58 | 59 | This plugin is released under a [MIT](LICENSE) license. 60 | 61 | ## Thanks 62 | - PSPReverse for previous work and awesome resources. 63 | - Carstein (inspiration and reference for syscall annotation via [Syscaller](https://github.com/carstein/Syscaller). 64 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .bn_view_abl import ABLView 2 | from .bn_view_bootloader import BootloaderView 3 | 4 | ABLView.register() 5 | BootloaderView.register() -------------------------------------------------------------------------------- /bn_view_abl.py: -------------------------------------------------------------------------------- 1 | ''' 2 | File: bn_view_abl.py 3 | Author(s): @SpecterDev 4 | Purpose: Implements the ABL binary view for binja 5 | ''' 6 | 7 | import binaryninja as bn 8 | import json 9 | import os 10 | from .psp_file import PSPFile 11 | from .psp_types import * 12 | from .svc_annotate import annotate 13 | 14 | # Note: These came from reversing various firmwares. Some versions may differ where things are loaded in SRAM. 15 | # TODO: There might be a better way to do this, such as parsing instructions to determine the location? 16 | LOAD_ADDR_ABL0 = 0x15100 17 | LOAD_ADDR_ABLN = 0x16200 18 | 19 | class ABLView(bn.BinaryView): 20 | name = "AMD-SP ABL" 21 | long_name = "AMD-SP ABL" 22 | do_annotation = False 23 | 24 | def __init__(self, data): 25 | bn.BinaryView.__init__(self, parent_view=data, file_metadata=data.file) 26 | self.data = data 27 | self.psp_file = PSPFile(data) 28 | 29 | # Prompt the user if they want annotation if theres no existing database 30 | if not self.has_database: 31 | do_annotate = bn.get_choice_input('Do you want syscalls annotated?', 'Annotate syscalls?', ['Yes', 'No']) 32 | 33 | if do_annotate == 0: 34 | self.db = self.load_syscall_db() 35 | self.do_annotation = True 36 | 37 | def log(self, msg, error=False): 38 | msg = f"[AMD-SP ABL Loader] {msg}" 39 | if not error: 40 | bn.log_info(msg) 41 | else: 42 | bn.log_error(msg) 43 | 44 | def load_syscall_db(self): 45 | current_file_path = os.path.dirname(os.path.abspath(__file__)) 46 | db_path = os.path.join(current_file_path, 'data', 'syscalls.json') 47 | db_file = open(db_path, 'r') 48 | return json.load(db_file) 49 | 50 | def define_header_struct(self): 51 | header_struct = create_header_struct_type() 52 | self.define_user_data_var(self.load_address, header_struct, 'psp_file_header') 53 | 54 | @classmethod 55 | def is_valid_for_data(self, data): 56 | self.psp_file = PSPFile(data) 57 | 58 | if self.psp_file.is_abl() and self.psp_file.get_abl_num() >= 0: 59 | return True 60 | return False 61 | 62 | def on_complete(self): 63 | # Define structs 64 | self.define_header_struct() 65 | 66 | # Annotate syscalls (if the user has specified to do so) 67 | if self.do_annotation: 68 | self.log("Annotating syscalls...") 69 | funcs = self.functions 70 | annotate(funcs, self.db, self) 71 | else: 72 | self.log("Skipping syscall annotation") 73 | 74 | def init(self): 75 | # PSP binaries are always armv7 (and userspace at least will likely switch to thumb2) 76 | self.arch = bn.Architecture["armv7"] 77 | self.platform = self.arch.standalone_platform 78 | 79 | # Load the binary. Data is mixed with code, and so it should be RWX. 80 | abl_num = self.psp_file.get_abl_num() 81 | if abl_num == 0: 82 | self.load_address = LOAD_ADDR_ABL0 83 | else: 84 | self.load_address = LOAD_ADDR_ABLN 85 | 86 | self.log("Detected AMD-SP/PSP ABL binary (abl={:d})".format(abl_num)) 87 | 88 | # Header segment 89 | header_segment_offset = self.load_address 90 | header_segment_size = 0x100 91 | self.add_auto_segment( 92 | header_segment_offset, 93 | header_segment_size, 94 | 0, 95 | header_segment_size, 96 | bn.SegmentFlag.SegmentReadable 97 | ) 98 | 99 | self.add_user_section("header", header_segment_offset, header_segment_size, 100 | bn.SectionSemantics.ReadOnlyDataSectionSemantics) 101 | 102 | # Code/data segment 103 | code_segment_offset = self.load_address + header_segment_size 104 | code_segment_size = len(self.parent_view) - 0x100 105 | 106 | self.add_auto_segment( 107 | code_segment_offset, 108 | code_segment_size, 109 | header_segment_size, 110 | code_segment_size, 111 | bn.SegmentFlag.SegmentReadable | bn.SegmentFlag.SegmentWritable | bn.SegmentFlag.SegmentExecutable 112 | ) 113 | 114 | self.add_user_section("code", code_segment_offset, code_segment_size, 115 | bn.SectionSemantics.ReadOnlyCodeSectionSemantics) 116 | 117 | # Add the entrypoint, which is always immediately after the header at 0x100 118 | self.add_entry_point(code_segment_offset) 119 | 120 | self.define_auto_symbol_and_var_or_function( 121 | bn.Symbol(bn.SymbolType.FunctionSymbol, code_segment_offset, '_start'), 122 | bn.Type.function(bn.Type.void(), []), 123 | bn.Architecture["armv7"].standalone_platform 124 | ) 125 | 126 | self.update_analysis() 127 | 128 | # Register a completion event to annotate syscalls 129 | bn.AnalysisCompletionEvent(self, self.on_complete) 130 | return True 131 | -------------------------------------------------------------------------------- /bn_view_bootloader.py: -------------------------------------------------------------------------------- 1 | ''' 2 | File: bn_view_bootloader.py 3 | Author(s): @SpecterDev 4 | Purpose: Implements the bootloader binary view for binja 5 | ''' 6 | 7 | import binaryninja as bn 8 | from .psp_file import PSPFile 9 | from .psp_types import * 10 | 11 | # Note: These came from reversing various firmwares. Some versions may differ where things are loaded in SRAM. 12 | LOAD_ADDR_BOOTLOADER = 0 13 | 14 | class BootloaderView(bn.BinaryView): 15 | name = "AMD-SP Bootloader" 16 | long_name = "AMD-SP Bootloader" 17 | 18 | def log(self, msg, error=False): 19 | msg = f"[AMD-SP Bootloader Loader] {msg}" 20 | if not error: 21 | bn.log_info(msg) 22 | else: 23 | bn.log_error(msg) 24 | 25 | def __init__(self, data): 26 | bn.BinaryView.__init__(self, parent_view=data, file_metadata=data.file) 27 | self.data = data 28 | self.psp_file = PSPFile(data) 29 | 30 | def define_header_struct(self): 31 | header_struct = create_header_struct_type() 32 | self.define_user_data_var(self.load_address, header_struct, 'psp_file_header') 33 | 34 | @classmethod 35 | def is_valid_for_data(self, data): 36 | self.psp_file = PSPFile(data) 37 | return self.psp_file.is_bootloader() 38 | 39 | def on_complete(self): 40 | # Define structs 41 | self.define_header_struct() 42 | 43 | def init(self): 44 | # PSP binaries are always armv7 (and userspace at least will likely switch to thumb2) 45 | self.arch = bn.Architecture["armv7"] 46 | self.platform = self.arch.standalone_platform 47 | 48 | # Load the binary. Data is mixed with code, and so it should be RWX. 49 | self.load_address = LOAD_ADDR_BOOTLOADER 50 | 51 | self.log("Detected AMD-SP/PSP Bootloader binary") 52 | 53 | # Header segment 54 | header_segment_offset = self.load_address 55 | header_segment_size = 0x100 56 | self.add_auto_segment( 57 | header_segment_offset, 58 | header_segment_size, 59 | 0, 60 | header_segment_size, 61 | bn.SegmentFlag.SegmentReadable 62 | ) 63 | 64 | self.add_user_section("header", header_segment_offset, header_segment_size, 65 | bn.SectionSemantics.ReadOnlyDataSectionSemantics) 66 | 67 | # Code/data segment 68 | code_segment_offset = self.load_address + header_segment_size 69 | code_segment_size = len(self.parent_view) - 0x100 70 | 71 | self.add_auto_segment( 72 | code_segment_offset, 73 | code_segment_size, 74 | header_segment_size, 75 | code_segment_size, 76 | bn.SegmentFlag.SegmentReadable | bn.SegmentFlag.SegmentWritable | bn.SegmentFlag.SegmentExecutable 77 | ) 78 | 79 | self.add_user_section("code", code_segment_offset, code_segment_size, 80 | bn.SectionSemantics.ReadOnlyCodeSectionSemantics) 81 | 82 | # Add the entrypoint, which is always immediately after the header at 0x100 83 | self.add_entry_point(code_segment_offset) 84 | 85 | self.define_auto_symbol_and_var_or_function( 86 | bn.Symbol(bn.SymbolType.FunctionSymbol, code_segment_offset, '_start'), 87 | bn.Type.function(bn.Type.void(), []), 88 | bn.Architecture["armv7"].standalone_platform 89 | ) 90 | 91 | self.update_analysis() 92 | 93 | # Register a completion event to create structs 94 | bn.AnalysisCompletionEvent(self, self.on_complete) 95 | return True 96 | -------------------------------------------------------------------------------- /data/syscalls.json: -------------------------------------------------------------------------------- 1 | { 2 | "0":{ 3 | "name":"SVC_EXIT", 4 | "args":[ 5 | { 6 | "type":"uint32_t", 7 | "name":"status" 8 | } 9 | ] 10 | }, 11 | "1":{ 12 | "name":"_SVC_APP_INIT", 13 | "args":[ 14 | ] 15 | }, 16 | "2":{ 17 | "name":"SVC_ENTER", 18 | "args":[ 19 | { 20 | "type":"uint32_t", 21 | "name":"entry_type" 22 | }, 23 | { 24 | "type":"void*", 25 | "name":"dest" 26 | }, 27 | { 28 | "type":"uint32_t*", 29 | "name":"size" 30 | } 31 | ] 32 | }, 33 | "3":{ 34 | "name":"_SVC_SMN_MAP_EX", 35 | "args":[ 36 | ] 37 | }, 38 | "4":{ 39 | "name":"_SVC_SMN_MAP", 40 | "args":[ 41 | ] 42 | }, 43 | "6":{ 44 | "name":"SVC_DEBUG_PRINT", 45 | "args":[ 46 | { 47 | "type":"const char*", 48 | "name":"string" 49 | } 50 | ] 51 | }, 52 | "7":{ 53 | "name":"_SVC_X86_MEM_MAP", 54 | "args":[ 55 | ] 56 | }, 57 | "8":{ 58 | "name":"_SVC_X86_MEM_UNMAP", 59 | "args":[ 60 | ] 61 | }, 62 | "9":{ 63 | "name":"_SVC_X86_COPY_TO_PSP", 64 | "args":[ 65 | ] 66 | }, 67 | "10":{ 68 | "name":"_SVC_X86_COPY_FROM_PSP", 69 | "args":[ 70 | ] 71 | }, 72 | "26":{ 73 | "name":"SVC_DEBUG_PRINT_EX", 74 | "args":[ 75 | { 76 | "type":"uint32_t", 77 | "name":"dword0" 78 | }, 79 | { 80 | "type":"uint32_t", 81 | "name":"dword1" 82 | }, 83 | { 84 | "type":"uint32_t", 85 | "name":"dword2" 86 | }, 87 | { 88 | "type":"uint32_t", 89 | "name":"dword3" 90 | } 91 | ] 92 | }, 93 | "28":{ 94 | "name":"SVC_GET_BOOT_MODE", 95 | "args":[ 96 | { 97 | "type":"uint32_t*", 98 | "name":"boot_mode" 99 | } 100 | ] 101 | }, 102 | "37":{ 103 | "name":"_SVC_X86_MEM_MAP_EX", 104 | "args":[ 105 | ] 106 | }, 107 | "40":{ 108 | "name":"_SVC_SMU_MSG", 109 | "args":[ 110 | ] 111 | }, 112 | "57":{ 113 | "name":"_SVC_RNG", 114 | "args":[ 115 | ] 116 | }, 117 | "60":{ 118 | "name":"_SVC_QUERY_SAVE_STATE_REGION", 119 | "args":[ 120 | ] 121 | }, 122 | "72":{ 123 | "name":"_SVC_QUERY_SMM_REGION", 124 | "args":[ 125 | ] 126 | }, 127 | "96":{ 128 | "name":"SVC_GET_SPI_INFO", 129 | "args":[ 130 | { 131 | "type":"struct spirom_info*", 132 | "name":"spi_rom_info" 133 | } 134 | ] 135 | }, 136 | "97":{ 137 | "name":"SVC_MAP_SPIROM_DEVICE", 138 | "args":[ 139 | { 140 | "type":"void*", 141 | "name":"spi_rom_addr" 142 | }, 143 | { 144 | "type":"uint32_t", 145 | "name":"size" 146 | }, 147 | { 148 | "type":"void**", 149 | "name":"spi_rom_axi_addr" 150 | } 151 | ] 152 | }, 153 | "98":{ 154 | "name":"SVC_UNMAP_SPIROM_DEVICE", 155 | "args":[ 156 | { 157 | "type":"void*", 158 | "name":"spi_rom_addr" 159 | } 160 | ] 161 | }, 162 | "99":{ 163 | "name":"SVC_MAP_FCH_IO_DEVICE", 164 | "args":[ 165 | { 166 | "type":"uint32_t", 167 | "name":"io_device" 168 | }, 169 | { 170 | "type":"uint32_t", 171 | "name":"arg1" 172 | }, 173 | { 174 | "type":"uint32_t", 175 | "name":"arg2" 176 | }, 177 | { 178 | "type":"void**", 179 | "name":"io_device_axi_addr" 180 | } 181 | ] 182 | }, 183 | "100":{ 184 | "name":"SVC_UNMAP_FCH_IO_DEVICE", 185 | "args":[ 186 | { 187 | "type":"uint32_t", 188 | "name":"io_device" 189 | }, 190 | { 191 | "type":"void*", 192 | "name":"io_device_axi_addr" 193 | } 194 | ] 195 | }, 196 | "101":{ 197 | "name":"SVC_UPDATE_PSP_BIOS_DIR", 198 | "args":[ 199 | { 200 | "type":"uint32_t*", 201 | "name":"psp_dir_offset" 202 | }, 203 | { 204 | "type":"uint32_t*", 205 | "name":"bios_dir_offset" 206 | } 207 | ] 208 | }, 209 | "102":{ 210 | "name":"SVC_COPY_DATA_FROM_UAPP", 211 | "args":[ 212 | { 213 | "type":"void*", 214 | "name":"address" 215 | }, 216 | { 217 | "type":"uint32_t", 218 | "name":"size" 219 | } 220 | ] 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginmetadataversion": 2, 3 | "name": "AMD-SP/PSP Loader", 4 | "author": "dayzerosec", 5 | "type": [ 6 | "binaryview" 7 | ], 8 | "api": [ 9 | "python3" 10 | ], 11 | "description": "Loader for AMD-SP or PSP firmware binaries.", 12 | "longdescription": "# AMD-SP/PSP Loader \n\nAuthor: **SpecterDev**\n\n_Loader for AMD-SP or PSP firmware binaries._\n\n![](https://i.imgur.com/MH9C1hu.png)\n\n## Description\n\nBinary Ninja loader for AMD Secure Processor (SP) / Platform Security Processor (PSP) firmware binaries. It will try to load AGESA Bootloader (ABL) and Bootloader blobs and will setup the correct load addresses.\n\nThe ABL loader will also optionally annotate syscalls using the dictionary in `./data/syscalls.json`.\n\n## Usage\n\nThis loader is intended to be used with binaries extracted via [PSPTool](https://github.com/PSPReverse/PSPTool), as this loader will **not extract firmware from UEFI or perform any decompression before loading**.\n\nSimply load an `ABL*` or `PSP_FW_BOOTLOADER_*` binary to use the loader. Your view name on the top left of the disassembly pane should have an `AMD-SP` prefix. If your particular firmware blob doesn't load and/or loads at an incorrect address, please file an issue.\n\n## Future Work / Places for Contribution\n\n- [ ] Currently load addresses are static, perhaps this should be reworked to dynamically determine it via parsing entrypoint instructions?\n- [ ] Add loaders for other firmwares\n - [ ] SMU (xtensa)\n - [ ] Trusted OS (tOS)\n - [ ] Boot time trustlets\n- [ ] Reverse and add more syscalls to the annotation dictionary\n- [ ] Improve annotations to fix-up syscalls in HLIL\n\n## Notes\n\n- The loaders make some assumptions on the load address and such, so its possible a particular binary differs and won't load properly (open an issue).\n\n## Minimum Version\n\nThis plugin requires the following minimum version of Binary Ninja:\n * release - 3.2.3814\n\n## License\n\nThis plugin is released under a MIT license.\n", 13 | "license": { 14 | "name": "MIT", 15 | "text": "Copyright 2023 dayzerosec\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." 16 | }, 17 | "platforms": [ 18 | "Darwin", 19 | "Windows", 20 | "Linux" 21 | ], 22 | "installinstructions": { 23 | "Darwin": "", 24 | "Windows": "", 25 | "Linux": "" 26 | }, 27 | "version": "1.0", 28 | "minimumbinaryninjaversion": 3814 29 | } 30 | -------------------------------------------------------------------------------- /psp_file.py: -------------------------------------------------------------------------------- 1 | ''' 2 | File: psp_file.py 3 | Author(s): @SpecterDev, github.com/PSPReverse 4 | Purpose: Contains PSPFile helper class to parse AMD-SP/PSP file headers. 5 | Note: Much of the info here is undocumented and therefore reversed and not complete (and may not be accurate). 6 | A lot of it came from PSPReverse and their PSPTool. Check their repositories for more details. 7 | ''' 8 | import struct 9 | 10 | # PSP Magic constants + masks 11 | PSP_MAGIC_GENERIC = 0x24505331 # "$PS1" 12 | 13 | # PSP Magic Variant A (seen on ABL version <= 19.7.8.30) 14 | PSP_MAGIC_ABL_VARIANT_A = 0x00424157 # "[N]BAW" 15 | PSP_MAGIC_ABL_VARIANT_A_MASK = 0xFF000000 16 | PSP_MAGIC_ABL_VARIANT_A_SHIFT = 24 17 | 18 | # PSP Magic Variant B (seen on 19.8.12.0 <= ABL version <= 20.10.19.0) 19 | PSP_MAGIC_ABL_VARIANT_B = 0x41570042 # "AW[N]B" 20 | PSP_MAGIC_ABL_VARIANT_B_MASK = 0x0000FF00 21 | PSP_MAGIC_ABL_VARIANT_B_SHIFT = 8 22 | 23 | # PSP Directory Entry Types 24 | PSP_FW_BOOT_LOADER = 0x01 25 | PSP_FW_TRUSTED_OS = 0x02 26 | PSP_FW_RECOVERY_BOOT_LOADER = 0x03 27 | PSP_FW_BOOT_TIME_TRUSTLETS = 0x0C 28 | 29 | def swap32(i): 30 | return struct.unpack("I", i))[0] 31 | 32 | class PSPFile: 33 | ''' 34 | Layout of the PSP file header. Contributions to unknown fields + corrections would be appreciated. 35 | 36 | struct psp_file_header { 37 | char _unk_00h[0x10]; // 0x00 38 | char magic[0x4]; // 0x10 39 | uint32_t size_signed; // 0x14 40 | uint32_t is_encrypted; // 0x18 41 | char _unk_1Ch[0x4]; // 0x1C 42 | char aes_cbc_iv[0x10]; // 0x20 43 | uint32_t is_signed; // 0x30 44 | char _unk_34h[0x4]; // 0x34 45 | char signature_footprint[0x10]; // 0x38 46 | uint32_t is_compressed; // 0x48 47 | char _unk_4Ch[0x4]; // 0x4C 48 | uint32_t size_uncompressed; // 0x50 49 | uint32_t size_zlib; // 0x54 50 | char _unk_58h[0x8]; // 0x58 51 | uint32_t version; // 0x60 52 | char _unk_64h[0x4]; // 0x64 53 | uint32_t load_addr; // 0x68 54 | uint32_t rom_size; // 0x6C 55 | char _unk_70h[0x0C]; // 0x70 56 | uint32_t entry_type; // 0x7C - matches directory table entry types 57 | char wrapped_ikek[0x10]; // 0x80 58 | char _unk_90h[0x10]; // 0x90 59 | uint32_t metadata; // 0xA0 60 | char _unk_A4h[0x5C]; // 0xA4 61 | } // Size: 0x100 62 | ''' 63 | 64 | def __init__(self, f): 65 | # 0x10-0x1C 66 | (self.magic, self.size_signed, self.is_encrypted) = struct.unpack("= 0x30 and self.entry_type <= 0x37: 101 | return True 102 | 103 | # Fallback on metadata, if it's non-zero bytes, we can also probably assume it's an ABL binary 104 | if self.metadata != 0: 105 | return True 106 | 107 | return False 108 | 109 | def is_bootloader(self): 110 | if self.entry_type == PSP_FW_BOOT_LOADER or self.entry_type == PSP_FW_RECOVERY_BOOT_LOADER: 111 | return True 112 | return False 113 | 114 | def is_trusted_os(self): 115 | return self.entry_type == PSP_FW_TRUSTED_OS 116 | 117 | def is_boot_time_trustlets(self): 118 | return self.entry_type == PSP_FW_BOOT_TIME_TRUSTLETS 119 | 120 | def get_abl_num(self): 121 | # Sanity check 122 | if not self.is_abl(): 123 | return -1 124 | 125 | # Get via type field 126 | return self.entry_type - 0x30 127 | -------------------------------------------------------------------------------- /psp_types.py: -------------------------------------------------------------------------------- 1 | ''' 2 | File: psp_types.py 3 | Author(s): @SpecterDev, github.com/PSPReverse 4 | Purpose: Type library for PSP stuff 5 | Note: Much of the info here is undocumented and therefore reversed and not complete (and may not be accurate). 6 | A lot of it came from PSPReverse and their PSPTool. Check their repositories for more details. 7 | ''' 8 | import binaryninja as bn 9 | 10 | def create_header_struct_type(): 11 | struct_type = bn.types.StructureBuilder.create() 12 | 13 | struct_type.append(bn.Type.array(bn.Type.int(1), 0x10), '_unk_00h') 14 | struct_type.append(bn.Type.array(bn.Type.int(1), 0x4), 'magic') 15 | struct_type.append(bn.Type.int(4, False), 'size_signed') 16 | struct_type.append(bn.Type.int(4), 'is_encrypted') 17 | struct_type.append(bn.Type.array(bn.Type.int(1), 0x4), '_unk_1Ch') 18 | struct_type.append(bn.Type.array(bn.Type.int(1), 0x10), 'aes_cbc_iv') 19 | struct_type.append(bn.Type.int(4), 'is_signed') 20 | struct_type.append(bn.Type.array(bn.Type.int(1), 0x4), '_unk_34h') 21 | struct_type.append(bn.Type.array(bn.Type.int(1), 0x10), 'signature_footprint') 22 | struct_type.append(bn.Type.int(4), 'is_compressed') 23 | struct_type.append(bn.Type.array(bn.Type.int(1), 0x4), 'unk_4Ch') 24 | struct_type.append(bn.Type.int(4, False), 'size_uncompressed') 25 | struct_type.append(bn.Type.int(4, False), 'size_zlib') 26 | struct_type.append(bn.Type.array(bn.Type.int(1), 0x8), 'unk_58h') 27 | struct_type.append(bn.Type.int(4, False), 'version') 28 | struct_type.append(bn.Type.array(bn.Type.int(1), 0x4), 'unk_64h') 29 | struct_type.append(bn.Type.int(4, False), 'load_addr') 30 | struct_type.append(bn.Type.int(4, False), 'rom_size') 31 | struct_type.append(bn.Type.array(bn.Type.int(1), 0xC), 'unk_70h') 32 | struct_type.append(bn.Type.int(4, False), 'entry_type') 33 | struct_type.append(bn.Type.array(bn.Type.int(1), 0x10), 'wrapped_ikek') 34 | struct_type.append(bn.Type.array(bn.Type.int(1), 0x10), 'unk_90h') 35 | struct_type.append(bn.Type.int(4, False), 'metadata') 36 | struct_type.append(bn.Type.array(bn.Type.int(1), 0x5C), 'unk_A4h') 37 | 38 | return struct_type 39 | 40 | -------------------------------------------------------------------------------- /svc_annotate.py: -------------------------------------------------------------------------------- 1 | ''' 2 | File: svc_annotate.py 3 | Author(s): @SpecterDev 4 | Purpose: Implement svc annotation of functions post-analysis 5 | ''' 6 | import binaryninja as bn 7 | 8 | # Blue 9 | HIGHLIGHT_COLOR = [0, 115, 255] 10 | 11 | def annotate(funcs, db, bv): 12 | for fun in funcs: 13 | for bb in fun.basic_blocks: 14 | for txt in bb.get_disassembly_text(): 15 | if txt.tokens[0].text == "svc": 16 | # Highlight syscall lines because they're important 17 | color = bn.highlight.HighlightColor( 18 | red=HIGHLIGHT_COLOR[0], 19 | green=HIGHLIGHT_COLOR[1], 20 | blue=HIGHLIGHT_COLOR[2] 21 | ) 22 | 23 | fun.set_auto_instr_highlight(txt.address, color) 24 | svc_num = txt.tokens[-1].value 25 | 26 | # Lookup svc # in the database and comment 27 | if str(svc_num) in db: 28 | # Get svc info 29 | svc = db[str(svc_num)] 30 | svc_name = svc['name'] 31 | svc_args = svc['args'] 32 | 33 | # Build up comment and set 34 | comment_str = svc_name + '(' 35 | 36 | if len(svc['args']) > 0: 37 | last_arg = svc['args'][-1] 38 | for arg in svc['args']: 39 | comment_str += arg['type'] + ' ' + arg['name'] 40 | if arg != last_arg: 41 | comment_str += ', ' 42 | 43 | comment_str += ')' 44 | bv.set_comment_at(txt.address, comment_str) 45 | else: 46 | if svc_num > 0 and svc_num < 255: 47 | bn.log_warn(f"[AMD-SP ABL Loader] Don't have SVC #{svc_num} defined in dictionary (addr=0x{txt.address:08x}).") --------------------------------------------------------------------------------