JS逆向实战——绕过无限debugger与性能检测

📅 2026/7/13 13:34:41 👁️ 阅读次数 📝 编程学习
JS逆向实战——绕过无限debugger与性能检测

1. 无限debugger的运作原理与识别

无限debugger是网站用来阻止开发者调试JavaScript代码的常见手段。它的核心原理很简单:通过不断触发debugger语句,让开发者工具陷入无限暂停状态。我遇到过最夸张的情况是一个页面里埋了十几处debugger调用,每次单步执行都会卡住。

这种机制通常有两种实现方式:

第一种是直接循环调用:

setInterval(function(){ debugger; }, 100);

第二种是通过函数递归:

(function trap(){ debugger; trap(); })()

在实际逆向中,更常见的是经过混淆的变种。比如把debugger拆分成字符串拼接:

var a = "deb"; var b = "ugger"; setInterval(function(){ Function(a+b)(); }, 50);

识别这类防护有个小技巧:在Chrome开发者工具中打开Performance面板录制几秒,如果看到频繁的"Script Execution"事件,很可能就是遇到了无限debugger。另一个特征是页面会变得异常卡顿,因为CPU资源都被用来执行这些无意义的循环了。

2. 绕过无限debugger的四种实战方法

2.1 断点禁用大法

Chrome DevTools提供了直接禁用断点的功能:

  1. 在Sources面板找到触发debugger的代码行
  2. 右键选择"Never pause here"
  3. 刷新页面即可跳过这个断点

这个方法适合debugger位置固定的场景。我经常用它来处理简单的反调试,但遇到动态生成的debugger就力不从心了。

2.2 函数重写技巧

更彻底的方案是直接重写关键函数:

// 禁止所有debugger语句 Function.prototype.constructor = function() { return function(){}; }; // 或者更精准的拦截 var oldFunction = Function; Function = function(...args) { if(args[0] && args[0].includes('debugger')) { return function(){}; } return oldFunction(...args); };

实测这个方法的兼容性最好,能应对大多数变种。不过要注意执行时机,必须在页面加载前注入这段代码。

2.3 条件断点妙用

在Sources面板可以设置条件断点:

  1. 找到debugger所在行
  2. 右键选择"Add conditional breakpoint"
  3. 输入条件如"false" 这样断点就永远不会触发,相当于被静默跳过了。

2.4 脚本注入方案

对于顽固的案例,可以用Chrome Overrides功能:

  1. 在Sources面板点击"Overrides"
  2. 选择本地文件夹作为覆盖目录
  3. 找到目标JS文件,修改后保存
  4. 刷新页面会自动加载修改后的版本

我通常会直接删除所有debugger语句,或者把setInterval的时间改成60000毫秒让它基本不触发。

3. 性能检测的常见形式与破解

3.1 Performance API检测

现代网站常用Performance API来检测调试:

const start = performance.now(); debugger; const end = performance.now(); if(end - start > 100) { location.reload(); // 判定为调试状态 }

破解方法是重写performance方法:

const originalNow = performance.now; performance.now = function() { return originalNow.call(performance) * 0.1; // 人为降低时间差 };

3.2 执行时间差检测

另一种是基于Date的时间检测:

var start = new Date(); for(var i=0; i<100000; i++){} var end = new Date(); if(end - start > 50) { // 疑似调试 }

对抗方案是hook Date构造函数:

var originalDate = Date; Date = function() { if(new.target) { return new originalDate(originalDate.now() - 50); } return originalDate(); };

3.3 内存占用检测

高级的反调试会监控内存变化:

const startMem = performance.memory.usedJSHeapSize; // 执行一些操作 const endMem = performance.memory.usedJSHeapSize; if(endMem - startMem > 1e6) { // 可能在被调试 }

对应的破解代码:

Object.defineProperty(performance.memory, 'usedJSHeapSize', { get: function() { return 1e7; // 固定返回值 } });

4. 综合实战:某网盘网站案例分析

最近逆向一个网盘网站时遇到了组合防护:

  1. 无限debugger循环
  2. Performance API检测
  3. 控制台打开检测

最终绕过方案如下:

// 在控制台一次性执行 (() => { // 禁用debugger Function.prototype.constructor = function() { return function(){}; }; // 伪造performance数据 const origPerf = performance; window.performance = { now: () => origPerf.now() * 0.1, memory: {usedJSHeapSize: 1e7} }; // 屏蔽控制台检测 window.console.log = ()=>{}; Object.defineProperty(window, 'console', { get: () => ({log:()=>{}}) }); })();

关键是要在页面加载前执行这些代码。我通常通过以下方式注入:

  1. 使用Chrome插件Tampermonkey
  2. 配置DevTools的"Local Overrides"
  3. 通过Mitmproxy等代理工具注入

这些方法各有优劣,Tampermonkey最方便但可能被检测,代理注入最彻底但配置复杂。根据目标网站的防护强度选择合适的方案很重要。