Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .agents/skills/reverse-engineer-anything/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ explicit path or endpoint and do not need it.
- PE resource type/name/language trees and static icon group references:
`inspect_pe_resources` with an explicit path. This portable operation requires
no active target or engine; opaque payloads retain file ranges and SHA-256.
- PE export directories and ELF dynamic-symbol exports: `inspect_native_exports`
with an explicit path; portable, no active target or engine.
- Native executable, library, or analysis database: `open_binary`, then
use focused analysis tools directly; call `binary_overview` when metadata or
inventory context is useful and available from the selected provider.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,16 @@ Other payloads are opaque, and this operation does not decode images or simulate
Windows resource loading. Consult the budgets and supported tree profile in
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/binary-diagnostics.md).

## Offline PE and ELF export tables

Use `inspect_native_exports` with an absolute `path`, or
`rea inspect-native-exports ./libexample.so --json`, to read a PE export
directory or ELF dynamic symbol table on any host without opening a native
target. Ordinal-only, forwarded and hidden-version exports stay distinct;
partial or unsupported coverage means the inventory is incomplete, not that
exports are absent. Use `inspect_macho` for Mach-O. See
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/binary-diagnostics.md).

For exact core-module import/export differences, use `compare_wasm_interfaces`
with two complete inspection Evidence records (or current-session retained
references) and explicit scope. Preserve unknown types/duplicate names; static
Expand Down
49 changes: 49 additions & 0 deletions docs/binary-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,52 @@ arm64 host and Node version. Cold disk I/O, token counts and an agent
investigation were not measured; there was no previous equivalent tool to
benchmark. These figures do not establish a portable peak-memory bound,
constant memory for retained Evidence or faster agent task completion.

## Portable native exports

`inspect_native_exports` / `inspect-native-exports` reads the export table of an
explicit PE or ELF image on any supported host, without an active target,
native engine, `objdump` or Windows SDK:

```sh
rea inspect-native-exports ./runtimes/linux-x64/native/libexample.so --json
```

```json
{
"name": "inspect_native_exports",
"arguments": { "path": "/artifacts/native/example.dll" }
}
```

The complete Evidence identifies the stable artifact snapshot by path, byte
count and SHA-256.

- PE rows reproduce the export directory: each used export-address-table slot
with its `ordinal`, one row per name (aliases share an ordinal), `address`
as the preferred-base virtual address, or `forwarder` text when the slot
forwards to another DLL. Ordinal-only exports have a null `name`. The
directory's DLL name, ordinal base and timestamp are reported, and an image
without an export directory is a complete empty inventory.
- ELF rows list defined `.dynsym` symbols with global, weak or GNU-unique
binding and default or protected visibility. Each row keeps `value`
(`st_value` as stored; bit 0 marks Thumb code on 32-bit ARM), `size`, `type`
and `binding`. `version` is null for an unversioned symbol, the
`.gnu.version_d` index, name and `hidden` flag for a versioned one, or
`"unknown"` when the version table cannot be read. A hidden version is not
the default version that a by-name `dlsym` lookup selects.

Unreadable names, out-of-range name ordinals and unreadable forwarders are
listed in `issues`, their entries are not reported, and coverage is `partial`.
Unresolved symbol versions keep their rows: the version name is null for an
index no definition names, and `version` is `"unknown"` when the version table
cannot be read; coverage is likewise `partial`. A dynamic symbol table without
its null entry is malformed. An ELF without section headers is `unsupported`:
enumerating dynamic symbols from the dynamic segment is not
implemented. Mach-O exports are reported by `inspect_macho`.

`max_file_bytes` defaults to 256 MiB (maximum 512 MiB; CLI `--max-file-bytes`).
Export directories in PE headers are read like those in sections. An export or
dynamic symbol table above 262144 entries, more than 262144 rows, or more than
16 MiB of retained names fails without partial success. Export rows do not establish that a symbol is reachable, safe to call
or loaded at runtime.
7 changes: 7 additions & 0 deletions scripts/lib/catalog-builders.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,13 @@ export const providerCatalog = (sources) => {
({ name }) => name === "inspect_byte_entropy",
),
},
{
identity: sources.artifactProviders.NATIVE_EXPORTS_PROVIDER,
contracts:
sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS.filter(
({ name }) => name === "inspect_native_exports",
),
},
{
identity: sources.captureProvider.TSHARK_PROVIDER_IDENTITY,
contracts: sources.captureContracts.CAPTURE_TOOL_CONTRACTS,
Expand Down
2 changes: 1 addition & 1 deletion src/application/CapabilityInventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ const workflowAvailabilityFor = ({
remediation:
"Supply WABT 1.0.42 via absolute REA_WABT_BIN_DIRECTORY. No active binary target is required; tools are checked at execution.",
};
if (name === "inspect_pe_resources")
if (name === "inspect_pe_resources" || name === "inspect_native_exports")
return { reason: "available", remediation: null };
if (name === "inspect_elf_notes")
return { reason: "available", remediation: null };
Expand Down
7 changes: 7 additions & 0 deletions src/application/InvestigationProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ export const BYTE_ENTROPY_PROVIDER = Object.freeze({
version: "1",
} as const);

/** Provider identity for portable, execution-free PE and ELF export tables. */
export const NATIVE_EXPORTS_PROVIDER = Object.freeze({
id: "rea-native-exports",
name: "REA PE/ELF export table reader",
version: "1",
} as const);

/** Provider identity for deterministic managed-code comparison workflows. */
export const MANAGED_WORKFLOW_PROVIDER = {
id: "rea-dotnet-workflows",
Expand Down
20 changes: 20 additions & 0 deletions src/application/binaryDiagnostics/NativeExportsPort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type {
ExecutionOptions,
ProviderIdentity,
} from "../AnalysisProvider.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import type {
InspectNativeExportsInput,
NativeExports,
} from "../../domain/native/nativeExports.js";
import type { Result } from "../../domain/result.js";

/** Stable read and export-table decoding, independent of an active engine session. */
export interface NativeExportsPort {
readonly identity: ProviderIdentity;
close?(): Promise<void>;
inspect(
input: InspectNativeExportsInput,
options?: ExecutionOptions,
): Promise<Result<NativeExports, AnalysisError>>;
}
79 changes: 79 additions & 0 deletions src/application/binaryDiagnostics/NativeExportsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { ExecutionOptions } from "../AnalysisProvider.js";
import type { NativeExportsPort } from "./NativeExportsPort.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import {
AnalysisCancelledError,
AnalysisOutputError,
} from "../../domain/analysisErrorCore.js";
import { createEvidence, type Evidence } from "../../domain/evidence.js";
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
import {
inspectNativeExportsInputSchema,
nativeExportsSchema,
} from "../../domain/native/nativeExports.js";
import { err, type Result } from "../../domain/result.js";
import { completeProviderEvidence } from "../ProviderOperationExecution.js";

const OPERATION = "inspect_native_exports";

/** Shared caller validation, output parsing and Evidence provenance for CLI/MCP. */
export class NativeExportsService {
constructor(readonly provider: NativeExportsPort) {}

close(): Promise<void> {
return this.provider.close?.() ?? Promise.resolve();
}

/** Read one explicitly selected PE or ELF image's export table. */
async inspect(
rawInput: unknown,
options?: ExecutionOptions,
): Promise<Result<Evidence, AnalysisError>> {
if (options?.signal?.aborted)
return err(
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
);
const input = inspectNativeExportsInputSchema.safeParse(rawInput);
if (!input.success)
return err(
analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput, {
cause: input.error,
}),
);
const result = await this.provider.inspect(input.data, options);
if (!result.ok) return result;
const report = nativeExportsSchema.safeParse(result.value);
if (
!report.success ||
report.data.artifact.path !== input.data.path ||
report.data.artifact.bytes > input.data.max_file_bytes ||
report.data.coverage.exports !== report.data.exports.length
)
return err(
new AnalysisOutputError(
OPERATION,
"Native export reader returned malformed output or changed artifact identity.",
),
);
const value = report.data;
return completeProviderEvidence(
createEvidence(
{
path: value.artifact.path,
format: value.format,
sha256: value.artifact.sha256,
},
this.provider.identity,
{
operation: OPERATION,
parameters: input.data,
result: value,
confidence: "observed",
limitations: value.limitations,
locations: [{ kind: "artifact-path", path: value.artifact.path }],
},
),
options?.signal,
);
}
}
33 changes: 33 additions & 0 deletions src/cli/binaryDiagnosticsCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
createBinaryLayoutService,
createElfNotesService,
createPeResourcesService,
createNativeExportsService,
} from "../composition/binaryDiagnostics.js";
import { CLI_COMMANDS } from "../cliCommandNames.js";
import { logCliCommand } from "../cliLogging.js";
Expand All @@ -15,6 +16,7 @@ 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 { inspectNativeExportsInputSchema } from "../domain/native/nativeExports.js";
import { inspectElfNotesInputSchema } from "../domain/native/elfNotes.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
Expand Down Expand Up @@ -130,6 +132,37 @@ export const registerBinaryDiagnosticsCommands = (
);
}),
});
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:
Expand Down
1 change: 1 addition & 0 deletions src/cliCommandNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const CLI_COMMANDS = Object.freeze({
inspectGpuCodeObjects: "inspect-gpu-code-objects",
inspectRustBinary: "inspect-rust-binary",
inspectPeResources: "inspect-pe-resources",
inspectNativeExports: "inspect-native-exports",
inspectElfNotes: "inspect-elf-notes",
inspectAnalysisView: "inspect-analysis-view",
listFridaDevices: "list-frida-devices",
Expand Down
5 changes: 5 additions & 0 deletions src/composition/binaryDiagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ import { PeResourcesProvider } from "../native/pe/PeResourcesProvider.js";
import { ElfNotesService } from "../application/binaryDiagnostics/ElfNotesService.js";
import { ElfNotesProvider } from "../native/elf/ElfNotesProvider.js";
import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import { NativeExportsService } from "../application/binaryDiagnostics/NativeExportsService.js";
import { NativeExportsProvider } from "../native/exports/NativeExportsProvider.js";

/** Portable PE inspection with no engine, installation or target acquisition. */
export const createPeResourcesService = (): PeResourcesService =>
new PeResourcesService(new PeResourcesProvider());

/** Portable PE/ELF export tables with no engine, toolchain or target acquisition. */
export const createNativeExportsService = (): NativeExportsService =>
new NativeExportsService(new NativeExportsProvider());
/** Portable ELF note-table inspection with no engine or package catalog. */
export const createElfNotesService = (): ElfNotesService =>
new ElfNotesService(new ElfNotesProvider());
Expand Down
19 changes: 19 additions & 0 deletions src/contracts/native/binaryDiagnosticsToolContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import {
analysisDetailOutputSchemaOf,
analysisDetailSchema,
} from "../analysisDetail.js";
import {
inspectNativeExportsInputSchema,
nativeExportsSchema,
} from "../../domain/native/nativeExports.js";
import type { ToolContract } from "../toolContractTypes.js";
import { toolContractMetadata } from "../toolEffects.js";
import { evidenceResultOf } from "../toolOutputSchemaPrimitives.js";
Expand Down Expand Up @@ -95,6 +99,21 @@ export const BINARY_DIAGNOSTICS_TOOL_CONTRACTS = [
},
],
},
{
name: "inspect_native_exports",
...toolContractMetadata("inspect_native_exports"),
kind: "native-provider",
description:
"Inspect one explicit local PE or ELF image's export table without executing it or opening an engine: PE export-directory names, ordinals, addresses and forwarders, or ELF defined default/protected global, weak and unique .dynsym symbols with type, binding and symbol version. Ordinal-only, forwarded and hidden-version exports stay distinct. Portable on any supported host. Coverage is partial when table entries are unreadable and unsupported for an ELF without section headers; an empty complete inventory means the image declares no exports. Mach-O exports use inspect_macho.",
inputSchema: inspectNativeExportsInputSchema,
outputSchema: evidenceResultOf(nativeExportsSchema),
examples: [
{
title: "List a native library's exports",
input: { path: "/artifacts/native/libexample.so" },
},
],
},
{
name: "inspect_binary_layout",
...toolContractMetadata("inspect_binary_layout"),
Expand Down
1 change: 1 addition & 0 deletions src/contracts/toolEffects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const EXECUTION_WORDING_EXCEPTIONS: Readonly<Record<string, string>> = {
resolve_native_call_targets: "states that it does not execute code",
inspect_byte_entropy: "states that it profiles the file without execution",
inspect_pe_resources: "states that it does not execute the image",
inspect_native_exports: "reads the export table without executing it",
inspect_elf_notes: "states that it does not execute the image",
inspect_desktop_application:
"states that directory triage performs no target execution",
Expand Down
1 change: 1 addition & 0 deletions src/contracts/toolEffects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ export const TOOL_EFFECTS: Readonly<Record<string, ToolEffects>> = {
}),
inspect_byte_entropy: evidence,
inspect_pe_resources: evidence,
inspect_native_exports: evidence,
inspect_elf_notes: evidence,
inspect_analysis_view: evidence,
inspect_firmware_regions: effects({
Expand Down
Loading
Loading