mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
* feat(pe): inspect import and delay-load tables Add inspect_pe_imports / inspect-pe-imports to read PE32/PE32+ import and delay-load directories without executing the image. The report keeps DLL names, named and ordinal functions, ILT/IAT RVAs, and a pefile import hash of the standard import table. A missing directory is observed absence, not proof the image never imported APIs. * fix(pe): default PE import fixture options Allow peImportImage() without an argument so CLI and filesystem tests can use the KERNEL32 sample without repeating an empty options object. * test(pe): keep import path cases on the dedicated MCP contract inspect_pe_imports already rejects relative MCP paths in its own boundary test. Leave the shared absolute-path matrix to tools whose handler issue projection matches that suite's expected shape. * fix(native): index virtual-to-file mapping lookups mappedFileOffset scans every mapping twice per span, so a parser that translates one span per table entry costs sections x entries. Add fileMappingLookup, which builds an O(n log n) interval index once and answers each span in O(log n) with the same accept, reject and translation results. A differential test checks it against mappedFileOffset over randomized overlapping mappings and zero fills. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Ra7LM2NyQ7DumdssxbUq5g * fix(pe): report partial import evidence and exact pefile hashes - Withhold imphash with an explicit reason whenever the standard import table is partial (unreadable names or thunks, missing tables), or when pefile 2024.8.26 would hash a different row sequence: invalid or truncated names, ordinal 0, descriptors without functions, tables its bounds or repeat/spread heuristics discard, and its 8192-thunk import symbol cap. Delay-load issues do not withhold it. - Hash ordinal imports with pefile's complete ordlookup tables and its ord<decimal> fallback instead of declining unmapped ordinals. - Report a nonterminal descriptor whose FirstThunk is zero, and a delay-load descriptor whose IAT or INT RVA is zero, as malformed_entry partial coverage instead of a complete empty inventory. - Directory evidence lists the file spans actually consumed for the descriptor array and terminator; discontiguous mappings produce separate spans and separate Evidence ranges. - Carry exhausted limits as PeImportLimitFailure.limit and derive recovery from it; the fixed 16 MiB retained-text ceiling reports a fixed allowance instead of max_file_bytes advice. - Translate RVAs through the indexed mapping lookup and yield with a cancellation check every 1024 table entries. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Ra7LM2NyQ7DumdssxbUq5g * fix(pe): read only own ordinal tables for DLL names DLL names such as constructor and __proto__ pass pefile's name rule but resolved inherited object members, throwing before the ord<n> fallback. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Ra7LM2NyQ7DumdssxbUq5g * fix(native): preserve PE import facts and prove hash compatibility * test(native): verify PE import workflows and measured costs * refactor(native): narrow PE facts across the container union * docs(native): record integrated PE import validation * fix(native): bound every PE import RVA span * docs(native): measure RVA guard completed workflows * fix(pe): reconcile import coverage with retained rows * fix(pe): retain partial imports at descriptor mapping gaps --------- Co-authored-by: Matt Van Horn <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: gensokyo-dev <[email protected]>
154 lines
4.8 KiB
TypeScript
154 lines
4.8 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { digestSchema } from "../digests.js";
|
|
import { absoluteLocalPathStringSchema } from "../localPath.js";
|
|
|
|
const uint = z.number().int().min(0).max(0xffffffff);
|
|
const range = z.strictObject({ offset: uint, bytes: uint });
|
|
const md5Schema = z.string().regex(/^[a-f0-9]{32}$/u);
|
|
|
|
/** Hard ceiling on the caller-selected input read budget. */
|
|
export const MAX_INSPECT_PE_IMPORT_FILE_BYTES = 536870912;
|
|
/** Hard ceiling on import and delay-load descriptors examined together. */
|
|
export const MAX_INSPECT_PE_IMPORT_DLLS = 4096;
|
|
/** Hard ceiling on named and ordinal functions across every table. */
|
|
export const MAX_INSPECT_PE_IMPORT_FUNCTIONS = 262144;
|
|
/** Fixed ceiling on decoded DLL and function name text retained in one report. */
|
|
export const MAX_INSPECT_PE_IMPORT_RETAINED_TEXT_BYTES = 16 * 1024 * 1024;
|
|
|
|
/** Complete bounded inspection of PE import and delay-load tables. */
|
|
export const inspectPeImportsInputSchema = z.strictObject({
|
|
path: absoluteLocalPathStringSchema.describe(
|
|
"Absolute filesystem path to a PE image",
|
|
),
|
|
max_file_bytes: z
|
|
.number()
|
|
.int()
|
|
.min(64)
|
|
.max(MAX_INSPECT_PE_IMPORT_FILE_BYTES)
|
|
.default(67108864)
|
|
.describe("Maximum stable artifact bytes to read (default 64 MiB)"),
|
|
max_dlls: z
|
|
.number()
|
|
.int()
|
|
.min(1)
|
|
.max(MAX_INSPECT_PE_IMPORT_DLLS)
|
|
.default(256)
|
|
.describe(
|
|
"Maximum import and delay-load descriptors to examine; exceeding the budget fails without partial success",
|
|
),
|
|
max_functions: z
|
|
.number()
|
|
.int()
|
|
.min(1)
|
|
.max(MAX_INSPECT_PE_IMPORT_FUNCTIONS)
|
|
.default(8192)
|
|
.describe(
|
|
"Maximum imported functions to examine across every table; exceeding the budget fails without partial success",
|
|
),
|
|
});
|
|
export type InspectPeImportsInput = z.infer<typeof inspectPeImportsInputSchema>;
|
|
|
|
const peImportFunctionSchema = z.strictObject({
|
|
hint: uint.nullable(),
|
|
name: z.string().min(1).nullable(),
|
|
ordinal: z.number().int().min(0).max(0xffff).nullable(),
|
|
ilt_rva: uint.nullable(),
|
|
iat_rva: uint,
|
|
thunk_hex: z.string().regex(/^[0-9a-f]+$/u),
|
|
});
|
|
|
|
const peImportDllSchema = z.strictObject({
|
|
index: uint,
|
|
name: z.string().min(1).nullable(),
|
|
name_rva: uint,
|
|
original_first_thunk_rva: uint,
|
|
first_thunk_rva: uint,
|
|
time_date_stamp: uint,
|
|
forwarder_chain: uint,
|
|
location: range,
|
|
functions: z.array(peImportFunctionSchema),
|
|
});
|
|
|
|
const peDelayImportDllSchema = z.strictObject({
|
|
index: uint,
|
|
name: z.string().min(1).nullable(),
|
|
name_rva: uint,
|
|
attributes: uint,
|
|
import_address_table_rva: uint,
|
|
import_name_table_rva: uint,
|
|
bound_import_rva: uint,
|
|
unload_information_table_rva: uint,
|
|
module_handle_rva: uint,
|
|
time_date_stamp: uint,
|
|
location: range,
|
|
functions: z.array(peImportFunctionSchema),
|
|
});
|
|
|
|
/**
|
|
* `locations` are the file spans the parser actually consumed for the
|
|
* descriptor array and its terminator, in RVA order. Consecutive descriptors
|
|
* that map to discontiguous file bytes produce separate spans.
|
|
* No bytes are consumed when the first slot cannot be examined; the declared
|
|
* directory remains present and coverage is partial.
|
|
*/
|
|
const directorySchema = z
|
|
.strictObject({
|
|
rva: uint,
|
|
size: uint,
|
|
locations: z.array(range),
|
|
})
|
|
.nullable();
|
|
|
|
const peImportIssueSchema = z.strictObject({
|
|
code: z.enum([
|
|
"malformed_entry",
|
|
"descriptor_unreadable",
|
|
"name_unreadable",
|
|
"thunk_unreadable",
|
|
"unsupported_form",
|
|
]),
|
|
location: z.string().min(1).nullable(),
|
|
message: z.string().min(1),
|
|
});
|
|
|
|
const coverageSchema = z.strictObject({
|
|
status: z.enum(["complete", "partial"]),
|
|
reason: z.string().min(1).nullable(),
|
|
examined_dlls: uint,
|
|
imports: uint,
|
|
delay_imports: uint,
|
|
});
|
|
|
|
const imphashSchema = z.strictObject({
|
|
algorithm: z.literal("pefile"),
|
|
md5: md5Schema.nullable(),
|
|
reason: z.string().min(1).nullable(),
|
|
});
|
|
|
|
/** Static import inventory of one PE32/PE32+ image, read without execution. */
|
|
export const peImportsSchema = z.strictObject({
|
|
artifact: z.strictObject({
|
|
path: z.string().min(1),
|
|
sha256: digestSchema,
|
|
bytes: uint,
|
|
}),
|
|
format: z.enum(["pe32", "pe32-plus"]),
|
|
machine: uint,
|
|
bits: z.union([z.literal(32), z.literal(64)]),
|
|
architecture: z.string().min(1),
|
|
import_directory: directorySchema,
|
|
delay_import_directory: directorySchema,
|
|
imports: z.array(peImportDllSchema),
|
|
delay_imports: z.array(peDelayImportDllSchema),
|
|
imphash: imphashSchema,
|
|
coverage: coverageSchema,
|
|
issues: z.array(peImportIssueSchema),
|
|
limitations: z.array(z.string().min(1)),
|
|
});
|
|
export type PeImports = z.infer<typeof peImportsSchema>;
|
|
export type PeImportDll = z.infer<typeof peImportDllSchema>;
|
|
export type PeDelayImportDll = z.infer<typeof peDelayImportDllSchema>;
|
|
export type PeImportFunction = z.infer<typeof peImportFunctionSchema>;
|
|
export type PeImportIssue = z.infer<typeof peImportIssueSchema>;
|