import { inspectByteEntropyInputSchema } from "../domain/native/byteEntropy.js"; import { z } from "incur"; import { createByteEntropyService, createRecordedCrashService, createBinaryLayoutService, createElfNotesService, createPeResourcesService, createPeCertificatesService, createNativeExportsService, } from "../composition/binaryDiagnostics.js"; import { CLI_COMMANDS } from "../cliCommandNames.js"; import { logCliCommand } from "../cliLogging.js"; import type { Logger } from "pino"; import type { CliInstance } from "./types.js"; import { withCommandCancellation } from "./commandCancellation.js"; import { inspectBinaryLayoutInputSchema } from "../domain/native/binaryLayout.js"; import { inspectRecordedCrashInputSchema } from "../domain/native/recordedCrash.js"; import { inspectPeResourcesInputSchema } from "../domain/native/peResources.js"; import { inspectPeCertificatesInputSchema } from "../domain/native/peCertificates.js"; import { inspectNativeExportsInputSchema } from "../domain/native/nativeExports.js"; import { inspectElfNotesInputSchema } from "../domain/native/elfNotes.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; import { finishAnalysisCommand } from "./finishAnalysisCommand.js"; import { absoluteCallerPath } from "../filesystem/callerPath.js"; /** Inspect operator-selected files through the same shared workflow as MCP. */ export const registerBinaryDiagnosticsCommands = ( cli: CliInstance, logger: Logger, environment: Readonly, ): void => { cli.command(CLI_COMMANDS.inspectElfNotes, { description: "Inspect ELF PT_NOTE and SHT_NOTE identities without executing the image", args: z.object({ path: z.string().describe("Local ELF32/ELF64 image") }), options: z.object({ maxFileBytes: inspectElfNotesInputSchema.shape.max_file_bytes, maxEntries: inspectElfNotesInputSchema.shape.max_entries, }), run: ({ args, options }) => withCommandCancellation(async (signal) => { const elf = createElfNotesService(); return await logCliCommand( logger, CLI_COMMANDS.inspectElfNotes, async () => finishAnalysisCommand( () => elf.inspect( { path: absoluteCallerPath(args.path), max_file_bytes: options.maxFileBytes, max_entries: options.maxEntries, }, { signal }, ), elf, "rea-elf-notes", "inspect_elf_notes", signal, ), ); }), }); const entropy = createByteEntropyService(); cli.command(CLI_COMMANDS.inspectByteEntropy, { description: "Inspect file-wide and block byte entropy without executing the file", args: z.object({ path: z.string().describe("Local file") }), options: z.object({ blockSize: inspectByteEntropyInputSchema.shape.block_size.describe( "Power-of-two block size from 256 to 65536 bytes (default 4096)", ), offset: inspectByteEntropyInputSchema.shape.offset.describe( "Window start as a decimal file byte offset (default 0)", ), length: inspectByteEntropyInputSchema.shape.length.describe( "Window length in bytes; omit to extend through EOF", ), }), run: ({ args, options }) => withCommandCancellation((signal) => logCliCommand(logger, CLI_COMMANDS.inspectByteEntropy, async () => { return await finishAnalysisCommand( () => entropy.inspect( { path: absoluteCallerPath(args.path), block_size: options.blockSize, offset: options.offset, length: options.length, }, { signal }, ), entropy, "rea-byte-entropy", "inspect_byte_entropy", signal, ); }), ), }); cli.command(CLI_COMMANDS.inspectPeCertificates, { description: "Inspect PE Authenticode certificate table identities without verifying signatures", args: z.object({ path: z.string().describe("Local PE32/PE32+ image") }), options: z.object({ maxFileBytes: inspectPeCertificatesInputSchema.shape.max_file_bytes, maxEntries: inspectPeCertificatesInputSchema.shape.max_entries, }), run: ({ args, options }) => withCommandCancellation(async (signal) => { const pe = createPeCertificatesService(); return await logCliCommand( logger, CLI_COMMANDS.inspectPeCertificates, async () => finishAnalysisCommand( () => pe.inspect( { path: absoluteCallerPath(args.path), max_file_bytes: options.maxFileBytes, max_entries: options.maxEntries, }, { signal }, ), pe, "rea-pe-certificates", "inspect_pe_certificates", signal, ), ); }), }); cli.command(CLI_COMMANDS.inspectPeResources, { description: "Inspect PE resource identities, payload ranges, hashes and icon-group relationships", args: z.object({ path: z.string().describe("Local PE32/PE32+ image") }), options: z.object({ maxFileBytes: inspectPeResourcesInputSchema.shape.max_file_bytes, maxEntries: inspectPeResourcesInputSchema.shape.max_entries, }), run: ({ args, options }) => withCommandCancellation(async (signal) => { const pe = createPeResourcesService(); return await logCliCommand( logger, CLI_COMMANDS.inspectPeResources, async () => finishAnalysisCommand( () => pe.inspect( { path: absoluteCallerPath(args.path), max_file_bytes: options.maxFileBytes, max_entries: options.maxEntries, }, { signal }, ), pe, "rea-pe-resources", "inspect_pe_resources", signal, ), ); }), }); cli.command(CLI_COMMANDS.inspectNativeExports, { description: "Inspect a PE or ELF image's export table without executing it", args: z.object({ path: z.string().describe("Local PE or ELF image") }), options: z.object({ maxFileBytes: inspectNativeExportsInputSchema.shape.max_file_bytes, }), run: ({ args, options }) => withCommandCancellation(async (signal) => { const exports = createNativeExportsService(); return await logCliCommand( logger, CLI_COMMANDS.inspectNativeExports, async () => finishAnalysisCommand( () => exports.inspect( { path: absoluteCallerPath(args.path), max_file_bytes: options.maxFileBytes, }, { signal }, ), exports, "rea-native-exports", "inspect_native_exports", signal, ), ); }), }); const service = createBinaryLayoutService(environment); cli.command(CLI_COMMANDS.inspectBinaryLayout, { description: "Inspect binary file/linked layout without executing the selected object", args: z.object({ path: z.string().describe("Local ELF binary or relocatable object"), }), options: z.object({ pwntoolsTimeoutMs: inspectBinaryLayoutInputSchema.shape.pwntools_timeout_ms, }), run: ({ args, options }) => withCommandCancellation((signal) => logCliCommand(logger, CLI_COMMANDS.inspectBinaryLayout, async () => { const result = await service.inspectAndClose( { path: absoluteCallerPath(args.path), ...(options.pwntoolsTimeoutMs === undefined ? {} : { pwntools_timeout_ms: options.pwntoolsTimeoutMs }), }, { signal }, ); return result.ok ? result.value : projectAnalysisError(result.error); }), ), }); const recorded = createRecordedCrashService(environment); cli.command(CLI_COMMANDS.inspectRecordedCrash, { description: "Inspect supplied core registers/signals and optional debugger mapping evidence", args: z.object({ path: z .string() .describe("Local Linux ELF64 amd64 or AArch64 little-endian core"), }), options: z.object({ debuggerContext: z .boolean() .default(false) .describe("Add BYO core-only GDB/pwndbg mapping candidates"), pwntoolsTimeoutMs: inspectRecordedCrashInputSchema.shape.pwntools_timeout_ms, }), run: ({ args, options }) => withCommandCancellation((signal) => logCliCommand(logger, CLI_COMMANDS.inspectRecordedCrash, async () => { const result = await recorded.inspectAndClose( { path: absoluteCallerPath(args.path), include_debugger_context: options.debuggerContext, ...(options.pwntoolsTimeoutMs === undefined ? {} : { pwntools_timeout_ms: options.pwntoolsTimeoutMs }), }, { signal }, ); return result.ok ? result.value : projectAnalysisError(result.error); }), ), }); };