From f6a33e180ee77d951fa9fd72647d2b8db75a9dbb Mon Sep 17 00:00:00 2001 From: Elise Amber Katze Date: Sun, 5 May 2024 18:40:53 +0200 Subject: [PATCH] feat: FIRM loader for Binary Ninja --- .gitignore | 1 + LICENSE | 7 +++ README.md | 31 ++++++++++ __init__.py | 4 ++ firm.py | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++ plugin.json | 27 ++++++++ 6 files changed, 245 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 __init__.py create mode 100644 firm.py create mode 100644 plugin.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c023970 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2024 EliseZeroTwo + +Permission 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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE 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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bfe9312 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# FIRM-Binja-Loader + +### Author: **EliseZeroTwo** + +Binary Ninja Loader for 3DS FIRMs + +## Installation Instructions + +### Windows + +Clone this repository into `%APPDATA%/Binary Ninja/plugins/` + +### Darwin + +Clone this repository into `~/Library/Application Support/Binary Ninja/plugins/` + +### Linux + +Clone this repository into `~/.binaryninja/plugins/` + +## Minimum Version + +Binary Ninja v1200 + +## License + +This plugin is released under the [MIT License](./LICENSE) + +## Metadata Version + +2 diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..ddc3df8 --- /dev/null +++ b/__init__.py @@ -0,0 +1,4 @@ +from binaryninja import * +from .firm import * + +FirmView.register() \ No newline at end of file diff --git a/firm.py b/firm.py new file mode 100644 index 0000000..13cc0ef --- /dev/null +++ b/firm.py @@ -0,0 +1,175 @@ +from binaryninja import * + +class Segment: + name = "" + address = 0 + size = 0 + + def __init__(self, address, size, name): + self.address = address + self.size = size + self.name = name + +ARM11_SEGMENTS = [ + Segment(0x00000000, 0x00010000, "BOOTROM"), + Segment(0x00010000, 0x00010000, "BOOTROM Mirror"), + Segment(0x10000000, 0x07E00000, "IO"), + Segment(0x17E00000, 0x00002000, "MPCore Private Region"), + Segment(0x17E10000, 0x00001000, "N3DS L2C-310 L2 Cache Controller"), + Segment(0x18000000, 0x00600000, "VRAM"), + Segment(0x1F000000, 0x00400000, "N3DS Extra WRAM"), + Segment(0x1FF00000, 0x00080000, "DSP"), + Segment(0x1FF80000, 0x00080000, "AXI WRAM"), + Segment(0x20000000, 0x08000000, "FCRAM"), + Segment(0x28000000, 0x08000000, "N3DS Extra FCRAM"), + Segment(0xFFFF0000, 0x00010000, "BOOTROM Mirror"), +] + +ARM9_SEGMENTS = [ + Segment(0x00000000, 0x08000000, "Instruction TCM"), + Segment(0x01FF8000, 0x00008000, "Instruction TCM Kernel & Process Access"), + Segment(0x07FF8000, 0x00008000, "Instruction TCM BOOTROM Access"), + Segment(0x08000000, 0x00100000, "WRAM"), + Segment(0x08100000, 0x00080000, "N3DS Extra WRAM (enabled by CONFIG)"), + Segment(0x10000000, 0x08000000, "IO Memory"), + Segment(0x18000000, 0x00600000, "VRAM"), + Segment(0x1FF00000, 0x00080000, "DSP"), + Segment(0x1FF80000, 0x00080000, "AXI WRAM"), + Segment(0x20000000, 0x08000000, "FCRAM"), + Segment(0x28000000, 0x08000000, "N3DS Extra FCRAM"), + Segment(0xFFF00000, 0x00004000, "Data TCM"), + Segment(0xFFFF0000, 0x00010000, "BOOTROM"), +] + +class FirmSectionHeader: + byte_offset = 0 + phys_addr = 0 + byte_len = 0 + copy_method = 0 + sha256_hash = bytearray(b'\x00'*0x20) + + def __init__(self, reader): + self.byte_offset = reader.read32() + self.phys_addr = reader.read32() + self.byte_len = reader.read32() + self.copy_method = reader.read32() + self.sha256_hash = reader.read(0x20) + + + +class FirmHeader: + arm11_entrypoint = 0 + arm9_entrypoint = 0 + sections = [] + + def __init__(self, reader): + reader.seek(0x8) + self.arm11_entrypoint = reader.read32() + self.arm9_entrypoint = reader.read32() + + for idx in range(0, 4): + self.sections.append(FirmSectionHeader(reader)) + +class FirmView(BinaryView): + long_name = "3DS FIRM" + name = "FIRM" + header = None + section_header = None + is_arm11 = False + + def log(self, msg, error=False, warn=False): + msg = f"[FIRM-Loader] {msg}" + if error: + log_error(msg) + elif warn: + log_warn(msg) + else: + log_info(msg) + + def perform_get_address_size(self): + return 4 + + def perform_is_executable(self): + return True + + def perform_get_entry_point(self): + return self.header.arm11_entrypoint if self.is_arm11 else self.header.arm9_entrypoint + + def __init__(self, data): + self.raw = data + self.reader = BinaryReader(data, Endianness.LittleEndian) + self.writer = BinaryWriter(data, Endianness.LittleEndian) + BinaryView.__init__(self, file_metadata=data.file, parent_view=data) + + @classmethod + def is_valid_for_data(cls, data): + return data.read(0, 4) == b'FIRM' + + def init(self): + self.arch = Architecture["armv7"] + self.platform = Architecture["armv7"].standalone_platform + + self.header = FirmHeader(self.reader) + + arm9_header = None + arm11_header = None + + for section in self.header.sections: + if section.byte_len == 0: + continue + + start = section.phys_addr + end = start + section.byte_len + already_set = False + + if start <= self.header.arm9_entrypoint and end > self.header.arm9_entrypoint: + already_set = True + arm9_header = section + + if start <= self.header.arm11_entrypoint and end > self.header.arm11_entrypoint: + arm11_header = section + if already_set: + self.log("ARM9 and ARM11 sections overlap", warn=True) + + if arm9_header == None and arm11_header == None: + self.log("FIRM contains no valid section to load", error=True) + + if arm9_header != None and arm11_header == None: + self.log("FIRM contains only an ARM9 section, loading it") + self.section_header = arm9_header + elif arm9_header == None and arm11_header != None: + self.log("FIRM contains only an ARM11 section, loading it") + self.section_header = arm11_header + else: + self.is_arm11 = get_choice_input("Which FIRM?", "Multiple FIRMs found", ["ARM9", "ARM11"]) != 0 + if self.is_arm11: + self.section_header = arm11_header + else: + self.section_header = arm9_header + self.log(f"Loading {'ARM11' if self.is_arm11 else 'ARM9'} FIRM from file: {hex(self.section_header.byte_offset)} to {hex(self.section_header.phys_addr)} with len {hex(self.section_header.byte_len)}") + + self.add_auto_segment(self.section_header.phys_addr, self.section_header.byte_len, self.section_header.byte_offset, self.section_header.byte_len, SegmentFlag.SegmentExecutable | SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable | SegmentFlag.SegmentContainsCode | SegmentFlag.SegmentContainsData) + entrypoint = self.header.arm11_entrypoint if self.is_arm11 else self.header.arm9_entrypoint + self.add_entry_point(entrypoint) + self.add_function(entrypoint, self.platform) + self.define_auto_symbol(Symbol(SymbolType.FunctionSymbol, entrypoint, "_start")) + + mappings = ARM11_SEGMENTS if self.is_arm11 else ARM9_SEGMENTS + for mapping in mappings: + if mapping.address <= self.section_header.phys_addr and (mapping.address + mapping.size) >= (self.section_header.phys_addr + self.section_header.byte_len): + lower_start = mapping.address + lower_length = self.section_header.phys_addr - mapping.address + + if lower_length > 0: + self.add_user_segment(lower_start, lower_length, 0, 0, SegmentFlag.SegmentExecutable | SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable | SegmentFlag.SegmentContainsCode | SegmentFlag.SegmentContainsData) + + upper_start = self.section_header.phys_addr + self.section_header.byte_len + upper_length = (mapping.address + mapping.size) - upper_start + + if upper_length > 0: + self.add_user_segment(upper_start, upper_length, 0, 0, SegmentFlag.SegmentExecutable | SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable | SegmentFlag.SegmentContainsCode | SegmentFlag.SegmentContainsData) + else: + self.add_user_segment(mapping.address, mapping.size, 0, 0, SegmentFlag.SegmentExecutable | SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable | SegmentFlag.SegmentContainsCode | SegmentFlag.SegmentContainsData) + + self.add_user_section(mapping.name, mapping.address, mapping.size) + return True diff --git a/plugin.json b/plugin.json new file mode 100644 index 0000000..3af257c --- /dev/null +++ b/plugin.json @@ -0,0 +1,27 @@ +{ + "pluginmetadataversion" : 2, + "name": "FIRM-Loader", + "type": ["binaryview", "helper"], + "api": ["python3"], + "description": "3DS FIRM Loader", + "longdescription": "3DS FIRM Loader", + "license": { + "name": "MIT", + "text": "Copyright (c) 2024 EliseZeroTwo\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." + }, + "platforms" : ["Darwin", "Linux", "Windows"], + "installinstructions" : { + "Darwin" : "", + "Linux" : "", + "Windows" : "" + }, + "dependencies": { + "pip": [], + "apt": [], + "installers": [], + "other": [] + }, + "version": "1.0.0", + "author": "EliseZeroTwo", + "minimumbinaryninjaversion": 1200 +}