rea-agents 6.3.0 → 6.4.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 +7 -4
- package/bridge/ghidra/ReaGhidraApplySeeds.java +151 -0
- package/bridge/ghidra/ReaGhidraBridge.java +597 -209
- package/bridge/ghidra/ReaGhidraNoReturnFix.java +123 -0
- package/dist/adb/AdbConfiguration.js +88 -0
- package/dist/adb/AdbDeviceOutput.js +186 -0
- package/dist/adb/AdbProvider.js +1190 -0
- package/dist/adb/AdbShellOutput.js +309 -0
- package/dist/android/AndroidTargetSnapshot.js +104 -22
- package/dist/android/JadxConfiguration.js +44 -24
- package/dist/android/JadxProvider.js +51 -19
- package/dist/apktool/ApktoolConfiguration.js +84 -0
- package/dist/apktool/ApktoolDecodeOutput.js +59 -0
- package/dist/apktool/ApktoolProvider.js +506 -0
- package/dist/application/AnalysisProvider.js +8 -2
- package/dist/application/AppBundleExecutable.js +27 -22
- package/dist/application/BinaryTargetResolver.js +22 -2
- package/dist/application/CapabilityInventory.js +11 -1
- package/dist/application/ClientConfigurationDocument.js +70 -0
- package/dist/application/ClientConfigurationFiles.js +91 -0
- package/dist/application/ClientRegistrationEnvironment.js +35 -0
- package/dist/application/ClientRegistrationStatus.js +66 -31
- package/dist/application/DirectAnalysis.js +2 -2
- package/dist/application/DirectAnalysisCleanup.js +1 -1
- package/dist/application/DirectAnalysisStatus.js +1 -1
- package/dist/application/Doctor.js +5 -3
- package/dist/application/DoctorDiagnostics.js +1 -1
- package/dist/application/EvidenceBundleCommands.js +6 -12
- package/dist/application/GeminiClientSettings.js +139 -0
- package/dist/application/HermesClientHome.js +115 -0
- package/dist/application/HopperDownload.js +39 -0
- package/dist/application/InvestigationProviders.js +6 -0
- package/dist/application/JsonFiles.js +6 -5
- package/dist/{cliJsonFile.js → application/JsonInputFile.js} +108 -35
- package/dist/application/LinuxHopper.js +4 -12
- package/dist/application/MacHopper.js +172 -149
- package/dist/application/MipsElfAbiFlags.js +140 -0
- package/dist/application/ProviderOperationExecution.js +31 -0
- package/dist/application/ReconstructionObligationCandidates.js +9 -3
- package/dist/application/ReferenceSourceImport.js +76 -29
- package/dist/application/ReferenceSourceImportEntries.js +18 -9
- package/dist/application/ReferenceSourceImportPolicy.js +99 -23
- package/dist/application/ReferenceSourceImportTypes.js +30 -22
- package/dist/application/ReferenceSourceVcsAdapter.js +145 -30
- package/dist/application/RegularFileRead.js +93 -7
- package/dist/application/Setup.js +54 -6
- package/dist/application/SetupClientConfiguration.js +116 -16
- package/dist/application/SetupHost.js +102 -24
- package/dist/application/SetupPlan.js +5 -8
- package/dist/application/SetupSkill.js +58 -39
- package/dist/application/SupportedClients.js +290 -65
- package/dist/application/Uninstall.js +158 -10
- package/dist/application/UpdateMaintenance.js +17 -21
- package/dist/application/Utf8JsonInput.js +7 -0
- package/dist/application/WebScriptExportService.js +131 -65
- package/dist/application/WebSourceLocationService.js +3 -0
- package/dist/application/adb/AdbDeviceAnalysisPort.js +1 -0
- package/dist/application/adb/AdbDeviceAnalysisService.js +13 -0
- package/dist/application/analysisView/AnalysisViewService.js +40 -2
- package/dist/application/apktool/ApktoolResourceAnalysisPort.js +1 -0
- package/dist/application/apktool/ApktoolResourceAnalysisService.js +13 -0
- package/dist/application/binary/AnalysisClientCleanup.js +0 -24
- package/dist/application/binary/BinarySession.js +2 -1
- package/dist/application/binaryDiagnostics/BinaryLayoutService.js +2 -1
- package/dist/application/binaryDiagnostics/PeResourcesPort.js +1 -0
- package/dist/application/binaryDiagnostics/PeResourcesService.js +111 -0
- package/dist/application/binaryDiagnostics/RecordedCrashService.js +2 -1
- package/dist/application/evm/EvmInterfaceService.js +3 -0
- package/dist/application/firmware/FirmwareAnalysisService.js +3 -0
- package/dist/application/investigation/InspectEvidenceBundle.js +18 -0
- package/dist/application/javascript/ElectronBoundaryGraphContext.js +3 -2
- package/dist/application/javascript/ElectronBoundaryGraphIpc.js +2 -2
- package/dist/application/javascript/ElectronBoundaryGraphNative.js +19 -6
- package/dist/application/javascript/ElectronBoundaryGraphWindows.js +3 -3
- package/dist/application/javascript/JavaScriptApplicationService.js +22 -11
- package/dist/application/javascript/JavaScriptApplicationWorkflowEvidence.js +5 -6
- package/dist/application/javascript/JavaScriptArtifactAnalysis.js +171 -30
- package/dist/application/javascript/JavaScriptArtifactGraphAccumulator.js +14 -16
- package/dist/application/javascript/JavaScriptArtifactGraphBuilder.js +67 -19
- package/dist/application/javascript/JavaScriptArtifactGraphBundlers.js +4 -3
- package/dist/application/javascript/JavaScriptArtifactGraphContext.js +11 -5
- package/dist/application/javascript/JavaScriptArtifactGraphFindings.js +19 -16
- package/dist/application/javascript/JavaScriptArtifactGraphStructure.js +92 -10
- package/dist/application/javascript/JavaScriptArtifactPathResolution.js +258 -235
- package/dist/application/javascript/JavaScriptArtifactReconstruction.js +115 -26
- package/dist/application/javascript/JavaScriptArtifactReconstructionInput.js +4 -1
- package/dist/application/javascript/JavaScriptModuleRelationships.js +6 -4
- package/dist/application/javascript/JavaScriptRecoveryService.js +3 -0
- package/dist/application/javascript/JavaScriptSemanticGraphAsyncProjection.js +9 -3
- package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +199 -61
- package/dist/application/javascript/JavaScriptSemanticGraphConstruction.js +12 -19
- package/dist/application/javascript/JavaScriptSemanticGraphDataProjection.js +4 -1
- package/dist/application/javascript/JavaScriptSemanticGraphFlowProjection.js +5 -3
- package/dist/application/javascript/JavaScriptSemanticGraphObjectProjection.js +5 -3
- package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +19 -8
- package/dist/application/jeb/JebAnalysisPort.js +1 -0
- package/dist/application/jeb/JebAnalysisService.js +13 -0
- package/dist/application/managed/ManagedApplicationGraphService.js +1 -1
- package/dist/application/native/NativeUiActionTrace.js +16 -8
- package/dist/application/native/NativeValueTrace.js +23 -6
- package/dist/application/process/ProcessCli.js +25 -20
- package/dist/artifacts/ArtifactPaths.js +129 -8
- package/dist/artifacts/ArtifactProvider.js +20 -15
- package/dist/artifacts/ArtifactReader.js +23 -0
- package/dist/artifacts/ArtifactResourceScope.js +100 -0
- package/dist/artifacts/AsarArtifactReader.js +176 -102
- package/dist/artifacts/AsarHeader.js +24 -0
- package/dist/artifacts/DirectoryArtifactReader.js +168 -65
- package/dist/artifacts/MachOSliceArtifactReader.js +116 -39
- package/dist/artifacts/NativeDmgArtifactReader.js +29 -22
- package/dist/artifacts/SafeOutputTree.js +245 -70
- package/dist/artifacts/SafeOutputTreeCleanup.js +45 -0
- package/dist/artifacts/SafeOutputTreeCreationFailure.js +20 -0
- package/dist/artifacts/SafeOutputTreeIdentity.js +42 -0
- package/dist/artifacts/SafeOutputTreeReadback.js +33 -0
- package/dist/artifacts/ZipArtifactReader.js +77 -27
- package/dist/artifacts/apple/InterfaceBuilderAnalysis.js +58 -18
- package/dist/artifacts/apple/InterfaceBuilderDecodeBudget.js +62 -24
- package/dist/artifacts/apple/KeyedArchiveReader.js +32 -13
- package/dist/artifacts/extraction/ArtifactExtraction.js +110 -86
- package/dist/artifacts/inventory/ArtifactGraphConstruction.js +110 -43
- package/dist/artifacts/inventory/ArtifactInventory.js +5 -4
- package/dist/artifacts/inventory/classify.js +75 -32
- package/dist/artifacts/inventory/hashStableRootArtifact.js +48 -15
- package/dist/artifacts/inventory/reader.js +13 -5
- package/dist/artifacts/inventory/scanCanonical.js +62 -25
- package/dist/artifacts/inventory/scanReader.js +38 -11
- package/dist/artifacts/javascript/JavaScriptArtifactFiles.js +15 -9
- package/dist/artifacts/javascript/JavaScriptSourceAnalysis.js +3 -0
- package/dist/browser/BrowserScenarioPartialObservation.js +8 -2
- package/dist/browser/BrowserScenarioSecrets.js +52 -37
- package/dist/browser/CdpBrowserProvider.js +5 -0
- package/dist/browser/CdpCaptureDocuments.js +40 -6
- package/dist/browser/CdpCaptureDomSvg.js +210 -0
- package/dist/browser/CdpCaptureEventBodyShapes.js +4 -8
- package/dist/browser/CdpCaptureEventHandlers.js +26 -9
- package/dist/browser/CdpCaptureEvents.js +4 -9
- package/dist/browser/CdpConnection.js +22 -5
- package/dist/browser/CdpPageCapture.js +1 -1
- package/dist/browser/CdpTargetSession.js +30 -34
- package/dist/browser/CdpWebMcpDiscovery.js +32 -10
- package/dist/browser/ElectronFileScope.js +99 -9
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +60 -4
- package/dist/browser/PlaywrightElectronActiveProvider.js +11 -6
- package/dist/browser/PlaywrightScenarioArtifacts.js +5 -5
- package/dist/browser/PlaywrightScenarioBrowser.js +69 -7
- package/dist/browser/PlaywrightScenarioSession.js +127 -54
- package/dist/browser/PlaywrightScenarioStorage.js +507 -0
- package/dist/browser/PngVisualDiff.js +198 -29
- package/dist/browser/WebSourceMapFetcher.js +3 -3
- package/dist/browser/assets/LocalWebScriptArtifacts.js +3 -1
- package/dist/browser/assets/PublishWebScripts.js +75 -20
- package/dist/browser/assets/WebArtifactReads.js +23 -1
- package/dist/browser/assets/WebSourceLocationArtifacts.js +1 -0
- package/dist/browser/execution/CdpExecutionCapture.js +3 -3
- package/dist/browser/execution/CdpPreciseCoverage.js +5 -8
- package/dist/browser/history/HarCapture.js +14 -0
- package/dist/browser/history/HarCaptureProcess.js +2 -10
- package/dist/browser/history/MitmproxyCaptureAdapter.js +3 -1
- package/dist/browser/modules/WebModuleArtifacts.js +1 -0
- package/dist/cli/adbCommands.js +337 -0
- package/dist/cli/analysisViewCommands.js +39 -1
- package/dist/cli/apktoolCommands.js +53 -0
- package/dist/cli/artifactCommands.js +8 -11
- package/dist/cli/binaryDiagnosticsCommands.js +19 -1
- package/dist/cli/browserAdvancedCommands.js +65 -36
- package/dist/cli/coreBinaryCommands.js +0 -5
- package/dist/cli/coreNativeCommands.js +1 -14
- package/dist/cli/electronCommands.js +6 -1
- package/dist/cli/evmCommands.js +13 -4
- package/dist/cli/firmwareCommands.js +7 -2
- package/dist/cli/javascriptApplicationAnalysis.js +17 -1
- package/dist/cli/javascriptRecoveryCommands.js +22 -15
- package/dist/cli/jebCommands.js +99 -0
- package/dist/cli/jsonOutput.js +11 -4
- package/dist/cli/referenceSourceImportStatus.js +7 -0
- package/dist/cli/setupCommands.js +15 -3
- package/dist/cli/utilityCommands.js +66 -18
- package/dist/cli/webScriptCommands.js +20 -1
- package/dist/cli/webSourceLocationCommands.js +21 -9
- package/dist/cli.js +6 -0
- package/dist/cliBrowserScenarioCommands.js +2 -1
- package/dist/cliCommandNames.js +32 -0
- package/dist/cliJsonInput.js +2 -2
- package/dist/cliObservationOptions.js +3 -0
- package/dist/composition/adb.js +3 -0
- package/dist/composition/apktool.js +3 -0
- package/dist/composition/binaryDiagnostics.js +4 -0
- package/dist/composition/directAnalysis.js +12 -4
- package/dist/composition/javascriptAnalysisWorkerProcess.js +48 -0
- package/dist/composition/jeb.js +6 -0
- package/dist/config/environment.js +10 -0
- package/dist/config/ghidraLanguageOverride.js +9 -0
- package/dist/config/mcpInputSchemaProfile.js +27 -0
- package/dist/config/parseConfig.js +28 -1
- package/dist/contracts/adb/adbToolContracts.js +353 -0
- package/dist/contracts/advertisedInputExamples.js +48 -0
- package/dist/contracts/analysisDetail.js +10 -0
- package/dist/contracts/analysisViewToolContracts.js +17 -2
- package/dist/contracts/apktool/apktoolToolContracts.js +33 -0
- package/dist/contracts/artifactToolContracts.js +8 -5
- package/dist/contracts/browserToolContracts.js +1 -1
- package/dist/contracts/compactInputSchema.js +256 -0
- package/dist/contracts/functionWorkflowToolContracts.js +1 -1
- package/dist/contracts/inputSchemaPresentation.js +10 -10
- package/dist/contracts/javascript/electronToolContracts.js +15 -3
- package/dist/contracts/javascript/javascriptRecoveryToolContracts.js +1 -1
- package/dist/contracts/jeb/jebToolContracts.js +64 -0
- package/dist/contracts/native/binaryDiagnosticsToolContracts.js +25 -3
- package/dist/contracts/officialToolContracts.js +5 -5
- package/dist/contracts/sessionToolContracts.js +2 -2
- package/dist/contracts/sessionToolSchemas.js +6 -2
- package/dist/contracts/toolContractExamples.js +6 -0
- package/dist/contracts/toolContractTypes.js +3 -0
- package/dist/contracts/toolContracts.js +6 -0
- package/dist/contracts/toolEffects.js +167 -0
- package/dist/contracts/toolOutputSchemaGroups.js +5 -3
- package/dist/contracts/toolSchemaMetadata.js +82 -7
- package/dist/domain/adb/adbDeviceAnalysis.js +583 -0
- package/dist/domain/analysisErrorCleanup.js +29 -0
- package/dist/domain/analysisErrorPresentation.js +18 -6
- package/dist/domain/analysisErrorProjection.js +4 -1
- package/dist/domain/analysisSnapshot.js +2 -1
- package/dist/domain/analysisView/analysisView.js +52 -2
- package/dist/domain/analysisView/binaryLayoutView.js +2 -0
- package/dist/domain/analysisView/javascriptApplicationView.js +2 -0
- package/dist/domain/analysisView/nativeFunctionView.js +155 -0
- package/dist/domain/android/androidApplication.js +25 -20
- package/dist/domain/apktool/apktoolResourceAnalysis.js +77 -0
- package/dist/domain/apple/appleApplication.js +40 -23
- package/dist/domain/apple/appleBundleAnatomy.js +95 -28
- package/dist/domain/apple/dyldPaths.js +9 -1
- package/dist/domain/apple/dylibResolution.js +2 -2
- package/dist/domain/apple/interfaceBuilderGraph.js +22 -19
- package/dist/domain/apple/interfaceBuilderKeyedArchive.js +10 -1
- package/dist/domain/artifactComparison.js +35 -7
- package/dist/domain/artifactGraph.js +5 -3
- package/dist/domain/artifactIdentity.js +30 -2
- package/dist/domain/artifactInspection.js +3 -43
- package/dist/domain/artifactIntegrityPolicy.js +10 -0
- package/dist/domain/artifactInventoryEvidence.js +11 -12
- package/dist/domain/artifactPathSyntax.js +1 -1
- package/dist/domain/binaryTarget.js +16 -0
- package/dist/domain/binaryTargetTypes.js +8 -1
- package/dist/domain/browserObservationSchemas.js +8 -4
- package/dist/domain/canonicalJsonDigestSteps.js +34 -12
- package/dist/domain/compactStringIdentity.js +9 -0
- package/dist/domain/evidence.js +2 -1
- package/dist/domain/evidenceRecordSummary.js +144 -0
- package/dist/domain/inputIssueProjection.js +16 -1
- package/dist/domain/javascript/electronStaticAnalysisBrowser.js +12 -13
- package/dist/domain/javascript/electronStaticAnalysisIpc.js +11 -20
- package/dist/domain/javascript/electronStaticAnalysisNative.js +70 -40
- package/dist/domain/javascript/electronStaticAnalysisValues.js +16 -0
- package/dist/domain/javascript/javascriptAnalysisResourceControls.js +45 -0
- package/dist/domain/javascript/javascriptApplicationAnalysis.js +4 -1
- package/dist/domain/javascript/javascriptApplicationChangeGraph.js +6 -6
- package/dist/domain/javascript/javascriptApplicationGraph.js +79 -18
- package/dist/domain/javascript/javascriptApplicationProjectionResources.js +35 -0
- package/dist/domain/javascript/javascriptApplicationVersionItems.js +28 -12
- package/dist/domain/javascript/javascriptApplicationVersionKeys.js +9 -1
- package/dist/domain/javascript/javascriptAstFingerprint.js +58 -31
- package/dist/domain/javascript/javascriptAstValues.js +32 -0
- package/dist/domain/javascript/javascriptElectronMemberWrites.js +50 -0
- package/dist/domain/javascript/javascriptModuleSemanticIr.js +15 -0
- package/dist/domain/javascript/javascriptNativeHandoff.js +6 -1
- package/dist/domain/javascript/javascriptRpcEndpoints.js +253 -0
- package/dist/domain/javascript/javascriptRuntimeLoadState.js +1 -3
- package/dist/domain/javascript/javascriptRuntimeReconciliationGraph.js +7 -2
- package/dist/domain/javascript/javascriptRuntimeStaticCandidates.js +8 -11
- package/dist/domain/javascript/javascriptSemanticAnalysis.js +190 -33
- package/dist/domain/javascript/javascriptSemanticCallableResults.js +42 -0
- package/dist/domain/javascript/javascriptSemanticCalls.js +8 -1
- package/dist/domain/javascript/javascriptSemanticDerivedAnalysis.js +15 -4
- package/dist/domain/javascript/javascriptSemanticFileProjection.js +1 -0
- package/dist/domain/javascript/javascriptSemanticFingerprints.js +42 -41
- package/dist/domain/javascript/javascriptSemanticGraph.js +69 -23
- package/dist/domain/javascript/javascriptSemanticGraphSchemas.js +2 -10
- package/dist/domain/javascript/javascriptSemanticIterationSources.js +40 -7
- package/dist/domain/javascript/javascriptSemanticMemberMutations.js +341 -87
- package/dist/domain/javascript/javascriptSemanticMutationValues.js +78 -35
- package/dist/domain/javascript/javascriptSemanticObjectReferences.js +381 -0
- package/dist/domain/javascript/javascriptSemanticObjects.js +7 -1
- package/dist/domain/javascript/javascriptSemanticProjection.js +52 -14
- package/dist/domain/javascript/javascriptSemanticPropertyPaths.js +77 -0
- package/dist/domain/javascript/javascriptSemanticQuery.js +27 -29
- package/dist/domain/javascript/javascriptSemanticQueryIdentity.js +1 -3
- package/dist/domain/javascript/javascriptSemanticReturnedReferences.js +219 -0
- package/dist/domain/javascript/javascriptSemanticReturns.js +13 -23
- package/dist/domain/javascript/javascriptSemanticState.js +22 -2
- package/dist/domain/javascript/javascriptSemanticTraversal.js +19 -1
- package/dist/domain/javascript/javascriptSemanticValues.js +47 -19
- package/dist/domain/javascript/javascriptSourceAnalysis.js +1 -0
- package/dist/domain/javascript/javascriptStaticAnalysis.js +29 -17
- package/dist/domain/javascript/javascriptStaticAnalysisBundler.js +8 -6
- package/dist/domain/javascript/javascriptStaticAnalysisCalls.js +49 -5
- package/dist/domain/javascript/javascriptStaticAnalysisFindings.js +8 -4
- package/dist/domain/javascript/javascriptStaticAnalysisState.js +1 -0
- package/dist/domain/jeb/jebAnalysis.js +114 -0
- package/dist/domain/jsonShape.js +63 -52
- package/dist/domain/jsonValue.js +3 -0
- package/dist/domain/managed/managedApplicationGraph.js +37 -0
- package/dist/domain/managed/managedApplicationGraphCoverage.js +8 -2
- package/dist/domain/managed/managedApplicationGraphNodes.js +32 -22
- package/dist/domain/managed/managedArtifact.js +1 -0
- package/dist/domain/managed/managedInspectionEvidence.js +13 -0
- package/dist/domain/native/nativeAotPe.js +1408 -0
- package/dist/domain/native/nativeInstruction.js +21 -0
- package/dist/domain/native/nativeMetadataRecovery.js +79 -6
- package/dist/domain/native/peResources.js +94 -0
- package/dist/domain/nativeConvention.js +33 -0
- package/dist/domain/process/processCapture.js +91 -1
- package/dist/domain/process/processCaptureExample.js +24 -0
- package/dist/domain/process/processCaptureValidation.js +79 -0
- package/dist/domain/process/processScenario.js +59 -7
- package/dist/domain/propertyListKeys.js +3 -1
- package/dist/domain/referenceSourceGraph.js +25 -16
- package/dist/domain/referenceSourceImportParsing.js +6 -3
- package/dist/domain/referenceSourcePathIdentity.js +22 -0
- package/dist/domain/referenceSourceRequireBindings.js +137 -0
- package/dist/domain/webBundleAnalyzerInspection.js +14 -2
- package/dist/domain/webCaptureDiff.js +102 -4
- package/dist/domain/webScriptExportError.js +10 -4
- package/dist/domain/webScriptExportPlan.js +7 -13
- package/dist/domain/zipPackageFormat.js +4 -1
- package/dist/domain/zodCollectionSteps.js +24 -0
- package/dist/dotnet/ManagedArtifactInspector.js +55 -43
- package/dist/dotnet/ManagedDecodeBudget.js +80 -0
- package/dist/dotnet/ManagedMemberInspector.js +13 -6
- package/dist/dotnet/ManagedMemberInspectorCore.js +2 -2
- package/dist/dotnet/ManagedMemberInstructionDecoder.js +4 -2
- package/dist/dotnet/ManagedMemberRows.js +9 -4
- package/dist/dotnet/ManagedMetadataHeaps.js +15 -5
- package/dist/dotnet/ManagedMetadataInventoryRows.js +20 -11
- package/dist/dotnet/ManagedMetadataLayout.js +10 -4
- package/dist/dotnet/ManagedMethodBodyReader.js +8 -3
- package/dist/dotnet/ManagedNativeBoundaryHelpers.js +3 -2
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +49 -34
- package/dist/dotnet/ManagedPeReader.js +41 -25
- package/dist/dotnet/ManagedReaderFailure.js +2 -1
- package/dist/dotnet/ManagedStaticProvider.js +55 -24
- package/dist/evaluation/AgentEvaluationReport.js +9 -5
- package/dist/evaluation/CodexAgentEval.js +43 -13
- package/dist/evm/EvmoleFailures.js +0 -2
- package/dist/evm/EvmoleInterfaceProvider.js +67 -2
- package/dist/filesystem/OwnedDirectoryHandle.js +29 -0
- package/dist/filesystem/OwnedFileHandle.js +36 -0
- package/dist/filesystem/RegularFile.js +33 -1
- package/dist/filesystem/StableFileHash.js +46 -0
- package/dist/filesystem/readFileHandleChunks.js +26 -0
- package/dist/firmware/FirmwareCommand.js +25 -3
- package/dist/firmware/FirmwareFiles.js +23 -8
- package/dist/firmware/FirmwareProvider.js +156 -32
- package/dist/firmware/FirmwarePublication.js +24 -3
- package/dist/firmware/FirmwareRelease.js +1 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraAnalysisProfile.js +55 -4
- package/dist/ghidra/GhidraAnalysisSeeds.js +211 -0
- package/dist/ghidra/GhidraClient.js +76 -4
- package/dist/ghidra/GhidraDiagnostics.js +11 -8
- package/dist/ghidra/GhidraFunctionValues.js +26 -2
- package/dist/ghidra/GhidraInstallation.js +1 -1
- package/dist/ghidra/GhidraInventoryValues.js +23 -1
- package/dist/ghidra/GhidraLanguageCatalog.js +99 -0
- package/dist/ghidra/GhidraLauncher.js +80 -39
- package/dist/ghidra/GhidraLoadImageAttest.js +49 -2
- package/dist/ghidra/GhidraMipsProfile.js +79 -0
- package/dist/ghidra/GhidraProvider.js +57 -9
- package/dist/ghidra/GhidraProviderCapabilities.js +18 -8
- package/dist/ghidra/GhidraProviderClient.js +282 -21
- package/dist/ghidra/GhidraSessionValues.js +9 -2
- package/dist/hopper/HopperAnalysisProfile.js +2 -0
- package/dist/hopper/HopperClient.js +15 -6
- package/dist/hopper/HopperMachOImage.js +2 -0
- package/dist/hopper/HopperProvider.js +13 -2
- package/dist/ida/IdaOperationRunner.js +8 -2
- package/dist/ida/IdaProvider.js +7 -0
- package/dist/ida/IdaSessionClient.js +3 -1
- package/dist/ida/IdaWorkspace.js +4 -2
- package/dist/javascript/analysis/JavaScriptAnalysisTransfer.js +326 -0
- package/dist/javascript/analysis/JavaScriptAnalysisWorker.js +520 -0
- package/dist/javascript/analysis/JavaScriptAnalysisWorkerProtocol.js +63 -0
- package/dist/javascript/analysis/JavaScriptAnalysisWorkerSchemas.js +521 -0
- package/dist/javascript/analysis/JavaScriptAnalysisWorkerServer.js +116 -0
- package/dist/javascript/recovery/RecoveryFiles.js +18 -7
- package/dist/javascript/recovery/WakaruCommand.js +22 -9
- package/dist/javascript/recovery/WakaruExecution.js +11 -7
- package/dist/javascript/recovery/WakaruProvider.js +139 -16
- package/dist/javascript/recovery/WakaruPublication.js +14 -4
- package/dist/javascript/recovery/WakaruRelease.js +10 -3
- package/dist/javascript/recovery/WakaruReport.js +12 -7
- package/dist/javascript/recovery/WakaruVersion.js +24 -0
- package/dist/javascript/sourceMaps/SourceMapCommand.js +16 -3
- package/dist/javascript/sourceMaps/SourceMapDecoder.js +63 -7
- package/dist/javascript/sourceMaps/SourceMapFormat.js +15 -4
- package/dist/jeb/JebMcpConnection.js +179 -0
- package/dist/jeb/JebMcpEndpoint.js +40 -0
- package/dist/jeb/JebProvider.js +299 -0
- package/dist/mcpDoctor.js +15 -6
- package/dist/native/AppleDispatchCoverage.js +25 -12
- package/dist/native/AppleDispatchMetadata.js +14 -7
- package/dist/native/AppleMachoFixups.js +71 -15
- package/dist/native/AppleMachoSelection.js +23 -3
- package/dist/native/NativeMacOSProvider.js +91 -16
- package/dist/native/NativeMachoInspection.js +11 -7
- package/dist/native/NativeUiHelperRuntime.js +6 -1
- package/dist/native/NativeUiObservation.js +4 -3
- package/dist/native/pe/PeResourceLayout.js +120 -0
- package/dist/native/pe/PeResourceParser.js +286 -0
- package/dist/native/pe/PeResourcesProvider.js +63 -0
- package/dist/process/BoundedFileBytes.js +30 -7
- package/dist/process/DarwinProcessRunTokenReader.js +8 -3
- package/dist/process/OwnedCommand.js +4 -3
- package/dist/process/ProcessOwnership.js +104 -112
- package/dist/process/ProcessOwnershipObservation.js +96 -39
- package/dist/process/ProcessOwnershipProcessTree.js +4 -4
- package/dist/process/ProcessRunTokenObservations.js +48 -0
- package/dist/process/ProviderDiagnosticRedaction.js +17 -0
- package/dist/process/ProviderProcess.js +4 -3
- package/dist/process/capture/FilesystemSnapshot.js +28 -4
- package/dist/process/capture/ProcessCaptureLifecycle.js +350 -59
- package/dist/process/capture/ProcessCaptureProgress.js +62 -0
- package/dist/process/capture/ProcessHarness.js +194 -26
- package/dist/process/capture/ProcessNormalization.js +3 -1
- package/dist/process/capture/ProcessScenarioRuntimeValidation.js +5 -2
- package/dist/process/capture/TerminalRenderer.js +24 -7
- package/dist/reference/ReferenceSourceReader.js +22 -5
- package/dist/reference/ReferenceSourceReaderEntries.js +114 -51
- package/dist/reference/ReferenceSourceReaderErrors.js +12 -0
- package/dist/reference/ReferenceSourceReaderFile.js +50 -10
- package/dist/reference/ReferenceSourceReaderValidate.js +4 -4
- package/dist/server/EvidenceMcpServer.js +36 -1
- package/dist/server/admittedToolOperation.js +9 -0
- package/dist/server/contractToolHandler.js +21 -0
- package/dist/server/createServer.js +97 -25
- package/dist/server/registerAdbTools.js +36 -0
- package/dist/server/registerAnalysisViewTool.js +4 -9
- package/dist/server/registerAndroidTools.js +3 -12
- package/dist/server/registerApktoolTools.js +14 -0
- package/dist/server/registerApplicationTools/compareExportShapes.js +4 -6
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +4 -5
- package/dist/server/registerApplicationTools/compareVersions.js +3 -2
- package/dist/server/registerApplicationTools/coverage.js +3 -2
- package/dist/server/registerApplicationTools/obligationLedger.js +3 -2
- package/dist/server/registerApplicationTools/projectMobileApplicationGraph.js +3 -2
- package/dist/server/registerApplicationTools/traceFeature.js +3 -2
- package/dist/server/registerApplicationTools/traceSemantics.js +4 -5
- package/dist/server/registerArtifactComparisonTool.js +5 -2
- package/dist/server/registerBinaryDiagnosticsTools.js +16 -4
- package/dist/server/registerBrowserScenarioTool.js +10 -7
- package/dist/server/registerBrowserTools.js +12 -9
- package/dist/server/registerBundleComparisonTool.js +16 -2
- package/dist/server/registerCloseLifecycleTool.js +32 -3
- package/dist/server/registerElectronTools.js +28 -10
- package/dist/server/registerEvmTools.js +4 -9
- package/dist/server/registerFirmwareTools.js +3 -11
- package/dist/server/registerFunctionComparisonTool.js +5 -2
- package/dist/server/registerJavaScriptRecoveryTool.js +4 -9
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +9 -6
- package/dist/server/registerJebTools.js +16 -0
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +4 -6
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +3 -2
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +3 -2
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +3 -2
- package/dist/server/registerPeResourcesTool.js +9 -0
- package/dist/server/registerProcessComparisonTool.js +5 -2
- package/dist/server/registerRecordedCrashTools.js +4 -9
- package/dist/server/registerSessionRecordTools.js +2 -1
- package/dist/server/registerSessionTools.js +49 -18
- package/dist/server/registerWebModuleTool.js +4 -9
- package/dist/server/registerWebNetworkCaptureTool.js +4 -9
- package/dist/server/registerWebRuntimeTools.js +6 -5
- package/dist/server/registerWebScriptTool.js +6 -3
- package/dist/server/registerWebSourceLocationTool.js +4 -9
- package/dist/server/toolResult.js +21 -2
- package/dist/windows/WindowsPrivateRuntime.js +12 -5
- package/install.sh +7 -4
- package/licenses/nativeaot-parser-MIT.txt +28 -0
- package/native/windows/build/manifest.json +2 -2
- package/native/windows/build/rea-windows-x64.node +0 -0
- package/package.json +33 -5
- package/scripts/rea.mjs +16 -1
- package/skills/reverse-engineer-anything/SKILL.md +52 -51
- package/skills/reverse-engineer-anything/references/connection-and-recovery.md +127 -0
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +8 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +92 -13
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +22 -1
|
@@ -27,6 +27,7 @@ import java.util.Comparator;
|
|
|
27
27
|
import java.util.HashSet;
|
|
28
28
|
import java.util.IdentityHashMap;
|
|
29
29
|
import java.util.Iterator;
|
|
30
|
+
import java.util.LinkedHashMap;
|
|
30
31
|
import java.util.List;
|
|
31
32
|
import java.util.Locale;
|
|
32
33
|
import java.util.Map;
|
|
@@ -80,12 +81,14 @@ import ghidra.program.database.mem.FileBytes;
|
|
|
80
81
|
import ghidra.program.model.address.SegmentedAddress;
|
|
81
82
|
import ghidra.program.model.reloc.Relocation;
|
|
82
83
|
import java.io.ByteArrayOutputStream;
|
|
84
|
+
import ghidra.program.model.symbol.Namespace;
|
|
83
85
|
import ghidra.program.model.symbol.RefType;
|
|
84
86
|
import ghidra.program.model.symbol.Reference;
|
|
85
87
|
import ghidra.program.model.symbol.ReferenceIterator;
|
|
86
88
|
import ghidra.program.model.symbol.SourceType;
|
|
87
89
|
import ghidra.program.model.symbol.Symbol;
|
|
88
90
|
import ghidra.program.model.symbol.SymbolIterator;
|
|
91
|
+
import ghidra.program.model.symbol.SymbolTable;
|
|
89
92
|
import ghidra.program.model.pcode.FunctionPrototype;
|
|
90
93
|
import ghidra.program.model.pcode.HighFunction;
|
|
91
94
|
import ghidra.program.model.pcode.HighParam;
|
|
@@ -120,6 +123,8 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
120
123
|
);
|
|
121
124
|
private static final String[] CAPABILITIES = {
|
|
122
125
|
"annotate_native_function",
|
|
126
|
+
"set_address_name",
|
|
127
|
+
"set_addresses_names",
|
|
123
128
|
"inspect_native_load_image",
|
|
124
129
|
"read_bytes",
|
|
125
130
|
"address_to_file_offset",
|
|
@@ -373,7 +378,16 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
373
378
|
case "inspect_native_instruction" -> inspectNativeInstruction(request.params);
|
|
374
379
|
case "resolve_native_call_targets" -> resolveNativeCallTargets(request.params);
|
|
375
380
|
case "xrefs" -> xrefs(request.params);
|
|
376
|
-
case "analyze_function" ->
|
|
381
|
+
case "analyze_function" -> {
|
|
382
|
+
FunctionAnalysisObservation observation = analyzeFunctionObservation(request.params);
|
|
383
|
+
yield functionObservation(observation.dossier(), observation.limitations());
|
|
384
|
+
}
|
|
385
|
+
case "set_address_name", "set_addresses_names" -> {
|
|
386
|
+
if (!descriptor.transport.equals("unix-socket"))
|
|
387
|
+
throw new RequestFailure("method_unavailable", "Windows P0 does not admit database mutation");
|
|
388
|
+
yield request.method.equals("set_address_name")
|
|
389
|
+
? setAddressName(request.params) : setAddressesNames(request.params);
|
|
390
|
+
}
|
|
377
391
|
case "annotate_native_function" -> {
|
|
378
392
|
if (!descriptor.transport.equals("unix-socket"))
|
|
379
393
|
throw new RequestFailure("method_unavailable", "Windows P0 does not admit database mutation");
|
|
@@ -417,12 +431,18 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
417
431
|
result.addProperty("analysis_timed_out", timedOut);
|
|
418
432
|
result.add("capabilities", GSON.toJsonTree(
|
|
419
433
|
java.util.Arrays.stream(CAPABILITIES)
|
|
420
|
-
.filter(value -> !readOnly || !value.equals("annotate_native_function")
|
|
434
|
+
.filter(value -> !readOnly || !(value.equals("annotate_native_function")
|
|
435
|
+
|| value.equals("set_address_name") || value.equals("set_addresses_names")))
|
|
421
436
|
.toArray(String[]::new)
|
|
422
437
|
));
|
|
423
438
|
result.add("target", target);
|
|
424
439
|
JsonArray extensions = analysisExtensions.reports();
|
|
425
440
|
if (extensions.size() > 0) result.add("analysis_extensions", extensions);
|
|
441
|
+
// Written by ReaGhidraApplySeeds before auto-analysis; REA checks it
|
|
442
|
+
// against the committed profile.
|
|
443
|
+
String seeds = currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
|
|
444
|
+
.getString("REA analysis seeds", null);
|
|
445
|
+
if (seeds != null) result.add("analysis_seeds", JsonParser.parseString(seeds));
|
|
426
446
|
return result;
|
|
427
447
|
}
|
|
428
448
|
|
|
@@ -641,10 +661,14 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
641
661
|
private JsonElement procedurePseudocode(JsonObject params) throws Exception {
|
|
642
662
|
requireKeys(params, Set.of("document", "procedure"));
|
|
643
663
|
requireDocument(params);
|
|
644
|
-
|
|
645
|
-
|
|
664
|
+
Function function = resolveProcedure(requireString(params, "procedure"));
|
|
665
|
+
String value = decompiledText(decompile(function));
|
|
666
|
+
return functionObservation(
|
|
667
|
+
value == null ? JsonNull.INSTANCE : GSON.toJsonTree(value),
|
|
668
|
+
noReturnCallLimitations(
|
|
669
|
+
currentProgram.getListing().getInstructions(function.getBody(), true)
|
|
670
|
+
)
|
|
646
671
|
);
|
|
647
|
-
return value == null ? JsonNull.INSTANCE : GSON.toJsonTree(value);
|
|
648
672
|
}
|
|
649
673
|
|
|
650
674
|
private JsonElement procedureAssembly(JsonObject params) throws Exception {
|
|
@@ -652,7 +676,10 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
652
676
|
requireDocument(params);
|
|
653
677
|
Function function = resolveProcedure(requireString(params, "procedure"));
|
|
654
678
|
InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
|
|
655
|
-
return
|
|
679
|
+
return functionObservation(
|
|
680
|
+
GSON.toJsonTree(renderAssembly(scan.instructions)),
|
|
681
|
+
noReturnCallLimitations(scan.instructions)
|
|
682
|
+
);
|
|
656
683
|
}
|
|
657
684
|
|
|
658
685
|
private JsonObject readFunctionInstructions(JsonObject params) throws Exception {
|
|
@@ -670,11 +697,13 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
670
697
|
limitations.add(
|
|
671
698
|
"The fast path does not invoke the decompiler or scan whole-program names and strings."
|
|
672
699
|
);
|
|
700
|
+
List<String> boundaryLimitations = noReturnCallLimitations(scan.instructions);
|
|
701
|
+
for (String limitation : boundaryLimitations) limitations.add(limitation);
|
|
673
702
|
JsonObject result = new JsonObject();
|
|
674
703
|
result.add("procedure", procedureIdentity(function));
|
|
675
704
|
result.add("instructions", items);
|
|
676
705
|
result.add("limitations", limitations);
|
|
677
|
-
return result;
|
|
706
|
+
return functionObservation(result, boundaryLimitations);
|
|
678
707
|
}
|
|
679
708
|
|
|
680
709
|
private JsonArray procedureCalls(JsonObject params, boolean callers) throws Exception {
|
|
@@ -707,7 +736,12 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
707
736
|
result.addProperty("signature", function.getPrototypeString(false, true));
|
|
708
737
|
result.add("locals", functionLocals(function));
|
|
709
738
|
result.add("classification", functionClassification(function));
|
|
710
|
-
return
|
|
739
|
+
return functionObservation(
|
|
740
|
+
result,
|
|
741
|
+
noReturnCallLimitations(
|
|
742
|
+
currentProgram.getListing().getInstructions(function.getBody(), true)
|
|
743
|
+
)
|
|
744
|
+
);
|
|
711
745
|
}
|
|
712
746
|
|
|
713
747
|
private JsonObject procedureReferences(JsonObject params) throws Exception {
|
|
@@ -841,14 +875,13 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
841
875
|
|
|
842
876
|
private JsonObject instructionReference(Reference reference) {
|
|
843
877
|
JsonObject item = new JsonObject();
|
|
844
|
-
|
|
878
|
+
JsonObject kind = referenceKind(reference);
|
|
879
|
+
item.addProperty("source_address", canonicalAddress(reference.getFromAddress()));
|
|
845
880
|
item.addProperty("target_address", canonicalAddress(reference.getToAddress()));
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
item.addProperty("
|
|
850
|
-
item.addProperty("computed", type.isComputed());
|
|
851
|
-
item.addProperty("operand_index", reference.getOperandIndex());
|
|
881
|
+
for (String field : List.of("type", "call", "jump", "indirect", "computed",
|
|
882
|
+
"operand_index", "data", "read", "write", "primary", "provenance"))
|
|
883
|
+
item.add(field, kind.get(field));
|
|
884
|
+
item.addProperty("source", normalizedSource(reference.getSource()));
|
|
852
885
|
return item;
|
|
853
886
|
}
|
|
854
887
|
|
|
@@ -1028,11 +1061,11 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1028
1061
|
for (String field : List.of("name", "comment", "inline_comment")) {
|
|
1029
1062
|
if (params.has(field)) validateAnnotationText(requireText(params, field), field);
|
|
1030
1063
|
}
|
|
1031
|
-
String leafName = params.has("name")
|
|
1032
|
-
? annotationLeafName(function, requireString(params, "name")) : null;
|
|
1033
1064
|
int transaction = currentProgram.startTransaction("REA function annotations");
|
|
1034
1065
|
boolean commit = false;
|
|
1035
1066
|
try {
|
|
1067
|
+
String leafName = params.has("name")
|
|
1068
|
+
? symbolLeafName(function.getSymbol(), requireString(params, "name")) : null;
|
|
1036
1069
|
// Validate edits through Ghidra's own setters, including name rules.
|
|
1037
1070
|
// A later invalid name must roll back earlier comment writes.
|
|
1038
1071
|
for (String field : List.of("comment", "inline_comment")) {
|
|
@@ -1081,18 +1114,18 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1081
1114
|
}
|
|
1082
1115
|
}
|
|
1083
1116
|
|
|
1084
|
-
private String
|
|
1117
|
+
private String symbolLeafName(Symbol symbol, String requested) throws ghidra.util.exception.InvalidInputException {
|
|
1085
1118
|
// Fully qualified readback must be usable as an idempotent rename input.
|
|
1086
|
-
if (requested.equals(
|
|
1087
|
-
String namespace =
|
|
1088
|
-
? "" :
|
|
1119
|
+
if (requested.equals(symbol.getName(true))) return symbol.getName();
|
|
1120
|
+
String namespace = symbol.getParentNamespace().isGlobal()
|
|
1121
|
+
? "" : symbol.getParentNamespace().getName(true);
|
|
1089
1122
|
String prefix = namespace + "::";
|
|
1090
1123
|
if (!namespace.isEmpty() && requested.startsWith(prefix)) {
|
|
1091
1124
|
String leaf = requested.substring(prefix.length());
|
|
1092
1125
|
if (!leaf.isEmpty()) return leaf;
|
|
1093
|
-
throw new
|
|
1094
|
-
"
|
|
1095
|
-
canonicalAddress(
|
|
1126
|
+
throw new ghidra.util.exception.InvalidInputException(
|
|
1127
|
+
"Symbol name has an empty leaf within namespace " + namespace + " at " +
|
|
1128
|
+
canonicalAddress(symbol.getAddress()) + ": " + requested);
|
|
1096
1129
|
}
|
|
1097
1130
|
return requested;
|
|
1098
1131
|
}
|
|
@@ -1120,6 +1153,10 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1120
1153
|
}
|
|
1121
1154
|
|
|
1122
1155
|
private JsonObject analyzeFunction(JsonObject params) throws Exception {
|
|
1156
|
+
return analyzeFunctionObservation(params).dossier();
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
private FunctionAnalysisObservation analyzeFunctionObservation(JsonObject params) throws Exception {
|
|
1123
1160
|
requireKeys(params, Set.of("procedure"));
|
|
1124
1161
|
Function function = resolveProcedure(requireString(params, "procedure"));
|
|
1125
1162
|
InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
|
|
@@ -1177,12 +1214,16 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1177
1214
|
limitations.add(
|
|
1178
1215
|
"Synthetic Ghidra entry-point references without actionable memory sources are omitted."
|
|
1179
1216
|
);
|
|
1217
|
+
List<String> boundaryLimitations = noReturnCallLimitations(scan.instructions);
|
|
1218
|
+
for (String limitation : boundaryLimitations) {
|
|
1219
|
+
limitations.add(limitation);
|
|
1220
|
+
}
|
|
1180
1221
|
if (decompilation != null && decompilation.getErrorMessage() != null &&
|
|
1181
1222
|
!decompilation.getErrorMessage().isBlank()) {
|
|
1182
1223
|
limitations.add("Ghidra decompiler diagnostic: " + decompilation.getErrorMessage());
|
|
1183
1224
|
}
|
|
1184
1225
|
result.add("limitations", limitations);
|
|
1185
|
-
return result;
|
|
1226
|
+
return new FunctionAnalysisObservation(result, boundaryLimitations);
|
|
1186
1227
|
}
|
|
1187
1228
|
|
|
1188
1229
|
private JsonObject nativeValueFlow(DecompileResults decompilation) throws Exception {
|
|
@@ -1417,6 +1458,89 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1417
1458
|
return symbol == null ? JsonNull.INSTANCE : GSON.toJsonTree(symbol.getName(true));
|
|
1418
1459
|
}
|
|
1419
1460
|
|
|
1461
|
+
// Analyst naming: a function entry renames the function, other addresses get a primary label.
|
|
1462
|
+
private boolean applyAddressName(Address address, String name) throws Exception {
|
|
1463
|
+
if (!currentProgram.getMemory().contains(address))
|
|
1464
|
+
throw new RequestFailure("invalid_request", "Address is not mapped: " + canonicalAddress(address));
|
|
1465
|
+
validateAnnotationText(name, "name");
|
|
1466
|
+
int transaction = currentProgram.startTransaction("REA address name");
|
|
1467
|
+
boolean commit = false;
|
|
1468
|
+
try {
|
|
1469
|
+
Function function = currentProgram.getFunctionManager().getFunctionAt(address);
|
|
1470
|
+
SymbolTable table = currentProgram.getSymbolTable();
|
|
1471
|
+
Symbol primary = function == null ? table.getPrimarySymbol(address) : function.getSymbol();
|
|
1472
|
+
Namespace namespace = primary == null
|
|
1473
|
+
? currentProgram.getGlobalNamespace()
|
|
1474
|
+
: primary.getParentNamespace();
|
|
1475
|
+
String expected = primary == null ? name : symbolLeafName(primary, name);
|
|
1476
|
+
for (Symbol symbol : table.getLabelOrFunctionSymbols(expected, namespace)) {
|
|
1477
|
+
if (!address.equals(symbol.getAddress())) return false;
|
|
1478
|
+
}
|
|
1479
|
+
if (function != null) {
|
|
1480
|
+
function.setName(expected, SourceType.USER_DEFINED);
|
|
1481
|
+
} else {
|
|
1482
|
+
if (primary != null && !primary.isDynamic()) {
|
|
1483
|
+
primary.setName(expected, SourceType.USER_DEFINED);
|
|
1484
|
+
} else {
|
|
1485
|
+
Symbol created = table.createLabel(address, expected, SourceType.USER_DEFINED);
|
|
1486
|
+
created.setPrimary();
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
invalidateAnalysisCaches();
|
|
1490
|
+
Symbol readback = currentProgram.getSymbolTable().getPrimarySymbol(address);
|
|
1491
|
+
commit = readback != null && expected.equals(readback.getName());
|
|
1492
|
+
return commit;
|
|
1493
|
+
}
|
|
1494
|
+
catch (ghidra.util.exception.InvalidInputException | ghidra.util.exception.DuplicateNameException exception) {
|
|
1495
|
+
return false;
|
|
1496
|
+
}
|
|
1497
|
+
finally {
|
|
1498
|
+
currentProgram.endTransaction(transaction, commit);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
private JsonElement setAddressName(JsonObject params) throws Exception {
|
|
1503
|
+
requireKeys(params, Set.of("document", "address", "name"));
|
|
1504
|
+
requireDocument(params);
|
|
1505
|
+
return GSON.toJsonTree(applyAddressName(requireAddress(params, "address"), requireString(params, "name")));
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
private JsonElement setAddressesNames(JsonObject params) throws Exception {
|
|
1509
|
+
requireKeys(params, Set.of("document", "names"));
|
|
1510
|
+
requireDocument(params);
|
|
1511
|
+
if (!params.has("names") || !params.get("names").isJsonObject())
|
|
1512
|
+
throw new RequestFailure("invalid_request", "names must be an object of address -> name");
|
|
1513
|
+
Map<Address, List<AddressNameRequest>> requestsByAddress = new LinkedHashMap<>();
|
|
1514
|
+
JsonObject result = new JsonObject();
|
|
1515
|
+
for (Map.Entry<String, JsonElement> entry : params.getAsJsonObject("names").entrySet()) {
|
|
1516
|
+
try {
|
|
1517
|
+
Address address = parseReaAddress(entry.getKey());
|
|
1518
|
+
requestsByAddress.computeIfAbsent(address, ignored -> new ArrayList<>())
|
|
1519
|
+
.add(new AddressNameRequest(entry.getKey(), address, entry.getValue().getAsString()));
|
|
1520
|
+
}
|
|
1521
|
+
catch (RequestFailure failure) {
|
|
1522
|
+
result.addProperty(entry.getKey(), false);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
for (List<AddressNameRequest> requests : requestsByAddress.values()) {
|
|
1526
|
+
String name = requests.get(0).name;
|
|
1527
|
+
boolean conflictingAliasNames = requests.stream()
|
|
1528
|
+
.anyMatch(request -> !request.name.equals(name));
|
|
1529
|
+
boolean success = false;
|
|
1530
|
+
if (!conflictingAliasNames) {
|
|
1531
|
+
try {
|
|
1532
|
+
success = applyAddressName(requests.get(0).address, name);
|
|
1533
|
+
}
|
|
1534
|
+
catch (RequestFailure failure) {
|
|
1535
|
+
// Keep per-address failures in the existing batch result shape.
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
for (AddressNameRequest request : requests)
|
|
1539
|
+
result.addProperty(request.inputAddress, success);
|
|
1540
|
+
}
|
|
1541
|
+
return result;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1420
1544
|
private JsonArray listDocuments(JsonObject params) {
|
|
1421
1545
|
requireKeys(params, Set.of());
|
|
1422
1546
|
JsonArray result = new JsonArray();
|
|
@@ -1880,9 +2004,20 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1880
2004
|
);
|
|
1881
2005
|
JumpTable.LoadTable[] loadTables = jumpTable.getLoadTables();
|
|
1882
2006
|
RecoveredAArch64RelativeTable recoveredAArch64 =
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
2007
|
+
recoverAArch64RelativeTable(jumpTable);
|
|
2008
|
+
if (recoveredAArch64 != null && loadTables.length > 0) {
|
|
2009
|
+
// A decoded table only adds evidence when Ghidra's own load table
|
|
2010
|
+
// describes the same bytes; any disagreement keeps Ghidra's view.
|
|
2011
|
+
boolean agrees = false;
|
|
2012
|
+
for (JumpTable.LoadTable loadTable : loadTables) {
|
|
2013
|
+
if (loadTable.getAddress().equals(recoveredAArch64.tableAddress) &&
|
|
2014
|
+
loadTable.getSize() == recoveredAArch64.entrySize &&
|
|
2015
|
+
loadTable.getNum() == recoveredAArch64.mappings.size()) {
|
|
2016
|
+
agrees = true;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
if (!agrees) recoveredAArch64 = null;
|
|
2020
|
+
}
|
|
1886
2021
|
List<Address> referencedDataAddresses = loadTables.length == 0
|
|
1887
2022
|
? dispatchPathDataReferences(jumpTable)
|
|
1888
2023
|
: List.of();
|
|
@@ -1916,7 +2051,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1916
2051
|
source.add("evidence", evidence);
|
|
1917
2052
|
dataSources.add(source);
|
|
1918
2053
|
}
|
|
1919
|
-
if (recoveredAArch64 != null) {
|
|
2054
|
+
if (recoveredAArch64 != null && loadTables.length == 0) {
|
|
1920
2055
|
JsonObject source = new JsonObject();
|
|
1921
2056
|
source.addProperty(
|
|
1922
2057
|
"address",
|
|
@@ -1931,9 +2066,16 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1931
2066
|
inferenceEvidence(
|
|
1932
2067
|
"jump-table",
|
|
1933
2068
|
"ghidra-instruction-and-memory",
|
|
1934
|
-
"Decoded " + recoveredAArch64.mappings.size() +
|
|
2069
|
+
"Decoded " + recoveredAArch64.mappings.size() + " " +
|
|
2070
|
+
(recoveredAArch64.signedEntries ? "signed" : "unsigned") +
|
|
1935
2071
|
" entries of size " + recoveredAArch64.entrySize + " bytes from " + canonicalAddress(recoveredAArch64.tableAddress) +
|
|
1936
|
-
"; the AArch64
|
|
2072
|
+
"; the AArch64 ADRP/ADD table, indexed load, ADR base, ADD" +
|
|
2073
|
+
(recoveredAArch64.offsetShift == 0 ? "" : " LSL #" + recoveredAArch64.offsetShift) +
|
|
2074
|
+
"/BR chain uses a selector bounded by CMP #" + recoveredAArch64.compareImmediate +
|
|
2075
|
+
" and " + recoveredAArch64.guardMnemonic + " on one straight-line path" +
|
|
2076
|
+
(recoveredAArch64.nullTarget == null
|
|
2077
|
+
? "."
|
|
2078
|
+
: "; zero entries are routed by CBZ to " + canonicalAddress(recoveredAArch64.nullTarget) + ".")
|
|
1937
2079
|
)
|
|
1938
2080
|
);
|
|
1939
2081
|
source.add("evidence", evidence);
|
|
@@ -1972,6 +2114,33 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1972
2114
|
result.add("data_sources", dataSources);
|
|
1973
2115
|
Address[] targets = jumpTable.getCases();
|
|
1974
2116
|
TypedJumpTableCases typedCases = typedJumpTableCases(jumpTable, caseTokens);
|
|
2117
|
+
// Table indices identify the register bounded by CMP. Typed labels may
|
|
2118
|
+
// instead identify a selector before normalization (for example, SUB
|
|
2119
|
+
// index, selector, #10). Do not merge those distinct value identities.
|
|
2120
|
+
if (recoveredAArch64 != null && recoveredAArch64.priorSelectorDefinition != null) {
|
|
2121
|
+
typedCases.limitations.add(
|
|
2122
|
+
"REA decoded the relative table, but the compared selector has an earlier register definition at " +
|
|
2123
|
+
canonicalAddress(recoveredAArch64.priorSelectorDefinition) + "; its table indices are not proven to use the typed switch selector's case values. Ghidra's typed labels are retained and additional case labels remain unknown."
|
|
2124
|
+
);
|
|
2125
|
+
recoveredAArch64 = null;
|
|
2126
|
+
}
|
|
2127
|
+
if (recoveredAArch64 != null) {
|
|
2128
|
+
for (Map.Entry<Long, Address> typed : typedCases.cases.entrySet()) {
|
|
2129
|
+
long value = typed.getKey();
|
|
2130
|
+
if (value < 0 || value >= recoveredAArch64.mappings.size()) {
|
|
2131
|
+
typedCases.limitations.add("Typed case labels and decoded table indices do not use a verified common selector; Ghidra's typed labels are retained.");
|
|
2132
|
+
recoveredAArch64 = null;
|
|
2133
|
+
break;
|
|
2134
|
+
}
|
|
2135
|
+
RecoveredAArch64Mapping decoded = recoveredAArch64.mappings.get((int) value);
|
|
2136
|
+
boolean divertedZero = decoded.nullEntry && typed.getValue().equals(recoveredAArch64.unreachableZeroTarget);
|
|
2137
|
+
if (!typed.getValue().equals(decoded.targetAddress) && !divertedZero) {
|
|
2138
|
+
typedCases.limitations.add("Typed case evidence and the decoded relative table disagree on a destination; Ghidra's typed labels are retained.");
|
|
2139
|
+
recoveredAArch64 = null;
|
|
2140
|
+
break;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
1975
2144
|
JsonArray mappings = new JsonArray();
|
|
1976
2145
|
JsonArray defaultTargets = new JsonArray();
|
|
1977
2146
|
for (Address target : typedCases.defaults) {
|
|
@@ -1979,27 +2148,30 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
1979
2148
|
}
|
|
1980
2149
|
Set<Address> coveredTargets = new HashSet<>(typedCases.cases.values());
|
|
1981
2150
|
coveredTargets.addAll(typedCases.defaults);
|
|
2151
|
+
if (recoveredAArch64 != null && recoveredAArch64.unreachableZeroTarget != null) {
|
|
2152
|
+
Address unreachable = recoveredAArch64.unreachableZeroTarget;
|
|
2153
|
+
coveredTargets.add(unreachable);
|
|
2154
|
+
for (RecoveredAArch64Mapping recovered : recoveredAArch64.mappings) {
|
|
2155
|
+
if (recovered.nullEntry && unreachable.equals(typedCases.cases.get((long) recovered.tableIndex))) {
|
|
2156
|
+
typedCases.cases.remove((long) recovered.tableIndex);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
typedCases.limitations.add(
|
|
2160
|
+
"Ghidra recovered destination " + canonicalAddress(unreachable) +
|
|
2161
|
+
" as the zero-offset decode of the relative table, but the CBZ before the dispatch diverts zero entries to " +
|
|
2162
|
+
canonicalAddress(recoveredAArch64.nullTarget) + ", so the indirect branch never reaches it for an in-range case."
|
|
2163
|
+
);
|
|
2164
|
+
}
|
|
1982
2165
|
if (recoveredAArch64 != null) {
|
|
1983
2166
|
for (int index = 0; index < recoveredAArch64.mappings.size(); index += 1) {
|
|
1984
2167
|
monitor.checkCancelled();
|
|
1985
2168
|
RecoveredAArch64Mapping recovered = recoveredAArch64.mappings.get(index);
|
|
1986
2169
|
if (
|
|
1987
2170
|
typedCases.unknownTargets.contains(recovered.targetAddress) ||
|
|
1988
|
-
typedCases.ambiguousValues.contains((long) recovered.
|
|
2171
|
+
typedCases.ambiguousValues.contains((long) recovered.tableIndex)
|
|
1989
2172
|
) continue;
|
|
1990
|
-
Address typedTarget = typedCases.cases.get((long) recovered.caseValue);
|
|
1991
|
-
if (typedTarget != null && !typedTarget.equals(recovered.targetAddress)) {
|
|
1992
|
-
typedCases.cases.remove((long) recovered.caseValue);
|
|
1993
|
-
typedCases.ambiguousValues.add((long) recovered.caseValue);
|
|
1994
|
-
typedCases.unknownTargets.add(typedTarget);
|
|
1995
|
-
typedCases.unknownTargets.add(recovered.targetAddress);
|
|
1996
|
-
typedCases.limitations.add(
|
|
1997
|
-
"Typed case evidence and the verified relative table disagree on a destination; both destinations remain unknown for that case."
|
|
1998
|
-
);
|
|
1999
|
-
continue;
|
|
2000
|
-
}
|
|
2001
2173
|
JsonObject mapping = new JsonObject();
|
|
2002
|
-
mapping.addProperty("case_value", recovered.
|
|
2174
|
+
mapping.addProperty("case_value", recovered.tableIndex);
|
|
2003
2175
|
String targetAddress = canonicalAddress(recovered.targetAddress);
|
|
2004
2176
|
mapping.addProperty("target_address", targetAddress);
|
|
2005
2177
|
mapping.addProperty("confidence", "high");
|
|
@@ -2008,14 +2180,18 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2008
2180
|
inferenceEvidence(
|
|
2009
2181
|
"jump-table",
|
|
2010
2182
|
"ghidra-instruction-and-memory",
|
|
2011
|
-
"Case value " + recovered.
|
|
2012
|
-
(recovered.
|
|
2013
|
-
|
|
2014
|
-
|
|
2183
|
+
"Case value " + recovered.tableIndex + (recoveredAArch64.entrySize == 1 ? " indexes byte " : " indexes halfword at byte offset ") +
|
|
2184
|
+
(recovered.tableIndex * recoveredAArch64.entrySize) + " at " + canonicalAddress(recoveredAArch64.tableAddress) +
|
|
2185
|
+
(recovered.nullEntry
|
|
2186
|
+
? "; the entry is zero, so the CBZ before the dispatch branches to " + targetAddress + "."
|
|
2187
|
+
: "; the loaded " + (recoveredAArch64.signedEntries ? "signed" : "unsigned") +
|
|
2188
|
+
" entry " + recovered.encodedEntry + " selects a " +
|
|
2189
|
+
(recoveredAArch64.offsetShift == 2 ? "four-byte instruction" : "byte") +
|
|
2190
|
+
" offset from " + canonicalAddress(recoveredAArch64.branchBase) + ", reaching " + targetAddress + ".")
|
|
2015
2191
|
)
|
|
2016
2192
|
);
|
|
2017
2193
|
mapping.add("evidence", evidence);
|
|
2018
|
-
if (!typedCases.cases.containsKey((long) recovered.
|
|
2194
|
+
if (!typedCases.cases.containsKey((long) recovered.tableIndex)) {
|
|
2019
2195
|
mappings.add(mapping);
|
|
2020
2196
|
}
|
|
2021
2197
|
coveredTargets.add(recovered.targetAddress);
|
|
@@ -2089,7 +2265,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2089
2265
|
);
|
|
2090
2266
|
}
|
|
2091
2267
|
if (recoveredAArch64 == null && loadTables.length == 0 && currentProgram.getLanguageID().getIdAsString().startsWith("AARCH64:")) {
|
|
2092
|
-
limitations.add("REA did not verify a supported byte/halfword relative dispatch chain with exact unsigned bounds and register definitions; unsupported encodings and ambiguous bounds remain unknown.");
|
|
2268
|
+
limitations.add("REA did not verify a supported signed or unsigned byte/halfword relative dispatch chain with exact unsigned bounds and register definitions; unsupported encodings and ambiguous bounds remain unknown.");
|
|
2093
2269
|
}
|
|
2094
2270
|
if (targets.length == 0) {
|
|
2095
2271
|
limitations.add(
|
|
@@ -2219,12 +2395,21 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2219
2395
|
}
|
|
2220
2396
|
|
|
2221
2397
|
private static final class RecoveredAArch64Mapping {
|
|
2222
|
-
private final int
|
|
2398
|
+
private final int tableIndex;
|
|
2223
2399
|
private final Address targetAddress;
|
|
2224
|
-
|
|
2225
|
-
private
|
|
2226
|
-
|
|
2400
|
+
private final long encodedEntry;
|
|
2401
|
+
private final boolean nullEntry;
|
|
2402
|
+
|
|
2403
|
+
private RecoveredAArch64Mapping(
|
|
2404
|
+
int tableIndex,
|
|
2405
|
+
Address targetAddress,
|
|
2406
|
+
long encodedEntry,
|
|
2407
|
+
boolean nullEntry
|
|
2408
|
+
) {
|
|
2409
|
+
this.tableIndex = tableIndex;
|
|
2227
2410
|
this.targetAddress = targetAddress;
|
|
2411
|
+
this.encodedEntry = encodedEntry;
|
|
2412
|
+
this.nullEntry = nullEntry;
|
|
2228
2413
|
}
|
|
2229
2414
|
}
|
|
2230
2415
|
|
|
@@ -2232,24 +2417,70 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2232
2417
|
private final Address tableAddress;
|
|
2233
2418
|
private final Address branchBase;
|
|
2234
2419
|
private final int entrySize;
|
|
2420
|
+
private final Address priorSelectorDefinition;
|
|
2421
|
+
private final boolean signedEntries;
|
|
2422
|
+
private final int offsetShift;
|
|
2423
|
+
private final String guardMnemonic;
|
|
2424
|
+
private final long compareImmediate;
|
|
2425
|
+
private final Address nullTarget;
|
|
2426
|
+
private final Address unreachableZeroTarget;
|
|
2235
2427
|
private final List<RecoveredAArch64Mapping> mappings;
|
|
2236
2428
|
|
|
2237
2429
|
private RecoveredAArch64RelativeTable(
|
|
2238
2430
|
Address tableAddress,
|
|
2239
2431
|
Address branchBase,
|
|
2240
2432
|
int entrySize,
|
|
2433
|
+
Address priorSelectorDefinition,
|
|
2434
|
+
boolean signedEntries,
|
|
2435
|
+
int offsetShift,
|
|
2436
|
+
String guardMnemonic,
|
|
2437
|
+
long compareImmediate,
|
|
2438
|
+
Address nullTarget,
|
|
2439
|
+
Address unreachableZeroTarget,
|
|
2241
2440
|
List<RecoveredAArch64Mapping> mappings
|
|
2242
2441
|
) {
|
|
2243
2442
|
this.tableAddress = tableAddress;
|
|
2244
2443
|
this.branchBase = branchBase;
|
|
2245
2444
|
this.entrySize = entrySize;
|
|
2445
|
+
this.priorSelectorDefinition = priorSelectorDefinition;
|
|
2446
|
+
this.signedEntries = signedEntries;
|
|
2447
|
+
this.offsetShift = offsetShift;
|
|
2448
|
+
this.guardMnemonic = guardMnemonic;
|
|
2449
|
+
this.compareImmediate = compareImmediate;
|
|
2450
|
+
this.nullTarget = nullTarget;
|
|
2451
|
+
this.unreachableZeroTarget = unreachableZeroTarget;
|
|
2246
2452
|
this.mappings = mappings;
|
|
2247
2453
|
}
|
|
2248
2454
|
}
|
|
2249
2455
|
|
|
2456
|
+
private static final Pattern AARCH64_DISPATCH_ADD = Pattern.compile(
|
|
2457
|
+
"(?i)^([wx]\\d+),\\s*([wx]\\d+),\\s*([wx]\\d+)(?:,\\s*LSL\\s*#(?:0x)?([0-9a-f]+))?$"
|
|
2458
|
+
);
|
|
2459
|
+
private static final Pattern AARCH64_TABLE_LOAD = Pattern.compile(
|
|
2460
|
+
"(?i)^([wx]\\d+),\\s*\\[([wx]\\d+),\\s*([wx]\\d+)(?:,\\s*(LSL|UXTW)\\s*(?:#(?:0x)?([0-9a-f]+))?)?\\s*\\]$"
|
|
2461
|
+
);
|
|
2462
|
+
private static final Pattern AARCH64_REGISTER_AND_ADDRESS = Pattern.compile(
|
|
2463
|
+
"(?i)^([wx]\\d+),\\s*(0x[0-9a-f]+)$"
|
|
2464
|
+
);
|
|
2465
|
+
private static final Pattern AARCH64_ADD_IMMEDIATE = Pattern.compile(
|
|
2466
|
+
"(?i)^([wx]\\d+),\\s*([wx]\\d+),\\s*#?(0x[0-9a-f]+|[0-9]+)$"
|
|
2467
|
+
);
|
|
2468
|
+
private static final Pattern AARCH64_COMPARE_IMMEDIATE = Pattern.compile(
|
|
2469
|
+
"(?i)^([wx]\\d+),\\s*#?(0x[0-9a-f]+|[0-9]+)$"
|
|
2470
|
+
);
|
|
2471
|
+
private static final Pattern AARCH64_MOVE_REGISTER = Pattern.compile(
|
|
2472
|
+
"(?i)^([wx]\\d+),\\s*([wx]\\d+)$"
|
|
2473
|
+
);
|
|
2474
|
+
private static final Set<String> AARCH64_FLAG_PRESERVING =
|
|
2475
|
+
Set.of("MOV", "ADRP", "ADR", "ADD");
|
|
2476
|
+
|
|
2250
2477
|
/**
|
|
2251
|
-
* Recover only
|
|
2252
|
-
* table
|
|
2478
|
+
* Recover only directly verified AArch64 relative branch tables:
|
|
2479
|
+
* ADRP/ADD table base, LDRB/LDRH/LDRSB/LDRSH entry load with an exact
|
|
2480
|
+
* index scale, ADR branch base, optional CBZ on the loaded entry, and
|
|
2481
|
+
* ADD (byte or LSL #2 scale)/BR, with a selector bounded by CMP #imm and an
|
|
2482
|
+
* unsigned B.HI or B.CS/B.HS guard on one straight-line path. Any other
|
|
2483
|
+
* instruction sequence keeps its case labels unknown.
|
|
2253
2484
|
*/
|
|
2254
2485
|
private RecoveredAArch64RelativeTable recoverAArch64RelativeTable(
|
|
2255
2486
|
JumpTable jumpTable
|
|
@@ -2271,167 +2502,265 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2271
2502
|
if (instruction.getAddress().equals(jumpTable.getSwitchAddress())) break;
|
|
2272
2503
|
}
|
|
2273
2504
|
int branchIndex = instructions.size() - 1;
|
|
2274
|
-
if (branchIndex < 1 ||
|
|
2275
|
-
!instructions.get(branchIndex).getMnemonicString().equalsIgnoreCase("BR")) {
|
|
2505
|
+
if (branchIndex < 1 || !mnemonic(instructions.get(branchIndex)).equals("BR")) {
|
|
2276
2506
|
return null;
|
|
2277
2507
|
}
|
|
2278
|
-
|
|
2279
|
-
Address[] recoveredTargets = jumpTable.getCases();
|
|
2280
2508
|
Set<String> recoveredTargetSet = new HashSet<>();
|
|
2281
|
-
for (Address target :
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2509
|
+
for (Address target : jumpTable.getCases()) recoveredTargetSet.add(canonicalAddress(target));
|
|
2510
|
+
if (recoveredTargetSet.isEmpty()) return null;
|
|
2511
|
+
return matchAArch64RelativeDispatch(
|
|
2512
|
+
instructions,
|
|
2513
|
+
branchIndex,
|
|
2514
|
+
recoveredTargetSet,
|
|
2515
|
+
dispatchPathDataReferences(jumpTable)
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
private RecoveredAArch64RelativeTable matchAArch64RelativeDispatch(
|
|
2520
|
+
List<Instruction> instructions,
|
|
2521
|
+
int branchIndex,
|
|
2522
|
+
Set<String> recoveredTargetSet,
|
|
2523
|
+
List<Address> dataAddresses
|
|
2524
|
+
) throws Exception {
|
|
2525
|
+
String branchRegister = normalizedRegister(operands(instructions.get(branchIndex)));
|
|
2526
|
+
|
|
2527
|
+
// ADD Xd, Xn, Xm{, LSL #s}; BR Xd. LSL applies to Xm only.
|
|
2528
|
+
Instruction dispatchAdd = instructions.get(branchIndex - 1);
|
|
2529
|
+
if (!mnemonic(dispatchAdd).equals("ADD")) return null;
|
|
2530
|
+
java.util.regex.Matcher addMatcher = AARCH64_DISPATCH_ADD.matcher(operands(dispatchAdd));
|
|
2531
|
+
if (!addMatcher.matches() || !addMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x")) return null;
|
|
2532
|
+
if (!normalizedRegister(addMatcher.group(1)).equals(branchRegister)) return null;
|
|
2533
|
+
int offsetShift = addMatcher.group(4) == null ? 0 : Integer.parseInt(addMatcher.group(4), 16);
|
|
2534
|
+
if (offsetShift != 0 && offsetShift != 2) return null;
|
|
2535
|
+
String firstSource = normalizedRegister(addMatcher.group(2));
|
|
2536
|
+
String secondSource = normalizedRegister(addMatcher.group(3));
|
|
2537
|
+
if (firstSource.equals(secondSource)) return null;
|
|
2538
|
+
|
|
2539
|
+
// The entry register is the source whose latest definition is the
|
|
2540
|
+
// table load; the other source must be defined by ADR.
|
|
2541
|
+
int loadIndex = -1;
|
|
2542
|
+
String entryRegister = null;
|
|
2543
|
+
String baseRegister = null;
|
|
2544
|
+
for (String candidate : offsetShift == 0 ? List.of(secondSource, firstSource) : List.of(secondSource)) {
|
|
2545
|
+
int writer = latestWriter(instructions, branchIndex - 1, candidate);
|
|
2546
|
+
if (writer >= 0 && Set.of("LDRB", "LDRH", "LDRSB", "LDRSH").contains(mnemonic(instructions.get(writer)))) {
|
|
2547
|
+
loadIndex = writer;
|
|
2548
|
+
entryRegister = candidate;
|
|
2549
|
+
baseRegister = candidate.equals(firstSource) ? secondSource : firstSource;
|
|
2550
|
+
break;
|
|
2320
2551
|
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2552
|
+
}
|
|
2553
|
+
if (loadIndex < 0) return null;
|
|
2554
|
+
|
|
2555
|
+
Instruction load = instructions.get(loadIndex);
|
|
2556
|
+
String loadMnemonic = mnemonic(load);
|
|
2557
|
+
int entrySize = loadMnemonic.endsWith("B") ? 1 : 2;
|
|
2558
|
+
boolean signedEntries = loadMnemonic.startsWith("LDRS");
|
|
2559
|
+
java.util.regex.Matcher loadMatcher = AARCH64_TABLE_LOAD.matcher(operands(load));
|
|
2560
|
+
if (!loadMatcher.matches()) return null;
|
|
2561
|
+
// A signed load into a W register sign-extends only to 32 bits; the
|
|
2562
|
+
// 64-bit dispatch ADD would then read a zero-extended value.
|
|
2563
|
+
if (signedEntries && !loadMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x")) return null;
|
|
2564
|
+
String tableBaseRegister = normalizedRegister(loadMatcher.group(2));
|
|
2565
|
+
String indexRegister = normalizedRegister(loadMatcher.group(3));
|
|
2566
|
+
boolean wordIndex = loadMatcher.group(3).toLowerCase(Locale.ROOT).startsWith("w");
|
|
2567
|
+
String extend = loadMatcher.group(4) == null ? null : loadMatcher.group(4).toUpperCase(Locale.ROOT);
|
|
2568
|
+
int indexShift = loadMatcher.group(5) == null ? 0 : Integer.parseInt(loadMatcher.group(5), 16);
|
|
2569
|
+
if (wordIndex ? !"UXTW".equals(extend) : "UXTW".equals(extend)) return null;
|
|
2570
|
+
if (indexShift != (entrySize == 1 ? 0 : 1)) return null;
|
|
2571
|
+
|
|
2572
|
+
// Only ADR and one CBZ on the entry may sit between load and ADD.
|
|
2573
|
+
Address nullTarget = null;
|
|
2574
|
+
for (int index = loadIndex + 1; index < branchIndex - 1; index += 1) {
|
|
2575
|
+
Instruction instruction = instructions.get(index);
|
|
2576
|
+
String name = mnemonic(instruction);
|
|
2577
|
+
if (name.equals("ADR")) continue;
|
|
2578
|
+
if (name.equals("CBZ") && nullTarget == null) {
|
|
2579
|
+
String[] parts = operands(instruction).split(",");
|
|
2580
|
+
Address[] flows = instruction.getFlows();
|
|
2581
|
+
if (parts.length != 2 || flows.length != 1 ||
|
|
2582
|
+
!normalizedRegister(parts[0]).equals(entryRegister)) return null;
|
|
2583
|
+
nullTarget = flows[0];
|
|
2584
|
+
continue;
|
|
2349
2585
|
}
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2586
|
+
return null;
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
int baseIndex = latestWriter(instructions, branchIndex - 1, baseRegister);
|
|
2590
|
+
if (baseIndex < 0 || !mnemonic(instructions.get(baseIndex)).equals("ADR")) return null;
|
|
2591
|
+
java.util.regex.Matcher baseMatcher = AARCH64_REGISTER_AND_ADDRESS.matcher(operands(instructions.get(baseIndex)));
|
|
2592
|
+
if (!baseMatcher.matches()) return null;
|
|
2593
|
+
Address branchBase = addressFromOperand(baseMatcher.group(2));
|
|
2594
|
+
|
|
2595
|
+
int tableAddIndex = latestWriter(instructions, loadIndex, tableBaseRegister);
|
|
2596
|
+
if (tableAddIndex < 0 || !mnemonic(instructions.get(tableAddIndex)).equals("ADD")) return null;
|
|
2597
|
+
java.util.regex.Matcher tableAddMatcher = AARCH64_ADD_IMMEDIATE.matcher(operands(instructions.get(tableAddIndex)));
|
|
2598
|
+
if (!tableAddMatcher.matches() ||
|
|
2599
|
+
!normalizedRegister(tableAddMatcher.group(1)).equals(tableBaseRegister) ||
|
|
2600
|
+
!normalizedRegister(tableAddMatcher.group(2)).equals(tableBaseRegister)) return null;
|
|
2601
|
+
int tablePageIndex = latestWriter(instructions, tableAddIndex, tableBaseRegister);
|
|
2602
|
+
if (tablePageIndex < 0 || !mnemonic(instructions.get(tablePageIndex)).equals("ADRP")) return null;
|
|
2603
|
+
java.util.regex.Matcher pageMatcher = AARCH64_REGISTER_AND_ADDRESS.matcher(operands(instructions.get(tablePageIndex)));
|
|
2604
|
+
if (!pageMatcher.matches() || !normalizedRegister(pageMatcher.group(1)).equals(tableBaseRegister)) return null;
|
|
2605
|
+
Address tableAddress = addressFromOperand(pageMatcher.group(2)).add(parseInteger(tableAddMatcher.group(3)));
|
|
2606
|
+
if (!currentProgram.getMemory().contains(tableAddress) ||
|
|
2607
|
+
(!dataAddresses.isEmpty() && !dataAddresses.contains(tableAddress))) return null;
|
|
2608
|
+
|
|
2609
|
+
// Unsigned guard: CMP Rs, #imm, then B.HI (imm + 1 entries) or
|
|
2610
|
+
// B.CS/B.HS (imm entries), with only flag-preserving moves and
|
|
2611
|
+
// address arithmetic between the compare and the load.
|
|
2612
|
+
int guardIndex = -1;
|
|
2613
|
+
for (int index = loadIndex - 1; index >= 0; index -= 1) {
|
|
2614
|
+
String name = mnemonic(instructions.get(index));
|
|
2615
|
+
if (name.startsWith("B.")) {
|
|
2616
|
+
guardIndex = index;
|
|
2617
|
+
break;
|
|
2618
|
+
}
|
|
2619
|
+
if (!AARCH64_FLAG_PRESERVING.contains(name)) return null;
|
|
2620
|
+
}
|
|
2621
|
+
if (guardIndex < 1) return null;
|
|
2622
|
+
Instruction guard = instructions.get(guardIndex);
|
|
2623
|
+
String guardMnemonic = mnemonic(guard);
|
|
2624
|
+
if (!Set.of("B.HI", "B.CS", "B.HS").contains(guardMnemonic)) return null;
|
|
2625
|
+
Address[] guardFlows = guard.getFlows();
|
|
2626
|
+
if (guardFlows.length != 1) return null;
|
|
2627
|
+
Address defaultTarget = guardFlows[0];
|
|
2628
|
+
int compareIndex = -1;
|
|
2629
|
+
for (int index = guardIndex - 1; index >= 0; index -= 1) {
|
|
2630
|
+
String name = mnemonic(instructions.get(index));
|
|
2631
|
+
if (name.equals("CMP")) {
|
|
2632
|
+
compareIndex = index;
|
|
2633
|
+
break;
|
|
2634
|
+
}
|
|
2635
|
+
if (!AARCH64_FLAG_PRESERVING.contains(name)) return null;
|
|
2636
|
+
}
|
|
2637
|
+
if (compareIndex < 0) return null;
|
|
2638
|
+
java.util.regex.Matcher compareMatcher = AARCH64_COMPARE_IMMEDIATE.matcher(operands(instructions.get(compareIndex)));
|
|
2639
|
+
if (!compareMatcher.matches()) return null;
|
|
2640
|
+
String comparedRegister = normalizedRegister(compareMatcher.group(1));
|
|
2641
|
+
boolean comparedFullWidth = compareMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x");
|
|
2642
|
+
long compareImmediate = parseInteger(compareMatcher.group(2));
|
|
2643
|
+
long entryCountLong = guardMnemonic.equals("B.HI") ? compareImmediate + 1 : compareImmediate;
|
|
2644
|
+
if (entryCountLong <= 0 || entryCountLong > 4096) return null;
|
|
2645
|
+
int entryCount = (int) entryCountLong;
|
|
2646
|
+
|
|
2647
|
+
// The loaded index must carry the compared value: either the compared
|
|
2648
|
+
// register itself, unchanged since CMP, or a MOV copy made after CMP.
|
|
2649
|
+
// A W compare bounds only the low 32 bits, so an X index needs a
|
|
2650
|
+
// zero-extending W copy; a W index is zero-extended by UXTW.
|
|
2651
|
+
int indexWriter = latestWriter(instructions, loadIndex, indexRegister);
|
|
2652
|
+
boolean bounded = false;
|
|
2653
|
+
if (indexWriter < compareIndex) {
|
|
2654
|
+
bounded = indexRegister.equals(comparedRegister) && (wordIndex || comparedFullWidth);
|
|
2655
|
+
}
|
|
2656
|
+
else if (mnemonic(instructions.get(indexWriter)).equals("MOV")) {
|
|
2657
|
+
java.util.regex.Matcher moveMatcher = AARCH64_MOVE_REGISTER.matcher(operands(instructions.get(indexWriter)));
|
|
2658
|
+
if (moveMatcher.matches() &&
|
|
2659
|
+
normalizedRegister(moveMatcher.group(2)).equals(comparedRegister) &&
|
|
2660
|
+
latestWriter(instructions, indexWriter, comparedRegister) < compareIndex) {
|
|
2661
|
+
boolean wordCopy = moveMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("w");
|
|
2662
|
+
bounded = wordCopy || comparedFullWidth;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
if (!bounded) return null;
|
|
2666
|
+
|
|
2667
|
+
// Every fact above holds only on one straight-line fall-through path
|
|
2668
|
+
// with no other entry into the window.
|
|
2669
|
+
int windowStart = Math.min(Math.min(tablePageIndex, baseIndex), compareIndex);
|
|
2670
|
+
for (int index = windowStart + 1; index <= branchIndex; index += 1) {
|
|
2671
|
+
monitor.checkCancelled();
|
|
2672
|
+
Instruction previous = instructions.get(index - 1);
|
|
2673
|
+
Instruction current = instructions.get(index);
|
|
2674
|
+
Address fallThrough = previous.getFallThrough();
|
|
2675
|
+
if (fallThrough == null || !fallThrough.equals(current.getAddress())) return null;
|
|
2676
|
+
ReferenceIterator incoming = currentProgram.getReferenceManager().getReferencesTo(current.getAddress());
|
|
2677
|
+
while (incoming.hasNext()) {
|
|
2678
|
+
if (incoming.next().getReferenceType().isFlow()) return null;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
List<RecoveredAArch64Mapping> mappings = new ArrayList<>();
|
|
2683
|
+
Set<String> mappedTargets = new HashSet<>();
|
|
2684
|
+
for (int tableIndex = 0; tableIndex < entryCount; tableIndex += 1) {
|
|
2685
|
+
monitor.checkCancelled();
|
|
2686
|
+
Address entry = tableAddress.add((long) tableIndex * entrySize);
|
|
2687
|
+
if (!currentProgram.getMemory().contains(entry)) return null;
|
|
2688
|
+
long encoded;
|
|
2689
|
+
if (entrySize == 1) {
|
|
2690
|
+
byte value = currentProgram.getMemory().getByte(entry);
|
|
2691
|
+
encoded = signedEntries ? value : value & 0xffL;
|
|
2692
|
+
}
|
|
2693
|
+
else {
|
|
2694
|
+
short value = currentProgram.getMemory().getShort(entry);
|
|
2695
|
+
encoded = signedEntries ? value : value & 0xffffL;
|
|
2696
|
+
}
|
|
2697
|
+
if (encoded == 0 && nullTarget != null) {
|
|
2698
|
+
mappings.add(new RecoveredAArch64Mapping(tableIndex, nullTarget, encoded, true));
|
|
2699
|
+
continue;
|
|
2700
|
+
}
|
|
2701
|
+
Address target;
|
|
2702
|
+
try {
|
|
2703
|
+
target = branchBase.addNoWrap(encoded << offsetShift);
|
|
2704
|
+
}
|
|
2705
|
+
catch (ghidra.program.model.address.AddressOverflowException overflow) {
|
|
2706
|
+
return null;
|
|
2707
|
+
}
|
|
2708
|
+
String canonicalTarget = canonicalAddress(target);
|
|
2709
|
+
if (!recoveredTargetSet.contains(canonicalTarget)) return null;
|
|
2710
|
+
mappedTargets.add(canonicalTarget);
|
|
2711
|
+
mappings.add(new RecoveredAArch64Mapping(tableIndex, target, encoded, false));
|
|
2712
|
+
}
|
|
2713
|
+
if (mappedTargets.contains(canonicalAddress(defaultTarget))) return null;
|
|
2714
|
+
// Ghidra's recovered destinations must be fully accounted for by the
|
|
2715
|
+
// decoded entries, the guard default, and the CBZ null path.
|
|
2716
|
+
Set<String> accountedTargets = new HashSet<>(mappedTargets);
|
|
2717
|
+
accountedTargets.add(canonicalAddress(defaultTarget));
|
|
2718
|
+
if (nullTarget != null) accountedTargets.add(canonicalAddress(nullTarget));
|
|
2719
|
+
// Ghidra does not model the CBZ, so it may decode a zero entry as a
|
|
2720
|
+
// branch to the base itself. Only a zero entry encodes offset 0, and
|
|
2721
|
+
// the CBZ diverts every zero entry, so the BR never reaches the base.
|
|
2722
|
+
Address unreachableZeroTarget = null;
|
|
2723
|
+
boolean divertedZeroEntry = mappings.stream().anyMatch(mapping -> mapping.nullEntry);
|
|
2724
|
+
if (divertedZeroEntry &&
|
|
2725
|
+
recoveredTargetSet.contains(canonicalAddress(branchBase)) &&
|
|
2726
|
+
!mappedTargets.contains(canonicalAddress(branchBase)) &&
|
|
2727
|
+
!branchBase.equals(defaultTarget) &&
|
|
2728
|
+
!branchBase.equals(nullTarget)) {
|
|
2729
|
+
unreachableZeroTarget = branchBase;
|
|
2730
|
+
accountedTargets.add(canonicalAddress(branchBase));
|
|
2731
|
+
}
|
|
2732
|
+
if (!accountedTargets.containsAll(recoveredTargetSet)) return null;
|
|
2733
|
+
int selectorWriter = latestWriter(instructions, compareIndex, comparedRegister);
|
|
2734
|
+
return new RecoveredAArch64RelativeTable(
|
|
2735
|
+
tableAddress,
|
|
2736
|
+
branchBase,
|
|
2737
|
+
entrySize,
|
|
2738
|
+
selectorWriter < 0 ? null : instructions.get(selectorWriter).getAddress(),
|
|
2739
|
+
signedEntries,
|
|
2740
|
+
offsetShift,
|
|
2741
|
+
guardMnemonic,
|
|
2742
|
+
compareImmediate,
|
|
2743
|
+
nullTarget,
|
|
2744
|
+
unreachableZeroTarget,
|
|
2745
|
+
List.copyOf(mappings)
|
|
2746
|
+
);
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
private static String mnemonic(Instruction instruction) {
|
|
2750
|
+
return instruction.getMnemonicString().toUpperCase(Locale.ROOT);
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
/** Index of the last instruction before {@code before} that writes {@code register}, or -1. */
|
|
2754
|
+
private static int latestWriter(List<Instruction> instructions, int before, String register) {
|
|
2755
|
+
for (int index = before - 1; index >= 0; index -= 1) {
|
|
2756
|
+
for (Object result : instructions.get(index).getResultObjects()) {
|
|
2757
|
+
if (result instanceof Register written &&
|
|
2758
|
+
normalizedRegister(written.getBaseRegister().getName()).equals(register)) {
|
|
2759
|
+
return index;
|
|
2424
2760
|
}
|
|
2425
|
-
mappedTargets.add(canonicalTarget);
|
|
2426
|
-
mappings.add(new RecoveredAArch64Mapping(caseValue, target));
|
|
2427
2761
|
}
|
|
2428
|
-
if (!valid || mappedTargets.contains(canonicalAddress(defaultTarget))) continue;
|
|
2429
|
-
Set<String> accountedTargets = new HashSet<>(mappedTargets);
|
|
2430
|
-
accountedTargets.add(canonicalAddress(defaultTarget));
|
|
2431
|
-
if (!accountedTargets.equals(recoveredTargetSet)) continue;
|
|
2432
|
-
return new RecoveredAArch64RelativeTable(tableAddress, branchBase, entrySize, List.copyOf(mappings));
|
|
2433
2762
|
}
|
|
2434
|
-
return
|
|
2763
|
+
return -1;
|
|
2435
2764
|
}
|
|
2436
2765
|
|
|
2437
2766
|
private static String operands(Instruction instruction) {
|
|
@@ -2578,6 +2907,63 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
2578
2907
|
return new InstructionScan(List.copyOf(instructions), iterator.hasNext());
|
|
2579
2908
|
}
|
|
2580
2909
|
|
|
2910
|
+
private JsonObject functionObservation(
|
|
2911
|
+
JsonElement value,
|
|
2912
|
+
List<String> limitations) {
|
|
2913
|
+
JsonObject result = new JsonObject();
|
|
2914
|
+
result.add("value", value);
|
|
2915
|
+
result.add("limitations", GSON.toJsonTree(limitations));
|
|
2916
|
+
return result;
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
private List<String> noReturnCallLimitations(Iterable<Instruction> instructions)
|
|
2920
|
+
throws Exception {
|
|
2921
|
+
List<String> limitations = new ArrayList<>();
|
|
2922
|
+
for (Instruction instruction : instructions) {
|
|
2923
|
+
monitor.checkCancelled();
|
|
2924
|
+
if (!instruction.getFlowType().isCall() || !instruction.getFlowType().isTerminal()) {
|
|
2925
|
+
continue;
|
|
2926
|
+
}
|
|
2927
|
+
Address[] destinations = instruction.getFlows();
|
|
2928
|
+
StringBuilder limitation = new StringBuilder();
|
|
2929
|
+
limitation.append("Ghidra Listing reports a terminal call at ")
|
|
2930
|
+
.append(canonicalAddress(instruction.getAddress()));
|
|
2931
|
+
if (destinations.length == 0) {
|
|
2932
|
+
limitation.append(" with no resolved callee; its no-return basis is unknown.");
|
|
2933
|
+
}
|
|
2934
|
+
else {
|
|
2935
|
+
limitation.append(" to ");
|
|
2936
|
+
for (int index = 0; index < destinations.length; index++) {
|
|
2937
|
+
if (index > 0) limitation.append(", ");
|
|
2938
|
+
Address destination = destinations[index];
|
|
2939
|
+
limitation.append(canonicalAddress(destination));
|
|
2940
|
+
Function target = currentProgram.getFunctionManager().getFunctionAt(destination);
|
|
2941
|
+
if (target == null) {
|
|
2942
|
+
limitation.append(" (callee identity unknown)");
|
|
2943
|
+
}
|
|
2944
|
+
else {
|
|
2945
|
+
limitation.append(" ").append(procedureName(target));
|
|
2946
|
+
limitation.append(" (external=").append(target.isExternal());
|
|
2947
|
+
limitation.append(", thunk=").append(target.isThunk());
|
|
2948
|
+
limitation.append(", hasNoReturn=").append(target.hasNoReturn()).append(")");
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
limitation.append("; fallthrough is excluded by Ghidra's flow model.");
|
|
2952
|
+
Function target = destinations.length == 1
|
|
2953
|
+
? currentProgram.getFunctionManager().getFunctionAt(destinations[0])
|
|
2954
|
+
: null;
|
|
2955
|
+
if (target != null && target.hasNoReturn()) {
|
|
2956
|
+
limitation.append(" This records Ghidra's FunctionManager flag, not an independent verification that the callee cannot return.");
|
|
2957
|
+
}
|
|
2958
|
+
else {
|
|
2959
|
+
limitation.append(" The reason for the terminal flow is not established by this observation.");
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
limitations.add(limitation.toString());
|
|
2963
|
+
}
|
|
2964
|
+
return limitations;
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2581
2967
|
private static String renderAssembly(List<Instruction> instructions) {
|
|
2582
2968
|
return String.join("\n", renderAssemblyLines(instructions));
|
|
2583
2969
|
}
|
|
@@ -3406,7 +3792,9 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
|
|
3406
3792
|
.thenComparing(Reference::isPrimary);
|
|
3407
3793
|
|
|
3408
3794
|
private record InventoryItem(Address address, String value, JsonObject facts) {}
|
|
3795
|
+
private record AddressNameRequest(String inputAddress, Address address, String name) {}
|
|
3409
3796
|
private record FunctionEntry(Function function, InventoryItem item) {}
|
|
3797
|
+
private record FunctionAnalysisObservation(JsonObject dossier, List<String> limitations) {}
|
|
3410
3798
|
private record InstructionScan(List<Instruction> instructions, boolean truncated) {}
|
|
3411
3799
|
private record SessionDescriptor(
|
|
3412
3800
|
String transport,
|