windows-exe-decompiler-mcp-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CODEX_INSTALLATION.md +69 -0
- package/COPILOT_INSTALLATION.md +77 -0
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/bin/windows-exe-decompiler-mcp-server.js +3 -0
- package/dist/analysis-provenance.d.ts +184 -0
- package/dist/analysis-provenance.js +74 -0
- package/dist/analysis-task-runner.d.ts +31 -0
- package/dist/analysis-task-runner.js +160 -0
- package/dist/artifact-inventory.d.ts +23 -0
- package/dist/artifact-inventory.js +175 -0
- package/dist/cache-manager.d.ts +128 -0
- package/dist/cache-manager.js +454 -0
- package/dist/confidence-semantics.d.ts +66 -0
- package/dist/confidence-semantics.js +122 -0
- package/dist/config.d.ts +335 -0
- package/dist/config.js +193 -0
- package/dist/database.d.ts +227 -0
- package/dist/database.js +601 -0
- package/dist/decompiler-worker.d.ts +441 -0
- package/dist/decompiler-worker.js +1962 -0
- package/dist/dynamic-trace.d.ts +95 -0
- package/dist/dynamic-trace.js +629 -0
- package/dist/env-validator.d.ts +15 -0
- package/dist/env-validator.js +249 -0
- package/dist/error-handler.d.ts +28 -0
- package/dist/error-handler.example.d.ts +22 -0
- package/dist/error-handler.example.js +141 -0
- package/dist/error-handler.js +139 -0
- package/dist/ghidra-analysis-status.d.ts +49 -0
- package/dist/ghidra-analysis-status.js +178 -0
- package/dist/ghidra-config.d.ts +134 -0
- package/dist/ghidra-config.js +464 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +200 -0
- package/dist/job-queue.d.ts +169 -0
- package/dist/job-queue.js +407 -0
- package/dist/logger.d.ts +106 -0
- package/dist/logger.js +176 -0
- package/dist/policy-guard.d.ts +115 -0
- package/dist/policy-guard.js +243 -0
- package/dist/process-output.d.ts +15 -0
- package/dist/process-output.js +90 -0
- package/dist/prompts/function-explanation-review.d.ts +5 -0
- package/dist/prompts/function-explanation-review.js +64 -0
- package/dist/prompts/semantic-name-review.d.ts +5 -0
- package/dist/prompts/semantic-name-review.js +63 -0
- package/dist/runtime-correlation.d.ts +34 -0
- package/dist/runtime-correlation.js +279 -0
- package/dist/runtime-paths.d.ts +3 -0
- package/dist/runtime-paths.js +11 -0
- package/dist/selection-diff.d.ts +667 -0
- package/dist/selection-diff.js +53 -0
- package/dist/semantic-name-suggestion-artifacts.d.ts +116 -0
- package/dist/semantic-name-suggestion-artifacts.js +314 -0
- package/dist/server.d.ts +129 -0
- package/dist/server.js +578 -0
- package/dist/tools/artifact-read.d.ts +235 -0
- package/dist/tools/artifact-read.js +317 -0
- package/dist/tools/artifacts-diff.d.ts +728 -0
- package/dist/tools/artifacts-diff.js +304 -0
- package/dist/tools/artifacts-list.d.ts +515 -0
- package/dist/tools/artifacts-list.js +389 -0
- package/dist/tools/attack-map.d.ts +290 -0
- package/dist/tools/attack-map.js +519 -0
- package/dist/tools/cache-observability.d.ts +4 -0
- package/dist/tools/cache-observability.js +36 -0
- package/dist/tools/code-function-cfg.d.ts +50 -0
- package/dist/tools/code-function-cfg.js +102 -0
- package/dist/tools/code-function-decompile.d.ts +55 -0
- package/dist/tools/code-function-decompile.js +103 -0
- package/dist/tools/code-function-disassemble.d.ts +43 -0
- package/dist/tools/code-function-disassemble.js +185 -0
- package/dist/tools/code-function-explain-apply.d.ts +255 -0
- package/dist/tools/code-function-explain-apply.js +225 -0
- package/dist/tools/code-function-explain-prepare.d.ts +535 -0
- package/dist/tools/code-function-explain-prepare.js +276 -0
- package/dist/tools/code-function-explain-review.d.ts +397 -0
- package/dist/tools/code-function-explain-review.js +589 -0
- package/dist/tools/code-function-rename-apply.d.ts +248 -0
- package/dist/tools/code-function-rename-apply.js +220 -0
- package/dist/tools/code-function-rename-prepare.d.ts +506 -0
- package/dist/tools/code-function-rename-prepare.js +279 -0
- package/dist/tools/code-function-rename-review.d.ts +574 -0
- package/dist/tools/code-function-rename-review.js +761 -0
- package/dist/tools/code-functions-list.d.ts +37 -0
- package/dist/tools/code-functions-list.js +91 -0
- package/dist/tools/code-functions-rank.d.ts +34 -0
- package/dist/tools/code-functions-rank.js +90 -0
- package/dist/tools/code-functions-reconstruct.d.ts +2725 -0
- package/dist/tools/code-functions-reconstruct.js +2807 -0
- package/dist/tools/code-functions-search.d.ts +39 -0
- package/dist/tools/code-functions-search.js +90 -0
- package/dist/tools/code-reconstruct-export.d.ts +1212 -0
- package/dist/tools/code-reconstruct-export.js +4002 -0
- package/dist/tools/code-reconstruct-plan.d.ts +274 -0
- package/dist/tools/code-reconstruct-plan.js +342 -0
- package/dist/tools/dotnet-metadata-extract.d.ts +541 -0
- package/dist/tools/dotnet-metadata-extract.js +355 -0
- package/dist/tools/dotnet-reconstruct-export.d.ts +567 -0
- package/dist/tools/dotnet-reconstruct-export.js +1151 -0
- package/dist/tools/dotnet-types-list.d.ts +325 -0
- package/dist/tools/dotnet-types-list.js +201 -0
- package/dist/tools/dynamic-dependencies.d.ts +115 -0
- package/dist/tools/dynamic-dependencies.js +213 -0
- package/dist/tools/dynamic-memory-import.d.ts +10 -0
- package/dist/tools/dynamic-memory-import.js +567 -0
- package/dist/tools/dynamic-trace-import.d.ts +10 -0
- package/dist/tools/dynamic-trace-import.js +235 -0
- package/dist/tools/entrypoint-fallback-disasm.d.ts +30 -0
- package/dist/tools/entrypoint-fallback-disasm.js +89 -0
- package/dist/tools/ghidra-analyze.d.ts +88 -0
- package/dist/tools/ghidra-analyze.js +208 -0
- package/dist/tools/ghidra-health.d.ts +37 -0
- package/dist/tools/ghidra-health.js +212 -0
- package/dist/tools/ioc-export.d.ts +209 -0
- package/dist/tools/ioc-export.js +542 -0
- package/dist/tools/packer-detect.d.ts +165 -0
- package/dist/tools/packer-detect.js +284 -0
- package/dist/tools/pe-exports-extract.d.ts +175 -0
- package/dist/tools/pe-exports-extract.js +253 -0
- package/dist/tools/pe-fingerprint.d.ts +234 -0
- package/dist/tools/pe-fingerprint.js +269 -0
- package/dist/tools/pe-imports-extract.d.ts +105 -0
- package/dist/tools/pe-imports-extract.js +245 -0
- package/dist/tools/report-generate.d.ts +157 -0
- package/dist/tools/report-generate.js +457 -0
- package/dist/tools/report-summarize.d.ts +2131 -0
- package/dist/tools/report-summarize.js +596 -0
- package/dist/tools/runtime-detect.d.ts +135 -0
- package/dist/tools/runtime-detect.js +247 -0
- package/dist/tools/sample-ingest.d.ts +94 -0
- package/dist/tools/sample-ingest.js +327 -0
- package/dist/tools/sample-profile-get.d.ts +183 -0
- package/dist/tools/sample-profile-get.js +121 -0
- package/dist/tools/sandbox-execute.d.ts +441 -0
- package/dist/tools/sandbox-execute.js +392 -0
- package/dist/tools/strings-extract.d.ts +375 -0
- package/dist/tools/strings-extract.js +314 -0
- package/dist/tools/strings-floss-decode.d.ts +143 -0
- package/dist/tools/strings-floss-decode.js +259 -0
- package/dist/tools/system-health.d.ts +434 -0
- package/dist/tools/system-health.js +446 -0
- package/dist/tools/task-cancel.d.ts +21 -0
- package/dist/tools/task-cancel.js +70 -0
- package/dist/tools/task-status.d.ts +27 -0
- package/dist/tools/task-status.js +106 -0
- package/dist/tools/task-sweep.d.ts +22 -0
- package/dist/tools/task-sweep.js +77 -0
- package/dist/tools/tool-help.d.ts +340 -0
- package/dist/tools/tool-help.js +261 -0
- package/dist/tools/yara-scan.d.ts +554 -0
- package/dist/tools/yara-scan.js +313 -0
- package/dist/types.d.ts +266 -0
- package/dist/types.js +41 -0
- package/dist/worker-pool.d.ts +204 -0
- package/dist/worker-pool.js +650 -0
- package/dist/workflows/deep-static.d.ts +104 -0
- package/dist/workflows/deep-static.js +276 -0
- package/dist/workflows/function-explanation-review.d.ts +655 -0
- package/dist/workflows/function-explanation-review.js +440 -0
- package/dist/workflows/reconstruct.d.ts +2053 -0
- package/dist/workflows/reconstruct.js +666 -0
- package/dist/workflows/semantic-name-review.d.ts +2418 -0
- package/dist/workflows/semantic-name-review.js +521 -0
- package/dist/workflows/triage.d.ts +659 -0
- package/dist/workflows/triage.js +1374 -0
- package/dist/workspace-manager.d.ts +150 -0
- package/dist/workspace-manager.js +411 -0
- package/ghidra_scripts/DecompileFunction.java +487 -0
- package/ghidra_scripts/DecompileFunction.py +150 -0
- package/ghidra_scripts/ExtractCFG.java +256 -0
- package/ghidra_scripts/ExtractCFG.py +233 -0
- package/ghidra_scripts/ExtractFunctions.java +442 -0
- package/ghidra_scripts/ExtractFunctions.py +101 -0
- package/ghidra_scripts/README.md +125 -0
- package/ghidra_scripts/SearchFunctionReferences.java +380 -0
- package/helpers/DotNetMetadataProbe/DotNetMetadataProbe.csproj +9 -0
- package/helpers/DotNetMetadataProbe/Program.cs +566 -0
- package/install-to-codex.ps1 +178 -0
- package/install-to-copilot.ps1 +303 -0
- package/package.json +101 -0
- package/requirements.txt +9 -0
- package/workers/requirements-dynamic.txt +11 -0
- package/workers/requirements.txt +8 -0
- package/workers/speakeasy_compat.py +175 -0
- package/workers/static_worker.py +5183 -0
- package/workers/yara_rules/default.yar +33 -0
- package/workers/yara_rules/malware_families.yar +93 -0
- package/workers/yara_rules/packers.yar +80 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dynamic.trace.import tool
|
|
3
|
+
* Import external runtime API traces or memory-snapshot summaries into the MCP workspace.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'fs/promises';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { createHash, randomUUID } from 'crypto';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { normalizeDynamicTrace, normalizeDynamicTraceArtifactPayload, summarizeDynamicTrace, } from '../dynamic-trace.js';
|
|
10
|
+
const TOOL_NAME = 'dynamic.trace.import';
|
|
11
|
+
const DynamicTraceImportInputSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
sample_id: z.string().describe('Sample ID (format: sha256:<hex>)'),
|
|
14
|
+
format: z
|
|
15
|
+
.enum(['auto', 'normalized', 'generic_json', 'frida_json', 'speakeasy_json'])
|
|
16
|
+
.optional()
|
|
17
|
+
.default('auto')
|
|
18
|
+
.describe('Format hint for imported trace payload'),
|
|
19
|
+
evidence_kind: z
|
|
20
|
+
.enum(['trace', 'memory_snapshot', 'hybrid'])
|
|
21
|
+
.optional()
|
|
22
|
+
.default('trace')
|
|
23
|
+
.describe('Whether the imported evidence reflects executed runtime trace, memory snapshot, or hybrid evidence'),
|
|
24
|
+
path: z.string().optional().describe('Path to external JSON trace file'),
|
|
25
|
+
trace_json: z.any().optional().describe('Inline JSON payload to import when path is not used'),
|
|
26
|
+
trace_name: z.string().optional().describe('Optional source name used in persisted artifact naming'),
|
|
27
|
+
persist_artifact: z
|
|
28
|
+
.boolean()
|
|
29
|
+
.optional()
|
|
30
|
+
.default(true)
|
|
31
|
+
.describe('Persist normalized trace into workspace reports/dynamic'),
|
|
32
|
+
register_analysis: z
|
|
33
|
+
.boolean()
|
|
34
|
+
.optional()
|
|
35
|
+
.default(true)
|
|
36
|
+
.describe('Insert a completed analysis row for imported runtime evidence'),
|
|
37
|
+
})
|
|
38
|
+
.refine((value) => Boolean(value.path) !== Boolean(value.trace_json), {
|
|
39
|
+
message: 'Provide exactly one of `path` or `trace_json`.',
|
|
40
|
+
});
|
|
41
|
+
const DynamicTraceImportOutputSchema = z.object({
|
|
42
|
+
ok: z.boolean(),
|
|
43
|
+
data: z
|
|
44
|
+
.object({
|
|
45
|
+
trace_id: z.string(),
|
|
46
|
+
format: z.string(),
|
|
47
|
+
evidence_kind: z.string(),
|
|
48
|
+
executed: z.boolean(),
|
|
49
|
+
summary: z.object({
|
|
50
|
+
artifact_count: z.number(),
|
|
51
|
+
executed: z.boolean(),
|
|
52
|
+
api_count: z.number(),
|
|
53
|
+
memory_region_count: z.number(),
|
|
54
|
+
stage_count: z.number(),
|
|
55
|
+
observed_apis: z.array(z.string()),
|
|
56
|
+
high_signal_apis: z.array(z.string()),
|
|
57
|
+
memory_regions: z.array(z.string()),
|
|
58
|
+
stages: z.array(z.string()),
|
|
59
|
+
risk_hints: z.array(z.string()),
|
|
60
|
+
evidence: z.array(z.string()),
|
|
61
|
+
summary: z.string(),
|
|
62
|
+
}),
|
|
63
|
+
normalized_trace: z.any(),
|
|
64
|
+
analysis_id: z.string().optional(),
|
|
65
|
+
artifact: z
|
|
66
|
+
.object({
|
|
67
|
+
id: z.string(),
|
|
68
|
+
type: z.string(),
|
|
69
|
+
path: z.string(),
|
|
70
|
+
sha256: z.string(),
|
|
71
|
+
mime: z.string().optional(),
|
|
72
|
+
})
|
|
73
|
+
.optional(),
|
|
74
|
+
})
|
|
75
|
+
.optional(),
|
|
76
|
+
warnings: z.array(z.string()).optional(),
|
|
77
|
+
errors: z.array(z.string()).optional(),
|
|
78
|
+
artifacts: z.array(z.any()).optional(),
|
|
79
|
+
metrics: z
|
|
80
|
+
.object({
|
|
81
|
+
elapsed_ms: z.number(),
|
|
82
|
+
tool: z.string(),
|
|
83
|
+
})
|
|
84
|
+
.optional(),
|
|
85
|
+
});
|
|
86
|
+
export const dynamicTraceImportToolDefinition = {
|
|
87
|
+
name: TOOL_NAME,
|
|
88
|
+
description: 'Import external runtime API traces or memory-snapshot summaries (Frida/Speakeasy/generic JSON) into the workspace and register them as MCP artifacts.',
|
|
89
|
+
inputSchema: DynamicTraceImportInputSchema,
|
|
90
|
+
outputSchema: DynamicTraceImportOutputSchema,
|
|
91
|
+
};
|
|
92
|
+
function sanitizeName(value) {
|
|
93
|
+
const base = (value || 'trace').trim().toLowerCase();
|
|
94
|
+
const normalized = base.replace(/[^a-z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
95
|
+
return normalized.length > 0 ? normalized.slice(0, 48) : 'trace';
|
|
96
|
+
}
|
|
97
|
+
function resolveSourceFormat(inputFormat, normalizedHint) {
|
|
98
|
+
if (normalizedHint) {
|
|
99
|
+
return normalizedHint.source_format;
|
|
100
|
+
}
|
|
101
|
+
if (inputFormat === 'auto' || inputFormat === 'normalized') {
|
|
102
|
+
return 'generic_json';
|
|
103
|
+
}
|
|
104
|
+
return inputFormat;
|
|
105
|
+
}
|
|
106
|
+
async function readInputPayload(input) {
|
|
107
|
+
if (input.path) {
|
|
108
|
+
const content = await fs.readFile(input.path, 'utf-8');
|
|
109
|
+
return JSON.parse(content);
|
|
110
|
+
}
|
|
111
|
+
if (typeof input.trace_json === 'string') {
|
|
112
|
+
return JSON.parse(input.trace_json);
|
|
113
|
+
}
|
|
114
|
+
return input.trace_json;
|
|
115
|
+
}
|
|
116
|
+
export function createDynamicTraceImportHandler(workspaceManager, database) {
|
|
117
|
+
return async (args) => {
|
|
118
|
+
const startTime = Date.now();
|
|
119
|
+
try {
|
|
120
|
+
const input = DynamicTraceImportInputSchema.parse(args);
|
|
121
|
+
const sample = database.findSample(input.sample_id);
|
|
122
|
+
if (!sample) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
errors: [`Sample not found: ${input.sample_id}`],
|
|
126
|
+
metrics: {
|
|
127
|
+
elapsed_ms: Date.now() - startTime,
|
|
128
|
+
tool: TOOL_NAME,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const rawPayload = await readInputPayload(input);
|
|
133
|
+
const autoNormalized = input.format === 'auto' || input.format === 'normalized'
|
|
134
|
+
? normalizeDynamicTraceArtifactPayload(rawPayload)
|
|
135
|
+
: null;
|
|
136
|
+
const normalizedTrace = autoNormalized ||
|
|
137
|
+
normalizeDynamicTrace(rawPayload, {
|
|
138
|
+
sourceFormat: resolveSourceFormat(input.format, autoNormalized),
|
|
139
|
+
evidenceKind: input.evidence_kind,
|
|
140
|
+
sourceName: input.trace_name || (input.path ? path.basename(input.path) : undefined),
|
|
141
|
+
});
|
|
142
|
+
const summary = summarizeDynamicTrace(normalizedTrace);
|
|
143
|
+
const warnings = [];
|
|
144
|
+
const artifacts = [];
|
|
145
|
+
let persistedArtifact;
|
|
146
|
+
let analysisId;
|
|
147
|
+
if (!normalizedTrace.executed) {
|
|
148
|
+
warnings.push('Imported evidence does not prove full execution; treat it as memory or hybrid runtime evidence until corroborated.');
|
|
149
|
+
}
|
|
150
|
+
if (input.persist_artifact) {
|
|
151
|
+
const workspace = await workspaceManager.createWorkspace(input.sample_id);
|
|
152
|
+
const reportDir = path.join(workspace.reports, 'dynamic');
|
|
153
|
+
await fs.mkdir(reportDir, { recursive: true });
|
|
154
|
+
const fileName = `imported_${sanitizeName(input.trace_name || path.basename(input.path || 'trace'))}_${Date.now()}.json`;
|
|
155
|
+
const absPath = path.join(reportDir, fileName);
|
|
156
|
+
const serialized = JSON.stringify(normalizedTrace, null, 2);
|
|
157
|
+
await fs.writeFile(absPath, serialized, 'utf-8');
|
|
158
|
+
const artifactId = randomUUID();
|
|
159
|
+
const artifactSha256 = createHash('sha256').update(serialized).digest('hex');
|
|
160
|
+
const relativePath = `reports/dynamic/${fileName}`;
|
|
161
|
+
database.insertArtifact({
|
|
162
|
+
id: artifactId,
|
|
163
|
+
sample_id: input.sample_id,
|
|
164
|
+
type: 'dynamic_trace_json',
|
|
165
|
+
path: relativePath,
|
|
166
|
+
sha256: artifactSha256,
|
|
167
|
+
mime: 'application/json',
|
|
168
|
+
created_at: new Date().toISOString(),
|
|
169
|
+
});
|
|
170
|
+
persistedArtifact = {
|
|
171
|
+
id: artifactId,
|
|
172
|
+
type: 'dynamic_trace_json',
|
|
173
|
+
path: relativePath,
|
|
174
|
+
sha256: artifactSha256,
|
|
175
|
+
mime: 'application/json',
|
|
176
|
+
};
|
|
177
|
+
artifacts.push(persistedArtifact);
|
|
178
|
+
}
|
|
179
|
+
if (input.register_analysis) {
|
|
180
|
+
analysisId = randomUUID();
|
|
181
|
+
database.insertAnalysis({
|
|
182
|
+
id: analysisId,
|
|
183
|
+
sample_id: input.sample_id,
|
|
184
|
+
stage: 'dynamic_trace_import',
|
|
185
|
+
backend: 'runtime_import',
|
|
186
|
+
status: 'done',
|
|
187
|
+
started_at: new Date(startTime).toISOString(),
|
|
188
|
+
finished_at: new Date().toISOString(),
|
|
189
|
+
output_json: JSON.stringify({
|
|
190
|
+
trace_format: normalizedTrace.source_format,
|
|
191
|
+
evidence_kind: normalizedTrace.evidence_kind,
|
|
192
|
+
executed: normalizedTrace.executed,
|
|
193
|
+
summary,
|
|
194
|
+
artifact_id: persistedArtifact?.id,
|
|
195
|
+
}),
|
|
196
|
+
metrics_json: JSON.stringify({
|
|
197
|
+
api_count: summary.api_count,
|
|
198
|
+
memory_region_count: summary.memory_region_count,
|
|
199
|
+
stage_count: summary.stage_count,
|
|
200
|
+
}),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
ok: true,
|
|
205
|
+
data: {
|
|
206
|
+
trace_id: randomUUID(),
|
|
207
|
+
format: normalizedTrace.source_format,
|
|
208
|
+
evidence_kind: normalizedTrace.evidence_kind,
|
|
209
|
+
executed: normalizedTrace.executed,
|
|
210
|
+
summary,
|
|
211
|
+
normalized_trace: normalizedTrace,
|
|
212
|
+
analysis_id: analysisId,
|
|
213
|
+
artifact: persistedArtifact,
|
|
214
|
+
},
|
|
215
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
216
|
+
artifacts: artifacts.length > 0 ? artifacts : undefined,
|
|
217
|
+
metrics: {
|
|
218
|
+
elapsed_ms: Date.now() - startTime,
|
|
219
|
+
tool: TOOL_NAME,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
return {
|
|
225
|
+
ok: false,
|
|
226
|
+
errors: [error.message],
|
|
227
|
+
metrics: {
|
|
228
|
+
elapsed_ms: Date.now() - startTime,
|
|
229
|
+
tool: TOOL_NAME,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=dynamic-trace-import.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for fallback disassembly via static_worker entrypoint.disasm.
|
|
3
|
+
*/
|
|
4
|
+
export interface EntrypointFallbackResult {
|
|
5
|
+
function: string;
|
|
6
|
+
address: string;
|
|
7
|
+
entry_point_rva: string;
|
|
8
|
+
entry_section: string;
|
|
9
|
+
architecture: string;
|
|
10
|
+
backend: string;
|
|
11
|
+
parser: string;
|
|
12
|
+
instruction_count: number;
|
|
13
|
+
assembly: string;
|
|
14
|
+
bytes_window: number;
|
|
15
|
+
requested_address?: string | null;
|
|
16
|
+
requested_rva?: string | null;
|
|
17
|
+
resolved_from?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface EntrypointFallbackPayload {
|
|
20
|
+
result: EntrypointFallbackResult;
|
|
21
|
+
warnings?: string[];
|
|
22
|
+
metrics?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export declare function runEntrypointFallbackDisasm(samplePath: string, options?: {
|
|
25
|
+
max_instructions?: number;
|
|
26
|
+
max_bytes?: number;
|
|
27
|
+
target_address?: string;
|
|
28
|
+
target_symbol?: string;
|
|
29
|
+
}): Promise<EntrypointFallbackPayload>;
|
|
30
|
+
//# sourceMappingURL=entrypoint-fallback-disasm.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for fallback disassembly via static_worker entrypoint.disasm.
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
import { resolvePackagePath } from '../runtime-paths.js';
|
|
7
|
+
function getPythonCommand() {
|
|
8
|
+
return process.platform === 'win32' ? 'python' : 'python3';
|
|
9
|
+
}
|
|
10
|
+
export async function runEntrypointFallbackDisasm(samplePath, options) {
|
|
11
|
+
const request = {
|
|
12
|
+
job_id: uuidv4(),
|
|
13
|
+
tool: 'entrypoint.disasm',
|
|
14
|
+
sample: {
|
|
15
|
+
sample_id: 'sha256:fallback',
|
|
16
|
+
path: samplePath,
|
|
17
|
+
},
|
|
18
|
+
args: {
|
|
19
|
+
max_instructions: options?.max_instructions ?? 120,
|
|
20
|
+
max_bytes: options?.max_bytes ?? 1024,
|
|
21
|
+
target_address: options?.target_address,
|
|
22
|
+
target_symbol: options?.target_symbol,
|
|
23
|
+
},
|
|
24
|
+
context: {
|
|
25
|
+
request_time_utc: new Date().toISOString(),
|
|
26
|
+
policy: {
|
|
27
|
+
allow_dynamic: false,
|
|
28
|
+
allow_network: false,
|
|
29
|
+
},
|
|
30
|
+
versions: {
|
|
31
|
+
tool_version: 'fallback-0.1.0',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const response = await callStaticWorker(request);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(response.errors.join('; ') || 'entrypoint.disasm failed');
|
|
38
|
+
}
|
|
39
|
+
const payload = response.data;
|
|
40
|
+
if (!payload || !payload.result || !payload.result.assembly) {
|
|
41
|
+
throw new Error('entrypoint.disasm returned invalid payload');
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
result: payload.result,
|
|
45
|
+
warnings: payload.warnings || response.warnings || [],
|
|
46
|
+
metrics: payload.metrics || response.metrics,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async function callStaticWorker(request) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const workerPath = resolvePackagePath('workers', 'static_worker.py');
|
|
52
|
+
const child = spawn(getPythonCommand(), [workerPath], {
|
|
53
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
54
|
+
});
|
|
55
|
+
let stdout = '';
|
|
56
|
+
let stderr = '';
|
|
57
|
+
child.stdout.on('data', (data) => {
|
|
58
|
+
stdout += data.toString();
|
|
59
|
+
});
|
|
60
|
+
child.stderr.on('data', (data) => {
|
|
61
|
+
stderr += data.toString();
|
|
62
|
+
});
|
|
63
|
+
child.on('error', (error) => {
|
|
64
|
+
reject(new Error(`Failed to spawn Python worker: ${error.message}`));
|
|
65
|
+
});
|
|
66
|
+
child.on('close', (code) => {
|
|
67
|
+
if (code !== 0) {
|
|
68
|
+
reject(new Error(`Python worker exited with code ${code}. stderr: ${stderr}`));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const lines = stdout.trim().split('\n');
|
|
73
|
+
const lastLine = lines[lines.length - 1];
|
|
74
|
+
resolve(JSON.parse(lastLine));
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
reject(new Error(`Failed to parse worker response: ${error.message}. stdout: ${stdout}`));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
try {
|
|
81
|
+
child.stdin.write(JSON.stringify(request) + '\n');
|
|
82
|
+
child.stdin.end();
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
reject(new Error(`Failed to write to worker stdin: ${error.message}`));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=entrypoint-fallback-disasm.js.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ghidra.analyze MCP Tool
|
|
3
|
+
*
|
|
4
|
+
* Requirements: 8.1, 8.2, 8.3
|
|
5
|
+
*
|
|
6
|
+
* Analyzes a binary sample with Ghidra Headless and extracts function list
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import type { ToolDefinition, ToolHandler } from '../types.js';
|
|
10
|
+
import type { DatabaseManager } from '../database.js';
|
|
11
|
+
import type { WorkspaceManager } from '../workspace-manager.js';
|
|
12
|
+
import type { JobQueue } from '../job-queue.js';
|
|
13
|
+
/**
|
|
14
|
+
* Input schema for ghidra.analyze tool
|
|
15
|
+
* Requirements: 8.1
|
|
16
|
+
*/
|
|
17
|
+
export declare const ghidraAnalyzeInputSchema: z.ZodObject<{
|
|
18
|
+
sample_id: z.ZodString;
|
|
19
|
+
options: z.ZodOptional<z.ZodObject<{
|
|
20
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
max_cpu: z.ZodOptional<z.ZodString>;
|
|
22
|
+
project_key: z.ZodOptional<z.ZodString>;
|
|
23
|
+
}, "strip", z.ZodTypeAny, {
|
|
24
|
+
timeout?: number | undefined;
|
|
25
|
+
project_key?: string | undefined;
|
|
26
|
+
max_cpu?: string | undefined;
|
|
27
|
+
}, {
|
|
28
|
+
timeout?: number | undefined;
|
|
29
|
+
project_key?: string | undefined;
|
|
30
|
+
max_cpu?: string | undefined;
|
|
31
|
+
}>>;
|
|
32
|
+
}, "strip", z.ZodTypeAny, {
|
|
33
|
+
sample_id: string;
|
|
34
|
+
options?: {
|
|
35
|
+
timeout?: number | undefined;
|
|
36
|
+
project_key?: string | undefined;
|
|
37
|
+
max_cpu?: string | undefined;
|
|
38
|
+
} | undefined;
|
|
39
|
+
}, {
|
|
40
|
+
sample_id: string;
|
|
41
|
+
options?: {
|
|
42
|
+
timeout?: number | undefined;
|
|
43
|
+
project_key?: string | undefined;
|
|
44
|
+
max_cpu?: string | undefined;
|
|
45
|
+
} | undefined;
|
|
46
|
+
}>;
|
|
47
|
+
export type GhidraAnalyzeInput = z.infer<typeof ghidraAnalyzeInputSchema>;
|
|
48
|
+
/**
|
|
49
|
+
* Output schema for ghidra.analyze tool
|
|
50
|
+
* Requirements: 8.2, 8.3
|
|
51
|
+
*/
|
|
52
|
+
export interface GhidraAnalyzeOutput {
|
|
53
|
+
ok: boolean;
|
|
54
|
+
data?: {
|
|
55
|
+
analysis_id: string;
|
|
56
|
+
job_id?: string;
|
|
57
|
+
backend: string;
|
|
58
|
+
function_count: number;
|
|
59
|
+
project_path: string;
|
|
60
|
+
status: string;
|
|
61
|
+
capabilities?: {
|
|
62
|
+
function_index: unknown;
|
|
63
|
+
decompile: unknown;
|
|
64
|
+
cfg: unknown;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
diagnostics?: unknown;
|
|
68
|
+
normalized_error?: unknown;
|
|
69
|
+
errors?: string[];
|
|
70
|
+
warnings?: string[];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Tool definition for ghidra.analyze
|
|
74
|
+
* Requirements: 8.1, 8.2, 8.3
|
|
75
|
+
*/
|
|
76
|
+
export declare const ghidraAnalyzeToolDefinition: ToolDefinition;
|
|
77
|
+
/**
|
|
78
|
+
* Create handler for ghidra.analyze tool
|
|
79
|
+
*
|
|
80
|
+
* Requirements: 8.1, 8.2, 8.3, 8.4, 8.5, 8.6
|
|
81
|
+
*
|
|
82
|
+
* @param workspaceManager - Workspace manager instance
|
|
83
|
+
* @param database - Database manager instance
|
|
84
|
+
* @param jobQueue - Job queue instance (optional, for async execution)
|
|
85
|
+
* @returns Tool handler function
|
|
86
|
+
*/
|
|
87
|
+
export declare function createGhidraAnalyzeHandler(workspaceManager: WorkspaceManager, database: DatabaseManager, jobQueue?: JobQueue): ToolHandler;
|
|
88
|
+
//# sourceMappingURL=ghidra-analyze.d.ts.map
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ghidra.analyze MCP Tool
|
|
3
|
+
*
|
|
4
|
+
* Requirements: 8.1, 8.2, 8.3
|
|
5
|
+
*
|
|
6
|
+
* Analyzes a binary sample with Ghidra Headless and extracts function list
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { DecompilerWorker, getGhidraDiagnostics, normalizeGhidraError } from '../decompiler-worker.js';
|
|
10
|
+
import { findBestGhidraAnalysis, getGhidraReadiness, parseGhidraAnalysisMetadata, } from '../ghidra-analysis-status.js';
|
|
11
|
+
import { logger } from '../logger.js';
|
|
12
|
+
/**
|
|
13
|
+
* Input schema for ghidra.analyze tool
|
|
14
|
+
* Requirements: 8.1
|
|
15
|
+
*/
|
|
16
|
+
export const ghidraAnalyzeInputSchema = z.object({
|
|
17
|
+
sample_id: z.string().describe('Sample identifier (sha256:<hex>)'),
|
|
18
|
+
options: z.object({
|
|
19
|
+
timeout: z.number().optional().describe('Analysis timeout in seconds (default: 300)'),
|
|
20
|
+
max_cpu: z.string().optional().describe('Maximum CPU cores to use (default: "4")'),
|
|
21
|
+
project_key: z.string().optional().describe('Optional project key for reusing existing project')
|
|
22
|
+
}).optional().describe('Ghidra analysis options')
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Tool definition for ghidra.analyze
|
|
26
|
+
* Requirements: 8.1, 8.2, 8.3
|
|
27
|
+
*/
|
|
28
|
+
export const ghidraAnalyzeToolDefinition = {
|
|
29
|
+
name: 'ghidra.analyze',
|
|
30
|
+
description: 'Analyze a binary sample with Ghidra Headless and extract function list. This is a long-running operation that may take several minutes depending on sample size.',
|
|
31
|
+
inputSchema: ghidraAnalyzeInputSchema
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Create handler for ghidra.analyze tool
|
|
35
|
+
*
|
|
36
|
+
* Requirements: 8.1, 8.2, 8.3, 8.4, 8.5, 8.6
|
|
37
|
+
*
|
|
38
|
+
* @param workspaceManager - Workspace manager instance
|
|
39
|
+
* @param database - Database manager instance
|
|
40
|
+
* @param jobQueue - Job queue instance (optional, for async execution)
|
|
41
|
+
* @returns Tool handler function
|
|
42
|
+
*/
|
|
43
|
+
export function createGhidraAnalyzeHandler(workspaceManager, database, jobQueue) {
|
|
44
|
+
return async (args) => {
|
|
45
|
+
try {
|
|
46
|
+
// Validate input
|
|
47
|
+
const input = ghidraAnalyzeInputSchema.parse(args);
|
|
48
|
+
logger.info({
|
|
49
|
+
sample_id: input.sample_id,
|
|
50
|
+
options: input.options
|
|
51
|
+
}, 'ghidra.analyze tool called');
|
|
52
|
+
// Check if sample exists
|
|
53
|
+
const sample = database.findSample(input.sample_id);
|
|
54
|
+
if (!sample) {
|
|
55
|
+
return {
|
|
56
|
+
content: [{
|
|
57
|
+
type: 'text',
|
|
58
|
+
text: JSON.stringify({
|
|
59
|
+
ok: false,
|
|
60
|
+
errors: [`Sample not found: ${input.sample_id}`]
|
|
61
|
+
}, null, 2)
|
|
62
|
+
}],
|
|
63
|
+
isError: true
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Create decompiler worker
|
|
67
|
+
const decompilerWorker = new DecompilerWorker(database, workspaceManager);
|
|
68
|
+
const analyses = database.findAnalysesBySample(input.sample_id);
|
|
69
|
+
const reusableAnalysis = (() => {
|
|
70
|
+
if (input.options?.project_key) {
|
|
71
|
+
return analyses.find((analysis) => {
|
|
72
|
+
if (analysis.backend !== 'ghidra') {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const metadata = parseGhidraAnalysisMetadata(analysis.output_json);
|
|
76
|
+
return (metadata.project_key === input.options?.project_key &&
|
|
77
|
+
getGhidraReadiness(analysis).function_index.status === 'ready');
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return findBestGhidraAnalysis(analyses, 'function_index');
|
|
81
|
+
})();
|
|
82
|
+
if (reusableAnalysis) {
|
|
83
|
+
const metadata = parseGhidraAnalysisMetadata(reusableAnalysis.output_json);
|
|
84
|
+
const reusedOutput = {
|
|
85
|
+
ok: true,
|
|
86
|
+
data: {
|
|
87
|
+
analysis_id: reusableAnalysis.id,
|
|
88
|
+
backend: reusableAnalysis.backend,
|
|
89
|
+
function_count: typeof metadata.function_count === 'number' ? metadata.function_count : 0,
|
|
90
|
+
project_path: typeof metadata.project_path === 'string' ? metadata.project_path : '',
|
|
91
|
+
status: 'reused',
|
|
92
|
+
capabilities: getGhidraReadiness(reusableAnalysis),
|
|
93
|
+
},
|
|
94
|
+
warnings: [
|
|
95
|
+
input.options?.project_key
|
|
96
|
+
? `Reused existing completed Ghidra analysis for project_key=${input.options.project_key}.`
|
|
97
|
+
: 'Reused existing completed Ghidra analysis for this sample.',
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: 'text',
|
|
104
|
+
text: JSON.stringify(reusedOutput, null, 2),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// Convert timeout from seconds to milliseconds
|
|
110
|
+
const timeoutMs = (input.options?.timeout || 300) * 1000;
|
|
111
|
+
// Prepare Ghidra options
|
|
112
|
+
const ghidraOptions = {
|
|
113
|
+
timeout: timeoutMs,
|
|
114
|
+
maxCpu: input.options?.max_cpu || '4',
|
|
115
|
+
projectKey: input.options?.project_key
|
|
116
|
+
};
|
|
117
|
+
// If job queue is available, enqueue the analysis
|
|
118
|
+
if (jobQueue) {
|
|
119
|
+
const jobId = await jobQueue.enqueue({
|
|
120
|
+
type: 'decompile',
|
|
121
|
+
tool: 'ghidra.analyze',
|
|
122
|
+
sampleId: input.sample_id,
|
|
123
|
+
args: input,
|
|
124
|
+
priority: 5,
|
|
125
|
+
timeout: timeoutMs,
|
|
126
|
+
retryPolicy: {
|
|
127
|
+
maxRetries: 2,
|
|
128
|
+
backoffMs: 5000,
|
|
129
|
+
retryableErrors: ['E_TIMEOUT', 'E_RESOURCE_EXHAUSTED']
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
logger.info({
|
|
133
|
+
job_id: jobId,
|
|
134
|
+
sample_id: input.sample_id
|
|
135
|
+
}, 'Ghidra analysis job enqueued');
|
|
136
|
+
const output = {
|
|
137
|
+
ok: true,
|
|
138
|
+
data: {
|
|
139
|
+
analysis_id: jobId,
|
|
140
|
+
job_id: jobId,
|
|
141
|
+
backend: 'ghidra',
|
|
142
|
+
function_count: 0,
|
|
143
|
+
project_path: '',
|
|
144
|
+
status: 'queued'
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
return {
|
|
148
|
+
content: [
|
|
149
|
+
{
|
|
150
|
+
type: 'text',
|
|
151
|
+
text: JSON.stringify(output, null, 2)
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// Otherwise, execute synchronously
|
|
157
|
+
const result = await decompilerWorker.analyze(input.sample_id, ghidraOptions);
|
|
158
|
+
logger.info({
|
|
159
|
+
analysis_id: result.analysisId,
|
|
160
|
+
function_count: result.functionCount
|
|
161
|
+
}, 'Ghidra analysis completed');
|
|
162
|
+
const output = {
|
|
163
|
+
ok: true,
|
|
164
|
+
data: {
|
|
165
|
+
analysis_id: result.analysisId,
|
|
166
|
+
backend: result.backend,
|
|
167
|
+
function_count: result.functionCount,
|
|
168
|
+
project_path: result.projectPath,
|
|
169
|
+
status: result.status === 'partial_success' ? 'partial_success' : 'completed',
|
|
170
|
+
capabilities: result.readiness,
|
|
171
|
+
},
|
|
172
|
+
warnings: result.warnings,
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
content: [
|
|
176
|
+
{
|
|
177
|
+
type: 'text',
|
|
178
|
+
text: JSON.stringify(output, null, 2)
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
185
|
+
const diagnostics = getGhidraDiagnostics(error);
|
|
186
|
+
const normalizedError = normalizeGhidraError(error, 'ghidra.analyze');
|
|
187
|
+
logger.error({
|
|
188
|
+
error: errorMessage,
|
|
189
|
+
ghidra_diagnostics: diagnostics,
|
|
190
|
+
normalized_error: normalizedError,
|
|
191
|
+
}, 'ghidra.analyze tool failed');
|
|
192
|
+
const output = {
|
|
193
|
+
ok: false,
|
|
194
|
+
diagnostics,
|
|
195
|
+
normalized_error: normalizedError,
|
|
196
|
+
errors: [errorMessage]
|
|
197
|
+
};
|
|
198
|
+
return {
|
|
199
|
+
content: [{
|
|
200
|
+
type: 'text',
|
|
201
|
+
text: JSON.stringify(output, null, 2)
|
|
202
|
+
}],
|
|
203
|
+
isError: true
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=ghidra-analyze.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ghidra.health MCP Tool
|
|
3
|
+
*
|
|
4
|
+
* Performs both environment validation and an optional downstream live probe
|
|
5
|
+
* against a real analyzed sample/project to verify end-to-end usability.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import type { ToolDefinition, ToolHandler } from '../types.js';
|
|
9
|
+
import type { WorkspaceManager } from '../workspace-manager.js';
|
|
10
|
+
import type { DatabaseManager } from '../database.js';
|
|
11
|
+
import { type GhidraHealthStatus } from '../ghidra-config.js';
|
|
12
|
+
import { DecompilerWorker } from '../decompiler-worker.js';
|
|
13
|
+
export declare const ghidraHealthInputSchema: z.ZodObject<{
|
|
14
|
+
timeout_ms: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
15
|
+
sample_id: z.ZodOptional<z.ZodString>;
|
|
16
|
+
include_end_to_end: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
17
|
+
stale_running_ms: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
18
|
+
}, "strip", z.ZodTypeAny, {
|
|
19
|
+
timeout_ms: number;
|
|
20
|
+
include_end_to_end: boolean;
|
|
21
|
+
sample_id?: string | undefined;
|
|
22
|
+
stale_running_ms?: number | null | undefined;
|
|
23
|
+
}, {
|
|
24
|
+
sample_id?: string | undefined;
|
|
25
|
+
timeout_ms?: number | undefined;
|
|
26
|
+
stale_running_ms?: number | null | undefined;
|
|
27
|
+
include_end_to_end?: boolean | undefined;
|
|
28
|
+
}>;
|
|
29
|
+
export type GhidraHealthInput = z.infer<typeof ghidraHealthInputSchema>;
|
|
30
|
+
export declare const ghidraHealthToolDefinition: ToolDefinition;
|
|
31
|
+
interface GhidraHealthDependencies {
|
|
32
|
+
checkGhidra?: (timeoutMs: number) => GhidraHealthStatus;
|
|
33
|
+
decompilerWorker?: Pick<DecompilerWorker, 'decompileFunction' | 'getFunctionCFG'>;
|
|
34
|
+
}
|
|
35
|
+
export declare function createGhidraHealthHandler(workspaceManager?: WorkspaceManager, database?: DatabaseManager, dependencies?: GhidraHealthDependencies): ToolHandler;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=ghidra-health.d.ts.map
|