fix(ghidra): guide packed program import recovery (#8820)

* fix(ghidra): guide packed program import recovery

* test(ghidra): verify packed import recovery facts
This commit is contained in:
morluto authored and GitHub committed 2026-11-05 01:45:22 +08:00
1 parent fdfc210879
commit 9196c30f6e
3 files changed
+64 -4

No files matched your search

+14
View File
@@ -1429,6 +1429,20 @@ in the project root; when omitted it defaults to the selected target's filename.
Paths, globs and command flags are not program names. The matching `.rep`
directory must be present; restore `.grez` archives with Ghidra first.
For a `.gzf` packed program, first import it into a new Ghidra project in an
existing, caller-selected directory. A packed program is not a `.grez` project
archive:
```bash
"$GHIDRA_INSTALL_DIR/support/analyzeHeadless" "/absolute/new-project-directory" "new-project" \
-import "/absolute/program.gzf" -noanalysis
```
Select the resulting `new-project.gpr` with `REA_GHIDRA_PROJECT_PATH`. If the
imported program name differs from the selected target's filename, set
`REA_GHIDRA_PROGRAM_NAME` to the name reported by Ghidra's import. REA then
opens its own read-only project copy; it does not import the packed program.
```bash
export REA_ANALYSIS_PROVIDER=ghidra
export REA_GHIDRA_PROJECT_PATH=/absolute/projects/app-analysis.gpr
+13 -4
View File
@@ -1,15 +1,24 @@
import { isAbsolute, basename } from "node:path";
import { z } from "zod";
/** Recovery for a packed Program selected where REA requires a project. */
export const GHIDRA_PACKED_PROGRAM_RECOVERY =
"A .gzf is a packed program, not a .gpr project. Import it into a new project with Ghidra analyzeHeadless <project-directory> <project-name> -import <file.gzf> -noanalysis; select its .gpr and, if needed, set REA_GHIDRA_PROGRAM_NAME to the imported program name.";
/** A single expanded local project marker; archives must be restored first. */
export const ghidraProjectPathSchema = z
.string()
.min(1)
.refine(isAbsolute, "Ghidra project path must be absolute")
.refine(
(path) => path.endsWith(".gpr") && basename(path).length > 4,
"Select an expanded .gpr project marker; restore .grez archives first",
);
.superRefine((path, context) => {
if (path.endsWith(".gpr") && basename(path).length > 4) return;
context.addIssue({
code: "custom",
message: path.toLowerCase().endsWith(".gzf")
? GHIDRA_PACKED_PROGRAM_RECOVERY
: "Select an expanded .gpr project marker; restore .grez archives first",
});
});
/** Headless -process accepts globs and flags; this workflow selects one root program. */
export const ghidraProgramNameSchema = z
@@ -0,0 +1,37 @@
import { join } from "node:path";
import { expect } from "vitest";
import { GHIDRA_PACKED_PROGRAM_RECOVERY } from "../../../src/config/ghidraExistingProject.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
cliTest(
"explains how to import a packed Ghidra program before selecting a project",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-packed-project-doctor-");
const result = await cli.run({
arguments: ["--json", "doctor", "--provider", "ghidra"],
environment: { REA_GHIDRA_PROJECT_PATH: join(root, "program.gzf") },
});
expect(result.exitCode).toBe(1);
expect(result.json).toMatchObject({
healthy: false,
scope_checks: expect.arrayContaining([
expect.objectContaining({
name: "runtime-configuration:REA_GHIDRA_PROJECT_PATH",
ok: false,
classification: "config_drift",
detail: `REA_GHIDRA_PROJECT_PATH: ${GHIDRA_PACKED_PROGRAM_RECOVERY}`,
}),
]),
});
for (const required of [
"analyzeHeadless",
"-import",
"-noanalysis",
".gpr",
"REA_GHIDRA_PROGRAM_NAME",
])
expect(GHIDRA_PACKED_PROGRAM_RECOVERY).toContain(required);
},
);