Files
reverse-engineering-anything/src/ida/IdaNativeProtocol.ts
T
Ysopwnable 37b7550ecc fix(ida): preserve native coverage and verify returned values (#2324)
Reject native callee successes without an explicit truncation marker while preserving provider failure reasons. Verify positive procedure inventory/search and function-result parity in the real-provider lane; support independent string/xref oracles and label their absence as unverified.

Verified with Windows IDA Pro 9.3: eight normal production CLI/MCP checks passed, and six real-provider response-fault cases reproduced old false positives and validated the correction. Windows IDA Free was not used. No personal paths or license registration information are included.

Repair inconsistent complete-file Ghidra test probe sizes after the shared ELF-header validation change, deriving sizes from the fixture buffers. Reproduced the three failures before the correction; 115 related installation/doctor and ELF tests pass afterward. Runtime parsing and installation behavior are unchanged; no real-Ghidra verification is claimed.

207 focused IDA tests and static/documentation checks passed. Automated review completed on d1e0b1095 without findings. CI run 38327862093 passed all 36 jobs, including Windows, all six test shards, coverage/timing, and real Apple CLI/MCP workflows.

Follow-up to #1544.
2026-10-13 08:44:18 +08:00

130 lines
3.9 KiB
TypeScript

import { z } from "zod";
import { AnalysisProtocolError } from "../domain/analysisErrorCore.js";
import {
cursorSchema,
disassemblySchema,
idaAddressSchema,
modernCalleesSchema,
modernStringsSchema,
modernXrefsSchema,
} from "./IdaProtocolValues.js";
/** Native MCP renders an assembly header followed by bare hexadecimal coordinates. */
const nativeAssemblySchema = disassemblySchema
.omit({ lines: true })
.extend({
lines: z.string(),
})
.transform((asm) => {
const [header, ...body] = asm.lines.split("\n");
const match = header?.match(/^(.+) \((.*) @ (0x[0-9a-f]+)\):$/iu);
if (
match?.[1] !== asm.name ||
idaAddressSchema.parse(match?.[3]) !== asm.start_ea
)
throw new AnalysisProtocolError(
"Native IDA assembly header contradicts its function identity.",
);
return {
...asm,
lines: body.map((line) => {
const item = line.match(/^([0-9a-f]+) (.*)$/iu);
if (item === null)
throw new AnalysisProtocolError(
"Native IDA returned an unsupported assembly line.",
);
return {
address: idaAddressSchema.parse(item[1]),
instruction: item[2] ?? "",
};
}),
};
});
/** Native assembly retains the same explicit pagination cursor as modern MCP. */
export const nativeDisassemblySchema = z.object({
asm: nativeAssemblySchema.nullable(),
error: z.string().optional(),
cursor: cursorSchema,
});
/** GUI decompilation reports the resolved entry and an explicit success/failure. */
export const nativeDecompileSchema = z.object({
addr: idaAddressSchema,
ok: z.boolean(),
pseudocode: z.string().min(1).optional(),
lines: z.number().int().nonnegative().optional(),
error: z.string().optional(),
});
/** Native relationship successes explicitly carry null errors. Normalize that omission here. */
const nativeErrorSchema = z
.string()
.nullish()
.transform((value) => value ?? undefined);
export const nativeCalleesSchema = z
.array(
modernCalleesSchema.element
.extend({ error: nativeErrorSchema })
.transform((page) => {
if (
page.error === undefined &&
page.callees !== null &&
page.more === undefined
)
throw new AnalysisProtocolError(
"Native IDA callees omitted an explicit truncation marker.",
);
return page;
}),
)
.length(1);
export const nativeXrefsSchema = z
.array(modernXrefsSchema.element.extend({ error: nativeErrorSchema }))
.length(1);
/** Native regex failures carry an empty page, which must not imply absence. */
export const nativeStringsSchema = modernStringsSchema
.extend({ error: z.string().optional() })
.transform((page) => {
if (page.error !== undefined)
throw new AnalysisProtocolError(
`Native IDA string search failed: ${page.error}`,
);
return page;
});
/**
* Native regexes fold bytes, while REA's insensitive literal search folds
* Unicode strings. Include every non-ASCII value as a candidate, then apply
* REA's exact predicate. Ill-formed UTF-16 cannot cross upstream JSON/UTF-8.
*/
export const nativeLiteralSearchPattern = (
pattern: string,
caseSensitive: boolean,
): string | undefined => {
if (/[\uD800-\uDFFF]/u.test(pattern)) return undefined;
let literal = (caseSensitive ? pattern : pattern.toLowerCase()).replace(
/[.*+?^${}()|[\]\\]/gu,
"\\$&",
);
// Spell ASCII variants explicitly rather than depending on an upstream locale.
if (!caseSensitive)
literal = literal.replace(
/[a-z]/gu,
(char) => `[${char}${char.toUpperCase()}]`,
);
return caseSensitive ? literal : `(?:${literal})|[\\x80-\\xff]`;
};
/** Read-only tools required from the native bridge; GUI lifecycle tools are excluded. */
export const nativeTools = [
"list_funcs",
"lookup_funcs",
"find_regex",
"decompile",
"disasm",
"callees",
"xrefs_to",
];