From 86e44bef08d36cc1b56e44b7b544d5d6d21d141d Mon Sep 17 00:00:00 2001 From: N0zoM1z0 <1705004398@qq.com> Date: Fri, 17 Jul 2026 00:35:28 +0800 Subject: [PATCH] feat(ghidra): inspect Windows headless installations Recognize the official Windows analyzeHeadless.bat launcher and java.exe/javac.exe layout while retaining the exact Ghidra 12.1.2, JDK 21, x64 admission contract. Build paths and PATH delimiters from the inspected host platform so deterministic Linux tests can exercise Windows coordinates without weakening discovery checks. --- src/ghidra/GhidraInstallation.ts | 54 ++++++++++++++++++++++++-------- tests/ghidraInstallation.test.ts | 43 +++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 13 deletions(-) diff --git a/src/ghidra/GhidraInstallation.ts b/src/ghidra/GhidraInstallation.ts index f71c465f8..80feb0bc1 100644 --- a/src/ghidra/GhidraInstallation.ts +++ b/src/ghidra/GhidraInstallation.ts @@ -1,7 +1,7 @@ import { spawnSync } from "node:child_process"; import { constants } from "node:fs"; import { accessSync, readFileSync } from "node:fs"; -import { delimiter, join } from "node:path"; +import { posix, win32 } from "node:path"; import type { ProviderRejectionCode } from "../contracts/providerSelection.js"; import type { JsonValue } from "../domain/jsonValue.js"; @@ -98,7 +98,7 @@ export const inspectGhidraInstallation = ( ): GhidraInstallationInspection => { const platform = options.platform ?? process.platform; const architecture = options.architecture ?? process.arch; - const coordinates = installationCoordinates(options, host); + const coordinates = installationCoordinates(options, platform, host); const checks = installationChecks({ coordinates, platform, @@ -126,23 +126,37 @@ export const inspectGhidraInstallation = ( const installationCoordinates = ( options: GhidraInstallationOptions, + platform: NodeJS.Platform, host: GhidraInstallationHost, ): GhidraInstallationCoordinates => { + const path = platform === "win32" ? win32 : posix; const installDir = options.installDir ?? null; const applicationPropertiesPath = installDir === null ? null - : join(installDir, "Ghidra", "application.properties"); + : path.join(installDir, "Ghidra", "application.properties"); const analyzeHeadlessPath = - installDir === null ? null : join(installDir, "support", "analyzeHeadless"); + installDir === null + ? null + : path.join( + installDir, + "support", + platform === "win32" ? "analyzeHeadless.bat" : "analyzeHeadless", + ); const properties = applicationPropertiesPath === null ? undefined : host.readText(applicationPropertiesPath); const javaCommand = options.javaHome === undefined - ? "java" - : join(options.javaHome, "bin", "java"); + ? platform === "win32" + ? "java.exe" + : "java" + : path.join( + options.javaHome, + "bin", + platform === "win32" ? "java.exe" : "java", + ); return { installDir, applicationPropertiesPath, @@ -153,7 +167,10 @@ const installationCoordinates = ( ? null : propertyValue(properties, "application.version"), javaCommand, - java: host.probeJava(javaCommand, ghidraJavaEnvironment(options.javaHome)), + java: host.probeJava( + javaCommand, + ghidraJavaEnvironment(options.javaHome, process.env, platform), + ), }; }; @@ -174,10 +191,11 @@ const installationChecks = ({ }), installationCheck({ name: "platform", - ok: platform === "linux", + ok: platform === "linux" || platform === "win32", code: "unsupported_host", detail: platform, - remediation: "REA's Ghidra v1 adapter currently supports Linux only.", + remediation: + "Use REA's Ghidra adapter on a supported Linux or Windows x64 host.", }), installationCheck({ name: "architecture", @@ -185,7 +203,7 @@ const installationChecks = ({ code: "unsupported_host", detail: architecture, remediation: - "Use the official Linux x86-64 Ghidra distribution on an x64 host.", + "Use the official x86-64 Ghidra distribution on an x64 Linux or Windows host.", }), installationCheck({ name: "installation", @@ -215,7 +233,7 @@ const installationChecks = ({ code: "executable_missing", detail: coordinates.analyzeHeadlessPath ?? "analyzeHeadless unavailable", remediation: - "Restore the executable support/analyzeHeadless script from the official Ghidra release.", + "Restore support/analyzeHeadless or support/analyzeHeadless.bat from the official Ghidra release.", }), javaCheck(coordinates.java, coordinates.javaCommand, javaHome), ]; @@ -245,6 +263,7 @@ export const ghidraInstallationDiagnostics = ( export const ghidraJavaEnvironment = ( javaHome: string | undefined, environment: NodeJS.ProcessEnv = process.env, + platform: NodeJS.Platform = process.platform, ): NodeJS.ProcessEnv => { const boundedEnvironment = { ...environment, @@ -258,7 +277,10 @@ export const ghidraJavaEnvironment = ( : { ...boundedEnvironment, JAVA_HOME: javaHome, - PATH: `${join(javaHome, "bin")}${delimiter}${environment.PATH ?? ""}`, + PATH: `${(platform === "win32" ? win32 : posix).join( + javaHome, + "bin", + )}${platform === "win32" ? win32.delimiter : posix.delimiter}${environment.PATH ?? ""}`, }; }; @@ -353,7 +375,13 @@ const systemGhidraInstallationHost = (): GhidraInstallationHost => ({ major: javaMajor(version), home, bits, - jdk: executablePath(join(home, "bin", "javac")), + jdk: executablePath( + (process.platform === "win32" ? win32 : posix).join( + home, + "bin", + process.platform === "win32" ? "javac.exe" : "javac", + ), + ), }; }, }); diff --git a/tests/ghidraInstallation.test.ts b/tests/ghidraInstallation.test.ts index 7aad65493..1deb84a70 100644 --- a/tests/ghidraInstallation.test.ts +++ b/tests/ghidraInstallation.test.ts @@ -12,6 +12,9 @@ import { const INSTALL = "/opt/ghidra"; const PROPERTIES = `${INSTALL}/Ghidra/application.properties`; const HEADLESS = `${INSTALL}/support/analyzeHeadless`; +const WINDOWS_INSTALL = "C:\\tools\\ghidra_12.1.2_PUBLIC"; +const WINDOWS_PROPERTIES = `${WINDOWS_INSTALL}\\Ghidra\\application.properties`; +const WINDOWS_HEADLESS = `${WINDOWS_INSTALL}\\support\\analyzeHeadless.bat`; const JAVA: GhidraJavaObservation = { version: "21.0.11", major: SUPPORTED_GHIDRA_JAVA_MAJOR, @@ -67,6 +70,46 @@ describe("Ghidra installation inspection", () => { }); }); + it("accepts the exact Windows x64 batch launcher and JDK commitment", () => { + const windowsHost: GhidraInstallationHost = { + platform: "win32", + architecture: "x64", + readText: (path) => + path === WINDOWS_PROPERTIES + ? `application.version=${SUPPORTED_GHIDRA_VERSION}\n` + : undefined, + executable: (path) => path === WINDOWS_HEADLESS, + probeJava: (command, environment) => { + expect(command).toBe("C:\\Java\\jdk-21\\bin\\java.exe"); + expect(environment.PATH).toMatch(/^C:\\Java\\jdk-21\\bin;/u); + return { + ...JAVA, + home: "C:\\Java\\jdk-21", + }; + }, + }; + + expect( + inspectGhidraInstallation( + { + installDir: WINDOWS_INSTALL, + javaHome: "C:\\Java\\jdk-21", + platform: "win32", + architecture: "x64", + }, + windowsHost, + ), + ).toMatchObject({ + available: true, + platform: "win32", + architecture: "x64", + analyzeHeadlessPath: WINDOWS_HEADLESS, + javaCommand: "C:\\Java\\jdk-21\\bin\\java.exe", + providerVersion: SUPPORTED_GHIDRA_VERSION, + rejectionCode: null, + }); + }); + it.each([ { name: "missing configuration",