浏览器断点调试终极技巧:搞定所有动态生成的加密函数 在JS逆向的日常工作中我们最常遇到的挑战之一就是动态生成的加密函数。很多网站为了防止被爬取会将核心的加密逻辑通过eval、new Function、document.write等方式在运行时动态生成。这些代码不会出现在静态的HTML或JS文件中传统的断点调试方法往往失效让很多逆向工程师束手无策。我见过太多新手在面对动态加密时只能一遍遍地刷新页面试图在代码生成的瞬间抓住它结果往往是浪费了大量时间却一无所获。其实只要掌握了正确的断点调试技巧无论加密函数如何动态生成我们都能精准地定位并调试它。本文将分享我在多年JS逆向实战中总结的所有断点调试终极技巧从基础的XHR断点到高级的代码注入Hook从匿名函数调试到反调试绕过手把手教你搞定所有动态生成的加密函数。一、为什么动态生成的加密函数难以调试在深入技巧之前我们首先要理解动态加密的本质和传统断点的局限性。1.1 动态代码的常见生成方式现代网站使用的动态代码生成技术主要有以下几种eval()函数最常见也最直接的方式将字符串作为代码执行new Function()构造器通过函数构造器动态创建匿名函数document.write()向文档写入脚本标签并执行动态创建script标签createElement(‘script’)然后appendChild到DOMWebAssembly将加密逻辑编译成WASM二进制在浏览器中运行代码虚拟化将JS代码转换成自定义虚拟机指令集执行1.2 传统断点的致命缺陷面对这些动态生成的代码我们平时使用的行号断点、函数断点几乎完全失效静态文件中找不到代码动态代码只存在于浏览器内存中不会出现在Sources面板的静态文件列表里函数名不固定混淆后的函数名每次刷新都会随机变化无法通过函数名永久下断点执行时机不确定加密函数可能在用户交互、定时器触发或特定条件满足时才生成反调试机制绝大多数使用动态加密的网站都会加入反调试代码检测开发者工具的存在二、动态加密函数调试的核心武器库下面我将按照从基础到高级的顺序介绍六种最实用的断点调试技巧每种技巧都配有详细的操作步骤和实战案例。2.1 XHR/fetch断点最快速的入口点这是所有JS逆向工程师必须掌握的第一个技巧也是定位加密函数最高效的方法。原理无论加密逻辑多么复杂最终生成的加密参数都要通过XHR或fetch请求发送到服务器。我们可以在请求发送前的最后一刻下断点然后通过调用栈回溯找到加密函数的源头。操作步骤打开开发者工具(F12)切换到Sources面板展开右侧的XHR/fetch Breakpoints区域点击“号输入目标请求URL的一部分比如”/api/login触发请求如点击登录按钮浏览器会自动在send()或fetch()调用处断下查看右侧Call Stack调用栈从栈顶向上回溯找到第一个非库函数的业务逻辑层关键技巧使用模糊匹配输入/api/可以拦截所有以/api开头的请求排除干扰在调用栈中右键点击jQuery、axios等库函数选择Blackbox script将其黑盒化这样单步执行时会自动跳过这些库代码查看作用域断下后在Scope面板中可以看到所有局部变量和参数包括加密前后的数据实战案例某电商平台登录sign参数逆向我曾经遇到一个电商网站它的登录接口有一个32位的sign参数。通过XHR断点断在/api/user/login请求后我在调用栈中向上翻了3层找到了一个名为_0x3a7b的函数。虽然函数名被混淆了但在Scope面板中我清楚地看到这个函数接收用户名和密码作为参数返回值就是sign参数。2.2 事件监听器断点捕获用户交互触发的加密很多加密函数是在用户点击按钮、提交表单、输入内容等事件触发时才生成和执行的。这时候事件监听器断点就派上了用场。原理浏览器允许我们在任何DOM事件触发时自动断下这样就能捕获到事件处理函数的执行。操作步骤打开Sources面板展开右侧的Event Listener Breakpoints展开对应的事件类别比如Mouse下的click事件勾选你想要监听的事件触发用户交互如点击登录按钮浏览器会在事件处理函数执行前断下逐步执行代码找到加密逻辑的入口进阶技巧使用元素选择器断点在Elements面板中右键点击目标元素选择Break on→Subtree modifications或Attribute modifications当元素内容或属性变化时自动断下移除事件监听器在Elements面板的Event Listeners标签中可以看到元素绑定的所有事件监听器点击Remove可以移除不需要的监听器2.3 匿名函数断点给没有名字的函数下断点动态生成的函数绝大多数都是匿名函数没有函数名无法直接通过函数名下断点。但我们有三种方法可以解决这个问题。方法1在VM文件中直接下断点当动态代码执行时浏览器会为它生成一个临时的VM文件形如VM12345显示在Sources面板的(no domain)目录下。你可以直接在这些VM文件的代码行号上点击下断点。方法2使用Function.prototype.toString Hook重写Function.prototype.toString方法当匿名函数被创建或调用时插入debugger语句constoriginalToStringFunction.prototype.toString;Function.prototype.toStringfunction(){// 只拦截包含加密特征的函数if(this.name(this.toString().includes(encrypt)||this.toString().includes(sign)||this.toString().includes(AES))){console.log(捕获到加密函数:,this.toString());debugger;}returnoriginalToString.call(this);};方法3使用arguments.callee在匿名函数内部可以通过arguments.callee引用函数本身然后设置断点// 在控制台执行这段代码给所有匿名函数添加断点constoriginalCallFunction.prototype.call;Function.prototype.callfunction(...args){if(this.name){debugger;}returnoriginalCall.apply(this,args);};2.4 代码注入断点Hook所有动态代码执行这是我认为最强大、最通用的技巧几乎可以搞定所有动态生成的代码。原理所有动态生成的代码最终都会通过eval、new Function、setTimeout等方法执行。我们可以重写这些原生方法在代码执行前插入debugger语句从而捕获所有动态代码。完整Hook代码(function(){// Hook eval函数constoriginalEvalwindow.eval;window.evalfunction(code){console.log( 执行eval代码 );console.log(code);debugger;// 在这里断下returnoriginalEval(code);};// 伪装成原生函数防止被检测window.eval.toStringfunction(){returnfunction eval() { [native code] };};// Hook Function构造函数constoriginalFunctionwindow.Function;window.Functionfunction(...args){constcodeargs[args.length-1];console.log( 创建新函数 );console.log(code);debugger;// 在这里断下returnoriginalFunction.apply(this,args);};window.Function.toStringfunction(){returnfunction Function() { [native code] };};// Hook setTimeout和setIntervalconstoriginalSetTimeoutwindow.setTimeout;window.setTimeoutfunction(callback,delay,...args){if(typeofcallbackstring){console.log( setTimeout执行字符串代码 );console.log(callback);debugger;}returnoriginalSetTimeout.call(this,callback,delay,...args);};constoriginalSetIntervalwindow.setInterval;window.setIntervalfunction(callback,delay,...args){if(typeofcallbackstring){console.log( setInterval执行字符串代码 );console.log(callback);debugger;}returnoriginalSetInterval.call(this,callback,delay,...args);};console.log(动态代码Hook已启用所有动态执行的代码都会被捕获);})();使用方法打开目标网站打开Console面板粘贴上面的代码并回车执行触发动态代码执行如刷新页面、点击按钮浏览器会自动在动态代码执行前断下你可以看到完整的代码内容实战案例某金融网站交易密码加密我曾经遇到一个非常顽固的金融网站它的交易密码加密函数是通过eval动态生成的而且每次刷新都会变化。传统的XHR断点只能断在请求发送时但加密函数已经执行完毕无法看到加密过程。使用上面的Hook代码后当eval执行加密函数代码时浏览器自动断下。我在Console面板中看到了完整的AES加密代码包括密钥和IV轻松还原了加密算法。2.5 调试器语句断点利用反调试反制对手很多网站会在代码中加入无限debugger语句来阻止开发者调试。但我们可以反过来利用debugger语句通过条件断点来过滤掉无用的debugger只在我们需要的时候断下。常见的无限debugger形式// 形式1直接debuggersetInterval(function(){debugger;},1000);// 形式2eval混淆型const_0x1234[debugger];setInterval(function(){eval(_0x1234[0]);},1000);// 形式3Function构造器setInterval(Function(debugger),1000);绕过方法条件断点法在debugger语句所在行右键点击选择Add conditional breakpoint输入条件false这样这个debugger就永远不会触发Never pause here在debugger语句所在行右键点击选择Never pause here浏览器会自动跳过这行的所有断点Hook法使用上面介绍的代码注入Hook在动态代码执行前移除debugger语句window.evalfunction(code){// 移除所有debugger语句codecode.replace(/debugger\s*;?/gi,);returnoriginalEval(code);};2.6 本地覆盖断点永久修改动态代码对于一些特别复杂的动态加密我们可能需要多次修改代码进行调试。这时候本地覆盖功能就非常有用了。原理Chrome开发者工具允许我们将远程的JS文件替换成本地的文件这样我们就可以永久修改代码添加断点和调试信息。操作步骤打开Sources面板找到你想要修改的JS文件右键点击该文件选择Save for overrides选择一个本地文件夹来保存覆盖文件在Sources面板中编辑保存后的文件添加断点或修改代码刷新页面浏览器会自动加载你修改后的本地文件注意对于VM文件动态生成的代码无法直接使用本地覆盖。但我们可以先通过Hook捕获动态代码将其保存到本地然后创建一个本地覆盖文件将动态代码替换成我们修改后的版本。三、综合实战搞定最复杂的动态加密函数现在我们通过一个完整的实战案例来演示如何综合运用上面的技巧搞定一个使用多种动态生成技术的网站。案例背景某社交网站的私信发送接口有一个非常复杂的加密参数token。这个token的生成过程如下页面加载时从服务器获取一段AES加密的JS代码前端使用硬编码的密钥解密这段代码然后用eval执行执行后生成一个匿名加密函数存储在全局变量window._encrypt中当用户发送私信时调用window._encrypt函数对私信内容和时间戳进行加密生成token调试步骤步骤1使用XHR断点定位请求首先我在XHR/fetch Breakpoints中添加了/api/send_message断点。当我发送一条私信时浏览器自动断在了请求发送前。查看调用栈我发现最上层是一个匿名函数位于VM12345文件中。点击这个函数我看到了加密后的token但看不到加密过程因为加密函数已经执行完毕了。步骤2使用代码注入Hook捕获动态代码于是我在Console面板中执行了上面的Hook代码然后刷新页面。这次当eval执行解密后的JS代码时浏览器自动断下了。我在Console面板中打印出了这段代码发现它确实是一个加密函数的定义window._encryptfunction(content,timestamp){// 复杂的AES加密逻辑constkeyxxxxxxxxxxxxxxxx;constivxxxxxxxxxxxxxxxx;// ... 省略100多行加密代码returnencrypted;};步骤3在加密函数中添加断点现在我已经看到了完整的加密函数代码。我在函数的第一行添加了一个断点然后再次发送私信。这次浏览器成功断在了加密函数执行的开始位置。我单步执行代码在Scope面板中看到了加密前的明文内容、密钥、IV和加密后的密文。通过分析代码我很快还原了token的生成算法。步骤4验证算法正确性最后我在Console面板中手动调用window._encrypt函数传入测试内容和时间戳得到的结果与网站生成的token完全一致说明我的分析是正确的。四、动态加密函数调试通用流程经过多年的实战我总结出了一套标准化的动态加密函数调试流程几乎可以应对所有场景是否否是发现动态加密参数尝试XHR/fetch断点是否找到加密函数?调试加密逻辑尝试事件监听器断点使用代码注入Hook eval/new Function捕获动态生成的代码还原加密算法编写爬虫代码遇到反调试?使用条件断点或Hook绕过五、总结与进阶建议断点调试是JS逆向工程师的基本功也是应对动态加密最有效的武器。很多新手觉得动态加密难其实只是没有掌握正确的调试方法。本文介绍的这六种技巧是我在多年实战中总结出来的精华。从最基础的XHR断点到最强大的代码注入Hook从匿名函数调试到反调试绕过几乎可以覆盖所有动态生成加密函数的场景。记住无论网站的反爬技术多么先进加密逻辑多么复杂最终都要在浏览器中执行。只要代码在浏览器中执行我们就有办法调试它。掌握了这些断点调试技巧你就能在爬虫攻防战中占据主动轻松搞定任何动态加密函数。最后给大家一个进阶建议当你熟练掌握了这些调试技巧后可以尝试开发自己的调试工具。比如你可以写一个Chrome插件自动Hook所有动态代码执行自动还原常见的混淆代码这样可以大大提高你的逆向效率。 点击我的头像进入主页关注专栏第一时间收到更新提醒有问题评论区交流看到都会回。