Keep malformed canonical devicetree phandles unresolved instead of falling back to legacy identities. Report the PE dynamic relocation offset and section at their exact source widths with Size-gated absence and zero preservation. Clarify classic kernel-module version coverage and byte-preserving string encoding. Validation: 158 focused parser, service, filesystem, and finalization regressions; 14 CLI/MCP tests; build, typecheck/lint, formatting, and docs checks passed. Fixtures establish parser behavior; no external analysis engines were exercised.
125 lines
4.2 KiB
TypeScript
125 lines
4.2 KiB
TypeScript
import { expect, it } from "vitest";
|
|
import { PeLoadConfigService } from "./PeLoadConfigService.js";
|
|
import { PE_LOAD_CONFIG_PROVIDER } from "../../domain/investigationProviders.js";
|
|
import type { PeLoadConfig } from "../../domain/native/peLoadConfig.js";
|
|
import { ok } from "../../domain/result.js";
|
|
|
|
const report: PeLoadConfig = {
|
|
artifact: { path: "/artifacts/app.exe", sha256: "a".repeat(64), bytes: 512 },
|
|
format: "pe32-plus",
|
|
machine: 0x8664,
|
|
bits: 64,
|
|
architecture: "x86_64",
|
|
tls: null,
|
|
load_config: null,
|
|
examined_callbacks: 0,
|
|
limitations: [],
|
|
};
|
|
const legacyX86: PeLoadConfig = {
|
|
...report,
|
|
artifact: { ...report.artifact, bytes: 0x400 },
|
|
format: "pe32",
|
|
machine: 0x14c,
|
|
bits: 32,
|
|
architecture: "386",
|
|
load_config: {
|
|
directory: { rva: 0x1040, size: 64, file_offset: 0x240 },
|
|
size: 188,
|
|
structure_location: { offset: 0x240, bytes: 188 },
|
|
security_cookie: null,
|
|
se_handler_table: null,
|
|
se_handler_count: 7,
|
|
guard_cf_check_function_pointer: null,
|
|
guard_cf_dispatch_function_pointer: null,
|
|
guard_cf_function_table: null,
|
|
guard_cf_function_count: null,
|
|
guard_flags: null,
|
|
guard_address_taken_iat_entry_table: null,
|
|
guard_address_taken_iat_entry_count: null,
|
|
guard_long_jump_target_table: null,
|
|
guard_long_jump_target_count: null,
|
|
dynamic_value_reloc_table: null,
|
|
chpe_metadata_pointer: null,
|
|
dynamic_value_reloc_table_offset: null,
|
|
dynamic_value_reloc_table_section: null,
|
|
guard_eh_continuation_table: null,
|
|
guard_eh_continuation_count: null,
|
|
guard_xfg_check_function_pointer: null,
|
|
inferences: [],
|
|
},
|
|
};
|
|
const service = (value: PeLoadConfig = report) =>
|
|
new PeLoadConfigService({
|
|
identity: PE_LOAD_CONFIG_PROVIDER,
|
|
inspect: () => Promise.resolve(ok(value)),
|
|
});
|
|
it("retains absent directories and artifact provenance", async () => {
|
|
const result = await service().inspect({ path: report.artifact.path });
|
|
expect(result.ok).toBe(true);
|
|
if (!result.ok) throw result.error;
|
|
expect(result.value).toMatchObject({
|
|
confidence: "observed",
|
|
subject: { format: "pe", architecture: "x86_64" },
|
|
normalized_result: report,
|
|
});
|
|
});
|
|
it("locates both the declared directory and the checked Size span", async () => {
|
|
const result = await service(legacyX86).inspect({
|
|
path: report.artifact.path,
|
|
});
|
|
if (!result.ok) throw result.error;
|
|
expect(result.value.locations).toEqual([
|
|
{ kind: "artifact-path", path: report.artifact.path },
|
|
{ kind: "file-offset-range", start: 0x240, end: 0x240 + 64 },
|
|
{ kind: "file-offset-range", start: 0x240, end: 0x240 + 188 },
|
|
]);
|
|
});
|
|
it("rejects input and cancellation before reading", async () => {
|
|
const boundary = new PeLoadConfigService({
|
|
identity: PE_LOAD_CONFIG_PROVIDER,
|
|
inspect: () => {
|
|
throw new Error("Unexpected read");
|
|
},
|
|
});
|
|
for (const input of [
|
|
{ path: "relative.exe" },
|
|
{ path: report.artifact.path, extra: true },
|
|
{ path: report.artifact.path, max_callbacks: 4097 },
|
|
]) {
|
|
const result = await boundary.inspect(input);
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) expect(result.error._tag).toBe("AnalysisInputError");
|
|
}
|
|
const result = await boundary.inspect(
|
|
{ path: report.artifact.path },
|
|
{ signal: AbortSignal.abort() },
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) expect(result.error._tag).toBe("AnalysisCancelledError");
|
|
});
|
|
it.each([
|
|
{ ...report, examined_callbacks: 1 },
|
|
{ ...report, bits: 32 as const },
|
|
{ ...report, artifact: { ...report.artifact, path: "/another.exe" } },
|
|
{ ...report, artifact: { ...report.artifact, bytes: 67108865 } },
|
|
...[
|
|
{ offset: 0x241, bytes: 188 },
|
|
{ offset: 0x240, bytes: 187 },
|
|
].map((structure_location) => ({
|
|
...legacyX86,
|
|
load_config: { ...legacyX86.load_config!, structure_location },
|
|
})),
|
|
{
|
|
...legacyX86,
|
|
load_config: {
|
|
...legacyX86.load_config!,
|
|
size: 0x400,
|
|
structure_location: { offset: 0x240, bytes: 0x400 },
|
|
},
|
|
},
|
|
])("rejects inconsistent provider output", async (value) => {
|
|
const result = await service(value).inspect({ path: report.artifact.path });
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) expect(result.error._tag).toBe("AnalysisOutputError");
|
|
});
|