Chrome中如何让JavaScript Proxy与原生目标对象表现完全一致?
问题:如何让被代理的原生对象在Chrome中表现得与原对象完全一致?
网站常用的反逆向工程手段之一,是在开发者工具打开时阻止JavaScript代码执行——这类手段通常依赖于向控制台输出带有getter或自定义toString函数的对象,这类函数仅在控制台可见时才会被调用。简单的应对方案是代理console.*系列函数并丢弃问题对象。
但近期遇到一个更棘手的网站,它能够检测用户是否代理或篡改了原生函数,且该网站的脚本经过重度混淆,无法确定具体检测逻辑。已知Chrome浏览器中存在一种可靠的检测原生函数是否被代理的方法,示例代码如下:
function isProxied(func) { return func.toString().length === 29; } console.log(isProxied(eval)); eval = new Proxy(eval, {}); console.log(isProxied(eval));
现需解决:如何让被代理的对象在Chrome中表现得与目标原生对象完全一致?
解决方案
核心思路
这类检测的核心通常是验证原生函数的toString输出、原型链、属性描述符等特性是否符合原生标准。我们需要通过Proxy的陷阱拦截,让代理对象在所有可检测维度上与原对象完全对齐。
1. 拦截toString方法,匹配原生输出
Chrome中原生函数的toString()返回固定格式的字符串(比如eval的返回是"function eval() { [native code] }",长度为29)。我们需要在Proxy的get陷阱中拦截toString访问,返回原函数的toString结果:
const originalEval = eval; eval = new Proxy(originalEval, { get(target, prop) { if (prop === 'toString') { // 绑定原函数上下文,确保toString输出原生内容 return target.toString.bind(target); } return Reflect.get(target, prop); }, // 拦截函数调用,确保执行逻辑和原函数一致 apply(target, thisArg, args) { return Reflect.apply(target, thisArg, args); } }); // 测试验证 function isProxied(func) { return func.toString().length === 29; } console.log(isProxied(eval)); // 输出false,与原生eval表现一致
2. 覆盖全维度检测点
部分网站会通过原型链、属性描述符、ownKeys等方式检测代理,需要补充更多陷阱拦截:
const originalEval = eval; const evalProxy = new Proxy(originalEval, { get(target, prop) { if (prop === 'toString') { return target.toString.bind(target); } const value = Reflect.get(target, prop); // 若属性是函数,绑定原上下文避免执行异常 if (typeof value === 'function') { return value.bind(target); } return value; }, apply(target, thisArg, args) { return Reflect.apply(target, thisArg, args); }, // 拦截属性存在性检测 has(target, prop) { return Reflect.has(target, prop); }, // 返回原函数的属性描述符 getOwnPropertyDescriptor(target, prop) { return Reflect.getOwnPropertyDescriptor(target, prop); }, // 返回原函数的所有键 ownKeys(target) { return Reflect.ownKeys(target); } }); // 同步原型链 Object.setPrototypeOf(evalProxy, Object.getPrototypeOf(originalEval)); // 复制原函数的所有属性描述符 Object.defineProperties(evalProxy, Object.getOwnPropertyDescriptors(originalEval)); eval = evalProxy;
3. 应对全局Function.prototype.toString检测
如果网站通过Function.prototype.toString.call(eval)来检测,需要全局拦截该方法,针对代理的原生函数返回正确结果:
const originalFunctionToString = Function.prototype.toString; const targetProxies = new WeakSet([evalProxy]); Function.prototype.toString = function() { if (targetProxies.has(this)) { return originalEval.toString(); } return originalFunctionToString.call(this); };
内容的提问来源于stack exchange,提问作者user2248702
相关产品推荐
相关产品推荐

