Files
HaloKeymind/scripts/firmware_elf.py

66 lines
2.9 KiB
Python

"""Small, dependency-free reader for the 32-bit little-endian firmware ELFs."""
from pathlib import Path
import struct
class FirmwareElf:
def __init__(self, path):
self.data = Path(path).read_bytes()
if self.data[:7] != b"\x7fELF\x01\x01\x01" or len(self.data) < 52:
raise ValueError("expected a 32-bit little-endian firmware ELF")
offset = self.unpack("I", 32)[0]
entry_size, count, names_index = self.unpack("HHH", 46)
if entry_size != 40 or not count or names_index >= count:
raise ValueError("missing or invalid ELF section table")
self.sections = [self.unpack("10I", offset + i * entry_size) for i in range(count)]
self.symbols = {}
for section in self.sections:
if section[1] != 2: # SHT_SYMTAB
continue
if section[9] != 16 or section[5] % 16 or section[6] >= count:
raise ValueError("invalid ELF symbol table")
strings = self.section_bytes(self.sections[section[6]])
for pos in range(section[4], section[4] + section[5], 16):
name, value, size, info, _, index = self.unpack("IIIBBH", pos)
if not index or not name:
continue
if name >= len(strings):
raise ValueError("invalid ELF symbol name")
name = strings[name:].split(b"\0", 1)[0].decode("ascii")
# Prefer global/weak definitions to same-named local symbols.
if name not in self.symbols or info >> 4:
self.symbols[name] = (value, size, index)
if not self.symbols:
raise ValueError("firmware ELF has no defined symbols")
def unpack(self, fmt, offset):
try:
return struct.unpack_from("<" + fmt, self.data, offset)
except struct.error as error:
raise ValueError("truncated firmware ELF") from error
def section_bytes(self, section):
offset, size = section[4:6]
if offset + size > len(self.data):
raise ValueError("truncated ELF section")
return self.data[offset:offset + size]
def address(self, name):
if name not in self.symbols:
raise ValueError(f"missing ELF symbol {name}")
return self.symbols[name][0]
def read(self, address, size):
for section in self.sections:
if section[1] == 8 or not section[2] & 2: # NOBITS / not allocated
continue
start, length = section[3], section[5]
if start <= address and address + size <= start + length:
offset = address - start
return self.section_bytes(section)[offset:offset + size]
raise ValueError(f"ELF has no initialized data at {address:#x} ({size} bytes)")
def words(self, name, count=1):
return struct.unpack("<" + "I" * count, self.read(self.address(name), count * 4))