diff --git a/codecut-gui/ghidra_scripts/OutputObjFile.py b/codecut-gui/ghidra_scripts/OutputObjFile.py index ab7a4e2..aded97d 100755 --- a/codecut-gui/ghidra_scripts/OutputObjFile.py +++ b/codecut-gui/ghidra_scripts/OutputObjFile.py @@ -1,3 +1,6 @@ +#@category CodeCut +#@runtime PyGhidra +# ## Copyright 2022 The Johns Hopkins University Applied Physics Laboratory LLC ## (JHU/APL). All Rights Reserved. # diff --git a/codecut-gui/ghidra_scripts/__pycache__/ModNamingRun.cpython-312.pyc b/codecut-gui/ghidra_scripts/__pycache__/ModNamingRun.cpython-312.pyc deleted file mode 100644 index 15e90c4..0000000 Binary files a/codecut-gui/ghidra_scripts/__pycache__/ModNamingRun.cpython-312.pyc and /dev/null differ diff --git a/codecut-gui/ghidra_scripts/__pycache__/dependency_bootstrap.cpython-312.pyc b/codecut-gui/ghidra_scripts/__pycache__/dependency_bootstrap.cpython-312.pyc deleted file mode 100644 index bd478b1..0000000 Binary files a/codecut-gui/ghidra_scripts/__pycache__/dependency_bootstrap.cpython-312.pyc and /dev/null differ diff --git a/codecut-gui/ghidra_scripts/__pycache__/modnaming.cpython-312.pyc b/codecut-gui/ghidra_scripts/__pycache__/modnaming.cpython-312.pyc deleted file mode 100644 index 00c4455..0000000 Binary files a/codecut-gui/ghidra_scripts/__pycache__/modnaming.cpython-312.pyc and /dev/null differ diff --git a/codecut-gui/ghidra_scripts/decomprange.py b/codecut-gui/ghidra_scripts/decomprange.py index 5851964..2cb853e 100644 --- a/codecut-gui/ghidra_scripts/decomprange.py +++ b/codecut-gui/ghidra_scripts/decomprange.py @@ -35,11 +35,11 @@ def decompile_user_functions_in_range( current_program.getAddressFactory().getAddress(end_address_str) if start_address is None or end_address is None: - print 'Invalid address range specified.' + print('Invalid address range specified.') return if start_address >= end_address: - print 'Invalid address range: start address should be less than end address.' + print('Invalid address range: start address should be less than end address.') return decompiler = DecompInterface() diff --git a/codecut-gui/ghidra_scripts/funcsig.py b/codecut-gui/ghidra_scripts/funcsig.py index 830f12a..b92cfec 100644 --- a/codecut-gui/ghidra_scripts/funcsig.py +++ b/codecut-gui/ghidra_scripts/funcsig.py @@ -1,3 +1,4 @@ +# @category CodeCut from ghidra.program.model.listing import Function from ghidra.program.model.symbol import SourceType @@ -39,6 +40,7 @@ def get_referenced_function_signatures_base(function, monitor): return signatures + def getFunctionReferences(function, monitor): refs = set() instructions = \ @@ -47,9 +49,10 @@ def getFunctionReferences(function, monitor): for instr in instructions: flowType = instr.getFlowType() if flowType.isCall(): - target = instr.getOperandReferences(0)[0].getToAddress() - func = \ - function.getProgram().getFunctionManager().getFunctionAt(target) + oprefs = instr.getOperandReferences(0) + if not oprefs: continue + target = oprefs[0].getToAddress() + func = function.getProgram().getFunctionManager().getFunctionAt(target) if func is not None: refs.add(func) return refs diff --git a/codecut-gui/ghidra_scripts/globalvars.py b/codecut-gui/ghidra_scripts/globalvars.py index 0895980..e9599ab 100644 --- a/codecut-gui/ghidra_scripts/globalvars.py +++ b/codecut-gui/ghidra_scripts/globalvars.py @@ -17,7 +17,7 @@ def get_global_variables(program, start_addr, end_addr): #set.addRange(start_addr, end_addr) print(start_address, end_address) print(addrset) - + #for symbol in symbol_table.getAllSymbols(False): for symbol in symbol_table.getSymbols(addrset,SymbolType.LABEL,True): print(symbol) @@ -27,30 +27,30 @@ def get_global_variables(program, start_addr, end_addr): if (program.getListing().getDataAt(symbol.getAddress())): global_vars.append(symbol) - ''' + ''' def is_user_defined(var): var_name = var.getName() var_addr = var.getAddress() - + if var_name.startswith('__') or var_name.startswith('_'): return False - + if var_name.startswith('imp_') or var_name.startswith('thunk_'): return False - + if var_name.startswith('fde_') or var_name.startswith('cie_'): return False - + if var_name.startswith('completed.0') \ or var_name.startswith('data_start'): return False - + if var_addr.toString().startswith('EXTERNAL:'): return False section_name = program.getMemory().getBlock(var_addr).getName() #if section_name not in ['.data', '.bss']: # return False - + return True ''' diff --git a/codecut-gui/ghidra_scripts/modnaming.py b/codecut-gui/ghidra_scripts/modnaming.py index 0f88a4d..c17d736 100644 --- a/codecut-gui/ghidra_scripts/modnaming.py +++ b/codecut-gui/ghidra_scripts/modnaming.py @@ -21,6 +21,8 @@ # under Contract Number N66001-20-C-4024. # +import sys +print(sys.executable) import sys import math diff --git a/codecut-gui/ghidra_scripts/range.py b/codecut-gui/ghidra_scripts/range.py index e557367..20e338c 100644 --- a/codecut-gui/ghidra_scripts/range.py +++ b/codecut-gui/ghidra_scripts/range.py @@ -1,4 +1,6 @@ -#@category AMP-Improved +#@category CodeCut +#@runtime PyGhidra + from generate_c import generate_recompilable_c_code import os from ghidra.util.task import TaskMonitor diff --git a/codecut-gui/src/main/java/codecutguiv2/CodeCutGUIPlugin.java b/codecut-gui/src/main/java/codecutguiv2/CodeCutGUIPlugin.java index 55a8343..7d6fc01 100644 --- a/codecut-gui/src/main/java/codecutguiv2/CodeCutGUIPlugin.java +++ b/codecut-gui/src/main/java/codecutguiv2/CodeCutGUIPlugin.java @@ -50,6 +50,7 @@ import docking.tool.ToolConstants; import docking.widgets.OptionDialog; import docking.widgets.filechooser.GhidraFileChooser; import functioncalls.plugin.FcgProvider; +import generic.jar.ResourceFile; import ghidra.app.CorePluginPackage; import ghidra.app.context.ProgramActionContext; import ghidra.app.context.ProgramContextAction; @@ -58,9 +59,12 @@ import ghidra.app.events.ProgramActivatedPluginEvent; import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; +import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.plugin.core.decompile.DecompilerProvider; import ghidra.app.plugin.core.symboltree.actions.*; import ghidra.app.script.GhidraScript; +import ghidra.app.script.GhidraScriptProvider; +import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraState; import ghidra.app.services.BlockModelService; import ghidra.app.services.GoToService; @@ -88,8 +92,7 @@ import ghidra.program.util.DefinedStringIterator; import ghidra.program.util.GhidraProgramUtilities; import ghidra.program.util.ProgramChangeRecord; import ghidra.program.util.ProgramLocation; -import ghidra.jython.GhidraJythonInterpreter; -import ghidra.jython.JythonScript; +import ghidra.program.util.ProgramSelection; import ghidra.util.HTMLUtilities; import ghidra.util.HelpLocation; import ghidra.util.Msg; @@ -1222,7 +1225,7 @@ public class CodeCutGUIPlugin extends ProgramPlugin implements DomainObjectListe } } - private class CExporter extends JythonScript{ + private class CExporter extends GhidraScript{ Program program = GhidraProgramUtilities.getCurrentProgram(tool); GhidraState state = new GhidraState(tool, tool.getProject(), program, null, null, null); String start_addr; @@ -1234,7 +1237,6 @@ public class CodeCutGUIPlugin extends ProgramPlugin implements DomainObjectListe this.start_addr = start; this.end_addr = end; this.outfile = file.getAbsolutePath(); - this.state.addEnvironmentVar("ghidra.python.interpreter", GhidraJythonInterpreter.get()); this.path = this.outfile.substring(0, this.outfile.lastIndexOf("/")+1); } @Override @@ -1245,7 +1247,8 @@ public class CodeCutGUIPlugin extends ProgramPlugin implements DomainObjectListe String[] args = {start_addr, end_addr, outfile}; try { //runScript("ghidra2dwarf.py", args); - runScript("range.py",args); + runScript("range.py", args); + } catch (Exception e) { e.printStackTrace(); } diff --git a/codecut-gui/src/main/java/codecutguiv2/ModNamingAnalyzer.java b/codecut-gui/src/main/java/codecutguiv2/ModNamingAnalyzer.java index c5c35ec..f31037c 100644 --- a/codecut-gui/src/main/java/codecutguiv2/ModNamingAnalyzer.java +++ b/codecut-gui/src/main/java/codecutguiv2/ModNamingAnalyzer.java @@ -29,13 +29,14 @@ package codecutguiv2; +import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.regex.Pattern; +import ghidra.app.script.GhidraScriptLoadException; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSetView; @@ -180,7 +181,7 @@ public class ModNamingAnalyzer { } - String suggestedName = ModNamingLauncher.runFileMode(currentProgram, set, allStrings, monitor); + String suggestedName = ModNamingLauncher.execute(currentProgram, set, allStrings, monitor); //if name is "unknown" (e.g. modnaming found no repeated strings) don't bother renaming if (suggestedName.equals("unknown")) { @@ -245,3 +246,34 @@ public class ModNamingAnalyzer { } + + + + +class ModNamingLauncher extends PyGhidraFileLauncher { + + @Override protected String getScriptName() { return "ModNamingRun.py"; } + @Override protected String getInfilePrefix() { return "modnaming_in"; } + @Override protected String getOutfilePrefix() { return "modnaming_out"; } + @Override protected String getInfileSuffix() { return ".txt"; } + @Override protected String getOutfileSuffix() { return ".txt"; } + + // No extra args needed + @Override protected String[] getExtraArgs(ghidra.framework.plugintool.PluginTool tool) { return new String[0]; } + + // Convenience static method to preserve your old call-site signature + public static String execute(Program program, + AddressSetView set, + String inputPayload, + TaskMonitor monitor) + throws IllegalAccessException, FileNotFoundException, GhidraScriptLoadException { + return new ModNamingLauncher().runFileMode(program, set, inputPayload, monitor); + } + + @Override protected String getLaunchFailDialogTitle() { return "Module Naming Failed to Launch"; } + @Override protected String getLaunchFailDialogBodyPrefix() { + return "Module Naming requires the PyGhidra extension.\n\nDetails: "; + } +} + + diff --git a/codecut-gui/src/main/java/codecutguiv2/ModNamingLauncher.java b/codecut-gui/src/main/java/codecutguiv2/ModNamingLauncher.java deleted file mode 100644 index 15d3512..0000000 --- a/codecut-gui/src/main/java/codecutguiv2/ModNamingLauncher.java +++ /dev/null @@ -1,170 +0,0 @@ -/* ### -* © 2022 The Johns Hopkins University Applied Physics Laboratory LLC -* (JHU/APL). -* -* NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL -* MAKES NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF -* THE MATERIALS, INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL -* VIABILITY, AND DISCLAIMS ALL WARRANTIES IN THE MATERIAL, WHETHER -* EXPRESS OR IMPLIED, INCLUDING (BUT NOT LIMITED TO) ANY AND ALL IMPLIED -* WARRANTIES OF PERFORMANCE, MERCHANTABILITY, FITNESS FOR A PARTICULAR -* PURPOSE, AND NON-INFRINGEMENT OF INTELLECTUAL PROPERTY OR OTHER THIRD -* PARTY RIGHTS. ANY USER OF THE MATERIAL ASSUMES THE ENTIRE RISK AND -* LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL JHU/APL BE LIABLE -* TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT, CONSEQUENTIAL, -* SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO USE, -* THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST -* PROFITS. -* -* This material is based upon work supported by the Defense Advanced Research -* Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific) -* under Contract Number N66001-20-C-4024. -* -* HAVE A NICE DAY. -*/ - -package codecutguiv2; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; - -import generic.jar.ResourceFile; -import ghidra.app.plugin.core.analysis.AutoAnalysisManager; -import ghidra.app.script.GhidraScript; -import ghidra.app.script.GhidraScriptLoadException; -import ghidra.app.script.GhidraScriptProvider; -import ghidra.app.script.GhidraScriptUtil; -import ghidra.app.script.GhidraState; -import ghidra.app.services.ConsoleService; -import ghidra.framework.model.Project; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.address.AddressSetView; -import ghidra.program.model.listing.Program; -import ghidra.program.util.ProgramLocation; -import ghidra.program.util.ProgramSelection; -import ghidra.util.Msg; -import ghidra.util.exception.CancelledException; -import ghidra.util.task.TaskMonitor; - -public class ModNamingLauncher { - - /** - * Runs modnaming.py synchronously with two args: infile and outfile. - * Writes the provided JSON string to a temp infile, runs the script, reads the temp outfile, - * and returns its contents. Best-effort temp cleanup. - * @throws IllegalAccessException - * @throws FileNotFoundException - */ - public static String runFileMode(Program program, - AddressSetView set, - String inputJson, - TaskMonitor monitor) throws IllegalAccessException, FileNotFoundException, GhidraScriptLoadException { - final String scriptName = "ModNamingRun.py"; // must be on Ghidra script path - - AutoAnalysisManager aam = AutoAnalysisManager.getAnalysisManager(program); - PluginTool tool = aam.getAnalysisTool(); - Project project = tool != null ? tool.getProject() : null; - - GhidraState state = new GhidraState( - tool, - project, - program, - new ProgramLocation(program, set.getMinAddress()), - new ProgramSelection(set), - null - ); - - PrintWriter out = getOut(tool); - PrintWriter err = getErr(tool); - - Path inFile = null, outFile = null; - // 1) Locate script - ResourceFile sourceFile = GhidraScriptUtil.findScriptByName(scriptName); - if (sourceFile == null) { - throw new IllegalAccessException("Couldn't find script: " + scriptName); - } - GhidraScriptProvider provider = GhidraScriptUtil.getProvider(sourceFile); - if (provider == null) { - throw new IllegalAccessException("Couldn't find script provider for: " + scriptName); - } - - - Msg.info(ModNamingLauncher.class, "Chosen provider: " + provider.getClass().getName() + " (runtime=" + provider.getRuntimeEnvironmentName() + ")"); - - GhidraScript script; - try { - script = provider.getScriptInstance(sourceFile, err); - } - catch(GhidraScriptLoadException e) { - Msg.showError( - null, // parent object (your plugin/analyzer/launcher) - null, // parent component (null = center on tool) - "Module Naming Failed to Launch", // dialog title - "Module Naming requires the PyGhidra extension.\n\nDetails: " + e.getMessage() - ); - throw new GhidraScriptLoadException("Module Naming requires PyGhidra", e); - } - - - - try { - - // 2) Prep temp files - inFile = Files.createTempFile("modnaming_in", ".txt"); - outFile = Files.createTempFile("modnaming_out", ".txt"); - // ensure delete on JVM exit as a fallback - inFile.toFile().deleteOnExit(); - outFile.toFile().deleteOnExit(); - - // write input JSON - Files.writeString(inFile, (inputJson == null ? "" : inputJson) + "\n", StandardCharsets.UTF_8); - - // 3) Pass args: [infile, outfile] - script.setScriptArgs(new String[] { - inFile.toString(), - outFile.toString() }); - - - - // 4) Run blocking - script.execute(state, monitor, out); - - // 5) Read result JSON - String result = Files.readString(outFile, StandardCharsets.UTF_8).trim(); - return result; - } - catch (CancelledException e) { - // user cancelled; return null or empty to signal no result - return null; - } - catch (Exception e) { - Msg.warn(ModNamingLauncher.class, "Error running script " + scriptName + ": " + e.getMessage(), e); - return null; - } - finally { - // best-effort cleanup - try { if (inFile != null) Files.deleteIfExists(inFile); } catch (IOException ignored) {} - try { if (outFile != null) Files.deleteIfExists(outFile); } catch (IOException ignored) {} - } - } - - private static PrintWriter getOut(PluginTool tool) { - if (tool != null) { - ConsoleService console = tool.getService(ConsoleService.class); - if (console != null) return console.getStdOut(); - } - return new PrintWriter(System.out); - } - - private static PrintWriter getErr(PluginTool tool) { - if (tool != null) { - ConsoleService console = tool.getService(ConsoleService.class); - if (console != null) return console.getStdErr(); - } - return new PrintWriter(System.err); - } -} diff --git a/codecut-gui/src/main/java/codecutguiv2/PyGhidraFileLauncher.java b/codecut-gui/src/main/java/codecutguiv2/PyGhidraFileLauncher.java new file mode 100644 index 0000000..1f9aa23 --- /dev/null +++ b/codecut-gui/src/main/java/codecutguiv2/PyGhidraFileLauncher.java @@ -0,0 +1,176 @@ +package codecutguiv2; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import generic.jar.ResourceFile; +import ghidra.app.plugin.core.analysis.AutoAnalysisManager; +import ghidra.app.script.GhidraScript; +import ghidra.app.script.GhidraScriptLoadException; +import ghidra.app.script.GhidraScriptProvider; +import ghidra.app.script.GhidraScriptUtil; +import ghidra.app.script.GhidraState; +import ghidra.app.services.ConsoleService; +import ghidra.framework.model.Project; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Base class for launching PyGhidra scripts in "file mode": + * - writes input payload to a temp infile + * - runs a named script with args [infile, outfile, ...extraArgs] + * - reads output payload from the outfile + * + * Subclasses override: + * - script name + * - temp file prefixes/suffixes (optional) + * - extra args (optional) + * - UI strings for error dialogs (optional) + */ +public abstract class PyGhidraFileLauncher { + + /** Provide the script file name that must be on the Ghidra script path. */ + protected abstract String getScriptName(); + + /** Prefix for the temp input file (default: "in_"). */ + protected String getInfilePrefix() { return "in_"; } + /** Suffix/extension for the temp input file (default: ".json"). */ + protected String getInfileSuffix() { return ".json"; } + + /** Prefix for the temp output file (default: "out_"). */ + protected String getOutfilePrefix() { return "out_"; } + /** Suffix/extension for the temp output file (default: ".json"). */ + protected String getOutfileSuffix() { return ".json"; } + + /** + * Return any extra script args after [infile, outfile]. Default: none. + * If this is used, the subclass must be defined in its own .java file + * in order for Ghidra the correctly find the module data file + * (yes this is strange) + */ + protected String[] getExtraArgs(PluginTool tool) throws Exception { return new String[0]; } + + /** Title and message for the “PyGhidra required” dialog. */ + protected String getLaunchFailDialogTitle() { return "Script Failed to Launch"; } + protected String getLaunchFailDialogBodyPrefix() { return "This feature requires the PyGhidra extension.\n\nDetails: "; } + + /** + * Run the script synchronously and return the outfile contents (trimmed), or null on cancel/error. + */ + public String runFileMode(Program program, + AddressSetView set, + String inputPayload, + TaskMonitor monitor) + throws IllegalAccessException, FileNotFoundException, GhidraScriptLoadException { + + final String scriptName = getScriptName(); + + AutoAnalysisManager aam = AutoAnalysisManager.getAnalysisManager(program); + PluginTool tool = aam.getAnalysisTool(); + Project project = tool != null ? tool.getProject() : null; + + Msg.info(getClass(), "tool: " + tool.getName()); + + GhidraState state = new GhidraState( + tool, + project, + program, + new ProgramLocation(program, set.getMinAddress()), + new ProgramSelection(set), + null + ); + + PrintWriter out = getOut(tool); + PrintWriter err = getErr(tool); + + Path inFile = null, outFile = null; + + // 1) Locate script + ResourceFile sourceFile = GhidraScriptUtil.findScriptByName(scriptName); + if (sourceFile == null) { + throw new IllegalAccessException("Couldn't find script: " + scriptName); + } + GhidraScriptProvider provider = GhidraScriptUtil.getProvider(sourceFile); + if (provider == null) { + throw new IllegalAccessException("Couldn't find script provider for: " + scriptName); + } + + Msg.info(getClass(), "Chosen provider: " + provider.getClass().getName() + + " (runtime=" + provider.getRuntimeEnvironmentName() + ")"); + + GhidraScript script; + try { + script = provider.getScriptInstance(sourceFile, err); + } catch (GhidraScriptLoadException e) { + ghidra.util.Msg.showError( + null, null, + getLaunchFailDialogTitle(), + getLaunchFailDialogBodyPrefix() + e.getMessage() + ); + throw e; + } + + try { + // 2) Prep temp files + inFile = Files.createTempFile(getInfilePrefix(), getInfileSuffix()); + outFile = Files.createTempFile(getOutfilePrefix(), getOutfileSuffix()); + inFile.toFile().deleteOnExit(); + outFile.toFile().deleteOnExit(); + + // write input payload + Files.writeString(inFile, (inputPayload == null ? "" : inputPayload) + "\n", StandardCharsets.UTF_8); + + // 3) Assemble args: [infile, outfile, ...extra] + String[] extras = getExtraArgs(tool); + String[] args = new String[2 + extras.length]; + args[0] = inFile.toString(); + args[1] = outFile.toString(); + System.arraycopy(extras, 0, args, 2, extras.length); + script.setScriptArgs(args); + + // 4) Run blocking + script.execute(state, monitor, out); + + // 5) Read result payload + return Files.readString(outFile, StandardCharsets.UTF_8).trim(); + } + catch (CancelledException e) { + return null; + } + catch (Exception e) { + Msg.warn(getClass(), "Error running script " + scriptName + ": " + e.getMessage(), e); + return null; + } + finally { + try { if (inFile != null) Files.deleteIfExists(inFile); } catch (IOException ignored) {} + try { if (outFile != null) Files.deleteIfExists(outFile); } catch (IOException ignored) {} + } + } + + // ----- console helpers ----- + protected static PrintWriter getOut(PluginTool tool) { + if (tool != null) { + ConsoleService console = tool.getService(ConsoleService.class); + if (console != null) return console.getStdOut(); + } + return new PrintWriter(System.out); + } + + protected static PrintWriter getErr(PluginTool tool) { + if (tool != null) { + ConsoleService console = tool.getService(ConsoleService.class); + if (console != null) return console.getStdErr(); + } + return new PrintWriter(System.err); + } +} diff --git a/deepcut-ghidra/ghidra_scripts/__pycache__/DeepCutRun.cpython-312.pyc b/deepcut-ghidra/ghidra_scripts/__pycache__/DeepCutRun.cpython-312.pyc deleted file mode 100644 index d40f7ad..0000000 Binary files a/deepcut-ghidra/ghidra_scripts/__pycache__/DeepCutRun.cpython-312.pyc and /dev/null differ diff --git a/deepcut-ghidra/ghidra_scripts/__pycache__/GNN_Net.cpython-312.pyc b/deepcut-ghidra/ghidra_scripts/__pycache__/GNN_Net.cpython-312.pyc deleted file mode 100644 index 9e584fc..0000000 Binary files a/deepcut-ghidra/ghidra_scripts/__pycache__/GNN_Net.cpython-312.pyc and /dev/null differ diff --git a/deepcut-ghidra/ghidra_scripts/__pycache__/deepcut.cpython-312.pyc b/deepcut-ghidra/ghidra_scripts/__pycache__/deepcut.cpython-312.pyc deleted file mode 100644 index f675bc6..0000000 Binary files a/deepcut-ghidra/ghidra_scripts/__pycache__/deepcut.cpython-312.pyc and /dev/null differ diff --git a/deepcut-ghidra/ghidra_scripts/__pycache__/dependency_bootstrap.cpython-312.pyc b/deepcut-ghidra/ghidra_scripts/__pycache__/dependency_bootstrap.cpython-312.pyc deleted file mode 100644 index 1194533..0000000 Binary files a/deepcut-ghidra/ghidra_scripts/__pycache__/dependency_bootstrap.cpython-312.pyc and /dev/null differ diff --git a/deepcut-ghidra/ghidra_scripts/__pycache__/vivvy_test.cpython-312.pyc b/deepcut-ghidra/ghidra_scripts/__pycache__/vivvy_test.cpython-312.pyc deleted file mode 100644 index 912d73a..0000000 Binary files a/deepcut-ghidra/ghidra_scripts/__pycache__/vivvy_test.cpython-312.pyc and /dev/null differ diff --git a/deepcut-ghidra/src/main/java/deepcut/DeepCutAnalyzer.java b/deepcut-ghidra/src/main/java/deepcut/DeepCutAnalyzer.java index 334f161..35f3519 100644 --- a/deepcut-ghidra/src/main/java/deepcut/DeepCutAnalyzer.java +++ b/deepcut-ghidra/src/main/java/deepcut/DeepCutAnalyzer.java @@ -30,11 +30,13 @@ import java.io.FileNotFoundException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import generic.jar.ResourceFile; import ghidra.app.script.GhidraScriptLoadException; import ghidra.app.services.AbstractAnalyzer; import ghidra.app.services.AnalysisPriority; import ghidra.app.services.AnalyzerType; import ghidra.app.util.importer.MessageLog; +import ghidra.framework.Application; import ghidra.framework.options.Options; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressFactory; @@ -104,7 +106,7 @@ public class DeepCutAnalyzer extends AbstractAnalyzer { // 2) Run DeepCut via the launcher (blocking, file-IO args under the hood) String cutsJson=""; try { - cutsJson = DeepCutLauncher.runFileMode(program, set, inputJson, monitor); + cutsJson = DeepCutLauncher.execute(program, set, inputJson, monitor); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -158,4 +160,7 @@ public class DeepCutAnalyzer extends AbstractAnalyzer { } function.setParentNamespace(ns); } -} \ No newline at end of file +} + + + diff --git a/deepcut-ghidra/src/main/java/deepcut/DeepCutLauncher.java b/deepcut-ghidra/src/main/java/deepcut/DeepCutLauncher.java index 237efe5..a86baa9 100644 --- a/deepcut-ghidra/src/main/java/deepcut/DeepCutLauncher.java +++ b/deepcut-ghidra/src/main/java/deepcut/DeepCutLauncher.java @@ -1,176 +1,47 @@ -/* ### -* © 2022 The Johns Hopkins University Applied Physics Laboratory LLC -* (JHU/APL). -* -* NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL -* MAKES NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF -* THE MATERIALS, INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL -* VIABILITY, AND DISCLAIMS ALL WARRANTIES IN THE MATERIAL, WHETHER -* EXPRESS OR IMPLIED, INCLUDING (BUT NOT LIMITED TO) ANY AND ALL IMPLIED -* WARRANTIES OF PERFORMANCE, MERCHANTABILITY, FITNESS FOR A PARTICULAR -* PURPOSE, AND NON-INFRINGEMENT OF INTELLECTUAL PROPERTY OR OTHER THIRD -* PARTY RIGHTS. ANY USER OF THE MATERIAL ASSUMES THE ENTIRE RISK AND -* LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL JHU/APL BE LIABLE -* TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT, CONSEQUENTIAL, -* SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO USE, -* THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST -* PROFITS. -* -* This material is based upon work supported by the Defense Advanced Research -* Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific) -* under Contract Number N66001-20-C-4024. -* -* HAVE A NICE DAY. -*/ - package deepcut; import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import generic.jar.ResourceFile; -import ghidra.app.plugin.core.analysis.AutoAnalysisManager; -import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScriptLoadException; -import ghidra.app.script.GhidraScriptProvider; -import ghidra.app.script.GhidraScriptUtil; -import ghidra.app.script.GhidraState; -import ghidra.app.services.ConsoleService; import ghidra.framework.Application; -import ghidra.framework.model.Project; -import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.Program; -import ghidra.program.util.ProgramLocation; -import ghidra.program.util.ProgramSelection; -import ghidra.util.Msg; -import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -public class DeepCutLauncher { +public class DeepCutLauncher extends PyGhidraFileLauncher { - /** - * Runs DeepCutRun.py synchronously with three args: infile, outfile, modelfile. - * Writes the provided JSON string to a temp infile, runs the script, reads the temp outfile, - * and returns its contents. Best-effort temp cleanup. - * @throws IllegalAccessException - * @throws FileNotFoundException - */ - public static String runFileMode(Program program, - AddressSetView set, - String inputJson, - TaskMonitor monitor) throws IllegalAccessException, FileNotFoundException, GhidraScriptLoadException { - final String scriptName = "DeepCutRun.py"; // must be on Ghidra script path + @Override protected String getScriptName() { return "DeepCutRun.py"; } + @Override protected String getInfilePrefix() { return "deepcut_in"; } + @Override protected String getOutfilePrefix() { return "deepcut_out"; } + @Override protected String getInfileSuffix() { return ".json"; } + @Override protected String getOutfileSuffix() { return ".json"; } - AutoAnalysisManager aam = AutoAnalysisManager.getAnalysisManager(program); - PluginTool tool = aam.getAnalysisTool(); - Project project = tool != null ? tool.getProject() : null; - - GhidraState state = new GhidraState( - tool, - project, - program, - new ProgramLocation(program, set.getMinAddress()), - new ProgramSelection(set), - null - ); - - PrintWriter out = getOut(tool); - PrintWriter err = getErr(tool); - - Path inFile = null, outFile = null; - // 1) Locate script - ResourceFile sourceFile = GhidraScriptUtil.findScriptByName(scriptName); - if (sourceFile == null) { - throw new IllegalAccessException("Couldn't find script: " + scriptName); - } - GhidraScriptProvider provider = GhidraScriptUtil.getProvider(sourceFile); - if (provider == null) { - throw new IllegalAccessException("Couldn't find script provider for: " + scriptName); - } - ResourceFile pythonFile = Application.getModuleDataFile("model_weights.p"); - if (pythonFile == null) { - throw new IllegalAccessException("Couldn't find weights file for: " + scriptName); - } - - - Msg.info(DeepCutLauncher.class, "Chosen provider: " + provider.getClass().getName() + " (runtime=" + provider.getRuntimeEnvironmentName() + ")"); - - GhidraScript script; - try { - script = provider.getScriptInstance(sourceFile, err); - } - catch(GhidraScriptLoadException e) { - Msg.showError( - null, // parent object (your plugin/analyzer/launcher) - null, // parent component (null = center on tool) - "DeepCut Failed to Launch", // dialog title - "DeepCut requires the PyGhidra extension.\n\nDetails: " + e.getMessage() - ); - throw new GhidraScriptLoadException("DeepCut requires PyGhidra", e); - } - - - - try { - - // 2) Prep temp files - inFile = Files.createTempFile("deepcut_in", ".json"); - outFile = Files.createTempFile("deepcut_out", ".json"); - // ensure delete on JVM exit as a fallback - inFile.toFile().deleteOnExit(); - outFile.toFile().deleteOnExit(); - - // write input JSON - Files.writeString(inFile, (inputJson == null ? "" : inputJson) + "\n", StandardCharsets.UTF_8); - - // 3) Pass args: [infile, outfile, modelfile] - script.setScriptArgs(new String[] { - inFile.toString(), - outFile.toString(), - pythonFile.toString() }); - - - - // 4) Run blocking - script.execute(state, monitor, out); - - // 5) Read result JSON - String result = Files.readString(outFile, StandardCharsets.UTF_8).trim(); - return result; + @Override + protected String[] getExtraArgs(ghidra.framework.plugintool.PluginTool tool) throws Exception { + + // derive the correct module name at runtime + String moduleName = Application.getMyModuleRootDirectory().getName(); + + // Locate model weights packaged with the extension/module + ResourceFile weights = Application.getModuleDataFile(moduleName, "model_weights.p"); + if (weights == null) { + throw new IllegalAccessException("Couldn't find weights file for: " + getScriptName()); } - catch (CancelledException e) { - // user cancelled; return null or empty to signal no result - return null; - } - catch (Exception e) { - Msg.warn(DeepCutLauncher.class, "Error running script " + scriptName + ": " + e.getMessage(), e); - return null; - } - finally { - // best-effort cleanup - try { if (inFile != null) Files.deleteIfExists(inFile); } catch (IOException ignored) {} - try { if (outFile != null) Files.deleteIfExists(outFile); } catch (IOException ignored) {} - } - } - - private static PrintWriter getOut(PluginTool tool) { - if (tool != null) { - ConsoleService console = tool.getService(ConsoleService.class); - if (console != null) return console.getStdOut(); - } - return new PrintWriter(System.out); + return new String[] { weights.toString() }; } - private static PrintWriter getErr(PluginTool tool) { - if (tool != null) { - ConsoleService console = tool.getService(ConsoleService.class); - if (console != null) return console.getStdErr(); - } - return new PrintWriter(System.err); + // Convenience static method to preserve your old call-site signature + public static String execute(Program program, + AddressSetView set, + String inputPayload, + TaskMonitor monitor) + throws IllegalAccessException, FileNotFoundException, GhidraScriptLoadException { + return new DeepCutLauncher().runFileMode(program, set, inputPayload, monitor); + } + + @Override protected String getLaunchFailDialogTitle() { return "DeepCut Failed to Launch"; } + @Override protected String getLaunchFailDialogBodyPrefix() { + return "DeepCut requires the PyGhidra extension.\n\nDetails: "; } } diff --git a/deepcut-ghidra/src/main/java/deepcut/PyGhidraFileLauncher.java b/deepcut-ghidra/src/main/java/deepcut/PyGhidraFileLauncher.java new file mode 100644 index 0000000..08855e6 --- /dev/null +++ b/deepcut-ghidra/src/main/java/deepcut/PyGhidraFileLauncher.java @@ -0,0 +1,174 @@ +package deepcut; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import generic.jar.ResourceFile; +import ghidra.app.plugin.core.analysis.AutoAnalysisManager; +import ghidra.app.script.GhidraScript; +import ghidra.app.script.GhidraScriptLoadException; +import ghidra.app.script.GhidraScriptProvider; +import ghidra.app.script.GhidraScriptUtil; +import ghidra.app.script.GhidraState; +import ghidra.app.services.ConsoleService; +import ghidra.framework.model.Project; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Base class for launching PyGhidra scripts in "file mode": + * - writes input payload to a temp infile + * - runs a named script with args [infile, outfile, ...extraArgs] + * - reads output payload from the outfile + * + * Subclasses override: + * - script name + * - temp file prefixes/suffixes (optional) + * - extra args (optional) + * - UI strings for error dialogs (optional) + */ +public abstract class PyGhidraFileLauncher { + + /** Provide the script file name that must be on the Ghidra script path. */ + protected abstract String getScriptName(); + + /** Prefix for the temp input file (default: "in_"). */ + protected String getInfilePrefix() { return "in_"; } + /** Suffix/extension for the temp input file (default: ".json"). */ + protected String getInfileSuffix() { return ".json"; } + + /** Prefix for the temp output file (default: "out_"). */ + protected String getOutfilePrefix() { return "out_"; } + /** Suffix/extension for the temp output file (default: ".json"). */ + protected String getOutfileSuffix() { return ".json"; } + + /** + * Return any extra script args after [infile, outfile]. Default: none. + * If this is used, the subclass must be defined in its own .java file + * in order for Ghidra the correctly find the module data file + * (yes this is strange) + */ + protected String[] getExtraArgs(PluginTool tool) throws Exception { return new String[0]; } + + /** Title and message for the “PyGhidra required” dialog. */ + protected String getLaunchFailDialogTitle() { return "Script Failed to Launch"; } + protected String getLaunchFailDialogBodyPrefix() { return "This feature requires the PyGhidra extension.\n\nDetails: "; } + + /** + * Run the script synchronously and return the outfile contents (trimmed), or null on cancel/error. + */ + public String runFileMode(Program program, + AddressSetView set, + String inputPayload, + TaskMonitor monitor) + throws IllegalAccessException, FileNotFoundException, GhidraScriptLoadException { + + final String scriptName = getScriptName(); + + AutoAnalysisManager aam = AutoAnalysisManager.getAnalysisManager(program); + PluginTool tool = aam.getAnalysisTool(); + Project project = tool != null ? tool.getProject() : null; + + GhidraState state = new GhidraState( + tool, + project, + program, + new ProgramLocation(program, set.getMinAddress()), + new ProgramSelection(set), + null + ); + + PrintWriter out = getOut(tool); + PrintWriter err = getErr(tool); + + Path inFile = null, outFile = null; + + // 1) Locate script + ResourceFile sourceFile = GhidraScriptUtil.findScriptByName(scriptName); + if (sourceFile == null) { + throw new IllegalAccessException("Couldn't find script: " + scriptName); + } + GhidraScriptProvider provider = GhidraScriptUtil.getProvider(sourceFile); + if (provider == null) { + throw new IllegalAccessException("Couldn't find script provider for: " + scriptName); + } + + Msg.info(getClass(), "Chosen provider: " + provider.getClass().getName() + + " (runtime=" + provider.getRuntimeEnvironmentName() + ")"); + + GhidraScript script; + try { + script = provider.getScriptInstance(sourceFile, err); + } catch (GhidraScriptLoadException e) { + ghidra.util.Msg.showError( + null, null, + getLaunchFailDialogTitle(), + getLaunchFailDialogBodyPrefix() + e.getMessage() + ); + throw e; + } + + try { + // 2) Prep temp files + inFile = Files.createTempFile(getInfilePrefix(), getInfileSuffix()); + outFile = Files.createTempFile(getOutfilePrefix(), getOutfileSuffix()); + inFile.toFile().deleteOnExit(); + outFile.toFile().deleteOnExit(); + + // write input payload + Files.writeString(inFile, (inputPayload == null ? "" : inputPayload) + "\n", StandardCharsets.UTF_8); + + // 3) Assemble args: [infile, outfile, ...extra] + String[] extras = getExtraArgs(tool); + String[] args = new String[2 + extras.length]; + args[0] = inFile.toString(); + args[1] = outFile.toString(); + System.arraycopy(extras, 0, args, 2, extras.length); + script.setScriptArgs(args); + + // 4) Run blocking + script.execute(state, monitor, out); + + // 5) Read result payload + return Files.readString(outFile, StandardCharsets.UTF_8).trim(); + } + catch (CancelledException e) { + return null; + } + catch (Exception e) { + Msg.warn(getClass(), "Error running script " + scriptName + ": " + e.getMessage(), e); + return null; + } + finally { + try { if (inFile != null) Files.deleteIfExists(inFile); } catch (IOException ignored) {} + try { if (outFile != null) Files.deleteIfExists(outFile); } catch (IOException ignored) {} + } + } + + // ----- console helpers ----- + protected static PrintWriter getOut(PluginTool tool) { + if (tool != null) { + ConsoleService console = tool.getService(ConsoleService.class); + if (console != null) return console.getStdOut(); + } + return new PrintWriter(System.out); + } + + protected static PrintWriter getErr(PluginTool tool) { + if (tool != null) { + ConsoleService console = tool.getService(ConsoleService.class); + if (console != null) return console.getStdErr(); + } + return new PrintWriter(System.err); + } +}