Json对象转化为字符串必定会用到JSON.stringify()方法对该方法进行hookJSON.stringify_ JSON.stringify; // 重写JSON.stringify方法 JSON.stringify function () { if (arguments[0] arguments[0][payload]) { // 设置断点 debugger; } // 调用原始的JSON.stringify方法并返回其结果 return JSON.stringify_.apply(this, arguments); };(function() { // 1. 立即执行避免污染全局作用域 const originalApply Function.prototype.apply; // 初始化存储 window.hyx window.hyx || []; const MAX_LOGS 2000; // 2. 定义新的 apply 函数 // 关键点我们要让它看起来像原生函数 function newApply(context, args) { // 【关键】防止死循环如果正在记录日志的函数内部又调用了 apply // 我们需要一个标记来跳过记录否则 JSON.stringify(hyx) 时可能会触发无限递归 if (newApply._isRecording) { return originalApply.call(this, context, args); } newApply._isRecording true; try { // 执行原始逻辑 const result originalApply.call(this, context, args); // 获取信息 const fnName this.name || (anonymous); // 安全序列化 let safeContext, safeArgs, safeResult; try { safeContext JSON.stringify(context); } catch(e) { safeContext [Circular/Complex]; } try { safeArgs JSON.stringify(args); } catch(e) { safeArgs [Circular/Complex]; } try { safeResult JSON.stringify(result); } catch(e) { safeResult [Circular/Complex]; } debugger const log [ NAME: fnName, THIS: safeContext, ARGS: safeArgs, RESULT: safeResult ].join(|||); // 存入数组 window.hyx.push(log); if (window.hyx.length MAX_LOGS) { window.hyx.shift(); } return result; } finally { newApply._isRecording false; } } // 3. 【防检测核心】复制原生函数的属性 // 这样别人查 Function.prototype.apply.name 或 .length 时结果和原生的一样 Object.defineProperties(newApply, { name: { value: apply, configurable: true }, length: { value: 2, configurable: true }, // apply 期望2个参数 toString: { value: () function apply() { [native code] }, configurable: true } }); // 4. 【防检测核心】使用 defineProperty 替换而不是直接赋值 // 直接赋值 (Function.prototype.apply newApply) 容易被某些框架检测到属性变化 Object.defineProperty(Function.prototype, apply, { value: newApply, writable: true, configurable: true, enumerable: false // 保持不可枚举和原生一致 }); })();(function() { // 1. 立即执行避免污染全局作用域 const originalCall Function.prototype.call; // 初始化存储 window.hyx window.hyx || []; const MAX_LOGS 2000; // 2. 定义新的 call 函数 // 【关键点】call 接收的是离散参数使用 ES6 的 Rest 参数 (...args) // 将 context 之后的所有参数收集为一个数组方便后续统一序列化 function newCall(context, ...args) { // 【关键】防止死循环如果正在记录日志的函数内部又调用了 call // 我们需要一个标记来跳过记录否则 JSON.stringify(hyx) 时可能会触发无限递归 if (newCall._isRecording) { return originalCall.call(this, context, ...args); } newCall._isRecording true; try { // 执行原始逻辑将 args 数组展开传回给原生 call const result originalCall.call(this, context, ...args); // 获取信息 const fnName this.name || (anonymous); // 安全序列化 let safeContext, safeArgs, safeResult; try { safeContext JSON.stringify(context); } catch(e) { safeContext [Circular/Complex]; } try { safeArgs JSON.stringify(args); } catch(e) { safeArgs [Circular/Complex]; } try { safeResult JSON.stringify(result); } catch(e) { safeResult [Circular/Complex]; } // debugger; // 【注意】原代码中的 debugger 已注释防止阻塞自动化脚本调试时可打开 const log [ NAME: fnName, THIS: safeContext, ARGS: safeArgs, RESULT: safeResult ].join(|||); // 存入数组 window.hyx.push(log); if (window.hyx.length MAX_LOGS) { window.hyx.shift(); } return result; } finally { newCall._isRecording false; } } // 3. 【防检测核心】复制原生函数的属性 // 这样别人查 Function.prototype.call.name 或 .length 时结果和原生的一样 Object.defineProperties(newCall, { name: { value: call, configurable: true }, // 【关键点】原生 Function.prototype.call 的 length 属性是 1代表 thisArg // 而 apply 的 length 是 2。这里必须改为 1 才能完美伪装 length: { value: 1, configurable: true }, toString: { value: () function call() { [native code] }, configurable: true } }); // 4. 【防检测核心】使用 defineProperty 替换而不是直接赋值 // 直接赋值 (Function.prototype.call newCall) 容易被某些框架检测到属性变化 Object.defineProperty(Function.prototype, call, { value: newCall, writable: true, configurable: true, enumerable: false // 保持不可枚举和原生一致 }); })();