资讯动态

JS逆向全流程实战:从浏览器断点调试到加密参数还原

发布时间:2026/10/18 7:07:24 来源:尧图企业网站定制
懂行的朋友都知道网上关于JS逆向的教程要么太零散、只讲某个点要么门槛太高、上来就甩一堆混淆代码让人劝退。今天这篇不整虚的就从一个纯粹的实操视角把从打开浏览器到抠出加密参数的全流程拆开揉碎讲清楚。标题里提到的“断点”“参数还原”本质上就是一套方法论找到入口、定位逻辑、还原算法、验证结果。这篇文章会带着你走完整个链路顺便把那些教程里不会明说的坑都填上。先说清楚这篇文章适合谁看想入门爬虫但被加密参数卡住的新手写前端代码想反向理解别人怎么调试的开发者以及所有对浏览器调试工具只停留在“看看Network”阶段的同学。如果你已经能熟练发起请求、能看到接口返回数据但对那个突然冒出来的sign、token、params一头雾水那你来对地方了。1. 内容整体设计与思路拆解1.1 JS逆向到底在“逆”什么很多人一听到“逆向”就觉得是要破解什么东西其实放到JS逆向这个语境下核心目标通常只有一个搞清楚某个加密参数是怎么生成的然后在自己的代码里复现它的生成过程。举个例子某网站的登录接口明文传的是用户名和密码但抓包发现实际请求参数里多了一个password的加密值和一串sign。那么问题就变成了这个加密值是拿什么算法、什么密钥、什么拼接顺序算出来的这本质上是一个“黑盒还原”的过程。你不需要去破解什么高深的密码学原理大多数时候你只是在做一件事找到生成这些参数的那段JavaScript代码读懂它然后在本地把它跑起来。从技术栈上看JS逆向依赖的核心能力有三个浏览器开发者工具特别是Sources面板、对JavaScript语法和运行机制的基本理解、以及一点耐心。前者是战场后两者是武器。1.2 为什么选择“浏览器断点”作为切入点市面上还流行另一种思路直接下载网页的JS文件丢进本地Node环境里面跑通过补环境、改代码来让加密函数吐结果。这种做法确实有效但对新手极不友好因为你不知道哪些环境变量缺了哪些接口没模拟报错信息又看不懂。相比而言在浏览器里直接下断点、动态调试是更符合人类直觉的方式。浏览器本身就是JS代码的完整运行环境所有变量、函数、调用栈都是现成的你不需要额外搭建任何模拟环境只需要“观察”代码在真实运行时发生了什么。打个比方前者像是你拿到一台拆散的机器要自己把所有零件装回去才能知道它怎么运转后者就像你站在一台正在运行的机器旁边通过透明外壳直接观察齿轮怎么咬合、皮带怎么传动。1.3 整体流程梳理从入口到还原的五个阶段整个JS逆向的实操流程我习惯把它拆成五个阶段这也是这篇文章的主线抓包定位找到目标接口确定需要逆向的参数名。全局搜索在源码里搜索参数名锁定可疑的JS代码位置。断点调试在可疑位置下断点观察调用过程回溯参数生成逻辑。逻辑还原把加密函数用Python、Node或者纯JavaScript复现出来。验证集成在本地脚本里调用还原后的函数与浏览器结果比对。这五个阶段环环相扣每一阶段都有对应的坑。下面我会逐一展开把关键的技术点和容易翻车的地方都说明白。2. 核心细节解析与实操要点2.1 抓包定位别急着看加密先弄清请求长什么样很多人一上来就搜sign搜完还搜不到就开始怀疑自己眼睛有问题。实际上90%的搜索失败是因为没搞清楚要搜什么。抓包阶段你需要确认的不是“有哪些参数”而是以下三件事触发时机是页面加载时就自动发起还是点了某个按钮才发起参数归属加密参数是在URL的query里?xxxabc还是在请求体Request Payload里还是在请求头Headers里是否动态变化同一个请求连续发两次加密参数一不一样如果不一样大概率带了时间戳或随机数。确认这些信息的意义在于它能帮你判断搜索方向和断点策略。比如参数在URL里你可以直接在Sources面板里全局搜索这个参数名如果在请求头里可能还要额外关注相关代码的赋值逻辑。检查动态变化这个步骤建议手动操作两次不要只看一次就下结论。有些网站的加密值有效期内固定不变比如半小时内同一个参数值可以反复用这类反而更好处理——你只需要重放不用还原。但绝大多数正规系统都会有时间戳参与这时候才需要走完整的逆向流程。2.2 全局搜索用法和过滤条件才是关键在Chrome的Sources面板里按CtrlShiftF可以全局搜索所有加载的JS文件内容。看起来很简单实际操作中有几个细节能显著提升效率。第一个技巧是搜索参数名时优先搜索短名。比如有个参数叫orderId你别搜orderId这个全名试试搜orderId或者orderId:这样能过滤掉大量无关引用。如果参数值是动态生成的代码里大概率有一个赋值语句长得像orderId: generateOrderId(...)或orderId: xxx yyy这样的模式。第二个技巧是关注搜索结果的“文件类型”。搜索结果里会显示匹配位置优先看JS文件忽略JSON、CSS等无关文件。同时很多网站会把JS拆成几十上百个模块搜索结果会很多这时候要根据文件名猜测哪个跟登录、下单、支付更相关。第三个技巧容易被忽略搜索关键词时要考虑编码和拼接场景。有些网站不会直接写sign: value这种形式而是先把所有参数拼接成一个字符串最后统一加密比如data {...params, sign: s}。你可能搜sign搜不到但搜pwd、password或其他明文参数时反而能更快找到这段代码的位置因为加密函数往往就在明文参数组装的地方附近。2.3 断点调试从哪里打断才能看到想要的东西找到了可疑代码只是第一步更关键的是在正确的位置下断点。断点下错地方要么一直没触发要么触发得太晚、加密值已经算完了。从我的经验看有两类断点对JS逆向尤其有用XHR断点和事件监听器断点。XHR断点在Sources面板右侧的XHR/fetch Breakpoints里设置意思是当页面发起包含指定关键字的请求时自动暂停JS执行。设置方式很简单点击加号输入URL片段即可。这个断点的妙处在于它会停在发起请求的那一行也就是你往前翻几行、看几个调用通常就能找到加密逻辑。很多人搜参数搜不到就是因为搜索逻辑里套了好几层函数名而XHR断点直接从执行流切入绕开了搜索这道坎。事件监听器断点则是针对点击、鼠标移动、键盘输入等交互事件。用它来定位某个按钮的点击处理函数适合只在你手动点击后才触发请求的场景。设置路径是Sources - Event Listener Breakpoints - 勾选click或mousedown等事件。断点触发后点击目标按钮浏览器会停在绑定事件的代码位置接下来就是常规的调用栈分析了。如果你已经知道大概的位置直接双击行号打普通断点也可以。但记住一个原则断点要打在“计算之后、返回之前”的位置。比如看到代码是function getSign(data){ return hex_md5(data); }你在return那一行打点暂停时就能看到data的具体内容和hex_md5函数的返回值相当于同时拿到了输入和输出。2.4 调用栈与作用域现场还原的两大法宝断点生效之后浏览器会将执行暂停在当前行界面下方右侧或下方会展示**Call Stack调用栈和Scope作用域**两块信息。这两块是JS逆向的核心情报来源。调用栈展示的是“当前这个函数是被谁调用的那函数又是被谁调用的”一层层往上就是完整的调用链。比如你要找sign的生成逻辑当前停在encode()函数内部某一行你往下点调用栈的上一层往往就能看到encode()是在哪个更大的函数里被调用、传入的参数是什么。沿着调用栈往上追踪通常四五层之内就能找到入口函数。作用域面板展示的是当前断点位置能访问到的所有变量包括局部变量、闭包变量和全局变量。它的价值在于你可以直接在Scope面板里看到加密函数输入参数的最终值不需要再费劲去搜。比如停在var s md5(params)这里你可以在Scope里展开params看它的完整内容确认是哪些字段参与了拼接。这里有一个实操技巧看到关键变量之后可以右键变量名选择“Add to watch”把它添加到Watch面板这样即使代码继续执行、作用域变化了Watch面板里也会实时同步该变量的值。这在观察变量怎么一步步被拼接、加密的过程中非常好用。3. 实操过程与核心环节实现3.1 完整案例背景某跨平台系统的扫码登录为了让整个过程更直观我用一个模拟的案例来走完整流程。假设目标是一个“某跨平台系统”网页版的扫码登录接口每次请求都会携带一个params参数看起来是一长串十六进制字符串。同时还有几个辅助参数比如appId123456。任务要求在本地脚本里模拟登录请求需要动态生成这个params。这个案例其实涵盖了JS逆向最经典的三要素加密参数、时间戳参与、算法识别。如果你想在自己的电脑上复现找一个类似结构的网页带加密参数的历史系统跟着操作即可重点看流程不看具体代码。3.2 第一步用XHR断点精准定位请求发起位置打开开发者工具切到Sources面板在右侧找到“XHR/fetch Breakpoints”点加号输入登录接口URL里独有的片段比如api/login。然后回到页面触发扫码登录操作。浏览器会在请求发起的那一行暂停下来当前代码大概率是fetch(/api/login?params...)或axios.post(url, data)这样的语句。此时注意看左侧和下方的信息左侧代码中params变量此刻已经是最终的加密串。你如果想看它怎么来的可以选中params变量右键选择“Evaluate in console”在控制台里看它的值。如果想看它在哪一步生成最简单的方式是回退执行或者直接往调用栈下层找。3.3 第二步从调用栈回溯到加密入口在Call Stack面板里调用栈从上到下是从当前行一直往上回溯到入口。我的习惯是从下往上翻先看最底部最早触发的函数再逐层看中间层很快就知道整个调用链长什么样。假设我发现调用链是这样的入口函数onLoginClick()它调用了handleLogin(data)handleLogin里调用了genParams(data)genParams里调用了encrypt(rawStr)当前断点停在encrypt(rawStr)内部也就是说params的最终值是在genParams里算出来的核心逻辑则是encrypt函数。现在要做的就是把genParams里的细节全部看清楚。3.4 第三步观察变量拆解params的拼接逻辑在genParams函数内部下断点刷新页面或重新触发登录这次停在更早的位置。此时可以逐步执行快捷键F10逐行F11进入函数观察每一行执行完后变量怎么变化。通常我发现这种params的生成逻辑逃不出以下几种拼法把所有业务参数按固定顺序拼接成字符串然后整体加密把业务参数填充进固定的格式比如JSON再对整个格式化字符串加密把时间戳、随机数、固定密钥混在业务参数里再做哈希或对称加密针对这个案例我用F10逐步观察发现genParams做了三件事取当前时间戳Date.now()转成字符串把appId、时间戳、一个固定字符串X7y2#k9P按顺序拼接成rawStr调用encrypt(rawStr)返回结果关键的rawStr最终值可以直接在Scope面板里复制出来比如形如123456|1701234567890|X7y2#k9P那问题就简化成了encrypt做了什么用什么算法。3.5 第四步进入加密函数识别算法和参数细节在encrypt函数内部下断点并进入如果函数没有混淆一般能直接看到类似这样的代码形态function encrypt(str) { var key CryptoJS.enc.Utf8.parse(1234567890abcdef); var iv CryptoJS.enc.Utf8.parse(abcdef1234567890); var encrypted CryptoJS.AES.encrypt(str, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.ciphertext.toString(); }这段代码清晰地告诉了你四件事算法是AES-CBC密钥是1234567890abcdef偏移量是abcdef1234567890输出是密文的十六进制字符串。如果函数不是这种直白风格而是混淆的也不用慌。你有两条路动态调用在控制台里直接调用encrypt(123456|1701234567890|X7y2#k9P)拿到输出。就算函数混淆得看不下去只要它还在全局或闭包作用域里你就能直接调用它。定位算法特征看函数里有没有charCodeAt、 0xff这种字节级操作可能涉及自定义MD5/MD5变体看到CryptoJS.enc字样基本就是标准库调用。把关键信息记录到笔记里尤其是密钥、偏移量、模式、填充方式、编码格式、输出编码后面本地复现全靠这些参数。3.6 第五步本地编写还原脚本并验证一致性拿到算法参数后就可以在本地写脚本复现了。用Node.js里的crypto-js包是最省事的方式npm install crypto-js然后新建一个测试脚本const CryptoJS require(crypto-js); function genParams(appId, timestamp, fixedStr) { const rawStr appId | timestamp | fixedStr; const key CryptoJS.enc.Utf8.parse(1234567890abcdef); const iv CryptoJS.enc.Utf8.parse(abcdef1234567890); const encrypted CryptoJS.AES.encrypt(rawStr, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.ciphertext.toString(); } console.log(genParams(123456, Date.now(), X7y2#k9P));不要只看能运行就完事。正确操作是先在浏览器控制台里用真实的rawStr算出一个结果A再用本地脚本算同一个rawStr得到结果B比对A和B是否完全一致。只有两边结果一致才说明你的还原是准确的不一致的话多半是编码或输出格式有细微差异。常见的差异点有这么几个浏览器输出是hex字符串Node里用了Base64编码输出浏览器里拼接时用了连接符你用了字符串模板但中间多了空格密钥或偏移量经过了额外处理比如MD5(key)之后才参与AES计算一旦验证通过就可以把这段生成逻辑集成进你自己的爬虫或自动化脚本里每次请求动态生成参数不需要手动复制。4. 常见问题与排查技巧实录4.1 搜索参数名搜不到怎么办这太常见了而且通常不是因为你操作失误而是因为参数名的生成过程绕了好几道弯。比如代码里根本没写sign: xxx而是先把所有参数放在一个对象里然后用一个函数统一遍历、拼接、添加签名参数名只在运行时才出现。遇到这种情况我的建议是放弃搜索思路改用XHR断点调用栈回溯的组合。让断点带你到发请求的实际代码行然后往回找。无论参数名怎么动态生成它最终写在请求发起处那行代码里从那里往调用栈的上游走一定会有线索。还有一种极端情况是网站用了比较强的混淆所有JS变成了压缩后的单行乱码。这时候优先查两处一是JS文件开头的全局变量定义区很多混淆只是把函数名混淆了变量名和字符串常量仍然保留二是搜索JSON.stringify或Object.keys这类调用它们经常出现在参数拼接逻辑里。4.2 断点一直不触发问题出在哪断点不触发的原因通常有三个。第一个原因是你打的断点在代码加载前就设置好了但页面代码是动态加载的。比如某个JS文件是点击按钮后异步加载的你在文件加载前打的断点会被清掉。解决办法是等页面完全加载后重新在Sources的文件列表里找到该文件并重新打点。第二个原因是断点打在了一个只初始化一次的函数里。如果这个函数在页面加载时就执行完了而你直到点击按钮才调试那它当然不会再次触发。解决办法是把断点下移打在真正每次都会执行的位置比如发请求的fetch调用那一行。第三个原因是事件监听器断点被更内层的事件捕获了。有时候你勾选了click断点但页面上层容器也有点击监听会先触发外层。这时候应该把断点改成XHR断点或者直接在包含目标按钮的DOM元素上右键 - Break on - subtree modifications观察DOM变化。4.3 函数在全局找不到无法在控制台调用调试时很多人习惯在Console里直接输入函数名来调用结果报ReferenceError。那是因为函数定义在闭包或模块作用域里没有挂到window上。解决办法不难在断点位置暂停时函数的局部作用域里的标识符在当前调试上下文里是可以直接访问的。也就是说当你暂停在函数内部时Console可以直接调用这个函数和它的局部变量。如果你需要跳出来调用可以把需要调用的函数赋值给一个全局变量比如在Console里输入window.myFunc encrypt;后续就可以随处调用了。但如果函数在模块作用域内部且你无法在其内部暂停那就迂回一点在调用它的外层函数处下断点从Scope面板找到该函数对象的引用然后用上面同样的方式挂载到全局。4.4 还原脚本报错或结果不对如何快速定位结果不对的排查顺序也很有讲究。不要瞎猜按以下顺序来核对输入字符串浏览器Scope里复制出来的rawStr是不是和本地拼接出来的一模一样空格、换行、特殊字符都要注意。核对编码网站给定的rawStr里如果有中文或特殊字符浏览器默认可能是UTF-8但有些老系统是GBK。编码不一致同样的字符串在浏览器和Node里会得到完全不同的加密输出。核对输出格式有的算法返回Base64有的返回Hex有的返回Base64Url。转换一下再比对。核对填充模式AES常见的PKCS7和NoPadding差异极大如果只报错不对比根本看不出来。核对加密模式CBC和ECB只差一个IV前者有IV参数后者没有一旦混用输出就完全变了。建立一个小型对照表把每一步两边的结果单独打出来比如输入字符串、中间Hash值、最终密文分段比对问题会迅速暴露。4.5 综合经验遇到压缩混淆代码的三种思路压缩混淆是JS逆向的常态但不用怕。这里说三种我常用的思路按优先级排列格式化代码在Sources面板左下角找到格式化按钮{}图标可以把压缩成一行代码还原成多行缩进阅读效率提升三倍不止。搜索字符串常量混淆再怎么变字符串常量尤其是密钥、固定值、URL片段很难全部动态生成出来搜字符串往往比搜函数名有效。直接应用黑盒思路不纠结代码逻辑在Console里反复调用目标函数、更换输入、观察输出把函数当成一个加密机通过输入输出对反向推断规律。最后一种思路在遇到重度混淆时尤其好用。有输入、有输出、有函数引用你其实根本不需要知道函数内部具体怎么写的只要能在自己的进程里调用它就够了。5. 实践心得与进阶方向这篇文章讲到现在流程已经完整走了一遍。最后分享几条自己在实战中最深刻的体会。第一搜索永远不如断点可靠。代码混淆、动态拼接、多层封装这些都可以让搜索失效但断点看到的是真实执行流只要找到了正确的位置一切信息都会以最原生的方式呈现在你面前。所以第一优先级永远是训练自己用Call Stack和Scope面板去分析问题。第二所有逆向都要回归到“输入输出一致性”上。很多新手卡在“代码好像还原了但参数不对”的泥潭里。我看过太多人照着网上的教程封装了一整套加解密工具类结果一个小错误就影响全局。务必记住浏览器和本地脚本各自输出的中间值要一个一个对不能只看最终结果。这个习惯能帮你省几个小时甚至几天的排错时间。第三重放与再生是两个境界。你如果只想调用一次接口复制浏览器里的加密值、原封不动地带上请求就够了但如果你想要的是一个能长期稳定自动运行的系统就必须完成完整的参数再生逻辑。把时间戳、随机数、密钥、算法都搞清楚才能在任意时刻生成合法参数否则系统一过期你就抓瞎。后续如果你想继续深入可以考虑这几个方向用hook技术拦截所有浏览器的加密函数调用看看网站到底调用了多少次、每次输入输出是什么或者学习补环境的思路把浏览器特有的window、document对象在Node里模拟出来做到脱离浏览器也能完整执行网站JS。再加上Charles、Fiddler这类抓包工具配合使用你的工具链基本就齐了。教程看十遍不如自己动手过一遍。找个有加密参数的目标网站从抓包开始一步步走完断点、回溯、还原、验证的流程踩几个坑、翻几次车回过头来你会发现加密参数也就那么回事。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑