Files
lofi-12xt-firmware/ghidra/headless/pyais.py
T
2026-09-30 22:48:06 +02:00

156 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""PyGhidra driver: map AIS sections, analyze, decompile checksum targets.
Runs with the project venv (has pyghidra): ./venv/bin/python /tmp/opencode/ghidra-lab/pyais.py
Env: GHIDRA_INSTALL_DIR + JAVA_HOME must be set (see launch command).
Writes /tmp/opencode/ghidra-lab/out/{dis,dec}_<ADDR>.txt
Stdlib + pyghidra + jpype only.
"""
import os
import struct
import sys
UNPACK = "/tmp/ais-unpack"
OUT = "/tmp/opencode/ghidra-lab/out"
SECT_IMAGE = "/tmp/ais-unpack/ais_sect1_addrC7074800.bin"
PROJ_LOC = "/tmp/opencode/ghidra-lab/pyproj"
PROJ_NAME = "LofiPy"
LANG = "C6000:LE:32:default"
ENTRY = "C70A28A0"
TARGETS = ["C70A0A60", "C708E4E4", "C7086400", "C70A28A0", "C707841C"]
def parse_ais(path):
d = open(path, "rb").read()
assert d[:4] == b"TIPA", "bad AIS magic"
segs, i, n = [], 4, len(d)
entry = None
while i + 12 <= n:
op = d[i:i + 4]
if op == bytes([0x01, 0x59, 0x53, 0x58]):
addr, sz = struct.unpack("<II", d[i + 4:i + 12])
segs.append((addr, d[i + 12:i + 12 + sz]))
i += 12 + sz
elif op == bytes([0x06, 0x59, 0x53, 0x58]):
entry = struct.unpack("<I", d[i + 4:i + 8])[0]
break
else:
i += 4
return segs, entry
def main():
os.makedirs(OUT, exist_ok=True)
import jpype
import pyghidra
segs, entry = parse_ais("/var/home/dejvino/Downloads/Lofi-12XT/ic300.bin")
print("segs=%d entry=%08X" % (len(segs), entry), flush=True)
pyghidra.start()
with pyghidra.open_program(
SECT_IMAGE,
project_location=PROJ_LOC,
project_name=PROJ_NAME,
analyze=False,
language=LANG,
) as flat:
program = flat.getCurrentProgram()
print("program=" + program.getName()
+ " lang=" + program.getLanguageID().toString(), flush=True)
JByte = jpype.JArray(jpype.JByte)
try:
from java.io import ByteArrayInputStream
except ImportError:
import jpype.imports # noqa
from java.io import ByteArrayInputStream
from ghidra.util.task import TaskMonitor
from ghidra.program.model.symbol import SourceType
# 1. map blocks at DDR addresses
with pyghidra.transaction(program, "map AIS"):
mem = program.getMemory()
for idx, (addr_int, blob) in enumerate(segs):
addr = flat.toAddr("0x%08X" % addr_int)
if mem.getBlock(addr) is not None:
print("ais%d already mapped" % idx, flush=True)
continue
stream = ByteArrayInputStream(JByte(bytes(blob)))
blk = mem.createInitializedBlock(
"ais%d" % idx, addr, stream, len(blob),
TaskMonitor.DUMMY, False)
blk.setRead(True)
blk.setWrite(False)
blk.setExecute(True)
print("mapped ais%d %08X len=%d"
% (idx, addr_int, len(blob)), flush=True)
# 2. entry + analyze
with pyghidra.transaction(program, "entry"):
eaddr = flat.toAddr("0x" + ENTRY)
try:
program.getSymbolTable().addExternalEntryPoint(eaddr)
except Exception as e:
print("entry point: " + str(e), flush=True)
flat.disassemble(eaddr)
if flat.getFunctionAt(eaddr) is None:
flat.createFunction(eaddr, "ais_entry")
print("analyzing...", flush=True)
flat.analyzeAll(program)
print("analysis done", flush=True)
# 3. decompile + disassembly dump per target
from ghidra.app.decompiler.flatapi import FlatDecompilerAPI
dapi = FlatDecompilerAPI(flat)
try:
for t in TARGETS:
addr = flat.toAddr("0x" + t)
try:
fn = flat.getFunctionAt(addr)
if fn is None:
flat.disassemble(addr)
try:
fn = flat.createFunction(addr, "sub_" + t)
except Exception as e:
print(t + " createFunction: " + str(e),
flush=True)
# disassembly window
try:
start = flat.toAddr("0x%08X"
% (int(t, 16) - 64))
it = program.getListing().getInstructions(start,
True)
lines, n = [], 0
while it.hasNext() and n < 150:
ins = it.next()
lines.append("%08X %s %s" % (
ins.getAddress().getOffset(),
ins.getMnemonicString(), ins.toString()))
n += 1
open(os.path.join(OUT, "dis_" + t + ".txt"),
"w").write("\n".join(lines) + "\n")
except Exception as e:
print(t + " disasm: " + str(e), flush=True)
# decompile
if fn is not None:
try:
c = dapi.decompile(fn)
if not c:
c = "DECOMPILE_NULL"
open(os.path.join(OUT, "dec_" + t + ".txt"),
"w").write(c if c else "DECOMPILE_NULL")
print(t + " decompiled %d chars"
% (len(c) if c else 0), flush=True)
except Exception as e:
print(t + " decompile: " + str(e), flush=True)
open(os.path.join(OUT, "dec_" + t + ".txt"),
"w").write("DECOMPILE_ERROR: " + str(e))
except Exception as e:
print(t + " FAILED: " + str(e), flush=True)
finally:
dapi.dispose()
print("ALL_DONE -> " + OUT, flush=True)
if __name__ == "__main__":
sys.exit(main())