mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
171 lines
5.4 KiB
JavaScript
171 lines
5.4 KiB
JavaScript
import assert from "node:assert/strict";
|
|
|
|
/** Verify actual reference facts on the source-owned Windows PE fixture. */
|
|
export async function verifyGhidraCompactReferences(call, x86, cli) {
|
|
const base = x86 ? 0x400000n : 0x1_4000_0000n;
|
|
const address = (offset) => `0x${(base + BigInt(offset)).toString(16)}`;
|
|
const cases = [
|
|
["data", 0x1020, 0x2000],
|
|
["read", x86 ? 0x1025 : 0x1027, 0x2000],
|
|
["write", x86 ? 0x102a : 0x102d, 0x2004],
|
|
];
|
|
const full = await call("procedure_references", {
|
|
procedure: address(0x1020),
|
|
});
|
|
const inspected = {};
|
|
for (const [kind, offset, target] of cases) {
|
|
const instruction = await call("inspect_native_instruction", {
|
|
address: address(offset),
|
|
});
|
|
inspected[kind] = instruction;
|
|
const reference = instruction.references.find(
|
|
(item) => item.target_address === address(target),
|
|
);
|
|
assert.ok(reference, `Missing ${kind} fixture reference`);
|
|
assertReference(reference, instruction.address, full.references);
|
|
assert.equal(reference.data, true);
|
|
assert.equal(reference.read, kind === "read");
|
|
assert.equal(reference.write, kind === "write");
|
|
}
|
|
const indexed = await call("inspect_native_instruction", {
|
|
address: address(x86 ? 0x103d : 0x1045),
|
|
});
|
|
for (const reference of indexed.references)
|
|
assertReference(reference, indexed.address, full.references);
|
|
assert.ok(
|
|
indexed.references.some(
|
|
(reference) =>
|
|
reference.primary === false && reference.source === "analysis",
|
|
),
|
|
"Indexed fixture lacks a non-primary analyzer reference",
|
|
);
|
|
|
|
const { direct, targets } = await verifyDirectCall(
|
|
call,
|
|
address(0x1000),
|
|
address(0x1010),
|
|
);
|
|
|
|
const indirectAddress = address(x86 ? 0x1041 : 0x1049);
|
|
const indirect = await call("inspect_native_instruction", {
|
|
address: indirectAddress,
|
|
});
|
|
const unresolved = await call("resolve_native_call_targets", {
|
|
address: indirectAddress,
|
|
});
|
|
assert.equal(indirect.flow.kind, "call");
|
|
assert.deepEqual(indirect.references, []);
|
|
assert.equal(unresolved.status, "unresolved");
|
|
assert.deepEqual(unresolved.targets, []);
|
|
|
|
const returned = await verifyEmptyReferences(
|
|
call,
|
|
address(x86 ? 0x1043 : 0x104c),
|
|
);
|
|
|
|
if (cli !== undefined) {
|
|
for (const instruction of [inspected.write, indexed, direct, returned])
|
|
assert.deepEqual(
|
|
await cli("inspect-native-instruction", instruction.address),
|
|
instruction,
|
|
);
|
|
assert.deepEqual(
|
|
await cli("resolve-native-call-targets", direct.address),
|
|
targets,
|
|
);
|
|
assert.deepEqual(
|
|
await cli("resolve-native-call-targets", indirectAddress),
|
|
unresolved,
|
|
);
|
|
}
|
|
return {
|
|
data_read_write: true,
|
|
source_primary_provenance: true,
|
|
full_reference_parity: true,
|
|
direct_call_reference_parity: true,
|
|
indexed_reference_count: indexed.references.length,
|
|
indexed_non_primary_count: indexed.references.filter(
|
|
(reference) => reference.primary === false,
|
|
).length,
|
|
indexed_non_primary_analyzer_reference: true,
|
|
indexed_reference_kinds: indexed.references.map(
|
|
({ type, read, write, primary, source }) => ({
|
|
type,
|
|
read,
|
|
write,
|
|
primary,
|
|
source,
|
|
}),
|
|
),
|
|
targetless_indirect: true,
|
|
empty_and_unavailable: true,
|
|
cli_mcp_parity: cli !== undefined,
|
|
};
|
|
}
|
|
|
|
async function verifyDirectCall(call, entry, callee) {
|
|
const direct = await call("inspect_native_instruction", { address: entry });
|
|
const targets = await call("resolve_native_call_targets", { address: entry });
|
|
assert.equal(targets.status, "direct");
|
|
assert.equal(targets.targets[0].address, callee);
|
|
assert.deepEqual(
|
|
targets.targets[0].references,
|
|
direct.references.filter((item) => item.call),
|
|
);
|
|
const full = await call("procedure_references", { procedure: entry });
|
|
for (const reference of direct.references)
|
|
assertReference(reference, entry, full.references);
|
|
return { direct, targets };
|
|
}
|
|
|
|
async function verifyEmptyReferences(call, address) {
|
|
const returned = await call("inspect_native_instruction", { address });
|
|
assert.equal(returned.flow.kind, "return");
|
|
assert.deepEqual(returned.references, []);
|
|
assert.equal(
|
|
(await call("resolve_native_call_targets", { address })).status,
|
|
"not-call",
|
|
);
|
|
const outside = await call("inspect_native_instruction", { address: "0x0" });
|
|
assert.equal(outside.status, "outside-memory");
|
|
assert.deepEqual(outside.references, []);
|
|
assert.equal(
|
|
(await call("resolve_native_call_targets", { address: "0x0" })).status,
|
|
"unavailable",
|
|
);
|
|
return returned;
|
|
}
|
|
|
|
function assertReference(reference, source, full) {
|
|
assert.equal(reference.source_address, source);
|
|
assert.equal(reference.provenance, "ghidra-reference-manager");
|
|
assert.equal(typeof reference.primary, "boolean");
|
|
assert.equal(typeof reference.source, "string");
|
|
assert.ok(reference.source.length > 0);
|
|
const edge = full.find(
|
|
(item) =>
|
|
item.source_address === source &&
|
|
item.target_address === reference.target_address &&
|
|
item.kind.operand_index === reference.operand_index,
|
|
);
|
|
assert.ok(edge, "Compact reference lacks its matching full reference");
|
|
for (const field of [
|
|
"type",
|
|
"call",
|
|
"jump",
|
|
"indirect",
|
|
"computed",
|
|
"operand_index",
|
|
"data",
|
|
"read",
|
|
"write",
|
|
"primary",
|
|
"provenance",
|
|
])
|
|
assert.equal(
|
|
reference[field],
|
|
edge.kind[field],
|
|
`Reference ${field} differs from its full representation`,
|
|
);
|
|
}
|