Initial public source release

Split reusable rendering and format support into vectorail-core and vectorail-gc.
This commit is contained in:
2026-08-02 17:05:27 +02:00
commit 831d96e562
109 changed files with 20558 additions and 0 deletions
@@ -0,0 +1,37 @@
// GhidraScript: find decompiled functions containing text, optionally limiting
// qualified function names to a second substring.
// Usage: DecompileAllSearch.java 0x6458 GameScene
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileResults;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionIterator;
public class DecompileAllSearch extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("DecompileAllSearch: needs text and optional function-name substring");
return;
}
String needle = args[0];
String nameNeedle = args.length > 1 ? args[1] : "";
DecompInterface decompiler = new DecompInterface();
decompiler.openProgram(currentProgram);
FunctionIterator functions = currentProgram.getFunctionManager().getFunctions(true);
while (functions.hasNext() && !monitor.isCancelled()) {
Function function = functions.next();
if (!nameNeedle.isEmpty() && !function.getName(true).contains(nameNeedle)) continue;
DecompileResults result = decompiler.decompileFunction(function, 20, monitor);
if (!result.decompileCompleted() || result.getDecompiledFunction() == null) continue;
String code = result.getDecompiledFunction().getC();
if (code == null || !code.contains(needle)) continue;
println(function.getEntryPoint() + " " + function.getName(true));
for (String line : code.split("\\R")) {
if (line.contains(needle)) println(" " + line.trim());
}
}
}
}
+65
View File
@@ -0,0 +1,65 @@
// GhidraScript: DecompileByAddr.java
// Usage (headless):
// analyzeHeadless <projDir> <projName> -process <progName> -noanalysis -readOnly \
// -scriptPath <path> -postScript DecompileByAddr.java 0x401000 0x...
//
// Prints decompiled C for the function at (or containing) each address.
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.listing.Function;
public class DecompileByAddr extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("DecompileByAddr: needs one or more addresses, e.g. 0x63ea70");
return;
}
DecompInterface decomp = new DecompInterface();
decomp.openProgram(currentProgram);
for (String a : args) {
long va;
try {
va = Long.decode(a);
} catch (Exception e) {
printerr("bad address: " + a);
continue;
}
Address addr = toAddr(va);
if (addr == null) {
printerr("addr not in program: " + a);
continue;
}
Function f = getFunctionContaining(addr);
if (f == null) f = getFunctionAt(addr);
println("================================================================================");
println("addr: " + addr);
if (f == null) {
println("(no function found)");
continue;
}
println("function: " + f.getName() + " @ " + f.getEntryPoint());
DecompileResults res = decomp.decompileFunction(f, 60, monitor);
if (!res.decompileCompleted()) {
println("(decompile failed)");
continue;
}
String c = res.getDecompiledFunction().getC();
// Keep output reasonable in headless logs; truncate if huge.
if (c != null && c.length() > 120000) {
c = c.substring(0, 120000) + "\n/* ... truncated ... */\n";
}
println(c);
}
}
}
@@ -0,0 +1,69 @@
// GhidraScript: decompile every caller of a function and print call-site context.
// Usage: DecompileCallContexts.java 0x0063c0f0 [context-lines]
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.listing.Function;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceIterator;
import java.util.LinkedHashMap;
import java.util.Map;
public class DecompileCallContexts extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("DecompileCallContexts: needs a function address");
return;
}
Address target = toAddr(Long.decode(args[0]));
Function targetFunction = getFunctionAt(target);
if (targetFunction == null) {
printerr("target is not a function");
return;
}
int context = args.length > 1 ? Integer.decode(args[1]) : 5;
Map<Address, Function> callers = new LinkedHashMap<>();
ReferenceIterator references =
currentProgram.getReferenceManager().getReferencesTo(target);
while (references.hasNext()) {
Reference reference = references.next();
Function caller = getFunctionContaining(reference.getFromAddress());
if (caller != null) callers.put(caller.getEntryPoint(), caller);
}
DecompInterface decompiler = new DecompInterface();
decompiler.openProgram(currentProgram);
String needle = targetFunction.getName();
for (Function caller : callers.values()) {
if (monitor.isCancelled()) break;
DecompileResults result = decompiler.decompileFunction(caller, 90, monitor);
if (!result.decompileCompleted() || result.getDecompiledFunction() == null) continue;
String[] lines = result.getDecompiledFunction().getC().split("\\R");
boolean[] selected = new boolean[lines.length];
for (int i = 0; i < lines.length; ++i) {
if (!lines[i].contains(needle)) continue;
for (int j = Math.max(0, i - context);
j <= Math.min(lines.length - 1, i + context); ++j) {
selected[j] = true;
}
}
println("================================================================================");
println(caller.getEntryPoint() + " " + caller.getName(true));
boolean gap = false;
for (int i = 0; i < lines.length; ++i) {
if (selected[i]) {
if (gap) println("...");
println(String.format("%5d %s", i + 1, lines[i]));
gap = false;
} else if (i > 0 && selected[i - 1]) {
gap = true;
}
}
}
}
}
+50
View File
@@ -0,0 +1,50 @@
// GhidraScript: decompile one function and print matching lines with context.
// Usage: DecompileSearch.java 0x005ed4c0 0x104 8
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileResults;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.Function;
public class DecompileSearch extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length < 2) {
printerr("DecompileSearch: needs address, text, and optional context-line count");
return;
}
Function function = getFunctionContaining(toAddr(Long.decode(args[0])));
if (function == null) {
printerr("function not found");
return;
}
int context = args.length > 2 ? Integer.decode(args[2]) : 5;
DecompInterface decomp = new DecompInterface();
decomp.openProgram(currentProgram);
DecompileResults result = decomp.decompileFunction(function, 120, monitor);
if (!result.decompileCompleted()) {
printerr("decompile failed");
return;
}
String[] lines = result.getDecompiledFunction().getC().split("\\R");
boolean[] selected = new boolean[lines.length];
for (int i = 0; i < lines.length; ++i) {
if (!lines[i].contains(args[1])) continue;
for (int j = Math.max(0, i - context); j <= Math.min(lines.length - 1, i + context); ++j) {
selected[j] = true;
}
}
println("function: " + function.getName() + " @ " + function.getEntryPoint());
boolean gap = false;
for (int i = 0; i < lines.length; ++i) {
if (selected[i]) {
if (gap) println("...");
println(String.format("%5d %s", i + 1, lines[i]));
gap = false;
} else if (i > 0 && selected[i - 1]) {
gap = true;
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
// GhidraScript: print little-endian dwords as hex, signed integers, and floats.
// Usage: DumpData.java 0x006fcbf0 32
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
public class DumpData extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length != 2) {
printerr("DumpData: needs address and dword count");
return;
}
Address base = toAddr(Long.decode(args[0]));
int count = Integer.decode(args[1]);
for (int i = 0; i < count; ++i) {
Address address = base.add(i * 4L);
int value = getInt(address);
println(address + " 0x" + String.format("%08x", value) +
" int=" + value + " float=" + Float.intBitsToFloat(value));
}
}
}
@@ -0,0 +1,38 @@
// GhidraScript: print instructions around one or more addresses.
// Usage: DumpInstructions.java 0x001b184c 0x001b1870
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Instruction;
public class DumpInstructions extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("DumpInstructions: needs one or more addresses");
return;
}
for (String arg : args) {
Instruction center = getInstructionContaining(toAddr(Long.decode(arg)));
println("=== " + arg + " ===");
if (center == null) {
println("(no instruction)");
continue;
}
Instruction cursor = center;
for (int i = 0; i < 18; ++i) {
Instruction previous = cursor.getPrevious();
if (previous == null) break;
cursor = previous;
}
for (int i = 0; i < 40 && cursor != null; ++i) {
Function function = getFunctionContaining(cursor.getAddress());
String marker = cursor.getAddress().equals(center.getAddress()) ? "=>" : " ";
println(marker + " " + cursor.getAddress() + " " +
(function == null ? "" : function.getName() + " ") + cursor);
cursor = cursor.getNext();
}
}
}
}
@@ -0,0 +1,42 @@
// GhidraScript: dump an array of 32-bit pointers as ASCII strings.
// Usage: DumpPointerStrings.java <address> [count]
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.mem.MemoryAccessException;
public class DumpPointerStrings extends GhidraScript {
private String readAscii(Address at) throws MemoryAccessException {
StringBuilder out = new StringBuilder();
for (int i = 0; i < 256; ++i) {
int value = getByte(at.add(i)) & 0xff;
if (value == 0) break;
if (value < 0x20 || value > 0x7e) return "<non-ascii>";
out.append((char)value);
}
return out.toString();
}
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("DumpPointerStrings: needs address and optional count");
return;
}
Address start = toAddr(Long.decode(args[0]));
int count = args.length > 1 ? Integer.decode(args[1]) : 32;
for (int i = 0; i < count; ++i) {
Address slot = start.add(i * 4L);
long raw = getInt(slot) & 0xffffffffL;
Address target = toAddr(raw);
String value;
try {
value = currentProgram.getMemory().contains(target) ? readAscii(target) : "<outside>";
} catch (Exception exc) {
value = "<invalid>";
}
println(String.format("%4d %s -> %08x %s", i, slot, raw, value));
}
}
}
+30
View File
@@ -0,0 +1,30 @@
// GhidraScript: DumpPointers.java
// Usage:
// analyzeHeadless <projDir> <projName> -process <progName> -noanalysis -readOnly \
// -scriptPath tools/ghidra_scripts -postScript DumpPointers.java 0x006f8990 16
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
public class DumpPointers extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length < 1) {
printerr("DumpPointers: needs address and optional count");
return;
}
Address at = toAddr(Long.decode(args[0]));
int count = args.length >= 2 ? Integer.decode(args[1]) : 32;
for (int i = 0; i < count; i++) {
Address slot = at.add(i * 4L);
long ptr = getInt(slot) & 0xffffffffL;
Address dst = toAddr(ptr);
Function f = getFunctionAt(dst);
if (f == null) f = getFunctionContaining(dst);
String name = f == null ? "" : f.getName() + " @ " + f.getEntryPoint();
println(String.format("%s +0x%02x -> %08x %s", slot, i * 4, ptr, name));
}
}
}
+26
View File
@@ -0,0 +1,26 @@
// GhidraScript: dump 32-bit words as hex, signed integer, and IEEE-754 float.
// Usage: DumpScalars.java <address> [count]
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
public class DumpScalars extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("DumpScalars: needs address and optional count");
return;
}
Address start = toAddr(Long.decode(args[0]));
int count = args.length > 1 ? Integer.decode(args[1]) : 1;
for (int i = 0; i < count; ++i) {
Address slot = start.add(i * 4L);
int raw = getInt(slot);
println(String.format(
"%s hex=%08x int=%d float=%.9g",
slot, raw, raw, Float.intBitsToFloat(raw)));
}
}
}
+28
View File
@@ -0,0 +1,28 @@
// GhidraScript: FindAddressRefs.java
// Usage: ... -postScript FindAddressRefs.java 0x401000 [...]
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.symbol.Reference;
public class FindAddressRefs extends GhidraScript {
@Override
public void run() throws Exception {
for (String arg : getScriptArgs()) {
Address target = toAddr(Long.decode(arg));
println("================================================================================");
println("target: " + target);
Reference[] refs = getReferencesTo(target);
println("refs: " + refs.length);
for (Reference ref : refs) {
Address from = ref.getFromAddress();
Function function = getFunctionContaining(from);
println(" " + from + " -> " +
(function == null ? "(no function)" :
function.getName() + " @ " + function.getEntryPoint()) +
" type=" + ref.getReferenceType());
}
}
}
}
@@ -0,0 +1,87 @@
// GhidraScript: FindD3DSetTransformSites.java
// Usage:
// analyzeHeadless <projDir> <projName> -process <progName> -noanalysis -readOnly \
// -scriptPath tools/ghidra_scripts -postScript FindD3DSetTransformSites.java [minAddr] [maxAddr]
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.scalar.Scalar;
public class FindD3DSetTransformSites extends GhidraScript {
private boolean hasScalar(Instruction ins, long want) {
for (int op = 0; op < ins.getNumOperands(); op++) {
for (Object obj : ins.getOpObjects(op)) {
if (obj instanceof Scalar) {
Scalar s = (Scalar)obj;
if (s.getUnsignedValue() == want || s.getSignedValue() == want) return true;
}
}
}
return false;
}
private boolean inRange(Address addr, Address min, Address max) {
if (min != null && addr.compareTo(min) < 0) return false;
if (max != null && addr.compareTo(max) > 0) return false;
return true;
}
private boolean followedByCall(Instruction ins, int maxSteps) {
Instruction cur = ins;
for (int i = 0; i < maxSteps; i++) {
cur = cur.getNext();
if (cur == null) return false;
if ("CALL".equals(cur.getMnemonicString())) return true;
}
return false;
}
private boolean nearbyPushState(Instruction ins) {
Instruction cur = ins;
for (int i = 0; i < 10; i++) {
cur = cur.getPrevious();
if (cur == null) break;
if (!"PUSH".equals(cur.getMnemonicString())) continue;
if (hasScalar(cur, 2) || hasScalar(cur, 3)) return true;
}
return false;
}
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
Address min = args.length > 0 ? toAddr(Long.decode(args[0])) : null;
Address max = args.length > 1 ? toAddr(Long.decode(args[1])) : null;
println("=== D3D SetTransform VIEW/PROJ candidates ===");
int hits = 0;
for (Instruction ins : currentProgram.getListing().getInstructions(true)) {
if (!inRange(ins.getAddress(), min, max)) continue;
if (!hasScalar(ins, 0xb0)) continue;
if (!followedByCall(ins, 3)) continue;
if (!nearbyPushState(ins)) continue;
hits++;
Function f = getFunctionContaining(ins.getAddress());
String fs = f == null ? "(no func)" : f.getName() + " @ " + f.getEntryPoint();
println("");
println("HIT " + hits + " " + ins.getAddress() + " " + fs);
Instruction cur = ins;
for (int i = 0; i < 10; i++) {
Instruction prev = cur.getPrevious();
if (prev == null) break;
cur = prev;
}
for (int i = 0; i < 18 && cur != null; i++) {
String mark = cur.getAddress().equals(ins.getAddress()) ? "=>" : " ";
println(mark + " " + cur.getAddress() + " " + cur);
cur = cur.getNext();
}
if (monitor.isCancelled()) break;
}
println("hits: " + hits);
}
}
@@ -0,0 +1,63 @@
// GhidraScript: FindFunctionCallers.java
// Usage (headless):
// analyzeHeadless <projDir> <projName> -process <progName> -noanalysis -readOnly \
// -scriptPath <path> -postScript FindFunctionCallers.java 0x0063ea70
//
// Prints the functions which contain callsites (or any references) to the given function entry.
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceIterator;
import java.util.LinkedHashSet;
public class FindFunctionCallers extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("FindFunctionCallers: needs one or more function entry addresses, e.g. 0x613710");
return;
}
for (String a : args) {
long va;
try {
va = Long.decode(a);
} catch (Exception e) {
printerr("bad address: " + a);
continue;
}
Address entry = toAddr(va);
if (entry == null) {
printerr("addr not in program: " + a);
continue;
}
println("=== callers for " + entry + " ===");
LinkedHashSet<String> callers = new LinkedHashSet<>();
ReferenceIterator it = currentProgram.getReferenceManager().getReferencesTo(entry);
int n = 0;
while (it.hasNext()) {
Reference r = it.next();
n++;
Function f = getFunctionContaining(r.getFromAddress());
if (f != null) {
callers.add(f.getName() + " @ " + f.getEntryPoint() + " (from " + r.getFromAddress() + ")");
} else {
callers.add("(no func) from " + r.getFromAddress());
}
if (n > 5000) break;
}
println("refs: " + n);
if (callers.isEmpty()) {
println("(none)");
} else {
for (String s : callers) println(" " + s);
}
println("");
}
}
}
+25
View File
@@ -0,0 +1,25 @@
// GhidraScript: list functions whose symbol name contains a substring.
// Usage: FindFunctions.java DrawMark
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionIterator;
public class FindFunctions extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("FindFunctions: needs a case-sensitive name substring");
return;
}
String needle = args[0];
FunctionIterator functions = currentProgram.getFunctionManager().getFunctions(true);
while (functions.hasNext() && !monitor.isCancelled()) {
Function function = functions.next();
if (function.getName(true).contains(needle)) {
println(function.getEntryPoint() + " " + function.getName(true));
}
}
}
}
+64
View File
@@ -0,0 +1,64 @@
// GhidraScript: FindScalarOps.java
// Usage:
// analyzeHeadless <projDir> <projName> -process <progName> -noanalysis -readOnly \
// -scriptPath tools/ghidra_scripts -postScript FindScalarOps.java 0x3b 0x3f800000
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.scalar.Scalar;
import java.util.LinkedHashSet;
public class FindScalarOps extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("FindScalarOps: needs one or more scalar values");
return;
}
for (String arg : args) {
long want;
try {
want = Long.decode(arg);
} catch (Exception e) {
printerr("bad scalar: " + arg);
continue;
}
println("=== scalar " + arg + " ===");
int hits = 0;
LinkedHashSet<String> funcs = new LinkedHashSet<>();
for (Instruction ins : currentProgram.getListing().getInstructions(true)) {
int n = ins.getNumOperands();
boolean matched = false;
for (int op = 0; op < n; op++) {
for (Object obj : ins.getOpObjects(op)) {
if (obj instanceof Scalar) {
Scalar s = (Scalar)obj;
long v = s.getUnsignedValue();
long sv = s.getSignedValue();
if (v == want || sv == want) {
matched = true;
break;
}
}
}
if (matched) break;
}
if (!matched) continue;
hits++;
Function f = getFunctionContaining(ins.getAddress());
String fs = f == null ? "(no func)" : f.getName() + " @ " + f.getEntryPoint();
funcs.add(fs);
if (hits <= 250) println(ins.getAddress() + " " + fs + " " + ins);
if (monitor.isCancelled()) break;
}
println("hits: " + hits);
for (String fs : funcs) println(" " + fs);
println("");
}
}
}
@@ -0,0 +1,76 @@
// GhidraScript: FindScalarWindow.java
// Usage:
// analyzeHeadless <projDir> <projName> -process <progName> -noanalysis -readOnly \
// -scriptPath tools/ghidra_scripts -postScript FindScalarWindow.java 0xb0 [minAddr] [maxAddr] [maxHits]
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.scalar.Scalar;
public class FindScalarWindow extends GhidraScript {
private boolean hasScalar(Instruction ins, long want) {
for (int op = 0; op < ins.getNumOperands(); op++) {
for (Object obj : ins.getOpObjects(op)) {
if (obj instanceof Scalar) {
Scalar s = (Scalar)obj;
if (s.getUnsignedValue() == want || s.getSignedValue() == want) return true;
}
}
}
return false;
}
private boolean inRange(Address addr, Address min, Address max) {
if (min != null && addr.compareTo(min) < 0) return false;
if (max != null && addr.compareTo(max) > 0) return false;
return true;
}
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("FindScalarWindow: needs scalar, optional minAddr maxAddr maxHits");
return;
}
long want = Long.decode(args[0]);
Address min = args.length > 1 ? toAddr(Long.decode(args[1])) : null;
Address max = args.length > 2 ? toAddr(Long.decode(args[2])) : null;
int maxHits = args.length > 3 ? Integer.decode(args[3]) : 120;
println("=== scalar window " + args[0] + " ===");
int hits = 0;
for (Instruction ins : currentProgram.getListing().getInstructions(true)) {
if (!inRange(ins.getAddress(), min, max)) continue;
if (!hasScalar(ins, want)) continue;
hits++;
Function f = getFunctionContaining(ins.getAddress());
String fs = f == null ? "(no func)" : f.getName() + " @ " + f.getEntryPoint();
println("");
println("HIT " + hits + " " + ins.getAddress() + " " + fs);
Instruction cur = ins;
for (int i = 0; i < 8; i++) {
Instruction prev = cur.getPrevious();
if (prev == null) break;
cur = prev;
}
for (int i = 0; i < 17 && cur != null; i++) {
String mark = cur.getAddress().equals(ins.getAddress()) ? "=>" : " ";
println(mark + " " + cur.getAddress() + " " + cur);
cur = cur.getNext();
}
if (hits >= maxHits) {
println("(truncated at " + maxHits + " hits)");
break;
}
if (monitor.isCancelled()) break;
}
println("hits: " + hits);
}
}
+96
View File
@@ -0,0 +1,96 @@
// GhidraScript: FindStringXrefs.java
// Usage (headless):
// analyzeHeadless <projDir> <projName> -process <progName> \
// -scriptPath <path> -postScript FindStringXrefs.java "<needle1>" ["<needle2>" ...]
//
// Prints the addresses where the ASCII needle is found (optionally with NUL terminator)
// and lists the functions that reference the string address.
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceIterator;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashSet;
public class FindStringXrefs extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("FindStringXrefs: needs at least one ASCII needle arg");
return;
}
Memory mem = currentProgram.getMemory();
Address min = mem.getMinAddress();
Address max = mem.getMaxAddress();
for (String needle : args) {
if (needle == null) continue;
if (needle.isEmpty()) continue;
println("=== needle: " + needle + " ===");
byte[] pat0 = needle.getBytes(StandardCharsets.US_ASCII);
byte[] pat1 = new byte[pat0.length + 1];
System.arraycopy(pat0, 0, pat1, 0, pat0.length);
pat1[pat1.length - 1] = 0;
// First try NUL-terminated.
int hits = 0;
Address at = min;
while (true) {
Address found = mem.findBytes(at, max, pat1, null, true, monitor);
if (found == null) break;
hits++;
dumpHit(found);
at = found.add(1);
}
// If no NUL-terminated matches, fall back to raw bytes search.
if (hits == 0) {
at = min;
while (true) {
Address found = mem.findBytes(at, max, pat0, null, true, monitor);
if (found == null) break;
hits++;
dumpHit(found);
at = found.add(1);
}
}
if (hits == 0) {
println("(no hits)");
}
println("");
}
}
private void dumpHit(Address strAddr) {
println("hit @ " + strAddr);
LinkedHashSet<String> funcs = new LinkedHashSet<>();
ReferenceIterator it = currentProgram.getReferenceManager().getReferencesTo(strAddr);
int nref = 0;
while (it.hasNext()) {
Reference r = it.next();
nref++;
Function f = getFunctionContaining(r.getFromAddress());
if (f != null) {
funcs.add(f.getName() + " @ " + f.getEntryPoint());
} else {
funcs.add("(no func) from " + r.getFromAddress());
}
if (nref > 2000) break; // avoid pathological spam
}
println("refs: " + nref);
for (String s : funcs) {
println(" " + s);
if (funcs.size() > 200) break;
}
}
}
+45
View File
@@ -0,0 +1,45 @@
// GhidraScript: FindSymbolRefs.java
// Usage:
// analyzeHeadless <projDir> <projName> -process <progName> -noanalysis -readOnly \
// -scriptPath <path> -postScript FindSymbolRefs.java D3DXMatrixLookAtLH ...
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolIterator;
public class FindSymbolRefs extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("FindSymbolRefs: needs symbol names");
return;
}
for (String needle : args) {
println("================================================================================");
println("symbol needle: " + needle);
boolean any = false;
SymbolIterator it = currentProgram.getSymbolTable().getAllSymbols(true);
while (it.hasNext() && !monitor.isCancelled()) {
Symbol s = it.next();
if (!s.getName(true).contains(needle)) continue;
any = true;
Address addr = s.getAddress();
println("symbol: " + s.getName(true) + " @ " + addr);
Reference[] refs = getReferencesTo(addr);
println("refs: " + refs.length);
for (Reference r : refs) {
Address from = r.getFromAddress();
Function f = getFunctionContaining(from);
String fn = f == null ? "(no function)" : f.getName() + " @ " + f.getEntryPoint();
println(" " + from + " -> " + fn + " type=" + r.getReferenceType());
}
}
if (!any) println("(no symbol hits)");
}
}
}
@@ -0,0 +1,78 @@
// GhidraScript: FindVirtualCallOffset.java
// Usage:
// analyzeHeadless <projDir> <projName> -process <progName> -noanalysis -readOnly \
// -scriptPath tools/ghidra_scripts -postScript FindVirtualCallOffset.java 0xc4 [minAddr] [maxAddr]
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.scalar.Scalar;
public class FindVirtualCallOffset extends GhidraScript {
private boolean hasScalar(Instruction ins, long want) {
for (int op = 0; op < ins.getNumOperands(); op++) {
for (Object obj : ins.getOpObjects(op)) {
if (obj instanceof Scalar) {
Scalar s = (Scalar)obj;
if (s.getUnsignedValue() == want || s.getSignedValue() == want) return true;
}
}
}
return false;
}
private boolean inRange(Address addr, Address min, Address max) {
if (min != null && addr.compareTo(min) < 0) return false;
if (max != null && addr.compareTo(max) > 0) return false;
return true;
}
private boolean followedByCall(Instruction ins) {
Instruction n = ins.getNext();
if (n != null && "CALL".equals(n.getMnemonicString())) return true;
n = n == null ? null : n.getNext();
return n != null && "CALL".equals(n.getMnemonicString());
}
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("FindVirtualCallOffset: needs vtable offset");
return;
}
long want = Long.decode(args[0]);
Address min = args.length > 1 ? toAddr(Long.decode(args[1])) : null;
Address max = args.length > 2 ? toAddr(Long.decode(args[2])) : null;
println("=== virtual call offset " + args[0] + " ===");
int hits = 0;
for (Instruction ins : currentProgram.getListing().getInstructions(true)) {
if (!inRange(ins.getAddress(), min, max)) continue;
if (!"MOV".equals(ins.getMnemonicString())) continue;
if (!hasScalar(ins, want)) continue;
if (!followedByCall(ins)) continue;
hits++;
Function f = getFunctionContaining(ins.getAddress());
String fs = f == null ? "(no func)" : f.getName() + " @ " + f.getEntryPoint();
println("");
println("HIT " + hits + " " + ins.getAddress() + " " + fs);
Instruction cur = ins;
for (int i = 0; i < 8; i++) {
Instruction prev = cur.getPrevious();
if (prev == null) break;
cur = prev;
}
for (int i = 0; i < 14 && cur != null; i++) {
String mark = cur.getAddress().equals(ins.getAddress()) ? "=>" : " ";
println(mark + " " + cur.getAddress() + " " + cur);
cur = cur.getNext();
}
if (monitor.isCancelled()) break;
}
println("hits: " + hits);
}
}
@@ -0,0 +1,62 @@
// GhidraScript: FindVtableOffsetCalls.java
// Usage:
// analyzeHeadless <projDir> <projName> -process <progName> -noanalysis -readOnly \
// -scriptPath tools/ghidra_scripts -postScript FindVtableOffsetCalls.java 0xb0
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.scalar.Scalar;
public class FindVtableOffsetCalls extends GhidraScript {
private boolean hasScalar(Instruction ins, long want) {
for (int op = 0; op < ins.getNumOperands(); op++) {
for (Object obj : ins.getOpObjects(op)) {
if (obj instanceof Scalar) {
Scalar s = (Scalar)obj;
if (s.getUnsignedValue() == want || s.getSignedValue() == want) return true;
}
}
}
return false;
}
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0) {
printerr("FindVtableOffsetCalls: needs one or more vtable offsets, e.g. 0xb0");
return;
}
for (String arg : args) {
long want = Long.decode(arg);
println("=== vtable call offset " + arg + " ===");
int hits = 0;
for (Instruction ins : currentProgram.getListing().getInstructions(true)) {
if (!"CALL".equals(ins.getMnemonicString())) continue;
if (!hasScalar(ins, want)) continue;
hits++;
Function f = getFunctionContaining(ins.getAddress());
String fs = f == null ? "(no func)" : f.getName() + " @ " + f.getEntryPoint();
println("");
println(ins.getAddress() + " " + fs + " " + ins);
Instruction prev = ins;
for (int i = 0; i < 10; i++) {
prev = prev.getPrevious();
if (prev == null) break;
println(" " + prev.getAddress() + " " + prev);
}
if (hits >= 300) {
println("(truncated at 300 hits)");
break;
}
if (monitor.isCancelled()) break;
}
println("hits: " + hits);
}
}
}
@@ -0,0 +1,28 @@
// GhidraScript: list functions whose entry points fall inside an address range.
// Usage: ListFunctionsRange.java 0x005b6700 0x005b8600
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionIterator;
public class ListFunctionsRange extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length != 2) {
printerr("ListFunctionsRange: needs minAddr maxAddr");
return;
}
Address min = toAddr(Long.decode(args[0]));
Address max = toAddr(Long.decode(args[1]));
FunctionIterator it = currentProgram.getFunctionManager().getFunctions(min, true);
while (it.hasNext() && !monitor.isCancelled()) {
Function function = it.next();
Address entry = function.getEntryPoint();
if (entry.compareTo(max) > 0) break;
println(entry + " " + function.getName() + " size=0x" +
Long.toHexString(function.getBody().getNumAddresses()));
}
}
}
+17
View File
@@ -0,0 +1,17 @@
// GhidraScript: print little-endian 32-bit words and their float view.
// Usage: ReadScalars.java 0x2703f8 0x2703fc
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
public class ReadScalars extends GhidraScript {
@Override
public void run() throws Exception {
for (String arg : getScriptArgs()) {
Address address = toAddr(Long.decode(arg));
int word = getInt(address);
println(address + " u32=0x" + Integer.toHexString(word) +
" f32=" + Float.intBitsToFloat(word));
}
}
}
+28
View File
@@ -0,0 +1,28 @@
// GhidraScript: rename functions from address/name pairs.
// Usage: RenameFunctions.java 0x00100000 FunctionName 0x00100100 OtherName
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.symbol.SourceType;
public class RenameFunctions extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args == null || args.length == 0 || (args.length & 1) != 0) {
printerr("RenameFunctions: needs address/name pairs");
return;
}
for (int i = 0; i < args.length; i += 2) {
Address address = toAddr(Long.decode(args[i]));
Function function = getFunctionAt(address);
if (function == null) {
printerr("no function at " + address);
continue;
}
function.setName(args[i + 1], SourceType.USER_DEFINED);
println(address + " " + function.getName());
}
}
}