WIP update APIs

This commit is contained in:
Teal Bauer 2025-04-09 23:36:05 +02:00
parent 6b2e572bd4
commit 57584581bc
4 changed files with 2089 additions and 526 deletions

399
JAVA_PLUGIN_API.md Normal file
View File

@ -0,0 +1,399 @@
# GhydraMCP Java Plugin REST API Documentation
## Base URL
`http://localhost:8192` (default port, may vary)
## Endpoints
### 1. Instance Information
- `GET /info`
- `GET /` (root path)
Returns basic instance information including:
- Port number
- Whether this is the base instance
- Current project name (if available)
- Current program name (if available)
Example Response:
```json
{
"port": 8192,
"isBaseInstance": true,
"project": "MyProject",
"file": "program.exe"
}
```
### 2. Function Operations
#### List Functions
- `GET /functions`
Parameters:
- `offset` (optional): Pagination offset (default: 0)
- `limit` (optional): Maximum results (default: 100)
- `query` (optional): Search term to filter functions
Example Response:
```json
{
"success": true,
"result": [
{
"name": "init_peripherals",
"address": "08000200"
},
{
"name": "uart_rx_valid_command",
"address": "0800029c"
}
],
"timestamp": 1743778219516,
"port": 8192,
"instanceType": "base"
}
```
#### Get Function Details
- `GET /functions/{name}`
Returns decompiled code for the specified function.
Example Response:
```json
{
"success": true,
"result": "int main() {\n // Decompiled code here\n}",
"timestamp": 1743778219516
}
```
#### Rename Function
- `POST /functions/{name}`
Body Parameters:
- `newName`: New name for the function
Example Response:
```json
{
"success": true,
"result": "Renamed successfully",
"timestamp": 1743778219516
}
```
#### Function Variables
- `GET /functions/{name}/variables`
Lists all variables (parameters and locals) in a function.
Example Response:
```json
{
"success": true,
"result": {
"function": "myFunction",
"parameters": [
{
"name": "param1",
"type": "int",
"kind": "parameter"
},
{
"name": "param2",
"type": "char*",
"kind": "parameter"
}
],
"localVariables": [
{
"name": "var1",
"type": "int",
"address": "08000234"
},
{
"name": "var2",
"type": "float",
"address": "08000238"
}
]
}
}
```
#### Rename/Retype Variable
- `POST /functions/{name}/variables/{varName}`
Body Parameters (one of):
- `newName`: New name for variable
- `dataType`: New data type for variable
Example Response:
```json
{
"success": true,
"result": "Variable renamed",
"timestamp": 1743778219516
}
```
### 3. Class Operations
- `GET /classes`
Parameters:
- `offset` (optional): Pagination offset (default: 0)
- `limit` (optional): Maximum results (default: 100)
Example Response:
```json
{
"success": true,
"result": [
"MyClass1",
"MyClass2"
],
"timestamp": 1743778219516
}
```
### 4. Memory Segments
- `GET /segments`
Parameters:
- `offset` (optional): Pagination offset (default: 0)
- `limit` (optional): Maximum results (default: 100)
Example Response:
```json
{
"success": true,
"result": [
{
"name": ".text",
"start": "08000000",
"end": "08001000"
},
{
"name": ".data",
"start": "08001000",
"end": "08002000"
}
]
}
```
### 5. Symbol Operations
#### Imports
- `GET /symbols/imports`
Parameters:
- `offset` (optional): Pagination offset (default: 0)
- `limit` (optional): Maximum results (default: 100)
Example Response:
```json
{
"success": true,
"result": [
{
"name": "printf",
"address": "EXTERNAL:00000000"
},
{
"name": "malloc",
"address": "EXTERNAL:00000004"
}
]
}
```
#### Exports
- `GET /symbols/exports`
Parameters:
- `offset` (optional): Pagination offset (default: 0)
- `limit` (optional): Maximum results (default: 100)
Example Response:
```json
{
"success": true,
"result": [
{
"name": "main",
"address": "08000200"
},
{
"name": "_start",
"address": "08000100"
}
]
}
```
### 6. Namespace Operations
- `GET /namespaces`
Parameters:
- `offset` (optional): Pagination offset (default: 0)
- `limit` (optional): Maximum results (default: 100)
Example Response:
```json
{
"success": true,
"result": [
"std",
"MyNamespace"
]
}
```
### 7. Data Operations
#### List Defined Data
- `GET /data`
Parameters:
- `offset` (optional): Pagination offset (default: 0)
- `limit` (optional): Maximum results (default: 100)
Example Response:
```json
{
"success": true,
"result": [
{
"address": "08001000",
"name": "myVar",
"value": "42"
},
{
"address": "08001004",
"name": "myString",
"value": "\"Hello\""
}
]
}
```
#### Rename Data
- `POST /data`
Body Parameters:
- `address`: Address of data to rename (hex string)
- `newName`: New name for data
Example Response:
```json
{
"success": true,
"result": {
"name": "main",
"decompiled": "int main() {\n // Decompiled code here\n}",
"metadata": {
"size": 256,
"entryPoint": "08000200"
}
},
"timestamp": 1743778219516
}
```
### 8. Variable Operations
#### Global Variables
- `GET /variables`
Parameters:
- `offset` (optional): Pagination offset (default: 0)
- `limit` (optional): Maximum results (default: 100)
- `search` (optional): Search term to filter variables
Example Response:
```json
{
"success": true,
"result": [
{
"name": "globalVar1",
"address": "08001000"
},
{
"name": "globalVar2",
"address": "08001004"
}
]
}
```
### 9. Instance Management
#### List Active Instances
- `GET /instances`
Example Response:
```json
{
"success": true,
"result": [
{
"port": 8192,
"type": "base"
},
{
"port": 8193,
"type": "secondary"
}
]
}
```
#### Register Instance
- `POST /registerInstance`
Body Parameters:
- `port`: Port number to register
Example Response:
```json
{
"success": true,
"result": "Instance registered on port 8193",
"timestamp": 1743778219516
}
```
#### Unregister Instance
- `POST /unregisterInstance`
Body Parameters:
- `port`: Port number to unregister
Example Response:
```json
{
"success": true,
"result": "Unregistered instance on port 8193",
"timestamp": 1743778219516
}
```
## Error Responses
All endpoints return JSON with success=false on errors:
```json
{
"success": false,
"error": "Error message",
"status": 500
}
```
Common status codes:
- 400: Bad request (invalid parameters)
- 404: Not found (invalid endpoint or resource)
- 405: Method not allowed
- 500: Internal server error

147
MCP_BRIDGE_API.md Normal file
View File

@ -0,0 +1,147 @@
# GhydraMCP Bridge API Documentation
## Overview
This document describes the MCP tools and resources exposed by the GhydraMCP bridge that connects to Ghidra's HTTP API. The bridge provides a higher-level interface optimized for AI agent usage.
## Core Concepts
- Each Ghidra instance runs its own HTTP server (default port 8192)
- The bridge discovers and manages multiple Ghidra instances
- Tools are organized by resource type (programs, functions, data, etc.)
- Consistent response format with success/error indicators
## Instance Management Tools
### `list_instances`
List all active Ghidra instances with their ports and project info.
### `discover_instances`
Scan for available Ghidra instances by port range.
### `register_instance`
Manually register a Ghidra instance by port/URL.
## Program Analysis Tools
### `list_functions`
List functions in current program with pagination.
### `get_function`
Get details and decompilation for a function by name.
### `get_function_by_address`
Get function details by memory address.
### `decompile_function_by_address`
Decompile function at specific address.
### `list_segments`
List memory segments/sections in program.
### `list_data_items`
List defined data items in program.
### `read_memory`
Read bytes from memory at address. Parameters:
- `address`: Hex address
- `length`: Bytes to read
- `format`: "hex", "base64" or "string" output format
### `write_memory`
Write bytes to memory at address (use with caution). Parameters:
- `address`: Hex address
- `bytes`: Data to write
- `format`: "hex", "base64" or "string" input format
### `list_variables`
List global variables with search/filter.
## Modification Tools
### `update_function`
Rename a function.
### `update_data`
Rename data at memory address.
### `set_function_prototype`
Change a function's signature.
### `rename_local_variable`
Rename variable within function.
### `set_local_variable_type`
Change variable's data type.
## Response Format
All tools return responses in this format:
```json
{
"id": "request-id",
"instance": "http://host:port",
"success": true/false,
"result": {...}, // Tool-specific data
"error": { // Only on failure
"code": "...",
"message": "..."
},
"_links": { // HATEOAS links
"self": {"href": "/path"},
"related": {"href": "/other"}
}
}
```
## Example Usage
1. Discover available instances:
```python
discover_instances()
```
2. List functions in first instance:
```python
list_functions(port=8192, limit=10)
```
3. Decompile main function:
```python
get_function(port=8192, name="main")
```
4. Rename a function:
```python
update_function(port=8192, name="FUN_1234", new_name="parse_data")
```
## Error Handling
- Check `success` field first
- On failure, `error` contains details
- Common error codes:
- `INSTANCE_NOT_FOUND`
- `RESOURCE_NOT_FOUND`
- `INVALID_PARAMETER`
- `TRANSACTION_FAILED`
## Advanced Analysis Tools
### `list_xrefs`
List cross-references between code/data. Parameters:
- `to_addr`: Filter refs to this address
- `from_addr`: Filter refs from this address
- `type`: Filter by ref type ("CALL", "READ", etc)
- Basic pagination via `offset`/`limit`
### `analyze_program`
Run Ghidra analysis with optional settings:
- `analysis_options`: Dict of analysis passes to enable
### `get_callgraph`
Get function call graph visualization data:
- `function`: Starting function (defaults to entry point)
- `max_depth`: Maximum call depth (default: 3)
### `get_dataflow`
Perform data flow analysis from address:
- `address`: Starting point in hex
- `direction`: "forward" or "backward"
- `max_steps`: Max analysis steps

View File

@ -317,18 +317,58 @@ def _discover_instances(port_range, host=None, timeout=0.5) -> dict:
}
@mcp.tool()
def list_functions(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
"""List functions in the current program with pagination
def list_functions(port: int = DEFAULT_GHIDRA_PORT,
offset: int = 0,
limit: int = 100,
addr: str = None,
name: str = None,
name_contains: str = None,
name_matches_regex: str = None) -> dict:
"""List functions in the current program with filtering and pagination
Args:
port: Ghidra instance port (default: 8192)
offset: Pagination offset (default: 0)
limit: Maximum items to return (default: 100)
addr: Filter by address (hexadecimal)
name: Exact name match filter (case-sensitive)
name_contains: Substring name filter (case-insensitive)
name_matches_regex: Regex name filter
Returns:
list: Function names and addresses
dict: {
"result": list of function info objects,
"size": total count,
"offset": current offset,
"limit": current limit,
"_links": pagination links
}
"""
return safe_get(port, "functions", {"offset": offset, "limit": limit})
params = {
"offset": offset,
"limit": limit
}
if addr:
params["addr"] = addr
if name:
params["name"] = name
if name_contains:
params["name_contains"] = name_contains
if name_matches_regex:
params["name_matches_regex"] = name_matches_regex
response = safe_get(port, "programs/current/functions", params)
if isinstance(response, dict) and "error" in response:
return response
# Transform to expected format if needed
return {
"result": response.get("result", []),
"size": response.get("size", len(response.get("result", []))),
"offset": offset,
"limit": limit,
"_links": response.get("_links", {})
}
@mcp.tool()
def list_classes(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
@ -393,21 +433,101 @@ def update_data(port: int = DEFAULT_GHIDRA_PORT, address: str = "", new_name: st
return safe_post(port, "data", {"address": address, "newName": new_name})
@mcp.tool()
def list_segments(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
"""List memory segments with pagination
def list_segments(port: int = DEFAULT_GHIDRA_PORT,
offset: int = 0,
limit: int = 100,
name: str = None) -> dict:
"""List memory segments with filtering and pagination
Args:
port: Ghidra instance port (default: 8192)
offset: Pagination offset (default: 0)
limit: Maximum items to return (default: 100)
name: Filter by segment name (case-sensitive substring match)
Returns:
list: Segment information strings
dict: {
"result": list of segment objects,
"size": total count,
"offset": current offset,
"limit": current limit,
"_links": pagination links
}
"""
return safe_get(port, "segments", {"offset": offset, "limit": limit})
params = {
"offset": offset,
"limit": limit
}
if name:
params["name"] = name
response = safe_get(port, "programs/current/segments", params)
if isinstance(response, dict) and "error" in response:
return response
return {
"result": response.get("result", []),
"size": response.get("size", len(response.get("result", []))),
"offset": offset,
"limit": limit,
"_links": response.get("_links", {})
}
@mcp.tool()
def list_imports(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
def list_symbols(port: int = DEFAULT_GHIDRA_PORT,
offset: int = 0,
limit: int = 100,
addr: str = None,
name: str = None,
name_contains: str = None,
type: str = None) -> dict:
"""List symbols with filtering and pagination
Args:
port: Ghidra instance port (default: 8192)
offset: Pagination offset (default: 0)
limit: Maximum items to return (default: 100)
addr: Filter by address (hexadecimal)
name: Exact name match filter (case-sensitive)
name_contains: Substring name filter (case-insensitive)
type: Filter by symbol type (e.g. "function", "data", "label")
Returns:
dict: {
"result": list of symbol objects,
"size": total count,
"offset": current offset,
"limit": current limit,
"_links": pagination links
}
"""
params = {
"offset": offset,
"limit": limit
}
if addr:
params["addr"] = addr
if name:
params["name"] = name
if name_contains:
params["name_contains"] = name_contains
if type:
params["type"] = type
response = safe_get(port, "programs/current/symbols", params)
if isinstance(response, dict) and "error" in response:
return response
return {
"result": response.get("result", []),
"size": response.get("size", len(response.get("result", []))),
"offset": offset,
"limit": limit,
"_links": response.get("_links", {})
}
@mcp.tool()
def list_imports(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> dict:
"""List imported symbols with pagination
Args:
@ -416,12 +536,28 @@ def list_imports(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int =
limit: Maximum items to return (default: 100)
Returns:
list: Imported symbol information
dict: {
"result": list of imported symbols,
"size": total count,
"offset": current offset,
"limit": current limit,
"_links": pagination links
}
"""
return safe_get(port, "symbols/imports", {"offset": offset, "limit": limit})
response = safe_get(port, "programs/current/symbols/imports", {"offset": offset, "limit": limit})
if isinstance(response, dict) and "error" in response:
return response
return {
"result": response.get("result", []),
"size": response.get("size", len(response.get("result", []))),
"offset": offset,
"limit": limit,
"_links": response.get("_links", {})
}
@mcp.tool()
def list_exports(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
def list_exports(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> dict:
"""List exported symbols with pagination
Args:
@ -430,9 +566,25 @@ def list_exports(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int =
limit: Maximum items to return (default: 100)
Returns:
list: Exported symbol information
dict: {
"result": list of exported symbols,
"size": total count,
"offset": current offset,
"limit": current limit,
"_links": pagination links
}
"""
return safe_get(port, "symbols/exports", {"offset": offset, "limit": limit})
response = safe_get(port, "programs/current/symbols/exports", {"offset": offset, "limit": limit})
if isinstance(response, dict) and "error" in response:
return response
return {
"result": response.get("result", []),
"size": response.get("size", len(response.get("result", []))),
"offset": offset,
"limit": limit,
"_links": response.get("_links", {})
}
@mcp.tool()
def list_namespaces(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
@ -449,18 +601,57 @@ def list_namespaces(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int
return safe_get(port, "namespaces", {"offset": offset, "limit": limit})
@mcp.tool()
def list_data_items(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
"""List data items with pagination
def list_data_items(port: int = DEFAULT_GHIDRA_PORT,
offset: int = 0,
limit: int = 100,
addr: str = None,
name: str = None,
name_contains: str = None,
type: str = None) -> dict:
"""List defined data items with filtering and pagination
Args:
port: Ghidra instance port (default: 8192)
offset: Pagination offset (default: 0)
limit: Maximum items to return (default: 100)
addr: Filter by address (hexadecimal)
name: Exact name match filter (case-sensitive)
name_contains: Substring name filter (case-insensitive)
type: Filter by data type (e.g. "string", "dword")
Returns:
list: Data item information strings
dict: {
"result": list of data item objects,
"size": total count,
"offset": current offset,
"limit": current limit,
"_links": pagination links
}
"""
return safe_get(port, "data", {"offset": offset, "limit": limit})
params = {
"offset": offset,
"limit": limit
}
if addr:
params["addr"] = addr
if name:
params["name"] = name
if name_contains:
params["name_contains"] = name_contains
if type:
params["type"] = type
response = safe_get(port, "programs/current/data", params)
if isinstance(response, dict) and "error" in response:
return response
return {
"result": response.get("result", []),
"size": response.get("size", len(response.get("result", []))),
"offset": offset,
"limit": limit,
"_links": response.get("_links", {})
}
@mcp.tool()
def search_functions_by_name(port: int = DEFAULT_GHIDRA_PORT, query: str = "", offset: int = 0, limit: int = 100) -> list:
@ -479,6 +670,81 @@ def search_functions_by_name(port: int = DEFAULT_GHIDRA_PORT, query: str = "", o
return ["Error: query string is required"]
return safe_get(port, "functions", {"query": query, "offset": offset, "limit": limit})
@mcp.tool()
def read_memory(port: int = DEFAULT_GHIDRA_PORT,
address: str = "",
length: int = 16,
format: str = "hex") -> dict:
"""Read bytes from memory
Args:
port: Ghidra instance port (default: 8192)
address: Memory address in hex format
length: Number of bytes to read (default: 16)
format: Output format - "hex", "base64", or "string" (default: "hex")
Returns:
dict: {
"address": original address,
"length": bytes read,
"format": output format,
"bytes": the memory contents,
"timestamp": response timestamp
}
"""
if not address:
return {
"success": False,
"error": "Address parameter is required",
"timestamp": int(time.time() * 1000)
}
response = safe_get(port, "programs/current/memory", {
"address": address,
"length": length,
"format": format
})
if isinstance(response, dict) and "error" in response:
return response
return {
"address": address,
"length": length,
"format": format,
"bytes": response.get("result", ""),
"timestamp": response.get("timestamp", int(time.time() * 1000))
}
@mcp.tool()
def write_memory(port: int = DEFAULT_GHIDRA_PORT,
address: str = "",
bytes: str = "",
format: str = "hex") -> dict:
"""Write bytes to memory (use with caution)
Args:
port: Ghidra instance port (default: 8192)
address: Memory address in hex format
bytes: Data to write (format depends on 'format' parameter)
format: Input format - "hex", "base64", or "string" (default: "hex")
Returns:
dict: Operation result with success status
"""
if not address or not bytes:
return {
"success": False,
"error": "Address and bytes parameters are required",
"timestamp": int(time.time() * 1000)
}
return safe_post(port, "programs/current/memory", {
"address": address,
"bytes": bytes,
"format": format
})
@mcp.tool()
def get_function_by_address(port: int = DEFAULT_GHIDRA_PORT, address: str = "") -> dict:
"""Get function details by memory address
@ -516,6 +782,113 @@ def get_current_address(port: int = DEFAULT_GHIDRA_PORT) -> dict:
"port": port
}
@mcp.tool()
def list_xrefs(port: int = DEFAULT_GHIDRA_PORT,
to_addr: str = None,
from_addr: str = None,
type: str = None,
offset: int = 0,
limit: int = 100) -> dict:
"""List cross-references with filtering and pagination
Args:
port: Ghidra instance port (default: 8192)
to_addr: Filter references to this address (hexadecimal)
from_addr: Filter references from this address (hexadecimal)
type: Filter by reference type (e.g. "CALL", "READ", "WRITE")
offset: Pagination offset (default: 0)
limit: Maximum items to return (default: 100)
Returns:
dict: {
"result": list of xref objects,
"size": total count,
"offset": current offset,
"limit": current limit,
"_links": pagination links
}
"""
params = {
"offset": offset,
"limit": limit
}
if to_addr:
params["to_addr"] = to_addr
if from_addr:
params["from_addr"] = from_addr
if type:
params["type"] = type
response = safe_get(port, "programs/current/xrefs", params)
if isinstance(response, dict) and "error" in response:
return response
return {
"result": response.get("result", []),
"size": response.get("size", len(response.get("result", []))),
"offset": offset,
"limit": limit,
"_links": response.get("_links", {})
}
@mcp.tool()
def analyze_program(port: int = DEFAULT_GHIDRA_PORT,
analysis_options: dict = None) -> dict:
"""Run analysis on the current program
Args:
port: Ghidra instance port (default: 8192)
analysis_options: Dictionary of analysis options to enable/disable
(e.g. {"functionRecovery": True, "dataRefs": False})
None means use default analysis options
Returns:
dict: Analysis operation result with status
"""
return safe_post(port, "programs/current/analysis", analysis_options or {})
@mcp.tool()
def get_callgraph(port: int = DEFAULT_GHIDRA_PORT,
function: str = None,
max_depth: int = 3) -> dict:
"""Get function call graph visualization data
Args:
port: Ghidra instance port (default: 8192)
function: Starting function name (None starts from entry point)
max_depth: Maximum call depth to analyze (default: 3)
Returns:
dict: Graph data in DOT format with nodes and edges
"""
params = {"max_depth": max_depth}
if function:
params["function"] = function
return safe_get(port, "programs/current/analysis/callgraph", params)
@mcp.tool()
def get_dataflow(port: int = DEFAULT_GHIDRA_PORT,
address: str = "",
direction: str = "forward",
max_steps: int = 50) -> dict:
"""Perform data flow analysis from an address
Args:
port: Ghidra instance port (default: 8192)
address: Starting address in hex format
direction: "forward" or "backward" (default: "forward")
max_steps: Maximum analysis steps (default: 50)
Returns:
dict: Data flow analysis results
"""
return safe_get(port, "programs/current/analysis/dataflow", {
"address": address,
"direction": direction,
"max_steps": max_steps
})
@mcp.tool()
def get_current_function(port: int = DEFAULT_GHIDRA_PORT) -> dict:
"""Get the function currently selected in Ghidra's UI

File diff suppressed because it is too large Load Diff