Skip to content
This repository was archived by the owner on Nov 16, 2025. It is now read-only.

Commit a6c67c1

Browse files
committed
Add new data/function handlers and update plugin info
Introduces new handlers: DisassembleBytes, SearchBytePatterns, ClearInstructionFlowOverride, ForceDecompile, SetFunctionNoReturn, and GetVersion. Removes the old SearchBytes handler. Updates the plugin description in GhidraMCPPlugin.java to reflect the new endpoint structure.
1 parent 0e9cd73 commit a6c67c1

8 files changed

Lines changed: 725 additions & 85 deletions

File tree

src/main/java/com/lauriewired/GhidraMCPPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
category = PluginCategoryNames.ANALYSIS,
3030
shortDescription = "GhidraMCP - HTTP server plugin",
3131
description = "GhidraMCP - Starts an embedded HTTP server to expose program data via REST API and MCP bridge. " +
32-
"Provides 100+ endpoints for reverse engineering automation. Port configurable via Tool Options. "
32+
"Provides endpoints for reverse engineering automation. Port configurable via Tool Options. "
3333
)
3434
public class GhidraMCPPlugin extends Plugin {
3535
/** The embedded HTTP server instance that handles all API requests */
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package com.lauriewired.handlers.data;
2+
3+
import com.lauriewired.handlers.Handler;
4+
import com.sun.net.httpserver.HttpExchange;
5+
import ghidra.framework.plugintool.PluginTool;
6+
import ghidra.program.model.address.Address;
7+
import ghidra.program.model.address.AddressSet;
8+
import ghidra.program.model.listing.CodeUnit;
9+
import ghidra.program.model.listing.Data;
10+
import ghidra.program.model.listing.Instruction;
11+
import ghidra.program.model.listing.Listing;
12+
import ghidra.program.model.listing.Program;
13+
import ghidra.util.Msg;
14+
15+
import java.io.IOException;
16+
import java.util.concurrent.atomic.AtomicReference;
17+
import java.util.Map;
18+
import javax.swing.SwingUtilities;
19+
20+
import static com.lauriewired.util.ParseUtils.*;
21+
import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
22+
23+
public final class DisassembleBytes extends Handler {
24+
/**
25+
* Constructor for the DisassembleBytes handler.
26+
*
27+
* @param tool The PluginTool instance to use.
28+
*/
29+
public DisassembleBytes(PluginTool tool) {
30+
super(tool, "/disassemble_bytes");
31+
}
32+
33+
@Override
34+
public void handle(HttpExchange exchange) throws IOException {
35+
Map<String, Object> params = parseJsonParams(exchange);
36+
String startAddress = (String) params.get("start_address");
37+
String endAddress = (String) params.get("end_address");
38+
Integer length = params.get("length") != null ? ((Number) params.get("length")).intValue() : null;
39+
boolean restrictToExecuteMemory = params.get("restrict_to_execute_memory") != null ?
40+
(Boolean) params.get("restrict_to_execute_memory") : true;
41+
42+
String result = disassembleBytes(startAddress, endAddress, length, restrictToExecuteMemory);
43+
sendResponse(exchange, result);
44+
}
45+
46+
private String disassembleBytes(String startAddress, String endAddress, Integer length,
47+
boolean restrictToExecuteMemory) {
48+
Program program = getCurrentProgram(tool);
49+
if (program == null) {
50+
return "{\"error\": \"No program loaded\"}";
51+
}
52+
53+
if (startAddress == null || startAddress.isEmpty()) {
54+
return "{\"error\": \"start_address parameter required\"}";
55+
}
56+
57+
final StringBuilder result = new StringBuilder();
58+
final AtomicReference<String> errorMsg = new AtomicReference<>();
59+
60+
try {
61+
Msg.debug(this, "disassembleBytes: Starting disassembly at " + startAddress +
62+
(length != null ? " with length " + length : "") +
63+
(endAddress != null ? " to " + endAddress : ""));
64+
65+
SwingUtilities.invokeAndWait(() -> {
66+
int tx = program.startTransaction("Disassemble Bytes");
67+
boolean success = false;
68+
69+
try {
70+
// Parse start address
71+
Address start = program.getAddressFactory().getAddress(startAddress);
72+
if (start == null) {
73+
errorMsg.set("Invalid start address: " + startAddress);
74+
return;
75+
}
76+
77+
// Determine end address
78+
Address end;
79+
if (endAddress != null && !endAddress.isEmpty()) {
80+
// Use explicit end address (exclusive)
81+
end = program.getAddressFactory().getAddress(endAddress);
82+
if (end == null) {
83+
errorMsg.set("Invalid end address: " + endAddress);
84+
return;
85+
}
86+
// Make end address inclusive for AddressSet
87+
try {
88+
end = end.subtract(1);
89+
} catch (Exception e) {
90+
errorMsg.set("End address calculation failed: " + e.getMessage());
91+
return;
92+
}
93+
} else if (length != null && length > 0) {
94+
// Use length to calculate end address
95+
try {
96+
end = start.add(length - 1);
97+
} catch (Exception e) {
98+
errorMsg.set("End address calculation from length failed: " + e.getMessage());
99+
return;
100+
}
101+
} else {
102+
// Auto-detect length (scan until we hit existing code/data)
103+
Listing listing = program.getListing();
104+
Address current = start;
105+
int maxBytes = 100; // Safety limit
106+
int count = 0;
107+
108+
while (count < maxBytes) {
109+
CodeUnit cu = listing.getCodeUnitAt(current);
110+
111+
// Stop if we hit an existing instruction
112+
if (cu instanceof Instruction) {
113+
break;
114+
}
115+
116+
// Stop if we hit defined data
117+
if (cu instanceof Data && ((Data) cu).isDefined()) {
118+
break;
119+
}
120+
121+
count++;
122+
try {
123+
current = current.add(1);
124+
} catch (Exception e) {
125+
break;
126+
}
127+
}
128+
129+
if (count == 0) {
130+
errorMsg.set("No undefined bytes found at address (already disassembled or defined data)");
131+
return;
132+
}
133+
134+
// end is now one past the last undefined byte
135+
try {
136+
end = current.subtract(1);
137+
} catch (Exception e) {
138+
end = current;
139+
}
140+
}
141+
142+
// Create address set
143+
AddressSet addressSet = new AddressSet(start, end);
144+
long numBytes = addressSet.getNumAddresses();
145+
146+
// Execute disassembly
147+
ghidra.app.cmd.disassemble.DisassembleCommand cmd =
148+
new ghidra.app.cmd.disassemble.DisassembleCommand(addressSet, null, restrictToExecuteMemory);
149+
150+
// Prevent auto-analysis cascade
151+
cmd.setSeedContext(null);
152+
cmd.setInitialContext(null);
153+
154+
if (cmd.applyTo(program, ghidra.util.task.TaskMonitor.DUMMY)) {
155+
// Success - build result
156+
Msg.debug(this, "disassembleBytes: Successfully disassembled " + numBytes + " byte(s) from " + start + " to " + end);
157+
result.append("{");
158+
result.append("\"success\": true, ");
159+
result.append("\"start_address\": \"").append(start).append("\", ");
160+
result.append("\"end_address\": \"").append(end).append("\", ");
161+
result.append("\"bytes_disassembled\": ").append(numBytes).append(", ");
162+
result.append("\"message\": \"Successfully disassembled ").append(numBytes).append(" byte(s)\"");
163+
result.append("}");
164+
success = true;
165+
} else {
166+
errorMsg.set("Disassembly failed: " + cmd.getStatusMsg());
167+
Msg.error(this, "disassembleBytes: Disassembly command failed - " + cmd.getStatusMsg());
168+
}
169+
170+
} catch (Exception e) {
171+
errorMsg.set("Exception during disassembly: " + e.getMessage());
172+
Msg.error(this, "disassembleBytes: Exception during disassembly", e);
173+
} finally {
174+
program.endTransaction(tx, success);
175+
}
176+
});
177+
178+
Msg.debug(this, "disassembleBytes: invokeAndWait completed");
179+
180+
if (errorMsg.get() != null) {
181+
Msg.error(this, "disassembleBytes: Returning error response - " + errorMsg.get());
182+
return "{\"error\": \"" + errorMsg.get().replace("\"", "\\\"") + "\"}";
183+
}
184+
} catch (Exception e) {
185+
Msg.error(this, "disassembleBytes: Exception in outer try block", e);
186+
return "{\"error\": \"" + e.getMessage().replace("\"", "\\\"") + "\"}";
187+
}
188+
189+
String response = result.toString();
190+
Msg.debug(this, "disassembleBytes: Returning success response, length=" + response.length());
191+
return response;
192+
}
193+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package com.lauriewired.handlers.data;
2+
3+
import com.lauriewired.handlers.Handler;
4+
import com.sun.net.httpserver.HttpExchange;
5+
import ghidra.framework.plugintool.PluginTool;
6+
import ghidra.program.model.address.Address;
7+
import ghidra.program.model.mem.Memory;
8+
import ghidra.program.model.mem.MemoryBlock;
9+
import ghidra.program.model.listing.Program;
10+
11+
import java.io.IOException;
12+
import java.util.Map;
13+
14+
import static com.lauriewired.util.ParseUtils.*;
15+
import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
16+
17+
public final class SearchBytePatterns extends Handler {
18+
/**
19+
* Constructor for the SearchBytePatterns handler.
20+
*
21+
* @param tool The PluginTool instance to use.
22+
*/
23+
public SearchBytePatterns(PluginTool tool) {
24+
super(tool, "/search_byte_patterns");
25+
}
26+
27+
@Override
28+
public void handle(HttpExchange exchange) throws IOException {
29+
Map<String, String> qparams = parseQueryParams(exchange);
30+
String pattern = qparams.get("pattern");
31+
String mask = qparams.get("mask");
32+
33+
String result = searchBytePatterns(pattern, mask);
34+
sendResponse(exchange, result);
35+
}
36+
37+
/**
38+
* Searches the current program's memory for the specified byte pattern.
39+
*
40+
* @param pattern The byte pattern to search for (e.g., "E8 ?? ?? ?? ??").
41+
* @param mask The mask indicating which bytes to check (not used in this implementation).
42+
* @return A JSON string containing the search results.
43+
*/
44+
private String searchBytePatterns(String pattern, String mask) {
45+
Program program = getCurrentProgram(tool);
46+
if (program == null) {
47+
return "Error: No program loaded";
48+
}
49+
50+
if (pattern == null || pattern.trim().isEmpty()) {
51+
return "Error: Pattern is required";
52+
}
53+
54+
try {
55+
final StringBuilder result = new StringBuilder();
56+
result.append("[\n");
57+
58+
// Parse hex pattern (e.g., "E8 ?? ?? ?? ??" or "E8????????")
59+
String cleanPattern = pattern.trim().toUpperCase().replaceAll("\\s+", "");
60+
61+
// Convert pattern to byte array and mask
62+
int patternLen = cleanPattern.replace("?", "").length() / 2 + cleanPattern.replace("?", "").length() % 2;
63+
if (cleanPattern.contains("?")) {
64+
patternLen = cleanPattern.length() / 2;
65+
}
66+
67+
byte[] patternBytes = new byte[patternLen];
68+
byte[] maskBytes = new byte[patternLen];
69+
70+
int byteIndex = 0;
71+
for (int i = 0; i < cleanPattern.length(); i += 2) {
72+
if (cleanPattern.charAt(i) == '?' || (i + 1 < cleanPattern.length() && cleanPattern.charAt(i + 1) == '?')) {
73+
patternBytes[byteIndex] = 0;
74+
maskBytes[byteIndex] = 0; // Don't check this byte
75+
} else {
76+
String hexByte = cleanPattern.substring(i, Math.min(i + 2, cleanPattern.length()));
77+
patternBytes[byteIndex] = (byte) Integer.parseInt(hexByte, 16);
78+
maskBytes[byteIndex] = (byte) 0xFF; // Check this byte
79+
}
80+
byteIndex++;
81+
}
82+
83+
// Search memory for pattern
84+
Memory memory = program.getMemory();
85+
int matchCount = 0;
86+
final int MAX_MATCHES = 1000; // Limit results
87+
88+
for (MemoryBlock block : memory.getBlocks()) {
89+
if (!block.isInitialized()) continue;
90+
91+
Address blockStart = block.getStart();
92+
long blockSize = block.getSize();
93+
94+
// Read block data
95+
byte[] blockData = new byte[(int) Math.min(blockSize, Integer.MAX_VALUE)];
96+
try {
97+
block.getBytes(blockStart, blockData);
98+
} catch (Exception e) {
99+
continue; // Skip blocks we can't read
100+
}
101+
102+
// Search for pattern in block
103+
for (int i = 0; i <= blockData.length - patternBytes.length; i++) {
104+
boolean match = true;
105+
for (int j = 0; j < patternBytes.length; j++) {
106+
if (maskBytes[j] != 0 && blockData[i + j] != patternBytes[j]) {
107+
match = false;
108+
break;
109+
}
110+
}
111+
112+
if (match) {
113+
if (matchCount > 0) result.append(",\n");
114+
Address matchAddr = blockStart.add(i);
115+
result.append(" {\"address\": \"").append(matchAddr).append("\"}");
116+
matchCount++;
117+
118+
if (matchCount >= MAX_MATCHES) {
119+
result.append(",\n {\"note\": \"Limited to ").append(MAX_MATCHES).append(" matches\"}");
120+
break;
121+
}
122+
}
123+
}
124+
125+
if (matchCount >= MAX_MATCHES) break;
126+
}
127+
128+
if (matchCount == 0) {
129+
result.append(" {\"note\": \"No matches found\"}");
130+
}
131+
132+
result.append("\n]");
133+
return result.toString();
134+
} catch (Exception e) {
135+
return "Error: " + e.getMessage();
136+
}
137+
}
138+
}

0 commit comments

Comments
 (0)