JS逆向工程实战:从网页加密参数破解到自动化请求模拟

📅 2026/7/5 23:55:54
JS逆向工程实战:从网页加密参数破解到自动化请求模拟
1. 项目概述从“黑盒”到“白盒”的探索最近在和一些做数据分析和自动化流程的朋友聊天发现大家对一个话题特别感兴趣但又觉得无从下手如何与那些没有开放API的网页应用进行交互特别是像一些大型平台的后台功能强大但接口封闭。很多人第一反应是去找“现成的工具”或者“破解版”但这不仅风险高而且一旦平台更新工具就失效了。我这些年处理过不少类似的需求从电商数据采集到办公自动化核心思路其实都绕不开一个技术——JS逆向工程。它不是什么“黑客技术”而是一种理解网页应用如何运作、数据如何流转的标准化分析方法。这次我们就以“微x公众平台”这个典型的、功能复杂且前端逻辑厚重的管理后台为例来一次完整的JS逆向实战拆解。目标不是教你做一个什么“万能工具”而是让你掌握一套通用的、可持续的“解题思路”。当你下次遇到任何需要分析网页请求、模拟登录、破解参数加密的场景时都能自己动手找到答案。这个项目适合谁呢如果你是一名爬虫工程师、测试开发或者是对Web前端安全、浏览器工作原理感兴趣的后端开发那么这篇文章会给你带来很多实操层面的启发。即使你只是偶尔需要从某个网站自动化获取一些数据理解JS逆向的基本流程也能帮你省去大量寻找不稳定第三方工具的时间。我们会从最基础的浏览器开发者工具使用讲起一步步追踪到核心的加密逻辑并最终用代码复现整个过程。记住我们的所有操作都基于公开的、前端必然要暴露给浏览器的JavaScript代码这是一种合法的技术分析手段。2. 逆向分析的核心思路与准备工作2.1 为什么选择JS逆向而不是其他方法面对一个功能丰富的Web应用我们通常有几种交互思路。最理想的是官方提供的API但像公众平台这类后台官方API往往只面向部分合作方或功能不全。其次是模拟浏览器操作的工具如Selenium或Puppeteer它们能完美执行点击、输入等动作但缺点也明显速度慢、资源消耗大、容易被反爬策略如验证码干扰且稳定性依赖于页面DOM结构。JS逆向的核心价值在于“降维打击”它直接分析前端JavaScript代码找到最终发起网络请求的那一刹那所有参数是如何生成的。一旦我们掌握了这个生成算法就可以在Node.js、Python等后端环境中脱离浏览器直接构造出合法的请求效率极高且行为更像一个“正常的API调用”。以“登录”这个场景为例。模拟浏览器需要加载整个登录页面渲染表单填入账号密码点击按钮等待跳转。而JS逆向的思路是打开登录页面在输入密码点击登录时用开发者工具抓包你会发现密码很可能不是明文发送的而是经过了一个名为encryptPassword的函数处理。我们的任务就是找到这个函数看懂它的逻辑是RSA加密还是AES密钥从哪里来然后用代码实现它。这样我们的脚本只需要输入明文密码就能生成和服务端匹配的密文直接发送登录请求。2.2 环境与工具准备你的数字侦探工具箱工欲善其事必先利其器。进行JS逆向分析你不需要什么特殊软件一个现代化的浏览器和几个插件足矣。主力浏览器Google Chrome 或 Microsoft Edge。它们的开发者工具DevTools功能最强大、更新最及时。本文将基于Chrome进行演示。核心武器浏览器开发者工具。快捷键 F12 或 CtrlShiftI 打开。我们需要重点掌握以下几个面板Network网络所有HTTP/HTTPS请求的记录器。这是我们的起点也是终点。务必勾选 “Preserve log”保留日志以防止页面跳转时清空记录并可以开启 “Disable cache”禁用缓存保证拿到最新资源。Sources源代码存放着该页面加载的所有JavaScript文件。我们可以在这里设置断点、单步调试是逆向分析的主战场。Console控制台可以执行任意的JavaScript代码用于测试我们找到的函数或者查看特定变量的值。Application应用查看和操作本地存储、Session Storage、Cookie等这些常常是加密密钥或令牌的存放地。辅助插件非必需但强烈推荐EditThisCookie方便地查看和编辑当前网站的Cookie在测试时非常有用。SwitchyOmega配合代理工具用于可能的请求拦截和重放测试。代码复现环境根据你的喜好准备Node.js或Python环境。我们将用它们来重写我们找到的加密算法。Python的话requests库用于发请求execjs或PyExecJS库可以执行JavaScript代码片段如果算法复杂不想翻译成Python的话。注意在进行任何分析前请务必阅读目标网站的robots.txt文件和服务条款确保你的行为在合规范围内。本教程仅用于技术学习和交流请勿用于干扰网站正常运行、窃取敏感数据等非法用途。3. 实战第一步网络请求抓包与关键接口定位一切分析始于观察。我们打开目标网站进入登录页面。先别急着输入账号打开开发者工具的 Network 面板并确保录制按钮是红色的开启状态。3.1 录制登录全过程寻找认证入口清空现有记录然后在页面上输入账号、密码可以用测试账号点击登录。此时Network面板会瞬间涌现出大量请求。我们的目标是找到那个真正负责“认证”的请求。通常它具有以下特征请求URL包含login,auth,signin等关键字或者是一个看起来像API的路径如/cgi-bin/login。请求方法通常是POST。请求载荷Form Data或Payload里会包含你的用户名和一个经过加密的密码字段而不是明文。响应结果登录成功后会返回一个重要的令牌如token,session_id,cookie信息或者直接跳转。例如你可能会找到一个名为webwxlogin或login的POST请求。点击它查看Headers和Payload。在Payload里你很可能看到usernameyour_emailpassword一长串看不懂的字符...。这“一长串字符”就是我们的主攻目标。记下这个请求的完整URL和请求体格式。3.2 深入查看请求载荷与参数构成除了密码这个登录请求往往还包含其他动态参数它们共同构成了服务端验证的“一次一密”机制。常见参数有nonce/salt一个随机数用于防止重放攻击。timestamp当前时间戳。sign/signature对某些参数可能包含密码、时间戳、随机数进行拼接后再通过某种算法如HMAC-SHA256计算出的签名。_一个下划线参数有时是毫秒级时间戳用于防止缓存。你需要把这些参数名全部记录下来。接下来我们的核心问题就是这个加密后的密码以及可能的签名到底是怎么生成的生成它们的JavaScript代码一定在页面上加载的某个.js文件里。4. 逆向核心源代码搜索与断点调试技巧找到目标请求后我们切换到Sources面板。面对成百上千个压缩过的JS文件如何大海捞针4.1 全局搜索关键参数名在Sources面板中按CtrlShiftF打开全局搜索框。这是一个超级强大的功能可以跨所有已加载的文件搜索字符串。搜索加密参数名比如你在Payload里看到加密后的密码参数名是encryptedPassword那就直接搜索这个变量名。如果找不到可以尝试搜索password,pwd,encrypt等更通用的关键词。搜索请求URL的关键部分搜索webwxlogin或/cgi-bin/login可能会直接找到发起这个AJAX请求的代码位置。搜索可能的关键函数名如getEncryptedPassword,sign,hmac,encode等。一旦搜索到结果点击进入对应的JS文件。这些文件通常是被压缩minified的变量名都是a, b, c, d。Chrome提供了{}Pretty Print按钮点击它可以将代码格式化变得可读一些。4.2 设置断点动态追踪执行流静态阅读混淆的代码非常困难。我们需要让代码“跑起来”并在关键位置暂停观察变量的实际值。这就是断点调试。在可疑函数处设断点在格式化后的代码中找到疑似加密函数的地方比如一个函数里调用了CryptoJS.AES.encrypt或出现了md5、sha256等字样在行号左侧点击设置一个断点蓝色标记。在事件监听处设断点在Sources面板的右侧有个 “Event Listener Breakpoints” 区域。展开 “Mouse” 事件勾选click。这样当你在页面点击登录按钮时代码会自动在触发点击事件的函数处暂停。然后你可以通过 “Call Stack” 调用堆栈一步步回溯找到最终处理表单提交和加密的函数。XHR/Fetch断点在右侧 “XHR/Fetch Breakpoints” 处点击输入你之前找到的登录请求URL的一部分如login。这样当任何JavaScript代码发起包含该关键词的请求时执行流会自动暂停并且暂停的位置就是发起请求如fetch()或XMLHttpRequest.send()的那一行代码。这是最直接、最有效的定位方法。当代码在断点处暂停后你就可以查看作用域变量在右侧 “Scope” 面板查看当前函数作用域内所有变量的值。特别是this对象和局部变量。单步执行使用F10Step Over逐过程执行F11Step Into逐语句执行会进入函数内部。跟着执行流看你的明文密码是如何一步步被加工成密文的。在控制台实时测试在Console面板你可以引用当前作用域内的任何变量或函数。例如当暂停在加密函数内时你可以在控制台输入encryptFunction(myPassword123)来测试这个函数看输出是否和抓包到的密文一致。4.3 一个典型的加密函数追踪案例假设我们通过XHR断点在send()前暂停。查看调用堆栈我们一层层往上找找到了一个函数它负责组装请求数据。在这个函数里我们看到了类似这样的代码var e { username: o.username, password: r.encrypt(t.password, i.salt), timestamp: Date.now(), nonce: s.generateNonce() }; e.signature c.calculateSign(e, i.secretKey);这里清晰地展示了逻辑password是使用r.encrypt函数对明文密码t.password和一个盐值i.salt进行加密的结果。生成了timestamp和nonce。最后使用c.calculateSign函数对整个对象e和一个密钥i.secretKey计算签名。我们的任务就变成了找到r.encrypt和c.calculateSign这两个函数的实现。你可以通过控制台查看r和c是什么对象或者直接在源代码中搜索encrypt:和calculateSign:。5. 算法解析与代码复现从JS到Python/Node.js经过一番调试我们终于找到了核心的加密函数。假设它看起来是这样的经过反混淆和简化// 假设的加密函数可能是AES加密 function encryptPassword(password, salt, key) { var CryptoJS require(crypto-js); // 假设页面引入了CryptoJS库 // 可能先将salt和password拼接 var dataToEncrypt salt password; // 使用CBC模式PKCS7填充密钥可能是固定的或从服务器获取 var encrypted CryptoJS.AES.encrypt( CryptoJS.enc.Utf8.parse(dataToEncrypt), CryptoJS.enc.Utf8.parse(key), { iv: CryptoJS.enc.Utf8.parse(1234567890123456), // 初始化向量 mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ); // 返回Base64字符串 return encrypted.toString(); }5.1 方案一使用Node.js直接调用JS代码最稳妥如果算法复杂或者重度依赖浏览器环境特有的对象如window,document直接翻译成其他语言可能出错。这时使用Node.js的vm2沙盒模块或jsdom来模拟浏览器环境并直接执行原JS函数是最稳妥的。提取关键函数将你找到的加密函数及其所有依赖函数包括它调用的其他函数、使用的全局变量完整地复制到一个单独的.js文件中。创建补丁环境检查原函数是否依赖CryptoJS、jQuery等库。你需要通过npm安装这些库npm install crypto-js并在你的JS文件开头通过require引入。封装成模块将加密逻辑封装成一个导出函数。在Node.js中调用// encrypt.js const CryptoJS require(crypto-js); module.exports function encryptPassword(password, salt, key) { // ... 粘贴完整的加密逻辑 return encrypted.toString(); }; // main.js const encrypt require(./encrypt.js); const encryptedPwd encrypt(myPassword, randomSalt, secretKey); console.log(encryptedPwd);将encryptedPwd与你抓包到的密文对比一致则成功。5.2 方案二使用Python的execjs库折中方案如果你的Python技术栈更熟练可以使用PyExecJS库。它提供了一个桥梁让你能在Python中执行JavaScript代码。import execjs # 读取包含加密函数的JS文件 with open(encrypt.js, r, encodingutf-8) as f: js_code f.read() # 创建上下文并编译 ctx execjs.compile(js_code) # 调用函数 encrypted_password ctx.call(encryptPassword, myPassword, randomSalt, secretKey) print(encrypted_password)注意PyExecJS背后需要有一个JavaScript运行时如Node.js。你需要先安装Node.js并且execjs库有时对复杂ES6语法或特定浏览器API支持不佳可能需要调整JS代码。5.3 方案三纯Python实现最优雅但难度高如果算法是标准的、已知的如MD5、SHA256、HMAC、AES、RSA那么用Python的密码学库如hashlib,hmac,pycryptodome重写是最干净、依赖最少的方案。哈希/MD5/SHA256直接使用hashlib。import hashlib m hashlib.md5() m.update(byour_password) print(m.hexdigest())HMAC使用hmac模块。import hmac import hashlib message bdata_to_sign key bsecret_key signature hmac.new(key, message, hashlib.sha256).hexdigest()AES使用pycryptodome库。from Crypto.Cipher import AES from Crypto.Util.Padding import pad import base64 key b16bytekey12345678 # 16, 24, 32 bytes iv b16byteiv12345678 cipher AES.new(key, AES.MODE_CBC, iv) data pad(bpasswordsalt, AES.block_size) encrypted cipher.encrypt(data) result base64.b64encode(encrypted).decode()关键点你必须通过JS调试100%确定算法的类型、模式、填充方式、密钥、初始向量IV以及数据的编码方式是字符串直接加密还是先转成了字节数组。一个参数不对结果就天差地别。6. 完整流程串联与模拟请求当我们成功复现了加密算法后就可以组装一个完整的自动化脚本了。流程如下获取动态参数很多加密需要的盐salt、密钥key或令牌token并不是写死在JS里的而是在页面加载时通过另一个接口从服务器获取的。你需要先模拟一个请求比如访问登录页从响应HTML或后续的接口中提取这些值。这可能涉及到解析HTML、执行内联JS或处理一个初始的JSONP请求。计算加密密码和签名使用我们复现的算法结合获取到的动态参数和用户的明文密码计算出加密后的密码和请求签名。组装请求体按照抓包看到的格式组装字典。发送请求使用requests.post()Python或axios.post()Node.js发送请求。务必注意请求头特别是Content-Type,User-Agent,Referer,X-Requested-With等尽量模拟得和浏览器一致。Cookie也可能在前期请求中设置需要维护一个会话requests.Session()。处理响应登录成功后服务器通常会在响应头Set-Cookie或响应体中返回一个会话标识如sessionid,token。你需要将这个标识保存下来并附加到后续所有请求的头部如Cookie头或Authorization头。# Python示例伪代码 import requests from your_encrypt_module import encrypt_password, calculate_sign session requests.Session() headers { User-Agent: Mozilla/5.0..., Referer: https://目标网站/login } # 1. 获取初始页面和动态参数 index_resp session.get(https://目标网站/login, headersheaders) # 从 index_resp.text 中解析出 salt, key 等可能需要正则或解析HTML # 2. 计算加密参数 encrypted_pwd encrypt_password(your_password, salt, key) timestamp int(time.time() * 1000) nonce generate_nonce() data { username: your_username, password: encrypted_pwd, timestamp: timestamp, nonce: nonce } data[signature] calculate_sign(data, secret_key) # 3. 发送登录请求 login_url https://目标网站/api/login login_resp session.post(login_url, jsondata, headersheaders) # 4. 检查登录是否成功并保存token if login_resp.json().get(code) 0: token login_resp.json()[data][token] session.headers.update({Authorization: fBearer {token}}) print(登录成功) # 后续可以使用这个 session 进行其他授权操作 else: print(登录失败:, login_resp.text)7. 常见问题、反爬策略与应对技巧在实际操作中你绝不会一帆风顺。平台会有各种防御措施来防止自动化脚本。7.1 代码混淆与压缩这是最基本的防御。代码被压缩成单行变量名替换为无意义的短字符。应对方法使用浏览器的Pretty Print这是第一步。关注字符串和数字常量混淆不会改变字符串和数字。搜索关键URL、参数名、错误信息可以帮你定位到相关代码区域。函数调用追踪即使变量名是a(b,c)你也可以通过断点查看a,b,c的实际值来判断这个函数在做什么。7.2 动态加载JS与代码分割核心加密逻辑可能不在最初的bundle.js里而是在用户执行某个操作如点击登录后通过AJAX动态加载另一个JS文件。应对方法在Network面板中筛选JS类型请求在点击操作后观察是否有新的JS文件被加载。对这些新加载的JS文件同样进行搜索和断点调试。7.3 反调试技巧有些网站会检测开发者工具是否打开如果打开可能会跳转到空白页、无限debugger或执行虚假代码。无限Debugger在Sources面板找到这个debugger语句所在的行右键点击行号选择 “Never pause here” 或 “Add conditional breakpoint” 并输入false。检测DevTools网站可能会通过检查window.outerHeight与window.innerHeight的差值等方式来检测。一种应对方法是使用 “Device Mode” (CtrlShiftM) 切换设备模拟有时可以绕过。更彻底的方法是使用无头浏览器如Puppeteer先获取到关键的JS代码和参数然后再在本地分析。7.4 环境依赖检测加密算法可能依赖浏览器特有的环境如window,navigator.userAgent,document.cookie甚至检测鼠标移动、键盘事件。应对方法在Node.js复现时使用jsdom库模拟一个基本的浏览器环境。在加密函数中如果遇到环境检测尝试分析其逻辑。有时它只是获取一个固定值如navigator.appName你完全可以在你的执行环境中定义一个同名的全局变量来“欺骗”它。对于复杂的鼠标键盘检测如果只是登录环节有可以考虑混合策略用无头浏览器完成登录获取到有效的Cookie或Token后再用这个Token去发起后续的API请求。7.5 密钥或盐值动态变化这是最棘手的情况。加密密钥不是写死的而是每次登录前都从一个接口动态获取并且可能和当前会话、时间戳甚至客户端的一些指纹信息绑定。应对方法仔细分析获取密钥的请求这个请求本身可能也有签名或验证。你需要先模拟这个请求。它的参数可能来自上一个请求的响应或者是在页面加载时初始化的一个全局变量。理解密钥的生成逻辑有时密钥本身也是通过一个算法生成的而不是服务器直接下发。你需要找到生成密钥的JS代码。保持会话确保你的脚本使用同一个会话Session来串联起“获取密钥”和“登录”两个请求因为服务器可能根据Session来配对。8. 安全、合规与伦理边界最后也是最重要的一部分我们必须反复强调安全与合规的边界。JS逆向是一项强大的技术分析技能但它必须被用在正确的场景。尊重robots.txt这是网站管理员表达爬虫意愿的文件。如果明确禁止了相关路径的爬取请遵守。遵守服务条款几乎所有网站的用户协议都禁止未经授权的自动化访问和数据抓取。在从事任何自动化操作前请仔细阅读。控制请求频率即使是为了测试也要避免高频请求这会对服务器造成压力可能被视为攻击。在脚本中添加随机延时如time.sleep(random.uniform(1, 3))。识别公开数据与私有数据分析公开可见的信息如商品列表、文章内容与需要登录才能访问的私有信息如个人订单、聊天记录有本质区别。后者涉及更高的法律和隐私风险。用于学习与效率工具最好的应用场景是为自己或团队创造效率工具比如自动备份自己发布的内容、统计自己的运营数据、将信息聚合到个人看板等。这些是“为自己赋能”风险可控价值也高。我个人的经验是把JS逆向看作一把“手术刀”而不是“锤子”。它的目的是精确地理解系统交互的机理从而创造出更优雅、更高效的解决方案。这个过程极大地锻炼了你阅读代码、调试程序和系统思考的能力这些能力的价值远超过你通过它获取的某一次数据。当你成功破解一个复杂的参数加密时那种“原来如此”的顿悟感和成就感才是这个技术最大的乐趣所在。