105 lines
4.8 KiB
JavaScript
105 lines
4.8 KiB
JavaScript
// Frida host (Node ESM) for the pdb-capture agent.
|
|
// - Attaches the agent (rbx_pdb_capture.js) to rekordbox processes.
|
|
// - Streams ALL their output to THIS console (agent lines are [pid]-tagged).
|
|
// - STAYS ALIVE via the Node event loop -- no frida-CLI REPL/EOF problem.
|
|
// - Uses CHILD-GATING (Windows-supported; spawn-gating is not) so children of a
|
|
// gated process are instrumented AT BIRTH. This is how we reach the
|
|
// rekordboxAgent that writes export.pdb: it's CIG-protected and refuses
|
|
// post-hoc injection, but as a freshly-spawned child it can be hooked.
|
|
// - Clears the captures/ folder on start.
|
|
//
|
|
// Setup (Windows, repo root, once): npm i frida
|
|
// Run:
|
|
// node tools\capture.mjs --spawn (RECOMMENDED: launch rekordbox under the
|
|
// gate so every child incl. the DB agent is
|
|
// hooked at birth. Close rekordbox first.)
|
|
// node tools\capture.mjs (attach to running procs + gate their
|
|
// future children; restart rekordbox to
|
|
// respawn the agent as a gated child.)
|
|
|
|
import * as fridaNs from "frida";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const frida = fridaNs.default ?? fridaNs;
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
const REKORDBOX = "C:\\Program Files\\rekordbox\\rekordbox 7.0.5\\rekordbox.exe";
|
|
const AGENT = fs.readFileSync(path.join(__dirname, "rbx_pdb_capture.js"), "utf8");
|
|
const CAP = path.join(__dirname, "..", "captures");
|
|
const SPAWN = process.argv.includes("--spawn");
|
|
|
|
function clearCaptures() {
|
|
fs.mkdirSync(CAP, { recursive: true });
|
|
let n = 0;
|
|
for (const f of fs.readdirSync(CAP)) {
|
|
try { fs.rmSync(path.join(CAP, f), { force: true, recursive: true }); n++; } catch (e) {}
|
|
}
|
|
console.log(`[host] cleared ${n} file(s) from ${CAP}`);
|
|
}
|
|
|
|
function isRbx(name) { return /rekordbox/i.test(name || ""); }
|
|
|
|
// Attach, load the agent, and gate this process's future children too.
|
|
async function instrument(device, pid, label) {
|
|
try {
|
|
const session = await device.attach(pid);
|
|
session.detached.connect(reason => console.log(`[host] detached ${label} (${pid}): ${reason}`));
|
|
const script = await session.createScript(AGENT);
|
|
script.logHandler = (level, text) => console.log(text);
|
|
script.message.connect(msg => {
|
|
if (msg.type === "error")
|
|
console.error(`[host] ${label} (${pid}) script error:`, msg.stack || msg.description || msg);
|
|
});
|
|
await script.load();
|
|
try { await session.enableChildGating(); } catch (e) { /* not always available */ }
|
|
console.log(`[host] instrumented ${label} (${pid})`);
|
|
return session;
|
|
} catch (e) {
|
|
console.error(`[host] FAILED to instrument ${label} (${pid}): ${e.message}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
clearCaptures();
|
|
const device = await frida.getLocalDevice();
|
|
|
|
// Children of any gated process are suspended at birth -> instrument, resume.
|
|
device.childAdded.connect(async child => {
|
|
const name = child.identifier || child.path || String(child.pid);
|
|
try {
|
|
console.log(`[host] child spawned: ${name} (${child.pid})`);
|
|
if (isRbx(name)) await instrument(device, child.pid, "child:" + name);
|
|
} catch (e) {
|
|
console.error(`[host] child handler error (${child.pid}): ${e.message}`);
|
|
} finally {
|
|
try { await device.resume(child.pid); } catch (_) {}
|
|
}
|
|
});
|
|
|
|
if (SPAWN) {
|
|
console.log(`[host] spawning ${REKORDBOX}`);
|
|
const pid = await device.spawn([REKORDBOX]);
|
|
await instrument(device, pid, "main"); // enables child gating on main
|
|
await device.resume(pid);
|
|
console.log(`[host] resumed main rekordbox (${pid}); children caught at birth`);
|
|
} else {
|
|
for (const p of await device.enumerateProcesses()) {
|
|
if (isRbx(p.name)) await instrument(device, p.pid, p.name);
|
|
}
|
|
console.log("[host] attached to running rekordbox processes (+child gating).");
|
|
console.log("[host] >>> the export.pdb writer (rekordboxAgent) refuses live injection.");
|
|
console.log("[host] >>> RESTART rekordbox -- the new agent spawns as a GATED CHILD and");
|
|
console.log("[host] >>> gets hooked at birth. Then Export and watch for [SAVED] ...export.pdb");
|
|
console.log("[host] >>> (or re-run with --spawn to launch rekordbox under the gate.)");
|
|
}
|
|
|
|
console.log("[host] ready. Ctrl-C to stop.");
|
|
process.stdin.resume();
|
|
process.on("SIGINT", () => { console.log("\n[host] stopping."); process.exit(0); });
|
|
}
|
|
|
|
main().catch(e => { console.error("[host] fatal:", e); process.exit(1); });
|