rea-agents 2.0.0 → 2.2.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/README.md +103 -38
- package/bridge/ghidra/ReaGhidraBridge.java +96 -11
- package/dist/application/AndroidApplicationService.js +17 -0
- package/dist/application/AppleApplicationService.js +17 -0
- package/dist/application/ArtifactExtraction.js +1 -1
- package/dist/application/ArtifactInventory/classify.js +42 -0
- package/dist/application/ArtifactInventory/hash.js +43 -0
- package/dist/application/ArtifactInventory/reader.js +41 -0
- package/dist/application/ArtifactInventory/scanCanonical.js +155 -0
- package/dist/application/ArtifactInventory/scanReader.js +124 -0
- package/dist/application/ArtifactInventory/types.js +10 -0
- package/dist/application/ArtifactInventory.js +9 -351
- package/dist/application/AuthorizedArtifactInventory.js +2 -2
- package/dist/application/BinarySession.js +47 -270
- package/dist/application/BinarySessionExecution.js +28 -0
- package/dist/application/BinarySessionOpen.js +39 -0
- package/dist/application/BinarySessionRecords.js +149 -0
- package/dist/application/BinarySessionStatus.js +75 -0
- package/dist/application/CapabilityInventory.js +24 -2
- package/dist/application/ClientRegistrationStatus.js +11 -4
- package/dist/application/CrossVersionInventory.js +8 -2
- package/dist/application/CrossVersionInvestigation.js +1 -23
- package/dist/application/CrossVersionInvestigationCollections.js +23 -0
- package/dist/application/DirectAnalysis.js +47 -44
- package/dist/application/Doctor.js +156 -238
- package/dist/application/DoctorDiagnostics.js +180 -0
- package/dist/application/ElectronBoundaryAnalysis.js +23 -7
- package/dist/application/ElectronBoundaryGraphWindows.js +32 -8
- package/dist/application/EnhancedToolPagination.js +22 -0
- package/dist/application/EnhancedToolTypes.js +4 -0
- package/dist/application/EnhancedTools.js +20 -37
- package/dist/application/InstrumentedJavaScriptReplayHost.js +36 -0
- package/dist/application/InventoryProjectionEvidence.js +41 -0
- package/dist/application/InvestigationProviders.js +12 -0
- package/dist/application/InvestigationWorkspaceStore.js +24 -10
- package/dist/application/JavaScriptArtifactAnalysis.js +45 -6
- package/dist/application/JavaScriptArtifactFiles.js +6 -2
- package/dist/application/JavaScriptArtifactGraphBuilder.js +23 -1
- package/dist/application/JavaScriptArtifactGraphBundlers.js +225 -0
- package/dist/application/JavaScriptArtifactGraphContext.js +37 -36
- package/dist/application/JavaScriptArtifactGraphDocuments.js +27 -4
- package/dist/application/JavaScriptArtifactGraphFindings.js +54 -8
- package/dist/application/JavaScriptArtifactGraphStructure.js +29 -101
- package/dist/application/JavaScriptArtifactPathResolution.js +269 -0
- package/dist/application/JavaScriptArtifactReconstruction.js +1 -1
- package/dist/application/JavaScriptJsonModules.js +39 -0
- package/dist/application/JavaScriptModuleRelationships.js +262 -0
- package/dist/application/JavaScriptReplayPermission.js +16 -0
- package/dist/application/JavaScriptReplayService.js +182 -156
- package/dist/application/LinuxHopper.js +10 -0
- package/dist/application/MacHopper.js +22 -11
- package/dist/application/NodeRuntimeCharacterizationService.js +196 -0
- package/dist/application/PermissionAuthority.js +106 -63
- package/dist/application/ProcessCaptureCapability.js +1 -1
- package/dist/application/ProcessCapturePermission.js +13 -0
- package/dist/application/ProcessCli.js +2 -12
- package/dist/application/ProcessHarness.js +1 -1
- package/dist/application/ProcessPairedExperiment.js +72 -0
- package/dist/application/ProjectPermissionStore.js +48 -9
- package/dist/application/RealHopperAssertions.js +76 -0
- package/dist/application/ReconstructionCoverageService.js +69 -0
- package/dist/application/ReconstructionCoverageWorkspaceStore.js +31 -0
- package/dist/application/RuntimeExecutableDiagnostics.js +172 -0
- package/dist/application/Setup.js +148 -260
- package/dist/application/SetupClientConfiguration.js +224 -0
- package/dist/application/SetupClients.js +24 -2
- package/dist/application/SetupPlan.js +124 -12
- package/dist/application/SetupSkill.js +54 -36
- package/dist/application/SupportedClients.js +1 -0
- package/dist/application/Uninstall.js +12 -3
- package/dist/application/Upgrade.js +16 -1
- package/dist/application/WindowsCapabilities.js +86 -0
- package/dist/artifacts/AsarArtifactReader.js +11 -0
- package/dist/browser/BrowserProcessStartup.js +88 -0
- package/dist/browser/CdpCaptureDocuments.js +12 -8
- package/dist/browser/CdpCaptureEventBodyShapes.js +75 -0
- package/dist/browser/CdpCaptureEventHandlers.js +336 -0
- package/dist/browser/CdpCaptureEventHelpers.js +71 -0
- package/dist/browser/CdpCaptureEventState.js +1 -0
- package/dist/browser/CdpCaptureEventTypes.js +1 -0
- package/dist/browser/CdpCaptureEvents.js +29 -496
- package/dist/browser/CdpObservationSession.js +51 -8
- package/dist/browser/CdpPageCapture.js +12 -133
- package/dist/browser/CdpPageCaptureScripts.js +97 -0
- package/dist/browser/CdpPageCaptureWorkers.js +46 -0
- package/dist/browser/CdpWebMcpDiscovery.js +38 -14
- package/dist/browser/PngVisualDiff.js +9 -2
- package/dist/catalogIdentity.js +4 -0
- package/dist/cli/artifactCommands.js +82 -0
- package/dist/cli/coreAnalysisCommands.js +232 -0
- package/dist/cli/managedCommands.js +397 -0
- package/dist/cli/managedProjectGraphCommand.js +24 -0
- package/dist/cli/options.js +9 -0
- package/dist/cli/setupCommands.js +113 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utilityCommands.js +122 -0
- package/dist/cli.js +10 -768
- package/dist/cliApplicationCommands.js +118 -4
- package/dist/cliBrowserAdvancedCommands.js +73 -16
- package/dist/cliBrowserCommands.js +39 -115
- package/dist/cliCommandNames.js +4 -0
- package/dist/cliElectronCommands.js +33 -62
- package/dist/cliEvidenceCommands.js +13 -2
- package/dist/cliInvestigationCommands.js +53 -10
- package/dist/cliObservationOptions.js +137 -0
- package/dist/cliPolicyCommands.js +80 -46
- package/dist/cliSetup.js +306 -0
- package/dist/config/environment.js +107 -0
- package/dist/config/parseConfig.js +228 -0
- package/dist/config/parsers.js +68 -0
- package/dist/config/permissions.js +30 -0
- package/dist/config/types.js +1 -0
- package/dist/config.js +1 -375
- package/dist/contracts/applicationToolContracts.js +134 -0
- package/dist/contracts/electronToolContracts.js +51 -3
- package/dist/contracts/managedToolContracts.js +8 -3
- package/dist/contracts/managedWorkflowToolContracts.js +3 -3
- package/dist/contracts/providerSelection.js +2 -0
- package/dist/contracts/sessionStatusContract.js +26 -0
- package/dist/contracts/toolContractTypes.js +12 -1
- package/dist/contracts/toolContracts.js +5 -12
- package/dist/contracts/toolEffects.js +14 -0
- package/dist/contracts/toolOutputSchemaGroups.js +274 -0
- package/dist/contracts/toolOutputSchemaPrimitives.js +328 -0
- package/dist/contracts/toolOutputSchemas.js +2 -550
- package/dist/domain/analysisErrorPresentation.js +234 -0
- package/dist/domain/analysisErrorProjection.js +284 -0
- package/dist/domain/androidApplication.js +206 -0
- package/dist/domain/appleApplication.js +221 -0
- package/dist/domain/artifactComparison.js +34 -15
- package/dist/domain/binaryTarget.js +87 -37
- package/dist/domain/boundedCartesianProjection.js +15 -0
- package/dist/domain/browserObservation.js +1 -285
- package/dist/domain/browserObservationSchemas.js +286 -0
- package/dist/domain/electronStaticAnalysisBrowser.js +14 -2
- package/dist/domain/errors.js +4 -486
- package/dist/domain/functionComparison.js +4 -165
- package/dist/domain/functionComparisonDimensions.js +164 -0
- package/dist/domain/javascriptApplicationVersionItems.js +18 -6
- package/dist/domain/javascriptApplicationVersionKeys.js +8 -8
- package/dist/domain/javascriptExportInstrumentation.js +97 -0
- package/dist/domain/javascriptFeatureTraversal.js +19 -7
- package/dist/domain/javascriptRuntimeStaticCandidates.js +3 -1
- package/dist/domain/javascriptSemanticAnalysis.js +386 -0
- package/dist/domain/javascriptSemanticIr.js +30 -0
- package/dist/domain/javascriptSemanticProjection.js +366 -0
- package/dist/domain/javascriptSemanticState.js +40 -0
- package/dist/domain/javascriptSemanticTraversal.js +22 -0
- package/dist/domain/javascriptSemanticValues.js +352 -0
- package/dist/domain/javascriptStaticAnalysis.js +5 -221
- package/dist/domain/javascriptStaticAnalysisBundler.js +209 -0
- package/dist/domain/javascriptStaticAnalysisCalls.js +180 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +71 -2
- package/dist/domain/knownPageCoverage.js +21 -0
- package/dist/domain/managedApplicationGraph.js +14 -364
- package/dist/domain/managedApplicationGraphCoverage.js +170 -0
- package/dist/domain/managedApplicationGraphNodeCoverage.js +6 -0
- package/dist/domain/managedApplicationGraphNodes.js +336 -0
- package/dist/domain/managedArtifact.js +16 -8
- package/dist/domain/managedMemberComparison.js +37 -391
- package/dist/domain/managedMemberComparisonCoverage.js +72 -0
- package/dist/domain/managedMemberComparisonMatch.js +356 -0
- package/dist/domain/managedNativeVerification.js +27 -389
- package/dist/domain/managedNativeVerificationMatch.js +272 -0
- package/dist/domain/managedNativeVerificationSchemas.js +141 -0
- package/dist/domain/managedReconstruction.js +82 -69
- package/dist/domain/managedRuntimeCorrelation.js +12 -3
- package/dist/domain/nodeRuntimeCharacterization.js +66 -0
- package/dist/domain/permissionPolicy.js +2 -0
- package/dist/domain/processComparison.js +19 -0
- package/dist/domain/processPairedExperiment.js +128 -0
- package/dist/domain/reconstructionCoverage.js +387 -0
- package/dist/domain/reconstructionCoverageEvaluation.js +212 -0
- package/dist/domain/reconstructionVerificationSchemas.js +8 -0
- package/dist/domain/replayMachine.js +316 -0
- package/dist/domain/replayMachineRuntime.js +215 -0
- package/dist/domain/runtimeCharacterization.js +85 -0
- package/dist/domain/runtimeIdentification.js +210 -0
- package/dist/domain/webBundleAnalyzer.js +3 -409
- package/dist/domain/webBundleAnalyzerAst.js +77 -0
- package/dist/domain/webBundleAnalyzerInspection.js +307 -0
- package/dist/domain/webBundleAnalyzerResult.js +120 -0
- package/dist/dotnet/ManagedArtifactInspector.js +50 -29
- package/dist/dotnet/ManagedMemberInspector.js +51 -785
- package/dist/dotnet/ManagedMemberInspectorCore.js +297 -0
- package/dist/dotnet/ManagedMemberInstructionDecoder.js +427 -0
- package/dist/dotnet/ManagedMemberRows.js +120 -0
- package/dist/dotnet/ManagedMetadataInventory.js +42 -306
- package/dist/dotnet/ManagedMetadataInventoryRows.js +292 -0
- package/dist/dotnet/ManagedMetadataLayout.js +48 -111
- package/dist/dotnet/ManagedMetadataRowSizes.js +54 -0
- package/dist/dotnet/ManagedMethodBodyReader.js +143 -0
- package/dist/dotnet/ManagedNativeBoundaryHelpers.js +311 -0
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +102 -350
- package/dist/dotnet/ManagedPeReader.js +59 -28
- package/dist/evaluation/CodexAgentEval.js +132 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraClient.js +76 -91
- package/dist/ghidra/GhidraClientStartup.js +29 -0
- package/dist/ghidra/GhidraClientWire.js +34 -0
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraDiagnostics.js +6 -1
- package/dist/ghidra/GhidraInstallation.js +20 -15
- package/dist/ghidra/GhidraLauncher.js +93 -29
- package/dist/ghidra/GhidraProvider.js +61 -161
- package/dist/ghidra/GhidraProviderCapabilities.js +173 -0
- package/dist/ghidra/GhidraSessionValues.js +2 -0
- package/dist/ghidra/GhidraSocketConnection.js +44 -7
- package/dist/ghidra/GhidraTargetSnapshot.js +30 -0
- package/dist/ghidra/GhidraTransport.js +71 -0
- package/dist/identity.js +4 -2
- package/dist/main/messages.js +5 -0
- package/dist/main/reload.js +49 -0
- package/dist/main/shutdown.js +25 -0
- package/dist/main/startup.js +14 -0
- package/dist/main/state.js +26 -0
- package/dist/main/transport.js +61 -0
- package/dist/main/types.js +1 -0
- package/dist/main.js +35 -146
- package/dist/process/PrivateRuntimeRoot.js +3 -2
- package/dist/process/ProcessOwnership.js +35 -0
- package/dist/process/ProviderProcess.js +2 -0
- package/dist/replay/JavaScriptReplayWorker.js +64 -50
- package/dist/replay/LinuxJavaScriptReplayRunner.js +173 -390
- package/dist/replay/LinuxJavaScriptReplaySandbox.js +140 -0
- package/dist/replay/ReplayOutcome.js +95 -0
- package/dist/replay/ReplayProcessLifecycle.js +71 -0
- package/dist/server/ProcessCaptureElicitation.js +157 -0
- package/dist/server/createServer.js +124 -56
- package/dist/server/javascriptApplicationResult.js +85 -0
- package/dist/server/registerApplicationTools/characterization.js +57 -0
- package/dist/server/registerApplicationTools/compareVersions.js +34 -0
- package/dist/server/registerApplicationTools/controlledReplay.js +38 -0
- package/dist/server/registerApplicationTools/coverage.js +84 -0
- package/dist/server/registerApplicationTools/helpers.js +42 -0
- package/dist/server/registerApplicationTools/traceFeature.js +26 -0
- package/dist/server/registerApplicationTools/types.js +1 -0
- package/dist/server/registerApplicationTools.js +10 -112
- package/dist/server/registerBrowserTools.js +59 -83
- package/dist/server/registerElectronTools.js +48 -49
- package/dist/server/registerEnhancedTools.js +34 -42
- package/dist/server/registerEvidenceResources.js +56 -0
- package/dist/server/registerInvestigationTools/helpers.js +74 -0
- package/dist/server/registerInvestigationTools/types.js +1 -0
- package/dist/server/registerInvestigationTools.js +83 -116
- package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
- package/dist/server/registerManagedTools.js +18 -1
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +65 -0
- package/dist/server/registerManagedWorkflowTools/contract.js +8 -0
- package/dist/server/registerManagedWorkflowTools/evidence.js +43 -0
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +39 -0
- package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +39 -0
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +52 -0
- package/dist/server/registerManagedWorkflowTools/types.js +1 -0
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +75 -0
- package/dist/server/registerManagedWorkflowTools.js +10 -274
- package/dist/server/registerOfficialTools.js +73 -59
- package/dist/server/registerSessionRecordTools.js +137 -0
- package/dist/server/registerSessionStatusTool.js +97 -26
- package/dist/server/registerSessionTools.js +60 -164
- package/dist/server/sessionAvailabilityPolicy.js +10 -0
- package/dist/server/toolRegistrationOptions.js +101 -3
- package/dist/server/toolResult.js +15 -4
- package/package.json +11 -11
- package/skills/reverse-engineer-anything/SKILL.md +76 -0
- package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +25 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
- package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
- package/scripts/prepare-node-pty.mjs +0 -35
- package/scripts/rebuild-native.mjs +0 -22
- package/skills/rea-analysis/SKILL.md +0 -222
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { mkdir } from "node:fs/promises";
|
|
2
|
-
import { basename, dirname, join } from "node:path";
|
|
2
|
+
import { basename, dirname, join, win32 } from "node:path";
|
|
3
3
|
import writeFileAtomic from "write-file-atomic";
|
|
4
4
|
import { AnalysisCancelledError } from "../domain/errors.js";
|
|
5
5
|
import { err, ok } from "../domain/result.js";
|
|
6
|
-
import { cleanupOwnedProcessGroup } from "../process/ProcessOwnership.js";
|
|
6
|
+
import { cleanupOwnedProcessGroup, cleanupWindowsProcessTree, } from "../process/ProcessOwnership.js";
|
|
7
7
|
import { spawnOwnedProviderProcess, } from "../process/ProviderProcess.js";
|
|
8
8
|
import { GHIDRA_ANALYSIS_TIMEOUT_SECONDS, GHIDRA_MAX_CPU, GHIDRA_MAX_HEAP, } from "./GhidraDefaults.js";
|
|
9
9
|
import { ghidraJavaEnvironment } from "./GhidraInstallation.js";
|
|
@@ -24,6 +24,7 @@ export class GhidraHeadlessLauncher {
|
|
|
24
24
|
if (isAborted(options.signal))
|
|
25
25
|
return err(new AnalysisCancelledError("open_binary"));
|
|
26
26
|
const paths = ghidraRuntimePaths(session.runtimeRoot);
|
|
27
|
+
const platform = this.options.platform ?? process.platform;
|
|
27
28
|
let started;
|
|
28
29
|
try {
|
|
29
30
|
await Promise.all([
|
|
@@ -35,30 +36,42 @@ export class GhidraHeadlessLauncher {
|
|
|
35
36
|
paths.dataRoot,
|
|
36
37
|
].map((path) => mkdir(path, { recursive: true, mode: 0o700 })));
|
|
37
38
|
await writeFileAtomic(paths.descriptorPath, `${JSON.stringify({
|
|
38
|
-
schema_version:
|
|
39
|
-
|
|
39
|
+
schema_version: 2,
|
|
40
|
+
transport: session.transport,
|
|
41
|
+
endpoint_path: session.endpointPath,
|
|
40
42
|
token: session.token,
|
|
41
43
|
run_id: session.runId,
|
|
44
|
+
target_sha256: session.targetSha256,
|
|
42
45
|
provider_version: session.providerVersion,
|
|
43
46
|
profile_digest: session.profileDigest,
|
|
44
47
|
})}\n`, { encoding: "utf8", mode: 0o600 });
|
|
45
48
|
if (isAborted(options.signal))
|
|
46
49
|
return err(new AnalysisCancelledError("open_binary"));
|
|
50
|
+
const headlessArguments = ghidraHeadlessArguments({
|
|
51
|
+
projectRoot: paths.projectRoot,
|
|
52
|
+
targetPath: session.targetPath,
|
|
53
|
+
bridgeScriptPath: this.options.bridgeScriptPath,
|
|
54
|
+
descriptorPath: paths.descriptorPath,
|
|
55
|
+
ghidraLogPath: paths.ghidraLogPath,
|
|
56
|
+
scriptLogPath: paths.scriptLogPath,
|
|
57
|
+
});
|
|
58
|
+
const command = ghidraHeadlessCommand({
|
|
59
|
+
platform,
|
|
60
|
+
analyzeHeadlessPath: this.options.analyzeHeadlessPath,
|
|
61
|
+
arguments: headlessArguments,
|
|
62
|
+
...(this.options.comSpec === undefined
|
|
63
|
+
? {}
|
|
64
|
+
: { comSpec: this.options.comSpec }),
|
|
65
|
+
});
|
|
47
66
|
started = await spawnOwnedProviderProcess({
|
|
48
|
-
command:
|
|
49
|
-
arguments:
|
|
50
|
-
projectRoot: paths.projectRoot,
|
|
51
|
-
targetPath: session.targetPath,
|
|
52
|
-
bridgeScriptPath: this.options.bridgeScriptPath,
|
|
53
|
-
descriptorPath: paths.descriptorPath,
|
|
54
|
-
ghidraLogPath: paths.ghidraLogPath,
|
|
55
|
-
scriptLogPath: paths.scriptLogPath,
|
|
56
|
-
}),
|
|
67
|
+
command: command.command,
|
|
68
|
+
arguments: command.arguments,
|
|
57
69
|
runId: session.runId,
|
|
58
70
|
// analyzeHeadless is an interpreter-driven script. Parent identity and
|
|
59
71
|
// the per-process run token remain the cleanup authority.
|
|
60
72
|
expectedCommand: null,
|
|
61
|
-
|
|
73
|
+
windowsVerbatimArguments: platform === "win32",
|
|
74
|
+
env: ghidraLaunchEnvironment(paths, this.options.javaHome, platform),
|
|
62
75
|
});
|
|
63
76
|
await writeFileAtomic(paths.ownershipPath, `${JSON.stringify({
|
|
64
77
|
schema_version: 1,
|
|
@@ -66,18 +79,21 @@ export class GhidraHeadlessLauncher {
|
|
|
66
79
|
pid: started.ownership.leaderPid,
|
|
67
80
|
process_group_id: started.ownership.processGroupId,
|
|
68
81
|
parent_pid: process.pid,
|
|
82
|
+
ownership_kind: platform === "win32"
|
|
83
|
+
? "windows-process-tree-p0"
|
|
84
|
+
: "posix-process-group",
|
|
69
85
|
launcher: this.options.analyzeHeadlessPath,
|
|
70
86
|
created_at: new Date().toISOString(),
|
|
71
87
|
})}\n`, { encoding: "utf8", mode: 0o600 });
|
|
72
88
|
if (isAborted(options.signal)) {
|
|
73
|
-
await
|
|
89
|
+
await cleanupStartedProcess(started, platform);
|
|
74
90
|
return err(new AnalysisCancelledError("open_binary"));
|
|
75
91
|
}
|
|
76
|
-
const
|
|
92
|
+
const spawned = started;
|
|
77
93
|
return ok({
|
|
78
|
-
process:
|
|
94
|
+
process: spawned.process,
|
|
79
95
|
ownsProcessLifetime: true,
|
|
80
|
-
cleanup: () =>
|
|
96
|
+
cleanup: () => cleanupStartedProcess(spawned, platform),
|
|
81
97
|
projectRoot: paths.projectRoot,
|
|
82
98
|
ghidraLogPath: paths.ghidraLogPath,
|
|
83
99
|
scriptLogPath: paths.scriptLogPath,
|
|
@@ -85,13 +101,49 @@ export class GhidraHeadlessLauncher {
|
|
|
85
101
|
}
|
|
86
102
|
catch (cause) {
|
|
87
103
|
if (started !== undefined)
|
|
88
|
-
await
|
|
104
|
+
await cleanupStartedProcess(started, platform).catch(() => undefined);
|
|
89
105
|
return isAborted(options.signal)
|
|
90
106
|
? err(new AnalysisCancelledError("open_binary"))
|
|
91
107
|
: err(new GhidraLaunchError("Ghidra headless launch failed", { cause }));
|
|
92
108
|
}
|
|
93
109
|
}
|
|
94
110
|
}
|
|
111
|
+
/** Build a direct POSIX launch or a conservatively quoted Windows batch call. */
|
|
112
|
+
export const ghidraHeadlessCommand = (options) => {
|
|
113
|
+
if (options.platform !== "win32")
|
|
114
|
+
return {
|
|
115
|
+
command: options.analyzeHeadlessPath,
|
|
116
|
+
arguments: [...options.arguments],
|
|
117
|
+
};
|
|
118
|
+
const comSpec = options.comSpec ??
|
|
119
|
+
process.env.ComSpec ??
|
|
120
|
+
(process.env.SystemRoot === undefined
|
|
121
|
+
? "C:\\Windows\\System32\\cmd.exe"
|
|
122
|
+
: win32.join(process.env.SystemRoot, "System32", "cmd.exe"));
|
|
123
|
+
if (!win32.isAbsolute(comSpec) ||
|
|
124
|
+
win32.basename(comSpec).toLowerCase() !== "cmd.exe")
|
|
125
|
+
throw new GhidraLaunchError("Windows ComSpec must be an absolute cmd.exe path");
|
|
126
|
+
const tokens = [options.analyzeHeadlessPath, ...options.arguments].map(quoteWindowsBatchToken);
|
|
127
|
+
const invocation = `"${tokens.join(" ")}"`;
|
|
128
|
+
if (invocation.length > 30_000)
|
|
129
|
+
throw new GhidraLaunchError("Windows Ghidra command exceeds the P0 length limit");
|
|
130
|
+
return {
|
|
131
|
+
command: comSpec,
|
|
132
|
+
arguments: ["/d", "/e:on", "/v:off", "/s", "/c", invocation],
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
const quoteWindowsBatchToken = (value) => {
|
|
136
|
+
if (value.length === 0 ||
|
|
137
|
+
value.includes("\0") ||
|
|
138
|
+
value.includes("\r") ||
|
|
139
|
+
value.includes("\n") ||
|
|
140
|
+
/["%^!&|<>]/u.test(value))
|
|
141
|
+
throw new GhidraLaunchError("Windows Ghidra P0 paths cannot contain command-interpreter metacharacters");
|
|
142
|
+
return `"${value}"`;
|
|
143
|
+
};
|
|
144
|
+
const cleanupStartedProcess = (started, platform) => platform === "win32"
|
|
145
|
+
? cleanupWindowsProcessTree(started.ownership.leaderPid)
|
|
146
|
+
: cleanupOwnedProcessGroup(started.ownership);
|
|
95
147
|
/** Build the complete read-only headless invocation in deterministic order. */
|
|
96
148
|
export const ghidraHeadlessArguments = (options) => [
|
|
97
149
|
options.projectRoot,
|
|
@@ -126,14 +178,26 @@ const ghidraRuntimePaths = (runtimeRoot) => ({
|
|
|
126
178
|
ghidraLogPath: join(runtimeRoot, "ghidra.log"),
|
|
127
179
|
scriptLogPath: join(runtimeRoot, "script.log"),
|
|
128
180
|
});
|
|
129
|
-
const ghidraLaunchEnvironment = (paths, javaHome) =>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
181
|
+
const ghidraLaunchEnvironment = (paths, javaHome, platform) => {
|
|
182
|
+
const javaOptions = platform === "win32"
|
|
183
|
+
? `"-Duser.home=${paths.homeRoot}" "-Djava.io.tmpdir=${paths.tempRoot}"`
|
|
184
|
+
: `-Duser.home=${paths.homeRoot} -Djava.io.tmpdir=${paths.tempRoot}`;
|
|
185
|
+
return {
|
|
186
|
+
...ghidraJavaEnvironment(javaHome, process.env, platform),
|
|
187
|
+
HOME: paths.homeRoot,
|
|
188
|
+
TMPDIR: paths.tempRoot,
|
|
189
|
+
...(platform === "win32"
|
|
190
|
+
? {
|
|
191
|
+
USERPROFILE: paths.homeRoot,
|
|
192
|
+
TEMP: paths.tempRoot,
|
|
193
|
+
TMP: paths.tempRoot,
|
|
194
|
+
}
|
|
195
|
+
: {}),
|
|
196
|
+
XDG_CACHE_HOME: paths.cacheRoot,
|
|
197
|
+
XDG_CONFIG_HOME: paths.configRoot,
|
|
198
|
+
XDG_DATA_HOME: paths.dataRoot,
|
|
199
|
+
GHIDRA_HEADLESS_MAXMEM: GHIDRA_MAX_HEAP,
|
|
200
|
+
GHIDRA_HEADLESS_JAVA_OPTIONS: javaOptions,
|
|
201
|
+
};
|
|
202
|
+
};
|
|
139
203
|
const isAborted = (signal) => signal?.aborted === true;
|
|
@@ -2,169 +2,18 @@ import { fileURLToPath } from "node:url";
|
|
|
2
2
|
import { createAnalysisExecution, } from "../application/AnalysisProvider.js";
|
|
3
3
|
import { AnalysisCancelledError, AnalysisCapabilityUnavailableError, AnalysisInputError, AnalysisTimeoutError, ProviderAdapterError, } from "../domain/errors.js";
|
|
4
4
|
import { err, ok } from "../domain/result.js";
|
|
5
|
-
import { ENHANCED_TOOL_CONTRACTS, OFFICIAL_TOOL_CONTRACTS, } from "../contracts/toolContracts.js";
|
|
6
5
|
import { GhidraClient } from "./GhidraClient.js";
|
|
7
|
-
import { GHIDRA_ANALYSIS_TIMEOUT_SECONDS, GHIDRA_DECOMPILE_REQUEST_TIMEOUT_MS, GHIDRA_DECOMPILE_TIMEOUT_SECONDS,
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import { GHIDRA_ANALYSIS_TIMEOUT_SECONDS, GHIDRA_DECOMPILE_REQUEST_TIMEOUT_MS, GHIDRA_DECOMPILE_TIMEOUT_SECONDS, GHIDRA_STARTUP_TIMEOUT_MS, } from "./GhidraDefaults.js";
|
|
7
|
+
import { isGhidraFunctionOperation, parseGhidraFunctionInput, parseGhidraFunctionResult, } from "./GhidraFunctionValues.js";
|
|
8
|
+
import { isGhidraInventoryOperation, parseGhidraInventoryInput, parseGhidraInventoryResult, } from "./GhidraInventoryValues.js";
|
|
10
9
|
import { ghidraInstallationDiagnostics, inspectGhidraInstallation, } from "./GhidraInstallation.js";
|
|
11
10
|
import { GhidraHeadlessLauncher } from "./GhidraLauncher.js";
|
|
12
11
|
import { resolveGhidraAnalysisProfile } from "./GhidraAnalysisProfile.js";
|
|
13
|
-
|
|
14
|
-
export
|
|
15
|
-
id: "ghidra",
|
|
16
|
-
name: "Ghidra",
|
|
17
|
-
version: null,
|
|
18
|
-
});
|
|
19
|
-
const providerContractByName = new Map([...OFFICIAL_TOOL_CONTRACTS, ...ENHANCED_TOOL_CONTRACTS].map((contract) => [
|
|
20
|
-
contract.name,
|
|
21
|
-
contract,
|
|
22
|
-
]));
|
|
23
|
-
/** Provider-neutral read-only contracts implemented by the Ghidra adapter. */
|
|
24
|
-
export const GHIDRA_PROVIDER_TOOL_CONTRACTS = Object.freeze([...GHIDRA_INVENTORY_OPERATIONS, ...GHIDRA_FUNCTION_OPERATIONS].map((operation) => {
|
|
25
|
-
const contract = providerContractByName.get(operation);
|
|
26
|
-
if (contract === undefined)
|
|
27
|
-
throw new TypeError(`Missing provider-neutral contract for ${operation}`);
|
|
28
|
-
return contract;
|
|
29
|
-
}));
|
|
30
|
-
const PAGINATED_OPERATIONS = new Set([
|
|
31
|
-
"list_names",
|
|
32
|
-
"list_procedures",
|
|
33
|
-
"list_strings",
|
|
34
|
-
"search_procedures",
|
|
35
|
-
"search_strings",
|
|
36
|
-
"procedure_references",
|
|
37
|
-
"analyze_function",
|
|
38
|
-
]);
|
|
39
|
-
const SEARCH_OPERATIONS = new Set([
|
|
40
|
-
"search_procedures",
|
|
41
|
-
"search_strings",
|
|
42
|
-
]);
|
|
43
|
-
const DECOMPILE_OPERATIONS = new Set([
|
|
44
|
-
"procedure_pseudo_code",
|
|
45
|
-
"analyze_function",
|
|
46
|
-
]);
|
|
47
|
-
const healthLimitations = Object.freeze([
|
|
48
|
-
"The session serves operations only after default Ghidra auto-analysis completes; an analysis timeout fails the open instead of exposing partial results.",
|
|
49
|
-
"The imported Program and temporary project are ephemeral, read-only to REA, and deleted on close.",
|
|
50
|
-
]);
|
|
51
|
-
const limitationsFor = (operation) => {
|
|
52
|
-
const common = [
|
|
53
|
-
...healthLimitations,
|
|
54
|
-
"Default-space addresses use lowercase 0x-prefixed hexadecimal; other address spaces use <percent-encoded-space>:0x<hex>.",
|
|
55
|
-
];
|
|
56
|
-
switch (operation) {
|
|
57
|
-
case "list_documents":
|
|
58
|
-
return [
|
|
59
|
-
...common,
|
|
60
|
-
"A headless Ghidra session contains exactly one imported Program, unlike Hopper's multi-document GUI session.",
|
|
61
|
-
];
|
|
62
|
-
case "list_names":
|
|
63
|
-
return [
|
|
64
|
-
...common,
|
|
65
|
-
"The symbol inventory includes memory and external symbols, including dynamic symbols, but excludes variable and no-address namespace records.",
|
|
66
|
-
];
|
|
67
|
-
case "list_procedures":
|
|
68
|
-
case "procedure_address":
|
|
69
|
-
return [
|
|
70
|
-
...common,
|
|
71
|
-
"External functions and local thunks are distinct; procedure metadata identifies both and preserves a thunk target when Ghidra resolves one.",
|
|
72
|
-
];
|
|
73
|
-
case "list_strings":
|
|
74
|
-
return [
|
|
75
|
-
...common,
|
|
76
|
-
"Only Ghidra-defined string Data is observed; charset is reported, while a non-missing terminator cannot distinguish a present terminator from a fixed or Pascal layout.",
|
|
77
|
-
"Returned values are bounded to 1,024 Unicode code points and mark value_truncated instead of silently crossing the wire budget.",
|
|
78
|
-
];
|
|
79
|
-
case "list_segments":
|
|
80
|
-
return [
|
|
81
|
-
...common,
|
|
82
|
-
"Memory-block end addresses are exclusive; permissions come from Ghidra MemoryBlock flags rather than inference from section names.",
|
|
83
|
-
];
|
|
84
|
-
case "search_procedures":
|
|
85
|
-
case "search_strings":
|
|
86
|
-
return [
|
|
87
|
-
...common,
|
|
88
|
-
"Literal search enforces 1,000,000 cumulative work units; regex mode also accepts only a conservative finite Java-regex subset with 10,000 static paths and 4,096 UTF-16 code units per candidate.",
|
|
89
|
-
];
|
|
90
|
-
case "procedure_pseudo_code":
|
|
91
|
-
return [
|
|
92
|
-
...common,
|
|
93
|
-
"Pseudocode is Ghidra decompiler output, not original source and not text-equivalent to Hopper output; each decompile has a 30-second native deadline.",
|
|
94
|
-
"External functions and functions without an analyzable body return null; other decompiler failures remain explicit.",
|
|
95
|
-
];
|
|
96
|
-
case "procedure_assembly":
|
|
97
|
-
return [
|
|
98
|
-
...common,
|
|
99
|
-
"Assembly is Ghidra Listing text and fails rather than silently truncating when the 100,000-instruction or 1 MiB wire bound is exceeded.",
|
|
100
|
-
];
|
|
101
|
-
case "procedure_callers":
|
|
102
|
-
case "procedure_callees":
|
|
103
|
-
return [
|
|
104
|
-
...common,
|
|
105
|
-
"Only resolved Ghidra call references are returned; unresolved computed or indirect calls remain unknown, while function classifications distinguish thunks and externals.",
|
|
106
|
-
];
|
|
107
|
-
case "xrefs":
|
|
108
|
-
return [
|
|
109
|
-
...common,
|
|
110
|
-
"The direct address list projects exact Ghidra references to one address but does not expose their kinds; procedure_references and analyze_function preserve available kind metadata.",
|
|
111
|
-
"Synthetic Ghidra entry-point references without actionable memory sources are omitted.",
|
|
112
|
-
];
|
|
113
|
-
case "procedure_references":
|
|
114
|
-
return [
|
|
115
|
-
...common,
|
|
116
|
-
"Reference kinds are direct Ghidra ReferenceManager observations; unresolved computed flows without a target are absent and remain unknown.",
|
|
117
|
-
"Synthetic Ghidra entry-point references without actionable memory sources are omitted.",
|
|
118
|
-
"Instruction scans stop at max_instructions; a truncated scan reports an unknown total and no false continuation.",
|
|
119
|
-
];
|
|
120
|
-
case "analyze_function":
|
|
121
|
-
return [
|
|
122
|
-
...common,
|
|
123
|
-
"The dossier combines Ghidra FunctionManager, Listing, ReferenceManager, BasicBlockModel, and decompiler observations; provider-specific pseudocode and assembly are not cross-provider text invariants.",
|
|
124
|
-
"Resolved reference metadata identifies computed, indirect, external, call, jump, and data edges; unresolved targetless flows remain unknown, and function classifications distinguish thunks and externals.",
|
|
125
|
-
"Synthetic Ghidra entry-point references without actionable memory sources are omitted.",
|
|
126
|
-
"The Java bridge serializes one function request per Program through a bounded 32-request queue and applies a 30-second native decompilation deadline.",
|
|
127
|
-
];
|
|
128
|
-
default:
|
|
129
|
-
return common;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
const CAPABILITIES = Object.freeze(GHIDRA_PROVIDER_TOOL_CONTRACTS.map((contract) => Object.freeze({
|
|
133
|
-
provider: GHIDRA_PROVIDER_IDENTITY,
|
|
134
|
-
operation: contract.name,
|
|
135
|
-
inputContractVersion: 1,
|
|
136
|
-
outputContractVersion: 1,
|
|
137
|
-
available: true,
|
|
138
|
-
reason: null,
|
|
139
|
-
pagination: PAGINATED_OPERATIONS.has(contract.name)
|
|
140
|
-
? "offset"
|
|
141
|
-
: "none",
|
|
142
|
-
exhaustive: !PAGINATED_OPERATIONS.has(contract.name),
|
|
143
|
-
effects: Object.freeze({
|
|
144
|
-
mutatesArtifact: false,
|
|
145
|
-
launchesProcess: true,
|
|
146
|
-
mayShowUi: false,
|
|
147
|
-
mayAccessNetwork: false,
|
|
148
|
-
mayWriteFilesystem: true,
|
|
149
|
-
changesPermissions: false,
|
|
150
|
-
requiresRoot: false,
|
|
151
|
-
}),
|
|
152
|
-
limits: Object.freeze({
|
|
153
|
-
maxResults: PAGINATED_OPERATIONS.has(contract.name)
|
|
154
|
-
? SEARCH_OPERATIONS.has(contract.name)
|
|
155
|
-
? 100
|
|
156
|
-
: 500
|
|
157
|
-
: null,
|
|
158
|
-
maxPayloadBytes: GHIDRA_MAX_LINE_BYTES,
|
|
159
|
-
timeoutMs: DECOMPILE_OPERATIONS.has(contract.name)
|
|
160
|
-
? GHIDRA_DECOMPILE_REQUEST_TIMEOUT_MS
|
|
161
|
-
: GHIDRA_REQUEST_TIMEOUT_MS,
|
|
162
|
-
}),
|
|
163
|
-
limitations: Object.freeze(limitationsFor(contract.name)),
|
|
164
|
-
})));
|
|
12
|
+
import { CAPABILITIES, WINDOWS_P0_CAPABILITIES, GHIDRA_PROVIDER_IDENTITY, GHIDRA_PROVIDER_TOOL_CONTRACTS, healthLimitations, windowsP0Limitations, limitationsFor, } from "./GhidraProviderCapabilities.js";
|
|
13
|
+
export { GHIDRA_PROVIDER_IDENTITY, GHIDRA_PROVIDER_TOOL_CONTRACTS };
|
|
165
14
|
const SUPPORTED_ARCHITECTURES = new Set(["x86", "x86_64", "arm", "arm64"]);
|
|
166
15
|
const SUPPORTED_FORMATS = new Set(["elf", "pe", "mach-o"]);
|
|
167
|
-
/**
|
|
16
|
+
/** Ghidra candidate backed by an isolated read-only headless import. */
|
|
168
17
|
export class GhidraProvider {
|
|
169
18
|
config;
|
|
170
19
|
logger;
|
|
@@ -181,7 +30,9 @@ export class GhidraProvider {
|
|
|
181
30
|
return GHIDRA_PROVIDER_IDENTITY;
|
|
182
31
|
}
|
|
183
32
|
capabilities() {
|
|
184
|
-
return
|
|
33
|
+
return (this.installationHost?.platform ?? process.platform) === "win32"
|
|
34
|
+
? WINDOWS_P0_CAPABILITIES
|
|
35
|
+
: CAPABILITIES;
|
|
185
36
|
}
|
|
186
37
|
inspectAvailability() {
|
|
187
38
|
const installation = this.#inspectInstallation();
|
|
@@ -201,10 +52,14 @@ export class GhidraProvider {
|
|
|
201
52
|
};
|
|
202
53
|
}
|
|
203
54
|
inspectTargetSupport(target) {
|
|
55
|
+
const hostPlatform = this.installationHost?.platform ?? process.platform;
|
|
204
56
|
const diagnostics = {
|
|
57
|
+
host_platform: hostPlatform,
|
|
205
58
|
target_kind: target.kind,
|
|
206
59
|
target_format: target.format,
|
|
207
60
|
architecture: target.architecture ?? null,
|
|
61
|
+
executable_role: target.executableRole ?? null,
|
|
62
|
+
managed: target.managed ?? null,
|
|
208
63
|
};
|
|
209
64
|
if (target.kind !== "executable")
|
|
210
65
|
return {
|
|
@@ -213,6 +68,8 @@ export class GhidraProvider {
|
|
|
213
68
|
reason: `Ghidra v1 imports executable targets, not ${target.kind} targets.`,
|
|
214
69
|
diagnostics,
|
|
215
70
|
};
|
|
71
|
+
if (hostPlatform === "win32")
|
|
72
|
+
return inspectWindowsP0TargetSupport(target, diagnostics);
|
|
216
73
|
if (!SUPPORTED_FORMATS.has(target.format))
|
|
217
74
|
return {
|
|
218
75
|
status: "unsupported",
|
|
@@ -244,6 +101,7 @@ export class GhidraProvider {
|
|
|
244
101
|
if (!prerequisites.ok)
|
|
245
102
|
return unavailableClient(prerequisites.error);
|
|
246
103
|
const committedProfile = prerequisites.value.profile;
|
|
104
|
+
const providerLimitations = installation.platform === "win32" ? windowsP0Limitations : [];
|
|
247
105
|
const client = this.clientFactory({
|
|
248
106
|
launcher: new GhidraHeadlessLauncher({
|
|
249
107
|
analyzeHeadlessPath: prerequisites.value.analyzeHeadlessPath,
|
|
@@ -251,8 +109,13 @@ export class GhidraProvider {
|
|
|
251
109
|
? {}
|
|
252
110
|
: { javaHome: this.config.ghidraJavaHome }),
|
|
253
111
|
bridgeScriptPath: fileURLToPath(new URL("../../bridge/ghidra/ReaGhidraBridge.java", import.meta.url)),
|
|
112
|
+
platform: installation.platform,
|
|
254
113
|
}),
|
|
255
114
|
targetPath: target.path,
|
|
115
|
+
targetSha256: target.sha256,
|
|
116
|
+
transport: installation.platform === "win32"
|
|
117
|
+
? "authenticated-loopback-tcp"
|
|
118
|
+
: "unix-socket",
|
|
256
119
|
providerVersion: prerequisites.value.providerVersion,
|
|
257
120
|
profileDigest: committedProfile.digest,
|
|
258
121
|
logger: this.logger.child({ layer: "ghidra-bridge" }),
|
|
@@ -269,7 +132,7 @@ export class GhidraProvider {
|
|
|
269
132
|
return err(projectSessionError(operation, started.error));
|
|
270
133
|
return ok(createAnalysisExecution(started.value, committedProfile.provider, {
|
|
271
134
|
analysisProfile: committedProfile,
|
|
272
|
-
limitations: healthLimitations,
|
|
135
|
+
limitations: [...healthLimitations, ...providerLimitations],
|
|
273
136
|
}));
|
|
274
137
|
}
|
|
275
138
|
const input = isGhidraFunctionOperation(operation)
|
|
@@ -279,7 +142,8 @@ export class GhidraProvider {
|
|
|
279
142
|
return input;
|
|
280
143
|
const called = await client.callTool(operation, input.value, {
|
|
281
144
|
...(options?.signal === undefined ? {} : { signal: options.signal }),
|
|
282
|
-
...(
|
|
145
|
+
...(operation === "procedure_pseudo_code" ||
|
|
146
|
+
operation === "analyze_function"
|
|
283
147
|
? { timeoutMs: GHIDRA_DECOMPILE_REQUEST_TIMEOUT_MS }
|
|
284
148
|
: {}),
|
|
285
149
|
});
|
|
@@ -293,7 +157,7 @@ export class GhidraProvider {
|
|
|
293
157
|
return ok(createAnalysisExecution(result.value, committedProfile.provider, {
|
|
294
158
|
rawResult: called.value,
|
|
295
159
|
analysisProfile: committedProfile,
|
|
296
|
-
limitations: limitationsFor(operation),
|
|
160
|
+
limitations: [...limitationsFor(operation), ...providerLimitations],
|
|
297
161
|
}));
|
|
298
162
|
},
|
|
299
163
|
close: () => client.close(),
|
|
@@ -321,6 +185,42 @@ export class GhidraProvider {
|
|
|
321
185
|
return this.#installation;
|
|
322
186
|
}
|
|
323
187
|
}
|
|
188
|
+
const inspectWindowsP0TargetSupport = (target, diagnostics) => {
|
|
189
|
+
if (target.format !== "pe")
|
|
190
|
+
return {
|
|
191
|
+
status: "unsupported",
|
|
192
|
+
code: "target_format_unsupported",
|
|
193
|
+
reason: "Windows Ghidra P0 accepts PE targets only.",
|
|
194
|
+
diagnostics,
|
|
195
|
+
};
|
|
196
|
+
if (target.architecture !== "x86_64")
|
|
197
|
+
return {
|
|
198
|
+
status: "unsupported",
|
|
199
|
+
code: "architecture_unsupported",
|
|
200
|
+
reason: "Windows Ghidra P0 accepts x86-64 PE targets only.",
|
|
201
|
+
diagnostics,
|
|
202
|
+
};
|
|
203
|
+
if (target.executableRole !== "application")
|
|
204
|
+
return {
|
|
205
|
+
status: "unsupported",
|
|
206
|
+
code: "target_role_unsupported",
|
|
207
|
+
reason: "Windows Ghidra P0 accepts PE applications, not DLL or non-executable images.",
|
|
208
|
+
diagnostics,
|
|
209
|
+
};
|
|
210
|
+
if (target.managed !== false)
|
|
211
|
+
return {
|
|
212
|
+
status: "unsupported",
|
|
213
|
+
code: "managed_target_unsupported",
|
|
214
|
+
reason: "Windows Ghidra P0 accepts native PE applications; managed or unclassified PE targets are unsupported.",
|
|
215
|
+
diagnostics,
|
|
216
|
+
};
|
|
217
|
+
return {
|
|
218
|
+
status: "supported",
|
|
219
|
+
code: null,
|
|
220
|
+
reason: null,
|
|
221
|
+
diagnostics,
|
|
222
|
+
};
|
|
223
|
+
};
|
|
324
224
|
const ghidraClientPrerequisites = (target, profile, installation) => {
|
|
325
225
|
if (target.kind !== "executable")
|
|
326
226
|
return err(new AnalysisCapabilityUnavailableError("ghidra", "health", `Ghidra cannot import ${target.kind} targets through this adapter.`));
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { ENHANCED_TOOL_CONTRACTS, OFFICIAL_TOOL_CONTRACTS, } from "../contracts/toolContracts.js";
|
|
2
|
+
import { GHIDRA_DECOMPILE_REQUEST_TIMEOUT_MS, GHIDRA_MAX_LINE_BYTES, GHIDRA_REQUEST_TIMEOUT_MS, } from "./GhidraDefaults.js";
|
|
3
|
+
import { GHIDRA_FUNCTION_OPERATIONS } from "./GhidraFunctionValues.js";
|
|
4
|
+
import { GHIDRA_INVENTORY_OPERATIONS } from "./GhidraInventoryValues.js";
|
|
5
|
+
/** Public identity committed by every Ghidra-backed observation. */
|
|
6
|
+
export const GHIDRA_PROVIDER_IDENTITY = Object.freeze({
|
|
7
|
+
id: "ghidra",
|
|
8
|
+
name: "Ghidra",
|
|
9
|
+
version: null,
|
|
10
|
+
});
|
|
11
|
+
const providerContractByName = new Map([...OFFICIAL_TOOL_CONTRACTS, ...ENHANCED_TOOL_CONTRACTS].map((contract) => [
|
|
12
|
+
contract.name,
|
|
13
|
+
contract,
|
|
14
|
+
]));
|
|
15
|
+
/** Provider-neutral read-only contracts implemented by the Ghidra adapter. */
|
|
16
|
+
export const GHIDRA_PROVIDER_TOOL_CONTRACTS = Object.freeze([...GHIDRA_INVENTORY_OPERATIONS, ...GHIDRA_FUNCTION_OPERATIONS].map((operation) => {
|
|
17
|
+
const contract = providerContractByName.get(operation);
|
|
18
|
+
if (contract === undefined)
|
|
19
|
+
throw new TypeError(`Missing provider-neutral contract for ${operation}`);
|
|
20
|
+
return contract;
|
|
21
|
+
}));
|
|
22
|
+
const PAGINATED_OPERATIONS = new Set([
|
|
23
|
+
"list_names",
|
|
24
|
+
"list_procedures",
|
|
25
|
+
"list_strings",
|
|
26
|
+
"search_procedures",
|
|
27
|
+
"search_strings",
|
|
28
|
+
"procedure_references",
|
|
29
|
+
"analyze_function",
|
|
30
|
+
]);
|
|
31
|
+
const SEARCH_OPERATIONS = new Set([
|
|
32
|
+
"search_procedures",
|
|
33
|
+
"search_strings",
|
|
34
|
+
]);
|
|
35
|
+
const DECOMPILE_OPERATIONS = new Set([
|
|
36
|
+
"procedure_pseudo_code",
|
|
37
|
+
"analyze_function",
|
|
38
|
+
]);
|
|
39
|
+
/** Health limitations shared by every Ghidra-backed capability. */
|
|
40
|
+
export const healthLimitations = Object.freeze([
|
|
41
|
+
"The session serves operations only after default Ghidra auto-analysis completes; an analysis timeout fails the open instead of exposing partial results.",
|
|
42
|
+
"The imported Program and temporary project are ephemeral, read-only to REA, and deleted on close.",
|
|
43
|
+
]);
|
|
44
|
+
/** Additional limitations applied to the experimental Windows x64 P0 boundary. */
|
|
45
|
+
export const windowsP0Limitations = Object.freeze([
|
|
46
|
+
"Windows Ghidra P0 accepts approved native x86-64 PE applications only; DLL, managed, hostile, sensitive, and mutable-path targets are unsupported.",
|
|
47
|
+
"The Windows bridge uses authenticated IPv4 loopback because Node path-based IPC does not expose Java AF_UNIX sockets; the endpoint file contains no bearer token.",
|
|
48
|
+
"Windows P0 process-tree cleanup uses bounded taskkill termination and does not claim Job Object ownership, private DACL enforcement, or reparse-point-safe path authority.",
|
|
49
|
+
]);
|
|
50
|
+
/** Limitation text for one admitted operation, including the common base. */
|
|
51
|
+
export const limitationsFor = (operation) => {
|
|
52
|
+
const common = [
|
|
53
|
+
...healthLimitations,
|
|
54
|
+
"Default-space addresses use lowercase 0x-prefixed hexadecimal; other address spaces use <percent-encoded-space>:0x<hex>.",
|
|
55
|
+
];
|
|
56
|
+
switch (operation) {
|
|
57
|
+
case "list_documents":
|
|
58
|
+
return [
|
|
59
|
+
...common,
|
|
60
|
+
"A headless Ghidra session contains exactly one imported Program, unlike Hopper's multi-document GUI session.",
|
|
61
|
+
];
|
|
62
|
+
case "list_names":
|
|
63
|
+
return [
|
|
64
|
+
...common,
|
|
65
|
+
"The symbol inventory includes memory and external symbols, including dynamic symbols, but excludes variable and no-address namespace records.",
|
|
66
|
+
];
|
|
67
|
+
case "list_procedures":
|
|
68
|
+
case "procedure_address":
|
|
69
|
+
return [
|
|
70
|
+
...common,
|
|
71
|
+
"External functions and local thunks are distinct; procedure metadata identifies both and preserves a thunk target when Ghidra resolves one.",
|
|
72
|
+
];
|
|
73
|
+
case "list_strings":
|
|
74
|
+
return [
|
|
75
|
+
...common,
|
|
76
|
+
"Only Ghidra-defined string Data is observed; charset is reported, while a non-missing terminator cannot distinguish a present terminator from a fixed or Pascal layout.",
|
|
77
|
+
"Returned values are bounded to 1,024 Unicode code points and mark value_truncated instead of silently crossing the wire budget.",
|
|
78
|
+
];
|
|
79
|
+
case "list_segments":
|
|
80
|
+
return [
|
|
81
|
+
...common,
|
|
82
|
+
"Memory-block end addresses are exclusive; permissions come from Ghidra MemoryBlock flags rather than inference from section names.",
|
|
83
|
+
];
|
|
84
|
+
case "search_procedures":
|
|
85
|
+
case "search_strings":
|
|
86
|
+
return [
|
|
87
|
+
...common,
|
|
88
|
+
"Literal search enforces 1,000,000 cumulative work units; regex mode also accepts only a conservative finite Java-regex subset with 10,000 static paths and 4,096 UTF-16 code units per candidate.",
|
|
89
|
+
];
|
|
90
|
+
case "procedure_pseudo_code":
|
|
91
|
+
return [
|
|
92
|
+
...common,
|
|
93
|
+
"Pseudocode is Ghidra decompiler output, not original source and not text-equivalent to Hopper output; each decompile has a 30-second native deadline.",
|
|
94
|
+
"External functions and functions without an analyzable body return null; other decompiler failures remain explicit.",
|
|
95
|
+
];
|
|
96
|
+
case "procedure_assembly":
|
|
97
|
+
return [
|
|
98
|
+
...common,
|
|
99
|
+
"Assembly is Ghidra Listing text and fails rather than silently truncating when the 100,000-instruction or 1 MiB wire bound is exceeded.",
|
|
100
|
+
];
|
|
101
|
+
case "procedure_callers":
|
|
102
|
+
case "procedure_callees":
|
|
103
|
+
return [
|
|
104
|
+
...common,
|
|
105
|
+
"Only resolved Ghidra call references are returned; unresolved computed or indirect calls remain unknown, while function classifications distinguish thunks and externals.",
|
|
106
|
+
];
|
|
107
|
+
case "xrefs":
|
|
108
|
+
return [
|
|
109
|
+
...common,
|
|
110
|
+
"The direct address list projects exact Ghidra references to one address but does not expose their kinds; procedure_references and analyze_function preserve available kind metadata.",
|
|
111
|
+
"Synthetic Ghidra entry-point references without actionable memory sources are omitted.",
|
|
112
|
+
];
|
|
113
|
+
case "procedure_references":
|
|
114
|
+
return [
|
|
115
|
+
...common,
|
|
116
|
+
"Reference kinds are direct Ghidra ReferenceManager observations; unresolved computed flows without a target are absent and remain unknown.",
|
|
117
|
+
"Synthetic Ghidra entry-point references without actionable memory sources are omitted.",
|
|
118
|
+
"Instruction scans stop at max_instructions; a truncated scan reports an unknown total and no false continuation.",
|
|
119
|
+
];
|
|
120
|
+
case "analyze_function":
|
|
121
|
+
return [
|
|
122
|
+
...common,
|
|
123
|
+
"The dossier combines Ghidra FunctionManager, Listing, ReferenceManager, BasicBlockModel, and decompiler observations; provider-specific pseudocode and assembly are not cross-provider text invariants.",
|
|
124
|
+
"Resolved reference metadata identifies computed, indirect, external, call, jump, and data edges; unresolved targetless flows remain unknown, and function classifications distinguish thunks and externals.",
|
|
125
|
+
"Synthetic Ghidra entry-point references without actionable memory sources are omitted.",
|
|
126
|
+
"The Java bridge serializes one function request per Program through a bounded 32-request queue and applies a 30-second native decompilation deadline.",
|
|
127
|
+
];
|
|
128
|
+
default:
|
|
129
|
+
return common;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
/** Provider-neutral capabilities advertised by every non-Windows Ghidra session. */
|
|
133
|
+
export const CAPABILITIES = Object.freeze(GHIDRA_PROVIDER_TOOL_CONTRACTS.map((contract) => Object.freeze({
|
|
134
|
+
provider: GHIDRA_PROVIDER_IDENTITY,
|
|
135
|
+
operation: contract.name,
|
|
136
|
+
inputContractVersion: 1,
|
|
137
|
+
outputContractVersion: 1,
|
|
138
|
+
available: true,
|
|
139
|
+
reason: null,
|
|
140
|
+
pagination: PAGINATED_OPERATIONS.has(contract.name)
|
|
141
|
+
? "offset"
|
|
142
|
+
: "none",
|
|
143
|
+
exhaustive: !PAGINATED_OPERATIONS.has(contract.name),
|
|
144
|
+
effects: Object.freeze({
|
|
145
|
+
mutatesArtifact: false,
|
|
146
|
+
launchesProcess: true,
|
|
147
|
+
mayShowUi: false,
|
|
148
|
+
mayAccessNetwork: false,
|
|
149
|
+
mayWriteFilesystem: true,
|
|
150
|
+
changesPermissions: false,
|
|
151
|
+
requiresRoot: false,
|
|
152
|
+
}),
|
|
153
|
+
limits: Object.freeze({
|
|
154
|
+
maxResults: PAGINATED_OPERATIONS.has(contract.name)
|
|
155
|
+
? SEARCH_OPERATIONS.has(contract.name)
|
|
156
|
+
? 100
|
|
157
|
+
: 500
|
|
158
|
+
: null,
|
|
159
|
+
maxPayloadBytes: GHIDRA_MAX_LINE_BYTES,
|
|
160
|
+
timeoutMs: DECOMPILE_OPERATIONS.has(contract.name)
|
|
161
|
+
? GHIDRA_DECOMPILE_REQUEST_TIMEOUT_MS
|
|
162
|
+
: GHIDRA_REQUEST_TIMEOUT_MS,
|
|
163
|
+
}),
|
|
164
|
+
limitations: Object.freeze(limitationsFor(contract.name)),
|
|
165
|
+
})));
|
|
166
|
+
/** Capabilities advertised for the experimental Windows x64 P0 boundary. */
|
|
167
|
+
export const WINDOWS_P0_CAPABILITIES = Object.freeze(CAPABILITIES.map((capability) => Object.freeze({
|
|
168
|
+
...capability,
|
|
169
|
+
limitations: Object.freeze([
|
|
170
|
+
...capability.limitations,
|
|
171
|
+
...windowsP0Limitations,
|
|
172
|
+
]),
|
|
173
|
+
})));
|