Files
openfut-launcher/tools/re/ghidra_env.py
T

152 lines
4.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""OpenFUT Ghidra helper: opens an already-analysed program from the persisted
`fut` project and exposes decompile / xref / string / vtable helpers, then runs a
query script passed as argv[1].
Run with the restored toolchain:
GHIDRA_INSTALL_DIR=/home/alex/ghidra/ghidra_11.1.2_PUBLIC \
/home/alex/re-venv/bin/python tools/re/ghidra_env.py <query.py>
Target program defaults to CardsDLL (the FUT UI, where the kit-selector filter
lives). Override for powdll (the EASFC/POW layer):
GHIDRA_PROG=powdll.dll ... ghidra_env.py <query.py>
"""
import os, sys
os.environ.setdefault("GHIDRA_INSTALL_DIR", "/home/alex/ghidra/ghidra_11.1.2_PUBLIC")
# Ghidra 11.1.2 does not bundle the in-tree PyGhidra module that the pip
# `pyghidra` 2.x/3.x require, so use the standalone `pyhidra` package (same API).
try:
import pyhidra as _pg
except ImportError:
import pyghidra as _pg
_pg.start(verbose=False)
from ghidra.app.decompiler import DecompInterface # noqa: E402
from ghidra.util.task import ConsoleTaskMonitor # noqa: E402
PROJ_DIR = os.environ.get("GHIDRA_PROJ_DIR", "/home/alex/ghidra_projects")
PROJ = os.environ.get("GHIDRA_PROJ", "fut")
PROG = os.environ.get("GHIDRA_PROG", "cardsdll.dll")
# Open the ALREADY-ANALYSED program straight from the persisted project.
# pyhidra.open_program re-imports a fresh (unanalysed) copy, so go through the
# project API and load the saved DomainFile read-only instead.
from ghidra.base.project import GhidraProject # noqa: E402
_project = GhidraProject.openProject(PROJ_DIR, PROJ, True)
prog = _project.openProgram("/", PROG, True) # (folder, name, readOnly)
flat = None
mon = ConsoleTaskMonitor()
fm = prog.getFunctionManager()
listing = prog.getListing()
mem = prog.getMemory()
refs = prog.getReferenceManager()
_dec = DecompInterface()
_dec.openProgram(prog)
def addr(a):
return prog.getAddressFactory().getDefaultAddressSpace().getAddress(int(a))
def func(a):
return fm.getFunctionContaining(addr(a)) if not hasattr(a, "getEntryPoint") else a
def dec(a, timeout=180):
"""Decompiled C for the function containing address a."""
f = func(a)
if f is None:
return "// no function at %#x" % int(a)
r = _dec.decompileFunction(f, timeout, mon)
if r is None or not r.decompileCompleted():
return "// decompile failed for %s" % f.getName()
return str(r.getDecompiledFunction().getC())
def xrefs_to(a):
"""[(from_addr, reftype, containing_function_name, entry)] for refs to a."""
out = []
for r in refs.getReferencesTo(addr(a)):
fr = r.getFromAddress()
f = fm.getFunctionContaining(fr)
out.append((int(fr.getOffset()), str(r.getReferenceType()),
f.getName() if f else "?",
int(f.getEntryPoint().getOffset()) if f else 0))
return out
def qword(a):
return mem.getLong(addr(a)) & 0xFFFFFFFFFFFFFFFF
def dword(a):
return mem.getInt(addr(a)) & 0xFFFFFFFF
import jpype # noqa: E402
_JBYTE = jpype.JArray(jpype.JByte)
def read_bytes(a, n):
buf = _JBYTE(n)
got = mem.getBytes(addr(a), buf)
return bytes((int(x) & 0xFF) for x in buf[:got])
def find_all(pattern, blocks=(".text", ".rdata", ".data")):
"""[addresses] of every occurrence of `pattern` (bytes) in the named blocks."""
if isinstance(pattern, str):
pattern = pattern.encode()
hits = []
for b in mem.getBlocks():
if b.getName() not in blocks:
continue
start = b.getStart()
size = int(b.getSize())
data = read_bytes(int(start.getOffset()), size)
i = data.find(pattern)
while i != -1:
hits.append(int(start.getOffset()) + i)
i = data.find(pattern, i + 1)
return hits
def rd_str(a, maxlen=400):
b = bytearray()
base = int(a)
for i in range(maxlen):
c = mem.getByte(addr(base + i)) & 0xFF
if c == 0:
break
b.append(c)
return b.decode("utf-8", "replace")
def fname(a):
f = func(a)
return f.getName() if f else "?"
def callees(a):
f = func(a)
return sorted({(int(c.getEntryPoint().getOffset()), c.getName())
for c in f.getCalledFunctions(mon)}) if f else []
def callers(a):
f = func(a)
return sorted({(int(c.getEntryPoint().getOffset()), c.getName())
for c in f.getCallingFunctions(mon)}) if f else []
if __name__ == "__main__":
if len(sys.argv) > 1:
with open(sys.argv[1]) as fh:
code = fh.read()
exec(compile(code, sys.argv[1], "exec"), globals())
os._exit(0)