This commit is contained in:
Nathan Rashleigh committed 2026-06-08 23:22:16 +10:00
1 parent 6a6553077b
commit f71a2a3512
23 files changed
+7773 -68

No files matched your search

+104
View File
@@ -0,0 +1,104 @@
// 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); });