Add ghidra plugin source
This commit is contained in:
parent
f65578b295
commit
30ec90e650
145
pom.xml
Normal file
145
pom.xml
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.lauriewired</groupId>
|
||||||
|
<artifactId>GhidraMCP</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<name>GhidraMCP</name>
|
||||||
|
<url>http://maven.apache.org</url>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Ghidra JARs as system-scoped dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ghidra</groupId>
|
||||||
|
<artifactId>Generic</artifactId>
|
||||||
|
<version>11.3.1</version>
|
||||||
|
<scope>system</scope>
|
||||||
|
<systemPath>${project.basedir}/lib/Generic.jar</systemPath>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ghidra</groupId>
|
||||||
|
<artifactId>SoftwareModeling</artifactId>
|
||||||
|
<version>11.3.1</version>
|
||||||
|
<scope>system</scope>
|
||||||
|
<systemPath>${project.basedir}/lib/SoftwareModeling.jar</systemPath>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ghidra</groupId>
|
||||||
|
<artifactId>Project</artifactId>
|
||||||
|
<version>11.3.1</version>
|
||||||
|
<scope>system</scope>
|
||||||
|
<systemPath>${project.basedir}/lib/Project.jar</systemPath>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ghidra</groupId>
|
||||||
|
<artifactId>Docking</artifactId>
|
||||||
|
<version>11.3.1</version>
|
||||||
|
<scope>system</scope>
|
||||||
|
<systemPath>${project.basedir}/lib/Docking.jar</systemPath>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ghidra</groupId>
|
||||||
|
<artifactId>Decompiler</artifactId>
|
||||||
|
<version>11.3.1</version>
|
||||||
|
<scope>system</scope>
|
||||||
|
<systemPath>${project.basedir}/lib/Decompiler.jar</systemPath>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ghidra</groupId>
|
||||||
|
<artifactId>Utility</artifactId>
|
||||||
|
<version>11.3.1</version>
|
||||||
|
<scope>system</scope>
|
||||||
|
<systemPath>${project.basedir}/lib/Utility.jar</systemPath>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ghidra</groupId>
|
||||||
|
<artifactId>Base</artifactId>
|
||||||
|
<version>11.3.1</version>
|
||||||
|
<scope>system</scope>
|
||||||
|
<systemPath>${project.basedir}/lib/Base.jar</systemPath>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JUnit (test only) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!-- Use custom MANIFEST.MF -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.2.2</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
|
||||||
|
</archive>
|
||||||
|
<!-- Set a fixed name for the JAR without version -->
|
||||||
|
<finalName>GhidraMCP</finalName>
|
||||||
|
<!-- Exclude the App class -->
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/App.class</exclude>
|
||||||
|
</excludes>
|
||||||
|
<!-- Make sure output directory is target for consistency -->
|
||||||
|
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- The Assembly Plugin for creating the Ghidra extension ZIP -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>3.3.0</version>
|
||||||
|
<configuration>
|
||||||
|
<!-- Using the custom assembly descriptor -->
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>src/assembly/ghidra-extension.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
|
||||||
|
<!-- The name of the final zip -->
|
||||||
|
<finalName>GhidraMCP-${project.version}</finalName>
|
||||||
|
|
||||||
|
<!-- Don't append the assembly ID -->
|
||||||
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
|
</configuration>
|
||||||
|
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>make-assembly</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Copy dependencies to target/lib for the assembly -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<version>3.1.2</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-dependencies</id>
|
||||||
|
<phase>prepare-package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||||
|
<includeScope>runtime</includeScope>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
40
src/assembly/ghidra-extension.xml
Normal file
40
src/assembly/ghidra-extension.xml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<assembly
|
||||||
|
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3
|
||||||
|
http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||||
|
|
||||||
|
<!-- Just a name for reference -->
|
||||||
|
<id>ghidra-extension</id>
|
||||||
|
|
||||||
|
<!-- We want a .zip file -->
|
||||||
|
<formats>
|
||||||
|
<format>zip</format>
|
||||||
|
</formats>
|
||||||
|
|
||||||
|
<!-- Don't put everything in an extra top-level directory named after the assembly ID -->
|
||||||
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
|
|
||||||
|
<fileSets>
|
||||||
|
<!-- 1) Copy extension.properties and Module.manifest into the top level
|
||||||
|
of a folder named GhidraMCP/ (the actual extension folder). -->
|
||||||
|
<fileSet>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<includes>
|
||||||
|
<include>extension.properties</include>
|
||||||
|
<include>Module.manifest</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>GhidraMCP</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
|
|
||||||
|
<!-- 2) Copy your built plugin JAR into GhidraMCP/lib -->
|
||||||
|
<fileSet>
|
||||||
|
<directory>${project.build.directory}</directory>
|
||||||
|
<includes>
|
||||||
|
<!-- Use the finalized JAR name from the maven-jar-plugin -->
|
||||||
|
<include>GhidraMCP.jar</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>GhidraMCP/lib</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
|
</fileSets>
|
||||||
|
</assembly>
|
||||||
13
src/main/java/com/lauriewired/App.java
Normal file
13
src/main/java/com/lauriewired/App.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package com.lauriewired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hello world!
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class App
|
||||||
|
{
|
||||||
|
public static void main( String[] args )
|
||||||
|
{
|
||||||
|
System.out.println( "Hello World!" );
|
||||||
|
}
|
||||||
|
}
|
||||||
288
src/main/java/com/lauriewired/GhidraMCPPlugin.java
Normal file
288
src/main/java/com/lauriewired/GhidraMCPPlugin.java
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
package com.lauriewired;
|
||||||
|
|
||||||
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.model.listing.*;
|
||||||
|
import ghidra.program.model.symbol.*;
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
|
import ghidra.program.model.mem.*;
|
||||||
|
import ghidra.app.decompiler.DecompInterface;
|
||||||
|
import ghidra.app.decompiler.DecompileResults;
|
||||||
|
import ghidra.util.task.ConsoleTaskMonitor;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
import ghidra.app.DeveloperPluginPackage;
|
||||||
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
|
import ghidra.framework.plugintool.PluginInfo;
|
||||||
|
import ghidra.app.services.ProgramManager;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
@PluginInfo(
|
||||||
|
status = PluginStatus.RELEASED,
|
||||||
|
packageName = DeveloperPluginPackage.NAME,
|
||||||
|
category = PluginCategoryNames.ANALYSIS,
|
||||||
|
shortDescription = "HTTP server plugin",
|
||||||
|
description = "Starts an embedded HTTP server to expose program data."
|
||||||
|
)
|
||||||
|
public class GhidraMCPPlugin extends Plugin {
|
||||||
|
|
||||||
|
private HttpServer server;
|
||||||
|
|
||||||
|
public GhidraMCPPlugin(PluginTool tool) {
|
||||||
|
super(tool);
|
||||||
|
Msg.info(this, "✅ GhidraMCPPlugin loaded!");
|
||||||
|
|
||||||
|
try {
|
||||||
|
startServer();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Msg.error(this, "Failed to start HTTP server", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startServer() throws IOException {
|
||||||
|
int port = 8080;
|
||||||
|
server = HttpServer.create(new InetSocketAddress(port), 0);
|
||||||
|
|
||||||
|
server.createContext("/methods", exchange -> sendResponse(exchange, getAllFunctionNames()));
|
||||||
|
server.createContext("/classes", exchange -> sendResponse(exchange, getAllClassNames()));
|
||||||
|
server.createContext("/decompile", exchange -> {
|
||||||
|
String name = new String(exchange.getRequestBody().readAllBytes());
|
||||||
|
sendResponse(exchange, decompileFunctionByName(name));
|
||||||
|
});
|
||||||
|
server.createContext("/renameFunction", exchange -> {
|
||||||
|
Map<String, String> params = parsePostParams(exchange);
|
||||||
|
String response = renameFunction(params.get("oldName"), params.get("newName"))
|
||||||
|
? "Renamed successfully" : "Rename failed";
|
||||||
|
sendResponse(exchange, response);
|
||||||
|
});
|
||||||
|
server.createContext("/renameData", exchange -> {
|
||||||
|
Map<String, String> params = parsePostParams(exchange);
|
||||||
|
renameDataAtAddress(params.get("address"), params.get("newName"));
|
||||||
|
sendResponse(exchange, "Rename data attempted");
|
||||||
|
});
|
||||||
|
server.createContext("/segments", exchange -> sendResponse(exchange, listSegments()));
|
||||||
|
server.createContext("/imports", exchange -> sendResponse(exchange, listImports()));
|
||||||
|
server.createContext("/exports", exchange -> sendResponse(exchange, listExports()));
|
||||||
|
server.createContext("/namespaces", exchange -> sendResponse(exchange, listNamespaces()));
|
||||||
|
server.createContext("/data", exchange -> sendResponse(exchange, listDefinedData()));
|
||||||
|
|
||||||
|
server.setExecutor(null);
|
||||||
|
new Thread(() -> {
|
||||||
|
server.start();
|
||||||
|
Msg.info(this, "🌐 GhidraMCP HTTP server started on port " + port);
|
||||||
|
}, "GhidraMCP-HTTP-Server").start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendResponse(HttpExchange exchange, String response) throws IOException {
|
||||||
|
byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
|
||||||
|
exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
exchange.sendResponseHeaders(200, bytes.length);
|
||||||
|
try (OutputStream os = exchange.getResponseBody()) {
|
||||||
|
os.write(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> parsePostParams(HttpExchange exchange) throws IOException {
|
||||||
|
String body = new String(exchange.getRequestBody().readAllBytes());
|
||||||
|
Map<String, String> params = new HashMap<>();
|
||||||
|
for (String pair : body.split("&")) {
|
||||||
|
String[] kv = pair.split("=");
|
||||||
|
if (kv.length == 2) params.put(kv[0], kv[1]);
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAllFunctionNames() {
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
if (program == null) return "No program loaded";
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Function f : program.getFunctionManager().getFunctions(true)) {
|
||||||
|
sb.append(f.getName()).append("\n");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAllClassNames() {
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
if (program == null) return "No program loaded";
|
||||||
|
Set<String> classNames = new HashSet<>();
|
||||||
|
for (Symbol symbol : program.getSymbolTable().getAllSymbols(true)) {
|
||||||
|
Namespace ns = symbol.getParentNamespace();
|
||||||
|
if (ns != null && !ns.isGlobal()) {
|
||||||
|
classNames.add(ns.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String.join("\n", classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String decompileFunctionByName(String name) {
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
if (program == null) return "No program loaded";
|
||||||
|
DecompInterface decomp = new DecompInterface();
|
||||||
|
decomp.openProgram(program);
|
||||||
|
for (Function func : program.getFunctionManager().getFunctions(true)) {
|
||||||
|
if (func.getName().equals(name)) {
|
||||||
|
DecompileResults result = decomp.decompileFunction(func, 30, new ConsoleTaskMonitor());
|
||||||
|
if (result != null && result.decompileCompleted()) {
|
||||||
|
return result.getDecompiledFunction().getC();
|
||||||
|
} else return "Decompilation failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Function not found";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean renameFunction(String oldName, String newName) {
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
if (program == null) return false;
|
||||||
|
|
||||||
|
// Use AtomicBoolean to capture the result from inside the Task
|
||||||
|
AtomicBoolean successFlag = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Run in Swing EDT to ensure proper transaction handling
|
||||||
|
SwingUtilities.invokeAndWait(() -> {
|
||||||
|
int tx = program.startTransaction("Rename function via HTTP");
|
||||||
|
try {
|
||||||
|
for (Function func : program.getFunctionManager().getFunctions(true)) {
|
||||||
|
if (func.getName().equals(oldName)) {
|
||||||
|
func.setName(newName, SourceType.USER_DEFINED);
|
||||||
|
successFlag.set(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
Msg.error(this, "Error renaming function", e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
program.endTransaction(tx, successFlag.get());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (InterruptedException | InvocationTargetException e) {
|
||||||
|
Msg.error(this, "Failed to execute rename on Swing thread", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return successFlag.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renameDataAtAddress(String addressStr, String newName) {
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
if (program == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Run in Swing EDT to ensure proper transaction handling
|
||||||
|
SwingUtilities.invokeAndWait(() -> {
|
||||||
|
int tx = program.startTransaction("Rename data");
|
||||||
|
try {
|
||||||
|
Address addr = program.getAddressFactory().getAddress(addressStr);
|
||||||
|
Listing listing = program.getListing();
|
||||||
|
Data data = listing.getDefinedDataAt(addr);
|
||||||
|
if (data != null) {
|
||||||
|
SymbolTable symTable = program.getSymbolTable();
|
||||||
|
Symbol symbol = symTable.getPrimarySymbol(addr);
|
||||||
|
if (symbol != null) {
|
||||||
|
symbol.setName(newName, SourceType.USER_DEFINED);
|
||||||
|
} else {
|
||||||
|
symTable.createLabel(addr, newName, SourceType.USER_DEFINED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
Msg.error(this, "Rename data error", e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
program.endTransaction(tx, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (InterruptedException | InvocationTargetException e) {
|
||||||
|
Msg.error(this, "Failed to execute rename data on Swing thread", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String listSegments() {
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (MemoryBlock block : program.getMemory().getBlocks()) {
|
||||||
|
sb.append(String.format("%s: %s - %s\n", block.getName(), block.getStart(), block.getEnd()));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String listImports() {
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Symbol symbol : program.getSymbolTable().getExternalSymbols()) {
|
||||||
|
sb.append(symbol.getName()).append(" -> ").append(symbol.getAddress()).append("\n");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String listExports() {
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Function func : program.getFunctionManager().getFunctions(true)) {
|
||||||
|
if (func.isExternal()) {
|
||||||
|
sb.append(func.getName()).append(" -> ").append(func.getEntryPoint()).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String listNamespaces() {
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
Set<String> namespaces = new HashSet<>();
|
||||||
|
for (Symbol symbol : program.getSymbolTable().getAllSymbols(true)) {
|
||||||
|
Namespace ns = symbol.getParentNamespace();
|
||||||
|
if (ns != null && !(ns instanceof GlobalNamespace)) {
|
||||||
|
namespaces.add(ns.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String.join("\n", namespaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String listDefinedData() {
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (MemoryBlock block : program.getMemory().getBlocks()) {
|
||||||
|
DataIterator it = program.getListing().getDefinedData(block.getStart(), true);
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Data data = it.next();
|
||||||
|
if (block.contains(data.getAddress())) {
|
||||||
|
sb.append(String.format("%s: %s = %s\n",
|
||||||
|
data.getAddress(),
|
||||||
|
data.getLabel() != null ? data.getLabel() : "(unnamed)",
|
||||||
|
data.getDefaultValueRepresentation()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Program getCurrentProgram() {
|
||||||
|
ProgramManager programManager = tool.getService(ProgramManager.class);
|
||||||
|
return programManager != null ? programManager.getCurrentProgram() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
if (server != null) {
|
||||||
|
server.stop(0);
|
||||||
|
Msg.info(this, "🛑 HTTP server stopped.");
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/main/resources/META-INF/MANIFEST.MF
Normal file
6
src/main/resources/META-INF/MANIFEST.MF
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Manifest-Version: 1.0
|
||||||
|
Plugin-Class: com.lauriewired.GhidraMCP
|
||||||
|
Plugin-Name: GhidraMCP
|
||||||
|
Plugin-Version: 1.0
|
||||||
|
Plugin-Author: LaurieWired
|
||||||
|
Plugin-Description: A custom plugin by LaurieWired
|
||||||
2
src/main/resources/Module.manifest
Normal file
2
src/main/resources/Module.manifest
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
GHIDRA_MODULE_NAME=GhidraMCP
|
||||||
|
GHIDRA_MODULE_DESC=An HTTP server plugin for Ghidra
|
||||||
6
src/main/resources/extension.properties
Normal file
6
src/main/resources/extension.properties
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
name=GhidraMCP
|
||||||
|
description=A plugin that runs an embedded HTTP server to expose program data.
|
||||||
|
author=LaurieWired
|
||||||
|
createdOn=2025-03-22
|
||||||
|
version=11.2
|
||||||
|
ghidraVersion=11.2
|
||||||
38
src/test/java/com/lauriewired/AppTest.java
Normal file
38
src/test/java/com/lauriewired/AppTest.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package com.lauriewired;
|
||||||
|
|
||||||
|
import junit.framework.Test;
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
import junit.framework.TestSuite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for simple App.
|
||||||
|
*/
|
||||||
|
public class AppTest
|
||||||
|
extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create the test case
|
||||||
|
*
|
||||||
|
* @param testName name of the test case
|
||||||
|
*/
|
||||||
|
public AppTest( String testName )
|
||||||
|
{
|
||||||
|
super( testName );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the suite of tests being tested
|
||||||
|
*/
|
||||||
|
public static Test suite()
|
||||||
|
{
|
||||||
|
return new TestSuite( AppTest.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rigourous Test :-)
|
||||||
|
*/
|
||||||
|
public void testApp()
|
||||||
|
{
|
||||||
|
assertTrue( true );
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user