This commit is contained in:
Dejvino
2026-09-30 22:48:06 +02:00
commit 8913cb4314
37 changed files with 3190 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileResults;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.InstructionIterator;
import ghidra.program.model.listing.Listing;
import java.io.FileWriter;
import java.io.PrintWriter;
/**
* DumpAIS.java — Ghidra headless postScript. Decompiles + disassembles
* checksum-hunt targets into /tmp/opencode/ghidra-lab/out/.
*/
public class DumpAIS extends GhidraScript {
static final String OUT = "/tmp/opencode/ghidra-lab/out";
static final String[] TARGETS = {
"C70A0A60", "C708E4E4", "C7086400", "C70A28A0", "C707841C"
};
@Override
public void run() throws Exception {
new java.io.File(OUT).mkdirs();
AddressSpace space = currentProgram.getAddressFactory()
.getDefaultAddressSpace();
Listing listing = currentProgram.getListing();
DecompInterface iface = new DecompInterface();
iface.openProgram(currentProgram);
for (String t : TARGETS) {
Address addr = space.getAddress(t);
Function fn = getFunctionAt(addr);
if (fn == null) {
try {
disassemble(addr);
}
catch (Exception e) {
println("[DumpAIS] " + t + " disassemble: " + e);
}
try {
fn = createFunction(addr, "sub_" + t);
}
catch (Exception e) {
println("[DumpAIS] " + t + " createFunction: " + e);
}
}
else {
println("[DumpAIS] " + t + " fn=" + fn.getName());
}
// disassembly window: 64B back, ~120 insns forward
try {
Address start = addr.addNoWrap(-64);
InstructionIterator it = listing.getInstructions(start, true);
PrintWriter pw = new PrintWriter(new FileWriter(
OUT + "/dis_" + t + ".txt"));
int n = 0;
while (it.hasNext() && n < 150) {
Instruction ins = it.next();
pw.println(String.format("%08X %s %s",
ins.getAddress().getOffset(),
ins.getMnemonicString(), ins.toString()));
n++;
if (ins.getAddress().compareTo(addr) > 0
&& n > 100) {
break;
}
}
pw.close();
}
catch (Exception e) {
println("[DumpAIS] " + t + " disasm dump: " + e);
}
if (fn != null) {
try {
DecompileResults res = iface.decompileFunction(
fn, 120, getMonitor());
String c = (res != null
&& res.getDecompiledFunction() != null)
? res.getDecompiledFunction().getC()
: "DECOMPILE_NULL";
PrintWriter pw = new PrintWriter(new FileWriter(
OUT + "/dec_" + t + ".txt"));
pw.print(c);
pw.close();
println("[DumpAIS] " + t + " decompiled "
+ (c == null ? 0 : c.length()) + " chars");
}
catch (Exception e) {
println("[DumpAIS] " + t + " decompile: " + e);
PrintWriter pw = new PrintWriter(new FileWriter(
OUT + "/dec_" + t + ".txt"));
pw.print("DECOMPILE_ERROR: " + e);
pw.close();
}
}
}
iface.dispose();
println("[DumpAIS] done -> " + OUT);
}
}
+67
View File
@@ -0,0 +1,67 @@
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.SourceType;
import java.io.File;
/**
* MapAIS.java — Ghidra headless preScript. Creates one memory block per
* AIS section of ic300_1.bin at its DDR load address, marks the AIS
* entry point. Paths are hardcoded (see tools/ais_unpack.py output).
*/
public class MapAIS extends GhidraScript {
static final String UNPACK = "/tmp/ais-unpack";
// {name, addrHex, len, file}
static final String[][] SEGS = {
{"ais0", "C7074630", "464", "ais_sect0_addrC7074630.bin"},
{"ais1", "C7074800", "211712", "ais_sect1_addrC7074800.bin"},
{"ais2", "C70A8300", "10444", "ais_sect2_addrC70A8300.bin"},
{"ais3", "C70AABD0", "12", "ais_sect3_addrC70AABD0.bin"},
{"ais4", "C70AABE0", "12", "ais_sect4_addrC70AABE0.bin"},
{"ais5", "C70AABF0", "3396", "ais_sect5_addrC70AABF0.bin"},
{"ais6", "C70AF000", "512", "ais_sect6_addrC70AF000.bin"},
{"ais7", "C70AF4E8", "260", "ais_sect7_addrC70AF4E8.bin"},
{"ais8", "C70AF5EC", "112", "ais_sect8_addrC70AF5EC.bin"},
{"ais9", "C70AF660", "3896", "ais_sect9_addrC70AF660.bin"},
};
static final String ENTRY = "C70A28A0";
@Override
public void run() throws Exception {
AddressSpace space = currentProgram.getAddressFactory()
.getDefaultAddressSpace();
Memory mem = currentProgram.getMemory();
for (String[] s : SEGS) {
Address addr = space.getAddress(s[1]);
if (mem.getBlock(addr) != null) {
println("[MapAIS] " + s[0] + " already mapped, skip");
continue;
}
File f = new File(UNPACK + "/" + s[3]);
long len = Long.parseLong(s[2]);
try {
mem.createInitializedBlock(s[0], addr, f, len,
"ais_unpack", "", false);
MemoryBlock blk = mem.getBlock(addr);
blk.setRead(true);
blk.setWrite(false);
blk.setExecute(true);
println("[MapAIS] mapped " + s[0] + " " + s[1]
+ " len=" + len);
}
catch (Exception e) {
println("[MapAIS] FAILED " + s[0] + ": " + e);
}
}
Address entry = space.getAddress(ENTRY);
currentProgram.getSymbolTable().addExternalEntryPoint(entry);
createLabel(entry, "ais_entry", true, SourceType.IMPORTED);
disassemble(entry);
createFunction(entry, "ais_entry");
println("[MapAIS] entry " + ENTRY + " marked");
}
}
+23
View File
@@ -0,0 +1,23 @@
# Headless Ghidra drivers (C6000 AIS analysis)
These drove the checksum extraction in `RE-PROCESS.md`. They need a local lab
that is **not** in this repo (too big, machine-specific):
- Temurin JDK 21, Ghidra 12.1.3, [geepot/ghidra-c6000](https://github.com/geepot/ghidra-c6000)
release built for that exact Ghidra version (`C6000:LE:32:default`).
- `pip install pyghidra` (project venv), `GHIDRA_INSTALL_DIR` + `JAVA_HOME` set.
Files:
| File | Role |
|---|---|
| `MapAIS.java` / `DumpAIS.java` | Reference only — Ghidra **12 headless cannot run `.java`
outside an OSGi bundle** here (not even the extension's own scripts); kept for GUI use |
| `pyais.py` | The working driver: map AIS sections at DDR bases → analyze → decompile
targets (`C70A0A60`, `C708E4E4`, `C7086400`, entry, CRC-16) into `out/` |
| `pyais4.py` | Dense disassembly seeding + full-range listing dumps |
| `pyais5.py` | Batch create-function + decompile for a target list |
Prepare segments with `../tools/ais_unpack.py ic300.bin /tmp/ais-unpack`
(IC300 dump stays local — never commit it), then adapt the hardcoded paths at
the top of `pyais.py` to your machine.
+155
View File
@@ -0,0 +1,155 @@
#!/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())
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""PyGhidra pass 4: dense disassembly seeding + full-range listing dumps.
Reuses /tmp/opencode/ghidra-lab/pyproj/LofiPy.
Writes /tmp/opencode/ghidra-lab/out/gfull_<name>.txt
"""
import os
OUT = "/tmp/opencode/ghidra-lab/out"
PROJ_LOC = "/tmp/opencode/ghidra-lab/pyproj/LofiPy"
PROJ_NAME = "LofiPy"
PROG = "/ais_sect1_addrC7074800.bin"
RANGES = {
"eloop": ("C708E400", 0x400),
"callers2": ("C709E700", 0x500),
"orch": ("C7086300", 0x900),
}
def main():
import pyghidra
pyghidra.start()
with pyghidra.open_project(PROJ_LOC, PROJ_NAME) as project:
with pyghidra.program_context(project, PROG) as program:
from ghidra.program.flatapi import FlatProgramAPI
flat = FlatProgramAPI(program)
with pyghidra.transaction(program, "seed"):
for name, (t, size) in RANGES.items():
base = int(t, 16)
n = 0
a = base
while a < base + size:
try:
flat.disassemble(flat.toAddr("0x%08X" % a))
n += 1
except Exception:
pass
a += 8
print("%s seeded %d" % (name, n), flush=True)
for name, (t, size) in RANGES.items():
try:
base = int(t, 16)
it = program.getListing().getInstructions(
flat.toAddr("0x%08X" % base), True)
lines = []
while it.hasNext():
ins = it.next()
off = ins.getAddress().getOffset()
if off >= base + size:
break
lines.append("%08X %s %s" % (
off, ins.getMnemonicString(), ins.toString()))
if len(lines) > 4000:
break
open(os.path.join(OUT, "gfull_" + name + ".txt"),
"w").write("\n".join(lines) + "\n")
print("%s: %d insns" % (name, len(lines)), flush=True)
except Exception as e:
print(name + " FAILED: " + str(e)[:200], flush=True)
print("GFULL_DONE", flush=True)
if __name__ == "__main__":
main()
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""PyGhidra pass 5: create + decompile check/orchestrator functions.
Reuses /tmp/opencode/ghidra-lab/pyproj/LofiPy.
Writes /tmp/opencode/ghidra-lab/out/fn_<ADDR>.c.txt
"""
import os
OUT = "/tmp/opencode/ghidra-lab/out"
PROJ_LOC = "/tmp/opencode/ghidra-lab/pyproj/LofiPy"
PROJ_NAME = "LofiPy"
PROG = "/ais_sect1_addrC7074800.bin"
FNS = ["C709E7C0", "C7086788", "C708E4A4", "C708E790", "C708E860",
"C709E888", "C708E468", "C709E990"]
def main():
import pyghidra
pyghidra.start()
with pyghidra.open_project(PROJ_LOC, PROJ_NAME) as project:
with pyghidra.program_context(project, PROG) as program:
from ghidra.program.flatapi import FlatProgramAPI
from ghidra.app.decompiler.flatapi import FlatDecompilerAPI
flat = FlatProgramAPI(program)
with pyghidra.transaction(program, "mkfn2"):
for t in FNS:
a = flat.toAddr("0x" + t)
try:
flat.disassemble(a)
except Exception as e:
print(t + " dis: " + str(e)[:100], flush=True)
try:
if flat.getFunctionAt(a) is None:
flat.createFunction(a, "fn_" + t)
print(t + " fn created", flush=True)
except Exception as e:
print(t + " mkfn: " + str(e)[:150], flush=True)
dapi = FlatDecompilerAPI(flat)
try:
for t in FNS:
try:
fn = flat.getFunctionAt(flat.toAddr("0x" + t))
if fn is None:
print(t + " no fn", flush=True)
continue
c = dapi.decompile(fn)
open(os.path.join(OUT, "fn_" + t + ".c.txt"),
"w").write(c if c else "DECOMPILE_NULL")
print(t + " %d chars" % (len(c) if c else 0),
flush=True)
except Exception as e:
print(t + " dec: " + str(e)[:200], flush=True)
finally:
dapi.dispose()
print("FN_DONE", flush=True)
if __name__ == "__main__":
main()