利用crypto, jsdom,vm等补环境模块,进行逆向获取加密参数
补环境头部代码如:
const vm = require("node:vm");
const nodeCrypto = require("node:crypto");
const { TextEncoder, TextDecoder } = require("node:util");
function optionalRequire(name) {
try {
return require(name);
} catch (_) {
return null;
}
}
const jsdomPkg = optionalRequire("jsdom");
const undiciPkg = optionalRequire("undici");
const xhr2Pkg = optionalRequire("xhr2");
const wsPkg = optionalRequire("ws");
const fakeIndexedDbPkg = optionalRequire("fake-indexeddb");
function nativeLike(fn, name) {
try {
Object.defineProperty(fn, "name", { value: name, configurable: true });
} catch () {}
try {
Object.defineProperty(fn, "toString", {
value: () => function ${name || fn.name || ""}() { [native code] },
configurable: true,
});
} catch () {}
return fn;
}
function defineValue(target, key, value, options = {}) {
const descriptor = Object.getOwnPropertyDescriptor(target, key);
if (descriptor && descriptor.configurable === false) {
if ("value" in descriptor && descriptor.value !== undefined) return descriptor.value;
try {
if (target[key] !== undefined) return target[key];
} catch () {}
return value;
}
try {
Object.defineProperty(target, key, {
value,
writable: options.writable !== false,
enumerable: options.enumerable === true,
configurable: options.configurable !== false,
});
} catch () {
try {
target[key] = value;
} catch (_) {}
}
return target[key] || value;
}
function defineGetter(target, key, getter, options = {}) {
const descriptor = Object.getOwnPropertyDescriptor(target, key);
if (descriptor && descriptor.configurable === false) return;
try {
Object.defineProperty(target, key, {
get: nativeLike(getter, get ${String(key)}),
enumerable: options.enumerable === true,
configurable: options.configurable !== false,
});
} catch (_) {}
}
function createStorage() {
const map = new Map();
const storage = {
get length() {
return map.size;
},
key(index) {
return Array.from(map.keys())[index] || null;
},
getItem(key) {
key = String(key);
return map.has(key) ? map.get(key) : null;
},
setItem(key, value) {
map.set(String(key), String(value));
},
removeItem(key) {
map.delete(String(key));
},
clear() {
map.clear();
},
_dump() {
return Object.fromEntries(map.entries());
},
};
for (const name of ["key", "getItem", "setItem", "removeItem", "clear"]) {
nativeLike(storage[name], name);
}
return storage;
}
function createSimpleEventTarget() {
const listeners = new Map();
return {
addEventListener(type, listener) {
if (!listeners.has(type)) listeners.set(type, new Set());
listeners.get(type).add(listener);
},
removeEventListener(type, listener) {
if (listeners.has(type)) listeners.get(type).delete(listener);
},
dispatchEvent(event) {
const type = event && event.type;
if (!type || !listeners.has(type)) return true;
for (const listener of listeners.get(type)) {
if (typeof listener === "function") listener.call(this, event);
else if (listener && typeof listener.handleEvent === "function") listener.handleEvent(event);
}
return !event.defaultPrevented;
},
};
}
function createLocation(url) {
const u = new URL(url || "https://example.com/");
const location = {};
function syncFromUrl(nextUrl) {
const next = new URL(nextUrl, u.href);
for (const key of [
"href",
"origin",
"protocol",
"host",
"hostname",
"port",
"pathname",
"search",
"hash",
]) {
defineGetter(location, key, () => next[key], { enumerable: true });
}
}
syncFromUrl(u.href);
defineValue(location, "assign", nativeLike((next) => syncFromUrl(next), "assign"));
defineValue(location, "replace", nativeLike((next) => syncFromUrl(next), "replace"));
defineValue(location, "reload", nativeLike(() => {}, "reload"));
defineValue(location, "toString", nativeLike(() => location.href, "toString"));
return location;
}
function createNavigator(profile = {}) {
const nav = {};
const values = {
userAgent:
profile.userAgent ||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
appVersion: profile.appVersion || "5.0 (Windows NT 10.0; Win64; x64)",
platform: profile.platform || "Win32",
vendor: profile.vendor || "Google Inc.",
language: profile.language || "en-US",
languages: profile.languages || ["en-US", "en"],
hardwareConcurrency: profile.hardwareConcurrency || 8,
deviceMemory: profile.deviceMemory || 8,
maxTouchPoints: profile.maxTouchPoints || 0,
webdriver: profile.webdriver,
cookieEnabled: profile.cookieEnabled !== false,
onLine: profile.onLine !== false,
doNotTrack: profile.doNotTrack || null,
product: "Gecko",
productSub: "20030107",
appName: "Netscape",
appCodeName: "Mozilla",
};
for (const [key, value] of Object.entries(values)) {
defineGetter(nav, key, () => value, { enumerable: true });
}
defineValue(
nav,
"permissions",
{
query: nativeLike(async () => ({ state: "prompt", onchange: null }), "query"),
},
{ enumerable: true },
);
defineValue(nav, "plugins", profile.plugins || [], { enumerable: true });
defineValue(nav, "mimeTypes", profile.mimeTypes || [], { enumerable: true });
return nav;
}
function createScreen(profile = {}) {
const screen = {};
const values = {
width: profile.width || 1920,
height: profile.height || 1080,
availWidth: profile.availWidth || profile.width || 1920,
availHeight: profile.availHeight || profile.height || 1040,
colorDepth: profile.colorDepth || 24,
pixelDepth: profile.pixelDepth || 24,
orientation: profile.orientation || { angle: 0, type: "landscape-primary" },
};
for (const [key, value] of Object.entries(values)) {
defineGetter(screen, key, () => value, { enumerable: true });
}
return screen;
}
function createDocument(location) {
const eventTarget = createSimpleEventTarget();
const document = {
nodeType: 9,
readyState: "complete",
visibilityState: "visible",
hidden: false,
referrer: "",
URL: location.href,
documentURI: location.href,
domain: new URL(location.href).hostname,
title: "",
characterSet: "UTF-8",
charset: "UTF-8",
compatMode: "CSS1Compat",
body: null,
head: null,
documentElement: null,
createElement(tagName) {
return createElement(tagName, document);
},
createTextNode(text) {
return { nodeType: 3, textContent: String(text), data: String(text) };
},
getElementById() {
return null;
},
getElementsByTagName() {
return [];
},
querySelector() {
return null;
},
querySelectorAll() {
return [];
},
write() {},
writeln() {},
...eventTarget,
};
document.documentElement = createElement("html", document);
document.head = createElement("head", document);
document.body = createElement("body", document);
let cookieValue = "";
Object.defineProperty(document, "cookie", {
get: nativeLike(() => cookieValue, "get cookie"),
set: nativeLike((value) => {
const pair = String(value).split(";")[0];
const eq = pair.indexOf("=");
if (eq === -1) return;
const name = pair.slice(0, eq).trim();
const next = pair.trim();
const parts = cookieValue ? cookieValue.split(/;\s*/) : [];
const kept = parts.filter((part) => part.split("=")[0] !== name);
kept.push(next);
cookieValue = kept.join("; ");
}, "set cookie"),
configurable: true,
});
return document;
}
function createElement(tagName, ownerDocument) {
const eventTarget = createSimpleEventTarget();
const attrs = new Map();
const upper = String(tagName || "").toUpperCase();
const element = {
nodeType: 1,
tagName: upper,
nodeName: upper,
ownerDocument,
style: {},
children: [],
childNodes: [],
parentNode: null,
innerHTML: "",
outerHTML: "",
textContent: "",
className: "",
id: "",
appendChild(child) {
child.parentNode = element;
element.children.push(child);
element.childNodes.push(child);
return child;
},
removeChild(child) {
element.children = element.children.filter((item) => item !== child);
element.childNodes = element.childNodes.filter((item) => item !== child);
child.parentNode = null;
return child;
},
setAttribute(name, value) {
attrs.set(String(name), String(value));
if (name === "id") element.id = String(value);
if (name === "class") element.className = String(value);
},
getAttribute(name) {
return attrs.has(String(name)) ? attrs.get(String(name)) : null;
},
removeAttribute(name) {
attrs.delete(String(name));
},
hasAttribute(name) {
return attrs.has(String(name));
},
getBoundingClientRect() {
return { x: 0, y: 0, width: 0, height: 0, top: 0, right: 0, bottom: 0, left: 0 };
},
querySelector() {
return null;
},
querySelectorAll() {
return [];
},
...eventTarget,
};
if (upper === "CANVAS") {
element.width = 300;
element.height = 150;
element.getContext = nativeLike((type) => createCanvasContext(type), "getContext");
element.toDataURL = nativeLike(() => "data:image/png;base64,", "toDataURL");
}
return element;
}
function createCanvasContext(type) {
if (type === "webgl" || type === "experimental-webgl" || type === "webgl2") {
return createWebGLContext();
}
return {
fillRect() {},
clearRect() {},
getImageData() {
return { data: new Uint8ClampedArray(4), width: 1, height: 1 };
},
putImageData() {},
createImageData() {
return { data: new Uint8ClampedArray(4), width: 1, height: 1 };
},
measureText(text) {
return { width: String(text).length * 8 };
},
fillText() {},
strokeText() {},
drawImage() {},
beginPath() {},
moveTo() {},
lineTo() {},
stroke() {},
closePath() {},
arc() {},
};
}
function createWebGLContext() {
return {
getParameter(parameter) {
const map = new Map([
[0x1f00, "WebKit"],
[0x1f01, "WebKit WebGL"],
[0x1f02, "WebGL 1.0"],
[0x9245, "Google Inc. (Intel)"],
[0x9246, "ANGLE (Intel, Intel(R) UHD Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)"],
]);
return map.has(parameter) ? map.get(parameter) : 0;
},
getExtension(name) {
if (name === "WEBGL_debug_renderer_info") {
return {
UNMASKED_VENDOR_WEBGL: 0x9245,
UNMASKED_RENDERER_WEBGL: 0x9246,
};
}
return null;
},
};
}
function installEncoding(target) {
defineValue(target, "TextEncoder", target.TextEncoder || TextEncoder);
defineValue(target, "TextDecoder", target.TextDecoder || TextDecoder);
defineValue(
target,
"atob",
target.atob ||
nativeLike((input) => Buffer.from(String(input), "base64").toString("binary"), "atob"),
);
defineValue(
target,
"btoa",
target.btoa ||
nativeLike((input) => Buffer.from(String(input), "binary").toString("base64"), "btoa"),
);
}
function installCrypto(target) {
const webcrypto = nodeCrypto.webcrypto;
defineValue(target, "crypto", target.crypto || webcrypto);
if (!target.crypto.getRandomValues) {
target.crypto.getRandomValues = nativeLike((arr) => webcrypto.getRandomValues(arr), "getRandomValues");
}
if (!target.Crypto) defineValue(target, "Crypto", function Crypto() {});
}
function installNetwork(target, options = {}) {
if (undiciPkg) {
defineValue(target, "fetch", target.fetch || undiciPkg.fetch);
defineValue(target, "Headers", target.Headers || undiciPkg.Headers);
defineValue(target, "Request", target.Request || undiciPkg.Request);
defineValue(target, "Response", target.Response || undiciPkg.Response);
defineValue(target, "FormData", target.FormData || undiciPkg.FormData);
defineValue(target, "File", target.File || undiciPkg.File);
defineValue(target, "WebSocket", target.WebSocket || undiciPkg.WebSocket);
} else {
defineValue(target, "fetch", target.fetch || fetch);
defineValue(target, "Headers", target.Headers || Headers);
defineValue(target, "Request", target.Request || Request);
defineValue(target, "Response", target.Response || Response);
defineValue(target, "FormData", target.FormData || FormData);
defineValue(target, "Blob", target.Blob || Blob);
}
if (xhr2Pkg && !target.XMLHttpRequest) defineValue(target, "XMLHttpRequest", xhr2Pkg);
if (wsPkg && !target.WebSocket) defineValue(target, "WebSocket", wsPkg);
if (options.blockNetwork) {
defineValue(
target,
"fetch",
nativeLike(async (url) => {
throw new Error(Network blocked by env: ${url});
}, "fetch"),
);
}
}
function installTimers(target) {
defineValue(target, "setTimeout", target.setTimeout || setTimeout);
defineValue(target, "clearTimeout", target.clearTimeout || clearTimeout);
defineValue(target, "setInterval", target.setInterval || setInterval);
defineValue(target, "clearInterval", target.clearInterval || clearInterval);
let rafId = 0;
const rafMap = new Map();
defineValue(
target,
"requestAnimationFrame",
target.requestAnimationFrame ||
nativeLike((cb) => {
const id = ++rafId;
const timer = setTimeout(() => cb(Date.now()), 16);
rafMap.set(id, timer);
return id;
}, "requestAnimationFrame"),
);
defineValue(
target,
"cancelAnimationFrame",
target.cancelAnimationFrame ||
nativeLike((id) => {
clearTimeout(rafMap.get(id));
rafMap.delete(id);
}, "cancelAnimationFrame"),
);
}
function installCoreGlobals(target, options = {}) {
const url = options.url || "https://example.com/";
const profile = options.profile || {};
const location = target.location || createLocation(url);
const navigator = target.navigator || createNavigator(profile.navigator || profile);
const screen = target.screen || createScreen(profile.screen || {});
const localStorage = target.localStorage || createStorage();
const sessionStorage = target.sessionStorage || createStorage();
const document = target.document || createDocument(location);
defineValue(target, "window", target.window || target, { enumerable: true });
defineValue(target, "self", target.self || target, { enumerable: true });
defineValue(target, "top", target.top || target, { enumerable: true });
defineValue(target, "parent", target.parent || target, { enumerable: true });
defineValue(target, "globalThis", target.globalThis || target, { enumerable: true });
defineValue(target, "location", location, { enumerable: true });
defineValue(target, "navigator", navigator, { enumerable: true });
defineValue(target, "screen", screen, { enumerable: true });
defineValue(target, "localStorage", localStorage, { enumerable: true });
defineValue(target, "sessionStorage", sessionStorage, { enumerable: true });
defineValue(target, "document", document, { enumerable: true });
defineValue(target, "history", target.history || createHistory(location), { enumerable: true });
defineValue(target, "URL", target.URL || URL);
defineValue(target, "URLSearchParams", target.URLSearchParams || URLSearchParams);
defineValue(target, "performance", target.performance || performance);
defineValue(target, "Event", target.Event || class Event {
constructor(type, init = {}) {
this.type = String(type);
this.bubbles = Boolean(init.bubbles);
this.cancelable = Boolean(init.cancelable);
this.defaultPrevented = false;
}
preventDefault() {
if (this.cancelable) this.defaultPrevented = true;
}
});
defineValue(target, "CustomEvent", target.CustomEvent || class CustomEvent extends target.Event {
constructor(type, init = {}) {
super(type, init);
this.detail = init.detail;
}
});
defineValue(target, "EventTarget", target.EventTarget || class EventTarget {
constructor() {
Object.assign(this, createSimpleEventTarget());
}
});
if (fakeIndexedDbPkg) {
defineValue(target, "indexedDB", target.indexedDB || fakeIndexedDbPkg.indexedDB);
defineValue(target, "IDBKeyRange", target.IDBKeyRange || fakeIndexedDbPkg.IDBKeyRange);
}
}
function createHistory(location) {
const history = {
length: 1,
state: null,
back() {},
forward() {},
go() {},
pushState(state, , url) {
history.state = state;
if (url) location.assign(url);
},
replaceState(state, , url) {
history.state = state;
if (url) location.replace(url);
},
};
for (const key of ["back", "forward", "go", "pushState", "replaceState"]) {
nativeLike(history[key], key);
}
return history;
}
function traceMissing(name, obj, logs) {
return new Proxy(obj, {
get(target, prop, receiver) {
if (typeof prop !== "symbol" && !(prop in target)) {
const path = ${name}.${String(prop)};
logs.push(path);
if (logs.length <= 200) console.warn("[env missing]", path);
}
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
return Reflect.set(target, prop, value, receiver);
},
});
}
function installHooks(target, hooks = {}) {
const records = [];
function wrap(obj, key, label) {
if (!obj || typeof obj[key] !== "function") return;
const original = obj[key];
obj[key] = function hookedFunction(...args) {
const item = { label, args, time: Date.now() };
records.push(item);
if (hooks.log !== false) console.log("[hook]", label, args);
const result = original.apply(this, args);
item.result = result;
return result;
};
}
wrap(target, "fetch", "fetch");
if (target.XMLHttpRequest && target.XMLHttpRequest.prototype) {
wrap(target.XMLHttpRequest.prototype, "open", "XMLHttpRequest.open");
wrap(target.XMLHttpRequest.prototype, "send", "XMLHttpRequest.send");
}
if (target.crypto) {
wrap(target.crypto, "getRandomValues", "crypto.getRandomValues");
}
if (target.crypto && target.crypto.subtle) {
wrap(target.crypto.subtle, "digest", "crypto.subtle.digest");
wrap(target.crypto.subtle, "encrypt", "crypto.subtle.encrypt");
wrap(target.crypto.subtle, "decrypt", "crypto.subtle.decrypt");
wrap(target.crypto.subtle, "sign", "crypto.subtle.sign");
}
if (target.localStorage) {
wrap(target.localStorage, "getItem", "localStorage.getItem");
wrap(target.localStorage, "setItem", "localStorage.setItem");
}
return records;
}
function createJsdomEnv(options = {}) {
const { JSDOM, CookieJar, VirtualConsole } = jsdomPkg;
const cookieJar = new CookieJar();
const virtualConsole = new VirtualConsole();
if (options.console !== false) virtualConsole.sendTo(console);
const jsdomOptions = {
url: options.url || "https://example.com/",
runScripts: "outside-only",
pretendToBeVisual: true,
resources: options.resources || "usable",
storageQuota: options.storageQuota || 10_000_000,
cookieJar,
virtualConsole,
beforeParse(window) {
installCoreGlobals(window, options);
installEncoding(window);
installCrypto(window);
installNetwork(window, options);
installTimers(window);
if (typeof options.beforeParse === "function") options.beforeParse(window);
},
};
if (options.referrer) jsdomOptions.referrer = options.referrer;
const dom = new JSDOM(
options.html || "<!doctype html><html><head></head><body></body></html>",
jsdomOptions,
);
const window = dom.window;
installCoreGlobals(window, options);
installEncoding(window);
installCrypto(window);
installNetwork(window, options);
installTimers(window);
const missing = [];
if (options.traceMissing) {
window.navigator = traceMissing("navigator", window.navigator, missing);
window.document = traceMissing("document", window.document, missing);
window.location = traceMissing("location", window.location, missing);
}
const hooks = options.hooks ? installHooks(window, options.hooks) : [];
return {
kind: "jsdom",
window,
global: window,
document: window.document,
cookieJar,
dom,
context: dom.getInternalVMContext(),
missing,
hooks,
};
}
function createMinimalEnv(options = {}) {
const global = {};
installCoreGlobals(global, options);
installEncoding(global);
installCrypto(global);
installNetwork(global, options);
installTimers(global);
const missing = [];
let runtimeGlobal = global;
if (options.traceMissing) {
runtimeGlobal = traceMissing("window", global, missing);
runtimeGlobal.window = runtimeGlobal;
runtimeGlobal.self = runtimeGlobal;
runtimeGlobal.globalThis = runtimeGlobal;
}
const context = vm.createContext(runtimeGlobal, {
name: options.contextName || "browser-env",
origin: options.url || "https://example.com/",
});
const hooks = options.hooks ? installHooks(runtimeGlobal, options.hooks) : [];
return {
kind: "minimal",
window: runtimeGlobal,
global: runtimeGlobal,
document: runtimeGlobal.document,
context,
missing,
hooks,
};
}
function createBrowserEnv(options = {}) {
return jsdomPkg && options.forceMinimal !== true ? createJsdomEnv(options) : createMinimalEnv(options);
}
function runBrowserCode(env, code, filename = "target.js", options = {}) {
const script = new vm.Script(String(code), {
filename,
displayErrors: true,
timeout: options.compileTimeout,
});
return script.runInContext(env.context, {
timeout: options.timeout || 10_000,
displayErrors: true,
});
}
function evaluate(env, expression, options = {}) {
return runBrowserCode(env, expression, options.filename || "evaluate.js", options);
}
module.exports = {
createBrowserEnv,
createJsdomEnv,
createMinimalEnv,
runBrowserCode,
evaluate,
nativeLike,
defineValue,
defineGetter,
createStorage,
createNavigator,
createScreen,
createLocation,
installCoreGlobals,
installNetwork,
installCrypto,
installEncoding,
installTimers,
installHooks,
};
if (require.main === module) {
const env = createBrowserEnv({
url: "https://target.example/path?x=1",
traceMissing: true,
hooks: { log: true },
});
const result = runBrowserCode(
env,
console.log(navigator.userAgent);
localStorage.setItem("k", "v");
document.cookie = "sid=demo; path=/";
globalThis.__demo = {
href: location.href,
cookie: document.cookie,
storage: localStorage.getItem("k"),
hasCrypto: !!crypto.getRandomValues
};
__demo;,
"demo.js",
);
console.log("[demo result]", result);
if (env.missing.length) console.log("[missing]", Array.from(new Set(env.missing)));
}
评论交流
还没有公开评论。