JS逆向实战:破解前端加密参数payload与sig的完整流程

JS逆向实战:破解前端加密参数payload与sig的完整流程 1. 项目概述从抓包到逆向破解前端JS加密参数最近在分析一个数据接口时遇到了典型的JS加密参数问题。目标网站的数据加载方式很有意思它不是传统的分页而是通过鼠标下滑动态加载。用Selenium这类自动化工具去模拟滚动效率低不说还容易被反爬机制识别。更关键的是直接请求其API返回的数据是加密的而请求必须携带两个由前端JavaScript动态生成的加密参数payload和sig。去掉或者传错这两个参数服务器直接返回错误。这显然是一个学习JS逆向和Python模拟加密的绝佳案例。通过逆向分析我们不仅能拿到数据这套思路同样适用于分析那些对登录密码进行前端加密的站点进行安全审计或授权测试。今天我就把完整的分析思路、逆向过程和Python实现代码拆解清楚让你不仅能复现更能理解背后的“为什么”。2. 核心思路与逆向分析前的准备2.1 目标分析与工具选型首先明确目标我们需要绕过前端JS加密直接使用Python构造合法的HTTP请求来获取数据。核心在于逆向出payload和sig这两个参数的生成算法。为什么不直接用Selenium原文提到了网站是动态加载没有分页。这意味着你需要用Selenium控制浏览器不断模拟滚动等待新内容加载再提取。这个过程极其耗时对网络稳定性要求高并且大量、频繁的自动化操作很容易触发网站的反爬策略如IP限制、验证码。对于需要稳定、高效获取数据的场景直接逆向接口才是更优解。工欲善其事必先利其器。逆向分析离不开浏览器开发者工具Chrome/Edge DevTools核心工具。主要用到Network网络面板抓包和Sources源代码面板进行JavaScript调试。Overrides本地覆盖Chrome DevTools的一个神级功能。允许你将在线JS文件映射到本地修改后的版本实现断点持久化、代码修改即时生效无需每次刷新都重新寻找断点。Python环境需要requests库发起网络请求以及hashlib等标准库用于实现加密算法。注意所有分析与操作均应在法律允许和网站授权范围内进行仅用于学习交流与技术研究切勿用于非法爬取、侵犯他人隐私或破坏网站正常运行。2.2 抓包与参数定位打开目标网页清空Network记录然后进行下滑操作触发数据加载。很快就能在Network面板中找到一个XHR或Fetch请求其响应内容是一串看似乱码的加密数据。点击这个请求查看Headers部分重点是Payload或Query String Parameters。在这个案例中我们发现在请求的负载Payload里有两个关键的参数payload: 一长串Base64编码样式的字符串。sig: 一个32位的十六进制大写字符串很像MD5。尝试在Python中用requests库模拟这个请求如果不带这两个参数或者任意修改其中一个字符服务器返回的都是错误信息如{code: 400, msg: invalid signature}。这证实了这两个参数是服务端进行请求合法性校验的关键。初步观察payload参数很可能包含了我们真正的查询条件如页码、每页条数并被加密了。而sig看起来像是一个签名很可能由payload加上某个密钥_P后再经过MD5之类哈希算法生成用于防止参数被篡改。3. JS逆向实战深入加密函数腹地逆向的核心是在庞大的JS代码中找到生成这两个参数的函数。这需要耐心和技巧。3.1 定位加密入口搜索与断点全局搜索在Sources面板按CtrlShiftF进行全局搜索。可以尝试搜索关键词如payload、sig、encrypt、md5、_P等。在这个案例中搜索sig可能直接定位到类似sig: md5(e _p).toUpperCase()的代码行这就是突破口。XHR/Fetch断点如果搜索无果可以在Network面板找到那个请求的Initiator发起者点击跳转到发起请求的JS代码行。更通用的方法是在Sources面板的XHR/Fetch Breakpoints里添加一个包含部分请求URL的断点。当浏览器发起匹配的请求时代码执行会自动暂停。事件监听断点在Event Listener Breakpoints中勾选Script-Script First Statement然后触发页面动作如下滑代码会在第一时间暂停然后你可以一步步F10执行观察网络请求何时被发出。通过上述方法我们成功在代码中找到了疑似生成sig的地方并在此处打上断点。3.2 逆向payload加密流程刷新页面或再次触发数据加载代码会在断点处暂停。我们顺着调用栈Call Stack向上追溯寻找payload被加密的地方。从原文描述和调试过程我们梳理出payload的加密路径明文payload对象最初是一个简单的JavaScript对象例如{sort: 1, start: 40, limit: 20}。其中start是起始位置limit是每页条数通过改变start来实现“翻页”。进入函数e2(e)参数e是明文对象。内部调用了_u_e(e)这个函数看起来只是将对象JSON序列化成字符串{sort:1,start:40,limit:20}此时值未变。e2(e)的后续操作在_u_e返回后e2函数内部有一个for循环对字符串进行了处理。经过这一步字符串变成了一个包含不可见字符和乱码的中间状态,x177WB:d\ym{1L$x10nx02x04x15p8[ olwx022。这是一个关键转变说明加密的核心可能发生在这里可能是某种自定义的混淆或编码。进入函数e1(e)上一步的结果作为参数e传入e1。这个函数执行后返回了最终的payload加密值LBc3V0I6ZGB5bXsxTCQnPRBuBwYJfnZeJCM7OXR/AH8q。逆向心得payload的加密并非标准算法如AES而是一个网站自定义的、由e2和e1两个函数组成的流程。其中e2可能负责混淆e1看起来像是进行了Base64编码因为输出字符集符合Base64特征。但注意直接对中间状态的乱码做Base64编码是得不到这个结果的。这说明e1内部可能还包含了字符替换或二次加密。我们需要把e2和e1这两个函数的完整JS代码抠出来。3.3 逆向sig签名生成流程sig的生成相对清晰。在断点处继续执行发现sig的值是通过md5(e _p).toUpperCase()计算得出的。e就是上一步得到的加密后的payload字符串LBc3V0I6ZGB5bXsxTCQnPRBuBwYJfnZeJCM7OXR/AH8q。_p一个常量字符串在JS代码中可以找到例如可能是W5D80NFZHAYB8EUI2T649RT2MNRMVE2O。将两者拼接然后进行MD5哈希最后将结果转为大写。通过跟踪md5函数通常是一个名为md5的函数或CryptoJS.MD5确认其内部实现是标准的MD5算法没有额外的魔改。这意味着在Python中我们可以直接用hashlib库的标准MD5来实现无需调用JS环境。踩坑记录在抠md5函数时注意检查它是否依赖了其他全局变量或函数。有些站点会修改MD5的初始常量IV来定制算法。务必在调试器中用相同的输入payload_p运行JS的md5函数与Pythonhashlib.md5().hexdigest().upper()的结果进行比对确保完全一致。4. Python实现还原加密与数据获取分析清楚后我们就可以用Python来模拟整个流程了。这里分为两个部分实现加密函数以及发起请求并解密响应。4.1 环境准备与JS代码移植首先我们需要将关键的JS函数移植到Python中。对于sig的MD5直接用Python标准库。import hashlib def generate_sig(encrypted_payload: str, p_constant: str) - str: 生成 sig 参数 :param encrypted_payload: 加密后的 payload 字符串 :param p_constant: 从JS中提取的常量 _P :return: 大写格式的MD5 hexdigest data encrypted_payload p_constant return hashlib.md5(data.encode(utf-8)).hexdigest().upper()对于payload的加密我们需要将e2和e1函数用Python重写。这需要仔细分析原JS代码。假设我们通过“Overrides”功能将JS文件保存到本地并仔细分析后发现e2函数是一个简单的字符替换/位移混淆e1是一个变种的Base64编码可能更换了码表。import json import base64 # 假设我们分析出的 e2 函数是一个简单的异或混淆 def e2_js_logic(plain_dict: dict) - str: 模拟JS中的 e2 函数 json_str json.dumps(plain_dict, separators(,, :)) # 模拟 _u_e紧凑格式 # 假设JS中的for循环是每个字符码点与一个固定值进行异或 key 0x17 # 这个key需要从JS代码中分析得出 confused_chars [] for char in json_str: confused_chars.append(chr(ord(char) ^ key)) confused_str .join(confused_chars) return confused_str # 假设我们分析出的 e1 函数是更换了码表的Base64编码 # 标准码表: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/ # 自定义码表: 从JS代码中提取例如可能是倒序或替换的 CUSTOM_B64_TABLE W5D80NFZHAYB8EUI2T649RT2MNRMVE2OabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/ # 示例需替换真实码表 STANDARD_B64_TABLE ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/ def e1_js_logic(confused_str: str) - str: 模拟JS中的 e1 函数自定义Base64编码 # 先进行标准base64编码 standard_b64 base64.b64encode(confused_str.encode(utf-8)).decode(ascii) # 然后根据自定义码表进行字符替换 translation_table str.maketrans(STANDARD_B64_TABLE, CUSTOM_B64_TABLE) custom_b64 standard_b64.translate(translation_table) return custom_b64 def generate_payload(plain_dict: dict) - str: 生成加密的 payload 参数 confused e2_js_logic(plain_dict) encrypted e1_js_logic(confused) return encrypted重要提示上面的e2_js_logic和e1_js_logic是示例逻辑绝非真实算法。你必须根据自己逆向分析出的真实JS代码来编写对应的Python函数。可能需要使用execjs库直接执行抠出来的JS代码片段这对于复杂混淆是最稳妥的办法。import execjs # 将抠出来的完整e2和e1函数代码放在一个字符串里 js_code function _u_e(obj) { return JSON.stringify(obj); } function e2(e) { // ... 完整的e2函数JS代码 ... } function e1(e) { // ... 完整的e1函数JS代码 ... } function generatePayload(obj) { return e1(e2(obj)); } ctx execjs.compile(js_code) encrypted_payload ctx.call(generatePayload, {sort: 1, start: 40, limit: 20}) print(encrypted_payload) # 输出应与浏览器生成的一致4.2 构建请求与解密响应有了生成参数的函数就可以组装请求了。import requests def get_data(page: int, page_size: int 20): 获取某一页数据 :param page: 页码从0开始 :param page_size: 每页条数 # 1. 构造明文参数 plain_params { sort: 1, start: page * page_size, # 计算start limit: page_size } # 2. 生成加密payload (使用execjs或Python还原函数) encrypted_payload generate_payload(plain_params) # 使用上文定义的方法 # 3. 生成sig (假设已知常量_P) _P W5D80NFZHAYB8EUI2T649RT2MNRMVE2O # 从JS中提取的真实常量 sig generate_sig(encrypted_payload, _P) # 4. 构造请求 url https://目标网站/api/data # 替换为真实API地址 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Content-Type: application/x-www-form-urlencoded, # 根据抓包确定 # 可能还需要其他Headers如Referer, Authorization等 } data { payload: encrypted_payload, sig: sig } # 5. 发送请求 resp requests.post(url, datadata, headersheaders) resp.raise_for_status() # 检查HTTP错误 # 6. 处理加密响应 encrypted_response resp.json().get(d) # 假设响应JSON中数据在d字段 if not encrypted_response: print(响应中未找到加密数据字段) return None # 响应数据也是加密的需要找到对应的JS解密函数并移植 # 假设解密函数叫 decryptData decrypted_data decrypt_response_data(encrypted_response) # 需要实现此函数 return decrypted_data # 使用示例 data_page_2 get_data(page2) # 获取第三页数据start40 if data_page_2: print(json.dumps(data_page_2, indent2, ensure_asciiFalse))4.3 响应数据解密原文提到返回的d字段也是加密的。我们需要用同样的逆向思路找到解密d的JS函数。通常解密函数会在同一个JS文件里可能在加密函数附近。找到后用同样的方式分析算法用Python重写或直接用execjs调用实现解密。def decrypt_response_data(encrypted_data: str) - dict: 解密响应中的d字段 :param encrypted_data: 加密的字符串 :return: 解密后的Python字典 # 方法A如果解密算法简单用Python重写 # ... 分析JS解密函数并实现 ... # 方法B使用execjs调用抠出来的JS解密函数推荐更可靠 js_decrypt_code function decryptData(encryptedStr) { // ... 抠出来的完整JS解密函数 ... } ctx execjs.compile(js_decrypt_code) decrypted_str ctx.call(decryptData, encrypted_data) return json.loads(decrypted_str)5. 常见问题、调试技巧与进阶思考5.1 逆向与调试中的常见问题断点被反调试有些网站会检测开发者工具并在调试时触发无限debugger或跳转。解决方法在Sources面板找到干扰的代码行右键选择Never pause here。使用Overrides功能将包含反调试代码的JS文件替换为清理后的版本。使用条件断点右键行号 -Add conditional breakpoint设置一个永远为假的条件。代码被混淆压缩变量名变成a,b,c难以阅读。解决方法利用浏览器的Pretty Print功能{}图标格式化代码。关注字符串常量、API接口URL它们通常不会被混淆是重要的定位锚点。逐步执行观察变量值的变化来推断函数功能。加密函数依赖浏览器环境某些函数可能依赖window、document或其他浏览器特有对象。在Node.js或execjs环境中执行时会报错。解决方法使用jsdom、pyppeteer等库模拟一个简易浏览器环境。更简单的方法是分析其依赖在JS代码执行前手动在全局注入这些缺失的对象或函数window {}; document {};但只注入必要的空对象或模拟函数。Python生成的签名与JS不一致这是最常遇到的问题。编码问题确保字符串拼接和编码完全一致。JS和Python的字符串编码要统一通常使用UTF-8。在MD5前确认拼接后的字符串完全一致包括不可见字符。常量错误双重检查从JS中提取的常量_P是否正确是否有隐藏的换行符或空格。算法差异确认JS中的MD5是否是标准实现。可以用一个已知的字符串如test分别在JS控制台和Python中计算MD5进行比对。5.2 效率优化与工程化建议缓存与复用对于固定的_P常量和不变化的加密逻辑部分只需初始化一次如execjs编译环境避免每次请求都重新编译JS极大提升效率。错误重试与日志网络请求不稳定应添加重试机制和详细的日志记录记录每次请求的参数和响应便于排查问题。参数化与配置将URL、请求头、常量_P、加解密函数代码等提取到配置文件或单独模块中使代码更易维护。应对算法更新网站可能会更新加密算法。最好能监控请求是否突然开始失败并准备快速响应重新进行逆向分析。5.3 进阶更复杂的加密与RPC调用本例的加密相对直接。你可能会遇到更复杂的情况WebAssembly加密核心算法用Wasm实现逆向难度大。可以考虑直接调用Wasm模块或者用工具将Wasm反编译为C/Go再分析。Obfuscator等高级混淆代码被严重混淆控制流扁平化。需要耐心和强大的静态分析工具辅助。动态密钥_P这类常量可能不是固定的而是每次页面加载时从服务器动态获取。这就需要先请求一个初始化接口获取本次会话的密钥。这套JS逆向的思路其价值远不止于爬虫。在Web安全测试中常用于分析登录接口的密码加密方式进行安全的密码爆破测试在授权范围内。在前端开发中理解加密流程有助于设计更安全的API交互方案。整个过程锻炼的是对网络协议、浏览器运行机制和代码调试的深入理解能力这才是最重要的收获。当你再遇到加密参数时不会再感到无从下手而是会系统地抓包、搜索、下断点、跟栈、抠代码最终解决问题。