mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
1242 字
3 分钟
补环境代理完整实现
2026-06-01

补环境深度解析:从 Proxy 监控到防检测实现#

一、为什么需要环境代理#

1.1 问题背景#

在 JS 逆向过程中,我们需要将浏览器环境中的代码移植到 Node.js 中运行。但直接运行会遇到两个核心问题:

问题一:环境缺失

// 浏览器中有但 Node.js 中没有
typeof window // 浏览器: "object", Node.js: "undefined"
typeof document // 浏览器: "object", Node.js: "undefined"
typeof navigator // 浏览器: "object", Node.js: "undefined"

问题二:环境检测

// 安全代码会检测环境真实性
Object.prototype.toString.call(window) // 浏览器: "[object Window]"
// Node.js: "[object global]"
'process' in window // 浏览器: false, Node.js: true
navigator.webdriver // 自动化工具: true, 正常浏览器: false

1.2 代理的作用#

环境代理的核心作用是:

  1. 监控访问:追踪代码对浏览器对象的所有访问
  2. 模拟行为:提供符合浏览器规范的对象和方法
  3. 隐藏特征:屏蔽 Node.js 特有的属性和行为
  4. 防检测:绕过各种环境检测手段

二、核心代理机制:Proxy 监控#

2.1 Proxy 的基本用法#

function watch(obj, name) {
const loggedOperations = new Set(); // 去重记录
return new Proxy(obj, {
get: function(target, property) {
// 拦截属性读取
const value = target[property];
console.log(`[访问] ${name}.${String(property)}`);
return value;
},
set: function(target, property, newValue) {
// 拦截属性设置
console.log(`[设置] ${name}.${String(property)} = ${newValue}`);
return Reflect.set(target, property, newValue);
},
has: function(target, property) {
// 拦截 in 操作符
console.log(`[检测] '${property}' in ${name}`);
return Reflect.has(target, property);
},
ownKeys: function(target) {
// 拦截属性枚举
const keys = Reflect.ownKeys(target);
console.log(`[枚举] ${name} 的属性: ${keys}`);
return keys;
}
});
}

2.2 不同类型属性的处理策略#

2.2.1 Symbol 类型处理#

get: function(target, property) {
const value = target[property];
const type = typeof value;
if (type === "symbol") {
const symbolDescription = property.description || 'no description';
logMessage = `对象=>${name},读取属性:${symbolDescription},这是一个 Symbol 类型的值`;
operationId = `get:${name}:symbol:${symbolDescription}`;
}
// ...
}

为什么要特殊处理 Symbol?

  • Symbol 是 ES6 引入的新类型,用于创建唯一标识符
  • 浏览器对象大量使用 Symbol(如 Symbol.toStringTagSymbol.toPrimitive
  • 某些检测会检查特定 Symbol 是否存在

2.2.2 Function 类型处理#

if (type === "function") {
const functionName = value.name || 'anonymous';
logMessage = `对象=>${name},读取属性:${property.toString()},这是一个名为 ${functionName} 的函数`;
operationId = `get:${name}:function:${property.toString()}:${functionName}`;
}

函数处理的关键点

  • 记录函数名便于调试
  • 函数需要绑定正确的 this 上下文
  • 某些检测会验证函数的 toString 输出

2.2.3 普通对象处理#

else {
logMessage = `对象=>${name},读取属性:${String(property)},值为:${displayValue},类型为:${type}`;
operationId = `get:${name}:${String(property)}:${type}:${String(value)}`;
}

普通值的处理原则

  • 记录类型信息
  • 截断过长的字符串(避免日志刷屏)
  • 使用唯一 ID 去重,避免重复输出

2.3 去重机制的重要性#

const loggedOperations = new Set();
// 检查是否已记录
if (!loggedOperations.has(operationId)) {
loggedOperations.add(operationId);
logToConsole(logMessage);
}

为什么需要去重?

  • 代码可能反复访问同一个属性(如循环中访问 window.document
  • 不去重会导致日志爆炸,难以分析
  • 使用 Set + 唯一 ID 实现高效去重

三、防检测策略#

3.1 Function.toString 检测#

3.1.1 问题分析#

浏览器中的原生函数 toString() 返回特殊格式:

window.alert.toString()
// "function alert() { [native code] }"
document.createElement.toString()
// "function createElement() { [native code] }"

而 Node.js 中的函数 toString() 返回实际代码:

(function(){}).toString()
// "function(){}"

3.1.2 解决方案#

const safeFunction = (function () {
// 保存原始的 call 方法
Function.prototype.$call = Function.prototype.call;
const $toString = Function.toString;
// 创建一个 Symbol 作为标记
const myFunction_toString_symbol = Symbol('('.concat('', ')'));
// 自定义 toString 方法
const myToString = function myToString() {
// 如果函数有标记,返回原生代码格式
return typeof this === 'function' && this[myFunction_toString_symbol] || $toString.$call(this);
}
// 删除原始 toString,替换为自定义版本
delete Function.prototype['toString'];
// 使用 Object.defineProperty 设置(确保不可枚举)
Object.defineProperty(Function.prototype, "toString", {
"enumerable": false,
"configurable": true,
"writable": true,
"value": myToString
});
// 设置 toString 本身也是原生函数
Object.defineProperty(Function.prototype.toString, myFunction_toString_symbol, {
value: "function toString() { [native code] }",
enumerable: false,
configurable: true,
writable: true
});
// 返回一个函数,用于为其他函数添加标记
return function (func) {
Object.defineProperty(func, myFunction_toString_symbol, {
value: "function" + (func.name ? " " + func.name : "") + "() { [native code] }",
enumerable: false,
configurable: true,
writable: true
});
}
})();

使用示例

const myFunc = function test() { return 1; };
safeFunction(myFunc);
console.log(myFunc.toString());
// "function test() { [native code] }"

3.2 Symbol.toStringTag 检测#

3.2.1 问题分析#

浏览器对象的 toStringTag 有特定值:

Object.prototype.toString.call(window) // "[object Window]"
Object.prototype.toString.call(document) // "[object HTMLDocument]"
Object.prototype.toString.call(navigator) // "[object Navigator]"

3.2.2 解决方案#

// 在 createConstructor 中设置
Object.defineProperty(this, Symbol.toStringTag, {
value: constructorName, // 如 "Window"
writable: false,
enumerable: false,
configurable: false
});

关键点

  • 使用 Object.defineProperty 而非直接赋值
  • 设置为不可枚举、不可写、不可配置(模拟浏览器行为)
  • 值必须与浏览器一致(区分大小写)

3.3 Symbol.toPrimitive 检测#

3.3.1 问题分析#

某些检测会通过 Symbol.toPrimitive 检查对象类型:

// 浏览器中
window[Symbol.toPrimitive]('string') // "[object Window]"
document[Symbol.toPrimitive]('string') // "[object HTMLDocument]"

3.3.2 解决方案#

Object.defineProperty(this, Symbol.toPrimitive, {
value: function (hint) {
switch (hint) {
case 'number':
return this._element ? instancesData[this._element].toString().length : 0;
case 'string':
return `[${constructorName} Instance]`;
default:
return `[object ${constructorName}]`;
}
},
writable: false,
enumerable: false,
configurable: false
});

处理不同 hint 的策略

  • 'number':返回数字(通常是长度)
  • 'string':返回字符串表示
  • 默认:返回标准对象格式

3.4 Node.js 特征隐藏#

3.4.1 必须删除的属性#

// 删除 Node.js 特有属性
delete global.Buffer;
delete global.process;
delete global.module;
delete global.exports;
delete global.require;
delete global.__dirname;
delete global.__filename;
delete Error.prepareStackTrace;

为什么不能用 = undefined

// 错误做法
global.Buffer = undefined;
'Buffer' in global // 仍然返回 true!
// 正确做法
delete global.Buffer;
'Buffer' in global // 返回 false

3.4.2 属性描述符检测#

某些检测会使用 Object.getOwnPropertyDescriptor

// 检测是否为 accessor 属性
const desc = Object.getOwnPropertyDescriptor(window, 'Buffer');
// 浏览器: undefined(属性不存在)
// Node.js (delete后): undefined
// Node.js (=undefined): { value: undefined, writable: true, ... }

对策:必须使用 delete 删除,不能直接赋值为 undefined


四、类构造函数模拟#

4.1 创建构造函数工厂#

function createConstructor(constructorName, enableStrictMode, propertiesList, prototypeMethods, parentConstructorName) {
// 存储实例数据(使用 Symbol 作为 key)
const instancesData = {};
const constructorFunction = function (element, propertySetter, validationToken) {
// 严格模式下,只有传入正确的 token 才能创建实例
if (enableStrictMode && !(validationToken && validationToken === "fatdog")) {
throw new Error("Illegal constructor");
}
// 设置 Symbol.toStringTag
Object.defineProperty(this, Symbol.toStringTag, {
value: constructorName,
writable: false,
enumerable: false,
configurable: false
});
// 设置 Symbol.toPrimitive
Object.defineProperty(this, Symbol.toPrimitive, {
value: function (hint) { /* ... */ },
writable: false,
enumerable: false,
configurable: false
});
// 存储实例数据
const instanceProperties = element && typeof element === "object" ? element : {};
this._element = Symbol("_element");
instancesData[this._element] = instanceProperties;
};
// 设置构造函数名称
Object.defineProperty(constructorFunction, 'name', {value: constructorName});
// 处理继承关系
if (parentConstructorName && window[parentConstructorName]) {
const ParentConstructor = window[parentConstructorName];
constructorFunction.prototype = Object.create(ParentConstructor.prototype);
Object.defineProperty(constructorFunction.prototype, 'constructor', {
value: constructorFunction,
writable: false,
enumerable: false,
configurable: false
});
}
// 添加原型方法
Object.keys(prototypeMethods).forEach(methodName => {
constructorFunction.prototype[methodName] = prototypeMethods[methodName];
if (typeof constructorFunction.prototype[methodName] === "function") {
safeFunction(constructorFunction.prototype[methodName]);
}
});
// 保护构造函数
safeFunction(constructorFunction);
// 挂载到全局
window[constructorName] = constructorFunction;
return constructorFunction;
}

4.2 原型链设置#

// 创建继承链
createConstructor('EventTarget', true, [], {});
createConstructor('Node', true, [], {}, 'EventTarget');
createConstructor('WindowProperties', true, [], {}, 'EventTarget');
createConstructor('Window', true, [], {}, 'WindowProperties');
// 设置 window 的原型
Object.setPrototypeOf(window, Window.prototype);

原型链结构

window -> Window.prototype -> WindowProperties.prototype
-> EventTarget.prototype
-> Object.prototype

4.3 严格模式保护#

if (enableStrictMode && !(validationToken && validationToken === "fatdog")) {
throw new Error("Illegal constructor");
}

作用

  • 防止代码直接使用 new Window() 创建实例
  • 浏览器中某些构造函数是受保护的,不能直接实例化
  • 只有传入正确的 token 才能创建实例

五、实战:构建完整的浏览器环境#

5.1 步骤一:初始化全局对象#

// 基础设置
window = globalThis;
// 删除 Node.js 特有属性
delete global.Buffer;
delete global.process;
// ...
// 创建构造函数链
createConstructor('EventTarget', true, [], {});
createConstructor('Node', true, [], {}, 'EventTarget');
createConstructor('Window', true, [], {}, 'Node');
Object.setPrototypeOf(window, Window.prototype);

5.2 步骤二:创建核心对象#

// 创建 document
const fakeDocument = {
createElement: function(tag) {
const el = { tagName: tag.toUpperCase(), style: {} };
// 设置正确的 toStringTag
Object.defineProperty(el, Symbol.toStringTag, {
value: tag === 'canvas' ? 'HTMLCanvasElement' : `HTML${tag}Element`,
enumerable: false
});
return el;
},
// ... 其他方法
};
safeFunction(fakeDocument.createElement);
// 创建 navigator
const fakeNavigator = {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
webdriver: false,
// ... 其他属性
};
// 使用 Proxy 监控
window.document = watch(fakeDocument, 'document');
window.navigator = watch(fakeNavigator, 'navigator');

5.3 步骤三:测试与迭代#

// 加载目标代码
require('./target.js');
// 测试签名函数
console.log('签名函数:', typeof window.signFunction);
// 根据日志补充缺失的属性
// 如果看到 "[访问] document.createElement('canvas')"
// 就需要为 canvas 添加 getContext 方法

六、高级防检测技巧#

6.1 方法调用追踪#

// 在 watch 函数中添加对函数调用的追踪
get: function(target, property) {
const value = target[property];
if (typeof value === 'function') {
// 返回包装后的函数,记录调用
return function(...args) {
console.log(`[调用] ${name}.${property}(${args.map(a => truncateValue(a)).join(', ')})`);
const result = value.apply(target, args);
console.log(`[返回] ${name}.${property} -> ${truncateValue(result)}`);
return result;
};
}
return value;
}

6.2 属性描述符模拟#

// 模拟浏览器属性的描述符
Object.defineProperty(fakeNavigator, 'userAgent', {
get: () => 'Mozilla/5.0...',
enumerable: true, // 可枚举
configurable: true, // 可配置
writable: false // 只读
});

为什么要设置描述符?

某些检测会检查属性是否可写:

const desc = Object.getOwnPropertyDescriptor(navigator, 'userAgent');
if (desc.writable) {
// 可能是伪造的!
}

6.3 动态属性生成#

// 延迟生成属性,只在访问时创建
const fakeNavigator = {};
Object.defineProperty(fakeNavigator, 'plugins', {
get: function() {
// 第一次访问时生成
const plugins = { length: 0, item: () => null };
// 缓存结果
Object.defineProperty(fakeNavigator, 'plugins', {
value: plugins,
enumerable: true,
configurable: false
});
return plugins;
},
enumerable: true,
configurable: true
});

优势

  • 减少初始化时间
  • 避免创建永远不会被访问的对象
  • 更接近浏览器的惰性初始化行为

七、常见问题与解决方案#

7.1 TypeError: Illegal constructor#

// 问题:代码尝试直接实例化受保护的构造函数
new Window(); // Error: Illegal constructor
// 解决方案:在 createConstructor 中设置 enableStrictMode = false
// 或者在调用时传入正确的 token
const win = new Window(null, null, "fatdog");

7.2 toString 返回值不正确#

// 问题:函数的 toString 返回的是源码而非 "[native code]"
myFunc.toString(); // "function myFunc() { ... }"
// 解决方案:使用 safeFunction 包装
safeFunction(myFunc);
myFunc.toString(); // "function myFunc() { [native code] }"

7.3 属性不存在错误#

// 问题:代码访问了未定义的属性
window.someProperty.someMethod(); // TypeError
// 解决方案:使用 Proxy 的 get 拦截器提供默认值
get: function(target, property) {
if (!(property in target)) {
console.log(`[警告] ${name}.${property} 不存在,返回空对象`);
return {}; // 或 null,根据实际情况
}
return target[property];
}

7.4 原型链检测失败#

// 问题:instanceof 检测失败
window instanceof Window // false
// 解决方案:正确设置原型链
Object.setPrototypeOf(window, Window.prototype);
window instanceof Window // true

7.5完整代理与反检测#

借鉴大佬开源的模板

!(() => {
const origin_log = console.log;
logToConsole = function (){
return origin_log(...arguments)
}
})();
//环境代理
function watch(obj, name) {
// 用于存储已记录的操作,实现去重
const loggedOperations = new Set();
// 辅助函数:限制字符串长度,最长20个字符
const truncateValue = (value) => {
const str = String(value);
return str.length > 20 ? str.substring(0, 20) + '...' : str;
};
return new Proxy(obj, {
get: function (target, property) {
const value = target[property];
const type = typeof value;
// 生成操作唯一标识和日志消息
let operationId;
let logMessage;
// 处理要显示的值(截断处理)
const displayValue = truncateValue(value);
if (type === "symbol") {
const symbolDescription = property.description || 'no description';
logMessage = `对象=>${name},读取属性:${symbolDescription},这是一个 Symbol 类型的值`;
operationId = `get:${name}:symbol:${symbolDescription}`;
} else if (type === "function") {
const functionName = value.name || 'anonymous';
// 函数名也进行长度控制
const displayFunctionName = truncateValue(functionName);
logMessage = `对象=>${name},读取属性:${property.toString()},这是一个名为 ${displayFunctionName} 的函数`;
operationId = `get:${name}:function:${property.toString()}:${functionName}`;
} else {
logMessage = `对象=>${name},读取属性:${String(property)},值为:${displayValue},类型为:${type}`;
operationId = `get:${name}:${String(property)}:${type}:${String(value)}`;
}
// 检查是否已记录,如果没有则记录并输出
if (!loggedOperations.has(operationId)) {
loggedOperations.add(operationId);
logToConsole(logMessage);
}
// 关键修复:如果是函数,用 bind 绑定正确的 this
// 或者直接返回 value,让 receiver 自动处理 this
if (type === "function") {
// 方式1: 使用 bind 绑定到 target
// return value.bind(target);
// 方式2: 使用 Reflect.get 获取,保持 receiver 上下文(更推荐)
return Reflect.get(target, property, receiver);
}
return value;
},
set: (target, property, newValue, receiver) => {
const valueType = typeof newValue;
// 生成操作唯一标识和日志消息
let operationId;
let logMessage;
// 处理要显示的新值(截断处理)
const displayNewValue = truncateValue(newValue);
if (valueType === "symbol") {
const symbolDescription = newValue.description || 'no description';
logMessage = `对象=>${name},设置属性:${String(property)},这是一个 Symbol 类型的新值, 描述为: ${symbolDescription}`;
operationId = `set:${name}:${String(property)}:symbol:${symbolDescription}`;
} else {
logMessage = `对象=>${name},设置属性:${String(property)},值为:${displayNewValue},类型为:${valueType}`;
operationId = `set:${name}:${String(property)}:${valueType}:${String(newValue)}`;
}
// 检查是否已记录,如果没有则记录并输出
if (!loggedOperations.has(operationId)) {
loggedOperations.add(operationId);
logToConsole(logMessage);
}
return Reflect.set(target, property, newValue, receiver);
},
// 监听 in 操作符(检查属性是否存在)
has: (target, property) => {
let operationId;
let logMessage;
// 处理 Symbol 类型的属性
if (typeof property === 'symbol') {
const symbolDescription = property.description || 'no description';
logMessage = `对象=>${name},检查属性存在:${symbolDescription} (Symbol), 使用 in 操作符`;
operationId = `has:${name}:symbol:${symbolDescription}`;
} else {
logMessage = `对象=>${name},检查属性存在:${String(property)}, 使用 in 操作符`;
operationId = `has:${name}:${String(property)}`;
}
if (!loggedOperations.has(operationId)) {
loggedOperations.add(operationId);
logToConsole(logMessage);
}
return Reflect.has(target, property);
},
// 监听属性枚举操作(如 Object.keys、for...in 等)
ownKeys: (target) => {
const keys = Reflect.ownKeys(target);
// 截断过长的键列表显示
const displayKeys = truncateValue(keys.map(k =>
typeof k === 'symbol' ? `Symbol(${k.description || ''})` : String(k)
).join(', '));
const operationId = `ownKeys:${name}:${keys.length}`;
const logMessage = `对象=>${name},枚举属性,共 ${keys.length} 个属性:${displayKeys}`;
if (!loggedOperations.has(operationId)) {
loggedOperations.add(operationId);
logToConsole(logMessage);
}
return keys;
},
// 拦截 Object.getOwnPropertyDescriptor
getOwnPropertyDescriptor: (target, property) => {
const desc = Reflect.getOwnPropertyDescriptor(target, property);
if (desc) {
logToConsole(`[描述符] ${name}.${String(property)}: configurable=${desc.configurable}, enumerable=${desc.enumerable}, writable=${desc.writable}`);
}
return desc;
},
// 拦截 delete
deleteProperty: (target, property) => {
logToConsole(`[删除] ${name}.${String(property)}`);
return Reflect.deleteProperty(target, property);
},
// 拦截 Object.getPrototypeOf() 和 __proto__
getPrototypeOf: (target) => {
const proto = Reflect.getPrototypeOf(target);
const protoName = proto ? (proto.name || proto[Symbol.toStringTag] || 'Object') : 'null';
const operationId = `getPrototypeOf:${name}:${protoName}`;
const logMessage = `[原型] ${name}.getPrototypeOf() = [${protoName}]`;
if (!loggedOperations.has(operationId)) {
loggedOperations.add(operationId);
logToConsole(logMessage);
}
return proto;
},
// 拦截 Object.setPrototypeOf()
setPrototypeOf: (target, proto) => {
const protoName = proto ? (proto.name || proto[Symbol.toStringTag] || 'Object') : 'null';
const operationId = `setPrototypeOf:${name}:${protoName}`;
const logMessage = `[原型] 设置 ${name}.prototype = [${protoName}]`;
if (!loggedOperations.has(operationId)) {
loggedOperations.add(operationId);
logToConsole(logMessage);
}
return Reflect.setPrototypeOf(target, proto);
},
});
}
//安全函数
const safeFunction = (function () {
//处理安全函数
Function.prototype.$call = Function.prototype.call;
const $toString = Function.toString;
const myFunction_toString_symbol = Symbol('('.concat('', ')'));
const myToString = function myToString() {
return typeof this === 'function' && this[myFunction_toString_symbol] || $toString.$call(this);
}
const set_native = function set_native(func, key, value) {
Object.defineProperty(func, key, {
"enumerable": false,
"configurable": true,
"writable": true,
"value": value
});
}
delete Function.prototype['toString'];
set_native(Function.prototype, "toString", myToString);
set_native(Function.prototype.toString, myFunction_toString_symbol, "function toString() { [native code] }");
return function (func) {
set_native(func, myFunction_toString_symbol, "function" + (func.name ? " " + func.name : "") + "() { [native code] }");
}
})();
//类构造函数
function createConstructor(constructorName, enableStrictMode, propertiesList, prototypeMethods, parentConstructorName) {
const instancesData = {};
const constructorFunction = function (element, propertySetter, validationToken) {
if (enableStrictMode && !(validationToken && validationToken === "fatdog")) {
throw new Error("Illegal constructor");
}
// 为实例添加Symbol.toStringTag
Object.defineProperty(this, Symbol.toStringTag, {
value: constructorName,
writable: false,
enumerable: false,
configurable: false
});
// 为实例添加Symbol.toPrimitive
Object.defineProperty(this, Symbol.toPrimitive, {
value: function (hint) {
switch (hint) {
case 'number':
return this._element ? instancesData[this._element].toString().length : 0;
case 'string':
return `[${constructorName} Instance]`;
default:
return `[object ${constructorName}]`;
}
},
writable: false,
enumerable: false,
configurable: false
});
if (propertySetter && typeof propertySetter === "function") {
propertySetter(this, instancesData[this._element]);
}
const instanceProperties = element && typeof element === "object" ? element : {};
this._element = Symbol("_element");
instancesData[this._element] = instanceProperties;
if (element && typeof element === "object") {
Object.keys(element).forEach(key => {
if (!this[key]) {
this[key] = element[key];
}
});
}
};
// 设置构造函数名称
Object.defineProperty(constructorFunction, 'name', {value: constructorName});
// 处理继承关系
if (parentConstructorName && window[parentConstructorName]) {
const ParentConstructor = window[parentConstructorName];
constructorFunction.prototype = Object.create(ParentConstructor.prototype);
Object.defineProperty(constructorFunction.prototype, 'constructor', {
value: constructorFunction,
writable: false,
enumerable: false,
configurable: false
});
}
// 为构造函数本身添加Symbol.toStringTag
Object.defineProperty(constructorFunction, Symbol.toStringTag, {
value: constructorName,
writable: false,
enumerable: false,
configurable: false
});
// 为构造函数本身添加Symbol.toPrimitive
Object.defineProperty(constructorFunction, Symbol.toPrimitive, {
value: function (hint) {
switch (hint) {
case 'number':
return constructorName.length;
case 'string':
return `[Constructor ${constructorName}]`;
default:
return constructorName;
}
},
writable: false,
enumerable: false,
configurable: false
});
// 添加原型方法
Object.keys(prototypeMethods).forEach(methodName => {
constructorFunction.prototype[methodName] = prototypeMethods[methodName];
if (typeof constructorFunction.prototype[methodName] === "function") {
safeFunction(constructorFunction.prototype[methodName]);
}
});
// 保护构造函数
safeFunction(constructorFunction);
// 挂载到全局
window[constructorName] = constructorFunction;
return constructorFunction;
};

八、总结#

8.1 核心要点#

检测类型解决方案
Function.toString使用 safeFunction 包装
Symbol.toStringTag通过 defineProperty 设置
Symbol.toPrimitive定义转换函数
Node.js 特征使用 delete 删除
属性描述符使用 defineProperty 精确控制
原型链使用 setPrototypeOf 设置

8.2 设计原则#

  1. 最小化原则:只模拟被实际访问的属性,避免过度实现
  2. 真实性原则:属性值和行为要尽可能接近真实浏览器
  3. 隐蔽性原则:隐藏所有 Node.js 特征
  4. 可监控原则:使用 Proxy 追踪所有访问,便于调试

8.3 工具链推荐#

  • Proxy:用于监控和拦截属性访问
  • Object.defineProperty:用于精确控制属性描述符
  • Symbol:用于创建唯一标识符和特殊属性
  • Set:用于去重日志记录

通过以上方法,我们可以构建一个高度仿真的浏览器环境,成功绕过各种检测,让加密代码在 Node.js 中正常运行。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

补环境代理完整实现
https://fatdog.20060113.xyz/posts/envproxy/
作者
神秘大胖狗
发布于
2026-06-01
许可协议
MIT

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00