做 JS 逆向的朋友应该都有过这种经历断点打到一半一头扎进动态混淆拼出来的函数堆里往上翻调用栈全是_0x开头的名字往下看又不知道哪一层才是真正的签名计算位置。以前我处理这类问题基本就是手工跟栈F11 一步步入console 里反复打印中间值运气好两三个小时理清一条链路运气不好一个混淆函数能熬掉一整个晚上。最近我试着把Trae和MCP组合起来搭了一个能自动逆向动态混淆的JS 智能体实际跑了一段时间算是把“手工跟栈”这件事真正降了级——不再是我追着代码跑而是让智能体自己去判断该断哪里、该看什么变量、该继续往哪一层走。这套方案的核心思路其实不复杂把逆向过程中那些重复、机械的排查动作封装成 MCP 工具让 Trae 的 Agent 模式在分析循环里反复调用这些工具自动完成断点、变量读取、调用栈抓取和算法还原。这篇文章我会把整个搭建过程、工具设计、踩过的坑都拆开讲清楚适合对 JS 逆向有一定基础、同时正在尝试用 AI 工具提效的人参考。1. 为什么“手工跟栈”会成为逆向效率的瓶颈1.1 动态混淆到底在跟什么动态混淆在 JS 逆向里并不是指简单地把变量名改成_0x1234那种静态混淆而是代码在运行过程中才会还原出真正的逻辑。常见的动态混淆形态有这么几类字符串数组化把字符串密文存在数组里运行时通过解码函数还原真实值。控制流平坦化把顺序执行的逻辑打散塞进循环加 switch 分支的壳里。Proxy 代理劫持对象的读写、函数的调用全部经过代理层this和参数都被转移过。VM 解释器模式原始逻辑被翻译成指令序列由一个自建的解释器逐条执行。内存动态拼接通过eval、new Function在运行期现场生成代码。“跟栈”本质上做的是两件事第一搞清楚函数之间的调用关系第二搞清楚关键变量从哪来、经过哪些变换、最终流向哪里。动态混淆恰好同时把这两件事藏了起来——调用关系被抹平了变量名和生成位置也全部被隐藏。比如你看到一个_0x4c3e(arr, i)光看代码根本不知道它只是个数组下标读取还是某种复杂的解码函数必须实际运行到那一行看输入输出关系才能确定。这也是为什么动态混淆比普通压缩混淆更让人头疼。压缩混淆顶多是名字不可读跑起来后调用关系还是清晰的动态混淆是连同调用关系、变量生命周期、控制流结构一起打乱你用静态分析工具看 AST 可能看到一堆无从下手的节点唯一可靠的办法就是用动态调试去观测运行时状态。1.2 手工跟栈的三个致命痛点第一个痛点是“步步入就像掉进兔子洞”。你按一次 F11 步进可能直接掉进一个几百行的平坦化函数里里面全是switch分支和循环跳转根本没有函数边界可言转半天出来已经忘了自己最初要追什么。第二个痛点是变量名完全没有语义。所有函数名、变量名统一叫_0x开头你没办法像读正常代码一样推测_0x5f2c到底是个什么含义只能靠运行时打印它的值去猜。这个过程极其消耗精力连续看几十个这样的变量大脑基本就转不动了。第三个痛点是异步调用链和代理拦截让“栈”失去参考价值。很多动态混淆会在回调、定时器、Promise 微任务里跳来跳去你抓到的调用栈和实际逻辑链是割裂的如果还有 Proxy 代理层你看到的函数调用栈只是代理转发栈真正的目标函数被藏得更深了。打个比方手工跟栈就像在一栋没有门牌号的楼里排查漏水。你从一楼顺着水渍找到十楼以为找到了源头结果发现那只是上一层漏下来的积水点。真正的泄漏点可能藏在负一层你不到某个特定位置根本看不见。手工方案不是不能用但它的效率完全依赖个人经验而且极难复制——换个人、换个请求、换个混淆版本又得重新来一遍。2. 方案选型为什么是 Trae 和 MCP而不是其他组合2.1 MCP 到底解决了什么问题MCP 全称是 Model Context Protocol是一个开放协议它定义了“模型”和“外部工具”之间标准化通信的方式。在这个协议里有两个核心角色MCP Server 负责暴露工具MCP Client 负责把工具注册给 LLM 使用。模型在推理过程中可以生成“调用某个工具”的指令客户端去执行再把结果返回给模型如此循环。打个比方MCP 就像一个给 AI 用的标准 USB 接口集线器。以前每个工具都得单独给 AI 定制适配器现在大家都在同一个协议下工作同一个工具可以被不同的 AI 宿主直接调用。这在逆向场景里特别关键——逆向本身就是一个“观察、推断、再观察”的循环过程模型必须不断从外部获取信息才能往下推理而 MCP 恰好提供了模型与环境交互的统一方式。实测下来MCP 在工具调用场景里稳定性的确很重要。工具返回的 JSON 如果格式不规范模型读起来要多费很多轮而 MCP 标准的content返回结构配合清晰的输入 schema能显著减少模型误读的情况。这也是我后来坚持用 MCP 而不是随便写个 API 给模型调用的原因之一。2.2 Trae 在智能体编排上的优势选 Trae 当宿主并不是说其他工具不行而是 Trae 在“智能体编排”这件事上做得比较顺手。它有内置的 AI 模型通道不需要额外配 API Key 就能直接用更重要的是它原生支持 MCP 客户端在设置里添加 MCP Server 的配置就能让 Agent 调用外部工具。Trae 的 Agent 模式对多轮工具调用的支持也比较接近理想状态。它可以在一个任务里连续执行“调用工具、看结果、再调用工具”的循环而不是每次都要人工介入确认。这个能力对逆向分析来说太重要了——你不可能每分析一层就手动点一次按钮希望的是整个自动分析链路能跑起来。还有一个很实际的优势Trae 作为一个代码编辑器AST 解析、代码跳转、终端集成、文件对比这些能力都是现成的。逆向分析经常要一边看源代码一边看运行时输出在同一个编辑器里完成这些动作比在聊天工具里来回切换舒服得多。Trae 添加 MCP Server 的配置非常简单本质就是一个 JSON 配置指定命令和参数即可{ mcpServers: { js-obfuscation-tracer: { command: node, args: [path/to/server.js] } } }这个配置的意思是告诉 Trae请启动这个命令让 MCP Server 通过标准输入输出和客户端通信。Trae 起来后会读取这个配置文件把里面的工具注册给 AI然后你在对话里就能让 Agent 调用这些工具了。2.3 整体架构与数据流设计整套系统的组成部分如下JSSandbox负责运行目标 JS 页面我用的是 Playwright 启动的无头浏览器既能执行页面脚本又能通过调试协议拿到运行时变量。MCP Server封装沙箱能力向上暴露trace_call_stack、inspect_variable、eval_in_context等工具。Trae Agent作为推理和决策核心通过 MCP 协议与沙箱层通信决定下一步调用哪个工具、怎么解读结果。工作区存放目标 JS 文件、临时脚本、最终分析报告。数据流的顺序大致是把待分析的 JS 文件放到工作区Agent 读取源码初步定位可疑入口。Agent 调用eval_in_context在页面中执行表达式确认关键函数的类型和值。工具返回 JSON 快照Agent 根据快照判断当前所在混淆层。循环调用直到还原出核心逻辑最终把结果整理成报告输出到工作区。这套架构的好处是每一层都能独立替换。沙箱可以换MCP 工具可以加甚至 Agent 宿主也能从 Trae 换到别的兼容客户端核心的分析逻辑基本不用改。3. 核心环节落地从 MCP Server 到自动逆向链路3.1 先定义一个最小可用的 MCP Server实现 MCP Server 用的是官方 TypeScript SDK整体非常简单。先创建一个 Node 项目安装依赖然后写一个最小的 Server。下面这个例子只实现了一个eval_in_context工具用于在页面上下文中执行表达式并返回 JSON 结果。import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { z } from zod; import { chromium } from playwright; let page: any; async function getPage() { if (!page) { const browser await chromium.launch({ headless: true }); const context await browser.newContext(); page await context.newPage(); await page.goto(about:blank); } return page; } const server new McpServer({ name: js-obfuscation-tracer, version: 0.1.0, }); server.tool( eval_in_context, 在目标JS页面上下文中执行表达式并返回JSON结果, { expression: z.string() }, async ({ expression }) { const page await getPage(); const result await page.evaluate((expr) { try { return { ok: true, data: eval(expr) }; } catch (e: any) { return { ok: false, error: String(e) }; } }, expression); return { content: [{ type: text, text: JSON.stringify(result, null, 2) }], }; } ); const transport new StdioServerTransport(); await server.connect(transport);这段代码里有个关键设计所有的错误都被捕获然后统一包装成{ ok: false, error: ... }返回。这样模型收到工具结果时不会因为 JSON 解析失败而中断推理而是能读到错误信息后自己调整策略修改表达式再试一次。另外我特意用 Playwright 无头浏览器作为沙箱而不是直接用 Node 的vm模块。原因是真实逆向目标几乎都依赖window、document、navigator这些浏览器环境在纯净的 Node 环境里跑代码可能一开始就报错根本走不到目标函数。3.2 工具设计与 prompt 编排真正跑起来后你会发现自己需要的工具远远不止一个eval_in_context。我把工具拆成了几个小而专的粒度这样模型更容易编排也更容易针对单个工具排障trace_call_stack抓取当前执行点的调用栈返回函数名和调用关系。inspect_variable读取某个变量的当前值支持指定对象路径。eval_in_context执行任意 JS 表达式并返回结果。set_breakpoint在指定源码位置设置断点。snapshot_scope抓取当前作用域里所有可见变量的快照。工具拆细的原因很简单LLM 最适合做小工具的调度者而不是一个大而全工具的调用者。工具越复杂模型越难判断该传什么参数、怎么解读返回值。相反每个工具职责越单一模型就越容易做出正确的选择。prompt 编排同样重要。我给 Agent 设置的系统提示词里明确了分析时的推荐行动顺序限定它在“先确认、再推断、后验证”的框架内行动你是一个JS逆向分析专家。你的任务是在页面中分析动态混淆代码还原算法逻辑。 每次行动前先说明你的假设再调用工具验证。 推荐的行动顺序 1. 用 eval_in_context 确认目标函数的类型和内容。 2. 用 trace_call_stack 获取调用关系。 3. 用 inspect_variable 检查关键变量。 4. 用 eval_in_context 验证假设。 5. 当找到核心算法时输出还原结果。 注意 - 如果工具返回 ok: false请阅读错误信息后修改表达式重试。 - 不要一次性执行大段代码优先用小表达式逐步验证。 - 假设未被验证前不要写入最终结论。3.3 实际执行效果与链路串联整个自动分析链路串起来之后效果还是很明显的。有一次我拿一个加密 JS 做测试目标函数是getSign(input)但代码被动态混淆过入口函数套了三层字符串数组函数名全是_0x开头完全无法直接阅读。Agent 的处理过程大致是这样先读取源码定位到getSign入口然后调用eval_in_context(typeof getSign)确认函数存在接着调用eval_in_context(getSign.toString())查看函数体源码发现里面有一层_0x4c3e调用再逐步还原_0x4c3e的真实内容确认它只是数组下标读取最终定位到c ^ 0x66这个异或运算判断出核心算法是逐字符 XOR0x66并给出了可验证的还原代码。整个过程跑下来大概 5 分钟而以前纯手工跟栈保守估计要 1 到 2 个小时。更重要的是这套流程是确定性的——下次遇到类似混淆不需要再从零开始智能体会按同一套分析框架走一遍。4. 实战调试智能体逆向动态混淆的完整过程4.1 一个足够典型的案例脚本为了演示方便我自己写了一个包含动态混淆特征的示例脚本让你直观理解 Agent 的决策路径const _0x5f2c [\x63\x68\x61\x72\x43\x6f\x64\x65\x41\x74, \x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65, \x6c\x65\x6e\x67\x74\x68]; function _0x4c3e(arr, i) { return arr[i]; } function getSign(s) { let out ; for (let i 0; i s[_0x4c3e(_0x5f2c, 2)]; i) { let c s[_0x4c3e(_0x5f2c, 0)](i); out _0x4c3e(_0x5f2c, 1)(c ^ 0x66); } return out; }这段代码不算复杂但特征很典型字符串被十六进制转义后存进了数组读取数组的操作被包装成一个_0x4c3e函数核心逻辑被XOR 0x66掩盖。对于纯静态分析来说理解_0x4c3e需要花一些时间但对于一个能动态执行、逐步验证的智能体来说整个过程可以非常顺畅。4.2 智能体的决策路径拆解我用表格把一次完整的分析过程记录下来方便你理解 Agent 每一步在干什么步骤工具调用模型推断返回结果1eval_in_context(typeof _0x5f2c)确认 _0x5f2c 是数组object2eval_in_context(_0x5f2c)还原数组内容[charCodeAt,fromCharCode,length]3eval_in_context(_0x4c3e.toString())确认函数是简单的取值器function 源码4eval_in_context(_0x4c3e(_0x5f2c, 0))验证函数作用charCodeAt5eval_in_context(_0x4c3e(_0x5f2c, 1))验证第二个元素fromCharCode6eval_in_context(getSign(ab))获取实际输入输出样本两个字符的 XOR 结果7推理并输出核心算法为逐字符 XOR 0x66还原代码这个表格的每一行在传统手工逆向里都对应一次 console 输入或一次断点检查。而现在这些都成了模型自动决策的一部分我不需要手动点击任何东西。有一个细节值得注意Agent 在步骤 4 之后并没有直接跳到最终结论而是先验证了第二个数组元素再通过输入输出样本确认 XOR 的具体值。这种“逐步验证、不急于下结论”的行为是我在 prompt 里专门强调的——动态混淆场景里模型太容易根据局部特征做出错误推断。4.3 中途失败如何自愈与修正智能体分析过程不是一帆风顺的实际上遇到错误的情况比顺利的情况还多。下面几个是真实发生过的失败场景场景一Agent 调用inspect_variable读取一个闭包内变量结果页面报错说变量未定义。原因很简单——那个变量存在于某个函数作用域内当前顶层作用域根本访问不到。这种时候 Agent 不能放弃而应该修改策略改用eval_in_context在目标函数运行断点上读取或者先给那个变量注入到全局再读取。场景二Agent 拿到一个undefined的返回值误判为“变量不存在”。后来我给eval_in_context的返回统一加了类型标记比如{ ok: true, data: null, type: undefined }模型就能区分“变量确实不存在”和“变量当前值是 undefined”两种情况误判率明显下降。场景三异步链路导致调用栈抓取失败。目标代码在 setTimeout 回调里执行Agent 用trace_call_stack拿到的是定时器的调用栈而不是真正业务逻辑的调用链。解决办法是加了一个wait_for_condition工具让 Agent 先等待某个条件成立再抓栈相当于手工逆向里的“等回调触发后再断点”。这些自愈能力不是模型天生自带的而是靠工具设计和 prompt 约束“逼”出来的。工具返回信息越丰富模型就越容易找出自己的错误并修正prompt 约束越清晰模型就越不容易掉进“看到局部信息就写结论”的陷阱。5. 常见问题与避坑清单5.1 作用域丢失与日志截断问题一eval_in_context执行时经常找不到变量因为变量被包在闭包或者模块作用域里顶层访问不到。解决思路是在目标函数执行时先用一个工具把关键变量挂到全局比如window.__debug value再通过eval_in_context读取这个中间变量。问题二大对象返回时 MCP 响应过长模型上下文窗口被过早占满。解决方法是给工具加一个输出截断逻辑默认只返回前 2000 个字符如果模型需要看更多再通过一个带path参数的工具按路径取子字段。server.tool( inspect_variable, 读取指定变量的值可指定对象路径结果默认截断, { name: z.string(), path: z.string().optional() }, async ({ name, path }) { const page await getPage(); const expr path ? ${name}.${path} : name; const value await page.evaluate((e) { try { return JSON.stringify(eval(e), null, 2); } catch (err) { return String(err); } }, expr); return { content: [{ type: text, text: value.slice(0, 2000) }] }; } );5.2 参数解析失败与上下文污染MCP Server 接收到的参数来自 LLM 生成偶尔会有格式问题。比如 agent 传进来一个字符串[a,b]但 zod 校验期望的是数组类型就会报错。我的做法是工具入口统一包一层宽松解析先尝试 JSON.parse解析失败再按普通字符串处理。这样能显著减少因为参数格式问题导致的中断。上下文污染也是一个容易忽略的问题。多次eval之后页面全局变量会被塞满window.__debug1、window.__debug2之类的临时变量如果不清理后续的变量查找会变得混乱。我的方案是在每次分析会话结束时统一执行一次清理脚本删除所有window.__debug*属性。5.3 成本与超时控制Agent 多轮调用模型 API 的成本会随着工具调用次数增加而累积。实测下来一次完整分析大约消耗 8 到 15 轮工具调用如果目标函数特别复杂可能更多。控制成本的方法有两个给高频查询加缓存同一个表达式在会话周期内只执行一次后续直接读缓存。在 prompt 里限制最大轮数比如“如果 15 轮后仍未找到核心逻辑输出当前进展并停止”。超时问题也需要提前设置。页面里的 JS 有可能进入死循环工具调用如果没有超时保护整个会话会卡死。我给每个工具执行包了一层 Promise.race统一设 30 秒超时超时后返回一个结构化的超时错误给模型让它换一种方式继续分析。常见问题可以整理成一个速查表问题现象可能原因解决方案MCP Server 启动失败SDK 版本不一致、stdio 通信异常升级 modelcontextprotocol/sdk 到最新版工具执行超时目标代码出现死循环或耗时长用 Promise.race 设置 30 秒超时变量读取不到变量在闭包作用域先注入到全局再读取返回内容过载对象过大撑满上下文截断输出按路径分批读取误判混淆算法多个混淆层叠加干扰先做静态预分析再用动态验证最后再分享一个小技巧实际用了两周之后我最大的感受是智能体不会替代逆向工程师它替代的是那些重复、机械的排查动作。真正决定分析上限的还是你对目标代码的判断力和对混淆形态的敏感度。如果你刚开始尝试这套方案我建议先别急着做一堆工具从一个小目标开始——比如只搭通eval_in_context和trace_call_stack拿一个带加密逻辑的页面反复测。等这条最小链路稳定了再往里面加断点管理、覆盖率、AST 预分析这些能力整个系统会慢慢变得像一个真正能帮你干活的“逆向助手”。还有个很实用的点每次分析完把 Agent 的执行记录和工具返回结果存成一份日志文件。这不仅方便复盘还能在排查“为什么这次分析结论不对”的时候回放当时的每一步决策找到是哪一步的假设错了。这套方法论比单个工具本身更值钱。
企业数字化 ERP 产品动态
相关推荐
构建高可用MCP Server服务中枢:从元工具设计到Grix实战落地 在Grix里接入一个MCP Server不难,难的是接入之后它能不能扛住AI的不按套路出牌。我最早遇到的问题是,工具在本地测试一切正常,一交给大模型调用就各种出幺蛾子:参数多传、超时、文件资源加载失败,甚至整个Server进程直… · 2026/9/24 23:22:07
Cua:让大模型看懂屏幕并操作电脑的跨平台桌面自动化框架 我到现在还记得第一次跑通 Cua 时那种感觉:对着终端敲下一句“帮我把桌面上所有图片按月份归档”,然后屏幕上的鼠标自己动了起来——打开文件夹、框选图片、右键菜单、新建目录、拖拽移动,全程没有一行写死的操作脚本。这个 2 万 Star 的开源… · 2026/9/24 23:22:07
HT06近场探头实战指南:DC-20GHz电磁诊断与SDR闭环分析 1. 这支探头不是“万能钥匙”,但它是EMC整改现场最值得信赖的“听诊器”你有没有遇到过这样的场景:产品在EMC实验室里反复失败,辐射骚扰曲线在300MHz和1.8GHz两个频点上顽固地凸起——实验室工程师说“可能是电源模块开关噪声”,结… · 2026/9/24 23:21:54
反相器链级数与尺寸优化:从理论公式到HSPICE实证 简介:本资源是一份面向VLSI与CMOS数字集成电路初学者及课程实验者的实践型设计报告,聚焦反相器链缓冲器优化与D触发器时序性能提升两大核心问题。内容涵盖反相器级数(N4/6/8/10)与尺寸比例的理论推导、HSPICE网表编写、瞬态仿真波… · 2026/9/24 23:55:50
4200张果蔬图图像分类实战:PyTorch迁移学习与避坑指南 简介:面向图像分类任务,这份数据集提供已标注的常见果蔬图像,覆盖香蕉、苹果、梨、葡萄、橙子、黄瓜、胡萝卜、辣椒、洋葱、土豆等36个类别,共约4200张图片。json文件保存了36个类别的名称对应关系,图片已预处理&#… · 2026/9/24 23:55:50
从Harness工程到认知工程:Agent架构升级的完整实战指南 1. 先聊清楚:harness 工程是 Agent 开发的"地基"还是"天花板"如果你最近在折腾 Agent 开发,大概率会遇到这样一个词:harness。刚开始接触这个概念的时候,我一度以为它指的是某个自动化测试框架,直… · 2026/9/24 23:55:50
基于SSM的停车场停车缴费管理系统开发实战解析 写论文、搞课程设计、应付毕设答辩的时候,很多同学一听到“Java项目源码”第一反应就是去下载一个成品然后改个名字交上去。但说句实话,作为一个这些年看过无数份毕业设计代码的老开发,停车缴费管理系统这个题目属于“看着简单、做起来全是细… · 2026/9/24 23:55:37
从标定到视差:Python+OpenCV双目视觉测距全流程详解 简介:一套基于PythonOpenCV实现的双目立体视觉实战资源,聚焦维视MV-VS220平台,完整覆盖相机标定、图像预处理、SIFT/SURF特征提取与匹配、视差计算与深度测距流程,适合高校学生、课程设计者及OpenCV开发者参考。包体共213个文件&a… · 2026/9/24 23:55:37
AI Agent无人值守实战:定时任务的可靠性设计与效果验证 做无人值守 Agent 有个很有意思的分水岭:开发环境里跑通一次,和让它每天凌晨自动跑完还能自己处理异常,完全是两码事。我最近把一个定时自动化任务从“人盯着跑”改造成“无人值守”,中间踩的坑比我预想的多一整个量级。这篇文章不… · 2026/9/24 23:55:37
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44