
1. 为什么需要环境模拟做JS逆向的朋友应该都遇到过这种情况明明代码逻辑分析得很清楚但直接复制到本地运行就是报错。最常见的就是各种undefined错误比如navigator.plugins找不到、window.outerWidth返回null。这其实就是网站的环境检测机制在作祟。现代前端反爬虫技术越来越智能它们会通过检查浏览器环境的各种特征来判断代码是否在真实浏览器中运行。比如检测window对象的属性、检查navigator的插件列表、验证document的操作权限等。如果发现环境异常就会触发反爬机制或者直接抛出错误。我去年逆向一个电商网站时就踩过这个坑。当时花了三天时间分析加密逻辑结果代码移植到Node.js环境死活跑不通。后来发现是网站检测了window.devicePixelRatio属性而Node.js根本没有这个属性。这就是典型的环境检测案例。2. Proxy对象的基本原理2.1 什么是ProxyProxy是ES6引入的一个强大特性它允许你创建一个对象的代理拦截并自定义该对象的基本操作。简单说Proxy就像是一个中间人所有对目标对象的操作都要经过它。举个例子假设你有个普通对象const person { name: 张三, age: 30 }我们可以用Proxy给它加个中间人const handler { get(target, prop) { console.log(有人想获取${prop}属性); return target[prop]; } }; const proxy new Proxy(person, handler);现在当你访问proxy.name时控制台会先打印日志再返回真实值。这就是Proxy的基本用法。2.2 Proxy的拦截能力Proxy最强大的地方在于它能拦截13种基本操作包括属性读取get属性设置set函数调用apply构造函数调用construct删除属性deleteProperty等等在逆向工程中我们主要用get和set这两个拦截器。get可以捕获所有属性访问set可以捕获所有属性赋值。这就给了我们无限可能——可以动态生成属性、记录访问日志、修改返回值等等。3. 实战环境模拟3.1 模拟window对象window是浏览器环境的顶级对象也是反爬虫检测的重点目标。下面我们看看如何用Proxy模拟一个真实的window环境。const fakeWindow {}; const windowHandler { get(target, prop) { // 特殊属性处理 if (prop devicePixelRatio) { return 2; // 常见显示器值 } if (prop outerWidth) { return 1920; // 常见屏幕宽度 } // 默认行为 if (!(prop in target)) { console.warn(window.${prop} 未定义); return undefined; } return target[prop]; } }; window new Proxy(fakeWindow, windowHandler);这个简单的实现已经能应付很多基础检测了。当代码访问window.devicePixelRatio时会返回2访问未定义的属性时会打印警告而不是直接报错。3.2 模拟navigator对象navigator对象包含大量浏览器指纹信息是反爬虫的重灾区。下面是一个增强版的模拟实现const fakeNavigator {}; const navigatorHandler { get(target, prop) { // 常见属性 const commonProps { userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, platform: Win32, language: zh-CN, plugins: [{ name: Chrome PDF Plugin, filename: internal-pdf-viewer }], webdriver: undefined // 重要很多网站检测这个 }; if (prop in commonProps) { return commonProps[prop]; } // 默认行为 if (!(prop in target)) { console.warn(navigator.${prop} 未定义); return undefined; } return target[prop]; } }; navigator new Proxy(fakeNavigator, navigatorHandler);这里有几个关键点设置了合理的userAgent模拟Chrome浏览器plugins数组模拟了常见的PDF插件特别将webdriver设为undefined因为很多网站会检测这个来判断是否是自动化工具4. 高级技巧与调试4.1 动态属性生成有时候我们不知道网站会检测哪些属性这时候可以动态生成属性值const documentHandler { get(target, prop) { if (prop cookie) { return samplevalue; path/; // 模拟cookie } if (prop referrer) { return https://www.google.com/; // 模拟来自Google的流量 } // 对于函数返回一个空函数 if (typeof prop function) { return function() { console.log(document.${prop} called); }; } // 对于未定义的属性返回一个Proxy if (!(prop in target)) { return new Proxy({}, { get: function() { return default value; } }); } return target[prop]; } };这种懒加载式的属性生成可以大大减少环境模拟的工作量因为你不需要预先定义所有可能的属性。4.2 调试技巧环境模拟最难的部分是找出网站检测了哪些属性。这里分享几个实用技巧日志记录修改Proxy的get处理器记录所有属性访问get(target, prop) { console.log(访问属性: ${prop}); // ...原有逻辑 }断点调试在可疑的属性访问处设置断点查看调用栈特征对比在真实浏览器中运行代码记录所有环境属性然后与你的模拟环境对比我曾经遇到一个网站检测了navigator.hardwareConcurrencyCPU核心数这个属性很容易被忽略。通过日志记录才发现问题所在。5. 完整代码示例下面是一个完整的浏览器环境模拟实现包含了window、navigator、document等核心对象// 环境模拟工具函数 function createEnvProxy(objName, customHandler {}) { const target {}; const defaultHandler { get(target, prop) { // 记录所有属性访问 console.log([${objName}] 访问属性: ${String(prop)}); // 如果是函数返回空函数 if (typeof prop function) { return function() { console.log([${objName}] 调用方法: ${prop}); }; } // 默认返回一个Proxy return new Proxy({}, { get: function() { return default value; } }); } }; const handler Object.assign({}, defaultHandler, customHandler); return new Proxy(target, handler); } // 模拟window window createEnvProxy(window, { get(target, prop) { if (prop devicePixelRatio) return 2; if (prop outerWidth) return 1920; return defaultHandler.get(target, prop); } }); // 模拟navigator navigator createEnvProxy(navigator, { get(target, prop) { const commonProps { userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, platform: Win32, webdriver: undefined }; if (prop in commonProps) return commonProps[prop]; return defaultHandler.get(target, prop); } }); // 模拟document document createEnvProxy(document, { get(target, prop) { if (prop cookie) return samplevalue; if (prop referrer) return https://www.google.com/; return defaultHandler.get(target, prop); } });这个实现虽然简单但已经能应付大多数基础检测了。在实际项目中你可能需要根据目标网站的具体检测逻辑来调整和完善。