reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Environment patcher for running obfuscated JS in Node.js VM sandbox.
|
||||
|
||||
Diagnoses missing browser APIs and generates patches to make the JS runnable.
|
||||
|
||||
Usage:
|
||||
python3 scripts/env_patcher.py --input bundle.js --output artifacts/env_report.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from common import dump_json, load_json
|
||||
|
||||
|
||||
DIAGNOSE_SCRIPT = r'''
|
||||
const vm = require('vm');
|
||||
const fs = require('fs');
|
||||
|
||||
const input = process.argv[2];
|
||||
const code = fs.readFileSync(input, 'utf8');
|
||||
|
||||
// Create sandbox with basic browser APIs
|
||||
const sandbox = {
|
||||
window: {}, self: {}, global: {}, globalThis: {},
|
||||
console: { log: () => {}, warn: () => {}, error: () => {} },
|
||||
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
||||
setInterval: (fn, ms) => setInterval(fn, ms),
|
||||
clearTimeout: (id) => clearTimeout(id),
|
||||
clearInterval: (id) => clearInterval(id),
|
||||
atob: (s) => Buffer.from(s, 'base64').toString('binary'),
|
||||
btoa: (s) => Buffer.from(s, 'binary').toString('base64'),
|
||||
TextEncoder: TextEncoder,
|
||||
TextDecoder: TextDecoder,
|
||||
URL: URL,
|
||||
URLSearchParams: URLSearchParams,
|
||||
};
|
||||
|
||||
// Make window/self/global/globalThis point to sandbox
|
||||
sandbox.window = sandbox;
|
||||
sandbox.self = sandbox;
|
||||
sandbox.global = sandbox;
|
||||
sandbox.globalThis = sandbox;
|
||||
|
||||
// Proxy to track undefined access
|
||||
const undefinedPaths = new Set();
|
||||
const proxy = new Proxy(sandbox, {
|
||||
get(target, prop) {
|
||||
if (prop === Symbol.unscopables) return undefined;
|
||||
if (prop in target) return target[prop];
|
||||
if (typeof prop === 'string' && !prop.startsWith('_')) {
|
||||
undefinedPaths.add(prop);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
set(target, prop, value) {
|
||||
target[prop] = value;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Run in VM
|
||||
const context = vm.createContext(proxy);
|
||||
try {
|
||||
vm.runInContext(code, context, { timeout: 5000 });
|
||||
console.log(JSON.stringify({
|
||||
success: true,
|
||||
error: null,
|
||||
undefinedPaths: Array.from(undefinedPaths).sort(),
|
||||
stats: { events: undefinedPaths.size }
|
||||
}));
|
||||
} catch (error) {
|
||||
console.log(JSON.stringify({
|
||||
success: false,
|
||||
error: error.message,
|
||||
undefinedPaths: Array.from(undefinedPaths).sort(),
|
||||
stats: { events: undefinedPaths.size }
|
||||
}));
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
def diagnose(js_path: str) -> dict:
|
||||
"""Run diagnosis on a JS file to find missing browser APIs."""
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
|
||||
f.write(DIAGNOSE_SCRIPT)
|
||||
diag_script = f.name
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['node', diag_script, js_path],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return json.loads(result.stdout)
|
||||
return {"success": False, "error": result.stderr, "undefinedPaths": []}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e), "undefinedPaths": []}
|
||||
finally:
|
||||
Path(diag_script).unlink(missing_ok=True)
|
||||
|
||||
|
||||
# Mapping from undefined paths to required modules
|
||||
MODULE_MAP = {
|
||||
'navigator': 'bom/navigator',
|
||||
'location': 'bom/location',
|
||||
'screen': 'bom/screen',
|
||||
'localStorage': 'bom/storage',
|
||||
'sessionStorage': 'bom/storage',
|
||||
'crypto': 'bom/crypto',
|
||||
'performance': 'bom/performance',
|
||||
'history': 'bom/history',
|
||||
'document': 'dom/document',
|
||||
'Element': 'dom/elements',
|
||||
'HTMLElement': 'dom/elements',
|
||||
'Event': 'dom/event',
|
||||
'fetch': 'webapi/fetch',
|
||||
'XMLHttpRequest': 'webapi/xhr',
|
||||
'Blob': 'webapi/blob',
|
||||
'FormData': 'webapi/blob',
|
||||
'File': 'webapi/blob',
|
||||
'WebSocket': 'webapi/network',
|
||||
'TextEncoder': 'encoding/textencoder',
|
||||
'TextDecoder': 'encoding/textencoder',
|
||||
'atob': 'encoding/atob',
|
||||
'btoa': 'encoding/atob',
|
||||
}
|
||||
|
||||
|
||||
def select_modules(undefined_paths: list[str]) -> list[str]:
|
||||
"""Select required modules based on undefined paths."""
|
||||
modules = set()
|
||||
for path in undefined_paths:
|
||||
for prefix, module in MODULE_MAP.items():
|
||||
if path.startswith(prefix) or path == prefix:
|
||||
modules.add(module)
|
||||
return sorted(modules)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Environment patcher for JS sandbox.")
|
||||
parser.add_argument("--input", required=True, help="Path to JS file.")
|
||||
parser.add_argument("--output", required=True, help="Path to output report.")
|
||||
args = parser.parse_args()
|
||||
|
||||
report = diagnose(args.input)
|
||||
if report.get("undefinedPaths"):
|
||||
report["selectedModules"] = select_modules(report["undefinedPaths"])
|
||||
|
||||
dump_json(args.output, report)
|
||||
print(json.dumps({
|
||||
"success": report.get("success", False),
|
||||
"undefinedPaths": len(report.get("undefinedPaths", [])),
|
||||
"selectedModules": len(report.get("selectedModules", [])),
|
||||
"output": args.output
|
||||
}, ensure_ascii=False))
|
||||
return 0 if report.get("success") else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user