From a0cd558b120c473d7bdc6f42f53fffe4230b92d5 Mon Sep 17 00:00:00 2001 From: K VAISHAKH <133781093+vaishakh787@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:23:28 +0530 Subject: [PATCH 1/4] ghidra: initial capa explorer extension MVP --- capa/ghidra/plugin/extension/README.md | 1 + capa/ghidra/plugin/extension/build.gradle | 66 +++++++ .../plugin/extension/extension.properties | 5 + .../src/main/help/help/TOC_Source.xml | 57 ++++++ .../help/help/topics/capaexplorer/help.html | 23 +++ .../src/main/java/capa/ghidra/CapaPlugin.java | 59 ++++++ .../main/java/capa/ghidra/CapaProvider.java | 52 +++++ .../java/capa/ghidra/CapaPythonBridge.java | 47 +++++ .../capaexplorer/CapaExplorerAnalyzer.java | 74 +++++++ .../capaexplorer/CapaExplorerExporter.java | 77 ++++++++ .../capaexplorer/CapaExplorerFileSystem.java | 184 ++++++++++++++++++ .../java/capaexplorer/CapaExplorerLoader.java | 80 ++++++++ .../java/capaexplorer/CapaExplorerPlugin.java | 116 +++++++++++ .../src/main/resources/images/README.txt | 2 + 14 files changed, 843 insertions(+) create mode 100644 capa/ghidra/plugin/extension/README.md create mode 100644 capa/ghidra/plugin/extension/build.gradle create mode 100644 capa/ghidra/plugin/extension/extension.properties create mode 100644 capa/ghidra/plugin/extension/src/main/help/help/TOC_Source.xml create mode 100644 capa/ghidra/plugin/extension/src/main/help/help/topics/capaexplorer/help.html create mode 100644 capa/ghidra/plugin/extension/src/main/java/capa/ghidra/CapaPlugin.java create mode 100644 capa/ghidra/plugin/extension/src/main/java/capa/ghidra/CapaProvider.java create mode 100644 capa/ghidra/plugin/extension/src/main/java/capa/ghidra/CapaPythonBridge.java create mode 100644 capa/ghidra/plugin/extension/src/main/java/capaexplorer/CapaExplorerAnalyzer.java create mode 100644 capa/ghidra/plugin/extension/src/main/java/capaexplorer/CapaExplorerExporter.java create mode 100644 capa/ghidra/plugin/extension/src/main/java/capaexplorer/CapaExplorerFileSystem.java create mode 100644 capa/ghidra/plugin/extension/src/main/java/capaexplorer/CapaExplorerLoader.java create mode 100644 capa/ghidra/plugin/extension/src/main/java/capaexplorer/CapaExplorerPlugin.java create mode 100644 capa/ghidra/plugin/extension/src/main/resources/images/README.txt diff --git a/capa/ghidra/plugin/extension/README.md b/capa/ghidra/plugin/extension/README.md new file mode 100644 index 000000000..08fe9847f --- /dev/null +++ b/capa/ghidra/plugin/extension/README.md @@ -0,0 +1 @@ +# CapaExplorer diff --git a/capa/ghidra/plugin/extension/build.gradle b/capa/ghidra/plugin/extension/build.gradle new file mode 100644 index 000000000..a3a305cb9 --- /dev/null +++ b/capa/ghidra/plugin/extension/build.gradle @@ -0,0 +1,66 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +// Builds a Ghidra Extension for a given Ghidra installation. +// +// An absolute path to the Ghidra installation directory must be supplied either by setting the +// GHIDRA_INSTALL_DIR environment variable or Gradle project property: +// +// > export GHIDRA_INSTALL_DIR= +// > gradle +// +// or +// +// > gradle -PGHIDRA_INSTALL_DIR= +// +// Gradle should be invoked from the directory of the project to build. Please see the +// application.gradle.version property in /Ghidra/application.properties +// for the correction version of Gradle to use for the Ghidra installation you specify. + +//----------------------START "DO NOT MODIFY" SECTION------------------------------ +def ghidraInstallDir + +if (System.env.GHIDRA_INSTALL_DIR) { + ghidraInstallDir = System.env.GHIDRA_INSTALL_DIR +} +else if (project.hasProperty("GHIDRA_INSTALL_DIR")) { + ghidraInstallDir = project.getProperty("GHIDRA_INSTALL_DIR") +} +else { + ghidraInstallDir = "" +} + +task distributeExtension { + group = "Ghidra" + + apply from: new File(ghidraInstallDir).getCanonicalPath() + "/support/buildExtension.gradle" + dependsOn ':buildExtension' +} +//----------------------END "DO NOT MODIFY" SECTION------------------------------- + +repositories { + // Declare dependency repositories here. This is not needed if dependencies are manually + // dropped into the lib/ directory. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html for more info. + // Ex: mavenCentral() +} + +dependencies { + // Any external dependencies added here will automatically be copied to the lib/ directory when + // this extension is built. +} + +// Exclude additional files from the built extension +// Ex: buildExtension.exclude '.idea/**' diff --git a/capa/ghidra/plugin/extension/extension.properties b/capa/ghidra/plugin/extension/extension.properties new file mode 100644 index 000000000..86a8a29a2 --- /dev/null +++ b/capa/ghidra/plugin/extension/extension.properties @@ -0,0 +1,5 @@ +name=@extname@ +description=The extension description can be customized by editing the extension.properties file. +author= +createdOn= +version=@extversion@ diff --git a/capa/ghidra/plugin/extension/src/main/help/help/TOC_Source.xml b/capa/ghidra/plugin/extension/src/main/help/help/TOC_Source.xml new file mode 100644 index 000000000..a34f62e8f --- /dev/null +++ b/capa/ghidra/plugin/extension/src/main/help/help/TOC_Source.xml @@ -0,0 +1,57 @@ + + + + + + + diff --git a/capa/ghidra/plugin/extension/src/main/help/help/topics/capaexplorer/help.html b/capa/ghidra/plugin/extension/src/main/help/help/topics/capaexplorer/help.html new file mode 100644 index 000000000..1f9d6a1fc --- /dev/null +++ b/capa/ghidra/plugin/extension/src/main/help/help/topics/capaexplorer/help.html @@ -0,0 +1,23 @@ + + + + + + + + + + + Skeleton Help File for a Module + + + + +

Skeleton Help File for a Module

+ +

This is a simple skeleton help topic. For a better description of what should and should not + go in here, see the "sample" Ghidra extension in the Extensions/Ghidra directory, or see your + favorite help topic. In general, language modules do not have their own help topics.

+ + diff --git a/capa/ghidra/plugin/extension/src/main/java/capa/ghidra/CapaPlugin.java b/capa/ghidra/plugin/extension/src/main/java/capa/ghidra/CapaPlugin.java new file mode 100644 index 000000000..9eadf6647 --- /dev/null +++ b/capa/ghidra/plugin/extension/src/main/java/capa/ghidra/CapaPlugin.java @@ -0,0 +1,59 @@ +package capa.ghidra; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.MenuData; +import ghidra.app.plugin.PluginCategoryNames; +import ghidra.app.plugin.ProgramPlugin; +import ghidra.framework.plugintool.PluginInfo; +import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.util.Msg; + +@PluginInfo( + status = PluginStatus.STABLE, + packageName = "Capa", + category = PluginCategoryNames.ANALYSIS, + shortDescription = "Run capa analysis", + description = "Capa explorer MVP for Ghidra" +) +public class CapaPlugin extends ProgramPlugin { + + private DockingAction action; + private CapaProvider provider; + + public CapaPlugin(PluginTool tool) { + super(tool); + + provider = new CapaProvider(tool); + createActions(); + } + + private void createActions() { + + action = new DockingAction("Run capa analysis", getName()) { + + @Override + public void actionPerformed(ActionContext context) { + + Msg.showInfo( + this, + null, + "CapaPlugin", + "Run capa analysis clicked" + ); + + provider.runCapa(currentProgram); + } + }; + + action.setMenuBarData( + new MenuData(new String[] { + "Tools", + "Run capa analysis" + }) + ); + + tool.addAction(action); + } +} \ No newline at end of file diff --git a/capa/ghidra/plugin/extension/src/main/java/capa/ghidra/CapaProvider.java b/capa/ghidra/plugin/extension/src/main/java/capa/ghidra/CapaProvider.java new file mode 100644 index 000000000..9ad87a662 --- /dev/null +++ b/capa/ghidra/plugin/extension/src/main/java/capa/ghidra/CapaProvider.java @@ -0,0 +1,52 @@ +package capa.ghidra; + +import docking.ComponentProvider; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; + +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JComponent; +import java.awt.Font; + +public class CapaProvider extends ComponentProvider { + + private final JTextArea output; + + public CapaProvider(PluginTool tool) { + super(tool, "Capa Explorer", "CapaExplorer"); + + output = new JTextArea(); + output.setEditable(false); + output.setFont(new Font("Monospaced", Font.PLAIN, 12)); + + tool.addComponentProvider(this, true); + tool.showComponentProvider(this, true); + } + + @Override + public JComponent getComponent() { + return new JScrollPane(output); + } + + public void runCapa(Program program) { + + if (program == null) { + output.setText("No program is currently open.\n"); + return; + } + + output.setText("Running capa...\n"); + + try { + String result = + CapaPythonBridge.run(program); + + output.append(result); + } + catch (Exception e) { + output.append("\nERROR:\n"); + output.append(e.toString()); + } + } +} \ No newline at end of file diff --git a/capa/ghidra/plugin/extension/src/main/java/capa/ghidra/CapaPythonBridge.java b/capa/ghidra/plugin/extension/src/main/java/capa/ghidra/CapaPythonBridge.java new file mode 100644 index 000000000..59b9a494e --- /dev/null +++ b/capa/ghidra/plugin/extension/src/main/java/capa/ghidra/CapaPythonBridge.java @@ -0,0 +1,47 @@ +package capa.ghidra; + +import ghidra.framework.Application; +import ghidra.program.model.listing.Program; +import generic.jar.ResourceFile; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; + +public class CapaPythonBridge { + + public static String run(Program program) throws Exception { + + // data/python/ + ResourceFile pythonDir = + Application.getModuleDataSubDirectory("python"); + + // data/python/capa_runner.py + File scriptFile = + new File(pythonDir.getFile(false), "capa_runner.py"); + + Process process = + new ProcessBuilder( + "python3", + scriptFile.getAbsolutePath() + ) + .redirectErrorStream(true) + .start(); + + StringBuilder output = new StringBuilder(); + + try (BufferedReader reader = + new BufferedReader( + new InputStreamReader( + process.getInputStream()))) { + + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + } + } + + process.waitFor(); + return output.toString(); + } +} \ No newline at end of file diff --git a/capa/ghidra/plugin/extension/src/main/java/capaexplorer/CapaExplorerAnalyzer.java b/capa/ghidra/plugin/extension/src/main/java/capaexplorer/CapaExplorerAnalyzer.java new file mode 100644 index 000000000..b73f7a936 --- /dev/null +++ b/capa/ghidra/plugin/extension/src/main/java/capaexplorer/CapaExplorerAnalyzer.java @@ -0,0 +1,74 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package capaexplorer; + +import ghidra.app.services.AbstractAnalyzer; +import ghidra.app.services.AnalyzerType; +import ghidra.app.util.importer.MessageLog; +import ghidra.framework.options.Options; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Program; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Provide class-level documentation that describes what this analyzer does. + */ +public class CapaExplorerAnalyzer extends AbstractAnalyzer { + + public CapaExplorerAnalyzer() { + + // Name the analyzer and give it a description. + + super("My Analyzer", "Analyzer description goes here", AnalyzerType.BYTE_ANALYZER); + } + + @Override + public boolean getDefaultEnablement(Program program) { + + // Return true if analyzer should be enabled by default + + return true; + } + + @Override + public boolean canAnalyze(Program program) { + + // Examine 'program' to determine of this analyzer should analyze it. Return true + // if it can. + + return true; + } + + @Override + public void registerOptions(Options options, Program program) { + + // If this analyzer has custom options, register them here + + options.registerOption("Option name goes here", false, null, + "Option description goes here"); + } + + @Override + public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) + throws CancelledException { + + // Perform analysis when things get added to the 'program'. Return true if the + // analysis succeeded. + + return false; + } +} diff --git a/capa/ghidra/plugin/extension/src/main/java/capaexplorer/CapaExplorerExporter.java b/capa/ghidra/plugin/extension/src/main/java/capaexplorer/CapaExplorerExporter.java new file mode 100644 index 000000000..d70fe9f62 --- /dev/null +++ b/capa/ghidra/plugin/extension/src/main/java/capaexplorer/CapaExplorerExporter.java @@ -0,0 +1,77 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +package capaexplorer; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.*; +import ghidra.app.util.exporter.Exporter; +import ghidra.app.util.exporter.ExporterException; +import ghidra.framework.model.DomainObject; +import ghidra.program.model.address.AddressSetView; +import ghidra.util.task.TaskMonitor; + +/** + * Provide class-level documentation that describes what this exporter does. + */ +public class CapaExplorerExporter extends Exporter { + + /** + * Exporter constructor. + */ + public CapaExplorerExporter() { + + // Name the exporter and associate a file extension with it + + super("My Exporter", "exp", null); + } + + @Override + public boolean supportsAddressRestrictedExport() { + + // Return true if addrSet export parameter can be used to restrict export + + return false; + } + + @Override + public boolean export(File file, DomainObject domainObj, AddressSetView addrSet, + TaskMonitor monitor) throws ExporterException, IOException { + + // Perform the export, and return true if it succeeded + + return false; + } + + @Override + public List