FIRM-Binja-Loader/firm.py

176 lines
7.1 KiB
Python

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