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提供了直接禁用断点的功能:
- 在Sources面板找到触发debugger的代码行
- 右键选择"Never pause here"
- 刷新页面即可跳过这个断点
这个方法适合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面板可以设置条件断点:
- 找到debugger所在行
- 右键选择"Add conditional breakpoint"
- 输入条件如"false" 这样断点就永远不会触发,相当于被静默跳过了。
2.4 脚本注入方案
对于顽固的案例,可以用Chrome Overrides功能:
- 在Sources面板点击"Overrides"
- 选择本地文件夹作为覆盖目录
- 找到目标JS文件,修改后保存
- 刷新页面会自动加载修改后的版本
我通常会直接删除所有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. 综合实战:某网盘网站案例分析
最近逆向一个网盘网站时遇到了组合防护:
- 无限debugger循环
- Performance API检测
- 控制台打开检测
最终绕过方案如下:
// 在控制台一次性执行 (() => { // 禁用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:()=>{}}) }); })();关键是要在页面加载前执行这些代码。我通常通过以下方式注入:
- 使用Chrome插件Tampermonkey
- 配置DevTools的"Local Overrides"
- 通过Mitmproxy等代理工具注入
这些方法各有优劣,Tampermonkey最方便但可能被检测,代理注入最彻底但配置复杂。根据目标网站的防护强度选择合适的方案很重要。