AI 赋能 JS 逆向MCP+Skill+autoDecoder 全自动化落地加密自动破解
0x01 前言
随着各大开发的安全意识逐渐提高,前端防护手段越来越复杂,参数加密场景的越来越广泛,并且其生成逻辑往往经过多层混淆与封装,对我这种不具备深厚的代码功底和逻辑还原能力的小菜鸡来说倍感压力,人工逆向的方法不仅耗时费力,还高度依赖个人经验,往往在对抗JS逆向时就已经筋疲力尽,所有想试试结合chrome-devtools-mcp的能力并加上Skill的规范,实现JSRPC+Flask+autoDecoder方案的前端JS逆向自动化分析,提升JS逆向的效率。
本文内容仅供技术学习与交流使用,严禁用于任何非法用途。请遵守《中华人民共和国网络安全法》等相关法律法规,因违规使用产生的一切后果,由使用者自行承担,与作者无关。
现在只对常读和星标的公众号才展示大图推送,建议大家把渗透安全HackTwo“设为星标”,否则可能就看不到了啦!
参考文章:
https://www.hacktwohub.com/末尾可领取挖洞资料/加圈子 #渗透安全HackTwo
0x02 漏洞详情
传统JS逆向方法回顾
传统JS逆向工程在实践中已经形成了一系列由简至繁、由手动到半自动的技术路径,我大概归纳成了这四类:
1、直接修改变量法:进入到调试中,调试到参数加密前,并直接在作用域中修改参数,效果类似如下。

2、中间劫持法(JS-forward):通过JS-forward实现在明文点处插入一段JS代码,使其通过AJAX请求将现有的请求内容发送给burpsuite,burpsuite拦截并修改内容后,返回到原始变量中,效果类似如下。

3、远程调用法(JS-RPC):注入到浏览器页面中的客户端中,通过websocket与本地的python服务端相连,直接调用在浏览器上下文中预先注册好的JavaScript函数,效果类似如下。

4、硬核对抗法(JS原生):通过反混淆分析代码逻辑研究参数作用添加补环境最终通过本地运行JS来加解密,效果类似如下。

在接下来的方案中,我选择了选择远程调用法(JS-RPC)作为MCP赋能JS逆向的底座,因为它能将浏览器环境直接转化为加密服务接口,避免了纯手动逆向的低效,又绕开了纯JS还原的高复杂度,并且可以适配复杂场景下的JS逆向对抗。
MCP赋能的集成方案
工具链介绍
- JSRPC作为注入到浏览器页面中的客户端,建立通过WebSocket协议的远程过程调用机制,直接调用在浏览器上下文中预先注册好的JavaScript函数。
- Flask接收来自自动化工具如Burp Suite插件的请求,提取出需要加密的参数,然后通过JSRPC调用浏览器中的函数进行加密,最后重构请求并转发至目标服务器,实现自动化流程的串联。
- autoDecoder作为Burp Suite的插件,用于自动化处理应用中的加密/编码接口,自动将明文参数发送至该服务器,并完成加密请求的构建,实现“无感”的JS逆向处理。
- chrome-devtools-mcp:通过Chrome DevTools Protocol与浏览器实例建立连接,将浏览器的底层控制能力如执行JS、调试、网络监控、DOM操作等封装成一系列标准化的工具,并通过MCP协议暴露给AI客户端。
流程设计
针对远程调用法(JS-RPC)初始配置阶段中定位加密函数、编写注册代码、编写python代码等繁琐操作,通过引入AI技术进行赋能,让AI自动完成函数发现与注册代码生成,最终实现从“半自动”到“高自动”的跨越,人员全程只需下方指令,并最终配置一下burp即可完成JS逆向的全流程。

对于手工注入原理不懂的可以看这位师傅的文章有个流程图我感觉还是比较直观的解释的:
MCP配置
1、这边使用的是codex来调用MCP服务,首先将MCP服务器写进 Codex 的配置
codex mcp add chrome-devtools npx -y chrome-devtools-mcp@latest
2、修改 Codex 的配置文件MAC的在~/.codex/config.toml,添加如下字段
[mcp_servers.chrome-devtools]
3、检测是否生效

4、启动mcp服务,当看到打开浏览器后MCP服务就配置好了。
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
Skill配置
Skill是能否一次性完成js分析和代码生成的关键,创建3个mrakdown文件用于规范AI的操作以及输出:
1、主技能模板 (js-reverse-automation)
负责:
- 负责整体流程控制和协调
- 规范AI通过MCP协议连接和控制浏览器环境
- 让AI实现分析和理解代码生成的核心逻辑
- name: js-reverse-automation description: 通过 MCP 连接浏览器,为目标自动化搭建JS环境,定位签名/加密函数入口,生成 JSRPC 代码,创建 Flask 代理,并输出 Burp autoDecoder 配置。用于分析 JS 加密/签名逻辑、JSRPC代码生成、或搭建完整工具链的需求。 - # JS 逆向自动化技能 ## 补充模板引用 - 使用时先阅读本主技能,再按需加载补充模板:`JSRPC.md`(JSRPC 注入模板)、`flask.md`(Flask 代理模板)、`burp-autodecoder.md`(Burp autoDecoder 配置说明模板)。 ## 使用本技能完成 - 通过 MCP 连接真实浏览器 - 定位 JS 中的签名/加密入口 - 基于模板生成 JSRPC 注入代码 - 构建 Flask 代理与 Burp autoDecoder 配置 - 验证端到端流程可用 ## 需要收集的输入 - 目标 URL 或功能需求(如“生成 sign/enc”) - 环境限制(浏览器版本、代理、插件) <label>目标网址:</label> <input type="text" placeholder="https://example.com" /> <label>需要分析的加密参数:</label> <input type="text" placeholder="例如 sign / enc / token" /> <label>Fetch 示例(可选):</label> <textarea rows="6" placeholder="粘贴 fetch 请求示例,便于识别参数与签名位置"></textarea> ## 需要交付的输出 1. JSRPC 注入代码(模板 + 站点适配) 2. Flask 代理服务(API 封装 + 错误处理) 3. Burp autoDecoder 配置说明 4. **完整的加密/签名逻辑代码**(可直接复制用于人工调试) 5. 验证记录(示例调用 + 期望输出) ## 工作流程 ### 阶段一:初始化与连接 1. 通过 MCP 连接浏览器 2. 加载目标页面并打开 DevTools ### 阶段二:分析与入口定位 1. 触发签名流程并抓取调用栈 2. 检查打包代码并定位 sign/encrypt等函数 3. 确认入口类型:全局 / 对象方法 / 动态 resolver ### 阶段三:生成与注入 JSRPC 1. 基于模板生成 JSRPC 注入代码 - 配置入口路径或 resolver - 需要时绑定 `this` - 处理同步 / Promise 异步 - 规范化输入/输出 - 添加错误兜底 2. 注入并注册 JSRPC action 3. 验证 JSRPC 调用是否返回预期结果 4. 输出完整的加密/签名逻辑代码片段(含所需依赖与调用示例),便于人工调试 ### 阶段四:构建服务端代理 1. 生成 Flask 代理 - JSRPC 客户端连接 - API 路由封装 - 结构化错误处理 2. 启动服务并做健康检查 ### 阶段五:集成与交付 1. 输出 Burp autoDecoder 配置说明 2. 端到端验证并记录结果 3. 提供维护建议与更新要点 ## 交付前检查 - 入口定位稳定且可复现 - `this` 绑定正确 - Promise/异步处理正确 - 输入/输出规范化正确 - 错误有兜底且不影响调用 - Burp/Flask 配置端到端可用 - 加密/签名逻辑代码完整可独立运行2、JSRPC注入模板 (jsrpc-injection-template)
负责:
- 提供通用的函数拦截和远程调用能力
- 提供模版让生成时支持多种加密场景的适配配置
- 确保代码注入的稳定性和安全性
- name: jsrpc-injection-template description: 作为主技能 `js-reverse-automation` 的补充资源,提供通用 JSRPC 注入模板,用于定位签名/加密函数入口并对外注册 action。用于生成适配不同 JS 加密场景的 JSRPC 代码。 - # JSRPC 注入工具模板(补充资源) > 本文件是主技能 `js-reverse-automation` 的补充模板,配合 `skill.md` 使用。 ## 目标 - 统一生成可迁移的 JSRPC 注入代码 - 支持全局函数、对象方法、动态定位 - 兼容同步/异步 Promise 返回 - 支持输入输出规范化与错误兜底 ## 输入 - `actionName`:JSRPC 注册的 action 名称 - `entry`:目标函数入口定位方式 - `bindThis`:需要绑定的上下文对象(可选) - `async`:是否返回 Promise - `normalizeInput/normalizeOutput`:输入/输出标准化函数 ## 输出 - 可直接注入的 JSRPC 代码段 ## 代码模板 ```js // 1) 建立 JSRPC 连接 var client = new Hlclient("ws://127.0.0.1:12080/ws?group=fausto&name=burp"); // 2) 配置区 var JSRPC_CONFIG = { actionName: "generate_sign", entry: { type: "global", // global | object | resolver path: "signFunction", resolver: null }, bindThis: null, async: false, normalizeInput: function(param) { return param; }, normalizeOutput: function(result) { return result; }, onError: function(err) { return "ERROR_" + Date.now(); } }; // 3) 工具函数 function getByPath(root, path) { if (!root || !path) return null; var parts = path.split("."); var cur = root; for (var i = 0; i < parts.length; i++) { cur = cur[parts[i]]; if (!cur) return null; } return cur; } function resolveEntry(config) { if (config.entry.type === "global") return window[config.entry.path]; if (config.entry.type === "object") return getByPath(window, config.entry.path); if (config.entry.type === "resolver" && typeof config.entry.resolver === "function") { return config.entry.resolver(); } return null; } // 4) 注入入口 client.regAction(JSRPC_CONFIG.actionName, function(resolve, param) { try { var fn = resolveEntry(JSRPC_CONFIG); if (typeof fn !== "function") throw new Error("签名/加密函数未找到"); var input = JSRPC_CONFIG.normalizeInput(param); var ctx = JSRPC_CONFIG.bindThis || null; var result = fn.call(ctx, input); if (JSRPC_CONFIG.async && result && typeof result.then === "function") { result.then(function(res) { resolve(JSRPC_CONFIG.normalizeOutput(res)); }).catch(function(err) { resolve(JSRPC_CONFIG.onError(err)); }); return; } resolve(JSRPC_CONFIG.normalizeOutput(result)); } catch (error) { resolve(JSRPC_CONFIG.onError(error)); } }); ``` ## 适配示例 ### A. 全局函数 ```js JSRPC_CONFIG.entry = { type: "global", path: "signFunction" }; ``` ### B. 对象方法 ```js JSRPC_CONFIG.entry = { type: "object", path: "crypto.sign" }; JSRPC_CONFIG.bindThis = window.crypto; ``` ### C. 动态定位 ```js JSRPC_CONFIG.entry = { type: "resolver", resolver: function() { return window.__SIGN_FN__ || (window.crypto && window.crypto.sign); } }; ``` ### D. Promise 异步 ```js JSRPC_CONFIG.async = true; ``` ## 注意事项 - 保证入口函数定位稳定 - 处理 `this` 绑定与异步返回 - 输入输出需要可复现 - 失败要有兜底,避免 JSRPC 调用中断3、Flask代理模板 (flask-jsrpc-proxy-template)
负责:
- 确保正常构建中间件服务,连接JSRPC和Burp Suite
- 确保实现请求转发和签名回填的自动化
- 确保提供完整的错误处理和日志记录
- name: flask-jsrpc-proxy-template description: 面向 Burp autoDecoder 的 Flask 代理服务模板,用于将请求体交给 JSRPC 生成加密后的结果并回填。适用于需要自动签名/加密参数回填的场景。 - # Flask JSRPC 代理工具模板 ## 目标 - 接收 Burp autoDecoder 的编码请求 - 解析请求体 - 调用 JSRPC 生成加密/签名 - 回填签名并返回给 Burp ## 输入 - `dataBody`:autoDecoder 传入的请求体(默认表单字段) - `dataHeaders`:autoDecoder 传入的请求头(默认表单字段) ## 输出 - 更新后的请求体(可包含更新后的请求头) ## 可配置项 - JSRPC 地址(如 `http://127.0.0.1:12080/go`) - JSRPC 参数:`group`、`action` - 签名字段名(默认 `sign`) - 超时时间、日志级别 ## 代码模板 ```python from flask import Flask, request import requests import json import logging logging.basicConfig(level=logging.DEBUG) app = Flask(__name__) JSRPC_URL = "http://127.0.0.1:12080/go" JSRPC_GROUP = "fausto" JSRPC_ACTION = "generate_sign" SIGN_FIELD = "sign" TIMEOUT = 5 @app.route('/encode', methods=['POST']) def handle_encode(): app.logger.info("- 收到来自 Burp/autoDecoder 的编码请求 -") data_body = request.form.get('dataBody', '') data_headers = request.form.get('dataHeaders', '') if not data_body: app.logger.error("未接收到 dataBody") return data_headers + "\r\n\r\n\r\n\r\n" + data_body if data_headers else data_body try: json_data = json.loads(data_body) except json.JSONDecodeError as e: app.logger.error(f"解析 JSON 失败: {e}") return data_headers + "\r\n\r\n\r\n\r\n" + data_body if data_headers else data_body old_sign = json_data.pop(SIGN_FIELD, None) app.logger.info(f"已移除旧签名: {old_sign}") params_for_jsrpc = { "group": JSRPC_GROUP, "action": JSRPC_ACTION, "param": json.dumps(json_data, ensure_ascii=False, separators=(',', ':')) } try: jsrpc_response = requests.get(JSRPC_URL, params=params_for_jsrpc, timeout=TIMEOUT) jsrpc_response.raise_for_status() result = jsrpc_response.json() new_sign = result.get('data', '') except requests.exceptions.RequestException as e: app.logger.error(f"调用 JSRPC 失败: {e}") new_sign = "" except json.JSONDecodeError as e: app.logger.error(f"解析 JSRPC 返回失败: {e}") new_sign = "" json_data[SIGN_FIELD] = new_sign new_data_body = json.dumps(json_data, ensure_ascii=False, separators=(',', ':')) if data_headers: new_content_length = len(new_data_body) new_headers = data_headers.replace( f"Content-Length: {len(data_body)}", f"Content-Length: {new_content_length}" ) return new_headers + "\r\n\r\n\r\n\r\n" + new_data_body return new_data_body if __name__ == '__main__': app.run(host='0.0.0.0', port=8888, debug=True) ``` ## 使用说明 - Burp autoDecoder 将请求体与头部作为表单字段传入 `/encode` - 服务返回的内容将被 autoDecoder 用于替换原始请求 ## 注意事项 - JSRPC 端口/参数需与注入端一致 - 若加密依赖更多字段,需在加密前补齐实战测试
以某大学人事管理系统为例,其password字段被加密,我将全程使用ai进行分析,并全程使用ai生成的代码进行测试,来验证流程的有效性。

1、首先输入触发Skill的提示词,AI会提示我们需要输入的内容。
按 skill.md 的流程执行
2、在网页中提取我们所需的参数。

3、然后输入提示我们需要输入的所需参数至codex。
目标址(URL):https://xxx/login/index 
4、等待代码执行完成发现所需的代码均以创建完成。
Using skill js-reverse-automation (and its JSRPC/Flask/Burp templates) because the task matches the skill description.5、启动JSRPC

6、在浏览器开发者工具的Console中,执行JSRpc项目中的 JsEnv_Dev.js文件内容。

7、在控制台注入AI生成的jsrpc_password_md5.js。
// JSRPC injection for hr.ncu.edu.cn login password MD5 // Requires Hlclient to be available in the page context. var client = new Hlclient("ws://127.0.0.1:12080/ws?group=fausto&name=burp"); var JSRPC_CONFIG = { actionName: "generate_password_md5", entry: { type: "resolver", resolver: function () { if (window.$ && typeof $.md5 === "function") return $.md5; if (window.jQuery && typeof jQuery.md5 === "function") return jQuery.md5; return null; }, }, bindThis: null, async: false, normalizeInput: function (param) { if (param && typeof param === "object") { if (param.password != null) return String(param.password); if (param.pwd != null) return String(param.pwd); } return String(param == null ? "" : param); }, normalizeOutput: function (result) { return result; }, onError: function (err) { return "ERROR_" + Date.now(); }, }; function resolveEntry(config) { if (config.entry.type === "resolver" && typeof config.entry.resolver === "function") { return config.entry.resolver(); } return null; } client.regAction(JSRPC_CONFIG.actionName, function (resolve, param) { try { var fn = resolveEntry(JSRPC_CONFIG); if (typeof fn !== "function") throw new Error("md5 function not found"); var input = JSRPC_CONFIG.normalizeInput(param); var ctx = JSRPC_CONFIG.bindThis || null; var result = fn.call(ctx, input); if (JSRPC_CONFIG.async && result && typeof result.then === "function") { result.then(function (res) { resolve(JSRPC_CONFIG.normalizeOutput(res)); }).catch(function (err) { resolve(JSRPC_CONFIG.onError(err)); }); return; } resolve(JSRPC_CONFIG.normalizeOutput(result)); } catch (error) { resolve(JSRPC_CONFIG.onError(error)); } }); // Quick local check in console (optional): // console.log($.md5("111111"));
8、测试jsrpc调用函数是否正常,可以看到是没问题的。
http://127.0.0.1:12080/go?group=fausto&action=generate_password_md5¶m=111111
9、运行flask_proxy_password.py
from flask import Flask, request import requests import json import logging from urllib.parse import parse_qs, urlencode import re logging.basicConfig(level=logging.DEBUG) app = Flask(__name__) JSRPC_URL = "http://127.0.0.1:12080/go" JSRPC_GROUP = "fausto" JSRPC_ACTION = "generate_password_md5" PASSWORD_FIELD = "password" TIMEOUT = 5 CONTENT_LENGTH_RE = re.compile(r"Content-Length:\s*\d+", re.IGNORECASE) def call_jsrpc(raw_password: str) -> str: params_for_jsrpc = { "group": JSRPC_GROUP, "action": JSRPC_ACTION, "param": raw_password, } try: jsrpc_response = requests.get(JSRPC_URL, params=params_for_jsrpc, timeout=TIMEOUT) jsrpc_response.raise_for_status() result = jsrpc_response.json() return result.get("data", "") except requests.exceptions.RequestException as e: app.logger.error(f"JSRPC request failed: {e}") return "" except json.JSONDecodeError as e: app.logger.error(f"JSRPC response parse failed: {e}") return "" @app.route('/encode', methods=['POST']) def handle_encode(): app.logger.info("- incoming autoDecoder request -") data_body = request.form.get('dataBody', '') data_headers = request.form.get('dataHeaders', '') if not data_body: app.logger.error("missing dataBody") return data_headers + "\r\n\r\n\r\n\r\n" + data_body if data_headers else data_body # Try JSON first new_body = None try: json_data = json.loads(data_body) raw_pwd = str(json_data.get(PASSWORD_FIELD, "")) if raw_pwd: json_data[PASSWORD_FIELD] = call_jsrpc(raw_pwd) new_body = json.dumps(json_data, ensure_ascii=False, separators=(',', ':')) except json.JSONDecodeError: # Fallback to x-www-form-urlencoded form = parse_qs(data_body, keep_blank_values=True) raw_pwd = form.get(PASSWORD_FIELD, [""])[0] if raw_pwd: form[PASSWORD_FIELD] = [call_jsrpc(raw_pwd)] new_body = urlencode(form, doseq=True) if data_headers: new_len = len(new_body) new_headers = CONTENT_LENGTH_RE.sub(f"Content-Length: {new_len}", data_headers) return new_headers + "\r\n\r\n\r\n\r\n" + new_body return new_body if __name__ == '__main__': app.run(host='0.0.0.0', port=8888, debug=True)
10、测试Flask是否可以正常加密,可以看到也是没问题的。
curl -X POST http://127.0.0.1:8888/encode \
11、最后参考Burp autoDecoder 配置说明:burp_autodecoder_config.md配置burp的autoDecoder插件,也成功加密了参数,整体完美运行下来了!

涉及所有工具和Skill可在文章末尾获取下载地址
0x03 总结
如果需要Skill代码的师傅们可以在文章末尾自取,本文也是成功的实现了全自动化的js分析和代码生成,但是对于一些复杂场景的js代码可能分析起来还是略微有点吃力,但是AI的好处就是可以反复调试,也期待师傅们和优化出更好的思路和代码一起交流学习。最后愿各位师傅在后续挖洞之路中,精准定位漏洞、高效挖掘,天天出高危、次次有收获,挖洞顺利、不踩坑、多拿奖励,共同提升支付业务安全测试能力!🔥喜欢这类文章或挖掘SRC技巧文章师傅可以点赞转发支持一下谢谢!
公众号回复20260323获取Skill代码