JS 逆向这个方向很多人卡在第一步——不知道从哪下手看到一个站点密密麻麻的请求就头大。我拿「百度翻译」这个站点做练手对象已经有一段时间了它的逆向门槛不算高但该有的坑一个不少签名参数、动态令牌、请求头校验、返回数据加密几乎把常见的反爬套路都覆盖了一遍。拿它当入门到进阶的过渡案例性价比很高。这篇内容就是把我整个处理过程完整摊开从抓包定位、参数溯源、算法还原到最终请求复现一步步讲清楚每一步为什么这么做、卡住了怎么绕。适合已经会一点浏览器调试、能看懂基本 JS 语法、但还没独立完整啃下一个站点的朋友。看完你至少能拿到一套可复用的分析流程而不是只会抄某一段代码。1. 整体思路与方案选型拆解1.1 先想清楚要拿什么再决定怎么打动手之前必须先明确目标边界。翻译类站点的核心诉求通常就两类一是拿到翻译结果文本二是拿到词典释义、例句这类结构化数据。这两类数据走的接口往往不是同一个参数校验强度也不一样。我一开始图省事直接冲主翻译接口结果被签名参数卡了大半天后来退一步先把词典查询接口跑通摸清了它的通用请求结构再回头攻主接口效率高很多。所以我的整体策略是「先易后难、先结构后算法」先找一个校验最弱的接口把请求链路跑通确认请求头、Cookie、公共参数这些基础设施没问题再逐个啃签名、令牌这类硬骨头。这个顺序很重要反过来做的话你会在一个还没跑通的链路上反复调试算法根本分不清是算法错了还是请求头缺了东西。具体到百度翻译我把它拆成四层来看传输层请求方法、URL 结构、公共 query 参数、请求头字段校验层签名 sign、时间戳、动态令牌、Cookie 中的标识业务层待翻译文本、源语言、目标语言等业务参数响应层返回 JSON 的结构、是否有字段加密、错误码含义这四层里传输层和业务层是「看得见」的抓包就能抄校验层是「看不见」的需要逆向响应层是「看得见但可能看不懂」的需要解密或字段映射。绝大多数人的时间都耗在校验层而校验层的突破口永远在 JS 里。1.2 为什么选浏览器调试而不是纯抓包工具有人喜欢用抓包工具直接看请求觉得够用了。但抓包工具只能告诉你「发了什么」告诉不了你「为什么这么发」。签名参数是怎么算出来的、某个字段为什么这次是这个值下次变了这些只有回到 JS 执行现场才能看清楚。我选的是浏览器开发者工具为主、抓包工具为辅的组合。浏览器负责看调用栈、下断点、跟变量抓包工具负责做请求重放和批量验证。这个组合的好处是分析阶段用浏览器拿到算法逻辑验证阶段用抓包工具快速试错两边不打架。提示分析阶段不要急着写代码复现先在浏览器里把整个调用链路走通、把每个参数的值和来源都记下来形成一份「参数清单」后面写代码就是照着清单填空效率高很多。1.3 方案选型的几个取舍逆向方案大致有三种纯手工还原算法、半自动用浏览器环境跑 JS、全自动无头浏览器直接跑页面。我这次的取舍是这样的方案优点缺点适用场景纯手工还原运行快、资源省、可控性强工作量大、遇到混淆难啃算法清晰、参数少半自动跑 JS省去还原工作量依赖 JS 引擎、性能一般算法复杂、混淆重无头浏览器最省事、最接近真实资源占用大、速度慢快速验证、临时任务百度翻译的签名算法本身不算特别复杂属于「能手工还原」的范畴所以我主攻纯手工还原中间用半自动方式做交叉验证。这个选择的关键判断依据是算法里有没有大量依赖浏览器环境对象比如 canvas 指纹、WebGL 信息。如果有手工还原成本会飙升如果没有就值得手工啃。2. 抓包定位与参数溯源实操2.1 打开开发者工具锁定目标请求第一步永远是抓包。打开翻译页面按 F12 切到 Network 面板勾选 XHR/Fetch 过滤然后在输入框里敲一段文字触发翻译。这时候会冒出若干请求重点看两类一类是返回翻译结果的一类是返回词典/建议词的。怎么快速判断哪个是目标看响应体。返回内容里包含你输入的原文和译文的那条就是主接口。我习惯先按响应大小排序翻译结果通常响应体较大容易一眼锁定。锁定之后把这条请求的完整信息复制下来URL、请求方法、所有 query 参数、所有请求头、请求体如果是 POST。这里有个细节很多人忽略同一个接口在不同操作下参数可能不同。比如你手动点翻译和页面自动联想触发的请求sign 的算法可能一样但参与签名的字段集合不同。所以抓包要抓「典型场景」最好多抓几次对比把变化的字段和不变的字段分开。2.2 建立参数清单区分静态与动态抓到请求后我做的第一件事是列一张参数表把每个参数标上「静态/动态」静态参数多次请求值不变比如 appid、from、to、client 类型标识动态参数每次请求都变比如 sign、ts时间戳、salt、token半动态参数跟输入内容相关比如 query 本身、query 的长度这张表是整个逆向的作战地图。静态参数直接抄动态参数才是要攻的对象。我实测下来百度翻译主接口的动态参数主要是sign和ts两个sign依赖ts和其他字段ts是毫秒时间戳。有些版本还会带一个token这个 token 来自页面初始化时的一次请求属于「会话级」参数需要单独处理。注意不要假设参数名永远不变。站点改版时参数名可能微调所以清单里除了记参数名还要记「这个参数在请求里的位置」和「它的值长什么样」方便改版后快速重新定位。2.3 用 XHR 断点回溯调用栈参数清单列好后接下来要找到 sign 是在哪里生成的。最有效的手段是 XHR 断点在 Sources 面板里找到 XHR/fetch Breakpoints添加一个断点条件填目标接口 URL 的关键片段。这样每次发请求前都会暂停你能看到完整的调用栈。暂停后从调用栈自底向上看找到最靠近业务代码的那一层。通常 sign 的生成就在发起请求前的那几帧里。我的习惯是从栈顶往下逐帧点看每一帧的局部变量里有没有出现 sign 的值或者类似签名的字符串。一旦某一帧的变量里出现了和请求中 sign 一模一样的值那一帧就是生成点往上跟一层就能看到算法。这个过程中有个技巧在 Console 里直接搜值。把请求里的 sign 值复制出来在 Sources 面板全局搜索CtrlShiftF有时候能直接搜到硬编码或者拼接的地方省去大量跟栈时间。当然混淆过的代码搜不到那就老老实实跟栈。2.4 定位到算法函数后的第一件事找到算法函数后别急着读代码。先做两件事一是把这个函数完整复制出来二是搞清楚它的入参和出参。入参通常是参与签名的字段集合出参就是 sign。搞清楚这两点你就知道「喂什么进去、期望吐什么出来」。然后拿真实数据做一次手工验算把抓包拿到的入参值代入看算出来的结果和请求里的 sign 是否一致。一致说明你找对了函数不一致说明还有隐藏入参或者函数被包装过。这一步是分水岭验算通过之前不要往下走。3. 签名算法还原与代码实现3.1 读懂混淆代码的几个实用技巧百度翻译的 JS 有一定混淆但不是那种变态级别。读混淆代码我有几个固定套路第一先格式化。开发者工具自带的 pretty print 能把压缩代码展开展开后结构就清晰多了。第二找字符串数组。混淆代码通常把所有字符串集中到一个数组里通过索引访问。找到这个数组和访问函数就能把大部分「看不懂的字符串」还原成明文。第三盯住加密相关的关键字。搜sign、md5、sha、encrypt、btoa、charCodeAt这些词能快速定位到核心逻辑。百度翻译的签名用到了常见的哈希运算搜这些词命中率很高。第四用 Console 动态验证。读到可疑函数直接在 Console 里调用它传几个测试值看输出比干读代码快得多。3.2 签名逻辑的拆解与复现我实测下来百度翻译的 sign 生成逻辑大致是把若干字段按固定顺序拼接成一个字符串再对这个字符串做一次哈希运算最后可能再做一次编码。拼接顺序是关键顺序错了结果就全错。复现的时候我建议先用 Python 写一版因为 Python 调试方便、字符串处理直观。核心步骤是import hashlib import time def build_sign(query, salt, ts, appid, secret_key): # 拼接顺序必须和 JS 中完全一致这里仅为示意 raw appid query str(salt) str(ts) secret_key # 哈希算法要和 JS 中一致常见是 md5 sign hashlib.md5(raw.encode(utf-8)).hexdigest() return sign写完之后拿抓包的真实数据代入和请求里的 sign 对比。这一步必须完全一致才算通过差一个字符都不行。如果不一致排查方向按优先级来拼接顺序、分隔符、大小写、编码方式UTF-8 还是 GBK、哈希算法md5 还是 sha1/sha256。我踩过的一个坑是JS 里对 query 做了encodeURIComponent而我在 Python 里直接用了原文导致中文输入时 sign 对不上。后来在拼接前统一做一次 URL 编码才通过。这类编码差异是签名对不上的高频原因务必逐字段核对。3.3 时间戳与令牌的处理ts是毫秒时间戳直接用int(time.time() * 1000)生成即可。但要注意时区问题——如果服务端校验时间偏差本地时间不准会导致请求被拒。我的做法是请求前先同步一次网络时间或者允许一定误差范围。token这类会话级参数稍微麻烦点。它通常来自页面加载时的一次初始化请求有效期有限。处理方式有两种一是每次运行前先请求一次初始化接口拿 token二是把 token 缓存起来定期刷新。我倾向第一种简单可靠多一次请求而已。提示token 的获取接口本身可能也有校验如果发现拿不到 token回头检查这个接口的请求头往往缺了某个 Referer 或 User-Agent。3.4 请求头的完整复刻很多人算法还原对了请求还是失败问题出在请求头。服务端校验的不只是 sign还有一整套请求头指纹。必须复刻的通常包括User-Agent要和真实浏览器一致版本号别太老Referer指向翻译页面缺了容易被拒Origin跨域场景下需要Cookie会话标识有些接口强依赖Accept、Accept-Language影响返回内容语言我的做法是把抓包拿到的请求头整份复制然后在代码里逐条设置跑通之后再逐条删减看哪些是必需的。这样既保证能跑通又能精简到最小集合减少维护成本。4. 响应解析与异常处理4.1 返回数据的结构映射请求跑通后返回的通常是 JSON。但别高兴太早有些字段是编码过的比如译文可能做了 Base64 或者自定义编码。我遇到的情况是部分字段直接明文、部分字段需要解码。处理方式是先打印完整响应人工看一遍结构把「看起来像密文」的字段挑出来单独处理。字段映射建议做成配置而不是硬编码在逻辑里。因为站点改版时字段名可能变做成配置后改一处就行。我一般会维护一个字段路径表比如result.translation对应译文result.dict对应词典数据解析时按路径取值取不到就给默认值避免 KeyError 直接崩掉。4.2 常见错误码与应对跑批量请求时错误码是家常便饭。我整理了一份实测遇到的错误码对照错误现象可能原因排查方向返回空或 403请求头缺失/UA 被识别补全 Referer、UAsign 校验失败拼接顺序或编码错逐字段核对、对比真实 sign频率限制请求过快加延时、降并发token 失效会话过期重新获取 token返回乱码编码不一致统一 UTF-8、检查响应头这张表是我踩坑踩出来的尤其是「sign 校验失败」这一条十有八九是编码问题而不是算法本身错。遇到校验失败先别怀疑算法先怀疑你喂进去的字符串和 JS 里喂进去的是不是同一个东西。4.3 稳定性与频率控制单次请求跑通不代表批量能用。我实测下来连续快速请求很容易触发限制。控制策略有几个层次单线程加随机延时比如 0.5 到 1.5 秒随机、限制并发数不超过 2 到 3、失败重试带退避第一次等 2 秒第二次等 5 秒第三次放弃。注意延时不要用固定值固定间隔的请求模式很容易被识别。随机化是基本操作但随机范围也别太夸张否则整体速度太慢。5. 实操心得与避坑清单5.1 我踩过的几个典型坑第一个坑是盲目相信抓包工具的「复制为 cURL」。复制出来的命令看着能跑但里面的 Cookie 往往是临时的过一会儿就失效。正确做法是把 Cookie 里的关键字段单独拎出来搞清楚哪个是会话标识、哪个是设备标识分别处理。第二个坑是忽略请求体编码。POST 请求的 body 如果是 JSONContent-Type 要设对如果是表单要注意 URL 编码。我有一次 body 里带了中文没编码服务端直接返回参数错误排查了半天才发现是编码问题。第三个坑是算法还原后没做边界测试。空字符串、超长文本、特殊字符emoji、换行这些边界输入往往能暴露编码处理的漏洞。建议还原完算法后专门拿这几类输入跑一遍。5.2 提高效率的几个习惯分析时随手记笔记把每个参数的来源、每个函数的入参出参都写下来别指望脑子记。我一般开一个文本文件边分析边记最后这份笔记就是复现代码的蓝本。多用 Console 做小实验别在编辑器里改代码再刷新页面验证太慢。Console 里直接调函数、看输出几秒钟一个实验效率高一个数量级。遇到卡壳先放一放换个接口或者换个时间段再回来看。有时候是站点临时调整有时候是自己思路钻了牛角尖隔一会儿再看往往豁然开朗。5.3 关于合规与边界的提醒这类分析仅限个人学习和技术研究用于理解前端加密与请求校验的常见实现方式。实际使用时要遵守站点的服务条款控制请求频率不要对服务造成压力。技术本身是中性的怎么用取决于人。我个人的原则是只做学习验证不做大规模采集遇到明确的技术保护就止步。最后分享一个小技巧如果你在还原某个哈希算法时死活对不上试试把 JS 里的输入字符串用console.log打印出来和你在 Python 里拼出来的字符串逐字符对比包括不可见字符。我遇到过好几次是 JS 里多拼了一个看不见的分隔符肉眼看不出来逐字符对比才发现的。这个笨办法屡试不爽值得养成习惯。
阅读完成 · 觉得有帮助?