news 2026/10/18 16:44:36

JS逆向实战:解密翻译接口的sign与salt加密参数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JS逆向实战:解密翻译接口的sign与salt加密参数

1. 逆向前的准备工作与目标梳理

1.1 从一次网络请求开始:哪些参数藏着加密

拿到“某道翻译”这个目标时,我的第一反应不是急着扒JS,而是先打开浏览器开发者工具,切到Network面板,随便输入一段英文触发一次翻译请求。这里有个经验:别只看XHR请求,要同时留意请求体的表单数据和请求头的自定义字段。我这次抓到的请求体大概是这样的:

i: hello from: AUTO to: zh-CHS smartresult: dict client: fanyideskweb salt: 1713800000000 sign: d3c7f3f5e214xxx ts: 1713800000000 bv: 3a4d1e5a1b2c3d4e5f6g7h8i9j0k

一眼扫过去,真正像“加密产物”的只有salt、sign、bv,ts看着就是毫秒时间戳。短时间内你没法确认它们怎么生成,但至少知道了要攻克的点:salt不是单纯时间戳,sign大概率是拼接字符串后的哈希,bv可能是浏览器指纹相关的固定值。

这一步的核心价值是“圈定范围”。很多新手一上来就全文件搜索“sign”,然后陷入一大坨混淆代码里,其实应该先建立请求参数表,标记哪些值会变、哪些值固定、哪些值疑似依赖环境。我习惯用表格记录:

参数名特征初步判断
i用户输入明文
ts毫秒时间戳时间
salt时间戳+短随机数时间+随机
sign32位hex大概率MD5
bv长度固定浏览器指纹/常量

这样排查的好处是,后续定位代码时可以直接拿这些参数名做全局搜索锚点,不用大海捞针。

1.2 工具链清单:别等开工才想起来缺东少西

逆向工程不是纯看代码,工具顺手能省一半时间。我个人常用的组合是:

  • 浏览器:Chrome或Edge都行,必须熟练使用开发者工具的Sources、Network、Console三大面板。
  • 抓包工具:Charles或Fiddler(此处泛指抓包调试工具),用于查看加密请求的完整内容,尤其是移动端或非浏览器环境。
  • Node.js:本地模拟加密算法,验证纯算法跑出来的值是否和浏览器一致。
  • Python:写最后的请求脚本,或者做批量对比。
  • 代码编辑器:VS Code或任意支持代码搜索的编辑器,用来格式化、断点、搜索大文件。

这里要特意说一句:抓包工具的作用是“看流量”,不是“改流量”,别把它和某些代理概念混在一起。我们只需要确认请求发出前和发出后的数据长什么样就够了。在浏览器里调试时,重点学会XHR/fetch断点——右键请求面板里的某条记录,选择“Break on URL”,再刷新页面,就能在请求发出前暂停在调用栈上,这样能直接看到是谁构造了这次请求。

我踩过最大的坑是忘记开启“禁用JavaScript缓存”。如果没关,改完代码刷新后可能还在用旧JS文件,导致打的断点不生效或者定位到旧代码。操作路径:Network面板勾选“Disable cache”,同时开启开发者工具时保持窗口打开状态。

2. 从网络请求到加密入口:定位关键JS文件

2.1 全局搜索参数名:最快找出加密函数位置

有了参数名sign和salt,接下来就是定位。先看这个请求是哪个JS文件发起的——Network面板里点请求记录,右侧的“Initiator”会列出调用链,一般能看到类似main.js或translate.js的文件名。如果调用链被混淆压平了,就直接去Sources面板全局搜索。

在Sources面板按下Ctrl+Shift+F打开全局搜索,输入sign:,通常能搜到一处或者几十处。这里要注意区分:很多是CSS或HTML里的sign字样,需要筛选出JavaScript语句里的赋值。我习惯先搜salt =或者'salt',因为salt比sign更独特,而且在某些加密逻辑里salt先生成,sign基于salt计算,定位到salt的赋值,往上翻几行就能看到sign的生成。

我这次搜到的代码片段(已经过格式化)大致是这样的:

var r = Date.parse(new Date()); var i = r + parseInt(10 * Math.random(), 10); var s = n.md5("fanyideskweb" + e + i + "Ygy_4fsqDeI");

看起来清晰明了,但往往实际文件里被改成:

var r = Date.parse(new Date()), i = r + parseInt(10 * Math.random(), 10), s = n.md5(___.join(e, i));

这时候不要慌,先记录三个关键信息:r是时间戳,i是加随机数的盐,s是最终的sign。至于___.join到底是什么,下一步继续跟。

2.2 通过调用栈和函数签名识别加密算法

定位到赋值语句后,要确认n.md5是怎么来的。往文件顶部找,通常有一行:

var n = require("md5") // 或 var n = md5

如果看到md5字样,基本就确定是MD5。但不少站点会把算法封装成自执行函数,甚至用crypto-js、forge这类库。这时候得多看一眼:

  • 函数名是md5、sha256、sha1这种明确的——直接对照标准算法实现。
  • 函数名是encode、digest、hash——需要进函数内部,看有没有使用位运算、常量表(如MD5的0x5A827999,SHA-1的0x67452301)。

还有一个技巧:在Console里手动调用这个函数。比如在断点状态下,在Console输入n.md5("test"),如果能返回098f6bcd4621d373cade4e832627b4f6,那说明就是标准MD5。这比在代码里追来追去快得多。

定位到算法后,别急着写Python代码,先把整个加密函数的作用域完整复制出来,包括它的依赖函数。我见过很多人只复制了md5函数,结果漏了它内部引用的utf8编码函数,导致中文输入时算出来的签名不一致——这正是下一节要解决的纯算法问题。

3. 核心算法还原:纯算实现关键加密参数

3.1 时间戳与盐值:看穿随机数

先还原最简单的两个参数:ts和salt。从代码中能看到:

var r = Date.parse(new Date()) // 等价于 Date.now() var i = r + parseInt(10 * Math.random(), 10) // r + [0,9]

也就是说:

  • ts就是当前系统时间对应的毫秒数。
  • salt是ts加上一个0到9之间的随机整数(注意Math.random()最大不到1,乘10后取整数部分,就是0-9)。

有人会问,为什么还要加随机数?这就是防重放:如果只有时间戳,攻击者可以直接改时间戳来重放请求。加一个微小随机数让每次签名值不同,增加一点破解门槛。

实际用Python模拟就是:

import time import random ts = int(time.time() * 1000) salt = ts + random.randint(0, 9)

这里有个细节:Date.parse(new Date())在某些浏览器里和Date.now()几乎一致,但为了保证一致性,纯算法里我直接用int(time.time() * 1000)。如果发现实际请求里的ts和salt的差值偶尔大于9,说明算法已经升级,比如随机数位数变了或者用了其他环境变量,这时候就得重新抓包分析。

3.2 签名与浏览器指纹:还原关键算法

按照前面看到的代码,sign的真实计算逻辑可以抽象为:

sign = MD5( 固定前缀 + 翻译内容 + salt + 固定后缀 )

但“固定前缀”和“固定后缀”是什么?直接从JS源码里就能找到,通常是两个长字符串常量。比如我这次拿到的是:

前缀: fanyideskweb 后缀: Ygy_4fsqDeI

这两个字符串在混淆代码里通常以数组形式存放,搜索时搜不到完整常量,需要在运行中断点后,将经过解码的字符串打印出来。我的做法是:在加密函数那一行打断点,然后在Console输入完整的MD5拼接表达式,用真实的i和e手动算一次,和请求参数里的sign对比。如果一致,说明算法找对了。

至于bv参数,大多是从浏览器信息里取UA等信息后做摘要,比如MD5或SHA256。由于它通常在一个会话里是固定的,纯算法实现时可以先从浏览器请求里抓一个真实的bv,硬编码到代码中。这里要提醒:别小看bv,某道翻译的接口可能同时校验sign和bv,两者都必须正确,否则会报参数错误。

下面是Node.js版本的纯算法示例,我平时用来快速验证:

const crypto = require('crypto'); const md5 = (str) => crypto.createHash('md5').update(str, 'utf8').digest('hex'); function generateSign(text, salt) { return md5('fanyideskweb' + text + salt + 'Ygy_4fsqDeI'); } // 测试 const ts = Date.now(); const salt = ts + Math.floor(Math.random() * 10); const sign = generateSign('hello', salt); console.log({ts, salt, sign});

用Node跑一遍,如果输出结果和浏览器请求里的签名形式一致(32位小写HEX),说明核心算法已经还原成功。顺带说一句,为什么强调“纯算法”?因为最后写Python或Node请求脚本时,不可能起一个浏览器去执行JS,必须在本地直接用同样的算法计算出参数。这一步的成功标志就是:不发请求,只算签名,能和浏览器发出的签名匹配。

3.3 中文与特殊字符的编码陷阱

算法还原后最容易翻车的地方是中文编码。某道翻译的输入允许中文、英文、日文等,而这些字符在MD5计算时的字节表示不同。JavaScript里字符串是以UTF-16存储的,但crypto.createHash默认按UTF-8编码处理字节。直接用中文调用:

md5('fanyideskweb' + '你好' + salt + '后缀')

Node的update(str, 'utf8')将字符串编码为UTF-8字节,但如果原JS内部用的是encodeURIComponent或者别的编码方式,结果就会不一样。怎么看原JS怎么编码?在Console里执行:

unescape(encodeURIComponent('你好'))

看输出的字符串形式,如果和原JS拼接时用的字符串一致,那么Node里就不用额外处理;如果不一致,可能需要改成encodeURIComponent之后再拼接。

踩过这个坑的人应该知道,有时候算法完全正确,但中文翻译始终报错,最后发现是编码差异。解决方式就是:先在浏览器Console里模拟完整拼接,把结果打印出来和纯算法对比,确认无误再写脚本。

4. 混淆与反调试:全程避坑实录

4.1 无限debugger:不要硬拼,直接禁用

某道翻译的JS里塞了不少反调试代码,最常见的表现形式是无限debugger。只要打开开发者工具,断点就会在某个函数里反复触发,强制暂停。有经验的逆向者知道,不能一个个点“继续”,那会让操作变得极其痛苦。

我的处理顺序是:

  1. 在Sources面板点击“Deactivate breakpoints”(禁用断点)图标,这个按钮能让所有断点暂时失效,但注意它也会让你自己打的断点失效。
  2. 更彻底的方法是找到触发debugger的那行代码,比如:
function debuggerKiller() { debugger; }

然后在Console里手动hook掉Function.prototype.constructor,或者直接在代码里把debugger语句替换成空操作。实操中,我会在文件开头加一行:

window.alert = function(){};

这只是误导,重点是断点。个人最推荐“禁用断点+条件断点”组合:在可疑函数下条件断点(右键断点,添加表达式,如1===1),这样只暂停一次,之后继续就不会被反复打断。

另外,还有一个阴险的混淆:用debugger关键字的字符串拼接,比如'de'+'bugger',单纯搜“debugger”搜不到。这时需要用AST解析或者正则匹配识别debugger的字面量,才能完整替换。

4.2 数组混淆与字符串解码

某道翻译的JS夹杂着类似obfuscator.io的数组混淆。整个代码开头是一个大数组,元素是加密过的字符串,然后通过一系列移位函数取出真实字符串。这种混淆的本质是“字符串被提取到数组,调用时经过旋转”。直接阅读简直要命。

我推荐用AST工具做自动还原。目前开源方案很多,但给一个新思路:不需要完美还原整个文件,只需要还原加密函数附近的字符串。因为真正关键的计算逻辑就那几十行,手动提取数组后,在Console里把数组打印出来,再回去看代码,那些_[0x123]就会被识别成真实字符串。

拿本次分析举例,代码里有:

var _0xabc = ['Ygy_4fsqDeI', 'fanyideskweb', 'salt'];

运行时把数组部分解码成['Ygy_4fsqDeI','fanyideskweb','salt'],接下来的引用就一目了然。如果数组元素自带十六进制,则可能是编码后的字符串,比如\x66\x61\x6e就是 “fan”。遇到这种情况,直接在Console用decodeURIComponent或unescape还原。

4.3 内存爆破与循环陷阱

有些反爬脚本会做“内存爆破”:构造一个超大循环,让页面假死。常见的就是:

function blowMemory() { let arr = []; while (true) { arr.push(new Array(1000000).join('*')); } }

这种代码一般不会在正常路径执行,只有当检测到开发者工具打开时才会触发。反检测的核心思路是“不打开工具”或者“让工具看起来像被模拟”。但作为学习,我建议在分析时使用无头浏览器或者直接在本地Node环境中模拟关键函数。

如果确实需要在浏览器里调试,又遇到内存爆满,最快的办法是点击“运行时暂停”图标(快捷键F8),让JS暂停执行,然后再查看调用栈。但有些网站会循环嵌套,暂停后又继续。这时我更喜欢在规则匹配到while(true)的地方,直接在代码上双击改为while(false),或者用本地覆盖(Override)功能强制替换JS文件。

这里要强调,这些手段仅仅用于学习防御机制,真正做合规项目时,不应该去对抗对方的安全机制,而是通过官方API或授权接口获取数据。

5. 易错点与常见问题排查:给新手的实战建议

5.1 参数顺序与特殊字符处理

在还原拼接逻辑时,参数顺序必须严格一致。这里有一个通用测试法:从浏览器Network面板里复制某个真实请求的所有参数,然后用纯净算法按同样的顺序重新拼接,计算MD5,看是否和原始sign一致。如果不一致,先检查顺序,再检查是否少了某个隐藏参数(比如某些接口会拼接client=...或者version=...)。

另一个非常容易错的是空参数。如果翻译内容为空,或者某些字段为空字符串,有些实现不会拼接这个字段,有些则拼接“空”。我见过一个项目,因为i为空时后端直接返回错误,前端根本不会发请求,导致分析时遗漏了空串分支。处理建议:在搜索代码时,多留意if (e)这种判断,非空才拼接。

特殊字符方面,英文标点、表情符号、换行符都会影响MD5结果。我的Node实现里,对输入文本直接使用原始字符串,不做trim或转义,这正是从JS里学到的行为。如果发现带换行的文本签名不对,多半是JS里用了replace(/\n/g,'')之类的预处理,得在纯算法里补齐。

5.2 版本更新与动态加密策略

某道翻译这类站点会不定期更新加密逻辑。今天分析的sign算法明天可能就变了,甚至同一个页面不同接口的加密方式都不一样。这不算技术难点,就是维护成本问题。

我的应对方法:把整个解析过程做成“定位文档”,每次升级时只需要按以下步骤重新走一遍——先抓包看参数变化,再全局搜索新参数名,然后断点对比算法。只要这三点按顺序做,基本能在半小时内找到新的加密入口。不要指望一劳永逸,写代码时也要留好配置接口,比如把MD5前缀后缀做成可配置项,方便后续修改。

我还习惯定期保存抓包样本,包括请求头、请求体、响应体。这样即使站点更新后,还能对比历史数据发现变化规律。这个做法在多次实战中都帮我快速定位了“新增了某个请求头参数”之类的问题。

5.3 合规与伦理提醒:逆向的边界

写到这里必须泼一盆冷水。JS逆向本身是一种技术学习手段,但这个领域的应用场景非常容易越界。可以研究自己的项目、分析自己开发的接口,也可以在获得授权的情况下对目标做安全测试。但是,未经授权爬取他人站点数据、绕过付费限制、抓取用户个人信息,都是明确不提倡且可能违法的行为。

即使是“某道翻译”这种公开的翻译服务,其数据使用条款通常也限制商业抓取。所以在动手之前,建议先确认项目用途。如果只是个人学习、调用少量数据、有官方API就走官方API,不要用逆向手段做规模化采集。同时,尊重网站的robots.txt和访问频率限制,高并发请求只会让对方加强反爬,最后谁也拿不到数据。

我的实践准则是:逆向分析后,把结论写成防御建议或算法学习笔记,而不是直接产出恶意攻击脚本。这样既锻炼了技术,又不至于把自己置于法律风险里。

最后分享一个调试小技巧:在分析bv这类跟浏览器环境相关的参数时,别急着去硬编码。先在Console里手动构造一个新的UA,然后用它去执行加密函数,观察bv是否变化。如果UA变了但值不变,说明它和UA无关,很可能是固定值;如果变了,说明它有逻辑依赖。用这个方法可以快速判断哪些参数是动态的,哪些可以写死。

踩过几次坑之后,我更倾向于把整个分析过程分成“抓包→定位→解混淆→纯算→验证”五个步骤,每一步都做好记录。这样不管目标怎么升级,你都能像走迷宫一样,拿着标记好的地图一步步走通。这也是逆向这条路上最值钱的技能——不是背熟某个站点的加密,而是掌握快速定位和还原的方法论。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 16:43:58

批量归一化BN原理与实操:训练推理差异、小batch避坑与替代方案

批量归一化(Batch Normalization,BN)几乎是现在所有卷积神经网络和Transformer结构里绕不开的标配层。我记得第一次在项目里把BN加到某个图像分类模型上时,训练损失下降速度肉眼可见地加快,精度也高了几个点。后来做了…

作者头像 李华
网站建设 2026/10/18 16:39:13

13ft:免费自托管的付费墙穿透服务,十分钟跑起来

13ft:免费自托管的付费墙穿透服务,十分钟跑起来 【免费下载链接】13ft My own custom 12ft.io replacement 项目地址: https://gitcode.com/GitHub_Trending/13/13ft 13ft 是一个可自托管的付费墙穿透服务:贴上被付费墙拦住的文章链接…

作者头像 李华
网站建设 2026/10/18 16:38:41

金三银四网安跳槽指南:十大岗位方向与技术底气解析

金三银四这四个字,放在网安圈和通用研发圈完全是两种滋味。通用研发岗位主要看技术栈新旧,而安全岗位的跳槽,往往跟着预算周期、合规节点和上一年的项目复盘走。每年二三月份,各家安全负责人开始敲定新一年的编制和预算&#xff0…

作者头像 李华
网站建设 2026/10/18 16:36:45

GB200液冷实战:选型、监控与避坑指南

简介:这份源码资源围绕NVIDIA GB200 Superchip的液冷技术展开,面向关注AI数据中心散热方案、高功耗服务器与机柜设计的开发者及硬件工程师。内容涵盖GB200将双Blackwell GPU与Grace CPU集成的架构特点、2700W功耗下的液冷散热需求,并对比GH20…

作者头像 李华
网站建设 2026/10/18 16:36:17

基于SSM的连锁咖啡店后台管理系统:Java毕业设计完整拆解

简介:一份基于Java开发的连锁咖啡店后台管理系统毕业设计源码,面向计算机相关专业在校学生、老师及行业从业者。系统采用SSM框架整合,包含完整的前后台代码、Maven配置及JSP页面,覆盖门店、商品、订单、会员等典型连锁咖啡业务模块…

作者头像 李华
网站建设 2026/10/18 16:35:58

业务Agent评测平台:货运场景的工程实践与踩坑复盘

做这个业务 Agent 评测平台之前,我在货运平台这边已经吃了快一个月的“人工验收”的亏。业务同学提了一堆 Agent 需求,客服机器人、调度助手、费用申诉助理,每个听起来都能提效,但每次要上线,大家都不知道怎么验收。拉…

作者头像 李华