reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
const { scaffoldModule } = require("./scaffold-module");
|
||||
const { buildPluginGraph } = require("./build-plugin-graph");
|
||||
|
||||
function clone(value) {
|
||||
return JSON.parse(JSON.stringify(value || {}));
|
||||
}
|
||||
|
||||
function normalizeNavigatorSeed(seed = {}) {
|
||||
const navigatorSeed = seed.navigator && typeof seed.navigator === "object" ? seed.navigator : seed;
|
||||
const plugins = Array.isArray(seed.plugins) ? clone(seed.plugins) : [];
|
||||
const mimeTypes = Array.isArray(seed.mimeTypes) ? clone(seed.mimeTypes) : [];
|
||||
|
||||
return {
|
||||
navigator: {
|
||||
userAgent: navigatorSeed.userAgent || "",
|
||||
platform: navigatorSeed.platform || "",
|
||||
vendor: navigatorSeed.vendor || "",
|
||||
language: navigatorSeed.language || "",
|
||||
languages: Array.isArray(navigatorSeed.languages) ? navigatorSeed.languages.slice() : [],
|
||||
webdriver: typeof navigatorSeed.webdriver === "boolean" ? navigatorSeed.webdriver : false,
|
||||
hardwareConcurrency: Number.isFinite(navigatorSeed.hardwareConcurrency)
|
||||
? navigatorSeed.hardwareConcurrency
|
||||
: 8,
|
||||
deviceMemory: Number.isFinite(navigatorSeed.deviceMemory) ? navigatorSeed.deviceMemory : 8,
|
||||
maxTouchPoints: Number.isFinite(navigatorSeed.maxTouchPoints) ? navigatorSeed.maxTouchPoints : 0,
|
||||
cookieEnabled: navigatorSeed.cookieEnabled !== false,
|
||||
pdfViewerEnabled: navigatorSeed.pdfViewerEnabled !== false,
|
||||
appCodeName: navigatorSeed.appCodeName || "Mozilla",
|
||||
appName: navigatorSeed.appName || "Netscape",
|
||||
appVersion: navigatorSeed.appVersion || navigatorSeed.userAgent || "",
|
||||
userAgentData: clone(seed.userAgentData || navigatorSeed.userAgentData || null),
|
||||
connection: clone(seed.connection || navigatorSeed.connection || null),
|
||||
},
|
||||
plugins,
|
||||
mimeTypes,
|
||||
battery: clone(seed.battery || null),
|
||||
};
|
||||
}
|
||||
|
||||
function renderNavigatorPatch(seed) {
|
||||
const serializedSeed = JSON.stringify(seed, null, 2);
|
||||
return `(
|
||||
function installNavigatorModule(globalObject) {
|
||||
const seed = ${serializedSeed};
|
||||
const protector = globalObject.__envNativeProtector || null;
|
||||
const pluginSeed = Array.isArray(seed.plugins) ? seed.plugins.slice() : [];
|
||||
const mimeSeed = Array.isArray(seed.mimeTypes) ? seed.mimeTypes.slice() : [];
|
||||
|
||||
function defineValue(target, key, value) {
|
||||
Object.defineProperty(target, key, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
|
||||
function defineGetter(target, key, getter) {
|
||||
Object.defineProperty(target, key, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
get: getter
|
||||
});
|
||||
if (protector && protector.protectDescriptor) {
|
||||
protector.protectDescriptor(target, key);
|
||||
}
|
||||
}
|
||||
|
||||
function createIndexedCollection(items, namedKey) {
|
||||
const collection = {};
|
||||
for (let index = 0; index < items.length; index += 1) {
|
||||
Object.defineProperty(collection, index, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: items[index]
|
||||
});
|
||||
if (items[index] && items[index][namedKey]) {
|
||||
Object.defineProperty(collection, items[index][namedKey], {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: items[index]
|
||||
});
|
||||
}
|
||||
}
|
||||
defineValue(collection, "length", items.length);
|
||||
defineValue(collection, "item", function item(index) {
|
||||
return items[index] || null;
|
||||
});
|
||||
defineValue(collection, "namedItem", function namedItem(name) {
|
||||
return items.find(function findItem(entry) {
|
||||
return entry && entry[namedKey] === name;
|
||||
}) || null;
|
||||
});
|
||||
defineValue(collection, "refresh", function refresh() {});
|
||||
return collection;
|
||||
}
|
||||
|
||||
const pluginArray = pluginSeed.map(function mapPlugin(plugin) {
|
||||
return {
|
||||
name: plugin.name || "",
|
||||
filename: plugin.filename || "",
|
||||
description: plugin.description || "",
|
||||
length: 0
|
||||
};
|
||||
});
|
||||
|
||||
const mimeTypeArray = mimeSeed.map(function mapMime(mime) {
|
||||
return {
|
||||
type: mime.type || "",
|
||||
suffixes: mime.suffixes || "",
|
||||
description: mime.description || "",
|
||||
enabledPlugin: null
|
||||
};
|
||||
});
|
||||
|
||||
for (let index = 0; index < mimeSeed.length; index += 1) {
|
||||
const pluginName = mimeSeed[index].enabledPluginName || "";
|
||||
const plugin = pluginArray.find(function findPlugin(entry) {
|
||||
return entry.name === pluginName;
|
||||
}) || null;
|
||||
mimeTypeArray[index].enabledPlugin = plugin;
|
||||
if (plugin) {
|
||||
const itemIndex = plugin.length;
|
||||
Object.defineProperty(plugin, itemIndex, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: mimeTypeArray[index]
|
||||
});
|
||||
plugin.length += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const plugins = createIndexedCollection(pluginArray, "name");
|
||||
const mimeTypes = createIndexedCollection(mimeTypeArray, "type");
|
||||
|
||||
function Navigator() {}
|
||||
const navigatorState = seed.navigator || {};
|
||||
const navigatorInstance = Object.create(Navigator.prototype);
|
||||
|
||||
const scalarKeys = [
|
||||
"userAgent",
|
||||
"platform",
|
||||
"vendor",
|
||||
"language",
|
||||
"webdriver",
|
||||
"hardwareConcurrency",
|
||||
"deviceMemory",
|
||||
"maxTouchPoints",
|
||||
"cookieEnabled",
|
||||
"pdfViewerEnabled",
|
||||
"appCodeName",
|
||||
"appName",
|
||||
"appVersion"
|
||||
];
|
||||
|
||||
scalarKeys.forEach(function defineScalar(key) {
|
||||
defineGetter(Navigator.prototype, key, function getScalar() {
|
||||
return navigatorState[key];
|
||||
});
|
||||
});
|
||||
|
||||
defineGetter(Navigator.prototype, "languages", function getLanguages() {
|
||||
return Array.isArray(navigatorState.languages) ? navigatorState.languages.slice() : [];
|
||||
});
|
||||
defineGetter(Navigator.prototype, "plugins", function getPlugins() {
|
||||
return plugins;
|
||||
});
|
||||
defineGetter(Navigator.prototype, "mimeTypes", function getMimeTypes() {
|
||||
return mimeTypes;
|
||||
});
|
||||
defineGetter(Navigator.prototype, "userAgentData", function getUserAgentData() {
|
||||
return navigatorState.userAgentData || null;
|
||||
});
|
||||
defineGetter(Navigator.prototype, "connection", function getConnection() {
|
||||
return navigatorState.connection || null;
|
||||
});
|
||||
|
||||
defineValue(Navigator.prototype, "javaEnabled", function javaEnabled() {
|
||||
return false;
|
||||
});
|
||||
defineValue(Navigator.prototype, "getBattery", function getBattery() {
|
||||
return Promise.resolve(seed.battery || {
|
||||
charging: true,
|
||||
chargingTime: 0,
|
||||
dischargingTime: Infinity,
|
||||
level: 1
|
||||
});
|
||||
});
|
||||
|
||||
if (protector && protector.repairFunctionMeta) {
|
||||
protector.repairFunctionMeta(Navigator, { name: "Navigator", length: 0 });
|
||||
protector.repairFunctionMeta(Navigator.prototype.javaEnabled, { name: "javaEnabled", length: 0 });
|
||||
protector.repairFunctionMeta(Navigator.prototype.getBattery, { name: "getBattery", length: 0 });
|
||||
}
|
||||
if (protector && protector.protectFunction) {
|
||||
protector.protectFunction(Navigator, "Navigator");
|
||||
protector.protectFunction(Navigator.prototype.javaEnabled, "javaEnabled");
|
||||
protector.protectFunction(Navigator.prototype.getBattery, "getBattery");
|
||||
}
|
||||
|
||||
defineValue(navigatorInstance, Symbol.toStringTag, "Navigator");
|
||||
Object.defineProperty(globalObject, "Navigator", {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: Navigator
|
||||
});
|
||||
Object.defineProperty(globalObject, "navigator", {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: navigatorInstance
|
||||
});
|
||||
|
||||
return navigatorInstance;
|
||||
})(typeof globalThis !== "undefined" ? globalThis : window);
|
||||
`;
|
||||
}
|
||||
|
||||
function buildNavigatorModule(seed = {}) {
|
||||
const module = scaffoldModule("navigator-module");
|
||||
const normalizedSeed = normalizeNavigatorSeed(seed);
|
||||
const pluginGraph = buildPluginGraph({
|
||||
plugins: normalizedSeed.plugins,
|
||||
mimeTypes: normalizedSeed.mimeTypes,
|
||||
});
|
||||
|
||||
module.patchPlan = [
|
||||
"构建 Navigator 构造器、prototype 和 navigator 实例。",
|
||||
"把 userAgent/language/webdriver 等高频字段补成 prototype getter,而不是直接灌到实例上。",
|
||||
"把 plugins/mimeTypes 构造成类数组图结构,并补 getBattery/userAgentData 等高频检测点。",
|
||||
];
|
||||
module.patchCode = renderNavigatorPatch(normalizedSeed);
|
||||
module.runtimeState = {
|
||||
navigator: normalizedSeed.navigator,
|
||||
plugins: pluginGraph.pluginArray,
|
||||
mimeTypes: pluginGraph.mimeTypeArray,
|
||||
battery: normalizedSeed.battery,
|
||||
};
|
||||
module.validation = [
|
||||
"检查 navigator.webdriver === false。",
|
||||
"检查 navigator.plugins.length 与 navigator.mimeTypes.length。",
|
||||
"检查 navigator.plugins.item(0) / namedItem(name) 与 mimeTypes.enabledPlugin 关系。",
|
||||
"检查 navigator.getBattery() 返回 Promise。",
|
||||
"检查 Navigator.prototype 上 getter 的描述符与 toString 保护是否生效。",
|
||||
];
|
||||
module.residualRisk = [
|
||||
"这里只覆盖常见字段与图结构,未实现 permissions、mediaDevices、bluetooth 等更深层能力对象。",
|
||||
"如果目标脚本会校验 brands/mobile/platformVersion 等 userAgentData 细节,仍需真实浏览器种子。",
|
||||
];
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildNavigatorModule,
|
||||
normalizeNavigatorSeed,
|
||||
};
|
||||
Reference in New Issue
Block a user