JS逆向60个案例背后的通用分析流程:从定位加密参数到Python还原
2026/8/31 9:16:12 网站建设 项目流程

第一次在开发者工具里看到满屏加密参数时,大多数人都会意识到一件事:JS 逆向不是背几个函数就能搞定的。明明 Network 面板里躺着完整的 JSON 数据,可请求地址是动态拼接的,请求头里多了 sign、token、ts 一堆字段,直接用 Python requests 发出去,服务器永远只回一句“参数错误”。这时候你才真正明白,爬虫这件事已经不只是写请求、解析响应那么简单,它变成了一场对前端加密逻辑的分析和还原。

标题里“60 个 JS 逆向案例”听起来像一份很全的资源合集,我也见过不少类似的目录,还带过一些刚入门的朋友。最后得出的结论是:案例本身不是重点,重点是你有没有在看完案例之后,学会一套“无论遇到什么加密都能自己拆开”的通用流程。这篇文章不评价某个课程值不值得买,只讲一个真正值得长期投入的能力应该怎么练,以及练的过程中最容易踩哪些坑。

1. 先想清楚:JS 逆向真正练的是哪一类分析能力

1.1 请求发出去之前,前端到底做了什么

先回到最基础的问题:为什么普通爬虫拿不到数据?

正常情况下,一个网页要展示数据,浏览器必须先向服务器发请求。服务器为了限制资源被大量抓取,或者为了保证数据安全,会在前端加一层加密逻辑。常见的做法是把业务参数、时间戳、随机字符串、固定密钥拼在一起,算出一个 sign 值,再塞进请求头或者查询参数里。服务器收到后按同样的规则再算一遍,对不上就拒绝,对得上才返回数据。

所以你会发现,数据不是藏在某个需要账号登录的接口后面,而是藏在请求的生成过程里。你看到的 sign、token、ts,本质上都是前端在请求发出之前,用 JavaScript 现算出来的。JS 逆向要做的,就是把这套“前端现算”的逻辑还原成 Python 代码。

这里有一个容易误解的地方:很多人以为逆向的目标是“破解密码”,其实大部分场景里它只是“复现一段计算流程”。你不需要知道私钥,不需要反编译整个系统,只需要找到加密函数的入口和出口,理解输入输出之间的关系,然后用另一门语言复现出来。

1.2 单次跑通不是目标,可复用才是

我见过不少新手第一次破掉某个网站的 sign 参数后特别兴奋,觉得自己已经会了。但过两天网站前端代码一更新,同样的问题又卡住了。原因很简单:他记住的是那个网站的特定写法,而不是一套分析流程。

真正值钱的能力是四个动作:定位、分析、还原、验证。

  • 定位,是快速锁定加密参数生成在哪个 JS 文件、哪个函数里。
  • 分析,是搞清楚这个函数做了什么,用了哈希、对称加密还是非对称加密。
  • 还原,是用 Python 或 Node.js 把同样的逻辑重新实现。
  • 验证,是用多组样本确认两端输出一致。

这四个动作是通用的。只要你会这套流程,今天拆一个 MD5 签名,明天拆一个 AES 加密,后天拆一个混淆过的自定义算法,本质上没有区别。案例数量带来的提升,远不如流程熟练度带来的提升大。

1.3 学之前必须划清的学习边界

还有一件事要放在最前面说。JS 逆向技术本身是分析手段,可以用在接口调试、前端性能排查、安全测试、自有系统自动化等合理场景。但使用它去抓取未获授权的数据、绕过付费限制、攻击他人系统,都是不合适的。

练习时尽量选择自己拥有的页面、公司内部授权的系统,或者专门设计的靶场环境。很多在线教程会拿电商、视频平台举例,你可以阅读学习,但不要直接对着生产环境做批量抓取。技术本身没有好坏,边界在自己心里。

2. 一次完整逆向分析,应该按什么顺序走

2.1 第一步:在 Network 面板定位加密参数

所有分析都从抓包开始。打开浏览器开发者工具,切到 Network 面板,勾选 XHR 过滤,然后刷新页面,找到返回目标数据的那个接口。

这时候不要急着看响应,先看请求头、查询参数和请求体。你需要做一张清单:

  • 哪些参数是固定的,比如 clientId、platform;
  • 哪些参数是变化的,比如 timestamp、nonce;
  • 哪些参数看起来像加密结果,比如一长串十六进制字符、Base64 字符串、随机字母数字组合。

如果请求体里有一个 32 位十六进制的字段,直觉上先想到 MD5 类的哈希;如果是一长串带 = 结尾的字符,先想到 Base64 或者 AES 加密后的结果。这个“第一眼判断”不需要准,只需要给你一个搜索方向。

2.2 第二步:顺着参数名找到加密函数

定位到加密参数之后,切到 Sources 面板,在全局文件里搜索参数名。快捷键通常是 Ctrl+Shift+F,可以搜索所有 JS 文件。用“sign”这种参数名去搜,结果可能很多,但通常有一个地方是“生成 sign”的赋值语句,比如:

const sign = getSign(params);

或者在某个对象里:

params.sign = md5(baseStr);

如果没有直接搜到参数名,可以改搜一些特征关键词,比如 encrypt、decrypt、CryptoJS、JSEncrypt、sha256、hex、sign。前端加密常用的库就那几个,CryptoJS 里包含了 AES、MD5、SHA 等实现,JSEncrypt 对应 RSA,搜到库引用之后,顺着库的调用点往回找,就能看到业务代码。

2.3 第三步:用断点和控制台还原逻辑

找到候选函数后,最有效的办法是在它那一行打断点,然后重新触发一次请求。浏览器会在执行到断点时停下来,这时候你可以在 Scope 面板里看到所有局部变量,在 Console 面板里手动执行表达式,逐行观察参数的拼接过程。

实际调试时你会发现,很多加密函数并不长,核心逻辑可能就是几十行:取几个字段 → 拼成字符串 → 做一次 MD5 → 转成大写 → 返回。真正花时间的是定位这几十行到底在哪里。

如果页面有反调试逻辑,比如无限 debugger 或者定时检测,也不需要慌。可以用 Chrome DevTools 的“Add script to ignore list”绕过部分调试干扰,或者直接格式化压缩后的代码再搜索。这类问题属于经验问题,多处理几个案例就熟了。

2.4 第四步:用 Node.js 先跑通,再转 Python

很多人习惯在浏览器控制台里验证完就去写 Python。我的建议是反过来:先在本地用 Node.js 跑通,再转 Python。

原因很简单。浏览器控制台里的环境很复杂,各种全局变量、异步逻辑都会影响结果,而且不容易复现。把相关函数复制到一个独立的 Node.js 文件里,mock 好输入参数,跑一下看输出,这样环境干净,定位问题也快。

比如你定位到一个用 MD5 生成签名的函数,可以先在本地建一个临时 JS 文件:

const crypto = require('crypto'); function getSign(params) { const raw = `name=${params.name}&age=${params.age}`; return crypto.createHash('md5').update(raw).digest('hex'); } console.log(getSign({ name: 'test', age: 18 }));

如果 Node.js 输出和抓包里的 sign 一致,说明逻辑还原成功。这时候再用 Python 重写,就非常稳妥:

import hashlib def get_sign(name, age): raw = f"name={name}&age={age}".encode("utf-8") return hashlib.md5(raw).hexdigest()

如果 JS 里用了 CryptoJS,在 Node.js 里装一个 crypto-js 包同样能跑。只有当你确认“同输入能产出同输出”之后,才轮到 Python 出场。

2.5 第五步:多组样本验证

最后一步很多人会跳过:验证。

不要只测一组数据,至少要抓三组不同时间、不同业务参数下的请求,用同样的入参分别喂给 JS 版和 Python 版,对比输出是否一致。还要检查时间戳是否有有效期,比如服务器要求 ts 和当前时间差不能超过 60 秒,超过了即使 sign 正确也会被拒绝。

验证这件事决定了你的代码能不能稳定使用。一次成功只能说明流程没断,多次一致才能说明逻辑真正被还原了。

注意:验证时不要直接对线上服务做高频请求,先抓几条真实请求样本,离线比对签名输出。

3. 常见加密类型:先认识算法,再谈还原

3.1 哈希类:MD5 与 SHA 系列

哈希类加密在爬虫逆向里出现频率最高,特点是输出固定长度,而且不可逆。MD5 输出 32 位十六进制,SHA-1 输出 40 位,SHA-256 输出 64 位。如果看到一个固定长度、由 0-9 和 a-f 组成的字段,优先考虑哈希。

哈希类最常考的是拼接顺序和 salt。同一个值,a=1&b=212算出来的 MD5 完全不一样。所以拿到代码后,第一件事是确认原始字符串到底怎么拼的。有的会把参数按字母序排序,有的会加固定前缀,有的还会在中间插入时间戳,这些细节都是决定成败的关键。

3.2 对称加密:AES 与 DES

对称加密需要密钥,和哈希不同,它可逆。特征上,密文通常是 Base64 字符串,长度和明文相关,常见模式有 AES-CBC、AES-ECB。CBC 模式需要 IV,ECB 模式不需要。

AES 还原时有四个关键点:

  • 密钥 key 是什么;
  • IV 是什么,是否固定;
  • 模式是 CBC 还是 ECB;
  • 填充方式是 PKCS7 还是其他。

JS 里常见写法是:

const CryptoJS = require('crypto-js'); function encrypt(plaintext, key, iv) { const encrypted = CryptoJS.AES.encrypt(plaintext, CryptoJS.enc.Utf8.parse(key), { iv: CryptoJS.enc.Utf8.parse(iv), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ); return encrypted.toString(); }

Python 里对应的写法是:

from Crypto.Cipher import AES from Crypto.Util.Padding import pad import base64 def encrypt_aes(plaintext, key, iv): cipher = AES.new(key.encode("utf-8"), AES.MODE_CBC, iv.encode("utf-8")) encrypted = cipher.encrypt(pad(plaintext.encode("utf-8"), AES.block_size)) return base64.b64encode(encrypted).decode("utf-8")

最常出错的是 key 和 IV 的编码形式。JS 里CryptoJS.enc.Utf8.parse(key)表示把字符串按 UTF-8 转成字节,Python 里对应key.encode("utf-8")。如果 JS 用的是 Hex 解析,Python 里就要用bytes.fromhex(key),这个差异会导致结果完全对不上。

3.3 非对称加密:RSA

RSA 的特点是公钥加密、私钥解密。前端拿到公钥,加密一段数据后发给服务器。逆向者只需要复现“用公钥加密”这个过程,不需要知道私钥,所以 RSA 的还原反而不难。

常见实现是 JSEncrypt:

const JSEncrypt = require('jsencrypt'); function encryptRSA(plaintext, publicKey) { const encryptor = new JSEncrypt(); encryptor.setPublicKey(publicKey); return encryptor.encrypt(plaintext); }

Python 里用 cryptography 库:

from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import serialization import base64 def encrypt_rsa(plaintext, pem_public_key): public_key = serialization.load_pem_public_key(pem_public_key.encode("utf-8")) encrypted = public_key.encrypt( plaintext.encode("utf-8"), padding.PKCS1v15() ) return base64.b64encode(encrypted).decode("utf-8")

RSA 相对简单,但要注意公钥来源。有时候公钥不是完整 PEM 格式,而是只有模数 n 和指数 e,需要在代码里手动组装公钥对象。这种情况虽然少见,但碰到了会卡很久。

3.4 编码类:Base64 与变种

Base64 不是加密,只是编码,但在逆向里经常作为中间层出现。特征很明显:字符集包含 A-Z、a-z、0-9、+、/,末尾可能有 =。

需要注意 URL-safe Base64,它把 + 和 / 替换成 - 和 _,而且可能会去掉末尾的 =。Python 里对应的处理方式是:

import base64 raw = base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))

如果你的请求里有这种字符变体,先考虑是不是 URL-safe Base64,而不是急着上 AES。

3.5 混淆与自定义算法:先找入口再读代码

最难的场景是 JavaScript 代码被混淆,比如变量名变成乱码、字符串被拆散、数组移位、控制流平坦化。这种情况下不要试图读懂全部代码,而是先找到加密函数的入口和出口。通常断点比阅读更有效:打断点、触发请求、看调用栈、观察传入和返回值,把注意力集中在输入输出上。等确认函数边界之后,再决定是把整段逻辑复制到 Node.js 里跑,还是根据观察结果重写。

各大厂场景里通常不是单层加密,而是“时间戳 + 随机数 + 参数排序 + Hash/MAC + 混淆”的组合。这也是为什么这类题目更像分析题而不是记忆题。

4. 提高效率的调试技巧:差距往往在工具熟练度

4.1 开发者工具里真正有用的三个面板

很多新手习惯在 Elements 面板上看页面结构,但逆向分析里 Elements 几乎没有作用。核心是三个面板:

  • Network:看请求、响应、参数,确定目标接口;
  • Sources:看 JS 文件、打断点、步进调试;
  • Console:执行临时代码、验证变量值、调用函数。

建议把这三个面板的快捷键练熟。Network 的过滤、Sources 的全局搜索、Console 的代码片段保存,都会直接影响分析效率。

4.2 XHR 断点和条件断点

XHR 断点是一个非常实用的功能:在 Sources 面板里添加 XHR/fetch 断点,填入 URL 片段,比如api/data,之后浏览器会在发出该请求之前自动暂停。这样你不需要反复刷新页面,也不需要手动找断点位置,很适合用于定位“请求发起前执行的加密代码”。

条件断点适合打断点位置命中太多次的情况。比如同一个 JS 函数被多个请求调用,你就可以在断点处加一个条件,只让包含特定参数值的那一次暂停。这能省下大量重复点击“继续执行”的时间。

4.3 用 Hook 打印调用信息

有时候你找到了加密函数,但不知道它被谁调用、传了什么参数。这时可以在 Console 里临时改写这个函数,打印出入参后再调用原逻辑:

const original = window.getSign; window.getSign = function (...args) { console.log('getSign 入参:', args); const result = original.apply(this, args); console.log('getSign 出参:', result); return result; };

这种“Hook”技巧非常适合在本地调试时观察函数行为。需要注意,改写全局函数只影响当前页面运行,刷新后会失效,所以它适合分析,不适合作为最终方案。

4.4 建立自己的 JS 本地运行环境

建议在本机安装 Node.js,建一个专门放提取代码的目录。把从页面里复制出来的函数放在独立文件里,用 Node.js 执行,再和 Python 结果对比。如果某个加密逻辑太复杂,重写工作量大,也可以直接用 Python 的 subprocess 调用 Node.js 脚本,把参数传进去拿到结果。

import subprocess result = subprocess.run( ["node", "sign.js", "test", "18"], capture_output=True, text=True, ) sign = result.stdout.strip()

这种做法的缺点是每次调用都会启动一次 Node.js 进程,性能一般,而且依赖 Node 环境,但对学习阶段或低频任务来说完全够用。等逻辑真正理解了,再考虑用 Python 重写。

5. 拿到 60 个案例之后,应该怎么安排学习顺序

5.1 给案例分难度,按阶段推进

如果手头确实有一套案例集,不管是教程里的还是自己整理的项目,都不要按顺序从头刷到最后。先分类,再按难度推进。

我建议分五个阶段:

  1. 编码与哈希类案例,熟悉整体定位流程;
  2. 对称加密与签名参数组合,掌握 key、IV、拼接顺序;
  3. RSA 与多参数联动,理解非对称加密和请求联动逻辑;
  4. 混淆与动态加密,练习断点和 Hook 技巧;
  5. 独立分析一个没看过的目标,不参考任何教程。

每完成一个阶段,记录自己花了多长时间、卡在哪一步。如果某个阶段反复卡住,不要硬刷,回去补对应的 JS 基础或加密知识。

5.2 每个案例只提炼一个核心点

案例做得越多越容易产生“我全都会”的错觉。实际上,一个案例里值得吸收的可能就一两个点。每做完一个案例,问自己三个问题:

  • 这个案例让我多学了一种定位方法吗?
  • 这个案例让我多认识了一种算法变体吗?
  • 这个案例让我踩了某个值得记住的坑吗?

如果三个答案都是否,说明这个案例对你已经没什么增量,可以快速跳过。学习效率不是由案例数量决定的,而是由有效的“认知点”数量决定的。

5.3 建立一套复盘模板

我建议每个案例都写成固定的复盘记录,格式可以参考:

项目内容
目标接口请求 URL 与方法
加密参数参数名、类型、特征
定位过程搜索了什么关键词、命中了什么函数
算法识别哈希 / 对称 / 非对称 / 自定义
关键代码JS 函数片段
Python 复现对应代码片段
验证结果三组样本输出是否一致
一句话收获本次最大的认知增量

这份记录不只是给别人看的,更是给自己留的一套检索库。以后遇到类似特征,直接搜自己的笔记就能快速定位方向。

5.4 练习目标一定要可控

案例练完之后,一定要自己找目标做独立练习。练习目标尽量选自己拥有或可控的页面。如果没有现成的,就用本地写一个带加密逻辑的页面来练,自己当“前端开发”,再自己当“逆向者”,从两端同时理解整个过程。这样既安全,又能清楚知道一个加密参数在前端代码里是什么形态。

6. 新手最容易踩的坑,和一套排查顺序

6.1 五个高频坑

根据我见过的案例,新手最容易踩的坑基本集中在五个地方。

第一个坑是只跑通一次就认为成功。请求里的时间戳会变、业务参数会变,一次成功不能证明逻辑正确,至少要做多组验证。

第二个坑是 Python 和 JS 的编码不一致。JS 里字符串按 UTF-8 处理,Python 里如果忘了.encode("utf-8"),或者用了str直接参与哈希,结果必然对不上。

第三个坑是参数拼接顺序错误。同一组参数,a=1&b=2b=2&a=1算出的哈希完全不同。拼接顺序必须以 JS 代码为准,不能凭感觉。

第四个坑是忽略了时间戳有效期。有的签名算法里 ts 就是当前时间,服务器会校验时间差。如果你在离线环境测试时用了很久以前的 ts,即使签名逻辑正确也会失败。

第五个坑是试图把整段混淆代码复制进 Python。混淆代码依赖浏览器环境、DOM、全局变量,在 Python 里根本无法运行。正确思路是把输入输出边界找出来,要么重写,要么用 Node.js 调用。

6.2 排查顺序

如果发现输出不一致,不要东一下西一下地乱试,按顺序排查:

  1. 看现象:是报错、返回参数错误,还是拿到了加密乱码;
  2. 看输入:抓包数据和本地 mock 数据是否完全一致,包括参数名、参数值、大小写、编码;
  3. 看算法:MD5 还是 SHA,AES 是 CBC 还是 ECB,填充方式一致性;
  4. 看参数:key、IV、salt、ts、nonce,是否拿到了正确值;
  5. 看格式:输出是 hex 还是 Base64,是否 URL-safe,是否去掉了 padding;
  6. 看环境:Node.js 版本、crypto-js 版本是否会影响结果。

这条链路看起来简单,但大多数人对不上结果的原因都藏在这一两步里。尤其是第 2 步,很多人直接用“看起来一样”的数据做比对,实际上参数顺序差了一个字符,就全错了。

6.3 长期来看,什么能力会沉淀下来

把 60 个案例拆完、把流程跑熟之后,最终沉淀下来的不是几个网站的破解代码,而是三样东西:阅读 JavaScript 代码的能力、调试复杂前端逻辑的能力、跨语言复现一段计算流程的能力。

这三样能力不会因为某个网站改版而失效,反而会迁移到其他领域。比如前端性能排查、接口自动化测试、Web 安全分析、浏览器插件开发,都会用到类似的思路。这也是我觉得 JS 逆向值得花时间学习的真正原因:它不是一门“爬虫专用技巧”,而是一套通用的前端代码分析能力。

回到开头那个判断:案例会过时,方法不会。你可以收集很多项目的逆向代码,但更要紧的是把“定位、分析、还原、验证”这八个字练成肌肉记忆。等你能不依赖教程,独立拆开一个陌生加密参数的时候,再回头看那些“秒变大神”的口号,就会明白真正值钱的是你在这条路上踩过的坑和沉淀下来的流程。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询