mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
354 lines
10 KiB
TypeScript
354 lines
10 KiB
TypeScript
import { z } from "incur";
|
|
import { stat } from "node:fs/promises";
|
|
import { resolve } from "node:path";
|
|
|
|
import { runDirectAnalysis } from "../application/DirectAnalysis.js";
|
|
import { CLI_COMMANDS } from "../cliCommandNames.js";
|
|
import { logCliCommand } from "../cliLogging.js";
|
|
import type { Logger } from "../logger.js";
|
|
import { directAnalysisOptions, providerSelectionOption } from "./options.js";
|
|
import type { CliInstance } from "./types.js";
|
|
import { runCliJavaScriptApplicationAnalysis } from "./javascriptApplicationAnalysis.js";
|
|
|
|
export const registerCoreAnalysisCommands = (
|
|
cli: CliInstance,
|
|
logger: Logger,
|
|
): void => {
|
|
registerCoreCommands(cli, logger);
|
|
registerFunctionCommand(cli, logger);
|
|
registerNativeApiCommand(cli, logger);
|
|
registerInstructionsCommand(cli, logger);
|
|
registerSearchCommand(cli, logger);
|
|
registerXrefsCommand(cli, logger);
|
|
registerTraceCommand(cli, logger);
|
|
};
|
|
|
|
const registerCoreCommands = (cli: CliInstance, logger: Logger): void => {
|
|
const overviewOptions = z.object({
|
|
snapshot: z
|
|
.string()
|
|
.min(1)
|
|
.optional()
|
|
.describe("Load and update a local analysis snapshot"),
|
|
provider: providerSelectionOption,
|
|
});
|
|
cli.command(CLI_COMMANDS.analyze, {
|
|
description: "Get an overview of an app",
|
|
args: z.object({
|
|
path: z.string().describe("App, program, or analysis database path"),
|
|
}),
|
|
options: overviewOptions,
|
|
run: ({ args, options }) =>
|
|
logCliCommand(logger, "analyze", () =>
|
|
runRoutedOverview(args.path, options, logger),
|
|
),
|
|
});
|
|
cli.command(CLI_COMMANDS.inspect, {
|
|
description: "Inspect an app overview with evidence",
|
|
args: z.object({
|
|
path: z.string().describe("App, program, or analysis database path"),
|
|
}),
|
|
options: overviewOptions,
|
|
run: ({ args, options }) =>
|
|
logCliCommand(logger, "inspect", () =>
|
|
runDirectAnalysis(
|
|
args.path,
|
|
"binary_overview",
|
|
{},
|
|
directAnalysisOptions(logger, options.snapshot, options.provider),
|
|
),
|
|
),
|
|
});
|
|
cli.command(CLI_COMMANDS.decompile, {
|
|
description: "Read one part of an app as code",
|
|
args: z.object({
|
|
path: z.string().describe("App or program path"),
|
|
address: z.string().describe("Procedure address"),
|
|
}),
|
|
options: z.object({
|
|
snapshot: z
|
|
.string()
|
|
.min(1)
|
|
.optional()
|
|
.describe("Load and update a local analysis snapshot"),
|
|
provider: providerSelectionOption,
|
|
}),
|
|
run: ({ args, options }) =>
|
|
logCliCommand(logger, "decompile", () =>
|
|
runDirectAnalysis(
|
|
args.path,
|
|
"procedure_pseudo_code",
|
|
{ procedure: args.address },
|
|
directAnalysisOptions(logger, options.snapshot, options.provider),
|
|
),
|
|
),
|
|
});
|
|
};
|
|
|
|
const runRoutedOverview = async (
|
|
path: string,
|
|
options: {
|
|
readonly snapshot?: string | undefined;
|
|
readonly provider?: string | undefined;
|
|
},
|
|
logger: Logger,
|
|
) => {
|
|
if (
|
|
options.provider === undefined &&
|
|
options.snapshot === undefined &&
|
|
(await isJavaScriptApplicationPath(path))
|
|
)
|
|
return runCliJavaScriptApplicationAnalysis({
|
|
input_path: resolve(path),
|
|
});
|
|
return runDirectAnalysis(
|
|
path,
|
|
"binary_overview",
|
|
{},
|
|
directAnalysisOptions(logger, options.snapshot, options.provider),
|
|
);
|
|
};
|
|
|
|
const isJavaScriptApplicationPath = async (path: string): Promise<boolean> => {
|
|
const lower = path.toLowerCase();
|
|
if (lower.endsWith(".asar")) return true;
|
|
if (lower.endsWith(".app")) return false;
|
|
try {
|
|
return (await stat(path)).isDirectory();
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const registerXrefsCommand = (cli: CliInstance, logger: Logger): void => {
|
|
cli.command(CLI_COMMANDS.xrefs, {
|
|
description: "List bounded references to an analyzed address",
|
|
args: z.object({
|
|
path: z.string().describe("App or program path"),
|
|
address: z.string().describe("Hexadecimal address"),
|
|
}),
|
|
options: z.object({
|
|
snapshot: z
|
|
.string()
|
|
.min(1)
|
|
.optional()
|
|
.describe("Load and update a local analysis snapshot"),
|
|
provider: providerSelectionOption,
|
|
}),
|
|
run: ({ args, options }) =>
|
|
logCliCommand(logger, "xrefs", () =>
|
|
runDirectAnalysis(
|
|
args.path,
|
|
"xrefs",
|
|
{ address: args.address },
|
|
directAnalysisOptions(logger, options.snapshot, options.provider),
|
|
),
|
|
),
|
|
});
|
|
};
|
|
|
|
const registerTraceCommand = (cli: CliInstance, logger: Logger): void => {
|
|
cli.command(CLI_COMMANDS.trace, {
|
|
description: "Trace a literal feature through analyzed references",
|
|
args: z.object({
|
|
path: z.string().describe("App or program path"),
|
|
query: z.string().min(1).describe("Literal feature query"),
|
|
}),
|
|
options: z.object({
|
|
caseSensitive: z
|
|
.boolean()
|
|
.default(false)
|
|
.describe("Match the query with exact letter case"),
|
|
limit: z
|
|
.number()
|
|
.int()
|
|
.min(1)
|
|
.default(10_000)
|
|
.describe("Maximum matching results to return"),
|
|
snapshot: z
|
|
.string()
|
|
.min(1)
|
|
.optional()
|
|
.describe("Load and update a local analysis snapshot"),
|
|
provider: providerSelectionOption,
|
|
}),
|
|
alias: {
|
|
caseSensitive: "case-sensitive",
|
|
},
|
|
run: ({ args, options }) =>
|
|
logCliCommand(logger, "trace", () =>
|
|
runDirectAnalysis(
|
|
args.path,
|
|
"trace_feature",
|
|
{
|
|
query: args.query,
|
|
case_sensitive: options.caseSensitive,
|
|
limit: options.limit,
|
|
},
|
|
directAnalysisOptions(logger, options.snapshot, options.provider),
|
|
),
|
|
),
|
|
});
|
|
};
|
|
|
|
const registerFunctionCommand = (cli: CliInstance, logger: Logger): void => {
|
|
cli.command(CLI_COMMANDS.function, {
|
|
description: "Analyze one complete function with evidence",
|
|
args: z.object({
|
|
path: z.string().describe("App or program path"),
|
|
address: z.string().describe("Procedure name or address"),
|
|
}),
|
|
options: z.object({
|
|
snapshot: z
|
|
.string()
|
|
.min(1)
|
|
.optional()
|
|
.describe("Load and update a local analysis snapshot"),
|
|
provider: providerSelectionOption,
|
|
}),
|
|
run: ({ args, options }) =>
|
|
logCliCommand(logger, "function", () =>
|
|
runDirectAnalysis(
|
|
args.path,
|
|
"analyze_function",
|
|
{ procedure: args.address },
|
|
directAnalysisOptions(logger, options.snapshot, options.provider),
|
|
),
|
|
),
|
|
});
|
|
};
|
|
|
|
const registerNativeApiCommand = (cli: CliInstance, logger: Logger): void => {
|
|
cli.command(CLI_COMMANDS.inspectNativeApi, {
|
|
description: "Reconstruct one native function API boundary with evidence",
|
|
args: z.object({
|
|
path: z.string().describe("App or program path"),
|
|
address: z.string().describe("Procedure name or address"),
|
|
}),
|
|
options: z.object({
|
|
snapshot: z
|
|
.string()
|
|
.min(1)
|
|
.optional()
|
|
.describe("Load and update a local analysis snapshot"),
|
|
provider: providerSelectionOption,
|
|
}),
|
|
run: ({ args, options }) =>
|
|
logCliCommand(logger, "inspect-native-api", () =>
|
|
runDirectAnalysis(
|
|
args.path,
|
|
"inspect_native_api",
|
|
{ procedure: args.address },
|
|
directAnalysisOptions(logger, options.snapshot, options.provider),
|
|
),
|
|
),
|
|
});
|
|
};
|
|
|
|
const registerInstructionsCommand = (
|
|
cli: CliInstance,
|
|
logger: Logger,
|
|
): void => {
|
|
cli.command(CLI_COMMANDS.instructions, {
|
|
description: "Read one bounded raw-instruction window without decompiling",
|
|
args: z.object({
|
|
path: z.string().describe("App or program path"),
|
|
address: z.string().describe("Procedure name or address"),
|
|
}),
|
|
options: z.object({
|
|
offset: z
|
|
.number()
|
|
.int()
|
|
.min(0)
|
|
.max(100_000)
|
|
.default(0)
|
|
.describe("Zero-based instruction offset"),
|
|
limit: z
|
|
.number()
|
|
.int()
|
|
.min(1)
|
|
.max(500)
|
|
.default(64)
|
|
.describe("Maximum raw instructions to return"),
|
|
snapshot: z
|
|
.string()
|
|
.min(1)
|
|
.optional()
|
|
.describe("Load and update a local analysis snapshot"),
|
|
provider: providerSelectionOption,
|
|
}),
|
|
run: ({ args, options }) =>
|
|
logCliCommand(logger, CLI_COMMANDS.instructions, () =>
|
|
runDirectAnalysis(
|
|
args.path,
|
|
"read_function_instructions",
|
|
{
|
|
procedure: args.address,
|
|
offset: options.offset,
|
|
limit: options.limit,
|
|
},
|
|
directAnalysisOptions(logger, options.snapshot, options.provider),
|
|
),
|
|
),
|
|
});
|
|
};
|
|
|
|
const registerSearchCommand = (cli: CliInstance, logger: Logger): void => {
|
|
cli.command(CLI_COMMANDS.search, {
|
|
description: "Search analyzed strings or procedure names",
|
|
args: z.object({
|
|
path: z.string().describe("App or program path"),
|
|
pattern: z.string().min(1).describe("Literal text or regex pattern"),
|
|
}),
|
|
options: z.object({
|
|
kind: z
|
|
.enum(["strings", "procedures"])
|
|
.default("strings")
|
|
.describe("Analyzed item kind to search"),
|
|
mode: z
|
|
.enum(["literal", "regex"])
|
|
.default("literal")
|
|
.describe("Interpret the pattern as literal text or a regex"),
|
|
caseSensitive: z
|
|
.boolean()
|
|
.default(false)
|
|
.describe("Match the pattern with exact letter case"),
|
|
offset: z
|
|
.number()
|
|
.int()
|
|
.min(0)
|
|
.default(0)
|
|
.describe("Zero-based matching-item offset"),
|
|
limit: z
|
|
.number()
|
|
.int()
|
|
.min(1)
|
|
.max(100)
|
|
.default(100)
|
|
.describe("Maximum matching items to return"),
|
|
snapshot: z
|
|
.string()
|
|
.min(1)
|
|
.optional()
|
|
.describe("Load and update a local analysis snapshot"),
|
|
provider: providerSelectionOption,
|
|
}),
|
|
alias: { caseSensitive: "case-sensitive" },
|
|
run: ({ args, options }) =>
|
|
logCliCommand(logger, "search", () =>
|
|
runDirectAnalysis(
|
|
args.path,
|
|
options.kind === "strings" ? "search_strings" : "search_procedures",
|
|
{
|
|
pattern: args.pattern,
|
|
mode: options.mode,
|
|
case_sensitive: options.caseSensitive,
|
|
offset: options.offset,
|
|
limit: options.limit,
|
|
},
|
|
directAnalysisOptions(logger, options.snapshot, options.provider),
|
|
),
|
|
),
|
|
});
|
|
};
|