Files
reverse-skills-max/vendor-new/js-reverse-automation--skill/js-reverse-automation/scripts/emit_adversarial_runtime_probe.py

457 lines
20 KiB
Python

#!/usr/bin/env python3
"""Emit a low-side-effect adversarial runtime probe.
The generated probe is deliberately split into observation and intervention:
observation records anti-debug/anti-hook/environment signals, while intervention
only applies the explicitly requested debugger-source filter. Every installed
patch is tracked and can be restored through ``uninstall()``.
Usage:
python3 scripts/emit_adversarial_runtime_probe.py \
--output generated/adversarial_runtime_probe.js
python3 scripts/emit_adversarial_runtime_probe.py \
--mode intervene --output generated/adversarial_runtime_probe.js
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
HOOK_REGISTRY = (Path(__file__).with_name("hook_registry.js")).read_text(encoding="utf-8")
TEMPLATE = r'''(() => {
"use strict";
const PROBE_ID = "adversarial-runtime";
const CONFIG = __CONFIG__;
const KEYWORDS = /pass(word)?|token|secret|authorization|cookie|session|credential/i;
const root = window;
__HOOK_REGISTRY__
const registry = getHookRegistry(root);
if (root.__JSRA_ADVERSARIAL__ && root.__JSRA_ADVERSARIAL__.probe_id === PROBE_ID) {
return root.__JSRA_ADVERSARIAL__;
}
const native = {
json: JSON.stringify.bind(JSON),
setTimeout: window.setTimeout.bind(window),
setInterval: window.setInterval.bind(window),
clearInterval: window.clearInterval.bind(window),
fetch: window.fetch,
eval: window.eval,
Function: window.Function,
defineProperty: Object.defineProperty,
getOwnPropertyDescriptor: Object.getOwnPropertyDescriptor,
getPrototypeOf: Object.getPrototypeOf,
toString: Function.prototype.toString,
};
const state = {
probe_id: PROBE_ID,
mode: CONFIG.mode,
installedAt: Date.now(),
events: [],
patches: [],
realms: [{ realm_id: "main", kind: "window", url: location.href }],
maxEvents: CONFIG.maxEvents,
dropped: 0,
suppressed: 0,
suppressedByPath: {},
health: { installed: true, lostPatches: 0, externalChanges: 0, reconcileErrors: 0, restored: false }
};
let counter = 0;
let healTimer = null;
let observer = null;
const active = new Map();
function id(prefix) {
counter += 1;
return `${prefix}-${Date.now().toString(36)}-${counter.toString(36)}`;
}
function preview(value, key = "") {
if (KEYWORDS.test(String(key))) return "<redacted>";
if (value === null) return "null";
if (value === undefined) return "undefined";
if (typeof value === "function") return "[function]";
if (value instanceof ArrayBuffer) return `[ArrayBuffer:${value.byteLength}]`;
if (ArrayBuffer.isView(value)) return `[${value.constructor.name}:${value.byteLength}]`;
if (value && typeof value === "object") return `[${value.constructor && value.constructor.name || "object"}]`;
const text = String(value);
return CONFIG.captureRaw ? text.slice(0, 160) : `${typeof value}:${text.length}`;
}
function emit(type, detail = {}) {
const event = {
event_id: id("adv"),
trace_id: detail.trace_id || id("trace"),
type,
path: detail.path || null,
function: detail.function || null,
input: preview(detail.input, detail.key),
output: preview(detail.output, detail.key),
metadata: detail.metadata || {},
timestamp: Date.now()
};
if (state.events.length >= state.maxEvents) {
state.events.shift();
state.dropped += 1;
}
state.events.push(event);
return event;
}
function descriptor(target, key) {
try {
let current = target;
while (current) {
const desc = native.getOwnPropertyDescriptor(current, key);
if (desc) return { owner: current, desc };
current = native.getPrototypeOf(current);
}
} catch (_) {}
return null;
}
function installPatch(name, target, key, factory, metadata = {}) {
if (!target || typeof target[key] !== "function") return null;
emit("patch.install", { path: name, metadata: { ...metadata, key } });
const layerId = `adversarial:${name}`;
const layer = registry.add(target, key, layerId, original => {
const replacement = factory(original);
try {
if (original && original.prototype && replacement && replacement.prototype)
replacement.prototype = original.prototype;
} catch (_) {}
return replacement;
});
if (!layer) return null;
const patch = { name, target, key, layerId, installed: true };
state.patches.push(patch);
active.set(name, patch);
patch.reconcile = () => {
const result = registry.reconcile(target, key);
if (result.changed) {
emit(result.status === "error" ? "patch.reconcile_error" : "patch.reconciled", {
path: name, metadata: { key, status: result.status, error: result.error || null }
});
if (result.status === "error") state.health.reconcileErrors += 1;
else state.health.externalChanges += 1;
}
return result;
};
return patch;
}
function wrap(target, key, type, path, options = {}) {
const sampleEvery = Math.max(1, Number(options.sampleEvery || 1));
const eventBudget = Number.isFinite(Number(options.eventBudget))
? Math.max(0, Number(options.eventBudget)) : Infinity;
const burst = Math.max(0, Number(options.burst || 0));
let invocationCount = 0;
function shouldRecord() {
invocationCount += 1;
const selected = invocationCount <= eventBudget &&
(invocationCount <= burst || invocationCount % sampleEvery === 0);
if (!selected) {
state.suppressed += 1;
state.suppressedByPath[path] = (state.suppressedByPath[path] || 0) + 1;
}
return selected;
}
return installPatch(path, target, key, original => function(...args) {
if (typeof options.skip === "function" && options.skip.call(this, args))
return original.apply(this, args);
const recordInvocation = shouldRecord();
const traceId = id("trace");
if (recordInvocation)
emit(`${type}.call`, { trace_id: traceId, path, function: path, input: args[0], metadata: { argc: args.length } });
let result;
try {
result = original.apply(this, args);
} catch (error) {
if (recordInvocation)
emit(`${type}.error`, { trace_id: traceId, path, function: path, metadata: { error: String(error) } });
throw error;
}
if (result && typeof result.then === "function") {
return result.then(value => {
if (recordInvocation)
emit(`${type}.return`, { trace_id: traceId, path, function: path, output: value });
return value;
}, error => {
if (recordInvocation)
emit(`${type}.reject`, { trace_id: traceId, path, function: path, metadata: { error: String(error) } });
throw error;
});
}
if (recordInvocation)
emit(`${type}.return`, { trace_id: traceId, path, function: path, output: result });
return result;
}, options);
}
function watchGetter(target, key, path) {
const found = descriptor(target, key);
if (!found || !found.desc || typeof found.desc.get !== "function" || found.desc.configurable === false)
return false;
const original = found.desc;
const replacement = { ...original, get() {
const value = original.get.call(this);
emit("environment.property_read", { path, key, output: value, metadata: { owner: path } });
return value;
}};
try {
native.defineProperty(found.owner, key, replacement);
state.patches.push({ name: `getter:${path}`, target: found.owner, key, descriptor: original, installed: true });
return true;
} catch (error) {
emit("patch.error", { path, metadata: { error: String(error) } });
return false;
}
}
function resolve(path) {
return String(path).split(".").reduce((value, key) => value == null ? null : value[key], root);
}
function watchProperties() {
const paths = CONFIG.properties || [
"navigator.webdriver", "navigator.userAgent", "navigator.platform",
"navigator.languages", "navigator.plugins", "screen.width", "screen.height",
"window.innerWidth", "window.innerHeight", "window.outerWidth", "window.outerHeight",
"document.cookie"
];
for (const path of paths) {
const parts = path.split(".");
const key = parts.pop();
const parent = resolve(parts.join("."));
if (parent && watchGetter(parent, key, path))
emit("environment.watch_installed", { path });
}
}
function observeDynamicCode() {
const stripDebugger = source => typeof source === "string"
? source.replace(/\bdebugger\s*;?/g, "") : source;
installPatch("window.eval", root, "eval", original => function(source) {
const next = CONFIG.mode === "intervene" ? stripDebugger(source) : source;
if (next !== source) emit("intervention.debugger_source", { path: "window.eval", input: source, metadata: { action: "strip" } });
emit("dynamic.eval.call", { path: "window.eval", input: next });
return original.call(this, next);
});
installPatch("window.Function", root, "Function", original => function(...args) {
const last = args.length - 1;
if (CONFIG.mode === "intervene" && last >= 0 && typeof args[last] === "string") {
const next = stripDebugger(args[last]);
if (next !== args[last]) emit("intervention.debugger_source", { path: "window.Function", input: args[last], metadata: { action: "strip" } });
args[last] = next;
}
emit("dynamic.function.call", { path: "window.Function", input: args[last], metadata: { argc: args.length } });
return new.target ? Reflect.construct(original, args, new.target) : original.apply(this, args);
});
root.Function.prototype = native.Function.prototype;
}
function observeNetwork() {
if (typeof root.fetch === "function") {
wrap(root, "fetch", "network.fetch", "window.fetch");
}
if (root.XMLHttpRequest) {
wrap(root.XMLHttpRequest.prototype, "open", "network.xhr_open", "XMLHttpRequest.open");
wrap(root.XMLHttpRequest.prototype, "setRequestHeader", "network.xhr_header", "XMLHttpRequest.setRequestHeader");
wrap(root.XMLHttpRequest.prototype, "send", "network.xhr_send", "XMLHttpRequest.send");
}
if (root.Response) {
["json", "text", "arrayBuffer", "blob"].forEach(key => wrap(root.Response.prototype, key, "network.response", `Response.${key}`));
}
if (root.WebSocket) wrap(root.WebSocket.prototype, "send", "network.websocket", "WebSocket.send", {
skip: function() {
try {
const url = String(this && this.url || "");
return (CONFIG.controlWebSocketUrls || []).some(fragment => url.includes(fragment));
} catch (_) { return false; }
}
});
}
function observeCrypto() {
if (!root.crypto || !root.crypto.subtle) return;
["encrypt", "decrypt", "sign", "verify", "digest", "importKey", "exportKey", "deriveKey", "deriveBits", "generateKey"].forEach(key => {
wrap(root.crypto.subtle, key, "crypto.subtle", `crypto.subtle.${key}`);
});
if (typeof root.crypto.getRandomValues === "function") wrap(root.crypto, "getRandomValues", "crypto.random", "crypto.getRandomValues");
}
function observeLoaders() {
if (root.Worker) {
installPatch("window.Worker", root, "Worker", original => function(...args) {
emit("realm.worker_create.call", { path: "window.Worker", input: args[0], metadata: { argc: args.length } });
return new original(...args);
});
}
if (root.SharedWorker) {
installPatch("window.SharedWorker", root, "SharedWorker", original => function(...args) {
emit("realm.shared_worker_create.call", { path: "window.SharedWorker", input: args[0], metadata: { argc: args.length } });
return new original(...args);
});
}
if (root.WebAssembly) {
["instantiate", "instantiateStreaming"].forEach(key => {
if (typeof root.WebAssembly[key] === "function") wrap(root.WebAssembly, key, "loader.wasm", `WebAssembly.${key}`);
});
}
if (root.document && root.MutationObserver) {
observer = new MutationObserver(records => records.forEach(record => record.addedNodes && Array.from(record.addedNodes).forEach(node => {
if (node && node.tagName === "SCRIPT") emit("loader.script", { path: "script.src", input: node.src, metadata: { async: !!node.async, type: node.type || "" } });
if (node && node.tagName === "IFRAME") emit("realm.iframe_create", { path: "iframe.src", input: node.src });
})));
observer.observe(root.document.documentElement || root.document, { childList: true, subtree: true });
}
}
function observeControlFlow() {
if (!CONFIG.controlFlow) return;
if (root.console) {
["clear", "table"].forEach(key => {
if (typeof root.console[key] === "function")
wrap(root.console, key, "control.console", `console.${key}`, { sampleEvery: 1, eventBudget: 64 });
});
}
if (root.history) {
["pushState", "replaceState", "back", "forward", "go"].forEach(key => {
if (typeof root.history[key] !== "function") return;
installPatch(`history.${key}`, root.history, key, original => function(...args) {
const blocked = CONFIG.mode === "intervene" && ["back", "forward", "go"].includes(key);
emit(blocked ? "intervention.history_blocked" : "control.history.call", {
path: `history.${key}`, input: args[0], metadata: { argc: args.length, blocked }
});
if (blocked) return undefined;
return original.apply(this, args);
});
});
}
if (root.Storage && root.Storage.prototype) {
["getItem", "setItem", "removeItem", "clear"].forEach(key => {
if (typeof root.Storage.prototype[key] !== "function") return;
wrap(root.Storage.prototype, key, "control.storage", `Storage.${key}`, {
sampleEvery: 1,
eventBudget: 128,
skip: function(args) {
return key !== "clear" && typeof args[0] === "string" && !KEYWORDS.test(args[0]);
}
});
});
}
if (typeof root.close === "function") {
installPatch("window.close", root, "close", original => function(...args) {
const blocked = CONFIG.mode === "intervene";
emit(blocked ? "intervention.close_blocked" : "control.close.call", {
path: "window.close", metadata: { argc: args.length, blocked }
});
if (blocked) return undefined;
return original.apply(this, args);
});
}
}
function observeIntegrity() {
const options = {
eventBudget: CONFIG.integrityEventBudget,
sampleEvery: CONFIG.integritySampleEvery,
burst: CONFIG.integrityBurst,
};
// Browser libraries call these methods at very high frequency. Capture
// an initial burst plus bounded samples so integrity evidence cannot evict
// crypto/network events from the ring buffer.
wrap(Function.prototype, "toString", "integrity.toString", "Function.prototype.toString", options);
wrap(Object, "getOwnPropertyDescriptor", "integrity.descriptor", "Object.getOwnPropertyDescriptor", options);
wrap(Object, "getPrototypeOf", "integrity.prototype", "Object.getPrototypeOf", options);
}
function install() {
watchProperties();
observeDynamicCode();
observeNetwork();
observeControlFlow();
observeCrypto();
observeLoaders();
observeIntegrity();
if (CONFIG.persistent) {
healTimer = native.setInterval(() => state.patches.slice().forEach(patch => {
try { if (patch.reconcile) patch.reconcile(); } catch (error) {
emit("patch.reconcile_error", { path: patch.name, metadata: { error: String(error) } });
state.health.reconcileErrors += 1;
}
}), CONFIG.healIntervalMs);
}
emit("probe.ready", { metadata: { mode: CONFIG.mode, persistent: CONFIG.persistent } });
}
function restore() {
if (healTimer) native.clearInterval(healTimer);
if (observer) observer.disconnect();
for (const patch of state.patches.slice().reverse()) {
try {
if (patch.descriptor) native.defineProperty(patch.target, patch.key, patch.descriptor);
else if (patch.layerId) registry.remove(patch.target, patch.key, patch.layerId);
} catch (_) {}
}
state.health.restored = true;
state.health.installed = false;
emit("probe.restored");
}
const api = {
probe_id: PROBE_ID,
state,
export() { return { probe_id: PROBE_ID, exportedAt: Date.now(), state: { ...state, events: state.events.slice(), patches: state.patches.map(p => ({ name: p.name, key: p.key, installed: p.installed })) } }; },
clear() { state.events.length = 0; state.dropped = 0; state.suppressed = 0; state.suppressedByPath = {}; },
uninstall: restore,
markTrace(label = "manual") { return id(label); }
};
root.__JSRA_ADVERSARIAL__ = api;
install();
return api;
})();
'''
def main() -> int:
parser = argparse.ArgumentParser(description="Emit JSRA adversarial runtime probe.")
parser.add_argument("--output", required=True)
parser.add_argument("--mode", choices=("observe", "intervene"), default="observe")
parser.add_argument("--max-events", type=int, default=5000)
parser.add_argument("--integrity-event-budget", type=int, default=256,
help="Maximum integrity-hook invocations recorded per method.")
parser.add_argument("--integrity-sample-every", type=int, default=64,
help="After the initial burst, record every Nth integrity invocation.")
parser.add_argument("--integrity-burst", type=int, default=16,
help="Record the first N integrity invocations in full.")
parser.add_argument("--heal-interval-ms", type=int, default=1000)
parser.add_argument("--properties", default="")
parser.add_argument("--control-websocket-url", action="append", default=[],
help="URL fragment for a JSRPC/control WebSocket to leave untouched.")
parser.add_argument("--no-control-flow", action="store_true",
help="Skip console/history/storage/window.close observation.")
parser.add_argument("--capture-raw", action="store_true")
parser.add_argument("--no-persistent", action="store_true")
args = parser.parse_args()
properties = [item.strip() for item in args.properties.split(",") if item.strip()]
if not properties:
properties = [
"navigator.webdriver", "navigator.userAgent", "navigator.platform",
"navigator.languages", "navigator.plugins", "screen.width", "screen.height",
"window.innerWidth", "window.innerHeight", "window.outerWidth", "window.outerHeight",
"document.cookie"
]
config = {
"mode": args.mode,
"maxEvents": max(100, args.max_events),
"integrityEventBudget": max(1, args.integrity_event_budget),
"integritySampleEvery": max(1, args.integrity_sample_every),
"integrityBurst": max(0, args.integrity_burst),
"healIntervalMs": max(250, args.heal_interval_ms),
"properties": properties,
"captureRaw": bool(args.capture_raw),
"persistent": not args.no_persistent,
"controlWebSocketUrls": args.control_websocket_url or ["127.0.0.1:12080", "localhost:12080"],
"controlFlow": not args.no_control_flow,
}
output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
content = TEMPLATE.replace("__CONFIG__", json.dumps(config, ensure_ascii=False))
content = content.replace("__HOOK_REGISTRY__", HOOK_REGISTRY)
output.write_text(content, encoding="utf-8")
print(json.dumps({"status": "ok", "output": str(output), "mode": args.mode}, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())