Initial public release: dockerized reverse-engineering workbench

ChipBench packages Ghidra, radare2, binwalk, chip-programming tools,
simulators, and firmware-unpacking utilities into one reproducible container
for analyzing raw chip dumps entirely from the command line or an AI CLI.
Headless Jython scripts drive import, forced-disassembly sweeps, live
queries, and bulk decompilation exports without any GUI.

Derived from a private engagement environment, generalized for public
release under AGPLv3. No engagement-specific artifacts are included.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
This commit is contained in:
2026-08-18 11:15:05 -05:00
commit 1b447b77ad
28 changed files with 1860 additions and 0 deletions
@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
#
# Part of ChipBench (AGPLv3). SPDX-FileCopyrightText: 2026 Starting Line Productions LLC
# ExportAll.py -- Jython bulk export (no javac needed).
# Produces <prog>.functions.txt, .strings.txt, .segments.txt, .decompiled.c
import os
from ghidra.app.decompiler import DecompInterface
from ghidra.app.decompiler import DecompileOptions
outdir = os.environ.get("DECOMPILE_OUT", "/data/output")
prog = currentProgram
name = prog.getName()
fm = prog.getFunctionManager()
rm = prog.getReferenceManager()
print("ExportAll.py -> %s" % outdir)
# functions
with open(os.path.join(outdir, name + ".functions.txt"), "w") as fh:
for f in fm.getFunctions(True):
fh.write("%s @ %s\n" % (f.getName(), f.getEntryPoint()))
print("functions: %d" % fm.getFunctionCount())
# segments
with open(os.path.join(outdir, name + ".segments.txt"), "w") as fh:
for b in prog.getMemory().getBlocks():
fh.write("%s %s-%s %d %s%s\n" % (b.getName(), b.getStart(), b.getEnd(),
b.getSize(), "R" if b.isRead() else "-", "W" if b.isWrite() else "-"))
# strings (defined string data)
with open(os.path.join(outdir, name + ".strings.txt"), "w") as fh:
di = prog.getDataTypeManager()
listing = prog.getListing()
it = listing.getDefinedData(True)
n = 0
while it.hasNext():
d = it.next()
dt = d.getDataType()
if dt is not None and ("string" in dt.getName().lower() or "char" in dt.getName().lower()):
try:
v = d.getValue()
except:
v = None
if v is not None and len(str(v)) >= 3:
fh.write("%s: %r\n" % (d.getAddress(), str(v)))
n += 1
print("strings: %d" % n)
# decompile everything
ifc = DecompInterface()
ifc.setOptions(DecompileOptions())
ifc.openProgram(prog)
nf = 0
with open(os.path.join(outdir, name + ".decompiled.c"), "w") as fh:
fh.write("// Decompiled by ExportAll.py (Ghidra %s)\n" % "11.3.2")
for f in fm.getFunctions(True):
res = ifc.decompileFunction(f, 90, monitor)
if res.decompileCompleted():
fh.write("\n/* ==== %s @ %s ==== */\n" % (f.getName(), f.getEntryPoint()))
fh.write(res.getDecompiledFunction().getC())
nf += 1
else:
fh.write("\n/* ==== %s @ %s : DECOMPILE FAILED ==== */\n" % (f.getName(), f.getEntryPoint()))
ifc.dispose()
print("decompiled: %d" % nf)
print("ExportAll.py done")