// Walk the call tree down from the export orchestrator and surface the page // serializer: flag functions that call file-I/O APIs or use the 4096 page size, // then decompile those candidates. Output -> ~/ghidra_proj/calltree.txt import java.io.PrintWriter; import java.util.*; import ghidra.app.script.GhidraScript; import ghidra.app.decompiler.*; import ghidra.program.model.listing.*; import ghidra.program.model.scalar.Scalar; public class CallTree extends GhidraScript { static final String ROOT = "0x14119d2d0"; static final int MAX_DEPTH = 4; boolean hasPageConst(Function f) { InstructionIterator it = currentProgram.getListing().getInstructions(f.getBody(), true); while (it.hasNext()) { Instruction insn = it.next(); for (int op = 0; op < insn.getNumOperands(); op++) for (Object o : insn.getOpObjects(op)) if (o instanceof Scalar) { long v = ((Scalar) o).getUnsignedValue(); if (v == 0x1000L || v == 4096L) return true; } } return false; } String ioCalls(Function f) { StringBuilder sb = new StringBuilder(); for (Function c : f.getCalledFunctions(monitor)) { String n = c.getName().toLowerCase(); if (n.contains("write") || n.contains("createfile") || n.contains("fopen") || n.contains("fwrite") || n.contains("mapviewof") || n.contains("setfilepointer") || n.contains("flushview") || n.contains("fclose")) sb.append(c.getName()).append(" "); } return sb.toString(); } @Override public void run() throws Exception { PrintWriter out = new PrintWriter("/home/ntr/ghidra_proj/calltree.txt"); FunctionManager fm = currentProgram.getFunctionManager(); Function root = fm.getFunctionContaining(currentProgram.getAddressFactory().getAddress(ROOT)); Map depth = new LinkedHashMap<>(); Deque q = new ArrayDeque<>(); depth.put(root, 0); q.add(root); while (!q.isEmpty() && !monitor.isCancelled()) { Function f = q.poll(); int d = depth.get(f); if (d >= MAX_DEPTH) continue; for (Function c : f.getCalledFunctions(monitor)) if (!depth.containsKey(c)) { depth.put(c, d + 1); q.add(c); } } out.println("call tree from " + root.getName() + " depth<=" + MAX_DEPTH + ": " + depth.size() + " funcs\n"); List candidates = new ArrayList<>(); for (Map.Entry e : depth.entrySet()) { Function f = e.getKey(); boolean page = hasPageConst(f); String io = ioCalls(f); String flags = (page ? "[4096]" : "") + (io.isEmpty() ? "" : "[io:" + io.trim() + "]"); out.println(String.format("d%d %-12s size=%-6d %s %s", e.getValue(), f.getEntryPoint(), f.getBody().getNumAddresses(), f.getName(), flags)); if (page || !io.isEmpty()) candidates.add(f); } DecompInterface di = new DecompInterface(); di.openProgram(currentProgram); out.println("\n===== CANDIDATE DECOMPILES (" + candidates.size() + ") ====="); for (Function f : candidates) { if (monitor.isCancelled()) break; out.println("\n//===== " + f.getName() + " @ " + f.getEntryPoint() + " ====="); DecompileResults r = di.decompileFunction(f, 60, monitor); if (r != null && r.decompileCompleted()) out.println(r.getDecompiledFunction().getC()); else out.println("// decompile failed"); } out.flush(); out.close(); println("wrote calltree.txt: " + depth.size() + " funcs, " + candidates.size() + " candidates"); } }