rea-agents 6.0.0 → 6.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/README.md +23 -17
- package/bridge/android/ReaJadxBridge.java +14 -5
- package/bridge/ghidra/ReaGhidraBridge.java +173 -47
- package/bridge/hopper_bridge.py +310 -128
- package/bridge/mitmproxy/capture.py +0 -2
- package/bridge/native/rea_lldb_tracer.py +630 -94
- package/bridge/process/ProcessRunTokenReader.swift +39 -5
- package/bridge/pwntools/recorded_crash_impl.py +4 -0
- package/dist/android/JadxConfiguration.js +196 -33
- package/dist/android/JadxMcpTransport.js +4 -0
- package/dist/android/JadxProtocol.js +6 -0
- package/dist/android/JadxProvider.js +19 -2
- package/dist/android/JadxSession.js +23 -4
- package/dist/application/AnalysisContextQueries.js +8 -35
- package/dist/application/AnalysisDocument.js +32 -0
- package/dist/application/AppBundleExecutable.js +67 -21
- package/dist/application/CallPathTracing.js +19 -3
- package/dist/application/CapabilityInventory.js +6 -3
- package/dist/application/ClientConfigurationDocument.js +871 -6
- package/dist/application/ClientRegistrationIdentity.js +26 -5
- package/dist/application/ClientRegistrationStatus.js +29 -14
- package/dist/application/DirectAnalysis.js +27 -37
- package/dist/application/Doctor.js +4 -2
- package/dist/application/DoctorScope.js +25 -13
- package/dist/application/EnhancedLiteralTracing.js +22 -25
- package/dist/application/EnhancedTools.js +13 -11
- package/dist/application/EvidenceBundleFiles.js +23 -11
- package/dist/application/FunctionAnalysisEvidence.js +2 -3
- package/dist/application/InventoryProjectionEvidence.js +4 -3
- package/dist/application/InvestigationProviders.js +3 -2
- package/dist/application/JsonFiles.js +50 -28
- package/dist/application/ProcedureAddressResolution.js +11 -0
- package/dist/application/ReconstructionCoverageService.js +7 -4
- package/dist/application/ReconstructionObligationLedgerService.js +4 -3
- package/dist/application/Setup.js +13 -1
- package/dist/application/SetupClientConfiguration.js +10 -15
- package/dist/application/SetupHost.js +5 -2
- package/dist/application/SetupPlan.js +5 -3
- package/dist/application/SupportedClients.js +34 -1
- package/dist/application/Uninstall.js +4 -3
- package/dist/application/Update.js +18 -3
- package/dist/application/UpdateRuntime.js +46 -25
- package/dist/application/WebModuleTraceService.js +4 -2
- package/dist/application/WebNetworkCaptureService.js +4 -2
- package/dist/application/WebRuntimeService.js +5 -3
- package/dist/application/WebScriptExportService.js +2 -2
- package/dist/application/WebSourceLocationService.js +4 -2
- package/dist/application/WorkflowEvidence.js +82 -0
- package/dist/application/binary/AnalysisProviderEvaluation.js +21 -5
- package/dist/application/binary/AnalysisProviderRegistry.js +2 -0
- package/dist/application/binary/AnalysisSnapshotCache.js +3 -6
- package/dist/application/binary/BinarySession.js +95 -68
- package/dist/application/binary/BinarySessionOpen.js +3 -3
- package/dist/application/binary/BinarySessionRecords.js +25 -3
- package/dist/application/binary/BinarySessionStatus.js +17 -1
- package/dist/application/binary/CompositeProvider.js +13 -0
- package/dist/application/binary/LazyAnalysisProvider.js +3 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutService.js +4 -2
- package/dist/application/binaryDiagnostics/RecordedCrashService.js +4 -2
- package/dist/application/evm/EvmInterfaceService.js +4 -2
- package/dist/application/investigation/EvidenceLedger.js +21 -7
- package/dist/application/investigation/InvestigationRecords.js +8 -0
- package/dist/application/javascript/JavaScriptAnalysisControl.js +34 -0
- package/dist/application/javascript/JavaScriptApplicationEvidence.js +24 -3
- package/dist/application/javascript/JavaScriptApplicationEvidenceGraph.js +19 -2
- package/dist/application/javascript/JavaScriptApplicationService.js +22 -15
- package/dist/application/javascript/JavaScriptApplicationWorkflowService.js +5 -5
- package/dist/application/javascript/JavaScriptArtifactAnalysis.js +5 -4
- package/dist/application/javascript/JavaScriptArtifactGraphBuilder.js +39 -7
- package/dist/application/javascript/JavaScriptArtifactPathResolution.js +6 -3
- package/dist/application/javascript/JavaScriptArtifactReconstruction.js +11 -3
- package/dist/application/javascript/JavaScriptModuleRelationships.js +37 -8
- package/dist/application/javascript/JavaScriptRecoveryService.js +4 -2
- package/dist/application/javascript/JavaScriptRuntimeObservationService.js +3 -1
- package/dist/application/javascript/JavaScriptRuntimeReconciliationService.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +63 -61
- package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +23 -1
- package/dist/application/managed/ManagedApplicationGraphService.js +4 -3
- package/dist/application/managed/ManagedMemberComparisonService.js +7 -4
- package/dist/application/managed/ManagedNativeVerificationService.js +4 -3
- package/dist/application/managed/ManagedReconstructionService.js +4 -3
- package/dist/application/process/ProcessCli.js +14 -6
- package/dist/application/process/ProcessEvidence.js +4 -3
- package/dist/application/workflowInputError.js +18 -0
- package/dist/artifacts/ArtifactProvider.js +12 -2
- package/dist/artifacts/MachOSliceArtifactReader.js +98 -14
- package/dist/artifacts/NativeDmgArtifactReader.js +13 -6
- package/dist/artifacts/apple/AppleAssetCatalogAnalysis.js +22 -3
- package/dist/artifacts/apple/InterfaceBuilderAnalysis.js +176 -56
- package/dist/artifacts/apple/InterfaceBuilderDecodeBudget.js +195 -0
- package/dist/artifacts/apple/KeyedArchiveReader.js +36 -22
- package/dist/artifacts/apple/MachoLoadCommandReader.js +92 -31
- package/dist/artifacts/apple/NibArchive.js +81 -14
- package/dist/artifacts/apple/NibViewHierarchy.js +10 -3
- package/dist/artifacts/apple/PlistNumberLiterals.js +164 -0
- package/dist/artifacts/inventory/ArtifactGraphConstruction.js +13 -8
- package/dist/artifacts/inventory/scanCanonical.js +11 -14
- package/dist/artifacts/javascript/JavaScriptArtifactFiles.js +6 -2
- package/dist/browser/CdpCaptureCompleteness.js +2 -0
- package/dist/browser/CdpCaptureEventHandlers.js +76 -11
- package/dist/browser/CdpCaptureEventTypes.js +5 -1
- package/dist/browser/CdpCaptureEvents.js +25 -0
- package/dist/browser/CdpConnection.js +30 -11
- package/dist/browser/CdpOptionalCommand.js +2 -3
- package/dist/browser/CdpPageCapture.js +23 -1
- package/dist/browser/PlaywrightElectronActiveActions.js +32 -23
- package/dist/browser/PlaywrightElectronActiveProjection.js +141 -0
- package/dist/browser/PlaywrightElectronActiveProvider.js +123 -124
- package/dist/browser/WebSourceMapFetcher.js +426 -72
- package/dist/browser/assets/LocalWebScriptArtifacts.js +2 -2
- package/dist/browser/history/CaptureFailures.js +37 -1
- package/dist/browser/history/CaptureRelease.js +5 -0
- package/dist/browser/history/HarCaptureAdapter.js +3 -3
- package/dist/browser/history/HarJson.js +3 -2
- package/dist/browser/history/HistoricalCaptureDecoder.js +4 -2
- package/dist/cli/analysisSelector.js +23 -0
- package/dist/cli/applicationCommands.js +2 -3
- package/dist/cli/coreBinaryCommands.js +70 -12
- package/dist/cli/electronCommands.js +7 -6
- package/dist/cli/javascriptApplicationAnalysis.js +7 -3
- package/dist/cli/jsonOutput.js +4 -99
- package/dist/cli/nativeCallCommands.js +2 -3
- package/dist/cli/webRuntimeCommands.js +8 -8
- package/dist/cli.js +2 -1
- package/dist/cliCommandNames.js +0 -1
- package/dist/cliJsonInput.js +9 -4
- package/dist/config/environment.js +4 -0
- package/dist/config/ghidraStartupTimeout.js +17 -0
- package/dist/config/mcpResponseBudget.js +22 -0
- package/dist/config/parseConfig.js +2 -1
- package/dist/contracts/enhancedInputs.js +5 -3
- package/dist/contracts/enhancedToolContracts.js +3 -3
- package/dist/contracts/hopperMemoryContracts.js +2 -5
- package/dist/contracts/javascript/electronToolContracts.js +1 -1
- package/dist/contracts/managed/managedToolContracts.js +2 -4
- package/dist/contracts/native/nativeToolContracts.js +4 -2
- package/dist/contracts/officialToolContracts.js +14 -15
- package/dist/contracts/promptContracts.js +3 -3
- package/dist/contracts/sessionLifecycleInputs.js +3 -7
- package/dist/contracts/sessionStatusContract.js +2 -4
- package/dist/contracts/sessionToolContracts.js +4 -6
- package/dist/contracts/sessionToolSchemas.js +2 -5
- package/dist/contracts/toolContractExamples.js +0 -1
- package/dist/contracts/toolContractHelpers.js +4 -1
- package/dist/contracts/toolEffects.js +0 -1
- package/dist/contracts/toolOutputSchemaGroups.js +28 -27
- package/dist/contracts/toolOutputSchemaPrimitives.js +18 -0
- package/dist/contracts/toolSchemaMetadata.js +69 -10
- package/dist/contracts/webNetworkCaptureToolContracts.js +1 -1
- package/dist/domain/analysisErrorBase.js +8 -3
- package/dist/domain/analysisErrorCore.js +3 -1
- package/dist/domain/analysisErrorPresentation.js +72 -17
- package/dist/domain/analysisErrorProjection.js +106 -27
- package/dist/domain/android/androidAnalysis.js +2 -4
- package/dist/domain/android/androidApplication.js +17 -9
- package/dist/domain/apple/appleApplication.js +41 -14
- package/dist/domain/apple/dyldEnvironment.js +73 -0
- package/dist/domain/apple/dylibResolution.js +120 -9
- package/dist/domain/apple/dylibResolutionFindings.js +9 -7
- package/dist/domain/apple/interfaceBuilderGraph.js +25 -13
- package/dist/domain/apple/interfaceBuilderKeyedArchive.js +13 -3
- package/dist/domain/apple/keyedArchive.js +9 -1
- package/dist/domain/apple/machoContainer.js +54 -0
- package/dist/domain/apple/plistNumbers.js +76 -0
- package/dist/domain/artifactIdentity.js +29 -0
- package/dist/domain/artifactInventoryEvidence.js +19 -24
- package/dist/domain/bridgeCandidateProjection.js +71 -0
- package/dist/domain/browserCompleteness.js +1 -0
- package/dist/domain/browserObservationError.js +24 -5
- package/dist/domain/browserScenarioValues.js +2 -6
- package/dist/domain/bundleComparison.js +3 -2
- package/dist/domain/canonicalJsonDigestSteps.js +133 -0
- package/dist/domain/canonicalOrdering.js +9 -5
- package/dist/domain/changedBehavior.js +6 -6
- package/dist/domain/comparisonSourceEvidence.js +40 -0
- package/dist/domain/configurationErrors.js +1 -1
- package/dist/domain/evidence.js +64 -9
- package/dist/domain/evidenceBundle.js +42 -1
- package/dist/domain/evidenceErrors.js +12 -1
- package/dist/domain/explicitSensitiveFailure.js +17 -1
- package/dist/domain/firmware/firmwareAnalysis.js +6 -7
- package/dist/domain/hopperErrors.js +23 -2
- package/dist/domain/hopperValues.js +51 -8
- package/dist/domain/immutableJson.js +114 -0
- package/dist/domain/inputIssueProjection.js +3 -0
- package/dist/domain/javascript/electronActiveObservation.js +30 -4
- package/dist/domain/javascript/electronStaticAnalysisValues.js +6 -12
- package/dist/domain/javascript/javascriptApplicationAnalysis.js +71 -23
- package/dist/domain/javascript/javascriptApplicationGraph.js +13 -0
- package/dist/domain/javascript/javascriptAstValues.js +20 -0
- package/dist/domain/javascript/javascriptRecovery.js +3 -8
- package/dist/domain/javascript/javascriptSemanticAnalysis.js +97 -2
- package/dist/domain/javascript/javascriptSemanticCoverage.js +6 -5
- package/dist/domain/javascript/javascriptSemanticDataEffectHelpers.js +9 -17
- package/dist/domain/javascript/javascriptSemanticDataEffects.js +22 -5
- package/dist/domain/javascript/javascriptSemanticGraph.js +13 -0
- package/dist/domain/javascript/javascriptSemanticGraphSchemas.js +1 -0
- package/dist/domain/javascript/javascriptSemanticPrimitives.js +25 -17
- package/dist/domain/javascript/javascriptSemanticQuerySchemas.js +8 -4
- package/dist/domain/javascript/javascriptSemanticResourceLimits.js +77 -0
- package/dist/domain/javascript/javascriptSemanticValues.js +83 -11
- package/dist/domain/javascript/javascriptSourceParser.js +6 -2
- package/dist/domain/javascript/javascriptStaticAnalysis.js +22 -17
- package/dist/domain/javascript/javascriptStaticAnalysisBundler.js +5 -3
- package/dist/domain/javascript/javascriptStaticAnalysisCalls.js +14 -4
- package/dist/domain/javascript/javascriptStaticAnalysisFindings.js +66 -7
- package/dist/domain/javascript/javascriptStaticAnalysisHelpers.js +28 -16
- package/dist/domain/javascript/javascriptStaticAnalysisState.js +3 -2
- package/dist/domain/jsonSerialization.js +109 -0
- package/dist/domain/jsonValue.js +90 -6
- package/dist/domain/localPath.js +6 -0
- package/dist/domain/managed/managedApplicationGraph.js +10 -10
- package/dist/domain/managed/managedInspectionEvidence.js +16 -0
- package/dist/domain/managed/managedMemberComparisonMatch.js +5 -9
- package/dist/domain/managed/managedNativeVerification.js +3 -9
- package/dist/domain/native/fileOffsetPartialObservation.js +1 -0
- package/dist/domain/native/nativeCallObservation.js +33 -11
- package/dist/domain/native/nativeCallPartialObservation.js +33 -0
- package/dist/domain/process/processCapture.js +8 -4
- package/dist/domain/process/processComparison.js +38 -4
- package/dist/domain/process/processScenario.js +11 -10
- package/dist/domain/propertyListKeys.js +1 -1
- package/dist/domain/propertyListXmlText.js +19 -0
- package/dist/domain/providerAdapterError.js +1 -1
- package/dist/domain/providerOperationHealth.js +54 -0
- package/dist/domain/reconstructionVerification.js +6 -43
- package/dist/domain/reconstructionVerificationSchemas.js +2 -2
- package/dist/domain/staticRuntimeCorrelation.js +6 -43
- package/dist/domain/webBundleAnalysis.js +20 -8
- package/dist/domain/webBundleAnalyzerInspection.js +13 -8
- package/dist/domain/webBundleAnalyzerResult.js +41 -2
- package/dist/domain/webRuntime.js +19 -5
- package/dist/domain/webSourceLocation.js +1 -0
- package/dist/firmware/FirmwareFiles.js +1 -1
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraAnalysisProfile.js +5 -0
- package/dist/ghidra/GhidraClient.js +47 -14
- package/dist/ghidra/GhidraDiagnostics.js +7 -7
- package/dist/ghidra/GhidraFunctionValues.js +10 -7
- package/dist/ghidra/GhidraInstallation.js +18 -21
- package/dist/ghidra/GhidraInventoryValues.js +14 -4
- package/dist/ghidra/GhidraJavaLaunch.js +62 -0
- package/dist/ghidra/GhidraLauncher.js +33 -16
- package/dist/ghidra/GhidraProviderCapabilities.js +14 -2
- package/dist/ghidra/GhidraProviderClient.js +23 -12
- package/dist/ghidra/GhidraSessionError.js +11 -7
- package/dist/ghidra/GhidraTargetSnapshot.js +47 -12
- package/dist/ghidra/GhidraTransport.js +20 -0
- package/dist/hopper/BridgeLauncher.js +31 -11
- package/dist/hopper/HopperAnalysisProfile.js +62 -4
- package/dist/hopper/HopperCleanup.js +15 -3
- package/dist/hopper/HopperClient.js +222 -14
- package/dist/hopper/HopperFileOffset.js +237 -0
- package/dist/hopper/HopperMachOImage.js +196 -0
- package/dist/hopper/HopperOperationStage.js +6 -0
- package/dist/hopper/HopperProvider.js +63 -9
- package/dist/hopper/HopperProviderCapabilities.js +19 -1
- package/dist/hopper/HopperRegexSearch.js +137 -0
- package/dist/hopper/HopperRegexWorker.js +27 -0
- package/dist/hopper/HopperRequestQueue.js +68 -19
- package/dist/hopper/HopperTargetLease.js +3 -2
- package/dist/inspector/JavaScriptRuntimeScope.js +9 -1
- package/dist/inspector/V8InspectorCaptureProjection.js +8 -3
- package/dist/inspector/V8InspectorProvider.js +47 -11
- package/dist/javascript/sourceMaps/DecodedSourceMap.js +47 -0
- package/dist/javascript/sourceMaps/SourceMapCommand.js +16 -0
- package/dist/javascript/sourceMaps/SourceMapFormat.js +79 -31
- package/dist/javascript/sourceMaps/TraceSourceMap.js +14 -28
- package/dist/mcpDoctor.js +62 -2
- package/dist/native/AppleDispatchCoverage.js +47 -6
- package/dist/native/AppleDispatchDecodeFacts.js +6 -0
- package/dist/native/AppleDispatchMetadata.js +6 -3
- package/dist/native/AppleMachoFixups.js +48 -24
- package/dist/native/AppleMachoSelection.js +32 -16
- package/dist/native/AppleObjcCategories.js +6 -2
- package/dist/native/AppleObjcDispatchFacets.js +5 -2
- package/dist/native/AppleObjcProperties.js +2 -1
- package/dist/native/AppleSwiftDispatchFacets.js +27 -21
- package/dist/native/AppleSwiftVtables.js +21 -15
- package/dist/native/CommandRunner.js +81 -84
- package/dist/native/LldbCallTracer.js +365 -102
- package/dist/native/LldbRetainedObservations.js +178 -0
- package/dist/native/NativeCallObservation.js +62 -3
- package/dist/native/NativeMacOSProvider.js +98 -17
- package/dist/native/SignatureTargetBinding.js +155 -0
- package/dist/native/parsers/lipo.js +71 -1
- package/dist/native/pwndbg/PwndbgCoreContext.js +25 -5
- package/dist/native/pwntools/RecordedCrashStructureBindings.js +2 -0
- package/dist/process/DarwinProcessRunTokenReader.js +8 -9
- package/dist/process/ExecFileOutput.js +20 -0
- package/dist/process/OwnedCommand.js +7 -2
- package/dist/process/ProcessOwnership.js +203 -53
- package/dist/process/ProcessOwnershipObservation.js +49 -4
- package/dist/process/ProviderProcess.js +38 -7
- package/dist/process/capture/ProcessCaptureError.js +2 -2
- package/dist/process/capture/ProcessCaptureLifecycle.js +100 -34
- package/dist/server/EvidenceMcpServer.js +146 -0
- package/dist/server/createServer.js +15 -8
- package/dist/server/registerAndroidTools.js +2 -4
- package/dist/server/registerApplicationTools/helpers.js +2 -4
- package/dist/server/registerArtifactComparisonTool.js +11 -7
- package/dist/server/registerBinaryDiagnosticsTools.js +2 -4
- package/dist/server/registerBrowserScenarioTool.js +2 -4
- package/dist/server/registerBrowserTools.js +2 -4
- package/dist/server/registerBundleComparisonTool.js +2 -3
- package/dist/server/registerCloseLifecycleTool.js +8 -18
- package/dist/server/registerElectronTools.js +2 -4
- package/dist/server/registerEnhancedTools.js +18 -59
- package/dist/server/registerEvidenceTools.js +2 -4
- package/dist/server/registerEvmTools.js +2 -4
- package/dist/server/registerFirmwareTools.js +2 -4
- package/dist/server/registerFunctionComparisonTool.js +10 -6
- package/dist/server/registerInvestigationTools.js +7 -7
- package/dist/server/registerJavaScriptRecoveryTool.js +2 -4
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +2 -4
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +2 -4
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +2 -4
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +2 -4
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +2 -4
- package/dist/server/registerOfficialTools.js +2 -4
- package/dist/server/registerProcessComparisonTool.js +71 -45
- package/dist/server/registerRecordedCrashTools.js +2 -4
- package/dist/server/registerSessionRecordTools.js +5 -6
- package/dist/server/registerSessionStatusTool.js +17 -3
- package/dist/server/registerSessionTools.js +11 -3
- package/dist/server/registerWebModuleTool.js +2 -4
- package/dist/server/registerWebNetworkCaptureTool.js +2 -4
- package/dist/server/registerWebRuntimeTools.js +3 -7
- package/dist/server/registerWebScriptTool.js +2 -4
- package/dist/server/registerWebSourceLocationTool.js +2 -4
- package/dist/server/runDerivedOperation.js +15 -3
- package/dist/server/sessionAvailabilityPolicy.js +0 -1
- package/dist/server/toolRegistrationOptions.js +2 -2
- package/dist/server/toolResult.js +62 -10
- package/dist/server/toolResultEncoding.js +33 -0
- package/install.sh +1 -1
- package/native/windows/build/manifest.json +1 -1
- package/package.json +8 -3
- package/scripts/electron-active-hook-boundaries.cjs +18 -20
- package/scripts/electron-active-hook.cjs +241 -37
- package/skills/reverse-engineer-anything/SKILL.md +11 -3
- package/skills/reverse-engineer-anything/references/javascript-applications.md +8 -1
- package/dist/ghidra/GhidraDefaults.js +0 -2
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
2
|
import { createAnalysisExecution, } from "../application/AnalysisProvider.js";
|
|
3
|
-
import { AnalysisCancelledError, AnalysisCapabilityUnavailableError, AnalysisInputError, AnalysisTimeoutError, } from "../domain/analysisErrorCore.js";
|
|
3
|
+
import { AnalysisCancelledError, AnalysisArtifactChangedError, AnalysisAccessDeniedError, AnalysisCapabilityUnavailableError, AnalysisInputError, AnalysisResourceConstraintError, AnalysisTimeoutError, } from "../domain/analysisErrorCore.js";
|
|
4
4
|
import {} from "../domain/analysisErrorBase.js";
|
|
5
5
|
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
|
6
6
|
import { err, ok } from "../domain/result.js";
|
|
7
7
|
import { GhidraClient } from "./GhidraClient.js";
|
|
8
|
-
import { GHIDRA_STARTUP_TIMEOUT_MS } from "./GhidraDefaults.js";
|
|
9
8
|
import { isGhidraFunctionOperation, parseGhidraFunctionInput, parseGhidraFunctionResult, } from "./GhidraFunctionValues.js";
|
|
10
9
|
import { isGhidraInventoryOperation, parseGhidraInventoryInput, parseGhidraInventoryResult, } from "./GhidraInventoryValues.js";
|
|
11
10
|
import { ghidraInstallationDiagnostics, } from "./GhidraInstallation.js";
|
|
@@ -55,12 +54,11 @@ export const createGhidraProviderClient = (input) => {
|
|
|
55
54
|
]
|
|
56
55
|
: [];
|
|
57
56
|
const client = clientFactory({
|
|
57
|
+
startupTimeoutMs: config.ghidraStartupTimeoutMs,
|
|
58
58
|
platform: installation.platform,
|
|
59
59
|
launcher: new GhidraHeadlessLauncher({
|
|
60
60
|
analyzeHeadlessPath: prerequisites.value.analyzeHeadlessPath,
|
|
61
|
-
|
|
62
|
-
? {}
|
|
63
|
-
: { javaHome: config.ghidraJavaHome }),
|
|
61
|
+
javaHome: prerequisites.value.javaHome,
|
|
64
62
|
bridgeScriptPath: fileURLToPath(new URL("../../bridge/ghidra/ReaGhidraBridge.java", import.meta.url)),
|
|
65
63
|
...(target.format === "dos-mz" ? { dosMz: true } : {}),
|
|
66
64
|
...(target.format === "dos-com" ? { dosCom: true } : {}),
|
|
@@ -110,7 +108,7 @@ export const createGhidraProviderClient = (input) => {
|
|
|
110
108
|
if (operation === "health") {
|
|
111
109
|
const started = await client.start(options?.signal);
|
|
112
110
|
if (!started.ok)
|
|
113
|
-
return err(projectSessionError(operation, started.error));
|
|
111
|
+
return err(projectSessionError(operation, started.error, config.ghidraStartupTimeoutMs));
|
|
114
112
|
const failed = await checkExtensions(operation, started.value);
|
|
115
113
|
if (failed !== undefined)
|
|
116
114
|
return err(failed);
|
|
@@ -127,14 +125,14 @@ export const createGhidraProviderClient = (input) => {
|
|
|
127
125
|
if (extensions.length > 0) {
|
|
128
126
|
const started = await client.start(options?.signal);
|
|
129
127
|
if (!started.ok)
|
|
130
|
-
return err(projectSessionError(operation, started.error));
|
|
128
|
+
return err(projectSessionError(operation, started.error, config.ghidraStartupTimeoutMs));
|
|
131
129
|
const failed = await checkExtensions(operation, started.value);
|
|
132
130
|
if (failed !== undefined)
|
|
133
131
|
return err(failed);
|
|
134
132
|
}
|
|
135
133
|
const called = await client.callTool(operation, input.value, options?.signal === undefined ? {} : { signal: options.signal });
|
|
136
134
|
if (!called.ok)
|
|
137
|
-
return err(projectSessionError(operation, called.error));
|
|
135
|
+
return err(projectSessionError(operation, called.error, config.ghidraStartupTimeoutMs));
|
|
138
136
|
const result = isGhidraFunctionOperation(operation)
|
|
139
137
|
? parseGhidraFunctionResult(operation, called.value)
|
|
140
138
|
: parseGhidraInventoryResult(operation, called.value);
|
|
@@ -142,7 +140,7 @@ export const createGhidraProviderClient = (input) => {
|
|
|
142
140
|
return result;
|
|
143
141
|
let normalized = result.value;
|
|
144
142
|
if (operation === "inspect_native_load_image") {
|
|
145
|
-
const attested = await attestGhidraNativeLoadImage(target, operation, result.value, client, (failure) => projectSessionError(operation, failure));
|
|
143
|
+
const attested = await attestGhidraNativeLoadImage(target, operation, result.value, client, (failure) => projectSessionError(operation, failure, config.ghidraStartupTimeoutMs));
|
|
146
144
|
if (!attested.ok)
|
|
147
145
|
return attested;
|
|
148
146
|
normalized = attested.value;
|
|
@@ -176,6 +174,7 @@ const ghidraClientPrerequisites = (target, profile, installation) => {
|
|
|
176
174
|
return err(new ProviderAdapterError("ghidra", "health"));
|
|
177
175
|
return ok({
|
|
178
176
|
analyzeHeadlessPath: installation.analyzeHeadlessPath,
|
|
177
|
+
javaHome: installation.javaHome,
|
|
179
178
|
providerVersion: installation.providerVersion,
|
|
180
179
|
profile,
|
|
181
180
|
});
|
|
@@ -184,7 +183,11 @@ const unavailableClient = (failure) => ({
|
|
|
184
183
|
execute: () => Promise.resolve(err(failure)),
|
|
185
184
|
close: () => Promise.resolve(),
|
|
186
185
|
});
|
|
187
|
-
const projectSessionError = (operation, failure) => {
|
|
186
|
+
const projectSessionError = (operation, failure, startupTimeoutMs) => {
|
|
187
|
+
if (failure.cause instanceof AnalysisAccessDeniedError)
|
|
188
|
+
return new AnalysisAccessDeniedError(operation, failure.cause.path, failure.cause.systemCode, { cause: failure });
|
|
189
|
+
if (failure.cause instanceof AnalysisArtifactChangedError)
|
|
190
|
+
return new AnalysisArtifactChangedError(operation, failure.cause.path, failure.cause.reason, { cause: failure });
|
|
188
191
|
if (operation === "annotate_native_function" &&
|
|
189
192
|
failure.kind === "remote" &&
|
|
190
193
|
failure.remoteCode === "invalid_function_name")
|
|
@@ -194,12 +197,20 @@ const projectSessionError = (operation, failure) => {
|
|
|
194
197
|
if (failure.kind === "cancelled")
|
|
195
198
|
return new AnalysisCancelledError(operation);
|
|
196
199
|
if (failure.kind === "timeout" || failure.kind === "analysis_timeout")
|
|
197
|
-
return new AnalysisTimeoutError(operation, failure.timeoutMs ??
|
|
200
|
+
return new AnalysisTimeoutError(operation, failure.timeoutMs ?? startupTimeoutMs);
|
|
198
201
|
if (failure.kind === "remote" && failure.remoteCode === "decompile_cancelled")
|
|
199
202
|
return new AnalysisCancelledError(operation);
|
|
203
|
+
if (failure.kind === "remote" &&
|
|
204
|
+
failure.remoteCode === "regex_stack_exhausted")
|
|
205
|
+
return new AnalysisResourceConstraintError(operation, "memory", failure.message, null, {
|
|
206
|
+
cause: failure,
|
|
207
|
+
remediationAction: "Retry this search in literal mode or simplify the regex. The active analysis session and annotations remain available.",
|
|
208
|
+
});
|
|
200
209
|
if (failure.kind === "remote" &&
|
|
201
210
|
["invalid_request", "not_found", "ambiguous"].includes(failure.remoteCode ?? ""))
|
|
202
|
-
return new AnalysisInputError(operation, { cause: failure }
|
|
211
|
+
return new AnalysisInputError(operation, { cause: failure }, [
|
|
212
|
+
{ path: [], reason: "invalid_value", message: failure.message },
|
|
213
|
+
]);
|
|
203
214
|
if (failure.kind === "remote" && failure.remoteCode === "method_unavailable")
|
|
204
215
|
return new AnalysisCapabilityUnavailableError("ghidra", operation, failure.message);
|
|
205
216
|
return new ProviderAdapterError("ghidra", operation, {
|
|
@@ -14,13 +14,17 @@ export class GhidraSessionError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
/** Build a session failure while preserving bounded remote failure metadata. */
|
|
17
|
-
const createGhidraSessionError = (failure) => new GhidraSessionError(failure.kind, failure.message,
|
|
18
|
-
|
|
19
|
-
:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
const createGhidraSessionError = (failure) => new GhidraSessionError(failure.kind, failure.message, {
|
|
18
|
+
...failure.diagnostics,
|
|
19
|
+
failure_kind: failure.kind,
|
|
20
|
+
failure_message: failure.message,
|
|
21
|
+
...(failure.remoteCode === undefined
|
|
22
|
+
? {}
|
|
23
|
+
: {
|
|
24
|
+
remote_code: failure.redact(failure.remoteCode),
|
|
25
|
+
remote_message: failure.message,
|
|
26
|
+
}),
|
|
27
|
+
}, {
|
|
24
28
|
...(failure.cause === undefined ? {} : { cause: failure.cause }),
|
|
25
29
|
...(failure.timeoutMs === undefined
|
|
26
30
|
? {}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { constants
|
|
3
|
-
import {
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { open, rm } from "node:fs/promises";
|
|
4
4
|
import { extname, join } from "node:path";
|
|
5
|
+
import { Transform } from "node:stream";
|
|
6
|
+
import { pipeline } from "node:stream/promises";
|
|
7
|
+
import { AnalysisArtifactChangedError, AnalysisAccessDeniedError, } from "../domain/analysisErrorCore.js";
|
|
5
8
|
import { windowsPrivateRuntime } from "../windows/WindowsPrivateRuntime.js";
|
|
6
9
|
/** Copy one target and require its bytes to match the parsed target identity. */
|
|
7
10
|
export const createGhidraTargetSnapshot = async (sourcePath, runtimeRoot, expectedSha256, options = {}) => {
|
|
@@ -12,29 +15,61 @@ export const createGhidraTargetSnapshot = async (sourcePath, runtimeRoot, expect
|
|
|
12
15
|
if ((options.platform ?? process.platform) === "win32") {
|
|
13
16
|
const observed = await windowsPrivateRuntime(runtimeRoot).snapshot(sourcePath, `target-${expectedSha256.slice(0, 12)}${suffix}`, options.signal);
|
|
14
17
|
if (observed.sha256 !== expectedSha256)
|
|
15
|
-
throw new
|
|
18
|
+
throw new AnalysisArtifactChangedError("open_binary", sourcePath, `Ghidra target snapshot digest mismatch: expected ${expectedSha256}, observed ${observed.sha256}`);
|
|
16
19
|
return {
|
|
17
20
|
path: snapshotPath,
|
|
18
21
|
sha256: observed.sha256,
|
|
19
22
|
admission: { source: observed.source, snapshot: observed.snapshot },
|
|
20
23
|
};
|
|
21
24
|
}
|
|
25
|
+
let source;
|
|
26
|
+
let destination;
|
|
22
27
|
try {
|
|
23
|
-
await
|
|
24
|
-
await
|
|
28
|
+
source = await openSnapshotSource(sourcePath);
|
|
29
|
+
if (!(await source.stat()).isFile())
|
|
30
|
+
throw new AnalysisArtifactChangedError("open_binary", sourcePath, "The selected Ghidra source is no longer a regular file");
|
|
31
|
+
destination = await open(snapshotPath, "wx", 0o600);
|
|
32
|
+
await destination.chmod(0o600);
|
|
25
33
|
const hash = createHash("sha256");
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
const digest = new Transform({
|
|
35
|
+
transform(chunk, _encoding, done) {
|
|
36
|
+
if (!Buffer.isBuffer(chunk)) {
|
|
37
|
+
done(new TypeError("Ghidra snapshot stream must contain bytes"));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
hash.update(chunk);
|
|
41
|
+
done(null, chunk);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
await pipeline(source.createReadStream(), digest, destination.createWriteStream(), options.signal === undefined ? {} : { signal: options.signal });
|
|
28
45
|
const observedSha256 = hash.digest("hex");
|
|
29
46
|
if (observedSha256 !== expectedSha256)
|
|
30
|
-
throw new
|
|
47
|
+
throw new AnalysisArtifactChangedError("open_binary", sourcePath, `Ghidra target snapshot digest mismatch: expected ${expectedSha256}, observed ${observedSha256}`);
|
|
31
48
|
return { path: snapshotPath, sha256: observedSha256 };
|
|
32
49
|
}
|
|
33
50
|
catch (cause) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
51
|
+
if (destination !== undefined)
|
|
52
|
+
await rm(snapshotPath, { force: true }).catch((cause) => {
|
|
53
|
+
// best-effort cleanup: snapshot removal must not mask the copy failure.
|
|
54
|
+
void cause;
|
|
55
|
+
});
|
|
56
|
+
throw cause;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await Promise.all([source?.close(), destination?.close()]);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const openSnapshotSource = async (path) => {
|
|
63
|
+
try {
|
|
64
|
+
// A path replaced by a FIFO must not block startup before its type is checked.
|
|
65
|
+
return await open(path, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
66
|
+
}
|
|
67
|
+
catch (cause) {
|
|
68
|
+
const code = cause instanceof Error && "code" in cause ? cause.code : undefined;
|
|
69
|
+
if (code === "EACCES" || code === "EPERM")
|
|
70
|
+
throw new AnalysisAccessDeniedError("open_binary", path, code, { cause });
|
|
71
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP")
|
|
72
|
+
throw new AnalysisArtifactChangedError("open_binary", path, `The selected Ghidra source could not be opened (${code})`, { cause });
|
|
38
73
|
throw cause;
|
|
39
74
|
}
|
|
40
75
|
};
|
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
import { access, open } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { PrivateRuntimeRoot } from "../process/PrivateRuntimeRoot.js";
|
|
2
4
|
import { readWindowsPrivateRuntimeFile } from "../windows/WindowsPrivateRuntime.js";
|
|
3
5
|
import { err, ok } from "../domain/result.js";
|
|
4
6
|
import { GhidraSessionError } from "./GhidraSessionError.js";
|
|
7
|
+
/** Allocate an endpoint that fits the host's Unix sockaddr without moving the project. */
|
|
8
|
+
export const createGhidraEndpoint = async (runtimeRoot, transport, platform) => {
|
|
9
|
+
const path = join(runtimeRoot, transport === "unix-socket" ? "bridge.sock" : "bridge-endpoint.json");
|
|
10
|
+
// sockaddr_un includes a terminating NUL: macOS has 104 bytes, Linux 108.
|
|
11
|
+
const maximumPathBytes = platform === "darwin" ? 103 : 107;
|
|
12
|
+
if (transport !== "unix-socket" ||
|
|
13
|
+
Buffer.byteLength(path, "utf8") <= maximumPathBytes)
|
|
14
|
+
return { endpoint: { transport, path } };
|
|
15
|
+
const socketRoot = await PrivateRuntimeRoot.create({
|
|
16
|
+
parent: "/tmp",
|
|
17
|
+
prefix: "rea-ghidra-socket-",
|
|
18
|
+
platform,
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
endpoint: { transport, path: join(socketRoot.path, "s") },
|
|
22
|
+
socketRoot,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
5
25
|
/** Observe a ready endpoint, returning null while its bridge is not listening. */
|
|
6
26
|
export const observeGhidraEndpoint = async (endpoint) => {
|
|
7
27
|
if (endpoint.transport === "unix-socket") {
|
|
@@ -10,6 +10,7 @@ import { spawnOwnedProviderProcess, } from "../process/ProviderProcess.js";
|
|
|
10
10
|
import { waitForAbortableDelay } from "../process/ProviderDeadline.js";
|
|
11
11
|
import { selectLinuxPrivateDisplayStrategy, } from "./LinuxPrivateDisplayProbe.js";
|
|
12
12
|
import writeFileAtomic from "write-file-atomic";
|
|
13
|
+
import { prepareHopperMachOImage, } from "./HopperMachOImage.js";
|
|
13
14
|
import { acquireHopperTargetLease } from "./HopperTargetLease.js";
|
|
14
15
|
const execFileAsync = promisify(execFile);
|
|
15
16
|
const HOPPER_BACKGROUND_STARTUP_MS = 5_000;
|
|
@@ -41,14 +42,22 @@ export class HopperApplicationLauncher {
|
|
|
41
42
|
const lease = leaseResult.value;
|
|
42
43
|
let leaseTransferred = false;
|
|
43
44
|
try {
|
|
45
|
+
let loadedPath = this.options.targetPath;
|
|
46
|
+
if (this.options.preparedImage !== undefined) {
|
|
47
|
+
const prepared = await prepareHopperMachOImage(this.options.targetPath, this.options.preparedImage.sourceSha256, this.options.preparedImage.image, join(session.directory, "image.macho"), options.signal);
|
|
48
|
+
if (!prepared.ok)
|
|
49
|
+
return prepared;
|
|
50
|
+
loadedPath = prepared.value;
|
|
51
|
+
}
|
|
44
52
|
const ownsProcessLifetime = usesLinuxDemo(this.options);
|
|
45
|
-
const bootstrap = await this.#writeBootstrap(session, ownsProcessLifetime);
|
|
53
|
+
const bootstrap = await this.#writeBootstrap(session, ownsProcessLifetime, loadedPath);
|
|
46
54
|
if (!bootstrap.ok)
|
|
47
55
|
return bootstrap;
|
|
48
56
|
const launched = await this.#launchPreparedTarget({
|
|
49
57
|
session,
|
|
50
58
|
signal: options.signal,
|
|
51
59
|
bootstrapPath: bootstrap.value,
|
|
60
|
+
loadedPath,
|
|
52
61
|
ownsProcessLifetime,
|
|
53
62
|
lease,
|
|
54
63
|
});
|
|
@@ -69,7 +78,7 @@ export class HopperApplicationLauncher {
|
|
|
69
78
|
"-Y",
|
|
70
79
|
bootstrapPath,
|
|
71
80
|
action,
|
|
72
|
-
|
|
81
|
+
input.loadedPath,
|
|
73
82
|
];
|
|
74
83
|
try {
|
|
75
84
|
if (signal?.aborted === true)
|
|
@@ -108,15 +117,26 @@ export class HopperApplicationLauncher {
|
|
|
108
117
|
await cleanupOwnedProcessGroup(started.ownership);
|
|
109
118
|
return err(new HopperStartError({ cause }));
|
|
110
119
|
}
|
|
111
|
-
|
|
120
|
+
const ownedLauncher = {
|
|
121
|
+
...(this.options.preparedImage === undefined
|
|
122
|
+
? {}
|
|
123
|
+
: { preparedImagePath: input.loadedPath }),
|
|
112
124
|
process: started.process,
|
|
113
125
|
ownsProcessLifetime: true,
|
|
114
126
|
ownership: started.ownership,
|
|
115
|
-
shutdownMode: ownsProcessLifetime
|
|
116
|
-
? "process-cleanup"
|
|
117
|
-
: "bridge-request",
|
|
118
127
|
cleanup,
|
|
119
|
-
}
|
|
128
|
+
};
|
|
129
|
+
return ownsProcessLifetime
|
|
130
|
+
? ok({
|
|
131
|
+
...ownedLauncher,
|
|
132
|
+
providerLifetime: "launcher-process",
|
|
133
|
+
shutdownMode: "process-cleanup",
|
|
134
|
+
})
|
|
135
|
+
: ok({
|
|
136
|
+
...ownedLauncher,
|
|
137
|
+
providerLifetime: "external-application",
|
|
138
|
+
shutdownMode: "bridge-request",
|
|
139
|
+
});
|
|
120
140
|
}
|
|
121
141
|
catch (cause) {
|
|
122
142
|
return err(hopperLaunchFailure(cause, signal));
|
|
@@ -144,10 +164,10 @@ export class HopperApplicationLauncher {
|
|
|
144
164
|
return err(new HopperStartError({ cause }));
|
|
145
165
|
}
|
|
146
166
|
}
|
|
147
|
-
async #writeBootstrap(session, ownsProcessLifetime) {
|
|
167
|
+
async #writeBootstrap(session, ownsProcessLifetime, loadedPath) {
|
|
148
168
|
const bootstrapPath = `${session.directory}/bootstrap.py`;
|
|
149
169
|
try {
|
|
150
|
-
await writeFile(bootstrapPath, bridgeBootstrapSource(session, this.options, ownsProcessLifetime), { encoding: "utf8", mode: 0o600 });
|
|
170
|
+
await writeFile(bootstrapPath, bridgeBootstrapSource(session, this.options, ownsProcessLifetime, loadedPath), { encoding: "utf8", mode: 0o600 });
|
|
151
171
|
await chmod(bootstrapPath, 0o600);
|
|
152
172
|
return ok(bootstrapPath);
|
|
153
173
|
}
|
|
@@ -173,11 +193,11 @@ export class HopperApplicationLauncher {
|
|
|
173
193
|
};
|
|
174
194
|
}
|
|
175
195
|
}
|
|
176
|
-
const bridgeBootstrapSource = (session, options, ownsProcessLifetime) => [
|
|
196
|
+
const bridgeBootstrapSource = (session, options, ownsProcessLifetime, loadedPath) => [
|
|
177
197
|
`REA_SOCKET = ${JSON.stringify(session.socketPath)}`,
|
|
178
198
|
`REA_TOKEN = ${JSON.stringify(session.token)}`,
|
|
179
199
|
`REA_RUN_ID = ${JSON.stringify(session.runId)}`,
|
|
180
|
-
`REA_TARGET_PATH = ${JSON.stringify(
|
|
200
|
+
`REA_TARGET_PATH = ${JSON.stringify(loadedPath)}`,
|
|
181
201
|
`REA_OWNS_PROCESS_LIFETIME = ${ownsProcessLifetime ? "True" : "False"}`,
|
|
182
202
|
`exec(compile(open(${JSON.stringify(options.bridgeScriptPath)}, 'rb').read(), ${JSON.stringify(options.bridgeScriptPath)}, 'exec'))`,
|
|
183
203
|
"",
|
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
3
|
import { createAnalysisProfile } from "../domain/analysisProfile.js";
|
|
4
|
-
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
|
4
|
+
import { AnalysisCancelledError, AnalysisCapabilityUnavailableError, } from "../domain/analysisErrorCore.js";
|
|
5
|
+
import { BinaryTargetError } from "../domain/configurationErrors.js";
|
|
5
6
|
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
|
6
7
|
import {} from "../domain/analysisErrorBase.js";
|
|
7
8
|
import { err, ok } from "../domain/result.js";
|
|
9
|
+
import { resolveHopperMachOImage, } from "./HopperMachOImage.js";
|
|
8
10
|
/** Resolve Hopper open semantics without placing its CLI flags in BinaryTarget. */
|
|
9
11
|
export const resolveHopperAnalysisProfile = async (target, options) => {
|
|
10
12
|
if (target.kind !== "executable" && target.kind !== "database")
|
|
11
13
|
return ok({ profile: null, compatibility: {} });
|
|
12
|
-
|
|
14
|
+
let machoContainer;
|
|
15
|
+
if (target.kind === "executable" && target.format === "mach-o") {
|
|
16
|
+
const header = await readMachoMagic(target.path, options.signal);
|
|
17
|
+
if (!header.ok)
|
|
18
|
+
return header;
|
|
19
|
+
machoContainer = [0xcafebabf, 0xbfbafeca].includes(header.value)
|
|
20
|
+
? "fat64"
|
|
21
|
+
: [0xcafebabe, 0xbebafeca].includes(header.value)
|
|
22
|
+
? "fat32"
|
|
23
|
+
: "thin";
|
|
24
|
+
}
|
|
25
|
+
let preparedImage;
|
|
26
|
+
if (target.kind === "executable" &&
|
|
27
|
+
machoContainer === "fat64" &&
|
|
28
|
+
options.loaderArgsOverride.length === 0) {
|
|
29
|
+
const resolved = await resolveHopperMachOImage(target, options.signal);
|
|
30
|
+
if (!resolved.ok)
|
|
31
|
+
return resolved;
|
|
32
|
+
preparedImage = resolved.value;
|
|
33
|
+
}
|
|
34
|
+
const derived = hopperLoaderArgsForTarget(target, preparedImage === undefined ? machoContainer : "thin");
|
|
13
35
|
if (!derived.ok)
|
|
14
36
|
return derived;
|
|
15
37
|
const loaderArgs = options.loaderArgsOverride.length === 0
|
|
@@ -34,6 +56,12 @@ export const resolveHopperAnalysisProfile = async (target, options) => {
|
|
|
34
56
|
available_architectures: [
|
|
35
57
|
...(target.availableArchitectures ?? []),
|
|
36
58
|
].sort(),
|
|
59
|
+
...(machoContainer === undefined
|
|
60
|
+
? {}
|
|
61
|
+
: { macho_container: machoContainer }),
|
|
62
|
+
...(preparedImage === undefined
|
|
63
|
+
? {}
|
|
64
|
+
: { prepared_image: { ...preparedImage } }),
|
|
37
65
|
loader: {
|
|
38
66
|
source: options.loaderArgsOverride.length === 0
|
|
39
67
|
? "derived"
|
|
@@ -48,7 +76,7 @@ export const resolveHopperAnalysisProfile = async (target, options) => {
|
|
|
48
76
|
});
|
|
49
77
|
};
|
|
50
78
|
/** Derive Hopper's complete non-interactive CLI loader selection. */
|
|
51
|
-
export const hopperLoaderArgsForTarget = (target) => {
|
|
79
|
+
export const hopperLoaderArgsForTarget = (target, machoContainer) => {
|
|
52
80
|
if (target.kind === "database")
|
|
53
81
|
return ok([]);
|
|
54
82
|
if (target.kind !== "executable")
|
|
@@ -57,7 +85,9 @@ export const hopperLoaderArgsForTarget = (target) => {
|
|
|
57
85
|
const flag = hopperArchitectureFlag(architecture);
|
|
58
86
|
switch (target.format) {
|
|
59
87
|
case "mach-o":
|
|
60
|
-
|
|
88
|
+
if (machoContainer === undefined)
|
|
89
|
+
return err(new AnalysisCapabilityUnavailableError("hopper", "resolve_analysis_profile", "Mach-O container kind is unknown; resolve the Hopper analysis profile before starting its client."));
|
|
90
|
+
return ok(machoContainer === "fat32" || machoContainer === "fat64"
|
|
61
91
|
? ["-l", "FAT", flag, "-l", "Mach-O"]
|
|
62
92
|
: ["-l", "Mach-O", flag]);
|
|
63
93
|
case "elf":
|
|
@@ -81,6 +111,34 @@ const hopperArchitectureFlag = (architecture) => {
|
|
|
81
111
|
return "--aarch64";
|
|
82
112
|
}
|
|
83
113
|
};
|
|
114
|
+
const readMachoMagic = async (path, signal) => {
|
|
115
|
+
if (signalIsAborted(signal))
|
|
116
|
+
return err(new AnalysisCancelledError("open_binary"));
|
|
117
|
+
const stream = createReadStream(path, { start: 0, end: 3 });
|
|
118
|
+
const onAbort = () => {
|
|
119
|
+
stream.destroy();
|
|
120
|
+
};
|
|
121
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
122
|
+
try {
|
|
123
|
+
const chunks = [];
|
|
124
|
+
for await (const chunk of stream)
|
|
125
|
+
chunks.push(Buffer.from(chunk));
|
|
126
|
+
if (signalIsAborted(signal))
|
|
127
|
+
return err(new AnalysisCancelledError("open_binary"));
|
|
128
|
+
const bytes = Buffer.concat(chunks);
|
|
129
|
+
return bytes.length === 4
|
|
130
|
+
? ok(bytes.readUInt32BE(0))
|
|
131
|
+
: err(new BinaryTargetError(path, "Mach-O header became truncated before Hopper loader selection"));
|
|
132
|
+
}
|
|
133
|
+
catch (cause) {
|
|
134
|
+
return signalIsAborted(signal)
|
|
135
|
+
? err(new AnalysisCancelledError("open_binary"))
|
|
136
|
+
: err(new BinaryTargetError(path, `Cannot read the Mach-O container header: ${cause instanceof Error ? cause.message : String(cause)}`, { cause }));
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
signal?.removeEventListener("abort", onAbort);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
84
142
|
const sha256File = async (path, signal) => {
|
|
85
143
|
if (signalIsAborted(signal))
|
|
86
144
|
return err(new AnalysisCancelledError("open_binary"));
|
|
@@ -11,7 +11,7 @@ export const cleanupHopperSession = async (input) => {
|
|
|
11
11
|
cleanupResult: undefined,
|
|
12
12
|
shutdownConfirmed: false,
|
|
13
13
|
};
|
|
14
|
-
await report(input.progress, 0, input.retainDocument
|
|
14
|
+
await report(input.progress, 0, input.retainDocument && input.launch?.preparedImagePath === undefined
|
|
15
15
|
? "detaching REA from the Hopper document"
|
|
16
16
|
: "requesting Hopper document shutdown");
|
|
17
17
|
await requestShutdown(input, state);
|
|
@@ -20,7 +20,18 @@ export const cleanupHopperSession = async (input) => {
|
|
|
20
20
|
await stopProcess(input, state);
|
|
21
21
|
recordUnconfirmedDocument(input, state);
|
|
22
22
|
await report(input.progress, 0.75, "removing Hopper private runtime files");
|
|
23
|
-
|
|
23
|
+
if (input.launch?.preparedImagePath !== undefined &&
|
|
24
|
+
!state.shutdownConfirmed) {
|
|
25
|
+
const resource = input.runtimeRoot?.path ?? input.launch.preparedImagePath;
|
|
26
|
+
state.resources.add(resource);
|
|
27
|
+
state.issues.push({
|
|
28
|
+
resource,
|
|
29
|
+
reason: "Prepared image retained because native document closure was not confirmed",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
await closeRuntimeRoot(input.runtimeRoot, state);
|
|
34
|
+
}
|
|
24
35
|
return cleanupOutcome(input, state);
|
|
25
36
|
};
|
|
26
37
|
const requestShutdown = async (input, state) => {
|
|
@@ -34,7 +45,8 @@ const requestShutdown = async (input, state) => {
|
|
|
34
45
|
}, "Hopper shutdown skipped because a bridge operation remained active");
|
|
35
46
|
return;
|
|
36
47
|
}
|
|
37
|
-
const method = input.retainDocument
|
|
48
|
+
const method = (input.retainDocument && input.launch?.preparedImagePath === undefined) ||
|
|
49
|
+
input.launch?.shutdownMode === "process-cleanup"
|
|
38
50
|
? "shutdown"
|
|
39
51
|
: "shutdown_document";
|
|
40
52
|
const shutdown = await input
|