feat: FIRM loader for Binary Ninja

This commit is contained in:
Elise Amber Katze 2024-05-05 18:40:53 +02:00
commit f6a33e180e
Signed by: Elise Amber Katze
GPG key ID: FA8F56FFFE6E8B3E
6 changed files with 245 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.pyc

7
LICENSE Normal file
View file

@ -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.

31
README.md Normal file
View file

@ -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

4
__init__.py Normal file
View file

@ -0,0 +1,4 @@
from binaryninja import *
from .firm import *
FirmView.register()

175
firm.py Normal file
View file

@ -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

27
plugin.json Normal file
View file

@ -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
}