首页 / 资讯中心 / 文章详情

无限debugger绕过与Cookie签名算法还原:JS逆向从卡死到破解

无限debugger绕过与Cookie签名算法还原:JS逆向从卡死到破解 ★ FEATURED ARTICLE
敲下F12的那一瞬间页面整个卡住。断点像被人焊死在代码里你按F8它停一秒再按再停永远有下一个debugger在等你。遇到这种场景登录流程里那个写了半天都摸不清的Cookie就会显得格外遥远。做JS逆向的人对这东西不会陌生网上一般叫它无限debbuger严格说其实是无限debugger。这篇文章就来完整复盘一次从“F12被卡死”到“Cookie算法彻底还原”的全过程。你不光会看到无限debugger的几种常见写法还有针对每一种写法的破解思路更重要的是我会把如何定位Cookie生成入口、如何顺着调用栈找到签名算法、如何写脚本复现验证这一整条分析链路走一遍。适合正在学习前端JS逆向的开发者也适合负责前端安全评估、内部系统调试的朋友。无论你是哪种身份都请先确认一件事你要分析的代码来自你有权测试或已获授权的项目。1. 无限debugger不是玄学先搞清它在代码里干了什么1.1 debugger语句的底层机制很多新手第一次接触无限debugger时会觉得这是什么高深的反调试手段其实它的核心就是一个普通的JavaScript关键字debugger。这个关键字在浏览器里有一个明确的行为只要JavaScript运行到这一行就强制暂停执行不管你有没有手动打断点。你可以把它理解成代码里预设了一个断点。正常开发时开发者偶尔会把它当作调试辅助手段但一旦把它放进一个定时器或者递归函数里它就变成了无法摆脱的“断点循环”。一个最原始的写法长这样function freeze() { debugger; } setInterval(freeze, 100);这段代码的逻辑很简单每100毫秒执行一次freezefreeze又一定会触发debugger。在DevTools打开的状态下脚本一执行到debugger就会暂停。你按一次继续它会在下一次定时器触发时再次暂停。如此反复页面就像被冻住一样你根本没法正常看代码、翻调用栈、或者在Console里做下一步操作。所以它的核心原理就是利用debugger关键字的强制暂停能力配合定时器或循环让你在调试器里几乎无法工作。1.2 三种常见的无限debugger变体实战里遇到的写法远不止setInterval这一种。我拆过几个不同的前端站点遇到过至少三种变体每一种都有对应的绕过思路所以值得分开说。第一种就是上面那种setInterval加普通debugger语句。这种最耿直代码里能直接搜到debugger关键字绕起来也最简单关闭断点或Hook定时器都能解决。第二种是用Function构造函数动态生成debugger语句常见写法是这样的setInterval(function() { return (function() { undefined.constructor.prototype.constructor.constructor(debugger)(); })(); }, 60);这个写法乍一看很绕本质上就是通过一串原型链访问最终调用Function构造函数把字符串“debugger”动态编译成可执行代码。好处是文件里不直接出现“debugger”这个字面量你用编辑器全局搜索是搜不到的。想要绕过得在Function这一层做拦截。第三种是把debugger包在递归或者大量try-catch嵌套里让你即使找到了也不好定位。有些还会配合时间戳判断比如每隔几秒才触发一次目的是干扰你复现。这三种变体在某些内部系统里甚至会混合出现。我处理过一个场景页面不仅用setInterval触发debugger还在多个闭包里保存了原生setInterval的引用导致常规Hook失效。这个坑后面会细说它直接决定你用哪条绕过路线。1.3 为什么它能拦住那么多分析者无限debugger本质上不复杂但它的杀伤力在于“消耗耐心”而不是“增加难度”。做JS逆向的人都知道分析一个前端逻辑最耗时间的往往不是算法本身而是你连正常的运行节奏都打乱之后根本没法快速试错。举一个生活化的例子你本来想按顺序读完一本书的某一章但每隔两三页就有一页被人用胶水粘住了你翻开之前必须先把胶水处理掉。多来几次阅读节奏就全乱了。无限debugger就是那层胶水它不能阻止你最终读到内容但能让你在读的过程中反复停下来极其烦躁。从防御者的角度看用这个手段的性价比非常高一行代码就能拖住大量分析者。而从分析者的角度看想要绕过去不能靠蛮力必须理解它的运行机制然后选择合适的Hook点。下一节我就按实操顺序把三条绕过路线完整展开。2. 绕过无限debugger三条路线逐个说清适用条件2.1 路线一一键关闭所有断点适合快速跳过最简单的办法是使用Chrome DevTools里的“Deactivate breakpoints”功能快捷键CtrlF8。点击之后当前页面所有断点都会被停用包括代码里的debugger语句。页面会像没有断点一样正常跑你也能手动在代码里加新的断点。这个方案的优点就是快几乎不需要动脑。但它有几个非常明显的局限第一如果页面做了DevTools检测你一打开控制台就会触发别的手段比如强制跳转、清空页面、或者拒绝执行核心逻辑第二关闭所有断点之后你可能也没法在后续的关键位置手动打断点了调试体验其实是有损失的第三对于一些挂在Worker线程里的定时器CtrlF8未必管得住。所以我的建议是如果你只是确认页面能不能跑、想快速看一下整体流程可以用这个方案。要是后面还需要深入分析Cookie生成逻辑那就别依赖这条路直接走Hook或替换。2.2 路线二Hook定时器与构造函数从源头干掉debugger面对需要深入分析的场景我更推荐用Hook的方式在debugger还没执行前就把它的触发源去掉。这里说的Hook就是改写JavaScript中的全局函数。以setInterval为例正常的调用链是代码执行setInterval(callback, delay)然后浏览器按照delay反复执行callback。我们可以把window下的setInterval替换成自己的函数在里面检查callback的内容一旦它跟debugger有关就直接忽略const originalSetInterval window.setInterval; window.setInterval function(callback, delay) { const cbStr callback.toString(); if (cbStr.includes(debugger)) { return 0; } return originalSetInterval(callback, delay); };把这段代码贴在DevTools的Console里再刷新页面。原有的debugger定时器回调会被跳过其余正常的定时器照常工作。有个细节必须提醒在Console里执行Hook代码时页面可能已经加载了很多脚本了如果闭包里已经保存了原生setInterval引用那你改window.setInterval对它是无效的。所以更可靠的做法是提前注入比如用Chrome开发者工具的Sources面板里的Snippets功能保存好Hook脚本在页面刷新前先执行一遍然后再刷新页面。遇到Function构造器生成debugger的变体思路也类似。把Function构造器包一层检查传入字符串如果包含“debugger”就不执行原构造器const originalFunction window.Function; window.Function function(...args) { const code args[args.length - 1] || ; if (code.includes(debugger)) { return function() {}; } return originalFunction.apply(this, args); };这种Hook方案的优点是不改变原始代码文件只是在运行时过滤掉有害逻辑缺点是被反检测到时可能失效需要配合其他手段使用。2.3 路线三本地替换与代理改包直接改写JS文件当Hook一直被绕、或者页面存在严格的完整性校验时最彻底的方式就是直接把JS文件改写掉去掉里面的debugger逻辑。这里有两个常用工具Chrome DevTools的Local Overrides以及抓包工具Fiddler的AutoResponder。先说Local Overrides。打开DevTools的Sources面板找到目标JS文件右键选择Save for overrides之后浏览器会让你选择一个本地目录保存文件。点击保存后文件内容会出现在Sources面板里你可以直接修改把debugger语句删掉或者把setInterval那行注释掉。修改后保存刷新页面浏览器会优先加载本地这份替换后的文件。这个方法很干净适合脚本不太大、或者你能快速定位到关键代码的场景。但它有一个前提如果JS文件是动态生成的或者内容里加了时间戳校验替换后可能因为签名不匹配而直接加载失败。这时候就得用Fiddler改包了。Fiddler的AutoResponder可以实现类似功能你先把目标JS文件下载到本地修改后在AutoResponder里配置一条规则把线上该文件的URL映射到本地文件。浏览器请求这个JS时Fiddler会直接返回本地版本这就是一种“改包”思路。它在面对校验不严格的场景时非常稳定而且可以作用在页面加载的全阶段不用你手动抢时间注入。不过要说清楚本地替换只适合用于你有权调试的代码。如果目标JS做了完整性校验比如Subresource Integrity替换后可能被浏览器拦截这时候继续强行改包的意义就不大了不如回去看前端逻辑本身有什么漏洞可以利用。2.4 三条路线的横向对比与选型建议我在处理不同项目时三套方案都用过。它们并不是互斥的经常要组合使用。比如先用CtrlF8快速跑一遍再用Hook定位到具体触发点最终用本地替换完成长期调试。为了方便你参考我整理了一个简单的对比表方案优点缺点适用场景CtrlF8关闭断点最快零成本可能被反调试检测干扰无法精细打断点快速验证页面流程Hook定时器/构造器不改源文件能保留运行环境闭包引用、Worker线程可能绕过需要深入分析页面逻辑时本地替换/改包彻底消除debugger可反复调试大文件定位难可能被校验拦截需要长期调试同一份代码时从我个人的经验来看Hook是性价比最高的一条路它保留了页面的运行环境能让后续Cookie生成逻辑正常执行又不会像CtrlF8那样把所有断点都屏蔽。后面第四节的模拟项目X我用的就是Hook方案。3. 从Cookie入口切入Hook写入、定位调用链、还原生成算法3.1 先分清Cookie是后端下发还是前端生成在动手分析Cookie之前第一步不是打开DevTools而是先判断目标Cookie到底来自哪里。这个判断错了后面所有工作都会白费。Cookie的生成位置通常有两种一种是服务端通过Set-Cookie响应头下发浏览器自动保存前端代码碰不到另一种是前端JavaScript通过document.cookie写入这属于纯前端生成的Cookie才是JS逆向要重点分析的。怎么判断可以在Network面板里看登录接口的响应头。如果有Set-Cookie字段而且Cookie名字对应上了那就主要看后端逻辑JS逆向意义不大如果响应头干干净净但页面上又确实存在某个Cookie那它大概率是前端通过脚本写入的。还有一种情况是混合式后端下发了一个临时票据前端基于这个票据加上时间戳、设备信息等进行计算生成一个新的签名Cookie再写入浏览器。这种模式在大型平台的登录体系里很常见也是分析起来最有意思的一类。3.2 用setter Hook拦截document.cookie赋值确认目标Cookie是前端生成之后接下来就要精准地抓住写入动作。最直接的办法就是给document.cookie设置一个setter拦截。在Console里执行下面这段const cookieDescriptor Object.getOwnPropertyDescriptor(Document.prototype, cookie); Object.defineProperty(document, cookie, { get() { return cookieDescriptor.get.call(document); }, set(value) { debugger; return cookieDescriptor.set.call(document, value); } });执行完这段代码之后只要页面任何JavaScript往document.cookie里写值都会被这个拦截到同时触发debugger停住。这时候在DevTools的Console面板旁边找到Call Stack就能看到写入动作完整调用链。有朋友可能问为什么是Document.prototype而不是document自身因为我试过在部分浏览器里直接给document.cookie下定义会失败而Document.prototype上的原始描述符才是规范入口。用Prototype层面的Hook兼容性更好这也是我在实际项目里踩过几次坑之后沉淀下来的经验。需要提醒的是在捕获到写入动作之前页面可能会触发其他JavaScript逻辑所以建议先关闭其他无关断点确保自己第一时间能看到目标调用栈。3.3 顺着调用栈找到加密函数并识别算法形态一旦断点触发你会在Call Stack面板里看到一串函数调用记录。比如某个登录流程里Cookie的写入路径可能是这样某个按钮的click回调一个名为composeSignature的函数一个名为cryptoHelper的内部方法最终落到document.cookie的setter这时候不要急着关掉断点先展开这些调用帧看每一层的参数返回值。通常越往下层的函数越接近算法核心。比如composeSignature接收了“用户名时间戳设备指纹”这样的参数最终返回一个字符串那这个字符串大概率就是Cookie的value。到了这一步核心问题就变成了这个返回字符串是怎么算出来的常见的形态有Base64编码、十六进制编码、各种哈希算法MD5、SHA1、SHA256、自定义加盐拼接、还有可能是模拟某种对称加密。判断方法也很直接观察返回值长度和字符集Base64有固定特征号结尾、字母数字加/十六进制就是一串0-9a-f哈希通常是固定长度。只要特征对上就能缩小查找范围。3.4 参数格式的判断编码、哈希与加盐我举一个很典型的例子。假设最终写入的Cookie值是这样一个字符串dXNlcm5hbWU9YWRtaW4udGltZXN0YW1wPTE3MjAwMDAwMDAuc2FsdD1zZWNyZXQ一眼能看出末尾有字符集是Base64。解码之后内容是“usernameadmin.timestamp1720000000.saltsecret”。这种就是典型的“明文加盐拼串再做Base64编码”。但如果遇到32位长度的16进制字符串那大概率就是MD5遇到40位长度的16进制字符串很可能是SHA1遇到64位则可能是SHA256或者自定义处理。还有一种情况是算法经过多次混淆比如先拼接再MD5再转Base64再倒序这种就需要你在本地复现时仔细比对每一步的输出。实际操作中我的经验是先抓入参再抓出参最后在本地写脚本逐一验证中间步骤。不要试图逆向混淆代码本身那都是在浪费时间。绝大多数算法的还原靠的是对最终输出的结构判断和逐步拟合。4. 一场完整的移除debugger加Cookie还原演练4.1 搭建可复现的模拟项目X光讲理论容易飘我带你走一遍实际的操作流程。为了便于说明我搭了一个模拟项目X它模拟了某内部管理系统中的登录页面。在这个页面里用户登录后会自动生成一个名为sign的Cookie。同时页面被设置了一个每隔60毫秒触发一次的无限debugger。这个模拟页面的核心JS文件cookie.js内容如下const salt fixed_salt_2024; const genSign function(user) { const timestamp Date.now(); const raw ${user}.${timestamp}.${salt}; return btoa(raw); }; setInterval(function() { debugger; }, 60); function init() { const currentUser demo_user; document.cookie sign${genSign(currentUser)};path/; } init();你可以把这段代码保存下来在自己本地环境打开跟着我下面的操作步骤跑一遍。这个模拟场景里的反调试就是最基础的setInterval加debugger但整个分析流程跟真实项目完全一致。4.2 第一阶段用Hook清除无限debugger打开DevTools页面果然卡死Console里面密密麻麻全是暂停信息。我没有着急先把CtrlF8关闭断点的方案跑了一次页面确实不再卡住了但我要分析的是整个Cookie生成链路关掉全部断点会让我后面很难下手。所以我选择了第二种方案Hook setInterval。在Console里执行const nativeSI window.setInterval; window.setInterval function(callback, delay) { if (callback.toString().includes(debugger)) { return 0; } return nativeSI(callback, delay); };执行之后刷新页面。这次页面正常运转不会再被debugger卡住。为了确认Hook生效我手动在Console里查看了定时器的execute情况发现setInterval已经不再注册那个debugger的定时任务了。这个过程在真实项目中可能不会这么顺利因为会存在闭包引用和Worker线程的问题这点我在第五节再展开。但模拟项目里这一阶段大概只花了一分钟。4.3 第二阶段捕获Cookie写入并锁定签名函数清除无限debugger只是热身真正的重点在于Cookie生成逻辑。我在Console里继续执行document.cookie的setter Hook代码const cookieDescriptor Object.getOwnPropertyDescriptor(Document.prototype, cookie); Object.defineProperty(document, cookie, { get() { return cookieDescriptor.get.call(document); }, set(value) { debugger; return cookieDescriptor.set.call(document, value); } });再次刷新页面。脚本执行到写入Cookie那行时断点成功触发。我在Call Stack里看到了一条清晰的调用链init - genSign - document.cookie的setter。这意味着sign这个Cookie的值就是genSign函数返回的结果。我直接在Console里手动执行了genSign函数的内部逻辑把参数固定为demo_user。给它传入一个固定的时间戳比如1720000000000然后加上固定盐值“fixed_salt_2024”最终得到Base64字符串。把浏览器里真实产生的Cookie拿过来一对比格式完全一致。到这里核心算法已经浮出水面用户名、时间戳、固定盐值三件事拼在一起Base64编码没有复杂的加密。4.4 第三阶段在Node里复现算法完成验证只靠肉眼对比还不够稳妥尤其真实项目中算法更复杂必须用脚本复现才能确认。我在Node环境里写了一个验证脚本模拟同样的拼接和编码逻辑function genSign(user) { const salt fixed_salt_2024; const timestamp Date.now(); const raw ${user}.${timestamp}.${salt}; return Buffer.from(raw).toString(base64); } console.log(genSign(demo_user));运行几组不同的用户和时间戳把输出与模拟页面里捕获的Cookie值做对照。两者完全一致说明分析结论成立。在真实场景中算法的验证会更耗时但思路完全一样输入端构造相同参数输出端比对签名结果一旦吻合就等于把算法复刻出来了。5. 从实战中沉淀的高频问题与避坑心得5.1 Hook被绕过的三个根因与解决办法Hook虽然好用但在真实项目里经常遇到失效的情况我自己踩过好几次坑原因主要是这三个第一个是闭包保存了原生引用。比如JS代码在最顶部就执行了const si window.setInterval.bind(window)后面代码里一直用si而不是window.setInterval。这样一来我改window.setInterval根本碰不到它。解决办法是用更早的注入时机比如通过Snippets或者抓包工具在页面任何脚本运行之前先把Hook装上。第二个是Worker线程独立环境。浏览器的Web Worker线程有自己的一套全局对象主线程的Hook对它不生效。遇到这种情况需要单独找到Worker创建的入口在那段代码里同样做setInterval的拦截。第三个是Hook代码写错了时机。如果你在Console里执行Hook的时刻页面脚本已经跑完了那当然拦截不到已经注册过的定时器。先Hook再刷新顺序千万别反。5.2 页面检测到调试状态时的特殊处理有些页面不只用无限debugger还会用DevTools检测手段。典型特征是你开着DevTools刷新页面页面表现和正常打开完全不一样轻则控制台被反复清空重则直接跳转或者白屏。遇到这种情况我的建议是先别急着开DevTools。用无头浏览器工具把页面跑一遍导出页面用到的JS文件在本地分析代码。等搞清楚关键逻辑再决定要不要回到浏览器上下文中做动态Hook。这样既绕开了检测又能继续分析Cookie算法。如果必须在浏览器里调试可以试试通过修改DevTools的窗口尺寸、UA等参数来规避检测但这种做法比较依赖特定环境不如无头浏览器稳妥。5.3 混淆代码读不懂时该从哪下手很多真实项目的JS都是混淆过的变量名全是a、b、c函数名被转成乱码看着就头疼。我之前也硬着头皮逐行读结果效率极低后来总结出一套更实用的方法不要试图读懂每一行只抓入口和出口。入口就是Cookie setter Hook触发时的调用栈出口就是最终返回给我们看的那个Cookie值。在这两个锚点之间任何函数都可以当作黑盒处理先在函数入口打印入参在函数返回前打印出参。通过多跑几次不同入参观察输出变化就能判断函数内部大概做了什么。比如入参多了1秒出参就完全变样大概率是掺入了时间戳入参固定但出参不变可能用了静态盐值或者固定逻辑。这个方法比逐行读混淆代码快得多也是我认为JS逆向里最有价值的实战技巧之一。5.4 边界意识这些技术只能用在合法授权的项目中写到这里我想专门把这件事单独拿出来说。整套无限debugger的绕过、Cookie生成逻辑的还原本质上都是JavaScript代码分析技术。它们本身不违法但使用边界必须清楚。我把这套流程应用在两类场景一是自己开发或维护的系统二是公司安排的、有明确授权的安全测试和风险排查。在这些场景里分析Cookie算法可以帮助我们发现前端是否存在敏感信息泄露、签名机制是否容易被伪造等问题这是正向的安全价值。反过来未经授权去分析别人线上系统的Cookie生成机制哪怕只是为了学习也可能越过边界。所以这篇记录里的所有操作请你一定只在自己有权限的代码和环境中尝试。在多次处理这类反调试代码之后我最大的体会是无限debugger这类手段并不高深它的战斗力几乎全部来自“消耗耐心”。很多人不是破解不了而是被反复暂停搞到失去分析的节奏。只要你的思路清晰先搞清原理再选择Hook或替换最后顺着Cookie入口往上追绝大部分前端反调试和Cookie算法还原都能在一个小时左右拆完。希望这篇记录能帮你少走一些弯路。
阅读完成 · 觉得有帮助?
咨询建站