From 47e343f0dfe29c53eaf2591729cbf099874baa77 Mon Sep 17 00:00:00 2001 From: Mike Hunhoff Date: Tue, 25 Apr 2023 11:03:39 -0600 Subject: [PATCH] use google-java-format to format Java files (#42) --- .../ghidrathon/GhidrathonClassEnquirer.java | 56 +- .../java/ghidrathon/GhidrathonConfig.java | 104 +-- .../GhidrathonConsoleInputThread.java | 242 ++--- .../java/ghidrathon/GhidrathonPlugin.java | 318 ++++--- .../java/ghidrathon/GhidrathonScript.java | 288 +++--- .../ghidrathon/GhidrathonScriptProvider.java | 147 ++- src/main/java/ghidrathon/GhidrathonUtils.java | 127 ++- .../interpreter/GhidrathonInterpreter.java | 876 +++++++++--------- 8 files changed, 1056 insertions(+), 1102 deletions(-) diff --git a/src/main/java/ghidrathon/GhidrathonClassEnquirer.java b/src/main/java/ghidrathon/GhidrathonClassEnquirer.java index 09fa0f8..9b7773f 100644 --- a/src/main/java/ghidrathon/GhidrathonClassEnquirer.java +++ b/src/main/java/ghidrathon/GhidrathonClassEnquirer.java @@ -3,48 +3,48 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt // Unless required by applicable law or agreed to in writing, software distributed under the License -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and limitations under the License. +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// See the License for the specific language governing permissions and limitations under the +// License. package ghidrathon; -import java.util.List; import java.util.ArrayList; - -import jep.ClassList; +import java.util.List; import jep.ClassEnquirer; +import jep.ClassList; /** - * Implements Jep ClassEnquirer used to handle Java imports from Python - specifically we - * use this class to handle naming conflicts, e.g. pdb + * Implements Jep ClassEnquirer used to handle Java imports from Python - specifically we use this + * class to handle naming conflicts, e.g. pdb */ public class GhidrathonClassEnquirer implements ClassEnquirer { - private final List javaExcludeLibs = new ArrayList(); - private final ClassEnquirer classList = ClassList.getInstance(); - - public void addJavaExcludeLib(String name) { - javaExcludeLibs.add(name); - } + private final List javaExcludeLibs = new ArrayList(); + private final ClassEnquirer classList = ClassList.getInstance(); - public void addJavaExcludeLibs(List names) { - javaExcludeLibs.addAll(names); - } + public void addJavaExcludeLib(String name) { + javaExcludeLibs.add(name); + } - public boolean isJavaPackage(String name) { - if (javaExcludeLibs.contains(name)) { - return false; - } + public void addJavaExcludeLibs(List names) { + javaExcludeLibs.addAll(names); + } - return classList.isJavaPackage(name); - } + public boolean isJavaPackage(String name) { + if (javaExcludeLibs.contains(name)) { + return false; + } - public String[] getClassNames(String name) { - return classList.getClassNames(name); - } + return classList.isJavaPackage(name); + } - public String[] getSubPackages(String name) { - return classList.getSubPackages(name); - } + public String[] getClassNames(String name) { + return classList.getClassNames(name); + } + public String[] getSubPackages(String name) { + return classList.getSubPackages(name); + } } diff --git a/src/main/java/ghidrathon/GhidrathonConfig.java b/src/main/java/ghidrathon/GhidrathonConfig.java index d3ae56a..260a38d 100644 --- a/src/main/java/ghidrathon/GhidrathonConfig.java +++ b/src/main/java/ghidrathon/GhidrathonConfig.java @@ -3,83 +3,83 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt // Unless required by applicable law or agreed to in writing, software distributed under the License -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and limitations under the License. +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// See the License for the specific language governing permissions and limitations under the +// License. package ghidrathon; -import java.util.List; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; +import java.util.List; /** * Ghidrathon's configuration class * - * Stores - * - stdout and stderr - * - Python modules to handle as shared modules - relevant to CPython modules - * - Java package names to exclude from Python imports - * - Python include paths to add to Python interpreter environment + *

Stores - stdout and stderr - Python modules to handle as shared modules - relevant to CPython + * modules - Java package names to exclude from Python imports - Python include paths to add to + * Python interpreter environment */ public class GhidrathonConfig { - private final List javaExcludeLibs = new ArrayList(); - private final List pyIncludePaths = new ArrayList(); - private final List pySharedModules = new ArrayList(); + private final List javaExcludeLibs = new ArrayList(); + private final List pyIncludePaths = new ArrayList(); + private final List pySharedModules = new ArrayList(); - private PrintWriter out = null; - private PrintWriter err = null; + private PrintWriter out = null; + private PrintWriter err = null; - public void addStdOut(PrintWriter out) { - this.out = out; - } + public void addStdOut(PrintWriter out) { + this.out = out; + } - public void addStdErr(PrintWriter err) { - this.err = err; - } + public void addStdErr(PrintWriter err) { + this.err = err; + } - public PrintWriter getStdOut() { - return out; - } + public PrintWriter getStdOut() { + return out; + } - public PrintWriter getStdErr() { - return err; - } + public PrintWriter getStdErr() { + return err; + } - public void addPythonSharedModule(String name) { - pySharedModules.add(name); - } + public void addPythonSharedModule(String name) { + pySharedModules.add(name); + } - public void addPythonSharedModules(List names) { - pySharedModules.addAll(names); - } + public void addPythonSharedModules(List names) { + pySharedModules.addAll(names); + } - public Iterable getPythonSharedModules() { - return Collections.unmodifiableList(pySharedModules); - } + public Iterable getPythonSharedModules() { + return Collections.unmodifiableList(pySharedModules); + } - public void addJavaExcludeLib(String name) { - javaExcludeLibs.add(name); - } + public void addJavaExcludeLib(String name) { + javaExcludeLibs.add(name); + } - public void addJavaExcludeLibs(List names) { - javaExcludeLibs.addAll(names); - } + public void addJavaExcludeLibs(List names) { + javaExcludeLibs.addAll(names); + } - public Iterable getJavaExcludeLibs() { - return Collections.unmodifiableList(javaExcludeLibs); - } + public Iterable getJavaExcludeLibs() { + return Collections.unmodifiableList(javaExcludeLibs); + } - public void addPythonIncludePath(String path) { - pyIncludePaths.add(path); - } + public void addPythonIncludePath(String path) { + pyIncludePaths.add(path); + } - public void addPythonIncludePaths(List paths) { - pyIncludePaths.addAll(paths); - } + public void addPythonIncludePaths(List paths) { + pyIncludePaths.addAll(paths); + } - public Iterable getPythonIncludePaths() { - return Collections.unmodifiableList(pyIncludePaths); - } + public Iterable getPythonIncludePaths() { + return Collections.unmodifiableList(pyIncludePaths); + } } diff --git a/src/main/java/ghidrathon/GhidrathonConsoleInputThread.java b/src/main/java/ghidrathon/GhidrathonConsoleInputThread.java index 7217852..d270332 100644 --- a/src/main/java/ghidrathon/GhidrathonConsoleInputThread.java +++ b/src/main/java/ghidrathon/GhidrathonConsoleInputThread.java @@ -3,171 +3,173 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt // Unless required by applicable law or agreed to in writing, software distributed under the License -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and limitations under the License. +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// See the License for the specific language governing permissions and limitations under the +// License. package ghidrathon; +import generic.jar.ResourceFile; +import ghidra.app.plugin.core.interpreter.InterpreterConsole; +import ghidra.app.script.GhidraState; +import ghidra.util.Msg; +import ghidrathon.interpreter.GhidrathonInterpreter; +import java.io.BufferedReader; import java.io.File; -import java.io.PrintWriter; import java.io.IOException; -import java.io.BufferedReader; import java.io.InputStreamReader; +import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicBoolean; -import generic.jar.ResourceFile; +public class GhidrathonConsoleInputThread extends Thread { -import ghidra.util.Msg; -import ghidra.app.script.GhidraState; -import ghidra.app.plugin.core.interpreter.InterpreterConsole; + private static int generationCount = 0; -import ghidrathon.GhidrathonUtils; -import ghidrathon.GhidrathonConfig; -import ghidrathon.interpreter.GhidrathonInterpreter; + private GhidrathonPlugin plugin = null; + private InterpreterConsole console = null; + private GhidrathonInterpreter python = null; -public class GhidrathonConsoleInputThread extends Thread { + private AtomicBoolean shouldContinue = new AtomicBoolean(true); + private GhidrathonConfig config = GhidrathonUtils.getDefaultGhidrathonConfig(); + + GhidrathonConsoleInputThread(GhidrathonPlugin plugin) { + + super("Ghidrathon console input thread (generation " + ++generationCount + ")"); - private static int generationCount = 0; - - private GhidrathonPlugin plugin = null; - private InterpreterConsole console = null; - private GhidrathonInterpreter python = null; + this.plugin = plugin; + this.console = plugin.getConsole(); - private AtomicBoolean shouldContinue = new AtomicBoolean(true); - private GhidrathonConfig config = GhidrathonUtils.getDefaultGhidrathonConfig(); + // init Ghidrathon configuration + config.addStdErr(console.getErrWriter()); + config.addStdOut(console.getOutWriter()); + } - GhidrathonConsoleInputThread(GhidrathonPlugin plugin) { + /** + * Console input thread. + * + *

This thread passes Python statements from Java to Python to be evaluated. The interpreter is + * is configured to print stdout and stderr to the console Window. Multi-line Python blocks are + * supported but this is mostly handled in by the interpreter. + */ + @Override + public void run() { - super("Ghidrathon console input thread (generation " + ++generationCount + ")"); + console.clear(); - this.plugin = plugin; - this.console = plugin.getConsole(); + try { - // init Ghidrathon configuration - config.addStdErr(console.getErrWriter()); - config.addStdOut(console.getOutWriter()); + python = GhidrathonInterpreter.get(config); - } + python.printWelcome(); - /** - * Console input thread. - * - * This thread passes Python statements from Java to Python to be evaluated. The interpreter is - * is configured to print stdout and stderr to the console Window. Multi-line Python blocks are - * supported but this is mostly handled in by the interpreter. - */ - @Override - public void run() { - - console.clear(); + } catch (RuntimeException e) { - try { + if (python != null) { + python.close(); + } - python = GhidrathonInterpreter.get(config); - - python.printWelcome(); + e.printStackTrace(config.getStdErr()); + return; + } - } catch (RuntimeException e) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(console.getStdin()))) { - if (python != null) { - python.close(); - } - - e.printStackTrace(config.getStdErr()); - return; + plugin.flushConsole(); + console.setPrompt(python.getPrimaryPrompt()); - } + // begin reading and passing input from console stdin to Python to be evaluated + while (shouldContinue.get()) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(console.getStdin()))) { + String line; - plugin.flushConsole(); - console.setPrompt(python.getPrimaryPrompt()); - - // begin reading and passing input from console stdin to Python to be evaluated - while (shouldContinue.get()) { + if (console.getStdin().available() > 0) { + line = reader.readLine(); + } else { + try { - String line; + Thread.sleep(50); - if (console.getStdin().available() > 0) { - line = reader.readLine(); - } else { - try { + } catch (InterruptedException e) { - Thread.sleep(50); + } - } catch (InterruptedException e) { + continue; + } - } + boolean moreInputWanted = evalPython(line); - continue; - } + this.plugin.flushConsole(); + this.console.setPrompt( + moreInputWanted ? python.getSecondaryPrompt() : python.getPrimaryPrompt()); + } - boolean moreInputWanted = evalPython(line); + } catch (RuntimeException | IOException e) { - this.plugin.flushConsole(); - this.console.setPrompt(moreInputWanted ? python.getSecondaryPrompt() : python.getPrimaryPrompt()); - } - - } catch (RuntimeException | IOException e) { + e.printStackTrace(); + Msg.error( + GhidrathonConsoleInputThread.class, + "Internal error reading commands from python console. Please reset.", + e); - e.printStackTrace(); - Msg.error(GhidrathonConsoleInputThread.class, - "Internal error reading commands from python console. Please reset.", e); + } finally { - } finally { + python.close(); + } + } - python.close(); + /** + * Configures Ghidra state and passes Python statement to Python. + * + *

This function must be called by the same thread that created the Jep instance. See + * https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/Python/src/main/java/ghidra/python/PythonPluginExecutionThread.java#L55 + * + * @param line Python to evaluate + * @return True if more input needed, otherwise False + * @throws RuntimeException + */ + private boolean evalPython(String line) throws RuntimeException { - } - } + boolean status; - /** - * Configures Ghidra state and passes Python statement to Python. - * - * This function must be called by the same thread that created the Jep instance. - * See https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/Python/src/main/java/ghidra/python/PythonPluginExecutionThread.java#L55 - * - * @param line Python to evaluate - * @return True if more input needed, otherwise False - * @throws RuntimeException - */ - private boolean evalPython(String line) throws RuntimeException { + // set transaction for the execution + int transactionNumber = -1; + if (plugin.getCurrentProgram() != null) { + transactionNumber = plugin.getCurrentProgram().startTransaction("Ghidrathon command"); + } - boolean status; - - // set transaction for the execution - int transactionNumber = -1; - if (plugin.getCurrentProgram() != null) { - transactionNumber = plugin.getCurrentProgram().startTransaction("Ghidrathon command"); - } + // setup Ghidra state to be passed into interpreter + plugin.getInteractiveTaskMonitor().clearCanceled(); + plugin.getInteractiveScript().setSourceFile(new ResourceFile(new File("Ghidrathon"))); + plugin + .getInteractiveScript() + .set( + new GhidraState( + plugin.getTool(), + plugin.getTool().getProject(), + plugin.getCurrentProgram(), + plugin.getProgramLocation(), + plugin.getProgramSelection(), + plugin.getProgramHighlight()), + plugin.getInteractiveTaskMonitor(), + new PrintWriter(console.getStdOut())); - // setup Ghidra state to be passed into interpreter - plugin.getInteractiveTaskMonitor().clearCanceled(); - plugin.getInteractiveScript().setSourceFile(new ResourceFile(new File("Ghidrathon"))); - plugin.getInteractiveScript() - .set(new GhidraState(plugin.getTool(), plugin.getTool().getProject(), plugin.getCurrentProgram(), - plugin.getProgramLocation(), plugin.getProgramSelection(), plugin.getProgramHighlight()), - plugin.getInteractiveTaskMonitor(), new PrintWriter(console.getStdOut())); + try { - try { - - status = python.eval(line, plugin.getInteractiveScript()); - - } finally { - - if (plugin.getCurrentProgram() != null) { - plugin.getCurrentProgram().endTransaction(transactionNumber, true); - } - - } + status = python.eval(line, plugin.getInteractiveScript()); - return status; + } finally { - } + if (plugin.getCurrentProgram() != null) { + plugin.getCurrentProgram().endTransaction(transactionNumber, true); + } + } - void dispose() { + return status; + } - shouldContinue.set(false); + void dispose() { - } + shouldContinue.set(false); + } } diff --git a/src/main/java/ghidrathon/GhidrathonPlugin.java b/src/main/java/ghidrathon/GhidrathonPlugin.java index 2598606..181cd07 100644 --- a/src/main/java/ghidrathon/GhidrathonPlugin.java +++ b/src/main/java/ghidrathon/GhidrathonPlugin.java @@ -3,176 +3,170 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt // Unless required by applicable law or agreed to in writing, software distributed under the License -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and limitations under the License. +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// See the License for the specific language governing permissions and limitations under the +// License. package ghidrathon; -import java.util.List; -import java.io.PrintWriter; -import java.io.OutputStream; - -import javax.swing.*; - -import ghidra.util.task.TaskMonitor; -import ghidra.framework.plugintool.*; -import ghidra.util.task.TaskLauncher; -import ghidra.app.plugin.ProgramPlugin; import ghidra.app.CorePluginPackage; -import ghidra.util.task.TaskMonitorAdapter; -import ghidra.framework.options.ToolOptions; import ghidra.app.plugin.PluginCategoryNames; -import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.core.console.CodeCompletion; -import ghidra.framework.options.OptionsChangeListener; -import ghidra.app.plugin.core.interpreter.InterpreterConsole; import ghidra.app.plugin.core.interpreter.InterpreterConnection; +import ghidra.app.plugin.core.interpreter.InterpreterConsole; import ghidra.app.plugin.core.interpreter.InterpreterPanelService; +import ghidra.framework.options.OptionsChangeListener; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.util.task.TaskLauncher; +import ghidra.util.task.TaskMonitor; +import ghidra.util.task.TaskMonitorAdapter; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.List; +import javax.swing.*; -//@formatter:off +// @formatter:off @PluginInfo( - status = PluginStatus.STABLE, - packageName = CorePluginPackage.NAME, - category = PluginCategoryNames.INTERPRETERS, - shortDescription = "Python 3 Interpreter", - description = "The FLARE team's open-source Python 3 interpreter console that is tightly integrated with a loaded Ghidra program.", - servicesRequired = { InterpreterPanelService.class }, - isSlowInstallation = true -) -//@formatter:on - -public class GhidrathonPlugin extends ProgramPlugin implements InterpreterConnection, OptionsChangeListener { - - private InterpreterConsole console; - private GhidrathonConsoleInputThread inputThread; - private TaskMonitor interactiveTaskMonitor; - private GhidrathonScript interactiveScript; - - public GhidrathonPlugin(PluginTool tool) { - - super(tool); - - } - - InterpreterConsole getConsole() { - - return console; - - } - - public TaskMonitor getInteractiveTaskMonitor() { - - return interactiveTaskMonitor; - - } - - GhidrathonScript getInteractiveScript() { - - return interactiveScript; - - } - - @Override - protected void init() { - - super.init(); - - console = getTool().getService(InterpreterPanelService.class).createInterpreterPanel(this, false); - console.addFirstActivationCallback(() -> resetInterpreter()); - - } - - @Override - public void optionsChanged(ToolOptions options, String optionName, Object oldValue, Object newValue) { - // TODO Auto-generated method stub - } - - @Override - public String getTitle() { - - return "Ghidrathon"; - - } - - @Override - public ImageIcon getIcon() { - // TODO Auto-generated method stub - return null; - } - - @Override - public String toString() { - - return getPluginDescription().getName(); - - } - - public void flushConsole() { - - this.getConsole().getOutWriter().flush(); - this.getConsole().getErrWriter().flush(); - - } - - @Override - public List getCompletions(String cmd) { - // TODO Auto-generated method stub - return null; - } - - private void resetInterpreter() { - - TaskLauncher.launchModal("Resetting Ghidrathon...", () -> { - resetInterpreterInBackground(); - }); - - } - - @Override - protected void dispose() { - - // Terminate the input thread - if (inputThread != null) { - inputThread.dispose(); - inputThread = null; - } - - // Dispose of the console - if (console != null) { - console.dispose(); - console = null; - } - - super.dispose(); - - } - - private void resetInterpreterInBackground() { - - interactiveScript = new GhidrathonScript(); - interactiveTaskMonitor = new PythonInteractiveTaskMonitor(console.getStdOut()); - - inputThread = new GhidrathonConsoleInputThread(this); - inputThread.start(); - - } - - class PythonInteractiveTaskMonitor extends TaskMonitorAdapter { - - private PrintWriter output = null; - - public PythonInteractiveTaskMonitor(PrintWriter stdOut) { - output = stdOut; - } - - public PythonInteractiveTaskMonitor(OutputStream stdout) { - this(new PrintWriter(stdout)); - } - - @Override - public void setMessage(String message) { - output.println(": " + message); - } - - } + status = PluginStatus.STABLE, + packageName = CorePluginPackage.NAME, + category = PluginCategoryNames.INTERPRETERS, + shortDescription = "Python 3 Interpreter", + description = + "The FLARE team's open-source Python 3 interpreter console that is tightly integrated with" + + " a loaded Ghidra program.", + servicesRequired = {InterpreterPanelService.class}, + isSlowInstallation = true) +// @formatter:on + +public class GhidrathonPlugin extends ProgramPlugin + implements InterpreterConnection, OptionsChangeListener { + + private InterpreterConsole console; + private GhidrathonConsoleInputThread inputThread; + private TaskMonitor interactiveTaskMonitor; + private GhidrathonScript interactiveScript; + + public GhidrathonPlugin(PluginTool tool) { + + super(tool); + } + + InterpreterConsole getConsole() { + + return console; + } + + public TaskMonitor getInteractiveTaskMonitor() { + + return interactiveTaskMonitor; + } + + GhidrathonScript getInteractiveScript() { + + return interactiveScript; + } + + @Override + protected void init() { + + super.init(); + + console = + getTool().getService(InterpreterPanelService.class).createInterpreterPanel(this, false); + console.addFirstActivationCallback(() -> resetInterpreter()); + } + + @Override + public void optionsChanged( + ToolOptions options, String optionName, Object oldValue, Object newValue) { + // TODO Auto-generated method stub + } + + @Override + public String getTitle() { + + return "Ghidrathon"; + } + + @Override + public ImageIcon getIcon() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String toString() { + + return getPluginDescription().getName(); + } + + public void flushConsole() { + + this.getConsole().getOutWriter().flush(); + this.getConsole().getErrWriter().flush(); + } + + @Override + public List getCompletions(String cmd) { + // TODO Auto-generated method stub + return null; + } + + private void resetInterpreter() { + + TaskLauncher.launchModal( + "Resetting Ghidrathon...", + () -> { + resetInterpreterInBackground(); + }); + } + + @Override + protected void dispose() { + + // Terminate the input thread + if (inputThread != null) { + inputThread.dispose(); + inputThread = null; + } + + // Dispose of the console + if (console != null) { + console.dispose(); + console = null; + } + + super.dispose(); + } + + private void resetInterpreterInBackground() { + + interactiveScript = new GhidrathonScript(); + interactiveTaskMonitor = new PythonInteractiveTaskMonitor(console.getStdOut()); + + inputThread = new GhidrathonConsoleInputThread(this); + inputThread.start(); + } + + class PythonInteractiveTaskMonitor extends TaskMonitorAdapter { + + private PrintWriter output = null; + + public PythonInteractiveTaskMonitor(PrintWriter stdOut) { + output = stdOut; + } + + public PythonInteractiveTaskMonitor(OutputStream stdout) { + this(new PrintWriter(stdout)); + } + + @Override + public void setMessage(String message) { + output.println(": " + message); + } + } } diff --git a/src/main/java/ghidrathon/GhidrathonScript.java b/src/main/java/ghidrathon/GhidrathonScript.java index b64a02c..34a98cb 100644 --- a/src/main/java/ghidrathon/GhidrathonScript.java +++ b/src/main/java/ghidrathon/GhidrathonScript.java @@ -3,163 +3,155 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt // Unless required by applicable law or agreed to in writing, software distributed under the License -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and limitations under the License. +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// See the License for the specific language governing permissions and limitations under the +// License. package ghidrathon; -import java.io.PrintWriter; -import java.io.FileNotFoundException; - import generic.jar.ResourceFile; - -import ghidra.app.script.GhidraState; import ghidra.app.script.GhidraScript; +import ghidra.app.script.GhidraScriptProvider; import ghidra.app.script.GhidraScriptUtil; +import ghidra.app.script.GhidraState; import ghidra.app.services.ConsoleService; import ghidra.framework.plugintool.PluginTool; -import ghidra.app.script.GhidraScriptProvider; - -import ghidrathon.GhidrathonUtils; -import ghidrathon.GhidrathonConfig; import ghidrathon.interpreter.GhidrathonInterpreter; +import java.io.FileNotFoundException; +import java.io.PrintWriter; public class GhidrathonScript extends GhidraScript { - - @Override - protected void run() { - - GhidrathonInterpreter python = null; - GhidrathonConfig config = GhidrathonUtils.getDefaultGhidrathonConfig(); - - // init Ghidrathon configuration - config.addStdOut(getStdOut()); - config.addStdErr(getStdErr()); - - try { - - python = GhidrathonInterpreter.get(config); - - // run Python script from Python interpreter - python.runScript(getSourceFile(), this); - - // flush stdout and stderr to ensure all is printed to console window - config.getStdErr().flush(); - config.getStdOut().flush(); - - } catch (RuntimeException e) { - - e.printStackTrace(config.getStdErr()); - - } finally { - - if (python != null) { - python.close(); - } - - } - - } - - /** - * Execute Python script using given script state - * See https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/Python/src/main/java/ghidra/python/PythonScript.java#L53 - * - * @param name Script name to execute - * @param scriptState Ghidra script state - */ - @Override - public void runScript(String name, GhidraState scriptState) { - - GhidrathonInterpreter python = null; - GhidrathonConfig config = GhidrathonUtils.getDefaultGhidrathonConfig(); - - config.addStdOut(getStdOut()); - config.addStdErr(getStdErr()); - - try { - - python = GhidrathonInterpreter.get(config); - - ResourceFile source = GhidraScriptUtil.findScriptByName(name); - if (source == null) { - throw new FileNotFoundException("could not find file " + name); - } - - GhidraScriptProvider provider = GhidraScriptUtil.getProvider(source); - GhidraScript script = provider.getScriptInstance(source, writer); - - if (script == null) { - throw new RuntimeException("could not init ghidra script instance"); - } - - if (scriptState == state) { - updateStateFromVariables(); - } - - if (script instanceof GhidrathonScript) { - script.set(scriptState, monitor, writer); - - GhidrathonScript ghidrathonScript = (GhidrathonScript) script; - - // run Python script using interpreter - python.runScript(ghidrathonScript.getSourceFile(), ghidrathonScript); - } else { - script.execute(scriptState, monitor, writer); - } - - if (scriptState == state) { - loadVariablesFromState(); - } - - } catch (Exception e) { - - e.printStackTrace(config.getStdErr()); - - } finally { - - if (python != null) { - python.close(); - } - - } - - } - - private PrintWriter getStdOut() { - - PluginTool tool = state.getTool(); - - if (tool != null) { - ConsoleService console = tool.getService(ConsoleService.class); - - if (console != null) { - return console.getStdOut(); - } - } - - return new PrintWriter(System.out, true); - } - - private PrintWriter getStdErr() { - - PluginTool tool = state.getTool(); - - if (tool != null) { - ConsoleService console = tool.getService(ConsoleService.class); - - if (console != null) { - return console.getStdErr(); - } - } - - return new PrintWriter(System.err, true); - } - - @Override - public String getCategory() { - - return "Ghidrathon"; - - } + + @Override + protected void run() { + + GhidrathonInterpreter python = null; + GhidrathonConfig config = GhidrathonUtils.getDefaultGhidrathonConfig(); + + // init Ghidrathon configuration + config.addStdOut(getStdOut()); + config.addStdErr(getStdErr()); + + try { + + python = GhidrathonInterpreter.get(config); + + // run Python script from Python interpreter + python.runScript(getSourceFile(), this); + + // flush stdout and stderr to ensure all is printed to console window + config.getStdErr().flush(); + config.getStdOut().flush(); + + } catch (RuntimeException e) { + + e.printStackTrace(config.getStdErr()); + + } finally { + + if (python != null) { + python.close(); + } + } + } + + /** + * Execute Python script using given script state See + * https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/Python/src/main/java/ghidra/python/PythonScript.java#L53 + * + * @param name Script name to execute + * @param scriptState Ghidra script state + */ + @Override + public void runScript(String name, GhidraState scriptState) { + + GhidrathonInterpreter python = null; + GhidrathonConfig config = GhidrathonUtils.getDefaultGhidrathonConfig(); + + config.addStdOut(getStdOut()); + config.addStdErr(getStdErr()); + + try { + + python = GhidrathonInterpreter.get(config); + + ResourceFile source = GhidraScriptUtil.findScriptByName(name); + if (source == null) { + throw new FileNotFoundException("could not find file " + name); + } + + GhidraScriptProvider provider = GhidraScriptUtil.getProvider(source); + GhidraScript script = provider.getScriptInstance(source, writer); + + if (script == null) { + throw new RuntimeException("could not init ghidra script instance"); + } + + if (scriptState == state) { + updateStateFromVariables(); + } + + if (script instanceof GhidrathonScript) { + script.set(scriptState, monitor, writer); + + GhidrathonScript ghidrathonScript = (GhidrathonScript) script; + + // run Python script using interpreter + python.runScript(ghidrathonScript.getSourceFile(), ghidrathonScript); + } else { + script.execute(scriptState, monitor, writer); + } + + if (scriptState == state) { + loadVariablesFromState(); + } + + } catch (Exception e) { + + e.printStackTrace(config.getStdErr()); + + } finally { + + if (python != null) { + python.close(); + } + } + } + + private PrintWriter getStdOut() { + + PluginTool tool = state.getTool(); + + if (tool != null) { + ConsoleService console = tool.getService(ConsoleService.class); + + if (console != null) { + return console.getStdOut(); + } + } + + return new PrintWriter(System.out, true); + } + + private PrintWriter getStdErr() { + + PluginTool tool = state.getTool(); + + if (tool != null) { + ConsoleService console = tool.getService(ConsoleService.class); + + if (console != null) { + return console.getStdErr(); + } + } + + return new PrintWriter(System.err, true); + } + + @Override + public String getCategory() { + + return "Ghidrathon"; + } } diff --git a/src/main/java/ghidrathon/GhidrathonScriptProvider.java b/src/main/java/ghidrathon/GhidrathonScriptProvider.java index 81c63df..89f0422 100644 --- a/src/main/java/ghidrathon/GhidrathonScriptProvider.java +++ b/src/main/java/ghidrathon/GhidrathonScriptProvider.java @@ -3,91 +3,86 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt // Unless required by applicable law or agreed to in writing, software distributed under the License -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and limitations under the License. +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// See the License for the specific language governing permissions and limitations under the +// License. package ghidrathon; +import generic.jar.ResourceFile; +import ghidra.app.script.GhidraScript; +import ghidra.app.script.GhidraScriptLoadException; +import ghidra.app.script.GhidraScriptProvider; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; -import generic.jar.ResourceFile; +public class GhidrathonScriptProvider extends GhidraScriptProvider { -import ghidra.app.script.GhidraScript; -import ghidra.app.script.GhidraScriptProvider; -import ghidra.app.script.GhidraScriptLoadException; + @Override + public String getDescription() { -public class GhidrathonScriptProvider extends GhidraScriptProvider { + return "Python 3"; + } + + @Override + public String getExtension() { + + return ".py"; + } + + @Override + public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) + throws GhidraScriptLoadException { + try { + GhidraScript script; + script = GhidrathonScript.class.getDeclaredConstructor().newInstance(); + script.setSourceFile(sourceFile); + + return script; + } catch (ReflectiveOperationException e) { + throw new GhidraScriptLoadException("Unable to instantiate: " + e.getMessage(), e); + } + } + + @Override + public void createNewScript(ResourceFile newScript, String category) throws IOException { + + PrintWriter writer = new PrintWriter(new FileWriter(newScript.getFile(false))); + + writeHeader(writer, category); + writer.println(""); + writeBody(writer); + writer.println(""); + writer.close(); + } + + @Override + public String getCommentCharacter() { + + return "#"; + } + + /** + * Commandeer the .py script extension + * + *

Ghidra loads script providers in order determined by Collections.sort; Ghidra then selects + * the first script provider that accepts the file extension of the script to be executed see + * https://github.com/NationalSecurityAgency/ghidra/blob/8b2ea61e27c07c48dc21eff9095905f739208703/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java#L274-L281 + * + *

Collections.sort invokes GhidraScriptProvider.compareTo so we can override compareTo and + * check if the script provider we are being compared to uses the .py extension; if true, we + * simply return -1 to be ordered higher in the list of script providers used by Ghidra + */ + @Override + public int compareTo(GhidraScriptProvider that) { - @Override - public String getDescription() { - - return "Python 3"; - - } - - @Override - public String getExtension() { - - return ".py"; - - } - - @Override - public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) - throws GhidraScriptLoadException { - try{ - GhidraScript script; - script = GhidrathonScript.class.getDeclaredConstructor().newInstance(); - script.setSourceFile(sourceFile); - - return script; - } - catch (ReflectiveOperationException e) { - throw new GhidraScriptLoadException("Unable to instantiate: " + e.getMessage(), e); - } - } - - @Override - public void createNewScript(ResourceFile newScript, String category) throws IOException { - - PrintWriter writer = new PrintWriter(new FileWriter(newScript.getFile(false))); - - writeHeader(writer, category); - writer.println(""); - writeBody(writer); - writer.println(""); - writer.close(); - - } - - @Override - public String getCommentCharacter() { - - return "#"; - - } - - /** - * Commandeer the .py script extension - * - * Ghidra loads script providers in order determined by Collections.sort; Ghidra then selects the first script provider that accepts the file extension of the script to be executed - * see https://github.com/NationalSecurityAgency/ghidra/blob/8b2ea61e27c07c48dc21eff9095905f739208703/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java#L274-L281 - * - * Collections.sort invokes GhidraScriptProvider.compareTo so we can override compareTo and check if the script provider we are being compared to uses the .py extension; if true, we simply return - * -1 to be ordered higher in the list of script providers used by Ghidra - */ - @Override - public int compareTo(GhidraScriptProvider that) { - - if (that.getExtension().equals(".py")) { - // return -1 so our script provider is preferred - return -1; - } - - return super.compareTo(that); - - } + if (that.getExtension().equals(".py")) { + // return -1 so our script provider is preferred + return -1; + } + return super.compareTo(that); + } } diff --git a/src/main/java/ghidrathon/GhidrathonUtils.java b/src/main/java/ghidrathon/GhidrathonUtils.java index f68fcad..c66cbeb 100644 --- a/src/main/java/ghidrathon/GhidrathonUtils.java +++ b/src/main/java/ghidrathon/GhidrathonUtils.java @@ -3,100 +3,97 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt // Unless required by applicable law or agreed to in writing, software distributed under the License -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and limitations under the License. +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// See the License for the specific language governing permissions and limitations under the +// License. package ghidrathon; +import ghidra.framework.Application; +import ghidra.framework.options.SaveState; +import ghidra.util.Msg; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; -import ghidra.util.Msg; -import ghidra.framework.Application; -import ghidra.framework.options.SaveState; - -import ghidrathon.GhidrathonConfig; - -/** - * Utility functions - */ +/** Utility functions */ public class GhidrathonUtils { - // name of this extension e.g. "Ghidrathon" - public static final String THIS_EXTENSION_NAME = Application.getMyModuleRootDirectory().getName(); - - private static final String DEFAULT_CONFIG_FILENAME = "GhidrathonConfig.xml"; - private static final String JAVA_EXCLUDE_LIBS_KEY = "JAVA_EXCLUDE_LIBS"; - private static final String PY_SHARED_MODULES_KEY = "PYTHON_SHARED_MODULES"; - private static final String PY_INCLUDE_PATHS_KEY = "PYTHON_INCLUDE_PATHS"; - - /** - * Get Ghidrathon's default configuration - default configuration is stored in data/ and copied to Ghidra user - * settings directory when first accessed - */ - public static GhidrathonConfig getDefaultGhidrathonConfig() { - - GhidrathonConfig config = new GhidrathonConfig(); - File userSettingsPath = new File(Application.getUserSettingsDirectory(), DEFAULT_CONFIG_FILENAME); - - // copy configuration from /data to Ghidra user settings if file does not already exist - if (!userSettingsPath.isFile()) { - - Msg.info(GhidrathonUtils.class, "Addings configuration to user settings at " + userSettingsPath); - - try { + // name of this extension e.g. "Ghidrathon" + public static final String THIS_EXTENSION_NAME = Application.getMyModuleRootDirectory().getName(); - File dataPath = Application.getModuleDataFile(THIS_EXTENSION_NAME, DEFAULT_CONFIG_FILENAME).getFile(false); - Files.copy(dataPath.toPath(), userSettingsPath.toPath(), StandardCopyOption.REPLACE_EXISTING); + private static final String DEFAULT_CONFIG_FILENAME = "GhidrathonConfig.xml"; + private static final String JAVA_EXCLUDE_LIBS_KEY = "JAVA_EXCLUDE_LIBS"; + private static final String PY_SHARED_MODULES_KEY = "PYTHON_SHARED_MODULES"; + private static final String PY_INCLUDE_PATHS_KEY = "PYTHON_INCLUDE_PATHS"; - } catch (IOException e) { + /** + * Get Ghidrathon's default configuration - default configuration is stored in data/ and copied to + * Ghidra user settings directory when first accessed + */ + public static GhidrathonConfig getDefaultGhidrathonConfig() { - Msg.error(GhidrathonUtils.class, "Failed to write user configuration [" + e + "]"); - return config; + GhidrathonConfig config = new GhidrathonConfig(); + File userSettingsPath = + new File(Application.getUserSettingsDirectory(), DEFAULT_CONFIG_FILENAME); - } - } + // copy configuration from /data to Ghidra user settings if file does not already exist + if (!userSettingsPath.isFile()) { - SaveState state = null; + Msg.info( + GhidrathonUtils.class, "Addings configuration to user settings at " + userSettingsPath); - // attempt to read configuration from Ghidra user settings - try { + try { - state = new SaveState(userSettingsPath); + File dataPath = + Application.getModuleDataFile(THIS_EXTENSION_NAME, DEFAULT_CONFIG_FILENAME) + .getFile(false); + Files.copy( + dataPath.toPath(), userSettingsPath.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { + } catch (IOException e) { - Msg.error(GhidrathonUtils.class, "Failed to read configuration state [" + e + "]"); - return config; + Msg.error(GhidrathonUtils.class, "Failed to write user configuration [" + e + "]"); + return config; + } + } - } + SaveState state = null; - // add Java exclude libs that will be ignored when importing from Python - this is used to avoid - // naming conflicts, e.g. "pdb" - for (String name: state.getStrings(JAVA_EXCLUDE_LIBS_KEY, new String[0])) { + // attempt to read configuration from Ghidra user settings + try { - config.addJavaExcludeLib(name); + state = new SaveState(userSettingsPath); - } + } catch (IOException e) { - // add Python include paths - for (String name: state.getStrings(PY_INCLUDE_PATHS_KEY, new String[0])) { + Msg.error(GhidrathonUtils.class, "Failed to read configuration state [" + e + "]"); + return config; + } - config.addPythonIncludePath(name); + // add Java exclude libs that will be ignored when importing from Python - this is used to avoid + // naming conflicts, e.g. "pdb" + for (String name : state.getStrings(JAVA_EXCLUDE_LIBS_KEY, new String[0])) { - } + config.addJavaExcludeLib(name); + } - // add Python shared modules - these modules are handled specially by Jep to avoid crashes caused - // by CPython extensions, e.g. numpy - for (String name: state.getStrings(PY_SHARED_MODULES_KEY, new String[0])) { + // add Python include paths + for (String name : state.getStrings(PY_INCLUDE_PATHS_KEY, new String[0])) { - config.addPythonSharedModule(name); + config.addPythonIncludePath(name); + } - } + // add Python shared modules - these modules are handled specially by Jep to avoid crashes + // caused + // by CPython extensions, e.g. numpy + for (String name : state.getStrings(PY_SHARED_MODULES_KEY, new String[0])) { - return config; - } + config.addPythonSharedModule(name); + } + return config; + } } diff --git a/src/main/java/ghidrathon/interpreter/GhidrathonInterpreter.java b/src/main/java/ghidrathon/interpreter/GhidrathonInterpreter.java index eed15ab..bc4b4e7 100644 --- a/src/main/java/ghidrathon/interpreter/GhidrathonInterpreter.java +++ b/src/main/java/ghidrathon/interpreter/GhidrathonInterpreter.java @@ -3,470 +3,444 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: [package root]/LICENSE.txt // Unless required by applicable law or agreed to in writing, software distributed under the License -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and limitations under the License. +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. +// See the License for the specific language governing permissions and limitations under the +// License. package ghidrathon.interpreter; -import java.io.File; -import java.lang.reflect.*; -import java.io.PrintWriter; -import java.io.IOException; -import java.io.FileNotFoundException; - -import org.apache.commons.io.output.WriterOutputStream; - import generic.jar.ResourceFile; - -import ghidra.framework.Application; import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScriptUtil; - +import ghidra.framework.Application; +import ghidrathon.GhidrathonClassEnquirer; +import ghidrathon.GhidrathonConfig; +import ghidrathon.GhidrathonScript; +import ghidrathon.GhidrathonUtils; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.*; import jep.Jep; import jep.JepConfig; import jep.JepException; import jep.MainInterpreter; +import org.apache.commons.io.output.WriterOutputStream; -import ghidrathon.GhidrathonUtils; -import ghidrathon.GhidrathonScript; -import ghidrathon.GhidrathonConfig; -import ghidrathon.GhidrathonClassEnquirer; - -/** - * Utility class used to configure a Jep instance to access Ghidra - */ +/** Utility class used to configure a Jep instance to access Ghidra */ public class GhidrathonInterpreter { - private Jep jep = null; - private GhidrathonConfig ghidrathonConfig = null; + private Jep jep = null; + private GhidrathonConfig ghidrathonConfig = null; + + private final JepConfig jepConfig = new JepConfig(); + private final GhidrathonClassEnquirer ghidrathonClassEnquirer = new GhidrathonClassEnquirer(); + + private boolean scriptMethodsInjected = false; + + /** + * Create and configure a new GhidrathonInterpreter instance. + * + * @throws JepException + * @throws IOException + */ + private GhidrathonInterpreter(GhidrathonConfig config) throws JepException, IOException { + + ghidrathonConfig = config; + + // configure the Python includes path with the user's Ghdira script directory + String paths = ""; + for (ResourceFile resourceFile : GhidraScriptUtil.getScriptSourceDirectories()) { + + paths += resourceFile.getFile(false).getAbsolutePath() + File.pathSeparator; + } + + // add data/python/ to Python includes directory + paths += + Application.getModuleDataSubDirectory(GhidrathonUtils.THIS_EXTENSION_NAME, "python") + + File.pathSeparator; + + // add paths specified in Ghidrathon config + for (String path : ghidrathonConfig.getPythonIncludePaths()) { + + paths += path + File.pathSeparator; + } + + // configure Java names that will be ignored when importing from Python + for (String name : ghidrathonConfig.getJavaExcludeLibs()) { + + ghidrathonClassEnquirer.addJavaExcludeLib(name); + } + + // set the class loader with access to Ghidra scripting API + jepConfig.setClassLoader(ClassLoader.getSystemClassLoader()); + + // set class enquirer used to handle Java imports from Python + jepConfig.setClassEnquirer(ghidrathonClassEnquirer); + + // configure Python includes Path + jepConfig.addIncludePaths(paths); + + // add Python shared modules - these should be CPython modules for Jep to handle specially + for (String name : ghidrathonConfig.getPythonSharedModules()) { + + jepConfig.addSharedModules(name); + } + + // configure Jep stdout + if (ghidrathonConfig.getStdOut() != null) { + + jepConfig.redirectStdout( + new WriterOutputStream( + ghidrathonConfig.getStdOut(), System.getProperty("file.encoding")) { + + @Override + public void write(byte[] b, int off, int len) throws IOException { + super.write(b, off, len); + flush(); // flush the output to ensure it is displayed in real-time + } + }); + } + + // configure Jep stderr + if (ghidrathonConfig.getStdErr() != null) { + jepConfig.redirectStdErr( + new WriterOutputStream( + ghidrathonConfig.getStdErr(), System.getProperty("file.encoding")) { + + @Override + public void write(byte[] b, int off, int len) throws IOException { + super.write(b, off, len); + flush(); // flush the error to ensure it is displayed in real-time + } + }); + } + + // we must set the native Jep library before creating a Jep instance + setJepNativeBinaryPath(); + + // create a new Jep interpreter instance + jep = new jep.SubInterpreter(jepConfig); + + // now that everything is configured, we should be able to run some utility scripts + // to help us further configure the Python environment + setJepEval(); + setJepRunScript(); + } + + /** + * Configure native Jep library. + * + *

User must build and include native Jep library in the appropriate OS folder prior to + * building this extension. Requires os/win64/libjep.dll for Windows Requires os/linux64/libjep.so + * for Linux + * + * @throws JepException + * @throws FileNotFoundException + */ + private void setJepNativeBinaryPath() throws JepException, FileNotFoundException { + + File nativeJep; + + try { + + nativeJep = Application.getOSFile(GhidrathonUtils.THIS_EXTENSION_NAME, "libjep.so"); + + } catch (FileNotFoundException e) { + + // whoops try Windows + nativeJep = Application.getOSFile(GhidrathonUtils.THIS_EXTENSION_NAME, "jep.dll"); + } + + try { + + MainInterpreter.setJepLibraryPath(nativeJep.getAbsolutePath()); + + } catch (IllegalStateException e) { + // library path has already been set elsewhere, we expect this to happen as Jep + // Maininterpreter + // thread exists forever once it's created + } + } + + /** + * Configure "jepeval" function in Python land. + * + *

We use Python to evaluate Python statements because as of Jep 4.0 interactive mode is no + * longer supported. As a side effect we also get better tracebacks. Requires + * data/python/jepeval.py. + * + * @throws JepException + * @throws FileNotFoundException + */ + private void setJepEval() throws JepException, FileNotFoundException { + + ResourceFile file = + Application.getModuleDataFile(GhidrathonUtils.THIS_EXTENSION_NAME, "python/jepeval.py"); + + jep.runScript(file.getAbsolutePath()); + } + + /** + * Configure "jep_runscript" function in Python land. + * + *

We use Python to run Python scripts because it gives us better access to tracebacks. + * Requires data/python/jeprunscript.py. + * + * @throws JepException + * @throws FileNotFoundException + */ + private void setJepRunScript() throws JepException, FileNotFoundException { + + ResourceFile file = + Application.getModuleDataFile( + GhidrathonUtils.THIS_EXTENSION_NAME, "python/jeprunscript.py"); + + jep.runScript(file.getAbsolutePath()); + } + + /** + * Configure GhidraState. + * + *

This exposes things like currentProgram, currentAddress, etc. similar to Jython. We need to + * repeat this prior to executing new Python code in order to provide the latest state e.g. that + * current currentAddress. Requires data/python/jepinject.py. + * + * @param script GhidrathonScript instance + * @throws JepException + * @throws FileNotFoundException + */ + private void injectScriptHierarchy(GhidraScript script) + throws JepException, FileNotFoundException { + if (script == null) { + return; + } + + ResourceFile file = + Application.getModuleDataFile(GhidrathonUtils.THIS_EXTENSION_NAME, "python/jepbuiltins.py"); + jep.runScript(file.getAbsolutePath()); + + // inject GhidraScript public/private fields e.g. currentAddress into Python + // see + // https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/Python/src/main/java/ghidra/python/GhidraPythonInterpreter.java#L341-L377 + for (Class scriptClass = script.getClass(); + scriptClass != Object.class; + scriptClass = scriptClass.getSuperclass()) { + for (Field field : scriptClass.getDeclaredFields()) { + if (Modifier.isPublic(field.getModifiers()) || Modifier.isProtected(field.getModifiers())) { + try { + field.setAccessible(true); + jep.invoke("jep_set_builtin", field.getName(), field.get(script)); + } catch (IllegalAccessException iae) { + throw new JepException("Unexpected security manager being used!"); + } + } + } + } + + if (!scriptMethodsInjected) { + // inject GhidraScript methods into Python + file = + Application.getModuleDataFile(GhidrathonUtils.THIS_EXTENSION_NAME, "python/jepinject.py"); + jep.set("__ghidra_script__", script); + jep.runScript(file.getAbsolutePath()); + } + + scriptMethodsInjected = true; + } + + /** + * Create a new GhidrathonInterpreter instance. + * + * @return GhidrathonInterpreter + * @throws RuntimeException + */ + public static GhidrathonInterpreter get(GhidrathonConfig ghidrathonConfig) + throws RuntimeException { + + try { + + return new GhidrathonInterpreter(ghidrathonConfig); + + } catch (Exception e) { + + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + /** + * Close Jep instance. + * + *

We must call this function when finished with a Jep instance, otherwise, issues arise if we + * try to create a new Jep instance on the same thread. This function must be called from the same + * thread that created the Jep instance. + */ + public void close() { + + try { + + if (jep != null) { + jep.close(); + jep = null; + } + + } catch (JepException e) { + + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + /** + * Pass value from Java to Python + * + * @param value name as seen in Python + * @param o Java object to be passed to Python + * @return + */ + public void set(String name, Object o) { + + try { + + jep.set(name, o); + + } catch (JepException e) { + + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + /** + * Evaluate Python statement. + * + *

This function must be called from the same thread that instantiated the Jep instance. + * + * @param line Python statement + * @return True (need more input), False (no more input needed) + */ + public boolean eval(String line) { + + try { + + return (boolean) jep.invoke("jepeval", line); + + } catch (JepException e) { + + // Python exceptions should be handled in Python land; something bad must have happened + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + /** + * Evaluate Python statement. + * + *

This function must be called from the same thread that instantiated the Jep instance. + * + * @param line Python statement + * @param script GhidrathonScript with desired state. + * @return True (need more input), False (no more input needed) + * @throws FileNotFoundException + */ + public boolean eval(String line, GhidrathonScript script) { + + try { + + injectScriptHierarchy(script); + + } catch (JepException | FileNotFoundException e) { + + // we made it here; something bad went wrong, raise to caller + e.printStackTrace(); + throw new RuntimeException(e); + } + + try { + + return (boolean) jep.invoke("jepeval", line); + + } catch (JepException e) { + + // Python exceptions should be handled in Python land; something bad must have happened + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + /** + * Run Python script. + * + *

This function must be called from the same thread that instantiated the Jep instance. + * + * @param file Python script to execute + */ + public void runScript(ResourceFile file) { + + try { + + jep.invoke("jep_runscript", file.getAbsolutePath()); + + } catch (JepException e) { + + // Python exceptions should be handled in Python land; something bad must have happened + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + /** + * Run Python script. + * + *

This function must be called from the same thread that instantiated the Jep instance. + * + * @param file Python script to execute + * @param script GhidrathonScript with desired state. + * @throws FileNotFoundException + */ + public void runScript(ResourceFile file, GhidraScript script) { + + try { + + injectScriptHierarchy(script); + jep.invoke("jep_runscript", file.getAbsolutePath()); + + } catch (JepException | FileNotFoundException e) { + + // Python exceptions should be handled in Python land; something bad must have happened + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + public void printWelcome() { + + try { + + ResourceFile file = + Application.getModuleDataFile( + GhidrathonUtils.THIS_EXTENSION_NAME, "python/jepwelcome.py"); + + jep.set("GhidraVersion", Application.getApplicationVersion()); + + jep.runScript(file.getAbsolutePath()); + + } catch (JepException | FileNotFoundException e) { + + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + public String getPrimaryPrompt() { + + return ">>> "; + } - private final JepConfig jepConfig = new JepConfig(); - private final GhidrathonClassEnquirer ghidrathonClassEnquirer = new GhidrathonClassEnquirer(); + public String getSecondaryPrompt() { - private boolean scriptMethodsInjected = false; - - /** - * Create and configure a new GhidrathonInterpreter instance. - * - * @throws JepException - * @throws IOException - */ - private GhidrathonInterpreter(GhidrathonConfig config) throws JepException, IOException{ - - ghidrathonConfig = config; - - // configure the Python includes path with the user's Ghdira script directory - String paths = ""; - for (ResourceFile resourceFile : GhidraScriptUtil.getScriptSourceDirectories()) { - - paths += resourceFile.getFile(false).getAbsolutePath() + File.pathSeparator; - - } - - // add data/python/ to Python includes directory - paths += Application.getModuleDataSubDirectory(GhidrathonUtils.THIS_EXTENSION_NAME, "python") + File.pathSeparator; - - // add paths specified in Ghidrathon config - for (String path: ghidrathonConfig.getPythonIncludePaths()) { - - paths += path + File.pathSeparator; - - } - - // configure Java names that will be ignored when importing from Python - for (String name: ghidrathonConfig.getJavaExcludeLibs()) { - - ghidrathonClassEnquirer.addJavaExcludeLib(name); - - } - - // set the class loader with access to Ghidra scripting API - jepConfig.setClassLoader(ClassLoader.getSystemClassLoader()); - - // set class enquirer used to handle Java imports from Python - jepConfig.setClassEnquirer(ghidrathonClassEnquirer); - - // configure Python includes Path - jepConfig.addIncludePaths(paths); - - // add Python shared modules - these should be CPython modules for Jep to handle specially - for (String name: ghidrathonConfig.getPythonSharedModules()) { - - jepConfig.addSharedModules(name); - - } - - // configure Jep stdout - if (ghidrathonConfig.getStdOut() != null) { - - jepConfig.redirectStdout(new WriterOutputStream(ghidrathonConfig.getStdOut(), System.getProperty("file.encoding")) { - - @Override - public void write(byte[] b, int off, int len) throws IOException { - super.write(b, off, len); - flush(); // flush the output to ensure it is displayed in real-time - } - - }); - } - - // configure Jep stderr - if (ghidrathonConfig.getStdErr() != null ) { - jepConfig.redirectStdErr(new WriterOutputStream(ghidrathonConfig.getStdErr(), System.getProperty("file.encoding")) { - - @Override - public void write(byte[] b, int off, int len) throws IOException { - super.write(b, off, len); - flush(); // flush the error to ensure it is displayed in real-time - } - - }); - - } - - - - // we must set the native Jep library before creating a Jep instance - setJepNativeBinaryPath(); - - // create a new Jep interpreter instance - jep = new jep.SubInterpreter(jepConfig); - - // now that everything is configured, we should be able to run some utility scripts - // to help us further configure the Python environment - setJepEval(); - setJepRunScript(); - - } - - /** - * Configure native Jep library. - * - * User must build and include native Jep library in the appropriate OS folder prior to - * building this extension. - * Requires os/win64/libjep.dll for Windows - * Requires os/linux64/libjep.so for Linux - * - * @throws JepException - * @throws FileNotFoundException - */ - private void setJepNativeBinaryPath() throws JepException, FileNotFoundException { - - File nativeJep; - - try { - - nativeJep = Application.getOSFile(GhidrathonUtils.THIS_EXTENSION_NAME, "libjep.so"); - - } catch (FileNotFoundException e) { - - // whoops try Windows - nativeJep = Application.getOSFile(GhidrathonUtils.THIS_EXTENSION_NAME, "jep.dll"); - - } - - try { - - MainInterpreter.setJepLibraryPath(nativeJep.getAbsolutePath()); - - } catch (IllegalStateException e) { - // library path has already been set elsewhere, we expect this to happen as Jep Maininterpreter - // thread exists forever once it's created - } - - } - - - /** - * Configure "jepeval" function in Python land. - * - * We use Python to evaluate Python statements because as of Jep 4.0 interactive mode - * is no longer supported. As a side effect we also get better tracebacks. - * Requires data/python/jepeval.py. - * - * @throws JepException - * @throws FileNotFoundException - */ - private void setJepEval() throws JepException, FileNotFoundException { - - ResourceFile file = Application.getModuleDataFile(GhidrathonUtils.THIS_EXTENSION_NAME, "python/jepeval.py"); - - jep.runScript(file.getAbsolutePath()); - - } - - /** - * Configure "jep_runscript" function in Python land. - * - * We use Python to run Python scripts because it gives us better access to tracebacks. - * Requires data/python/jeprunscript.py. - * - * @throws JepException - * @throws FileNotFoundException - */ - private void setJepRunScript() throws JepException, FileNotFoundException { - - ResourceFile file = Application.getModuleDataFile(GhidrathonUtils.THIS_EXTENSION_NAME, "python/jeprunscript.py"); - - jep.runScript(file.getAbsolutePath()); - - } - - /** - * Configure GhidraState. - * - * This exposes things like currentProgram, currentAddress, etc. similar to Jython. We need to repeat this - * prior to executing new Python code in order to provide the latest state e.g. that current currentAddress. - * Requires data/python/jepinject.py. - * - * @param script GhidrathonScript instance - * @throws JepException - * @throws FileNotFoundException - */ - private void injectScriptHierarchy(GhidraScript script) throws JepException, FileNotFoundException { - if (script == null) { - return; - } - - ResourceFile file = Application.getModuleDataFile(GhidrathonUtils.THIS_EXTENSION_NAME, "python/jepbuiltins.py"); - jep.runScript(file.getAbsolutePath()); - - // inject GhidraScript public/private fields e.g. currentAddress into Python - // see https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/Python/src/main/java/ghidra/python/GhidraPythonInterpreter.java#L341-L377 - for (Class scriptClass = script.getClass(); scriptClass != Object.class; scriptClass = - scriptClass.getSuperclass()) { - for (Field field : scriptClass.getDeclaredFields()) { - if (Modifier.isPublic(field.getModifiers()) || - Modifier.isProtected(field.getModifiers())) { - try { - field.setAccessible(true); - jep.invoke("jep_set_builtin", field.getName(), field.get(script)); - } - catch (IllegalAccessException iae) { - throw new JepException("Unexpected security manager being used!"); - } - } - } - } - - if (!scriptMethodsInjected) { - // inject GhidraScript methods into Python - file = Application.getModuleDataFile(GhidrathonUtils.THIS_EXTENSION_NAME, "python/jepinject.py"); - jep.set("__ghidra_script__", script); - jep.runScript(file.getAbsolutePath()); - } - - scriptMethodsInjected = true; - } - - /** - * Create a new GhidrathonInterpreter instance. - * - * @return GhidrathonInterpreter - * @throws RuntimeException - */ - public static GhidrathonInterpreter get(GhidrathonConfig ghidrathonConfig) throws RuntimeException { - - try { - - return new GhidrathonInterpreter(ghidrathonConfig); - - } catch (Exception e) { - - e.printStackTrace(); - throw new RuntimeException(e); - - } - - } - - /** - * Close Jep instance. - * - * We must call this function when finished with a Jep instance, otherwise, issues arise if we try to create a - * new Jep instance on the same thread. This function must be called from the same thread that created the Jep instance. - */ - public void close() { - - try { - - if (jep != null) { - jep.close(); - jep = null; - } - - } catch (JepException e) { - - e.printStackTrace(); - throw new RuntimeException(e); - - } - - } - - /** - * Pass value from Java to Python - * - * @param value name as seen in Python - * @param o Java object to be passed to Python - * @return - */ - public void set(String name, Object o) { - - try { - - jep.set(name, o); - - } catch (JepException e) { - - e.printStackTrace(); - throw new RuntimeException(e); - - } - - } - - /** - * Evaluate Python statement. - * - * This function must be called from the same thread that instantiated the Jep instance. - * - * @param line Python statement - * - * @return True (need more input), False (no more input needed) - */ - public boolean eval(String line) { - - try { - - return (boolean) jep.invoke("jepeval", line); - - } catch (JepException e) { - - // Python exceptions should be handled in Python land; something bad must have happened - e.printStackTrace(); - throw new RuntimeException(e); - - } - - } - - /** - * Evaluate Python statement. - * - * This function must be called from the same thread that instantiated the Jep instance. - * - * @param line Python statement - * @param script GhidrathonScript with desired state. - * - * @return True (need more input), False (no more input needed) - * @throws FileNotFoundException - */ - public boolean eval(String line, GhidrathonScript script) { - - try { - - injectScriptHierarchy(script); - - } catch (JepException | FileNotFoundException e) { - - // we made it here; something bad went wrong, raise to caller - e.printStackTrace(); - throw new RuntimeException(e); - - } - - try { - - return (boolean) jep.invoke("jepeval", line); - - } catch (JepException e) { - - // Python exceptions should be handled in Python land; something bad must have happened - e.printStackTrace(); - throw new RuntimeException(e); - - } - - } - - /** - * Run Python script. - * - * This function must be called from the same thread that instantiated the Jep instance. - * - * @param file Python script to execute - */ - public void runScript(ResourceFile file) { - - try { - - jep.invoke("jep_runscript", file.getAbsolutePath()); - - } catch (JepException e) { - - // Python exceptions should be handled in Python land; something bad must have happened - e.printStackTrace(); - throw new RuntimeException(e); - - } - - } - - /** - * Run Python script. - * - * This function must be called from the same thread that instantiated the Jep instance. - * - * @param file Python script to execute - * @param script GhidrathonScript with desired state. - * @throws FileNotFoundException - */ - public void runScript(ResourceFile file, GhidraScript script) { - - try { - - injectScriptHierarchy(script); - jep.invoke("jep_runscript", file.getAbsolutePath()); - - } catch (JepException | FileNotFoundException e) { - - // Python exceptions should be handled in Python land; something bad must have happened - e.printStackTrace(); - throw new RuntimeException(e); - - } - - } - - public void printWelcome() { - - try { - - ResourceFile file = Application.getModuleDataFile(GhidrathonUtils.THIS_EXTENSION_NAME, "python/jepwelcome.py"); - - jep.set("GhidraVersion", Application.getApplicationVersion()); - - jep.runScript(file.getAbsolutePath()); - - } catch (JepException | FileNotFoundException e) { - - e.printStackTrace(); - throw new RuntimeException(e); - - } - - } - - public String getPrimaryPrompt() { - - return ">>> "; - - } - - public String getSecondaryPrompt() { - - return "... "; - - } + return "... "; + } }