首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
Web端JS逆向实战:断点调试与签名算法分析全流程
📅 2026/9/15 1:06:53
✍️ 爱科研究院
👁 阅读 3,247
1. 我为什么要做这次Web端逆向分析做前端性能优化或者数据采集的同学应该都遇到过这样的情况网页上明明能看到数据但接口返回的内容却带着一堆看不懂的加密参数。你要么放弃要么就得去扒这些参数是怎么算出来的。我这次处理的目标是某书的Web端也就是标题里说的“小某书”。它属于典型的“外层看着简单、内部塞满加密逻辑”站点。为什么选择拿它做案例因为它足够有代表性页面功能不少前端打包产物做了细致的代码分割接口请求里塞了好几个签名相关的自定义Header同时它在不同版本的迭代里调整过参数的生成方式。这意味着你可以用一套非常通用的JS逆向流程去分析它而这套流程放到其他同类站点上也照样能用。本文把我从抓包、定位、断点调试到最终梳理出签名生成链路的过程完整记录下来。适合刚接触JS逆向、想建立一套可复用调试流程的人也适合已经能看代码但经常断点断不明白的朋友。先说清楚边界这篇文章只做技术分析和学习分享不针对任何平台做破解、绕过或攻击所有涉及目标站点的内容都以公开页面和前端资源为分析对象。很多人一听“逆向”两个字就紧张总觉得是灰产或者黑产才做的事。其实“JS逆向”本身是个中性词本质上就是通过可执行的前端代码去倒推它的运行逻辑。这和你在GitHub上读一个开源项目的源码没有本质区别只不过这个项目的代码是压缩混淆过的你只能通过动态调试去理解它。我常跟团队里新人说一句话写代码是把想法变成指令逆向是把指令还原成想法。你日常开发时调试自己的Bug其实也在做类似的事只不过对象变成了别人的代码。带着这种心态去做分析你会少很多心理负担也多很多耐心。2. 逆向分析的整体思路拆解2.1 先做黑盒观察不要急着上断点很多新手一上来就打开DevTools到处点看到有报错就打断点结果断点命中了也不知道自己在哪。我建议的流程是先做黑盒观察把目标当做一个不透明的系统只看输入和输出。比如某书Web端首页的信息流接口你往下滑动页面Network面板里就会出现一批请求。先别管加密参数怎么来的你先看这几个东西请求URL是什么路径里带了哪些参数请求方法是GET还是POST参数在Query里还是在Body里请求头里有哪些自定义字段哪些字段每次请求都变哪些不变响应数据里有没有你想要的核心信息是明文还是加密。这一步的目的是建立“最小可复现请求”。你先通过复制cURL或者直接改参数重放确认这个接口在什么条件下能返回数据。如果少了某个签名头就报错那么这个签名头就是你需要逆向的目标。2.2 从输入到输出建立自己的“调用链地图”有了最小可复现请求之后逆向的整体思路就非常清晰了。你需要沿着请求的触发路径找到加密参数生成的那一段代码然后把这段代码从浏览器环境中剥离出来在Node里模拟执行最终实现不依赖浏览器就能生成合法请求。这个流程可以拆成四步找到触发请求的入口逻辑无论是按钮点击、滚动加载还是定时器在该入口附近对网络请求相关方法做断点拿到完整的调用栈在调用栈中寻找生成签名参数的函数分析它的依赖数据把分析得到的逻辑移植到本地环境用同样的输入做验证。整套思路说白了就是一句话——“先看数据从哪里来再看参数在哪里被算出来”。如果把加密参数比作一盘菜那么抓包等同于看成品菜的外观调用栈就是后厨的监控录像你顺着录像就能看到厨师从哪个冰箱取了什么食材、用哪口锅炒、放了多少调料。2.3 为什么大多数攻略都强调“最新版”三个字标题里写了“最新版”这其实非常关键。Web端逆向有一个特征时效性极强。今天能用的签名算法可能过两周就换了也可能同一天在不同地区的用户身上返回不同的结果。你在网上搜到的老教程采用的是某个固定版本代码片段甚至连参数名都和现在完全不同照着抄基本不可能成功。所以我这次刻意以“最新版”为分析基调也建议你以后看到类似题目时保持怀疑与其找一个旧代码复制粘贴不如现场从当前的页面资源里重新定位。逆向分析最宝贵的不是最终那几行算法而是你定位算法的速度。版本越新市面上可参考的资料越少对你基本功的考验也就越大。3. 工具与准备环境3.1 工具清单工欲善其事必先利其器。做JS逆向用得最频繁的工具其实就那几样不需要装一堆花里胡哨的软件。我在这台机器上长期保留的是下面这套组合工具用途替代方案Chrome DevTools主战场断点调试、观察调用栈、编辑代码Edge DevTools、FirefoxFiddler/Charles抓包、重放、修改请求响应Whistle、MitmproxyNode.js本地模拟运行逆向出来的算法Python的PyExecJS、quickjsVS Code整理代码逻辑、写验证脚本Sublime、WebStorm如果你只想从零开始Chrome DevTools加Node.js这两样就够用。Fiddler主要解决的是移动端或者其他浏览器环境的抓包需求纯Web端调试用不上它。3.2 环境配置的几个细节很多人在本地运行逆向代码时报各种错回头发现是环境变量的问题。这里说几个我踩过坑的点。第一个是浏览器版本和Node版本之间的差异。现在前端构建环境大多支持ES6甚至ES2020以上的语法你从压缩代码里摘出来的函数大概率也会用到?.、??、BigInt这类特性。本地Node版本如果太老语法解析阶段就会报错。建议把Node升到LTS版本以上最好直接用18或者20。第二个是“补环境”的问题。浏览器里的代码会依赖window、document、navigator这些全局对象你把函数抽到Node里跑通常得临时造一个模拟环境。常见的做法是用jsdom把基础的DOM API补齐或者在代码入口处判断一下全局变量是否存在。需要注意这一步只是为了让你自己的验证脚本能跑通不是在教你伪造请求内容。第三个是数据流的一致性问题。很多算法的输出依赖输入而输入里面可能包含时间戳、随机数、页面上下文里的状态值。你在Chrome DevTools里断点命中的那一刻和你在Node里运行脚本的那一刻哪怕相差一秒钟生成的结果都不相同。所以在做对比验证时不能只看最终输出的字符串是否相等而是要把输入固定成同一个值或者把随机部分处理好再比对。4. 核心环节定位加密参数与断点调试4.1 如何快速锁定参数位置拿到一个请求后我通常会先看URL和Header里有哪些非常规字段。比如某书Web端请求头里会有类似x-s、x-t这种自定义签名Header一眼就能看出来是前端加上的。这时候直接在Sources面板里按CtrlShiftF全局搜索参数名。如果站点没有做严格的字符串拼接或编码你会直接在某个.js文件里定位到对应的赋值语句。但实际经验是很多站点不会把参数名明文写死。我之前遇到过一种情况参数名是由一个数组拼接出来的数组里的字符串片段分散在不同模块里全局搜索完整参数名完全没结果。这种时候就要换个思路在Network面板里把请求头整个复制出来搜索请求头里的“值”而不是“键”。因为值通常是一个动态结果你搜索值的时候如果能命中某个变量名说明这个变量就是该参数的上游来源。另一个很实用的技巧是使用XHR断点。在Network面板里右键你要分析的请求选择“Add XHR/fetch breakpoint”输入一个URL片段当代码发起匹配该片段的网络请求时浏览器会自动暂停。此时你切换到Sources面板就可以从Call Stack面板里看到一串调用过程。我自己的习惯是先在XHR断点处暂停然后看调用栈底部的几层找到发请求的入口函数再在入口函数里手动打断点刷新页面重新触发一次。这样做的目的是缩小范围不让自己深陷到库代码的迷宫里去。4.2 断点调试的三种常用姿势断点不是越多越好关键是断在正确的位置。我常用的断点姿势有这三种第一种是普通行断点。找到可疑代码行点击行号即可。这个是所有方法的基础适合你已经定位到某个函数但想知道它内部每一步执行结果时使用。配合Watch面板添加表达式可以在不打断代码节奏的情况下观察关键变量的变化。第二种是条件断点。如果代码在一段循环里执行了很多遍或者某个函数被频繁调用普通断点会让你按到怀疑人生。右键点击行号选择“Add conditional breakpoint”填入一个判断表达式比如i 3 this.value 100满足条件时才会暂停。这个技巧对过滤无关调用特别有用。第三种是Logpoint。它其实是断点加控制台输出的融合体在右键菜单中选择“Add logpoint”填入一段表达式浏览器不会暂停但会在Console里输出表达式的结果。适合你需要观察一个值的平缓变化过程又不想一次次手动恢复执行的情况。我在分析长列表滚动加载的请求时经常用这个方法。4.3 面对压缩混淆代码怎么办压缩后的代码确实难看。变量名全是a、b、c函数名完全没有语义但压缩混淆不等于不可读。Webpack打包的产物通常有固定的模块结构只要定位到模块加载器就能按模块去整理逻辑。一个常见做法是先点击DevTools里的“{}”按钮让浏览器帮把压缩代码格式化。格式化之后代码的可读性会提升不少但变量名不会变好。你还需要做二次整理把频繁出现的关键函数重命名成你能理解的名字把关键变量重新命名为timestamp、signatureInput这类标识符。Webpack的模块列表也有迹可循。模块加载器通常是webpackJsonp.push或者一个中心化的__webpack_require__函数。如果你搜索某一个比较特殊的字符串能命中多个模块说明这些模块可能共享同一个依赖而你定位的函数大概率就活跃在这个依赖关系网里。还有一个常见套路很多站点会把核心算法放到一个独立的IIFE立即执行函数中外部只暴露一个入口。此时的代码边界会比较清晰你要做的就是找到这个IIFE的返回值看看它被谁接收、最终赋给了哪个变量。知道了入口和出口算法内部就算再像是天书你也只需要关注和加密参数有关的几个分支。5. 实操记录以某书Web端签名头为例5.1 线索溯源从一个请求头开始我随便打开某书Web端刷新首页Network面板里看到信息流请求路径大概是/api/sns/web/v1/homefeed这类的结尾。现有的请求头里除了常规的user-agent、referer之外还带了一个自定义签名Header每次刷新值都不一样。我的第一步不是搜索这个Header名而是先把它当前的值复制出来放到Sources面板里全局搜索。这个值通常不是固定常量搜索值一般也不会直接命中源文件但搜索之后可能会得到零结果这本身就是有用信息——说明该参数不是直接以字符串形式出现在代码里的。于是我把策略调整为搜索Header名。这次命中了一段代码某个对象里定义了从请求上下文读取某些字段再经过一个函数处理最终把返回值赋给这个Header。顺着赋值语句向上追踪我看到它依赖了路径、查询参数、时间戳以及一个固定密钥相关的变量。看到依赖项之后我做了两件事第一用Network面板重新发一次同样的请求将签名Header值复制进本地临时文件里存档第二在赋值函数那行打上断点刷新页面等代码暂停后逐个查看Watch里的变量值。这个过程听起来行云流水实际会有很多反复。因为函数调用堆栈可能有三四十层深而且大量代码是异步的断点很可能命中在无关的Promise回调里。我的做法是宁可多刷新几次也要保证每次断点命中都能记录下调用栈、当前作用域和闭包变量凑三次以上再去归纳规律。5.2 断点命中与调用栈分析断点命中那一刻Chrome会在Sources面板右侧展示当时的Call Stack。栈顶是当前正在执行的函数往下是调用者。我在分析某书Web端时发现签名Header的生成并不是在XHR发送的最内层完成的而是在一个更早的请求包装层就被计算好然后作为Header一起传递给底层的fetch方法。这说明什么说明签名生成和请求发送是分开的。底层请求模块只管把Header带上实际签名逻辑在业务代码层早就已经算好。于是我的关注点从“发送请求前在哪里加密”切换到“业务代码里哪个模块调用了签名函数”。我选择在Call Stack中部的一帧里停下来往上层看调用者很快看到一个工具函数模块函数名不能说和签名完全无关但也不是明晃晃的createSignature这种风格而是更偏向于缩写。这时候我使用了本地重写功能在Sources面板里选中这个工具函数所在文件右键选择“Override content”把这行函数在执行后把入参输出到console里保存后再次刷新页面console里就能看到签名函数接收到的完整参数对象。得到入参后我打开浏览器的“Event Listener Breakpoints”把Fetch和XHR两类断点全部取消避免后面调试时频繁被无关请求打断。接着在工具函数内部的关键分支逐行单步执行同时用Watch面板观察一个对象变量从空对象一步步变成包含时间戳、路径、查询参数、密钥的完整结构。到这一步签名算法的输入输出全都在手里了。5.3 在本地环境里跑通整套逻辑有了输入和输出接下来要做的就是把函数依赖的代码从浏览器中复制到本地。复制的时候不能只复制签名函数自身它引用到的辅助函数、常量、编码方法都要一起带上。最稳妥的方式是借助Source Map或者模块列表把依赖模块逐个拷贝出来。我在某书Web端这个例子里需要的依赖包括一个MD5相关的摘要函数、一个时间戳格式化函数、一个对查询字符串做排序的工具函数以及一个密钥常量。把这些抽取到一个Node脚本后我构造同样的入参执行签名函数将输出的值和浏览器里抓到的值做批量比对。第一次比对就发现不一致这是正常的。我后来查证发现时间戳字段在签名逻辑里用的是毫秒级但请求Header里暴露的是秒级另外部分英文字母在排序时要统一转成小写。这种边界情况不看实际执行结果根本发现不了。环境补齐也是个问题。某书Web端的代码里会读取location.pathname但我在Node里没有这个对象。我用global.window {}、global.location { pathname: / }这样的模拟方式先把缺失变量补上。请记住这个动作是为了让研究代码能跑起来不代表你应该伪造任何敏感请求。最终跑通之后我写了一小段验证脚本用最近几次抓包得到的请求路径和查询参数作为输入自动生成签名Header再通过Node发起真实的HTTP请求去和浏览器行为做对比。需要强调的是我的验证请求发送得非常克制没有做循环采集、没有污染线上数据仅仅为了校验签名算法是否能复现。6. 常见问题与排查技巧6.1 新手最容易踩的五个坑排查问题这件事最大的成本往往不是修复而是定位。以下是我做JS逆向以来遇到的高频问题基本能覆盖80%以上的卡壳场景。现象可能原因处理办法全局搜索参数名搜不到参数名被拼接、编码或拆分成多段搜索参数值或用XHR断点直接拦截请求断点能命中但调用栈里全是库代码实际业务函数在异步回调里切换Event Listener断点只看Fetch或XHR本地Node执行结果和浏览器不一样缺少“环境”或依赖了随机数用jsdom补环境或固定时间戳后再比对同一个参数的版本变化太快服务端动态下发逻辑或灰度配置多抓几次请求观察规律不要依赖单次样本页面检测到调试就自动跳出站点存在反调试逻辑使用Logpoint代替断点避免长时间暂停6.2 反调试的几种常见手法与应对思路某书Web端这一类站点多少会带一点反调试逻辑最常见的是在源码里埋一个debugger语句配合无限循环让你一打开DevTools就卡死。还有一种是监听Console是否被打开改变页面行为或输出干扰信息。面对debugger卡死我自己的解决办法是不用普通断点改在Function.prototype.constructor或者关键函数调用处打断点。还可以用Never pause here右键菜单把某个debugger位置标记为永不暂停这样就不会被反复弹窗打扰。另外有些反调试逻辑会检查Date.now的变化频率。长时间在断点里停留会让时间戳出现异常跳变。如果发现签名结果在本地死活复现不了可以先检查一下是不是这个原因。我的习惯是提前在脚本里写好一个“时间冻结”的工具函数在需要复现时把Date.now临时替换成固定值。6.3 关于“签名算法失效”的排查思路服务端更新算法是常规操作。标志性现象就是你本地跑得好好的签名脚本过了一晚突然生成出来的Header不被接受。这时候不要慌按三个方向去排查第一检查请求参数是否有变化。服务端可能新增了一个必选参数你没带上去签名值自然就无效。你把浏览器里最新抓到的请求体和老请求体做diff一眼就能看出差异。第二检查密钥是否过期。有些签名算法会用服务端下发的临时token作为密钥的一部分token过期之后算法再正确也没用。这种时候先看页面里是否有新的token被注入到HTML或异步接口里再进行二次组合。第三检查是否引入了新的反爬策略。比如服务端开始校验完整的Header顺序或者要求请求必须携带特定的Cookie。别只盯着签名Header把整个请求的Header列表都对比一遍。6.4 一条争议边界什么样的分析行为是安全的写到这里我总觉得应该把边界问题说透。JS逆向这个技术本身没有原罪但它确实可能被用来做自动化脚本、绕过风控、批量爬取用户数据。这些用途在法律和道德上都有很大风险。我自己内部有一个安全红线清单只分析自己有合法权限的页面和接口比如公开页面、自有站点或获得明确授权的项目不将逆向结果用于批量采集、价格监控、恶意请求、撞库等行为不以任何方式获取或存储用户隐私数据哪怕这些数据在公开接口里能够拿到不在生产环境反复测试签名接口避免给平台带来压力或影响真实用户体验。这篇文章里写的所有内容本质上都是“如何阅读一段JavaScript代码”的延伸。如果你拿它去做正当的前端安全测试、性能优化或者是学习研究那是好事如果你拿它去打灰色擦边球那请你立刻关上页面。7. 写在最后这个能力应该怎么用如果你只想要一份可以抄的代码这篇文章可能会让你失望。但如果你想要的是面对陌生站点时的一套分析思路我相信前面这些内容足够你上手了。我做JS逆向这几年最大的收获不是“能搞定某个平台的加密参数”而是练出了一种习惯遇到问题先看现象、再列假设、然后用断点和调试逐一验证。这种习惯让我在做普通前端开发时也受益匪浅——很多东西我只会在真实执行时暴露问题而动态调试正是发现这类问题的最高效方式。对于想入门的朋友我的建议是不要从某书这种级别的大站开始。你可以先找一个没有混淆、没有反调试的小网站练手比如一个简单的后台管理系统试着找到登录接口的加密方式再逐步增加难度。先把定位参数的流程走顺再去面对代码混淆和反调试。如果你已经有一定基础不妨试着把你逆向出来的逻辑整理成一份技术笔记记录你当时是怎么找到入口的、踩过哪些坑、最终如何验证。这个整理过程会逼迫你把模糊的判断变成清晰的流程下一次再遇到类似站点你的速度会快上很多。最后说一句我一直挂在嘴边的话前端代码是跑在用户设备上的代码它注定是透明的。它不是用来做隐藏的工具而是用来做验证的入口。真正重要的是你看待它的时候遵守规则、保持克制。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/9/15 1:06:53
UE4+AirSim无人机目标跟踪强化学习仿真项目全流程解析
2026/9/15 1:06:53
Lynx 模板二进制编解码层(template_codec)架构解析与实战指南
2026/9/15 1:06:53
Cordova+Vue+PHP直播App源码解析:从混合开发到Android打包
2026/9/15 1:36:56
QMK Firmware 下的 1K 单键机械键盘移植指南:ATtiny85 + WS2812 + Micronucleus 全流程解析
2026/9/15 1:36:56
Dart与鸿蒙交互机制及性能优化实战
2026/9/15 1:36:56
YOLOv5+PyQt5火灾检测可视化系统实战指南
2026/9/15 1:36:56
2024谷歌算法更新解析与SEO优化策略
2026/9/15 1:36:56
GD32H759+RT-Thread工控入门:从点灯实验构建可信实时系统
2026/9/15 1:31:55
CEH-Orbit数字签名验证:轨道一致性机制解析与应用
2026/9/15 0:01:49
2026年NVMe SSD装机避坑指南:PCIe 4.0/5.0、NVMe启动与M.2 Key兼容性实测
2026/9/15 0:01:49
Flutter与OpenHarmony物理动画实现指南
2026/9/15 0:01:49
vscode插件开发之语言服务器,这次让用 TaoToken 接入的 Codex 排查 LSP 服务端连接
2026/9/14 7:37:16
拯救者Y7000黑屏故障排查与维修实战指南
2026/9/14 2:50:57
AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验
2026/9/14 11:25:37
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化