ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JS逆向60个案例背后的通用分析流程:从定位加密参数到Python还原

JS逆向60个案例背后的通用分析流程:从定位加密参数到Python还原 第一次在开发者工具里看到满屏加密参数时大多数人都会意识到一件事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 面板在全局文件里搜索参数名。快捷键通常是 CtrlShiftF可以搜索所有 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 fname{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。同一个值a1b2和12算出来的 MD5 完全不一样。所以拿到代码后第一件事是确认原始字符串到底怎么拼的。有的会把参数按字母序排序有的会加固定前缀有的还会在中间插入时间戳这些细节都是决定成败的关键。3.2 对称加密AES 与 DES对称加密需要密钥和哈希不同它可逆。特征上密文通常是 Base64 字符串长度和明文相关常见模式有 AES-CBC、AES-ECB。CBC 模式需要 IVECB 模式不需要。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 非对称加密RSARSA 的特点是公钥加密、私钥解密。前端拿到公钥加密一段数据后发给服务器。逆向者只需要复现“用公钥加密”这个过程不需要知道私钥所以 RSA 的还原反而不难。常见实现是 JSEncryptconst 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_outputTrue, textTrue, ) sign result.stdout.strip()这种做法的缺点是每次调用都会启动一次 Node.js 进程性能一般而且依赖 Node 环境但对学习阶段或低频任务来说完全够用。等逻辑真正理解了再考虑用 Python 重写。5. 拿到 60 个案例之后应该怎么安排学习顺序5.1 给案例分难度按阶段推进如果手头确实有一套案例集不管是教程里的还是自己整理的项目都不要按顺序从头刷到最后。先分类再按难度推进。我建议分五个阶段编码与哈希类案例熟悉整体定位流程对称加密与签名参数组合掌握 key、IV、拼接顺序RSA 与多参数联动理解非对称加密和请求联动逻辑混淆与动态加密练习断点和 Hook 技巧独立分析一个没看过的目标不参考任何教程。每完成一个阶段记录自己花了多长时间、卡在哪一步。如果某个阶段反复卡住不要硬刷回去补对应的 JS 基础或加密知识。5.2 每个案例只提炼一个核心点案例做得越多越容易产生“我全都会”的错觉。实际上一个案例里值得吸收的可能就一两个点。每做完一个案例问自己三个问题这个案例让我多学了一种定位方法吗这个案例让我多认识了一种算法变体吗这个案例让我踩了某个值得记住的坑吗如果三个答案都是否说明这个案例对你已经没什么增量可以快速跳过。学习效率不是由案例数量决定的而是由有效的“认知点”数量决定的。5.3 建立一套复盘模板我建议每个案例都写成固定的复盘记录格式可以参考项目内容目标接口请求 URL 与方法加密参数参数名、类型、特征定位过程搜索了什么关键词、命中了什么函数算法识别哈希 / 对称 / 非对称 / 自定义关键代码JS 函数片段Python 复现对应代码片段验证结果三组样本输出是否一致一句话收获本次最大的认知增量这份记录不只是给别人看的更是给自己留的一套检索库。以后遇到类似特征直接搜自己的笔记就能快速定位方向。5.4 练习目标一定要可控案例练完之后一定要自己找目标做独立练习。练习目标尽量选自己拥有或可控的页面。如果没有现成的就用本地写一个带加密逻辑的页面来练自己当“前端开发”再自己当“逆向者”从两端同时理解整个过程。这样既安全又能清楚知道一个加密参数在前端代码里是什么形态。6. 新手最容易踩的坑和一套排查顺序6.1 五个高频坑根据我见过的案例新手最容易踩的坑基本集中在五个地方。第一个坑是只跑通一次就认为成功。请求里的时间戳会变、业务参数会变一次成功不能证明逻辑正确至少要做多组验证。第二个坑是 Python 和 JS 的编码不一致。JS 里字符串按 UTF-8 处理Python 里如果忘了.encode(utf-8)或者用了str直接参与哈希结果必然对不上。第三个坑是参数拼接顺序错误。同一组参数a1b2和b2a1算出的哈希完全不同。拼接顺序必须以 JS 代码为准不能凭感觉。第四个坑是忽略了时间戳有效期。有的签名算法里 ts 就是当前时间服务器会校验时间差。如果你在离线环境测试时用了很久以前的 ts即使签名逻辑正确也会失败。第五个坑是试图把整段混淆代码复制进 Python。混淆代码依赖浏览器环境、DOM、全局变量在 Python 里根本无法运行。正确思路是把输入输出边界找出来要么重写要么用 Node.js 调用。6.2 排查顺序如果发现输出不一致不要东一下西一下地乱试按顺序排查看现象是报错、返回参数错误还是拿到了加密乱码看输入抓包数据和本地 mock 数据是否完全一致包括参数名、参数值、大小写、编码看算法MD5 还是 SHAAES 是 CBC 还是 ECB填充方式一致性看参数key、IV、salt、ts、nonce是否拿到了正确值看格式输出是 hex 还是 Base64是否 URL-safe是否去掉了 padding看环境Node.js 版本、crypto-js 版本是否会影响结果。这条链路看起来简单但大多数人对不上结果的原因都藏在这一两步里。尤其是第 2 步很多人直接用“看起来一样”的数据做比对实际上参数顺序差了一个字符就全错了。6.3 长期来看什么能力会沉淀下来把 60 个案例拆完、把流程跑熟之后最终沉淀下来的不是几个网站的破解代码而是三样东西阅读 JavaScript 代码的能力、调试复杂前端逻辑的能力、跨语言复现一段计算流程的能力。这三样能力不会因为某个网站改版而失效反而会迁移到其他领域。比如前端性能排查、接口自动化测试、Web 安全分析、浏览器插件开发都会用到类似的思路。这也是我觉得 JS 逆向值得花时间学习的真正原因它不是一门“爬虫专用技巧”而是一套通用的前端代码分析能力。回到开头那个判断案例会过时方法不会。你可以收集很多项目的逆向代码但更要紧的是把“定位、分析、还原、验证”这八个字练成肌肉记忆。等你能不依赖教程独立拆开一个陌生加密参数的时候再回头看那些“秒变大神”的口号就会明白真正值钱的是你在这条路上踩过的坑和沉淀下来的流程。
返回列表