rea-agents 3.1.0 → 3.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 +72 -95
- package/bridge/ghidra/ReaGhidraBridge.java +609 -841
- package/bridge/hopper_bridge.py +99 -344
- package/dist/application/AbortablePromise.js +6 -3
- package/dist/application/AnalysisSnapshotCache.js +0 -1
- package/dist/application/AnalysisSnapshotFiles.js +7 -7
- package/dist/application/AndroidApplicationService.js +1 -1
- package/dist/application/AppleApplicationService.js +1 -1
- package/dist/application/ArtifactExtraction.js +35 -54
- package/dist/application/ArtifactExtractionDestination.js +17 -0
- package/dist/application/ArtifactGraphConstruction.js +2 -17
- package/dist/application/ArtifactInventory/classify.js +2 -6
- package/dist/application/ArtifactInventory/hash.js +2 -20
- package/dist/application/ArtifactInventory/scanCanonical.js +8 -12
- package/dist/application/ArtifactInventory/scanReader.js +69 -51
- package/dist/application/ArtifactInventory.js +6 -14
- package/dist/application/AuthorizedArtifactInventory.js +3 -13
- package/dist/application/BinarySession.js +41 -13
- package/dist/application/BinarySessionComposition.js +0 -11
- package/dist/application/BinarySessionOpen.js +2 -1
- package/dist/application/BinarySessionRecords.js +5 -93
- package/dist/application/BinarySessionStatus.js +5 -16
- package/dist/application/BinaryTargetResolver.js +216 -0
- package/dist/application/BrowserEvidence.js +42 -58
- package/dist/application/BrowserObservationService.js +3 -6
- package/dist/application/BrowserScenarioCaptureService.js +3 -6
- package/dist/application/BrowserScenarioEvidence.js +3 -3
- package/dist/application/CallPathTracing.js +2 -36
- package/dist/application/CapabilityInventory.js +10 -15
- package/dist/application/ClientConfigurationDocument.js +27 -0
- package/dist/application/ClientRegistrationStatus.js +16 -13
- package/dist/application/CommandShimReplay.js +14 -1
- package/dist/application/DirectAnalysis.js +22 -46
- package/dist/application/DirectAnalysisStatus.js +5 -61
- package/dist/application/Doctor.js +11 -32
- package/dist/application/DoctorDiagnostics.js +1 -2
- package/dist/application/DoctorScope.js +1 -1
- package/dist/application/ElectronActiveEvidence.js +3 -8
- package/dist/application/ElectronActiveObservationService.js +3 -6
- package/dist/application/ElectronBoundaryAnalysis.js +7 -8
- package/dist/application/ElectronBoundaryGraphContext.js +1 -1
- package/dist/application/ElectronBoundaryGraphIpc.js +3 -3
- package/dist/application/ElectronBoundaryGraphNative.js +1 -2
- package/dist/application/ElectronBoundaryGraphWindows.js +6 -8
- package/dist/application/ElectronEvidence.js +4 -7
- package/dist/application/ElectronObservationService.js +5 -9
- package/dist/application/EnhancedLiteralTracing.js +42 -65
- package/dist/application/EnhancedTools.js +76 -44
- package/dist/application/EvidenceBundleCommands.js +11 -11
- package/dist/application/EvidenceBundleFiles.js +9 -15
- package/dist/application/EvidenceLedger.js +6 -41
- package/dist/application/FilesystemSnapshot.js +2 -3
- package/dist/application/InterfaceBuilderAnalysis.js +266 -0
- package/dist/application/InventoryProjectionEvidence.js +4 -2
- package/dist/application/InvestigationProviders.js +1 -1
- package/dist/application/JavaScriptApplicationEvidence.js +2 -5
- package/dist/application/JavaScriptApplicationEvidenceGraph.js +6 -10
- package/dist/application/JavaScriptApplicationService.js +6 -29
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +5 -5
- package/dist/application/JavaScriptApplicationWorkflowService.js +0 -8
- package/dist/application/JavaScriptArtifactAnalysis.js +38 -127
- package/dist/application/JavaScriptArtifactFiles.js +67 -107
- package/dist/application/JavaScriptArtifactGraphAccumulator.js +0 -7
- package/dist/application/JavaScriptArtifactGraphBuilder.js +7 -58
- package/dist/application/JavaScriptArtifactGraphBundlers.js +1 -6
- package/dist/application/JavaScriptArtifactGraphContext.js +12 -39
- package/dist/application/JavaScriptArtifactGraphDocuments.js +4 -11
- package/dist/application/JavaScriptArtifactGraphEvidence.js +0 -14
- package/dist/application/JavaScriptArtifactGraphFindings.js +3 -3
- package/dist/application/JavaScriptArtifactGraphStructure.js +16 -17
- package/dist/application/JavaScriptArtifactPathResolution.js +61 -49
- package/dist/application/JavaScriptArtifactReconstruction.js +5 -10
- package/dist/application/JavaScriptArtifactReconstructionInput.js +2 -14
- package/dist/application/JavaScriptJsonModules.js +2 -2
- package/dist/application/JavaScriptModuleRelationships.js +22 -27
- package/dist/application/JavaScriptReplayPlanning.js +9 -3
- package/dist/application/JavaScriptReplayService.js +6 -32
- package/dist/application/JavaScriptReturnShapeProjection.js +12 -42
- package/dist/application/JavaScriptRuntimeObservationEvidence.js +7 -8
- package/dist/application/JavaScriptRuntimeObservationService.js +4 -8
- package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +2 -3
- package/dist/application/JavaScriptSemanticGraphBuilder.js +11 -41
- package/dist/application/JavaScriptSemanticGraphConstruction.js +5 -29
- package/dist/application/JavaScriptSemanticGraphEvidence.js +1 -1
- package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +1 -1
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +0 -13
- package/dist/application/JavaScriptSemanticGraphProjection.js +1 -3
- package/dist/application/JavaScriptSemanticTraceService.js +1 -10
- package/dist/application/JsonFiles.js +79 -0
- package/dist/application/LinuxHopper.js +11 -9
- package/dist/application/LoopbackReplay.js +26 -23
- package/dist/application/LoopbackReplayRecorder.js +29 -1
- package/dist/application/ManagedApplicationGraphService.js +1 -2
- package/dist/application/ManagedMemberComparisonService.js +11 -41
- package/dist/application/ManagedNativeVerificationService.js +1 -2
- package/dist/application/ManagedReconstructionService.js +1 -1
- package/dist/application/ManagedRuntimeCorrelationService.js +5 -8
- package/dist/application/NativeApiInspection.js +2 -7
- package/dist/application/NativeUiActionTrace.js +394 -0
- package/dist/application/NodeRuntimeCharacterizationService.js +3 -7
- package/dist/application/PermissionAuthority.js +15 -7
- package/dist/application/PermissionFailure.js +5 -0
- package/dist/application/ProcessCaptureAuthority.js +5 -7
- package/dist/application/ProcessCaptureJournal.js +3 -2
- package/dist/application/ProcessCaptureLifecycle.js +5 -12
- package/dist/application/ProcessCheckpoints.js +29 -11
- package/dist/application/ProcessCli.js +6 -16
- package/dist/application/ProcessEvidence.js +5 -7
- package/dist/application/ProcessHarness.js +52 -17
- package/dist/application/ProcessReactiveCoordinator.js +2 -4
- package/dist/application/ProcessReactiveEffects.js +9 -3
- package/dist/application/ProcessReactiveHarness.js +13 -7
- package/dist/application/ProcessSampling.js +2 -4
- package/dist/application/ProcessTimer.js +39 -0
- package/dist/application/ProjectPermissionStore.js +39 -96
- package/dist/application/RealHopperAssertions.js +20 -66
- package/dist/application/ReconstructionCoverageService.js +12 -58
- package/dist/application/ReconstructionObligationAuthority.js +2 -2
- package/dist/application/ReconstructionObligationCandidates.js +4 -4
- package/dist/application/ReconstructionObligationLedgerEvaluation.js +8 -15
- package/dist/application/ReconstructionObligationLedgerProjection.js +3 -7
- package/dist/application/ReconstructionObligationLedgerService.js +19 -38
- package/dist/application/ReferenceSourceImport.js +19 -45
- package/dist/application/ReferenceSourceImportEntries.js +2 -4
- package/dist/application/ReferenceSourceImportPolicy.js +5 -32
- package/dist/application/ReferenceSourceImportTypes.js +0 -16
- package/dist/application/RuntimeExecutableDiagnostics.js +55 -29
- package/dist/application/SessionProviderRouter.js +2 -2
- package/dist/application/Setup.js +1 -3
- package/dist/application/SetupClientConfiguration.js +22 -93
- package/dist/application/SetupRegistrationEnvironment.js +0 -7
- package/dist/application/StoreFileLock.js +119 -0
- package/dist/application/TerminalRenderer.js +2 -3
- package/dist/application/Uninstall.js +9 -14
- package/dist/application/UnknownEvidence.js +1 -1
- package/dist/artifacts/ArtifactPaths.js +2 -5
- package/dist/artifacts/ArtifactProvider.js +36 -58
- package/dist/artifacts/DirectoryArtifactReader.js +13 -8
- package/dist/artifacts/MachOSliceArtifactReader.js +1 -5
- package/dist/artifacts/NativeDmgArtifactReader.js +11 -11
- package/dist/artifacts/NibArchive.js +269 -0
- package/dist/artifacts/SafeOutputTree.js +15 -24
- package/dist/browser/BrowserProcessStartup.js +1 -2
- package/dist/browser/CdpAuthorizedMainFrame.js +16 -0
- package/dist/browser/CdpBrowserProvider.js +3 -13
- package/dist/browser/CdpCanonicalJsonDigest.js +9 -0
- package/dist/browser/CdpCaptureDocuments.js +28 -76
- package/dist/browser/CdpCaptureEventBodyShapes.js +13 -33
- package/dist/browser/CdpCaptureEventHandlers.js +22 -87
- package/dist/browser/CdpCaptureEvents.js +2 -8
- package/dist/browser/CdpCaptureStorage.js +9 -20
- package/dist/browser/CdpCaptureStorageFingerprints.js +85 -98
- package/dist/browser/CdpCaptureValues.js +21 -21
- package/dist/browser/CdpConnection.js +10 -25
- package/dist/browser/CdpElectronInspection.js +26 -50
- package/dist/browser/CdpElectronProvider.js +10 -35
- package/dist/browser/CdpElectronScriptEvents.js +8 -16
- package/dist/browser/CdpElectronScripts.js +6 -35
- package/dist/browser/CdpElectronWorkers.js +6 -10
- package/dist/browser/CdpEndpoint.js +25 -41
- package/dist/browser/CdpObservationSession.js +14 -26
- package/dist/browser/CdpPageCapture.js +34 -48
- package/dist/browser/CdpPageCaptureScripts.js +1 -19
- package/dist/browser/CdpPageCaptureWorkers.js +6 -10
- package/dist/browser/CdpSafeMetadata.js +19 -22
- package/dist/browser/CdpScreenshot.js +3 -5
- package/dist/browser/CdpTargetSession.js +1 -2
- package/dist/browser/CdpWebMcpDiscovery.js +13 -22
- package/dist/browser/ElectronFileScope.js +8 -19
- package/dist/browser/JavaScriptRuntimeScope.js +9 -14
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +31 -64
- package/dist/browser/PlaywrightElectronActiveActions.js +54 -69
- package/dist/browser/PlaywrightElectronActiveProvider.js +14 -34
- package/dist/browser/PlaywrightExecutionBoundary.js +16 -5
- package/dist/browser/PlaywrightScenarioActions.js +3 -7
- package/dist/browser/PlaywrightScenarioArtifacts.js +23 -92
- package/dist/browser/PlaywrightScenarioBrowser.js +3 -5
- package/dist/browser/PlaywrightScenarioEvents.js +45 -94
- package/dist/browser/PlaywrightScenarioSession.js +14 -30
- package/dist/browser/PngVisualDiff.js +22 -14
- package/dist/browser/SensitiveTextCapture.js +0 -17
- package/dist/browser/V8InspectorCaptureProjection.js +7 -8
- package/dist/browser/V8InspectorEndpoint.js +12 -14
- package/dist/browser/V8InspectorProvider.js +20 -61
- package/dist/browser/WebSourceMapFetcher.js +56 -176
- package/dist/catalogIdentity.js +0 -54
- package/dist/cli/artifactCommands.js +49 -77
- package/dist/cli/coreAnalysisCommands.js +83 -133
- package/dist/cli/javascriptApplicationAnalysis.js +1 -9
- package/dist/cli/managedCommands.js +5 -270
- package/dist/cli/setupCommands.js +3 -12
- package/dist/cli/utilityCommands.js +7 -41
- package/dist/cli.js +0 -2
- package/dist/cliApplicationCommands.js +42 -112
- package/dist/cliBrowserAdvancedCommands.js +10 -106
- package/dist/cliBrowserCommands.js +15 -127
- package/dist/cliBrowserContext.js +30 -0
- package/dist/cliCommandNames.js +4 -5
- package/dist/cliElectronCommands.js +8 -72
- package/dist/cliEvidenceCommands.js +8 -77
- package/dist/cliJavaScriptRuntimeCommands.js +9 -91
- package/dist/cliJsonInput.js +4 -14
- package/dist/cliObservationOptions.js +11 -96
- package/dist/cliPolicyCommands.js +2 -17
- package/dist/cliProcessCommands.js +3 -3
- package/dist/cliSetup.js +0 -13
- package/dist/config/browserScenario.js +2 -2
- package/dist/config/environment.js +0 -9
- package/dist/config/parseConfig.js +1 -33
- package/dist/config/parsers.js +6 -27
- package/dist/config/passiveObservation.js +7 -77
- package/dist/config/permissions.js +0 -7
- package/dist/contracts/analysisSearchContract.js +2 -5
- package/dist/contracts/applicationToolContracts.js +56 -109
- package/dist/contracts/applicationWorkflowInputContracts.js +27 -102
- package/dist/contracts/artifactComparisonExample.js +58 -60
- package/dist/contracts/artifactToolContracts.js +20 -67
- package/dist/contracts/browserScenarioToolContracts.js +6 -51
- package/dist/contracts/browserToolContracts.js +8 -73
- package/dist/contracts/electronToolContracts.js +8 -108
- package/dist/contracts/enhancedInputs.js +23 -54
- package/dist/contracts/errorSchemas.js +0 -1
- package/dist/contracts/functionComparisonExample.js +10 -24
- package/dist/contracts/functionInstructionContract.js +1 -3
- package/dist/contracts/functionWorkflowToolContracts.js +2 -2
- package/dist/contracts/hopperMemoryContracts.js +3 -3
- package/dist/contracts/investigationExamples.js +14 -11
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +12 -19
- package/dist/contracts/javascriptRuntimeObservationToolContracts.js +2 -20
- package/dist/contracts/javascriptRuntimeReconciliationExample.js +45 -12
- package/dist/contracts/managedToolContracts.js +6 -95
- package/dist/contracts/managedWorkflowExamples.js +77 -128
- package/dist/contracts/managedWorkflowToolContracts.js +51 -77
- package/dist/contracts/nativeToolContracts.js +7 -11
- package/dist/contracts/processCaptureExample.js +2 -69
- package/dist/contracts/promptContracts.js +49 -67
- package/dist/contracts/providerSelection.js +0 -1
- package/dist/contracts/reconstructionReadinessExample.js +1 -6
- package/dist/contracts/sessionStatusContract.js +1 -17
- package/dist/contracts/sessionToolSchemas.js +6 -15
- package/dist/contracts/toolContractExamples.js +11 -11
- package/dist/contracts/toolContractHelpers.js +0 -4
- package/dist/contracts/toolContracts.js +60 -71
- package/dist/contracts/toolEffects.js +5 -13
- package/dist/contracts/toolOutputSchemaGroups.js +37 -91
- package/dist/contracts/toolOutputSchemaPrimitives.js +16 -56
- package/dist/contracts/toolSchemaMetadata.js +8 -19
- package/dist/contracts/unknownContractExamples.js +0 -2
- package/dist/domain/analysisErrorPresentation.js +46 -54
- package/dist/domain/analysisErrorProjection.js +28 -37
- package/dist/domain/analysisProfile.js +3 -31
- package/dist/domain/analysisSnapshot.js +3 -12
- package/dist/domain/androidApplication.js +15 -54
- package/dist/domain/appleApplication.js +24 -67
- package/dist/domain/artifactComparison.js +11 -29
- package/dist/domain/artifactGraph.js +14 -44
- package/dist/domain/artifactInspection.js +38 -69
- package/dist/domain/artifactInventoryEvidence.js +27 -21
- package/dist/domain/binaryTarget.js +2 -221
- package/dist/domain/browserCaptureComparison.js +47 -24
- package/dist/domain/browserObservation.js +28 -193
- package/dist/domain/browserObservationSchemas.js +5 -23
- package/dist/domain/browserScenario.js +11 -14
- package/dist/domain/browserScenarioCapture.js +11 -12
- package/dist/domain/browserScenarioCaptureValues.js +146 -84
- package/dist/domain/browserScenarioDiff.js +2 -59
- package/dist/domain/browserScenarioDiffEvidence.js +51 -0
- package/dist/domain/browserScenarioDiffValues.js +21 -38
- package/dist/domain/browserScenarioValues.js +80 -109
- package/dist/domain/browserSession.js +4 -7
- package/dist/domain/bundleComparison.js +6 -32
- package/dist/domain/bytecodeProvider.js +20 -19
- package/dist/domain/callPath.js +47 -167
- package/dist/domain/callPathSchemas.js +79 -0
- package/dist/domain/changedBehavior.js +34 -60
- package/dist/domain/completionLedgerGeneration.js +8 -14
- package/dist/domain/conformancePackage.js +126 -97
- package/dist/domain/conformanceReplay.js +36 -8
- package/dist/domain/conformanceTrustGate.js +23 -14
- package/dist/domain/customProtocolCapture.js +1 -1
- package/dist/domain/electronActiveObservation.js +70 -103
- package/dist/domain/electronObservation.js +14 -80
- package/dist/domain/electronStaticAnalysisBrowser.js +36 -46
- package/dist/domain/electronStaticAnalysisIpc.js +38 -14
- package/dist/domain/electronStaticAnalysisNative.js +1 -8
- package/dist/domain/electronStaticAnalysisValues.js +10 -17
- package/dist/domain/errors.js +33 -54
- package/dist/domain/eventProcessTree.js +20 -12
- package/dist/domain/evidence.js +2 -10
- package/dist/domain/evidenceBundle.js +3 -5
- package/dist/domain/evidenceCompletionLedger.js +6 -8
- package/dist/domain/functionComparison.js +4 -14
- package/dist/domain/functionComparisonNormalization.js +4 -11
- package/dist/domain/functionComparisonResults.js +25 -15
- package/dist/domain/functionComparisonSchemas.js +49 -33
- package/dist/domain/functionDossierEvidence.js +31 -156
- package/dist/domain/hopperStartupFailure.js +1 -1
- package/dist/domain/hopperValues.js +15 -136
- package/dist/domain/interfaceBuilderGraph.js +654 -0
- package/dist/domain/javascriptApplicationAnalysis.js +8 -112
- package/dist/domain/javascriptApplicationChangeGraph.js +22 -82
- package/dist/domain/javascriptApplicationEvidenceSchemas.js +59 -47
- package/dist/domain/javascriptApplicationGraph.js +3 -13
- package/dist/domain/javascriptApplicationGraphSchemas.js +12 -34
- package/dist/domain/javascriptApplicationVersionComparison.js +26 -30
- package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +123 -68
- package/dist/domain/javascriptApplicationVersionItems.js +120 -53
- package/dist/domain/javascriptApplicationVersionKeys.js +24 -11
- package/dist/domain/javascriptAstFingerprint.js +25 -58
- package/dist/domain/javascriptAstValues.js +9 -0
- package/dist/domain/javascriptExportInstrumentation.js +2 -4
- package/dist/domain/javascriptExportShapeComparison.js +6 -7
- package/dist/domain/javascriptExportShapeComparisonSchemas.js +68 -75
- package/dist/domain/javascriptExportShapeSelection.js +17 -19
- package/dist/domain/javascriptFeatureSeed.js +20 -12
- package/dist/domain/javascriptFeatureTrace.js +13 -67
- package/dist/domain/javascriptFeatureTraceSchemas.js +24 -52
- package/dist/domain/javascriptFeatureTraversal.js +1 -44
- package/dist/domain/javascriptNativeHandoff.js +1 -1
- package/dist/domain/javascriptReplay.js +24 -41
- package/dist/domain/javascriptRuntimeLoadState.js +3 -8
- package/dist/domain/javascriptRuntimeObservation.js +42 -103
- package/dist/domain/javascriptRuntimeReconciliation.js +1 -3
- package/dist/domain/javascriptRuntimeReconciliationActive.js +8 -17
- package/dist/domain/javascriptRuntimeReconciliationGraph.js +10 -23
- package/dist/domain/javascriptRuntimeReconciliationMatching.js +18 -12
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +16 -45
- package/dist/domain/javascriptRuntimeReconciliationResult.js +4 -5
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +8 -12
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +33 -48
- package/dist/domain/javascriptRuntimeStaticCandidates.js +3 -3
- package/dist/domain/javascriptSemanticAnalysis.js +12 -45
- package/dist/domain/javascriptSemanticAsyncEffects.js +1 -9
- package/dist/domain/javascriptSemanticCallResolution.js +6 -16
- package/dist/domain/javascriptSemanticCalls.js +5 -32
- package/dist/domain/javascriptSemanticChildProcesses.js +1 -11
- package/dist/domain/javascriptSemanticCoverage.js +11 -0
- package/dist/domain/javascriptSemanticDataEffectHelpers.js +1 -8
- package/dist/domain/javascriptSemanticDataEffects.js +1 -11
- package/dist/domain/javascriptSemanticFingerprints.js +3 -5
- package/dist/domain/javascriptSemanticGraphSchemas.js +25 -51
- package/dist/domain/javascriptSemanticGraphSerialization.js +2 -13
- package/dist/domain/javascriptSemanticIr.js +1 -29
- package/dist/domain/javascriptSemanticObjects.js +1 -5
- package/dist/domain/javascriptSemanticPrimitives.js +2 -9
- package/dist/domain/javascriptSemanticProjection.js +0 -18
- package/dist/domain/javascriptSemanticPromises.js +9 -16
- package/dist/domain/javascriptSemanticProvenance.js +16 -19
- package/dist/domain/javascriptSemanticQuery.js +17 -139
- package/dist/domain/javascriptSemanticQueryAssessment.js +9 -37
- package/dist/domain/javascriptSemanticQueryIdentity.js +2 -23
- package/dist/domain/javascriptSemanticQuerySchemas.js +14 -107
- package/dist/domain/javascriptSemanticResources.js +1 -6
- package/dist/domain/javascriptSemanticReturns.js +10 -27
- package/dist/domain/javascriptSemanticValueTypes.js +1 -0
- package/dist/domain/javascriptSemanticValues.js +44 -62
- package/dist/domain/javascriptStaticAnalysis.js +21 -43
- package/dist/domain/javascriptStaticAnalysisBundler.js +34 -93
- package/dist/domain/javascriptStaticAnalysisCalls.js +17 -16
- package/dist/domain/javascriptStaticAnalysisFindings.js +3 -19
- package/dist/domain/javascriptStaticAnalysisHelpers.js +47 -88
- package/dist/domain/javascriptStaticAnalysisState.js +1 -5
- package/dist/domain/javascriptStaticAnalysisTypes.js +1 -0
- package/dist/domain/jsonShape.js +7 -33
- package/dist/domain/localPath.js +7 -0
- package/dist/domain/managedApplicationGraph.js +9 -41
- package/dist/domain/managedApplicationGraphCoverage.js +24 -156
- package/dist/domain/managedApplicationGraphNodes.js +10 -13
- package/dist/domain/managedArtifact.js +14 -27
- package/dist/domain/managedMemberComparison.js +90 -92
- package/dist/domain/managedMemberComparisonCoverage.js +11 -33
- package/dist/domain/managedMemberComparisonItems.js +221 -0
- package/dist/domain/managedMemberComparisonMatch.js +8 -208
- package/dist/domain/managedNativeVerification.js +3 -6
- package/dist/domain/managedNativeVerificationMatch.js +55 -59
- package/dist/domain/managedNativeVerificationSchemas.js +83 -47
- package/dist/domain/managedReconstruction.js +19 -23
- package/dist/domain/managedRuntimeCorrelation.js +6 -9
- package/dist/domain/nativeApiBoundary.js +2 -7
- package/dist/domain/nativeInspection.js +0 -2
- package/dist/domain/nativeInvestigationGraph.js +351 -0
- package/dist/domain/nativeValueFlow.js +61 -0
- package/dist/domain/nodeRuntimeCharacterization.js +1 -5
- package/dist/domain/objcSwiftMetadata.js +254 -0
- package/dist/domain/peInspection.js +1 -1
- package/dist/domain/permissionPolicy.js +13 -0
- package/dist/domain/processCapture.js +76 -113
- package/dist/domain/processCaptureExample.js +68 -0
- package/dist/domain/processCaptureParsing.js +21 -0
- package/dist/domain/processCaptureReactiveSchema.js +68 -0
- package/dist/domain/processCaptureValidation.js +1 -7
- package/dist/domain/processComparison.js +3 -12
- package/dist/domain/processReactiveMatching.js +29 -50
- package/dist/domain/processReactiveRuntime.js +4 -8
- package/dist/domain/processReactiveScenario.js +28 -94
- package/dist/domain/processReactiveScenarioPreflight.js +5 -30
- package/dist/domain/processReactiveTransition.js +33 -18
- package/dist/domain/processScenario.js +56 -90
- package/dist/domain/processTraceComparison.js +0 -8
- package/dist/domain/processTraceEvaluation.js +14 -4
- package/dist/domain/processTraceSpecification.js +11 -17
- package/dist/domain/protocolCapture.js +3 -3
- package/dist/domain/reconstructionCoverage.js +62 -129
- package/dist/domain/reconstructionCoverageEvaluation.js +17 -17
- package/dist/domain/reconstructionObligationLedgerSchemas.js +45 -72
- package/dist/domain/reconstructionReadiness.js +1 -2
- package/dist/domain/reconstructionReadinessSchemas.js +36 -38
- package/dist/domain/reconstructionUnknowns.js +3 -3
- package/dist/domain/reconstructionVerification.js +13 -47
- package/dist/domain/reconstructionVerificationSchemas.js +50 -38
- package/dist/domain/referenceSourceGraph.js +53 -61
- package/dist/domain/referenceSourceImportParsing.js +1 -5
- package/dist/domain/replayMachine.js +19 -31
- package/dist/domain/replayMachineRun.js +15 -63
- package/dist/domain/replayMachineRuntime.js +41 -41
- package/dist/domain/replayMachineValues.js +84 -0
- package/dist/domain/residualUnknown.js +25 -30
- package/dist/domain/runtimeCharacterization.js +15 -16
- package/dist/domain/runtimeIdentification.js +48 -47
- package/dist/domain/sourceToBundleComparison.js +9 -85
- package/dist/domain/sourceToBundleComparisonSchemas.js +17 -42
- package/dist/domain/sourceToBundleSignals.js +4 -15
- package/dist/domain/staticRuntimeCorrelation.js +37 -49
- package/dist/domain/symbolAnalysis.js +10 -19
- package/dist/domain/unicodeCodePointOrder.js +12 -0
- package/dist/domain/webBundleAnalysis.js +70 -99
- package/dist/domain/webBundleAnalyzer.js +2 -4
- package/dist/domain/webBundleAnalyzerAst.js +0 -13
- package/dist/domain/webBundleAnalyzerInspection.js +26 -52
- package/dist/domain/webBundleAnalyzerResult.js +7 -24
- package/dist/domain/webCaptureDiff.js +33 -73
- package/dist/domain/webCaptureDiffSchemas.js +86 -0
- package/dist/domain/webContentArtifact.js +1 -10
- package/dist/domain/webMcpDiscovery.js +3 -14
- package/dist/domain/webScreenshot.js +58 -34
- package/dist/domain/zipPackageFormat.js +7 -0
- package/dist/dotnet/ManagedArtifactInspector.js +17 -50
- package/dist/dotnet/ManagedMemberInspector.js +27 -58
- package/dist/dotnet/ManagedMemberInspectorCore.js +14 -51
- package/dist/dotnet/ManagedMemberInstructionDecoder.js +2 -2
- package/dist/dotnet/ManagedMemberRows.js +16 -16
- package/dist/dotnet/ManagedMetadataHeaps.js +26 -8
- package/dist/dotnet/ManagedMetadataInventory.js +21 -30
- package/dist/dotnet/ManagedMetadataInventoryRows.js +57 -63
- package/dist/dotnet/ManagedMetadataLayout.js +3 -5
- package/dist/dotnet/ManagedMethodBodyReader.js +5 -30
- package/dist/dotnet/ManagedNativeBoundaryHelpers.js +19 -49
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +13 -41
- package/dist/dotnet/ManagedPeReader.js +2 -2
- package/dist/dotnet/ManagedStaticProvider.js +13 -71
- package/dist/generatedMcpToolCatalog.js +1 -1
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraAnalysisProfile.js +3 -8
- package/dist/ghidra/GhidraClient.js +4 -12
- package/dist/ghidra/GhidraClientWire.js +9 -5
- package/dist/ghidra/GhidraDefaults.js +0 -18
- package/dist/ghidra/GhidraDiagnostics.js +0 -2
- package/dist/ghidra/GhidraDoctor.js +16 -12
- package/dist/ghidra/GhidraFunctionValues.js +65 -108
- package/dist/ghidra/GhidraInstallation.js +98 -42
- package/dist/ghidra/GhidraInventoryValues.js +16 -85
- package/dist/ghidra/GhidraLauncher.js +0 -8
- package/dist/ghidra/GhidraProvider.js +15 -22
- package/dist/ghidra/GhidraProviderCapabilities.js +6 -47
- package/dist/ghidra/GhidraRequestQueue.js +6 -30
- package/dist/ghidra/GhidraResponseBuffer.js +1 -7
- package/dist/ghidra/GhidraSessionValues.js +0 -2
- package/dist/ghidra/GhidraTransport.js +4 -7
- package/dist/ghidra/protocol.js +2 -2
- package/dist/hopper/BridgeLauncher.js +112 -36
- package/dist/hopper/HopperAnalysisProfile.js +1 -1
- package/dist/hopper/HopperCleanup.js +6 -3
- package/dist/hopper/HopperClient.js +4 -7
- package/dist/hopper/HopperProcessDiagnostic.js +2 -2
- package/dist/hopper/HopperProvider.js +0 -16
- package/dist/hopper/HopperRequestQueue.js +21 -40
- package/dist/hopper/HopperResponseStream.js +1 -9
- package/dist/hopper/HopperSessionValues.js +3 -2
- package/dist/hopper/HopperTargetLease.js +119 -0
- package/dist/hopper/LinuxPrivateDisplayDiagnostic.js +24 -31
- package/dist/hopper/LinuxPrivateDisplayProbe.js +5 -16
- package/dist/hopper/protocol.js +6 -4
- package/dist/main/reload.js +0 -3
- package/dist/main/shutdown.js +1 -1
- package/dist/main/state.js +0 -9
- package/dist/main/transport.js +0 -5
- package/dist/mcpDoctor.js +25 -45
- package/dist/native/CommandRunner.js +18 -25
- package/dist/native/NativeMacOSProvider.js +13 -38
- package/dist/native/NativeMachoInspection.js +1 -1
- package/dist/process/BoundedFileBytes.js +17 -0
- package/dist/process/ExecFileOutput.js +11 -0
- package/dist/process/PendingOperations.js +8 -6
- package/dist/process/ProcessOwnership.js +3 -2
- package/dist/process/ProcessOwnershipObservation.js +0 -1
- package/dist/process/ProviderProcess.js +7 -28
- package/dist/reference/ReferenceSourceReader.js +8 -24
- package/dist/reference/ReferenceSourceReaderEntries.js +11 -39
- package/dist/reference/ReferenceSourceReaderFile.js +13 -31
- package/dist/reference/ReferenceSourceReaderPaths.js +3 -18
- package/dist/reference/ReferenceSourceReaderValidate.js +3 -6
- package/dist/replay/JavaScriptReplayWorker.js +3 -5
- package/dist/replay/LinuxJavaScriptReplayRunner.js +33 -29
- package/dist/replay/LinuxJavaScriptReplaySandbox.js +0 -19
- package/dist/replay/LinuxRuntimeClosure.js +1 -3
- package/dist/replay/ReplayOutcome.js +1 -3
- package/dist/replay/ReplayProcessLifecycle.js +32 -7
- package/dist/replay/ReplaySeccompFile.js +29 -0
- package/dist/replay/ReplayWorkerProtocol.js +0 -2
- package/dist/replay/SystemJavaScriptReplayHost.js +45 -74
- package/dist/server/ProcessCaptureElicitation.js +4 -8
- package/dist/server/createServer.js +6 -44
- package/dist/server/promptCompletion.js +37 -61
- package/dist/server/recordDerivedEvidence.js +1 -1
- package/dist/server/registerApplicationTools/characterization.js +4 -8
- package/dist/server/registerApplicationTools/compareExportShapes.js +5 -10
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +6 -10
- package/dist/server/registerApplicationTools/compareVersions.js +5 -10
- package/dist/server/registerApplicationTools/controlledReplay.js +3 -5
- package/dist/server/registerApplicationTools/coverage.js +6 -71
- package/dist/server/registerApplicationTools/helpers.js +1 -13
- package/dist/server/registerApplicationTools/obligationLedger.js +3 -2
- package/dist/server/registerApplicationTools/traceFeature.js +5 -9
- package/dist/server/registerApplicationTools/traceSemantics.js +5 -8
- package/dist/server/registerApplicationTools.js +0 -2
- package/dist/server/registerArtifactComparisonTool.js +23 -27
- package/dist/server/registerBrowserScenarioTool.js +4 -4
- package/dist/server/registerBrowserTools.js +5 -8
- package/dist/server/registerBundleComparisonTool.js +5 -7
- package/dist/server/registerCloseLifecycleTool.js +2 -16
- package/dist/server/registerElectronTools.js +10 -29
- package/dist/server/registerEnhancedTools.js +10 -11
- package/dist/server/registerEvidenceTools.js +15 -22
- package/dist/server/registerFunctionComparisonTool.js +30 -25
- package/dist/server/registerInvestigationTools/helpers.js +3 -32
- package/dist/server/registerInvestigationTools.js +20 -116
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +6 -4
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +15 -17
- package/dist/server/registerManagedWorkflowTools/evidence.js +19 -35
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +3 -8
- package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +3 -8
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +10 -14
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +9 -15
- package/dist/server/registerOfficialTools.js +1 -43
- package/dist/server/registerProcessComparisonTool.js +33 -23
- package/dist/server/registerPrompts.js +19 -17
- package/dist/server/registerSessionRecordTools.js +10 -156
- package/dist/server/registerSessionStatusTool.js +6 -69
- package/dist/server/registerSessionTools.js +10 -51
- package/dist/server/sessionAvailabilityPolicy.js +0 -2
- package/dist/server/sessionEvidence.js +9 -15
- package/dist/server/sessionToolPolicies.js +0 -8
- package/dist/server/toolResult.js +14 -50
- package/dist/serverIdentity.js +0 -20
- package/install.sh +1 -1
- package/package.json +11 -7
- package/scripts/electron-active-hook.cjs +23 -55
- package/scripts/hopper-demo-x11.py +13 -32
- package/skills/reverse-engineer-anything/SKILL.md +47 -12
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +6 -4
- package/skills/reverse-engineer-anything/references/javascript-applications.md +13 -13
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +12 -8
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +0 -188
- package/dist/application/BoundedJsonFiles.js +0 -86
- package/dist/application/CrossVersionInventory.js +0 -129
- package/dist/application/CrossVersionInvestigation.js +0 -301
- package/dist/application/CrossVersionInvestigationCollections.js +0 -23
- package/dist/application/CrossVersionInvestigationEvidence.js +0 -64
- package/dist/application/CrossVersionInvestigationReplay.js +0 -170
- package/dist/application/DeferredFileAuthorization.js +0 -61
- package/dist/application/DoctorProjection.js +0 -43
- package/dist/application/EnhancedToolPagination.js +0 -22
- package/dist/application/EvidenceReferenceResolver.js +0 -26
- package/dist/application/InvestigationWorkspaceStore.js +0 -204
- package/dist/application/ProcessPairedExperiment.js +0 -72
- package/dist/application/ReconstructionCoverageWorkspaceStore.js +0 -31
- package/dist/application/ReconstructionReadinessService.js +0 -48
- package/dist/cliInvestigationCommands.js +0 -173
- package/dist/domain/boundedCartesianProjection.js +0 -15
- package/dist/domain/investigationWorkspace.js +0 -347
- package/dist/domain/jsonLimits.js +0 -26
- package/dist/domain/knownPageCoverage.js +0 -29
- package/dist/domain/processPairedExperiment.js +0 -128
- package/dist/server/javascriptApplicationResult.js +0 -106
- package/dist/server/permissionFailure.js +0 -12
- package/dist/server/registerApplicationTools/readiness.js +0 -50
- package/dist/server/registerEvidenceResources.js +0 -315
- package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -65
- package/dist/server/registerReconstructionObligationLedgerResource.js +0 -32
- package/dist/server/registerReconstructionReadinessResource.js +0 -32
|
@@ -5,7 +5,6 @@ import java.io.BufferedReader;
|
|
|
5
5
|
import java.io.BufferedWriter;
|
|
6
6
|
import java.io.ByteArrayOutputStream;
|
|
7
7
|
import java.io.IOException;
|
|
8
|
-
import java.io.Reader;
|
|
9
8
|
import java.math.BigInteger;
|
|
10
9
|
import java.net.InetAddress;
|
|
11
10
|
import java.net.InetSocketAddress;
|
|
@@ -26,8 +25,11 @@ import java.security.MessageDigest;
|
|
|
26
25
|
import java.util.ArrayList;
|
|
27
26
|
import java.util.Comparator;
|
|
28
27
|
import java.util.HashSet;
|
|
28
|
+
import java.util.IdentityHashMap;
|
|
29
|
+
import java.util.Iterator;
|
|
29
30
|
import java.util.List;
|
|
30
31
|
import java.util.Locale;
|
|
32
|
+
import java.util.Map;
|
|
31
33
|
import java.util.Set;
|
|
32
34
|
import java.util.TreeMap;
|
|
33
35
|
import java.util.regex.Pattern;
|
|
@@ -75,32 +77,17 @@ import ghidra.program.model.pcode.FunctionPrototype;
|
|
|
75
77
|
import ghidra.program.model.pcode.HighFunction;
|
|
76
78
|
import ghidra.program.model.pcode.HighSymbol;
|
|
77
79
|
import ghidra.program.model.pcode.JumpTable;
|
|
80
|
+
import ghidra.program.model.pcode.PcodeOp;
|
|
81
|
+
import ghidra.program.model.pcode.PcodeOpAST;
|
|
82
|
+
import ghidra.program.model.pcode.Varnode;
|
|
83
|
+
import ghidra.program.model.pcode.VarnodeAST;
|
|
78
84
|
|
|
79
85
|
public final class ReaGhidraBridge extends HeadlessScript {
|
|
80
|
-
private static final int
|
|
81
|
-
private static final int
|
|
82
|
-
private static final int
|
|
83
|
-
private static final int MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
84
|
-
private static final int RESPONSE_ENVELOPE_RESERVE_BYTES = 32 * 1024;
|
|
85
|
-
private static final int MAX_NATIVE_API_RESPONSE_BYTES = 192 * 1024;
|
|
86
|
-
private static final int MAX_INVENTORY_ITEMS = 1_000_000;
|
|
87
|
-
private static final int MAX_FUNCTION_ITEMS = 100_000;
|
|
88
|
-
private static final int MAX_FUNCTION_INSTRUCTIONS = 100_000;
|
|
89
|
-
private static final int MAX_NATIVE_API_PARAMETERS = 64;
|
|
90
|
-
private static final int MAX_NATIVE_API_JUMP_TABLES = 4;
|
|
91
|
-
private static final int MAX_NATIVE_API_LOAD_TABLES = 4;
|
|
92
|
-
private static final int MAX_NATIVE_API_MAPPINGS = 32;
|
|
93
|
-
private static final int DECOMPILE_TIMEOUT_SECONDS = 30;
|
|
94
|
-
private static final int DECOMPILE_PAYLOAD_MBYTES = 8;
|
|
95
|
-
private static final int MAX_LIST_VALUE_CODE_POINTS = 1_024;
|
|
96
|
-
private static final int MAX_SEARCH_VALUE_CODE_POINTS = 4_096;
|
|
97
|
-
private static final int MAX_REGEX_CANDIDATE_CHARACTERS = 4_096;
|
|
98
|
-
private static final long MAX_REGEX_BACKTRACKING_PATHS = 10_000;
|
|
99
|
-
private static final long MAX_LITERAL_SEARCH_WORK_UNITS = 1_000_000;
|
|
100
|
-
private static final long MAX_REGEX_SEARCH_WORK_UNITS = 1_000_000;
|
|
86
|
+
private static final int MAX_HIGH_PCODE_OPS = 3000;
|
|
87
|
+
private static final int MAX_HIGH_PCODE_INPUTS_PER_OP = 64;
|
|
88
|
+
private static final int MAX_HIGH_PCODE_DEF_USE_EDGES = 12000;
|
|
101
89
|
private static final Gson GSON = new GsonBuilder().serializeNulls().create();
|
|
102
90
|
private static final Set<String> DESCRIPTOR_KEYS = Set.of(
|
|
103
|
-
"schema_version",
|
|
104
91
|
"transport",
|
|
105
92
|
"endpoint_path",
|
|
106
93
|
"token",
|
|
@@ -218,7 +205,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
218
205
|
server.bind(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0));
|
|
219
206
|
InetSocketAddress address = (InetSocketAddress) server.getLocalAddress();
|
|
220
207
|
JsonObject endpoint = new JsonObject();
|
|
221
|
-
endpoint.addProperty("schema_version", 1);
|
|
222
208
|
endpoint.addProperty("host", "127.0.0.1");
|
|
223
209
|
endpoint.addProperty("port", address.getPort());
|
|
224
210
|
Files.writeString(
|
|
@@ -261,7 +247,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
261
247
|
BufferedReader reader,
|
|
262
248
|
BufferedWriter writer) throws Exception {
|
|
263
249
|
while (true) {
|
|
264
|
-
String line =
|
|
250
|
+
String line = reader.readLine();
|
|
265
251
|
if (line == null) {
|
|
266
252
|
return;
|
|
267
253
|
}
|
|
@@ -359,7 +345,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
359
345
|
|
|
360
346
|
JsonObject result = new JsonObject();
|
|
361
347
|
result.addProperty("name", "REA Ghidra bridge");
|
|
362
|
-
result.addProperty("bridge_version", BRIDGE_VERSION);
|
|
363
348
|
result.addProperty("run_id", descriptor.runId);
|
|
364
349
|
result.addProperty("profile_digest", descriptor.profileDigest);
|
|
365
350
|
result.add("provider", provider);
|
|
@@ -373,9 +358,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
373
358
|
|
|
374
359
|
private void initializeDecompiler() {
|
|
375
360
|
DecompileOptions options = new DecompileOptions();
|
|
376
|
-
options.
|
|
377
|
-
options.setMaxPayloadMBytes(DECOMPILE_PAYLOAD_MBYTES);
|
|
378
|
-
options.setMaxInstructions(MAX_FUNCTION_INSTRUCTIONS);
|
|
361
|
+
options.setMaxInstructions(Integer.MAX_VALUE);
|
|
379
362
|
decompiler = new DecompInterface();
|
|
380
363
|
decompiler.setOptions(options);
|
|
381
364
|
decompiler.toggleCCode(true);
|
|
@@ -398,43 +381,19 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
398
381
|
requireKeys(params, Set.of("document", "procedure"));
|
|
399
382
|
requireDocument(params);
|
|
400
383
|
Function function = resolveProcedure(requireString(params, "procedure"));
|
|
401
|
-
InstructionScan scan = scanInstructions(function,
|
|
402
|
-
if (scan.truncated) {
|
|
403
|
-
throw functionLimit("Procedure assembly exceeds the 100000-instruction limit");
|
|
404
|
-
}
|
|
384
|
+
InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
|
|
405
385
|
return GSON.toJsonTree(renderAssembly(scan.instructions));
|
|
406
386
|
}
|
|
407
387
|
|
|
408
388
|
private JsonObject readFunctionInstructions(JsonObject params) throws Exception {
|
|
409
|
-
requireKeys(params, Set.of("document", "procedure"
|
|
389
|
+
requireKeys(params, Set.of("document", "procedure"));
|
|
410
390
|
requireDocument(params);
|
|
411
391
|
Function function = resolveProcedure(requireString(params, "procedure"));
|
|
412
|
-
|
|
413
|
-
int limit = requireBoundedInteger(params, "limit", 1, 500);
|
|
414
|
-
InstructionScan scan = scanInstructions(function, offset + limit + 1);
|
|
415
|
-
int start = Math.min(offset, scan.instructions.size());
|
|
416
|
-
int end = Math.min(scan.instructions.size(), start + limit);
|
|
392
|
+
InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
|
|
417
393
|
JsonArray items = new JsonArray();
|
|
418
|
-
for (String line : renderAssemblyLines(scan.instructions
|
|
394
|
+
for (String line : renderAssemblyLines(scan.instructions)) {
|
|
419
395
|
items.add(line);
|
|
420
396
|
}
|
|
421
|
-
boolean hasMore = scan.truncated || end < scan.instructions.size();
|
|
422
|
-
JsonObject instructions = new JsonObject();
|
|
423
|
-
instructions.add("items", items);
|
|
424
|
-
if (scan.truncated) {
|
|
425
|
-
instructions.add("total", JsonNull.INSTANCE);
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
instructions.addProperty("total", scan.instructions.size());
|
|
429
|
-
}
|
|
430
|
-
instructions.addProperty("returned", items.size());
|
|
431
|
-
instructions.addProperty("truncated", hasMore);
|
|
432
|
-
if (hasMore) {
|
|
433
|
-
instructions.addProperty("next_offset", end);
|
|
434
|
-
}
|
|
435
|
-
else {
|
|
436
|
-
instructions.add("next_offset", JsonNull.INSTANCE);
|
|
437
|
-
}
|
|
438
397
|
|
|
439
398
|
JsonArray limitations = new JsonArray();
|
|
440
399
|
limitations.add("Instruction text and ordering are Ghidra-specific representations.");
|
|
@@ -443,9 +402,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
443
402
|
);
|
|
444
403
|
JsonObject result = new JsonObject();
|
|
445
404
|
result.add("procedure", procedureIdentity(function));
|
|
446
|
-
result.add("instructions",
|
|
447
|
-
result.addProperty("instructions_scanned", scan.instructions.size());
|
|
448
|
-
result.addProperty("instruction_scan_truncated", scan.truncated);
|
|
405
|
+
result.add("instructions", items);
|
|
449
406
|
result.add("limitations", limitations);
|
|
450
407
|
return result;
|
|
451
408
|
}
|
|
@@ -457,9 +414,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
457
414
|
Set<Function> observed = callers
|
|
458
415
|
? function.getCallingFunctions(monitor)
|
|
459
416
|
: function.getCalledFunctions(monitor);
|
|
460
|
-
if (observed.size() > MAX_FUNCTION_ITEMS) {
|
|
461
|
-
throw functionLimit("Resolved call set exceeds the 100000-item limit");
|
|
462
|
-
}
|
|
463
417
|
List<Function> ordered = new ArrayList<>(observed);
|
|
464
418
|
ordered.sort(Comparator.comparing(Function::getEntryPoint));
|
|
465
419
|
JsonArray result = new JsonArray();
|
|
@@ -490,10 +444,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
490
444
|
Set.of(
|
|
491
445
|
"document",
|
|
492
446
|
"procedure",
|
|
493
|
-
"direction"
|
|
494
|
-
"offset",
|
|
495
|
-
"limit",
|
|
496
|
-
"max_instructions"
|
|
447
|
+
"direction"
|
|
497
448
|
)
|
|
498
449
|
);
|
|
499
450
|
requireDocument(params);
|
|
@@ -502,10 +453,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
502
453
|
if (!direction.equals("incoming") && !direction.equals("outgoing")) {
|
|
503
454
|
throw new RequestFailure("invalid_request", "direction must be incoming or outgoing");
|
|
504
455
|
}
|
|
505
|
-
|
|
506
|
-
int limit = requireBoundedInteger(params, "limit", 1, 500);
|
|
507
|
-
int maximum = requireBoundedInteger(params, "max_instructions", 1, 5_000);
|
|
508
|
-
InstructionScan scan = scanInstructions(function, maximum);
|
|
456
|
+
InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
|
|
509
457
|
List<Reference> references = collectReferences(scan.instructions, direction);
|
|
510
458
|
JsonArray edges = new JsonArray();
|
|
511
459
|
for (Reference reference : references) {
|
|
@@ -514,9 +462,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
514
462
|
JsonObject result = new JsonObject();
|
|
515
463
|
result.add("procedure", procedureIdentity(function));
|
|
516
464
|
result.addProperty("direction", direction);
|
|
517
|
-
result.add("references",
|
|
518
|
-
result.addProperty("instructions_scanned", scan.instructions.size());
|
|
519
|
-
result.addProperty("instruction_scan_truncated", scan.truncated);
|
|
465
|
+
result.add("references", edges);
|
|
520
466
|
return result;
|
|
521
467
|
}
|
|
522
468
|
|
|
@@ -533,10 +479,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
533
479
|
continue;
|
|
534
480
|
}
|
|
535
481
|
sources.add(reference.getFromAddress());
|
|
536
|
-
if (sources.size() > MAX_FUNCTION_ITEMS) {
|
|
537
|
-
throw functionLimit("Cross-reference set exceeds the 100000-item limit");
|
|
538
482
|
}
|
|
539
|
-
}
|
|
540
483
|
List<Address> ordered = new ArrayList<>(sources);
|
|
541
484
|
ordered.sort(Address::compareTo);
|
|
542
485
|
JsonArray result = new JsonArray();
|
|
@@ -547,48 +490,9 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
547
490
|
}
|
|
548
491
|
|
|
549
492
|
private JsonObject analyzeFunction(JsonObject params) throws Exception {
|
|
550
|
-
requireKeys(
|
|
551
|
-
params,
|
|
552
|
-
Set.of(
|
|
553
|
-
"procedure",
|
|
554
|
-
"include_assembly",
|
|
555
|
-
"limit",
|
|
556
|
-
"max_pseudocode_chars",
|
|
557
|
-
"max_instructions",
|
|
558
|
-
"pseudocode_offset",
|
|
559
|
-
"assembly_offset",
|
|
560
|
-
"collection_offset"
|
|
561
|
-
)
|
|
562
|
-
);
|
|
493
|
+
requireKeys(params, Set.of("procedure"));
|
|
563
494
|
Function function = resolveProcedure(requireString(params, "procedure"));
|
|
564
|
-
|
|
565
|
-
int limit = requireBoundedInteger(params, "limit", 1, 500);
|
|
566
|
-
int maximumCharacters = requireBoundedInteger(
|
|
567
|
-
params,
|
|
568
|
-
"max_pseudocode_chars",
|
|
569
|
-
1,
|
|
570
|
-
100_000
|
|
571
|
-
);
|
|
572
|
-
int maximumInstructions = requireBoundedInteger(
|
|
573
|
-
params,
|
|
574
|
-
"max_instructions",
|
|
575
|
-
1,
|
|
576
|
-
5_000
|
|
577
|
-
);
|
|
578
|
-
int pseudocodeOffset = requireBoundedInteger(
|
|
579
|
-
params,
|
|
580
|
-
"pseudocode_offset",
|
|
581
|
-
0,
|
|
582
|
-
Integer.MAX_VALUE
|
|
583
|
-
);
|
|
584
|
-
int assemblyOffset = requireBoundedInteger(
|
|
585
|
-
params,
|
|
586
|
-
"assembly_offset",
|
|
587
|
-
0,
|
|
588
|
-
Integer.MAX_VALUE
|
|
589
|
-
);
|
|
590
|
-
JsonObject offsets = requireCollectionOffsets(params);
|
|
591
|
-
InstructionScan scan = scanInstructions(function, maximumInstructions);
|
|
495
|
+
InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
|
|
592
496
|
DecompileResults decompilation = decompile(function);
|
|
593
497
|
String pseudocode = decompiledText(decompilation);
|
|
594
498
|
if (pseudocode == null) {
|
|
@@ -607,88 +511,29 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
607
511
|
JsonArray referencedStrings = referencedStrings(outgoingReferences);
|
|
608
512
|
JsonArray referencedNames = referencedNames(outgoingReferences);
|
|
609
513
|
JsonArray blocks = basicBlockValues(function);
|
|
610
|
-
JsonArray assembly =
|
|
611
|
-
|
|
612
|
-
: new JsonArray();
|
|
514
|
+
JsonArray assembly = GSON.toJsonTree(renderAssemblyLines(scan.instructions))
|
|
515
|
+
.getAsJsonArray();
|
|
613
516
|
|
|
614
517
|
JsonObject procedure = procedureIdentity(function);
|
|
615
518
|
procedure.addProperty("signature", function.getPrototypeString(false, true));
|
|
616
519
|
procedure.add("locals", functionLocals(function));
|
|
617
520
|
JsonObject result = new JsonObject();
|
|
618
521
|
result.add("procedure", procedure);
|
|
619
|
-
result.
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
);
|
|
623
|
-
result.add(
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
);
|
|
629
|
-
result.add(
|
|
630
|
-
"comments",
|
|
631
|
-
bounded(comments, collectionOffset(offsets, "comments"), limit, !scan.truncated)
|
|
632
|
-
);
|
|
633
|
-
result.add("callers", bounded(callers, collectionOffset(offsets, "callers"), limit, true));
|
|
634
|
-
result.add("callees", bounded(callees, collectionOffset(offsets, "callees"), limit, true));
|
|
635
|
-
result.add(
|
|
636
|
-
"incoming_references",
|
|
637
|
-
bounded(
|
|
638
|
-
incoming,
|
|
639
|
-
collectionOffset(offsets, "incoming_references"),
|
|
640
|
-
limit,
|
|
641
|
-
!scan.truncated
|
|
642
|
-
)
|
|
643
|
-
);
|
|
644
|
-
result.add(
|
|
645
|
-
"outgoing_references",
|
|
646
|
-
bounded(
|
|
647
|
-
outgoing,
|
|
648
|
-
collectionOffset(offsets, "outgoing_references"),
|
|
649
|
-
limit,
|
|
650
|
-
!scan.truncated
|
|
651
|
-
)
|
|
652
|
-
);
|
|
653
|
-
result.add(
|
|
654
|
-
"referenced_strings",
|
|
655
|
-
bounded(
|
|
656
|
-
referencedStrings,
|
|
657
|
-
collectionOffset(offsets, "referenced_strings"),
|
|
658
|
-
limit,
|
|
659
|
-
!scan.truncated
|
|
660
|
-
)
|
|
661
|
-
);
|
|
662
|
-
result.add(
|
|
663
|
-
"referenced_names",
|
|
664
|
-
bounded(
|
|
665
|
-
referencedNames,
|
|
666
|
-
collectionOffset(offsets, "referenced_names"),
|
|
667
|
-
limit,
|
|
668
|
-
!scan.truncated
|
|
669
|
-
)
|
|
670
|
-
);
|
|
671
|
-
result.add(
|
|
672
|
-
"basic_blocks",
|
|
673
|
-
bounded(blocks, collectionOffset(offsets, "basic_blocks"), limit, true)
|
|
674
|
-
);
|
|
675
|
-
JsonObject instructionScan = new JsonObject();
|
|
676
|
-
instructionScan.addProperty("scanned", scan.instructions.size());
|
|
677
|
-
instructionScan.addProperty("truncated", scan.truncated);
|
|
678
|
-
result.add("instruction_scan", instructionScan);
|
|
679
|
-
int nativeApiBudget = Math.min(
|
|
680
|
-
MAX_NATIVE_API_RESPONSE_BYTES,
|
|
681
|
-
Math.max(
|
|
682
|
-
0,
|
|
683
|
-
MAX_RESPONSE_BYTES -
|
|
684
|
-
encodedUtf8Length(result) -
|
|
685
|
-
RESPONSE_ENVELOPE_RESERVE_BYTES
|
|
686
|
-
)
|
|
687
|
-
);
|
|
522
|
+
result.addProperty("pseudocode", pseudocode);
|
|
523
|
+
result.add("assembly", assembly);
|
|
524
|
+
result.add("comments", comments);
|
|
525
|
+
result.add("callers", callers);
|
|
526
|
+
result.add("callees", callees);
|
|
527
|
+
result.add("incoming_references", incoming);
|
|
528
|
+
result.add("outgoing_references", outgoing);
|
|
529
|
+
result.add("referenced_strings", referencedStrings);
|
|
530
|
+
result.add("referenced_names", referencedNames);
|
|
531
|
+
result.add("basic_blocks", blocks);
|
|
688
532
|
result.add(
|
|
689
533
|
"native_api",
|
|
690
|
-
nativeApiBoundary(function, decompilation
|
|
534
|
+
nativeApiBoundary(function, decompilation)
|
|
691
535
|
);
|
|
536
|
+
result.add("native_value_flow", nativeValueFlow(decompilation));
|
|
692
537
|
JsonArray limitations = new JsonArray();
|
|
693
538
|
limitations.add(
|
|
694
539
|
"Unresolved computed or indirect flows without target addresses are not represented as reference edges."
|
|
@@ -706,6 +551,205 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
706
551
|
return result;
|
|
707
552
|
}
|
|
708
553
|
|
|
554
|
+
private JsonObject nativeValueFlow(DecompileResults decompilation) throws Exception {
|
|
555
|
+
HighFunction highFunction =
|
|
556
|
+
decompilation == null ? null : decompilation.getHighFunction();
|
|
557
|
+
if (highFunction == null) {
|
|
558
|
+
JsonObject unavailable = new JsonObject();
|
|
559
|
+
unavailable.addProperty("available", false);
|
|
560
|
+
unavailable.addProperty(
|
|
561
|
+
"reason",
|
|
562
|
+
"Ghidra has no HighFunction p-code model for this procedure"
|
|
563
|
+
);
|
|
564
|
+
JsonArray limitations = new JsonArray();
|
|
565
|
+
limitations.add(
|
|
566
|
+
"No p-code operations or def-use relationships were recovered for this procedure."
|
|
567
|
+
);
|
|
568
|
+
unavailable.add("limitations", limitations);
|
|
569
|
+
return unavailable;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
List<PcodeOpAST> selected = new ArrayList<>();
|
|
573
|
+
int omittedOperationsLowerBound = 0;
|
|
574
|
+
Iterator<PcodeOpAST> operations = highFunction.getPcodeOps();
|
|
575
|
+
while (operations.hasNext()) {
|
|
576
|
+
monitor.checkCancelled();
|
|
577
|
+
PcodeOpAST operation = operations.next();
|
|
578
|
+
if (selected.size() >= MAX_HIGH_PCODE_OPS) {
|
|
579
|
+
omittedOperationsLowerBound = 1;
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
selected.add(operation);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
Map<PcodeOpAST, String> operationIds = new IdentityHashMap<>();
|
|
586
|
+
for (PcodeOpAST operation : selected) {
|
|
587
|
+
operationIds.put(operation, pcodeOperationId(operation));
|
|
588
|
+
}
|
|
589
|
+
JsonArray operationValues = new JsonArray();
|
|
590
|
+
JsonArray defUse = new JsonArray();
|
|
591
|
+
JsonArray effects = new JsonArray();
|
|
592
|
+
int omittedInputs = 0;
|
|
593
|
+
int omittedEdges = 0;
|
|
594
|
+
for (PcodeOpAST operation : selected) {
|
|
595
|
+
monitor.checkCancelled();
|
|
596
|
+
String operationId = operationIds.get(operation);
|
|
597
|
+
JsonObject value = new JsonObject();
|
|
598
|
+
value.addProperty("id", operationId);
|
|
599
|
+
value.addProperty(
|
|
600
|
+
"address",
|
|
601
|
+
canonicalAddress(operation.getSeqnum().getTarget())
|
|
602
|
+
);
|
|
603
|
+
value.addProperty("sequence", operation.getSeqnum().getTime());
|
|
604
|
+
value.addProperty("opcode", operation.getMnemonic());
|
|
605
|
+
value.addProperty("is_dead", operation.isDead());
|
|
606
|
+
JsonArray inputs = new JsonArray();
|
|
607
|
+
int inputCount = operation.getNumInputs();
|
|
608
|
+
int capturedInputs = Math.min(inputCount, MAX_HIGH_PCODE_INPUTS_PER_OP);
|
|
609
|
+
for (int inputIndex = 0; inputIndex < capturedInputs; inputIndex += 1) {
|
|
610
|
+
Varnode input = operation.getInput(inputIndex);
|
|
611
|
+
inputs.add(varnodeValue(input));
|
|
612
|
+
if (input instanceof VarnodeAST astInput) {
|
|
613
|
+
PcodeOp definition = astInput.getDef();
|
|
614
|
+
if (definition instanceof PcodeOpAST astDefinition) {
|
|
615
|
+
String definitionId = operationIds.get(astDefinition);
|
|
616
|
+
if (definitionId == null || defUse.size() >= MAX_HIGH_PCODE_DEF_USE_EDGES) {
|
|
617
|
+
omittedEdges += 1;
|
|
618
|
+
} else {
|
|
619
|
+
JsonObject edge = new JsonObject();
|
|
620
|
+
edge.addProperty("definition", definitionId);
|
|
621
|
+
edge.addProperty("use", operationId);
|
|
622
|
+
edge.addProperty("input_index", inputIndex);
|
|
623
|
+
defUse.add(edge);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
omittedInputs += inputCount - capturedInputs;
|
|
629
|
+
value.add("inputs", inputs);
|
|
630
|
+
Varnode output = operation.getOutput();
|
|
631
|
+
value.add("output", output == null ? JsonNull.INSTANCE : varnodeValue(output));
|
|
632
|
+
operationValues.add(value);
|
|
633
|
+
addPcodeEffect(effects, operation, operationId);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
JsonObject result = new JsonObject();
|
|
637
|
+
result.addProperty("available", true);
|
|
638
|
+
result.addProperty("provenance", "ghidra-high-pcode");
|
|
639
|
+
result.add("operations", operationValues);
|
|
640
|
+
result.add("def_use", defUse);
|
|
641
|
+
result.add("effects", effects);
|
|
642
|
+
boolean truncated = omittedOperationsLowerBound > 0 ||
|
|
643
|
+
omittedInputs > 0 || omittedEdges > 0;
|
|
644
|
+
result.addProperty("truncated", truncated);
|
|
645
|
+
result.addProperty(
|
|
646
|
+
"omitted_operations_lower_bound",
|
|
647
|
+
omittedOperationsLowerBound
|
|
648
|
+
);
|
|
649
|
+
result.addProperty("known_omitted_inputs", omittedInputs);
|
|
650
|
+
result.addProperty("known_omitted_edges", omittedEdges);
|
|
651
|
+
JsonArray limitations = new JsonArray();
|
|
652
|
+
limitations.add(
|
|
653
|
+
"High p-code is a decompiler-derived intra-function representation, not original source or runtime behavior; dead operations are retained and marked."
|
|
654
|
+
);
|
|
655
|
+
limitations.add(
|
|
656
|
+
"Def-use links do not cross function boundaries and may omit memory aliasing, call side effects, or unresolved indirect flow."
|
|
657
|
+
);
|
|
658
|
+
limitations.add(
|
|
659
|
+
"Memory read/write effects include stack and temporary memory operations; they do not identify persistent state or business meaning."
|
|
660
|
+
);
|
|
661
|
+
limitations.add(
|
|
662
|
+
"Operation, input, and def-use limits can truncate large functions; inspect truncation, omitted lower bounds, and known omitted counts before drawing conclusions."
|
|
663
|
+
);
|
|
664
|
+
limitations.add(
|
|
665
|
+
"Known omitted-edge counts cover links omitted from retained operations; omitted operations may contain additional uncounted links."
|
|
666
|
+
);
|
|
667
|
+
result.add("limitations", limitations);
|
|
668
|
+
return result;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
private String pcodeOperationId(PcodeOpAST operation) {
|
|
672
|
+
return canonicalAddress(operation.getSeqnum().getTarget()) + "#" +
|
|
673
|
+
operation.getSeqnum().getTime();
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
private JsonObject varnodeValue(Varnode varnode) {
|
|
677
|
+
JsonObject result = new JsonObject();
|
|
678
|
+
String kind = varnode.isConstant()
|
|
679
|
+
? "constant"
|
|
680
|
+
: varnode.isAddress()
|
|
681
|
+
? "address"
|
|
682
|
+
: varnode.isRegister()
|
|
683
|
+
? "register"
|
|
684
|
+
: varnode.isUnique() ? "unique" : "other";
|
|
685
|
+
result.addProperty("kind", kind);
|
|
686
|
+
result.addProperty("size_bytes", varnode.getSize());
|
|
687
|
+
result.add(
|
|
688
|
+
"location",
|
|
689
|
+
varnode.isConstant()
|
|
690
|
+
? JsonNull.INSTANCE
|
|
691
|
+
: GSON.toJsonTree(canonicalAddress(varnode.getAddress()))
|
|
692
|
+
);
|
|
693
|
+
result.add(
|
|
694
|
+
"constant_hex",
|
|
695
|
+
varnode.isConstant()
|
|
696
|
+
? GSON.toJsonTree(Long.toUnsignedString(varnode.getOffset(), 16))
|
|
697
|
+
: JsonNull.INSTANCE
|
|
698
|
+
);
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private void addPcodeEffect(
|
|
703
|
+
JsonArray destination,
|
|
704
|
+
PcodeOp operation,
|
|
705
|
+
String operationId
|
|
706
|
+
) {
|
|
707
|
+
String kind = null;
|
|
708
|
+
Integer operandInput = null;
|
|
709
|
+
Integer valueInput = null;
|
|
710
|
+
switch (operation.getOpcode()) {
|
|
711
|
+
case PcodeOp.LOAD -> {
|
|
712
|
+
kind = "memory_read";
|
|
713
|
+
operandInput = 1;
|
|
714
|
+
}
|
|
715
|
+
case PcodeOp.STORE -> {
|
|
716
|
+
kind = "memory_write";
|
|
717
|
+
operandInput = 1;
|
|
718
|
+
valueInput = 2;
|
|
719
|
+
}
|
|
720
|
+
case PcodeOp.CBRANCH -> {
|
|
721
|
+
kind = "conditional_branch";
|
|
722
|
+
valueInput = 1;
|
|
723
|
+
}
|
|
724
|
+
case PcodeOp.BRANCHIND -> {
|
|
725
|
+
kind = "indirect_branch";
|
|
726
|
+
operandInput = 0;
|
|
727
|
+
}
|
|
728
|
+
case PcodeOp.CALL -> {
|
|
729
|
+
kind = "direct_call";
|
|
730
|
+
operandInput = 0;
|
|
731
|
+
}
|
|
732
|
+
case PcodeOp.CALLIND -> {
|
|
733
|
+
kind = "indirect_call";
|
|
734
|
+
operandInput = 0;
|
|
735
|
+
}
|
|
736
|
+
default -> { }
|
|
737
|
+
}
|
|
738
|
+
if (kind == null) return;
|
|
739
|
+
JsonObject effect = new JsonObject();
|
|
740
|
+
effect.addProperty("operation", operationId);
|
|
741
|
+
effect.addProperty("kind", kind);
|
|
742
|
+
effect.add(
|
|
743
|
+
"operand_input",
|
|
744
|
+
operandInput == null ? JsonNull.INSTANCE : GSON.toJsonTree(operandInput)
|
|
745
|
+
);
|
|
746
|
+
effect.add(
|
|
747
|
+
"value_input",
|
|
748
|
+
valueInput == null ? JsonNull.INSTANCE : GSON.toJsonTree(valueInput)
|
|
749
|
+
);
|
|
750
|
+
destination.add(effect);
|
|
751
|
+
}
|
|
752
|
+
|
|
709
753
|
private JsonElement addressName(JsonObject params) {
|
|
710
754
|
requireKeys(params, Set.of("document", "address"));
|
|
711
755
|
requireDocument(params);
|
|
@@ -721,8 +765,8 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
721
765
|
return result;
|
|
722
766
|
}
|
|
723
767
|
|
|
724
|
-
private
|
|
725
|
-
requireKeys(params, Set.of("document", "address"
|
|
768
|
+
private JsonArray listNames(JsonObject params) throws Exception {
|
|
769
|
+
requireKeys(params, Set.of("document", "address"));
|
|
726
770
|
requireDocument(params);
|
|
727
771
|
List<InventoryItem> inventory = names();
|
|
728
772
|
String requested = optionalString(params, "address");
|
|
@@ -732,26 +776,16 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
732
776
|
.filter(item -> item.address.equals(address))
|
|
733
777
|
.toList();
|
|
734
778
|
}
|
|
735
|
-
return
|
|
736
|
-
inventory,
|
|
737
|
-
requireBoundedInteger(params, "offset", 0, Integer.MAX_VALUE),
|
|
738
|
-
requireBoundedInteger(params, "limit", 1, 500),
|
|
739
|
-
"symbol"
|
|
740
|
-
);
|
|
779
|
+
return inventory(inventory, "symbol");
|
|
741
780
|
}
|
|
742
781
|
|
|
743
|
-
private
|
|
744
|
-
requireKeys(params, Set.of("document"
|
|
782
|
+
private JsonArray listProcedures(JsonObject params) throws Exception {
|
|
783
|
+
requireKeys(params, Set.of("document"));
|
|
745
784
|
requireDocument(params);
|
|
746
785
|
List<InventoryItem> inventory = procedures().stream()
|
|
747
786
|
.map(FunctionEntry::item)
|
|
748
787
|
.toList();
|
|
749
|
-
return
|
|
750
|
-
inventory,
|
|
751
|
-
requireBoundedInteger(params, "offset", 0, Integer.MAX_VALUE),
|
|
752
|
-
requireBoundedInteger(params, "limit", 1, 500),
|
|
753
|
-
"procedure"
|
|
754
|
-
);
|
|
788
|
+
return inventory(inventory, "procedure");
|
|
755
789
|
}
|
|
756
790
|
|
|
757
791
|
private JsonArray listSegments(JsonObject params) {
|
|
@@ -760,9 +794,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
760
794
|
JsonArray result = new JsonArray();
|
|
761
795
|
String imageBase = canonicalAddress(currentProgram.getImageBase());
|
|
762
796
|
MemoryBlock[] blocks = currentProgram.getMemory().getBlocks();
|
|
763
|
-
if (blocks.length > MAX_INVENTORY_ITEMS) {
|
|
764
|
-
throw inventoryLimit();
|
|
765
|
-
}
|
|
766
797
|
List<MemoryBlock> ordered = new ArrayList<>(List.of(blocks));
|
|
767
798
|
ordered.sort(Comparator.comparing(MemoryBlock::getStart));
|
|
768
799
|
for (MemoryBlock block : ordered) {
|
|
@@ -773,8 +804,8 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
773
804
|
return result;
|
|
774
805
|
}
|
|
775
806
|
|
|
776
|
-
private
|
|
777
|
-
requireKeys(params, Set.of("document", "address"
|
|
807
|
+
private JsonArray listStrings(JsonObject params) throws Exception {
|
|
808
|
+
requireKeys(params, Set.of("document", "address"));
|
|
778
809
|
requireDocument(params);
|
|
779
810
|
List<InventoryItem> inventory = strings();
|
|
780
811
|
String requested = optionalString(params, "address");
|
|
@@ -784,12 +815,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
784
815
|
.filter(item -> item.address.equals(address))
|
|
785
816
|
.toList();
|
|
786
817
|
}
|
|
787
|
-
return
|
|
788
|
-
inventory,
|
|
789
|
-
requireBoundedInteger(params, "offset", 0, Integer.MAX_VALUE),
|
|
790
|
-
requireBoundedInteger(params, "limit", 1, 500),
|
|
791
|
-
"string"
|
|
792
|
-
);
|
|
818
|
+
return inventory(inventory, "string");
|
|
793
819
|
}
|
|
794
820
|
|
|
795
821
|
private JsonElement procedureAddress(JsonObject params) throws Exception {
|
|
@@ -820,26 +846,18 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
820
846
|
return result;
|
|
821
847
|
}
|
|
822
848
|
|
|
823
|
-
private
|
|
849
|
+
private JsonArray search(JsonObject params, boolean procedureSearch) throws Exception {
|
|
824
850
|
requireKeys(
|
|
825
851
|
params,
|
|
826
|
-
Set.of("pattern", "mode", "case_sensitive", "
|
|
852
|
+
Set.of("pattern", "mode", "case_sensitive", "document")
|
|
827
853
|
);
|
|
828
854
|
requireDocument(params);
|
|
829
855
|
String expression = requireString(params, "pattern");
|
|
830
|
-
if (expression.codePointCount(0, expression.length()) > 256) {
|
|
831
|
-
throw new RequestFailure(
|
|
832
|
-
"invalid_request",
|
|
833
|
-
"pattern must contain between 1 and 256 characters"
|
|
834
|
-
);
|
|
835
|
-
}
|
|
836
856
|
String mode = requireString(params, "mode");
|
|
837
857
|
if (!mode.equals("literal") && !mode.equals("regex")) {
|
|
838
858
|
throw new RequestFailure("invalid_request", "mode must be literal or regex");
|
|
839
859
|
}
|
|
840
860
|
boolean caseSensitive = requireBoolean(params, "case_sensitive");
|
|
841
|
-
int offset = requireBoundedInteger(params, "offset", 0, Integer.MAX_VALUE);
|
|
842
|
-
int limit = requireBoundedInteger(params, "limit", 1, 100);
|
|
843
861
|
List<InventoryItem> inventory = procedureSearch
|
|
844
862
|
? procedures().stream().map(FunctionEntry::item).toList()
|
|
845
863
|
: strings();
|
|
@@ -847,18 +865,13 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
847
865
|
? literalMatcher(expression, caseSensitive)
|
|
848
866
|
: regexMatcher(expression, caseSensitive);
|
|
849
867
|
JsonArray items = new JsonArray();
|
|
850
|
-
int total = 0;
|
|
851
|
-
long pageEnd = (long) offset + limit;
|
|
852
868
|
for (InventoryItem item : inventory) {
|
|
853
869
|
if (!matcher.matches(item.value)) {
|
|
854
870
|
continue;
|
|
855
871
|
}
|
|
856
|
-
|
|
857
|
-
items.add(resultItem(item, null, MAX_SEARCH_VALUE_CODE_POINTS));
|
|
858
|
-
}
|
|
859
|
-
total += 1;
|
|
872
|
+
items.add(resultItem(item, null));
|
|
860
873
|
}
|
|
861
|
-
return
|
|
874
|
+
return items;
|
|
862
875
|
}
|
|
863
876
|
|
|
864
877
|
private List<InventoryItem> names() throws Exception {
|
|
@@ -880,10 +893,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
880
893
|
facts.addProperty("external", symbol.isExternal());
|
|
881
894
|
facts.addProperty("type", normalizedSymbolType(symbol));
|
|
882
895
|
facts.addProperty("source", normalizedSource(symbol.getSource()));
|
|
883
|
-
|
|
884
|
-
result,
|
|
885
|
-
new InventoryItem(address, symbol.getName(true), facts)
|
|
886
|
-
);
|
|
896
|
+
result.add(new InventoryItem(address, symbol.getName(true), facts));
|
|
887
897
|
}
|
|
888
898
|
result.sort(INVENTORY_ORDER);
|
|
889
899
|
nameInventory = List.copyOf(result);
|
|
@@ -929,9 +939,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
929
939
|
symbol == null ? function.getName() : symbol.getName(true),
|
|
930
940
|
facts
|
|
931
941
|
);
|
|
932
|
-
if (destination.size() >= MAX_INVENTORY_ITEMS) {
|
|
933
|
-
throw inventoryLimit();
|
|
934
|
-
}
|
|
935
942
|
destination.add(new FunctionEntry(function, item));
|
|
936
943
|
}
|
|
937
944
|
}
|
|
@@ -961,7 +968,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
961
968
|
instance.isMissingNullTerminator() ? "missing" : "present_or_not_required"
|
|
962
969
|
);
|
|
963
970
|
facts.addProperty("byte_length", Math.max(0, data.getLength()));
|
|
964
|
-
|
|
971
|
+
result.add(new InventoryItem(data.getAddress(), value, facts));
|
|
965
972
|
}
|
|
966
973
|
result.sort(INVENTORY_ORDER);
|
|
967
974
|
stringInventory = List.copyOf(result);
|
|
@@ -1000,8 +1007,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1000
1007
|
|
|
1001
1008
|
private JsonObject nativeApiBoundary(
|
|
1002
1009
|
Function function,
|
|
1003
|
-
DecompileResults decompilation
|
|
1004
|
-
int maximumBytes
|
|
1010
|
+
DecompileResults decompilation
|
|
1005
1011
|
) throws Exception {
|
|
1006
1012
|
if (decompilation == null || decompilation.getHighFunction() == null) {
|
|
1007
1013
|
return unavailableNativeApiBoundary(
|
|
@@ -1041,11 +1047,9 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1041
1047
|
|
|
1042
1048
|
JsonArray parameters = new JsonArray();
|
|
1043
1049
|
result.add("parameters", parameters);
|
|
1044
|
-
result.addProperty("parameters_truncated", false);
|
|
1045
1050
|
|
|
1046
1051
|
JsonArray jumpTables = new JsonArray();
|
|
1047
1052
|
result.add("jump_tables", jumpTables);
|
|
1048
|
-
result.addProperty("jump_tables_truncated", false);
|
|
1049
1053
|
|
|
1050
1054
|
JsonObject pseudocode = new JsonObject();
|
|
1051
1055
|
pseudocode.addProperty("classification", "decompiler-generated-non-source");
|
|
@@ -1071,17 +1075,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1071
1075
|
"Pseudocode is neither original source nor guaranteed to compile."
|
|
1072
1076
|
);
|
|
1073
1077
|
result.add("limitations", limitations);
|
|
1074
|
-
|
|
1075
|
-
return unavailableNativeApiBoundary(
|
|
1076
|
-
"The remaining Ghidra response budget cannot contain a structured native API boundary"
|
|
1077
|
-
);
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
int parameterCount = Math.min(
|
|
1081
|
-
prototype.getNumParams(),
|
|
1082
|
-
MAX_NATIVE_API_PARAMETERS
|
|
1083
|
-
);
|
|
1084
|
-
for (int index = 0; index < parameterCount; index += 1) {
|
|
1078
|
+
for (int index = 0; index < prototype.getNumParams(); index += 1) {
|
|
1085
1079
|
HighSymbol parameter = prototype.getParam(index);
|
|
1086
1080
|
if (parameter == null) {
|
|
1087
1081
|
continue;
|
|
@@ -1097,32 +1091,12 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1097
1091
|
signatureSource
|
|
1098
1092
|
)
|
|
1099
1093
|
);
|
|
1100
|
-
if (encodedUtf8Length(result) > maximumBytes) {
|
|
1101
|
-
parameters.remove(parameters.size() - 1);
|
|
1102
|
-
break;
|
|
1103
|
-
}
|
|
1104
1094
|
}
|
|
1105
|
-
result.addProperty(
|
|
1106
|
-
"parameters_truncated",
|
|
1107
|
-
prototype.getNumParams() > parameters.size()
|
|
1108
|
-
);
|
|
1109
1095
|
|
|
1110
1096
|
JumpTable[] recoveredJumpTables = highFunction.getJumpTables();
|
|
1111
|
-
int
|
|
1112
|
-
recoveredJumpTables.length,
|
|
1113
|
-
MAX_NATIVE_API_JUMP_TABLES
|
|
1114
|
-
);
|
|
1115
|
-
for (int index = 0; index < jumpTableCount; index += 1) {
|
|
1097
|
+
for (int index = 0; index < recoveredJumpTables.length; index += 1) {
|
|
1116
1098
|
jumpTables.add(inferredJumpTable(recoveredJumpTables[index]));
|
|
1117
|
-
if (encodedUtf8Length(result) > maximumBytes) {
|
|
1118
|
-
jumpTables.remove(jumpTables.size() - 1);
|
|
1119
|
-
break;
|
|
1120
|
-
}
|
|
1121
1099
|
}
|
|
1122
|
-
result.addProperty(
|
|
1123
|
-
"jump_tables_truncated",
|
|
1124
|
-
recoveredJumpTables.length > jumpTables.size()
|
|
1125
|
-
);
|
|
1126
1100
|
return result;
|
|
1127
1101
|
}
|
|
1128
1102
|
|
|
@@ -1137,10 +1111,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1137
1111
|
return unavailable;
|
|
1138
1112
|
}
|
|
1139
1113
|
|
|
1140
|
-
private static int encodedUtf8Length(JsonElement value) {
|
|
1141
|
-
return GSON.toJson(value).getBytes(StandardCharsets.UTF_8).length;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
1114
|
private static JsonObject inferredBoundaryType(
|
|
1145
1115
|
String role,
|
|
1146
1116
|
Integer ordinal,
|
|
@@ -1228,22 +1198,30 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1228
1198
|
canonicalAddress(jumpTable.getSwitchAddress())
|
|
1229
1199
|
);
|
|
1230
1200
|
JumpTable.LoadTable[] loadTables = jumpTable.getLoadTables();
|
|
1201
|
+
RecoveredAArch64ByteTable recoveredAArch64 =
|
|
1202
|
+
loadTables.length == 0
|
|
1203
|
+
? recoverAArch64ByteTable(jumpTable)
|
|
1204
|
+
: null;
|
|
1231
1205
|
List<Address> referencedDataAddresses = loadTables.length == 0
|
|
1232
1206
|
? dispatchPathDataReferences(jumpTable)
|
|
1233
1207
|
: List.of();
|
|
1234
1208
|
JsonArray dataSources = new JsonArray();
|
|
1235
|
-
int
|
|
1236
|
-
|
|
1237
|
-
MAX_NATIVE_API_LOAD_TABLES
|
|
1238
|
-
);
|
|
1239
|
-
for (int index = 0; index < loadTableCount; index += 1) {
|
|
1209
|
+
for (int index = 0; index < loadTables.length; index += 1) {
|
|
1210
|
+
monitor.checkCancelled();
|
|
1240
1211
|
JumpTable.LoadTable loadTable = loadTables[index];
|
|
1241
1212
|
JsonObject source = new JsonObject();
|
|
1242
1213
|
source.addProperty("address", canonicalAddress(loadTable.getAddress()));
|
|
1243
1214
|
source.addProperty("provenance", "ghidra-decompiler-load-table");
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
source.addProperty("
|
|
1215
|
+
int entrySize = loadTable.getSize();
|
|
1216
|
+
int entryCount = loadTable.getNum();
|
|
1217
|
+
if (entrySize > 0) source.addProperty("entry_size_bytes", entrySize);
|
|
1218
|
+
else source.add("entry_size_bytes", JsonNull.INSTANCE);
|
|
1219
|
+
if (entryCount > 0) source.addProperty("entry_count", entryCount);
|
|
1220
|
+
else source.add("entry_count", JsonNull.INSTANCE);
|
|
1221
|
+
source.addProperty(
|
|
1222
|
+
"confidence",
|
|
1223
|
+
entrySize > 0 && entryCount > 0 ? "high" : "medium"
|
|
1224
|
+
);
|
|
1247
1225
|
JsonArray evidence = new JsonArray();
|
|
1248
1226
|
evidence.add(
|
|
1249
1227
|
inferenceEvidence(
|
|
@@ -1257,12 +1235,37 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1257
1235
|
source.add("evidence", evidence);
|
|
1258
1236
|
dataSources.add(source);
|
|
1259
1237
|
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1238
|
+
if (recoveredAArch64 != null) {
|
|
1239
|
+
JsonObject source = new JsonObject();
|
|
1240
|
+
source.addProperty(
|
|
1241
|
+
"address",
|
|
1242
|
+
canonicalAddress(recoveredAArch64.tableAddress)
|
|
1243
|
+
);
|
|
1244
|
+
source.addProperty("provenance", "ghidra-aarch64-byte-table");
|
|
1245
|
+
source.addProperty("entry_size_bytes", 1);
|
|
1246
|
+
source.addProperty("entry_count", recoveredAArch64.mappings.size());
|
|
1247
|
+
source.addProperty("confidence", "high");
|
|
1248
|
+
JsonArray evidence = new JsonArray();
|
|
1249
|
+
evidence.add(
|
|
1250
|
+
inferenceEvidence(
|
|
1251
|
+
"jump-table",
|
|
1252
|
+
"ghidra-instruction-and-memory",
|
|
1253
|
+
"Decoded " + recoveredAArch64.mappings.size() +
|
|
1254
|
+
" byte entries from " + canonicalAddress(recoveredAArch64.tableAddress) +
|
|
1255
|
+
"; the AArch64 LDRB/ADR/ADD/BR chain uses a selector guarded by an unsigned upper-bound branch."
|
|
1256
|
+
)
|
|
1257
|
+
);
|
|
1258
|
+
source.add("evidence", evidence);
|
|
1259
|
+
dataSources.add(source);
|
|
1260
|
+
}
|
|
1261
|
+
for (int index = 0; index < referencedDataAddresses.size(); index += 1) {
|
|
1265
1262
|
Address address = referencedDataAddresses.get(index);
|
|
1263
|
+
if (
|
|
1264
|
+
recoveredAArch64 != null &&
|
|
1265
|
+
recoveredAArch64.tableAddress.equals(address)
|
|
1266
|
+
) {
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1266
1269
|
JsonObject source = new JsonObject();
|
|
1267
1270
|
source.addProperty("address", canonicalAddress(address));
|
|
1268
1271
|
source.addProperty(
|
|
@@ -1286,19 +1289,39 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1286
1289
|
dataSources.add(source);
|
|
1287
1290
|
}
|
|
1288
1291
|
result.add("data_sources", dataSources);
|
|
1289
|
-
result.addProperty(
|
|
1290
|
-
"data_sources_truncated",
|
|
1291
|
-
loadTables.length > loadTableCount ||
|
|
1292
|
-
referencedDataAddresses.size() > referencedDataCount
|
|
1293
|
-
);
|
|
1294
|
-
|
|
1295
1292
|
Address[] targets = jumpTable.getCases();
|
|
1296
1293
|
Integer[] labels = jumpTable.getLabelValues();
|
|
1294
|
+
boolean labelsAligned = labels != null && labels.length == targets.length;
|
|
1297
1295
|
JsonArray mappings = new JsonArray();
|
|
1298
|
-
|
|
1299
|
-
|
|
1296
|
+
if (recoveredAArch64 != null) {
|
|
1297
|
+
for (int index = 0; index < recoveredAArch64.mappings.size(); index += 1) {
|
|
1298
|
+
monitor.checkCancelled();
|
|
1299
|
+
RecoveredAArch64Mapping recovered = recoveredAArch64.mappings.get(index);
|
|
1300
|
+
JsonObject mapping = new JsonObject();
|
|
1301
|
+
mapping.addProperty("case_value", recovered.caseValue);
|
|
1302
|
+
String targetAddress = canonicalAddress(recovered.targetAddress);
|
|
1303
|
+
mapping.addProperty("target_address", targetAddress);
|
|
1304
|
+
mapping.addProperty("confidence", "high");
|
|
1305
|
+
JsonArray evidence = new JsonArray();
|
|
1306
|
+
evidence.add(
|
|
1307
|
+
inferenceEvidence(
|
|
1308
|
+
"jump-table",
|
|
1309
|
+
"ghidra-instruction-and-memory",
|
|
1310
|
+
"Case value " + recovered.caseValue + " indexes byte " +
|
|
1311
|
+
recovered.caseValue + " at " + canonicalAddress(recoveredAArch64.tableAddress) +
|
|
1312
|
+
"; the loaded byte selects a four-byte instruction offset from " +
|
|
1313
|
+
canonicalAddress(recoveredAArch64.branchBase) + ", reaching " + targetAddress + "."
|
|
1314
|
+
)
|
|
1315
|
+
);
|
|
1316
|
+
mapping.add("evidence", evidence);
|
|
1317
|
+
mappings.add(mapping);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
else {
|
|
1321
|
+
for (int index = 0; index < targets.length; index += 1) {
|
|
1322
|
+
monitor.checkCancelled();
|
|
1300
1323
|
JsonObject mapping = new JsonObject();
|
|
1301
|
-
if (
|
|
1324
|
+
if (!labelsAligned || labels[index] == null) {
|
|
1302
1325
|
mapping.add("case_value", JsonNull.INSTANCE);
|
|
1303
1326
|
}
|
|
1304
1327
|
else {
|
|
@@ -1306,61 +1329,299 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1306
1329
|
}
|
|
1307
1330
|
String targetAddress = canonicalAddress(targets[index]);
|
|
1308
1331
|
mapping.addProperty("target_address", targetAddress);
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1332
|
+
// Ghidra's recovered case-to-target relation does not identify
|
|
1333
|
+
// which backing load table produced an individual case. Keep the
|
|
1334
|
+
// table inventory and recovered mappings separate instead of
|
|
1335
|
+
// attaching every table address to every target.
|
|
1336
|
+
mapping.addProperty(
|
|
1337
|
+
"confidence",
|
|
1338
|
+
labelsAligned && labels[index] != null
|
|
1339
|
+
? "high"
|
|
1340
|
+
: "medium"
|
|
1341
|
+
);
|
|
1317
1342
|
JsonArray evidence = new JsonArray();
|
|
1318
1343
|
evidence.add(
|
|
1319
1344
|
inferenceEvidence(
|
|
1320
1345
|
"jump-table",
|
|
1321
1346
|
"ghidra-high-function",
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1347
|
+
labelsAligned && labels[index] != null
|
|
1348
|
+
? "Ghidra paired case value " +
|
|
1349
|
+
labels[index] +
|
|
1350
|
+
" with target " +
|
|
1351
|
+
targetAddress +
|
|
1352
|
+
" at dispatch " +
|
|
1353
|
+
canonicalAddress(jumpTable.getSwitchAddress()) +
|
|
1354
|
+
"."
|
|
1355
|
+
: "Ghidra recovered target " +
|
|
1356
|
+
targetAddress +
|
|
1357
|
+
" at dispatch " +
|
|
1358
|
+
canonicalAddress(jumpTable.getSwitchAddress()) +
|
|
1359
|
+
", but no aligned case value was available."
|
|
1327
1360
|
)
|
|
1328
1361
|
);
|
|
1329
1362
|
mapping.add("evidence", evidence);
|
|
1330
1363
|
mappings.add(mapping);
|
|
1331
1364
|
}
|
|
1365
|
+
}
|
|
1332
1366
|
result.add("mappings", mappings);
|
|
1333
|
-
result.addProperty("mappings_truncated", targets.length > mappingCount);
|
|
1334
|
-
|
|
1335
1367
|
JsonArray limitations = new JsonArray();
|
|
1336
1368
|
if (loadTables.length == 0 && referencedDataAddresses.isEmpty()) {
|
|
1337
1369
|
limitations.add(
|
|
1338
1370
|
"Ghidra recovered targets but exposed no backing load-table or dispatch-block data address."
|
|
1339
1371
|
);
|
|
1340
1372
|
}
|
|
1341
|
-
if (
|
|
1373
|
+
if (
|
|
1374
|
+
recoveredAArch64 == null &&
|
|
1375
|
+
loadTables.length == 0 &&
|
|
1376
|
+
!referencedDataAddresses.isEmpty()
|
|
1377
|
+
) {
|
|
1342
1378
|
limitations.add(
|
|
1343
1379
|
"Load-table metadata was absent; table-level candidate ownership is inferred from typed data references in the dispatch block and its immediate control-flow predecessors, but candidates are not assigned to individual mappings."
|
|
1344
1380
|
);
|
|
1345
1381
|
}
|
|
1346
|
-
if (
|
|
1382
|
+
if (recoveredAArch64 == null && !labelsAligned) {
|
|
1347
1383
|
limitations.add(
|
|
1348
|
-
"Case
|
|
1384
|
+
"Case-label and target counts differ or labels are unavailable; all case values are left unknown because index alignment cannot be established."
|
|
1349
1385
|
);
|
|
1350
1386
|
}
|
|
1351
|
-
if (
|
|
1352
|
-
loadTables.length > loadTableCount ||
|
|
1353
|
-
referencedDataAddresses.size() > referencedDataCount ||
|
|
1354
|
-
targets.length > mappingCount
|
|
1355
|
-
) {
|
|
1387
|
+
if (targets.length == 0) {
|
|
1356
1388
|
limitations.add(
|
|
1357
|
-
"
|
|
1389
|
+
"Ghidra exposed a jump-table dispatch without recovered case targets."
|
|
1358
1390
|
);
|
|
1359
1391
|
}
|
|
1360
1392
|
result.add("limitations", limitations);
|
|
1361
1393
|
return result;
|
|
1362
1394
|
}
|
|
1363
1395
|
|
|
1396
|
+
private static final class RecoveredAArch64Mapping {
|
|
1397
|
+
private final int caseValue;
|
|
1398
|
+
private final Address targetAddress;
|
|
1399
|
+
|
|
1400
|
+
private RecoveredAArch64Mapping(int caseValue, Address targetAddress) {
|
|
1401
|
+
this.caseValue = caseValue;
|
|
1402
|
+
this.targetAddress = targetAddress;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
private static final class RecoveredAArch64ByteTable {
|
|
1407
|
+
private final Address tableAddress;
|
|
1408
|
+
private final Address branchBase;
|
|
1409
|
+
private final List<RecoveredAArch64Mapping> mappings;
|
|
1410
|
+
|
|
1411
|
+
private RecoveredAArch64ByteTable(
|
|
1412
|
+
Address tableAddress,
|
|
1413
|
+
Address branchBase,
|
|
1414
|
+
List<RecoveredAArch64Mapping> mappings
|
|
1415
|
+
) {
|
|
1416
|
+
this.tableAddress = tableAddress;
|
|
1417
|
+
this.branchBase = branchBase;
|
|
1418
|
+
this.mappings = mappings;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
/**
|
|
1423
|
+
* Recover only the directly verified AArch64 byte-indexed relative branch
|
|
1424
|
+
* table form. Other instruction sequences keep their case labels unknown.
|
|
1425
|
+
*/
|
|
1426
|
+
private RecoveredAArch64ByteTable recoverAArch64ByteTable(
|
|
1427
|
+
JumpTable jumpTable
|
|
1428
|
+
) throws Exception {
|
|
1429
|
+
if (!currentProgram.getLanguageID().getIdAsString().startsWith("AARCH64:")) {
|
|
1430
|
+
return null;
|
|
1431
|
+
}
|
|
1432
|
+
Function function = containingFunction(jumpTable.getSwitchAddress());
|
|
1433
|
+
if (function == null) return null;
|
|
1434
|
+
|
|
1435
|
+
List<Instruction> instructions = new ArrayList<>();
|
|
1436
|
+
InstructionIterator iterator = currentProgram.getListing().getInstructions(
|
|
1437
|
+
function.getBody(), true
|
|
1438
|
+
);
|
|
1439
|
+
while (iterator.hasNext()) {
|
|
1440
|
+
monitor.checkCancelled();
|
|
1441
|
+
Instruction instruction = iterator.next();
|
|
1442
|
+
instructions.add(instruction);
|
|
1443
|
+
if (instruction.getAddress().equals(jumpTable.getSwitchAddress())) break;
|
|
1444
|
+
}
|
|
1445
|
+
int branchIndex = instructions.size() - 1;
|
|
1446
|
+
if (branchIndex < 1 ||
|
|
1447
|
+
!instructions.get(branchIndex).getMnemonicString().equalsIgnoreCase("BR")) {
|
|
1448
|
+
return null;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
Address[] recoveredTargets = jumpTable.getCases();
|
|
1452
|
+
Set<String> recoveredTargetSet = new HashSet<>();
|
|
1453
|
+
for (Address target : recoveredTargets) recoveredTargetSet.add(canonicalAddress(target));
|
|
1454
|
+
List<Address> dataAddresses = dispatchPathDataReferences(jumpTable);
|
|
1455
|
+
if (dataAddresses.isEmpty()) return null;
|
|
1456
|
+
|
|
1457
|
+
for (int loadIndex = 0; loadIndex < branchIndex; loadIndex += 1) {
|
|
1458
|
+
Instruction load = instructions.get(loadIndex);
|
|
1459
|
+
if (!load.getMnemonicString().equalsIgnoreCase("LDRB")) continue;
|
|
1460
|
+
String loadOperands = operands(load);
|
|
1461
|
+
java.util.regex.Matcher loadMatcher = java.util.regex.Pattern
|
|
1462
|
+
.compile("(?i)^[wx]\\d+,\\s*\\[([wx]\\d+),\\s*([wx]\\d+)\\]$")
|
|
1463
|
+
.matcher(loadOperands);
|
|
1464
|
+
if (!loadMatcher.matches()) continue;
|
|
1465
|
+
String loadedRegister = normalizedRegister(loadOperands.substring(0, loadOperands.indexOf(',')));
|
|
1466
|
+
String tableBaseRegister = normalizedRegister(loadMatcher.group(1));
|
|
1467
|
+
String indexRegister = normalizedRegister(loadMatcher.group(2));
|
|
1468
|
+
|
|
1469
|
+
Address tableAddress = null;
|
|
1470
|
+
for (Address candidate : dataAddresses) {
|
|
1471
|
+
if (currentProgram.getMemory().contains(candidate)) {
|
|
1472
|
+
tableAddress = candidate;
|
|
1473
|
+
break;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if (tableAddress == null) continue;
|
|
1477
|
+
|
|
1478
|
+
Address resolvedTableAddress = null;
|
|
1479
|
+
for (int index = 0; index < loadIndex; index += 1) {
|
|
1480
|
+
Instruction instruction = instructions.get(index);
|
|
1481
|
+
String mnemonic = instruction.getMnemonicString().toUpperCase(Locale.ROOT);
|
|
1482
|
+
String text = operands(instruction);
|
|
1483
|
+
if (mnemonic.equals("ADRP") &&
|
|
1484
|
+
text.matches("(?i)^[wx]\\d+,\\s*0x[0-9a-f]+$")) {
|
|
1485
|
+
String[] parts = text.split(",");
|
|
1486
|
+
if (normalizedRegister(parts[0]).equals(tableBaseRegister)) {
|
|
1487
|
+
resolvedTableAddress = addressFromOperand(parts[1]);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
else if (mnemonic.equals("ADD") && resolvedTableAddress != null &&
|
|
1491
|
+
text.matches("(?i)^[wx]\\d+,\\s*[wx]\\d+,\\s*#?(?:0x[0-9a-f]+|[0-9]+)$")) {
|
|
1492
|
+
String[] parts = text.split(",");
|
|
1493
|
+
if (normalizedRegister(parts[0]).equals(tableBaseRegister) &&
|
|
1494
|
+
normalizedRegister(parts[1]).equals(tableBaseRegister)) {
|
|
1495
|
+
resolvedTableAddress = resolvedTableAddress.add(parseInteger(parts[2].trim().replaceFirst("^#", "")));
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
if (!tableAddress.equals(resolvedTableAddress)) continue;
|
|
1500
|
+
|
|
1501
|
+
Address branchBase = null;
|
|
1502
|
+
boolean addressChain = false;
|
|
1503
|
+
for (int index = 0; index < branchIndex; index += 1) {
|
|
1504
|
+
Instruction instruction = instructions.get(index);
|
|
1505
|
+
String mnemonic = instruction.getMnemonicString().toUpperCase(Locale.ROOT);
|
|
1506
|
+
String text = operands(instruction);
|
|
1507
|
+
if (index < loadIndex && mnemonic.equals("ADR") && text.matches("(?i)^[wx]\\d+,\\s*0x[0-9a-f]+$")) {
|
|
1508
|
+
int comma = text.indexOf(',');
|
|
1509
|
+
String destination = normalizedRegister(text.substring(0, comma));
|
|
1510
|
+
if (destination.equals(tableBaseRegister)) continue;
|
|
1511
|
+
branchBase = addressFromOperand(text.substring(comma + 1));
|
|
1512
|
+
}
|
|
1513
|
+
if (index > loadIndex && mnemonic.equals("ADD") && branchBase != null &&
|
|
1514
|
+
text.matches("(?i)^[wx]\\d+,\\s*[wx]\\d+,\\s*[wx]\\d+,\\s*LSL\\s*#(?:0x)?2$")) {
|
|
1515
|
+
String[] parts = text.split(",");
|
|
1516
|
+
String destination = normalizedRegister(parts[0]);
|
|
1517
|
+
String base = normalizedRegister(parts[1]);
|
|
1518
|
+
String offset = normalizedRegister(parts[2]);
|
|
1519
|
+
if (destination.equals(base) &&
|
|
1520
|
+
base.equals(normalizedRegister(operands(instructions.get(branchIndex)))) &&
|
|
1521
|
+
offset.equals(loadedRegister)) {
|
|
1522
|
+
addressChain = true;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
if (!addressChain || branchBase == null) continue;
|
|
1527
|
+
|
|
1528
|
+
int entryCount = -1;
|
|
1529
|
+
Address defaultTarget = null;
|
|
1530
|
+
for (int compareIndex = 0; compareIndex < loadIndex; compareIndex += 1) {
|
|
1531
|
+
Instruction compare = instructions.get(compareIndex);
|
|
1532
|
+
if (!compare.getMnemonicString().equalsIgnoreCase("CMP")) continue;
|
|
1533
|
+
java.util.regex.Matcher compareMatcher = java.util.regex.Pattern
|
|
1534
|
+
.compile("(?i)^([wx]\\d+),\\s*#?(0x[0-9a-f]+|[0-9]+)$")
|
|
1535
|
+
.matcher(operands(compare));
|
|
1536
|
+
if (!compareMatcher.matches()) continue;
|
|
1537
|
+
String comparedRegister = normalizedRegister(compareMatcher.group(1));
|
|
1538
|
+
long upperBound = parseInteger(compareMatcher.group(2));
|
|
1539
|
+
if (upperBound < 0 || upperBound >= 4096) continue;
|
|
1540
|
+
for (int guardIndex = compareIndex + 1; guardIndex < loadIndex; guardIndex += 1) {
|
|
1541
|
+
Instruction guard = instructions.get(guardIndex);
|
|
1542
|
+
String mnemonic = guard.getMnemonicString().toUpperCase(Locale.ROOT);
|
|
1543
|
+
if (!mnemonic.equals("B.HI")) continue;
|
|
1544
|
+
Address[] flows = guard.getFlows();
|
|
1545
|
+
if (flows.length != 1 || !recoveredTargetSet.contains(canonicalAddress(flows[0]))) continue;
|
|
1546
|
+
boolean selectorFeedsTable = indexRegister.equals(comparedRegister);
|
|
1547
|
+
boolean supportedSequence = true;
|
|
1548
|
+
for (int sequenceIndex = compareIndex + 1; sequenceIndex < loadIndex; sequenceIndex += 1) {
|
|
1549
|
+
String sequenceMnemonic = instructions.get(sequenceIndex)
|
|
1550
|
+
.getMnemonicString().toUpperCase(Locale.ROOT);
|
|
1551
|
+
if (!Set.of("B.HI", "MOV", "ADRP", "ADD", "ADR").contains(sequenceMnemonic)) {
|
|
1552
|
+
supportedSequence = false;
|
|
1553
|
+
break;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
for (int moveIndex = compareIndex + 1; moveIndex < loadIndex && !selectorFeedsTable; moveIndex += 1) {
|
|
1557
|
+
Instruction move = instructions.get(moveIndex);
|
|
1558
|
+
if (!move.getMnemonicString().equalsIgnoreCase("MOV")) continue;
|
|
1559
|
+
String[] moveParts = operands(move).split(",");
|
|
1560
|
+
if (moveParts.length == 2 &&
|
|
1561
|
+
normalizedRegister(moveParts[0]).equals(indexRegister) &&
|
|
1562
|
+
normalizedRegister(moveParts[1]).equals(comparedRegister)) {
|
|
1563
|
+
selectorFeedsTable = true;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
if (selectorFeedsTable && supportedSequence) {
|
|
1567
|
+
entryCount = (int) upperBound + 1;
|
|
1568
|
+
defaultTarget = flows[0];
|
|
1569
|
+
break;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
if (entryCount > 0) break;
|
|
1573
|
+
}
|
|
1574
|
+
if (entryCount <= 0 || entryCount > 4096 || defaultTarget == null) continue;
|
|
1575
|
+
|
|
1576
|
+
List<RecoveredAArch64Mapping> mappings = new ArrayList<>();
|
|
1577
|
+
Set<String> mappedTargets = new HashSet<>();
|
|
1578
|
+
boolean valid = true;
|
|
1579
|
+
for (int caseValue = 0; caseValue < entryCount; caseValue += 1) {
|
|
1580
|
+
int encodedOffset = currentProgram.getMemory().getByte(tableAddress.add(caseValue)) & 0xff;
|
|
1581
|
+
Address target = branchBase.add((long) encodedOffset * 4L);
|
|
1582
|
+
String canonicalTarget = canonicalAddress(target);
|
|
1583
|
+
if (!recoveredTargetSet.contains(canonicalTarget)) {
|
|
1584
|
+
valid = false;
|
|
1585
|
+
break;
|
|
1586
|
+
}
|
|
1587
|
+
mappedTargets.add(canonicalTarget);
|
|
1588
|
+
mappings.add(new RecoveredAArch64Mapping(caseValue, target));
|
|
1589
|
+
}
|
|
1590
|
+
if (!valid || mappedTargets.contains(canonicalAddress(defaultTarget))) continue;
|
|
1591
|
+
Set<String> accountedTargets = new HashSet<>(mappedTargets);
|
|
1592
|
+
accountedTargets.add(canonicalAddress(defaultTarget));
|
|
1593
|
+
if (!accountedTargets.equals(recoveredTargetSet)) continue;
|
|
1594
|
+
return new RecoveredAArch64ByteTable(tableAddress, branchBase, List.copyOf(mappings));
|
|
1595
|
+
}
|
|
1596
|
+
return null;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
private static String operands(Instruction instruction) {
|
|
1600
|
+
List<String> values = new ArrayList<>();
|
|
1601
|
+
for (int index = 0; index < instruction.getNumOperands(); index += 1) {
|
|
1602
|
+
values.add(instruction.getDefaultOperandRepresentation(index));
|
|
1603
|
+
}
|
|
1604
|
+
return String.join(", ", values);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
private static String normalizedRegister(String value) {
|
|
1608
|
+
String register = value.trim().toLowerCase(Locale.ROOT);
|
|
1609
|
+
return register.matches("[wx]\\d+") ? "r" + register.substring(1) : register;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
private static long parseInteger(String value) {
|
|
1613
|
+
String number = value.toLowerCase(Locale.ROOT);
|
|
1614
|
+
return number.startsWith("0x")
|
|
1615
|
+
? Long.parseUnsignedLong(number.substring(2), 16)
|
|
1616
|
+
: Long.parseLong(number);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
private Address addressFromOperand(String value) {
|
|
1620
|
+
String hex = value.trim().replaceFirst("(?i)^0x", "");
|
|
1621
|
+
return currentProgram.getAddressFactory().getDefaultAddressSpace()
|
|
1622
|
+
.getAddress(Long.parseUnsignedLong(hex, 16));
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1364
1625
|
private List<Address> dispatchPathDataReferences(JumpTable jumpTable)
|
|
1365
1626
|
throws Exception {
|
|
1366
1627
|
BasicBlockModel model = new BasicBlockModel(currentProgram, false);
|
|
@@ -1443,22 +1704,16 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1443
1704
|
}
|
|
1444
1705
|
DecompileResults results = decompiler.decompileFunction(
|
|
1445
1706
|
function,
|
|
1446
|
-
|
|
1707
|
+
0,
|
|
1447
1708
|
monitor
|
|
1448
1709
|
);
|
|
1449
|
-
if (results.isTimedOut()) {
|
|
1450
|
-
throw new RequestFailure(
|
|
1451
|
-
"decompile_timeout",
|
|
1452
|
-
"Ghidra decompilation reached its 30-second deadline"
|
|
1453
|
-
);
|
|
1454
|
-
}
|
|
1455
1710
|
if (results.isCancelled()) {
|
|
1456
1711
|
throw new RequestFailure("decompile_cancelled", "Ghidra decompilation was cancelled");
|
|
1457
1712
|
}
|
|
1458
1713
|
if (!results.decompileCompleted()) {
|
|
1459
1714
|
throw new RequestFailure(
|
|
1460
1715
|
"decompile_failed",
|
|
1461
|
-
"Ghidra decompilation failed: " +
|
|
1716
|
+
"Ghidra decompilation failed: " + diagnosticMessage(results.getErrorMessage())
|
|
1462
1717
|
);
|
|
1463
1718
|
}
|
|
1464
1719
|
return results;
|
|
@@ -1540,9 +1795,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1540
1795
|
reference.getOperandIndex() + "\u0000" +
|
|
1541
1796
|
reference.isPrimary();
|
|
1542
1797
|
observed.putIfAbsent(key, reference);
|
|
1543
|
-
if (observed.size() > MAX_FUNCTION_ITEMS) {
|
|
1544
|
-
throw functionLimit("Reference set exceeds the 100000-item limit");
|
|
1545
|
-
}
|
|
1546
1798
|
}
|
|
1547
1799
|
|
|
1548
1800
|
private JsonArray referenceEdges(List<Reference> references) {
|
|
@@ -1623,9 +1875,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1623
1875
|
|
|
1624
1876
|
private static JsonArray functionLocals(Function function) {
|
|
1625
1877
|
Variable[] variables = function.getLocalVariables();
|
|
1626
|
-
if (variables.length > MAX_FUNCTION_ITEMS) {
|
|
1627
|
-
throw functionLimit("Local-variable set exceeds the 100000-item limit");
|
|
1628
|
-
}
|
|
1629
1878
|
List<Variable> ordered = new ArrayList<>(List.of(variables));
|
|
1630
1879
|
ordered.sort(
|
|
1631
1880
|
Comparator.comparing(Variable::getName)
|
|
@@ -1646,9 +1895,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1646
1895
|
}
|
|
1647
1896
|
|
|
1648
1897
|
private JsonArray procedureIdentities(Set<Function> functions) {
|
|
1649
|
-
if (functions.size() > MAX_FUNCTION_ITEMS) {
|
|
1650
|
-
throw functionLimit("Resolved call set exceeds the 100000-item limit");
|
|
1651
|
-
}
|
|
1652
1898
|
List<Function> ordered = new ArrayList<>(functions);
|
|
1653
1899
|
ordered.sort(Comparator.comparing(Function::getEntryPoint));
|
|
1654
1900
|
JsonArray result = new JsonArray();
|
|
@@ -1664,9 +1910,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1664
1910
|
List<CodeBlock> result = new ArrayList<>();
|
|
1665
1911
|
while (iterator.hasNext()) {
|
|
1666
1912
|
monitor.checkCancelled();
|
|
1667
|
-
if (result.size() >= MAX_FUNCTION_ITEMS) {
|
|
1668
|
-
throw functionLimit("Basic-block set exceeds the 100000-item limit");
|
|
1669
|
-
}
|
|
1670
1913
|
result.add(iterator.next());
|
|
1671
1914
|
}
|
|
1672
1915
|
result.sort(Comparator.comparing(CodeBlock::getFirstStartAddress));
|
|
@@ -1723,10 +1966,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1723
1966
|
String key = canonicalAddress(instruction.getAddress()) + "\u0000" +
|
|
1724
1967
|
kind + "\u0000" + type.name() + "\u0000" + text;
|
|
1725
1968
|
observed.put(key, item);
|
|
1726
|
-
|
|
1727
|
-
throw functionLimit("Comment set exceeds the 100000-item limit");
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1969
|
+
}
|
|
1730
1970
|
}
|
|
1731
1971
|
JsonArray result = new JsonArray();
|
|
1732
1972
|
observed.values().forEach(result::add);
|
|
@@ -1753,10 +1993,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1753
1993
|
String key = canonicalAddress(data.getAddress()) + "\u0000" + value + "\u0000" +
|
|
1754
1994
|
canonicalAddress(reference.getFromAddress());
|
|
1755
1995
|
observed.put(key, item);
|
|
1756
|
-
if (observed.size() > MAX_FUNCTION_ITEMS) {
|
|
1757
|
-
throw functionLimit("Referenced-string set exceeds the 100000-item limit");
|
|
1758
1996
|
}
|
|
1759
|
-
}
|
|
1760
1997
|
JsonArray result = new JsonArray();
|
|
1761
1998
|
observed.values().forEach(result::add);
|
|
1762
1999
|
return result;
|
|
@@ -1778,96 +2015,12 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1778
2015
|
String key = canonicalAddress(reference.getToAddress()) + "\u0000" +
|
|
1779
2016
|
symbol.getName(true) + "\u0000" + canonicalAddress(reference.getFromAddress());
|
|
1780
2017
|
observed.put(key, item);
|
|
1781
|
-
if (observed.size() > MAX_FUNCTION_ITEMS) {
|
|
1782
|
-
throw functionLimit("Referenced-name set exceeds the 100000-item limit");
|
|
1783
2018
|
}
|
|
1784
|
-
}
|
|
1785
2019
|
JsonArray result = new JsonArray();
|
|
1786
2020
|
observed.values().forEach(result::add);
|
|
1787
2021
|
return result;
|
|
1788
2022
|
}
|
|
1789
2023
|
|
|
1790
|
-
private static JsonObject pseudocodePage(String value, int offset, int limit) {
|
|
1791
|
-
int total = value.codePointCount(0, value.length());
|
|
1792
|
-
int start = Math.min(offset, total);
|
|
1793
|
-
int returned = Math.min(limit, total - start);
|
|
1794
|
-
int startIndex = value.offsetByCodePoints(0, start);
|
|
1795
|
-
int endIndex = value.offsetByCodePoints(startIndex, returned);
|
|
1796
|
-
int next = start + returned;
|
|
1797
|
-
JsonObject result = new JsonObject();
|
|
1798
|
-
result.addProperty("text", value.substring(startIndex, endIndex));
|
|
1799
|
-
result.addProperty("total_chars", total);
|
|
1800
|
-
result.addProperty("returned_chars", returned);
|
|
1801
|
-
result.addProperty("truncated", next < total);
|
|
1802
|
-
if (next < total) {
|
|
1803
|
-
result.addProperty("next_offset", next);
|
|
1804
|
-
}
|
|
1805
|
-
else {
|
|
1806
|
-
result.add("next_offset", JsonNull.INSTANCE);
|
|
1807
|
-
}
|
|
1808
|
-
return result;
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
|
-
private static JsonObject bounded(
|
|
1812
|
-
JsonArray values,
|
|
1813
|
-
int offset,
|
|
1814
|
-
int limit,
|
|
1815
|
-
boolean complete) {
|
|
1816
|
-
int start = Math.min(offset, values.size());
|
|
1817
|
-
int end = Math.min(values.size(), start + limit);
|
|
1818
|
-
JsonArray items = new JsonArray();
|
|
1819
|
-
for (int index = start; index < end; index += 1) {
|
|
1820
|
-
items.add(values.get(index).deepCopy());
|
|
1821
|
-
}
|
|
1822
|
-
boolean hasMore = end < values.size();
|
|
1823
|
-
JsonObject result = new JsonObject();
|
|
1824
|
-
result.add("items", items);
|
|
1825
|
-
if (complete) {
|
|
1826
|
-
result.addProperty("total", values.size());
|
|
1827
|
-
}
|
|
1828
|
-
else {
|
|
1829
|
-
result.add("total", JsonNull.INSTANCE);
|
|
1830
|
-
}
|
|
1831
|
-
result.addProperty("returned", items.size());
|
|
1832
|
-
result.addProperty("truncated", !complete || hasMore);
|
|
1833
|
-
if (complete && hasMore) {
|
|
1834
|
-
result.addProperty("next_offset", end);
|
|
1835
|
-
}
|
|
1836
|
-
else {
|
|
1837
|
-
result.add("next_offset", JsonNull.INSTANCE);
|
|
1838
|
-
}
|
|
1839
|
-
return result;
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
private static JsonObject requireCollectionOffsets(JsonObject params) {
|
|
1843
|
-
JsonElement value = params.get("collection_offset");
|
|
1844
|
-
if (value == null || !value.isJsonObject()) {
|
|
1845
|
-
throw new RequestFailure("invalid_request", "collection_offset must be an object");
|
|
1846
|
-
}
|
|
1847
|
-
JsonObject result = value.getAsJsonObject();
|
|
1848
|
-
requireKeys(
|
|
1849
|
-
result,
|
|
1850
|
-
Set.of(
|
|
1851
|
-
"comments",
|
|
1852
|
-
"callers",
|
|
1853
|
-
"callees",
|
|
1854
|
-
"incoming_references",
|
|
1855
|
-
"outgoing_references",
|
|
1856
|
-
"referenced_strings",
|
|
1857
|
-
"referenced_names",
|
|
1858
|
-
"basic_blocks"
|
|
1859
|
-
)
|
|
1860
|
-
);
|
|
1861
|
-
for (String name : result.keySet()) {
|
|
1862
|
-
requireBoundedInteger(result, name, 0, Integer.MAX_VALUE);
|
|
1863
|
-
}
|
|
1864
|
-
return result;
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
|
-
private static int collectionOffset(JsonObject offsets, String name) {
|
|
1868
|
-
return requireBoundedInteger(offsets, name, 0, Integer.MAX_VALUE);
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
2024
|
private static String exclusiveAddress(Address inclusive) {
|
|
1872
2025
|
Address next = inclusive.next();
|
|
1873
2026
|
if (next != null && next.getAddressSpace().equals(inclusive.getAddressSpace())) {
|
|
@@ -1877,58 +2030,28 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1877
2030
|
return canonicalOffset(inclusive.getAddressSpace(), offset.add(BigInteger.ONE));
|
|
1878
2031
|
}
|
|
1879
2032
|
|
|
1880
|
-
private static String
|
|
2033
|
+
private static String diagnosticMessage(String value) {
|
|
1881
2034
|
if (value == null || value.isBlank()) {
|
|
1882
2035
|
return "native decompiler returned no detail";
|
|
1883
2036
|
}
|
|
1884
|
-
return
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
private static RequestFailure functionLimit(String message) {
|
|
1888
|
-
return new RequestFailure("limit_exceeded", message);
|
|
2037
|
+
return value.replaceAll("[\\r\\n]+", " ");
|
|
1889
2038
|
}
|
|
1890
2039
|
|
|
1891
|
-
private
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
JsonArray items = new JsonArray();
|
|
1899
|
-
for (int index = start; index < end; index += 1) {
|
|
1900
|
-
items.add(resultItem(inventory.get(index), factsName, MAX_LIST_VALUE_CODE_POINTS));
|
|
1901
|
-
}
|
|
1902
|
-
return pageResult(items, offset, limit, inventory.size());
|
|
1903
|
-
}
|
|
1904
|
-
|
|
1905
|
-
private static JsonObject pageResult(JsonArray items, int offset, int limit, int total) {
|
|
1906
|
-
int next = offset + items.size();
|
|
1907
|
-
boolean hasMore = next < total;
|
|
1908
|
-
JsonObject result = new JsonObject();
|
|
1909
|
-
result.add("items", items);
|
|
1910
|
-
result.addProperty("offset", offset);
|
|
1911
|
-
result.addProperty("limit", limit);
|
|
1912
|
-
result.addProperty("total", total);
|
|
1913
|
-
if (hasMore) {
|
|
1914
|
-
result.addProperty("next_offset", next);
|
|
1915
|
-
}
|
|
1916
|
-
else {
|
|
1917
|
-
result.add("next_offset", JsonNull.INSTANCE);
|
|
2040
|
+
private JsonArray inventory(
|
|
2041
|
+
List<InventoryItem> inventory,
|
|
2042
|
+
String factsName) throws Exception {
|
|
2043
|
+
JsonArray result = new JsonArray();
|
|
2044
|
+
for (InventoryItem item : inventory) {
|
|
2045
|
+
monitor.checkCancelled();
|
|
2046
|
+
result.add(resultItem(item, factsName));
|
|
1918
2047
|
}
|
|
1919
|
-
result.addProperty("has_more", hasMore);
|
|
1920
2048
|
return result;
|
|
1921
2049
|
}
|
|
1922
2050
|
|
|
1923
|
-
private static JsonObject resultItem(
|
|
1924
|
-
InventoryItem item,
|
|
1925
|
-
String factsName,
|
|
1926
|
-
int maximumCodePoints) {
|
|
1927
|
-
TruncatedValue truncated = truncate(item.value, maximumCodePoints);
|
|
2051
|
+
private static JsonObject resultItem(InventoryItem item, String factsName) {
|
|
1928
2052
|
JsonObject result = new JsonObject();
|
|
1929
2053
|
result.addProperty("address", canonicalAddress(item.address));
|
|
1930
|
-
result.addProperty("value",
|
|
1931
|
-
result.addProperty("value_truncated", truncated.truncated);
|
|
2054
|
+
result.addProperty("value", item.value);
|
|
1932
2055
|
if (factsName != null) {
|
|
1933
2056
|
result.add(factsName, item.facts.deepCopy());
|
|
1934
2057
|
}
|
|
@@ -2088,55 +2211,17 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2088
2211
|
return source.name().toLowerCase(Locale.ROOT);
|
|
2089
2212
|
}
|
|
2090
2213
|
|
|
2091
|
-
private static void addBounded(List<InventoryItem> destination, InventoryItem item) {
|
|
2092
|
-
if (destination.size() >= MAX_INVENTORY_ITEMS) {
|
|
2093
|
-
throw inventoryLimit();
|
|
2094
|
-
}
|
|
2095
|
-
destination.add(item);
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
private static RequestFailure inventoryLimit() {
|
|
2099
|
-
return new RequestFailure(
|
|
2100
|
-
"limit_exceeded",
|
|
2101
|
-
"Ghidra inventory exceeds the 1000000-item safety limit"
|
|
2102
|
-
);
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
private static TruncatedValue truncate(String value, int maximumCodePoints) {
|
|
2106
|
-
int codePoints = value.codePointCount(0, value.length());
|
|
2107
|
-
if (codePoints <= maximumCodePoints) {
|
|
2108
|
-
return new TruncatedValue(value, false);
|
|
2109
|
-
}
|
|
2110
|
-
int end = value.offsetByCodePoints(0, maximumCodePoints);
|
|
2111
|
-
return new TruncatedValue(value.substring(0, end), true);
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
2214
|
private static ValueMatcher literalMatcher(String pattern, boolean caseSensitive) {
|
|
2115
2215
|
String needle = caseSensitive ? pattern : pattern.toLowerCase(Locale.ROOT);
|
|
2116
|
-
return
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
@Override
|
|
2120
|
-
public boolean matches(String value) {
|
|
2121
|
-
long required = (long) Math.max(value.length(), 1) + needle.length();
|
|
2122
|
-
if (required > remaining) {
|
|
2123
|
-
throw new RequestFailure(
|
|
2124
|
-
"limit_exceeded",
|
|
2125
|
-
"Literal search exceeds the 1000000-unit work budget"
|
|
2126
|
-
);
|
|
2127
|
-
}
|
|
2128
|
-
remaining -= required;
|
|
2129
|
-
String candidate = caseSensitive ? value : value.toLowerCase(Locale.ROOT);
|
|
2130
|
-
return candidate.contains(needle);
|
|
2131
|
-
}
|
|
2216
|
+
return value -> {
|
|
2217
|
+
String candidate = caseSensitive ? value : value.toLowerCase(Locale.ROOT);
|
|
2218
|
+
return candidate.contains(needle);
|
|
2132
2219
|
};
|
|
2133
2220
|
}
|
|
2134
2221
|
|
|
2135
2222
|
private static ValueMatcher regexMatcher(String expression, boolean caseSensitive) {
|
|
2136
|
-
RegexMetrics metrics;
|
|
2137
2223
|
Pattern pattern;
|
|
2138
2224
|
try {
|
|
2139
|
-
metrics = new BoundedRegexParser(expression).parse();
|
|
2140
2225
|
pattern = Pattern.compile(
|
|
2141
2226
|
expression,
|
|
2142
2227
|
caseSensitive ? 0 : Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE
|
|
@@ -2145,43 +2230,17 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2145
2230
|
catch (PatternSyntaxException exception) {
|
|
2146
2231
|
throw new RequestFailure("invalid_request", "Invalid regex pattern");
|
|
2147
2232
|
}
|
|
2148
|
-
|
|
2149
|
-
return new ValueMatcher() {
|
|
2150
|
-
private long remaining = MAX_REGEX_SEARCH_WORK_UNITS;
|
|
2151
|
-
|
|
2152
|
-
@Override
|
|
2153
|
-
public boolean matches(String value) {
|
|
2154
|
-
if (value.length() > MAX_REGEX_CANDIDATE_CHARACTERS) {
|
|
2155
|
-
throw new RequestFailure(
|
|
2156
|
-
"limit_exceeded",
|
|
2157
|
-
"Regex candidate exceeds the 4096-character safety limit"
|
|
2158
|
-
);
|
|
2159
|
-
}
|
|
2160
|
-
long required = workPerCharacter * Math.max(value.length(), 1);
|
|
2161
|
-
if (required > remaining) {
|
|
2162
|
-
throw new RequestFailure(
|
|
2163
|
-
"limit_exceeded",
|
|
2164
|
-
"Regex search exceeds the 1000000-unit work budget"
|
|
2165
|
-
);
|
|
2166
|
-
}
|
|
2167
|
-
remaining -= required;
|
|
2168
|
-
return pattern.matcher(value).find();
|
|
2169
|
-
}
|
|
2170
|
-
};
|
|
2233
|
+
return value -> pattern.matcher(value).find();
|
|
2171
2234
|
}
|
|
2172
2235
|
|
|
2173
2236
|
private static SessionDescriptor readDescriptor(Path path) throws IOException {
|
|
2174
|
-
if (!Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)
|
|
2175
|
-
Files.size(path) > MAX_DESCRIPTOR_BYTES) {
|
|
2237
|
+
if (!Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) {
|
|
2176
2238
|
throw new IllegalArgumentException("REA session descriptor is invalid");
|
|
2177
2239
|
}
|
|
2178
2240
|
JsonObject object = requireObject(
|
|
2179
2241
|
JsonParser.parseString(Files.readString(path, StandardCharsets.UTF_8)),
|
|
2180
2242
|
DESCRIPTOR_KEYS
|
|
2181
2243
|
);
|
|
2182
|
-
if (requireInteger(object, "schema_version") != 2) {
|
|
2183
|
-
throw new IllegalArgumentException("REA session descriptor version is invalid");
|
|
2184
|
-
}
|
|
2185
2244
|
return new SessionDescriptor(
|
|
2186
2245
|
requireString(object, "transport"),
|
|
2187
2246
|
requireString(object, "endpoint_path"),
|
|
@@ -2312,25 +2371,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2312
2371
|
);
|
|
2313
2372
|
}
|
|
2314
2373
|
|
|
2315
|
-
private static String readBoundedLine(Reader reader, int maximumCharacters) throws IOException {
|
|
2316
|
-
StringBuilder line = new StringBuilder();
|
|
2317
|
-
while (true) {
|
|
2318
|
-
int character = reader.read();
|
|
2319
|
-
if (character < 0) {
|
|
2320
|
-
return line.isEmpty() ? null : line.toString();
|
|
2321
|
-
}
|
|
2322
|
-
if (character == '\n') {
|
|
2323
|
-
return line.toString();
|
|
2324
|
-
}
|
|
2325
|
-
if (character != '\r') {
|
|
2326
|
-
if (line.length() >= maximumCharacters) {
|
|
2327
|
-
throw new IOException("Bridge request exceeded the maximum line size");
|
|
2328
|
-
}
|
|
2329
|
-
line.append((char) character);
|
|
2330
|
-
}
|
|
2331
|
-
}
|
|
2332
|
-
}
|
|
2333
|
-
|
|
2334
2374
|
private static void writeSuccess(BufferedWriter writer, int id, JsonElement result)
|
|
2335
2375
|
throws IOException {
|
|
2336
2376
|
JsonObject response = new JsonObject();
|
|
@@ -2357,17 +2397,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2357
2397
|
|
|
2358
2398
|
private static void writeResponse(BufferedWriter writer, JsonObject response) throws IOException {
|
|
2359
2399
|
String encoded = GSON.toJson(response);
|
|
2360
|
-
if (encoded.getBytes(StandardCharsets.UTF_8).length > MAX_RESPONSE_BYTES) {
|
|
2361
|
-
int id = response.get("id").getAsInt();
|
|
2362
|
-
JsonObject error = new JsonObject();
|
|
2363
|
-
error.addProperty("code", "output_limit");
|
|
2364
|
-
error.addProperty("message", "Ghidra response exceeds the 1 MiB wire limit");
|
|
2365
|
-
JsonObject bounded = new JsonObject();
|
|
2366
|
-
bounded.addProperty("id", id);
|
|
2367
|
-
bounded.addProperty("ok", false);
|
|
2368
|
-
bounded.add("error", error);
|
|
2369
|
-
encoded = GSON.toJson(bounded);
|
|
2370
|
-
}
|
|
2371
2400
|
writer.write(encoded);
|
|
2372
2401
|
writer.newLine();
|
|
2373
2402
|
writer.flush();
|
|
@@ -2396,7 +2425,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2396
2425
|
private record InventoryItem(Address address, String value, JsonObject facts) {}
|
|
2397
2426
|
private record FunctionEntry(Function function, InventoryItem item) {}
|
|
2398
2427
|
private record InstructionScan(List<Instruction> instructions, boolean truncated) {}
|
|
2399
|
-
private record TruncatedValue(String value, boolean truncated) {}
|
|
2400
2428
|
private record SessionDescriptor(
|
|
2401
2429
|
String transport,
|
|
2402
2430
|
String endpointPath,
|
|
@@ -2407,7 +2435,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2407
2435
|
String profileDigest
|
|
2408
2436
|
) {}
|
|
2409
2437
|
private record Request(int id, String method, JsonObject params) {}
|
|
2410
|
-
private record RegexMetrics(long paths, long steps, boolean containsRepeat) {}
|
|
2411
2438
|
|
|
2412
2439
|
private static final class RequestFailure extends RuntimeException {
|
|
2413
2440
|
private final String code;
|
|
@@ -2418,263 +2445,4 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2418
2445
|
}
|
|
2419
2446
|
}
|
|
2420
2447
|
|
|
2421
|
-
private static final class BoundedRegexParser {
|
|
2422
|
-
private final String expression;
|
|
2423
|
-
private int index;
|
|
2424
|
-
|
|
2425
|
-
BoundedRegexParser(String expression) {
|
|
2426
|
-
this.expression = expression;
|
|
2427
|
-
}
|
|
2428
|
-
|
|
2429
|
-
RegexMetrics parse() {
|
|
2430
|
-
RegexMetrics result = parseExpression(false);
|
|
2431
|
-
if (index != expression.length()) {
|
|
2432
|
-
throw invalidRegex();
|
|
2433
|
-
}
|
|
2434
|
-
return result;
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2437
|
-
private RegexMetrics parseExpression(boolean grouped) {
|
|
2438
|
-
RegexMetrics result = parseSequence(grouped);
|
|
2439
|
-
while (peek('|')) {
|
|
2440
|
-
index += 1;
|
|
2441
|
-
RegexMetrics branch = parseSequence(grouped);
|
|
2442
|
-
result = new RegexMetrics(
|
|
2443
|
-
checkedPaths(result.paths, branch.paths, false),
|
|
2444
|
-
Math.max(result.steps, branch.steps),
|
|
2445
|
-
result.containsRepeat || branch.containsRepeat
|
|
2446
|
-
);
|
|
2447
|
-
}
|
|
2448
|
-
return result;
|
|
2449
|
-
}
|
|
2450
|
-
|
|
2451
|
-
private RegexMetrics parseSequence(boolean grouped) {
|
|
2452
|
-
RegexMetrics result = new RegexMetrics(1, 0, false);
|
|
2453
|
-
while (index < expression.length() && !peek('|') && !(grouped && peek(')'))) {
|
|
2454
|
-
RegexMetrics item = parseAtom();
|
|
2455
|
-
result = new RegexMetrics(
|
|
2456
|
-
checkedPaths(result.paths, item.paths, true),
|
|
2457
|
-
result.steps + item.steps,
|
|
2458
|
-
result.containsRepeat || item.containsRepeat
|
|
2459
|
-
);
|
|
2460
|
-
}
|
|
2461
|
-
return result;
|
|
2462
|
-
}
|
|
2463
|
-
|
|
2464
|
-
private RegexMetrics parseAtom() {
|
|
2465
|
-
if (index >= expression.length()) {
|
|
2466
|
-
throw invalidRegex();
|
|
2467
|
-
}
|
|
2468
|
-
char item = expression.charAt(index++);
|
|
2469
|
-
RegexMetrics atom;
|
|
2470
|
-
if (item == '(') {
|
|
2471
|
-
if (peek('?')) {
|
|
2472
|
-
if (index + 1 >= expression.length() || expression.charAt(index + 1) != ':') {
|
|
2473
|
-
throw new RequestFailure(
|
|
2474
|
-
"invalid_request",
|
|
2475
|
-
"Regex lookarounds and backreferences are not supported"
|
|
2476
|
-
);
|
|
2477
|
-
}
|
|
2478
|
-
index += 2;
|
|
2479
|
-
}
|
|
2480
|
-
atom = parseExpression(true);
|
|
2481
|
-
if (!peek(')')) {
|
|
2482
|
-
throw invalidRegex();
|
|
2483
|
-
}
|
|
2484
|
-
index += 1;
|
|
2485
|
-
}
|
|
2486
|
-
else if (item == '[') {
|
|
2487
|
-
consumeCharacterClass();
|
|
2488
|
-
atom = leaf();
|
|
2489
|
-
}
|
|
2490
|
-
else if (item == '\\') {
|
|
2491
|
-
consumeEscape();
|
|
2492
|
-
atom = leaf();
|
|
2493
|
-
}
|
|
2494
|
-
else if (item == '*' || item == '+') {
|
|
2495
|
-
throw unboundedRepeat();
|
|
2496
|
-
}
|
|
2497
|
-
else if (item == '?' || item == '{' || item == '}' || item == ')') {
|
|
2498
|
-
throw invalidRegex();
|
|
2499
|
-
}
|
|
2500
|
-
else {
|
|
2501
|
-
atom = leaf();
|
|
2502
|
-
}
|
|
2503
|
-
if (index >= expression.length()) {
|
|
2504
|
-
return atom;
|
|
2505
|
-
}
|
|
2506
|
-
if (peek('*') || peek('+')) {
|
|
2507
|
-
throw unboundedRepeat();
|
|
2508
|
-
}
|
|
2509
|
-
int minimum;
|
|
2510
|
-
int maximum;
|
|
2511
|
-
if (peek('?')) {
|
|
2512
|
-
index += 1;
|
|
2513
|
-
minimum = 0;
|
|
2514
|
-
maximum = 1;
|
|
2515
|
-
}
|
|
2516
|
-
else if (peek('{')) {
|
|
2517
|
-
int[] bounds = consumeBounds();
|
|
2518
|
-
minimum = bounds[0];
|
|
2519
|
-
maximum = bounds[1];
|
|
2520
|
-
}
|
|
2521
|
-
else {
|
|
2522
|
-
return atom;
|
|
2523
|
-
}
|
|
2524
|
-
if (atom.containsRepeat) {
|
|
2525
|
-
throw new RequestFailure(
|
|
2526
|
-
"invalid_request",
|
|
2527
|
-
"Nested regex repetitions are not supported"
|
|
2528
|
-
);
|
|
2529
|
-
}
|
|
2530
|
-
if (maximum > 1_000) {
|
|
2531
|
-
throw unboundedRepeat();
|
|
2532
|
-
}
|
|
2533
|
-
return new RegexMetrics(
|
|
2534
|
-
repeatPaths(atom.paths, minimum, maximum),
|
|
2535
|
-
maximum * atom.steps,
|
|
2536
|
-
true
|
|
2537
|
-
);
|
|
2538
|
-
}
|
|
2539
|
-
|
|
2540
|
-
private void consumeCharacterClass() {
|
|
2541
|
-
boolean escaped = false;
|
|
2542
|
-
boolean hasContent = false;
|
|
2543
|
-
while (index < expression.length()) {
|
|
2544
|
-
char item = expression.charAt(index++);
|
|
2545
|
-
if (escaped) {
|
|
2546
|
-
escaped = false;
|
|
2547
|
-
hasContent = true;
|
|
2548
|
-
}
|
|
2549
|
-
else if (item == '\\') {
|
|
2550
|
-
escaped = true;
|
|
2551
|
-
}
|
|
2552
|
-
else if (item == ']' && hasContent) {
|
|
2553
|
-
return;
|
|
2554
|
-
}
|
|
2555
|
-
else {
|
|
2556
|
-
hasContent = true;
|
|
2557
|
-
}
|
|
2558
|
-
}
|
|
2559
|
-
throw invalidRegex();
|
|
2560
|
-
}
|
|
2561
|
-
|
|
2562
|
-
private void consumeEscape() {
|
|
2563
|
-
if (index >= expression.length()) {
|
|
2564
|
-
throw invalidRegex();
|
|
2565
|
-
}
|
|
2566
|
-
char escaped = expression.charAt(index++);
|
|
2567
|
-
if (Character.isDigit(escaped) || escaped == 'k') {
|
|
2568
|
-
throw new RequestFailure(
|
|
2569
|
-
"invalid_request",
|
|
2570
|
-
"Regex lookarounds and backreferences are not supported"
|
|
2571
|
-
);
|
|
2572
|
-
}
|
|
2573
|
-
if (escaped == 'Q' || escaped == 'E' || escaped == 'p' || escaped == 'P') {
|
|
2574
|
-
throw new RequestFailure(
|
|
2575
|
-
"invalid_request",
|
|
2576
|
-
"Regex operation is not supported by the bounded matcher"
|
|
2577
|
-
);
|
|
2578
|
-
}
|
|
2579
|
-
if (escaped == 'x') {
|
|
2580
|
-
consumeHexDigits(2);
|
|
2581
|
-
}
|
|
2582
|
-
else if (escaped == 'u') {
|
|
2583
|
-
consumeHexDigits(4);
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
|
|
2587
|
-
private void consumeHexDigits(int count) {
|
|
2588
|
-
if (index + count > expression.length()) {
|
|
2589
|
-
throw invalidRegex();
|
|
2590
|
-
}
|
|
2591
|
-
for (int current = 0; current < count; current += 1) {
|
|
2592
|
-
if (Character.digit(expression.charAt(index + current), 16) < 0) {
|
|
2593
|
-
throw invalidRegex();
|
|
2594
|
-
}
|
|
2595
|
-
}
|
|
2596
|
-
index += count;
|
|
2597
|
-
}
|
|
2598
|
-
|
|
2599
|
-
private int[] consumeBounds() {
|
|
2600
|
-
index += 1;
|
|
2601
|
-
int minimum = consumeDecimal();
|
|
2602
|
-
int maximum = minimum;
|
|
2603
|
-
if (peek(',')) {
|
|
2604
|
-
index += 1;
|
|
2605
|
-
if (peek('}')) {
|
|
2606
|
-
throw unboundedRepeat();
|
|
2607
|
-
}
|
|
2608
|
-
maximum = consumeDecimal();
|
|
2609
|
-
}
|
|
2610
|
-
if (!peek('}') || minimum > maximum) {
|
|
2611
|
-
throw invalidRegex();
|
|
2612
|
-
}
|
|
2613
|
-
index += 1;
|
|
2614
|
-
return new int[] { minimum, maximum };
|
|
2615
|
-
}
|
|
2616
|
-
|
|
2617
|
-
private int consumeDecimal() {
|
|
2618
|
-
int start = index;
|
|
2619
|
-
long value = 0;
|
|
2620
|
-
while (index < expression.length() && Character.isDigit(expression.charAt(index))) {
|
|
2621
|
-
value = value * 10 + Character.digit(expression.charAt(index), 10);
|
|
2622
|
-
if (value > Integer.MAX_VALUE) {
|
|
2623
|
-
throw unboundedRepeat();
|
|
2624
|
-
}
|
|
2625
|
-
index += 1;
|
|
2626
|
-
}
|
|
2627
|
-
if (index == start) {
|
|
2628
|
-
throw invalidRegex();
|
|
2629
|
-
}
|
|
2630
|
-
return (int) value;
|
|
2631
|
-
}
|
|
2632
|
-
|
|
2633
|
-
private boolean peek(char expected) {
|
|
2634
|
-
return index < expression.length() && expression.charAt(index) == expected;
|
|
2635
|
-
}
|
|
2636
|
-
|
|
2637
|
-
private static RegexMetrics leaf() {
|
|
2638
|
-
return new RegexMetrics(1, 1, false);
|
|
2639
|
-
}
|
|
2640
|
-
|
|
2641
|
-
private static long repeatPaths(long childPaths, int minimum, int maximum) {
|
|
2642
|
-
long result = 0;
|
|
2643
|
-
long repeated = 1;
|
|
2644
|
-
for (int count = 0; count <= maximum; count += 1) {
|
|
2645
|
-
if (count >= minimum) {
|
|
2646
|
-
result = checkedPaths(result, repeated, false);
|
|
2647
|
-
}
|
|
2648
|
-
if (count < maximum) {
|
|
2649
|
-
repeated = checkedPaths(repeated, childPaths, true);
|
|
2650
|
-
}
|
|
2651
|
-
}
|
|
2652
|
-
return result;
|
|
2653
|
-
}
|
|
2654
|
-
|
|
2655
|
-
private static long checkedPaths(long left, long right, boolean multiply) {
|
|
2656
|
-
boolean exceeded = multiply
|
|
2657
|
-
? right != 0 && left > MAX_REGEX_BACKTRACKING_PATHS / right
|
|
2658
|
-
: left > MAX_REGEX_BACKTRACKING_PATHS - right;
|
|
2659
|
-
long result = multiply ? left * right : left + right;
|
|
2660
|
-
if (exceeded || result > MAX_REGEX_BACKTRACKING_PATHS) {
|
|
2661
|
-
throw new RequestFailure(
|
|
2662
|
-
"invalid_request",
|
|
2663
|
-
"Regex exceeds the 10000-path backtracking budget"
|
|
2664
|
-
);
|
|
2665
|
-
}
|
|
2666
|
-
return result;
|
|
2667
|
-
}
|
|
2668
|
-
|
|
2669
|
-
private static RequestFailure unboundedRepeat() {
|
|
2670
|
-
return new RequestFailure(
|
|
2671
|
-
"invalid_request",
|
|
2672
|
-
"Unbounded or excessive regex repetitions are not supported"
|
|
2673
|
-
);
|
|
2674
|
-
}
|
|
2675
|
-
|
|
2676
|
-
private static RequestFailure invalidRegex() {
|
|
2677
|
-
return new RequestFailure("invalid_request", "Invalid regex pattern");
|
|
2678
|
-
}
|
|
2679
|
-
}
|
|
2680
2448
|
}
|