199 lines
6.5 KiB
JavaScript
199 lines
6.5 KiB
JavaScript
import { basename } from "node:path";
|
|
|
|
import { FridaInstrumentationService } from "../dist/application/frida/FridaInstrumentationService.js";
|
|
import { FridaInstrumentationManager } from "../dist/frida/FridaInstrumentationManager.js";
|
|
import { isValidFridaRemoteAddress } from "../dist/contracts/fridaRemoteAddress.js";
|
|
|
|
const options = parseArguments(process.argv.slice(2));
|
|
if (options.help) {
|
|
process.stdout.write(
|
|
"Usage: npm run verify:frida [-- --address HOST:PORT --pid PID [remote auth flags]]\n" +
|
|
"Without flags, verifies a local Node.js process created by this script.\n",
|
|
);
|
|
process.exit(0);
|
|
}
|
|
|
|
const manager = new FridaInstrumentationManager();
|
|
const service = new FridaInstrumentationService(manager);
|
|
const remote =
|
|
options.address === undefined
|
|
? undefined
|
|
: {
|
|
address: options.address,
|
|
...(options.token === undefined ? {} : { token: options.token }),
|
|
...(options.certificate === undefined
|
|
? {}
|
|
: { certificate: options.certificate }),
|
|
...(options.origin === undefined ? {} : { origin: options.origin }),
|
|
...(options.keepaliveInterval === undefined
|
|
? {}
|
|
: { keepaliveInterval: options.keepaliveInterval }),
|
|
};
|
|
|
|
try {
|
|
let targetProcess;
|
|
let input;
|
|
if (remote === undefined) {
|
|
input = {
|
|
mode: "spawn",
|
|
deviceId: "local",
|
|
program: process.execPath,
|
|
argv: ["-e", "setTimeout(() => {}, 1500)"],
|
|
source: {
|
|
sourceKind: "inline",
|
|
source: "send({ rea_frida_verification: 'ok', pid: Process.id });",
|
|
},
|
|
durationMs: 500,
|
|
};
|
|
targetProcess = { pid: undefined, name: basename(process.execPath) };
|
|
} else {
|
|
const inventory = await service.listProcesses({ remote });
|
|
if (!inventory.ok) throw new Error(inventory.error.message);
|
|
targetProcess = inventory.value.processes.find(
|
|
({ pid }) => pid === options.pid,
|
|
);
|
|
if (targetProcess === undefined)
|
|
throw new Error(
|
|
`Selected PID ${String(options.pid)} was not observed remotely`,
|
|
);
|
|
input = {
|
|
mode: "attach",
|
|
remote,
|
|
pid: options.pid,
|
|
source: {
|
|
sourceKind: "inline",
|
|
source: "send({ rea_frida_verification: 'ok', pid: Process.id });",
|
|
},
|
|
durationMs: 500,
|
|
};
|
|
}
|
|
|
|
const result = await service.instrument(input);
|
|
if (!result.ok) throw new Error(result.error.message);
|
|
const observed = result.value.evidence.normalized_result;
|
|
if (
|
|
typeof observed !== "object" ||
|
|
observed === null ||
|
|
Array.isArray(observed) ||
|
|
!Array.isArray(observed.messages)
|
|
)
|
|
throw new Error("Frida returned no message observation");
|
|
const messageObserved = observed.messages.some(
|
|
(message) =>
|
|
typeof message === "object" &&
|
|
message !== null &&
|
|
!Array.isArray(message) &&
|
|
message.type === "send" &&
|
|
typeof message.payload === "object" &&
|
|
message.payload !== null &&
|
|
!Array.isArray(message.payload) &&
|
|
message.payload.rea_frida_verification === "ok" &&
|
|
message.payload.pid === observed.pid,
|
|
);
|
|
if (!messageObserved)
|
|
throw new Error("Selected target did not return the Frida marker");
|
|
const report = {
|
|
ok: result.value.cleanupError === null,
|
|
provider: "frida",
|
|
host_platform: process.platform,
|
|
target_mode: remote === undefined ? "local_spawn" : "remote_attach",
|
|
target_pid: observed.pid,
|
|
target_name: targetProcess.name,
|
|
...(remote === undefined ? {} : { remote_target_observed: true }),
|
|
script_message_observed: true,
|
|
cleanup_error: result.value.cleanupError,
|
|
};
|
|
process.stdout.write(`${JSON.stringify(report)}\n`);
|
|
if (!report.ok) process.exitCode = 1;
|
|
} catch (cause) {
|
|
process.stderr.write(
|
|
`Frida real-provider verification failed: ${cause instanceof Error ? cause.message : String(cause)}\n`,
|
|
);
|
|
process.exitCode = 1;
|
|
} finally {
|
|
await manager.closeAll().catch(() => undefined);
|
|
}
|
|
|
|
function parseArguments(args) {
|
|
if (args.length === 1 && args[0] === "--help") return { help: true };
|
|
const values = parseOptionValues(args);
|
|
const address = values.get("--address");
|
|
const pidValue = values.get("--pid");
|
|
const pid = parseRemotePid(address, pidValue);
|
|
if (address === undefined) assertNoRemoteOptions(values);
|
|
const keepaliveInterval = parseKeepalive(values.get("--keepalive-interval"));
|
|
return {
|
|
help: false,
|
|
...(address === undefined ? {} : { address }),
|
|
...(pid === undefined ? {} : { pid }),
|
|
...(values.has("--token") ? { token: values.get("--token") } : {}),
|
|
...(values.has("--certificate")
|
|
? { certificate: values.get("--certificate") }
|
|
: {}),
|
|
...(values.has("--origin") ? { origin: values.get("--origin") } : {}),
|
|
...(keepaliveInterval === undefined ? {} : { keepaliveInterval }),
|
|
};
|
|
}
|
|
|
|
function parseOptionValues(args) {
|
|
const values = new Map();
|
|
const valueFlags = new Set([
|
|
"--address",
|
|
"--pid",
|
|
"--token",
|
|
"--certificate",
|
|
"--origin",
|
|
"--keepalive-interval",
|
|
]);
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const flag = args[index];
|
|
const value = args[index + 1];
|
|
if (
|
|
typeof flag !== "string" ||
|
|
!valueFlags.has(flag) ||
|
|
value === undefined ||
|
|
values.has(flag)
|
|
)
|
|
throw new Error(
|
|
"Expected no arguments for local-fixture verification, or --address HOST:PORT --pid PID and optional remote auth flags",
|
|
);
|
|
values.set(flag, value);
|
|
index += 1;
|
|
}
|
|
return values;
|
|
}
|
|
|
|
function parseRemotePid(address, pidValue) {
|
|
if ((address === undefined) !== (pidValue === undefined))
|
|
throw new Error("Remote verification requires both --address and --pid");
|
|
if (address !== undefined && !isValidFridaRemoteAddress(address))
|
|
throw new Error(
|
|
"--address must be a host[:port] without credentials, path, query, or fragment",
|
|
);
|
|
const pid = pidValue === undefined ? undefined : Number(pidValue);
|
|
if (pid !== undefined && (!Number.isSafeInteger(pid) || pid <= 0))
|
|
throw new Error("--pid must be a positive process identifier");
|
|
return pid;
|
|
}
|
|
|
|
function assertNoRemoteOptions(values) {
|
|
for (const flag of [
|
|
"--token",
|
|
"--certificate",
|
|
"--origin",
|
|
"--keepalive-interval",
|
|
])
|
|
if (values.has(flag))
|
|
throw new Error("Remote connection options require --address and --pid");
|
|
}
|
|
|
|
function parseKeepalive(value) {
|
|
const interval = value === undefined ? undefined : Number(value);
|
|
if (
|
|
interval !== undefined &&
|
|
(!Number.isSafeInteger(interval) || interval <= 0)
|
|
)
|
|
throw new Error("--keepalive-interval must be a positive integer");
|
|
return interval;
|
|
}
|