news 2026/9/26 5:08:08

MCP+Skill赋能JS逆向:从抓包到算法还原的自动化实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MCP+Skill赋能JS逆向:从抓包到算法还原的自动化实战

干前端和爬虫这行的朋友应该都有同感:纯手工JS逆向真的是个体力活。打开DevTools,盯着Network面板找加密参数,在Sources里逐个打断点,追调用栈追到头晕,遇到混淆代码还得靠经验去猜。更烦的是,这个过程极度依赖个人经验,新人接不了手,老人也不想做重复劳动。但最近我实测下来,MCP和Skill这套组合拳,确实能把前端JS逆向从“纯人工”往“半自动甚至全自动”推一大步。

这篇文章只讲一件事:怎么用MCP把调试工具变成AI能直接调用的大脑延伸,再用Skill把逆向方法论沉淀成可复用的工作流。里面会涉及Playwright MCP、BurpSuite MCP、自建Debugger MCP Server的具体配置,以及一个从抓包定位到算法还原的完整实操链路。适合被逆向需求反复折磨的前端工程师,想提升自动化测试深度和接口自动化覆盖率的测试,以及所有对AI Agent落地感兴趣的开发者。

1. 为什么JS逆向偏偏需要MCP和Skill:从“人肉逆向”到“AI编排”

1.1 传统JS逆向的三大痛点,你中了几条

先说第一性原理。JS逆向的本质是什么?是在不可信的JavaScript代码运行环境中,找出可信的业务逻辑。这句话听起来绕,但做起来就是这几步:找到加密参数的产生位置、定位对应的加密函数、还原算法的输入输出、最后在本地或服务端重新实现。

传统流程的问题在于,每一步都是高度动态的。页面改版了,参数位置就变了;加密函数被混淆了,调用栈就断了;接口加了时间戳和Nonce,本地复现就失效了。我见过太多人花一个下午追一个加密参数的生成逻辑,追到最终发现就是个简单的Base64编码,那种挫败感真的很伤人。

还有一个被忽视的痛点:逆向过程的知识密度太高,但信息流通性太差。每个前端工程师脑子里都有一堆零散的技巧——比如看到window.atob断在哪个位置、_0x开头的变量名大概率是混淆结果、getUuid这种函数名配上时间戳多半是签名算法。但这些经验很难显性化,团队协作时基本靠口口相传。

1.2 MCP是AI的手脚,Skill是AI的肌肉记忆

这里需要把MCP和Skill两个概念掰开揉碎讲清楚。

MCP(Model Context Protocol,模型上下文协议)是一个开放协议,解决的是“怎么让AI模型安全地调用外部工具”这个问题。你可以把MCP理解成AI世界的USB-C接口——以前每个AI应用都有一套自己的工具调用方式,现在大家统一一个接口规范,插上就能用。在JS逆向场景下,MCP的意义在于:AI不再只是一个“纸上谈兵”的聊天模型,它能直接控制浏览器、读取请求响应、执行调试命令、调用AST分析工具,拿到真实的运行时数据再决策。

Skill则是一套可复用的方法论模板。它不是代码,而是用一种结构化格式(通常是Markdown或YAML)描述“遇到某类问题时应该按什么步骤去排查”。举个例子,一个“JS加密参数定位”Skill可以写成:

name: js-encrypted-parameter-locator description: 定位前端页面中加密参数生成位置的标准流程 steps: 1. 使用浏览器MCP获取目标页面请求列表 2. 筛选包含加密参数(如sign、token、ts)的请求 3. 获取该请求的initiator调用栈 4. 将调用栈中JS文件URL与SourceMap关联 5. 对目标函数执行Hook注入并捕获入参

这套设计思路的直接收益是:Skill把“怎么做”(方法论)和“具体某个目标长什么样”(case细节)解耦了。AI收到一个新逆向任务时,先加载对应Skill拿到流程框架,再通过MCP实际操作浏览器和调试器,最后生成针对目标的定制化代码。这不就是老师傅带新徒弟的过程吗?老师傅脑子里的经验(Skill),配合新徒弟的手脚(MCP),效率比单打独斗高太多了。

1.3 MCP方案与传统自动化脚本的本质区别

肯定有人问:这不就是之前Playwright写个脚本自动抓数据吗?区别在哪?

我觉得最大的区别在于运行时决策能力。传统脚本的逻辑是固定的:打开页面、点击按钮、抓取数据、重复操作。一旦页面结构变了,脚本就废了。但在MCP+Skill的架构里,AI会先观察页面实际情况,再决定走哪条分支路径。比如目标页面是React渲染还是Vue渲染,接口加密是Web Crypto还是自研算法,AI会通过MCP工具去探测,然后匹配不同Skill分支。

说一个我实际测试的例子。之前的自动化方案里,定位加密参数需要人力先识别hmac或者MD5字样,再手写正则或AST查询。现在我用MCP协议让AI直接调用一个“代码语义搜索”工具,输入“这段JS代码中哪个函数接收了最多的请求参数并且做了不可逆变换”,AI就能结合代码上下文给出候选函数。这种语义级的模糊定位能力,是传统脚本做不到的。

2. 工具选型解析:一套可以直接抄作业的MCP工具栈

2.1 浏览器自动化侧:Playwright MCP

要跑JS逆向,第一步永远是“能看到页面在干嘛”。Playwright MCP是目前我实测下来最成熟的浏览器自动化MCP服务。它能让你用自然语言控制浏览器,比如“打开页面的登录入口,截图给我看”“在Console执行一段脚本并返回结果”“获取当前页面的所有请求URL”。

安装和配置很简单:

# 全局安装Playwright MCP npx @playwright/mcp@latest # 启动后,在MCP客户端(如Claude Desktop、Cherry Studio)中配置 # 标准JSON配置: { "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp@latest"] } } }

这个服务启动后默认会以headless模式开一个浏览器实例,但注意了,JS逆向场景下我强烈建议加一条配置:--headed,也就是有头模式。因为很多加密逻辑在headless模式下运行结果不一样(比如取navigator.userAgent做指纹参数时,headless模式的值跟正常浏览器有差异)。

npx @playwright/mcp@latest --headed --viewport-size=1280,720

2.2 请求审计侧:BurpSuite MCP与抓包数据接入

浏览器MCP只能看到页面侧的请求,但逆向加密参数时,我们更需要的是请求/响应级别的完整数据——包括Header、Cookie、请求体的原始JSON结构。这部分我用BurpSuite MCP来解决。

BurpSuite这个传统工具在渗透测试和接口分析领域地位不用多说,它的MCP扩展让AI可以直接查询代理抓到的流量。配置需要先装Burp插件,然后在MCP配置里指定WebSocket地址:

{ "mcpServers": { "burp": { "command": "python", "args": ["burp_mcp_bridge.py", "--ws", "ws://127.0.0.1:9876"] } } }

之后AI就可以用自然语言检索流量了,比如“找出最近的10个POST请求,其中包含名为sign的参数,把它们的请求体格式写成一个TypeScript接口定义”。

这里有个小技巧:逆向场景里流量数据其实是高度重复的,同一套页面操作点十几遍,抓到的可能就是同一批接口。所以在抓包阶段,反而是先用Playwright MCP去触发不同业务场景(登录、搜索、列表翻页)比一次性抓几十条更重要。

2.3 逆向工程专用:自建一个15分钟的Debugger MCP Server

Playwright和Burp组合拳能覆盖大部分场景,但真正的硬骨头——比如定位一个被混淆的加密函数体内到底干了什么——还需要一个能直接在运行时注入脚本并读取结果的专用MCP Server。这里我选择自己写一个,用TypeScript,核心就三件事:搜索JS文件、分析AST、注入Hook。

先上代码,这个server最核心的部分是暴露一个find_function_source工具,它接收一个关键词,通过CDP(Chrome DevTools Protocol)在目标页面里搜索所有JS文件中的匹配位置:

// mcp-debugger-server.ts 核心片段 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; const server = new McpServer({ name: "js-debugger-mcp", version: "1.0.0" }); server.tool( "find_function_source", "在页面已加载的JS中查找函数定义", { keyword: z.string().describe("函数名或字符串片段") }, async ({ keyword }) => { const files = await cdp.send("Debugger.searchInContent", { query: keyword, caseSensitive: false }); return { matches: files }; } );

实际运行时,我通过chrome-remote-interface库连接调试端口,注册Debugger.scriptParsed事件,把所有JS脚本先缓存到内存里。这样AI后续的“查找”“定位”都不需要重新加载页面,响应速度会快很多。

这个自建Server我觉得是整套方案里最值得动手的部分。因为直接决定你能在多大程度上把逆向过程自动化。像“搜索所有JS文件里包含md5或sha256字符串的位置”“查看某个函数,列出它的参数名”——这些都是高频操作,封装成工具后,AI执行起来非常快。

2.4 为什么我不用纯AST静态分析工具

也许你会问:现在不是有很多成熟的JS AST静态分析工具吗,比如babel-parser或者jsnice,挂到MCP里当工具用不就行了?

说实话我之前也这么干过,但最终发现静态分析的“假阳性”太高了。真实世界的JS逆向场景,加密函数往往不在静态可见的源码里——它可能在动态加载的new Function()里面,可能在Web Worker线程里,也可能在运行时通过字符串拼接再eval得到。这种情况静态解析根本看不到函数体,反而是运行时Hook更靠谱。

所以我的方案是把AST用于索引,把运行时用于确认:先通过AST工具把所有JS函数建立索引(函数名、参数个数、所在文件、行号),然后拿着候选函数列表去运行时注入Hook,看哪个函数的入参和输出能跟请求参数对得上。这个过程本来是逆向工程师的经典操作,现在通过MCP工具暴露给AI后,AI能更快地在几十个候选函数中收敛目标。

3. 核心实操流程:从拿到目标URL到产出可复现结果

3.1 第一阶段:请求特征采集与加密参数识别

拿到一个目标页面,别急着看代码。先让AI通过Playwright MCP打开目标,触发几个核心业务动作,同时用Burp MCP把流量数据导入一个临时集合。

我通常给AI的第一条指令是这样的:

打开目标站点首页,等待3秒后模拟点击登录按钮。 在Burp流量中筛选出POST请求。 列出所有请求参数中包含sign、token、ts、_signature、nonce中任意一个的请求。 对每个请求执行以下操作: 1. 记录请求的URL和请求体结构 2. 记录请求头发送顺序 3. 标记参数值是可预测(如时间戳)还是不可预测(如MD5摘要)

这条指令的意义不在于让AI机械地枚举参数,而在于让它建立“请求画像”:这个站点的加密参数集中在哪个接口、参数值长什么样、值的变化频率如何。这些信息直接决定后续逆向策略——如果参数是时间戳+固定盐的MD5,那走“搜索md5关键词”路线;如果参数是动态token且1分钟过期,那大概率要Hook某个getToken函数。

3.2 第二阶段:JS文件定位与代码语义检索

采集完请求特征,真正的逆向才开始。AI需要找到“生成加密参数的JS代码在哪”。

这里我用三步走。第一步,通过自建Debugger MCP的find_function_source工具搜索请求参数名的关键词。比如接口请求体重有"sign":,那就直接搜索sign字符串,大概率能在某个JS文件里找到sign:赋值语句。

第二步,如果第一步没有命中(通常是被混淆了),就搜索混淆代码的特征。Beautify之后的混淆代码通常有独特的模式:_0xabc123开头的十六进制变量名、['push']这种属性访问方式替代点号访问。我给MCP Server加了一个find_obfuscated_segments工具,专门用正则扫描这种模式,返回可疑代码片段。

第三步,也是我认为最关键的:让AI读取调用栈。通过Playwright MCP在Network面板找到那个请求的Initiator信息,直接查看是哪行JS代码发起XMLHttpRequest或fetch调用。然后往上追几层,找到真正的加密函数调用位置。

这三步走完,AI基本能锁定一个2-5个函数的候选集合。接下来就需要Hook验证。

3.3 第三阶段:运行时Hook注入与动态插桩

这一步是实战中最核心的环节。要验证函数是否参与加密参数生成,最直接的办法就是Hook它,看它的输入和输出。

我用Playwright MCP的addInitScript功能来注入Hook。这个API能在页面加载任何脚本之前就注入自定义脚本,用来拦截原生方法特别有效:

// hook-init.js (() => { const originalFetch = window.fetch; window.fetch = function(...args) { const url = args[0]; const options = args[1] || {}; if (typeof options.body === 'string') { // 捕获请求体 window.__capturedRequest = { url: String(url), body: options.body, timestamp: Date.now() }; } return originalFetch.apply(this, args); }; })();

注入之后,让AI通过Playwright MCP触发一次业务操作,再读取window.__capturedRequest,就能确认加密参数是在fetch调用之前已经生成,还是fetch内部自己算的。这个信息看似简单,实则极为关键——如果参数在fetch之前已生成,那说明加密逻辑在业务代码里;如果fetch内部动态生成,那Hook的位置就要换到XMLHttpRequest.send或WebSocket层面。

除了网络库Hook之外,针对疑似加密函数我还会注入一层代理Hook。比如怀疑函数是generateSignature(q),就注入:

const orig = window.generateSignature; window.generateSignature = function(q) { const start = performance.now(); const result = orig(q); console.log(`[Hook] generateSignature input=`, q, `output=`, result); window.__lastHookResult = { input: q, output: result }; return result; };

实践中你会发现,很多加密函数被塞进了闭包里,window上根本拿不到引用。这时就需要用CDP的Debugger.evaluateOnCallFrame或者Runtime.evaluate在函数执行的作用域内去替换引用。这部分我用Debugger MCP再加了一个inject_hook_at_breakpoint工具,大概逻辑是:先给目标函数所在行设置断点,然后通过CDP在断点命中时修改作用域内的函数引用。

3.4 第四阶段:算法还原、本地复现与回归校验

Hook验证通过后,AI已经拿到了加密函数的输入和输出。这时候就要把“黑盒”变成“白盒”:还原算法逻辑,用本地代码复现出相同结果。

我用的方法是把候选函数从浏览器里“拆”下来。通过Debugger MCP读取函数的source code,让AI分析这段代码使用了哪些全局变量、哪个标准库依赖(比如Web Crypto API的crypto.subtle、或jsencrypt库的实例),然后生成一个Node.js版的等价实现。

这里最容易踩坑的是环境差异。浏览器里的window.btoa和Node里的Buffer.toString('base64')可能填入不同字符集;crypto.getRandomValues()在两者实现上随机源不同;某些算法依赖Date.getTimezoneOffset()这种时区相关量,导致结果和场景相关。我的习惯是让AI生成完复制实现后,用一组固定输入做“向量比对”——一组输入在浏览器和Node跑出的结果必须完全一致,否则就要排查是哪部分逻辑有环境依赖。

回归校验也很重要,这套流程跑通一次不算完,要能反复跑。我直接用Playwright MCP在一个独立浏览器上下文里跑5次业务操作,每次校验本地复现结果与线上请求参数是否一致。连续5次一致才算过关。

3.5 Skill固化:把整条链路封装成可复用的技能模板

最后一步是“下沉”。人在这个过程中的沉淀,要落到Skill里。

我自己建了一个Skill库,结构上是这样的:

skills/ js-reverse/ SKILL.md templates/ hook-template.js request-capture.js local-repro.js workflows/ locate-encrypt-param.md verify-hook.md

SKILL.md是主入口,用YAML定义触发条件和执行流程:

--- name: js-reverse-automation description: 前端JS加密参数逆向的自动化执行流程 triggers: - 用户提供目标URL和加密参数名 - 请求中包含不可预测参数 workflow: step1: 请求采集 -> 通过Playwright MCP和Burp MCP获取完整流量 step2: 代码定位 -> 通过Debugger MCP搜索关键词并分析调用栈 step3: Hook验证 -> 注入代理钩子,收集输入输出对 step4: 算法复现 -> 生成Node.js等价代码并做回归比对 step5: 结果输出 -> 产出参数生成函数和相关文档 ---

这个Skill的价值在于,团队里的AI Agent在执行类似任务时不需要从零发挥,而是严格按workflow推进。Skill本质上是把以前“老师傅看着办”的经验变成了“新人照着做”的操作手册。我甚至会在Skill里附上一些常见的反模式,比如“如果页面使用了WebAssembly,直接切换策略,不要浪费时间在JS寻找”这类经验判断。

4. 常见问题与排查技巧实录

4.1 长链路自动化中的MCP上下文失控问题

我用了一个月的MCP+Skill流程后,最大的痛点是上下文窗口爆炸。一次完整的逆向过程中,AI会调用几十次工具,每次工具返回的页面HTML日志、调试数据都塞进上下文中。到了第三步、第四步,AI的“注意力”明显下降,开始答非所问——比如问它比对这个请求和上一个请求的签名,它居然说这两次是同一个请求。

这个问题的解法是任务切片。不要把一次逆向做成一个超长Agent对话,而是拆成几个独立子任务,每个子任务只保留必要上下文,最后再汇总。

我实测比较有效的切分方式是:

子任务目标MCP工具保留上下文
流量采集Playwright + Burp请求列表和参数名
代码定位Debugger MCP候选函数源码和调用栈
Hook验证Playwright输入输出对记录
本地复现自建工具Node.js代码和校验结果

每个子任务结束后,把关键结果写入一个临时文件或者单独的消息,然后开启新会话继续。这个优化之后,整个流程的稳定性和成功率是有显著提升的。

4.2 断点调试和自动化流程的互相干扰

很多用CDP做自动化的人都会遇到这个问题:你设置了断点,页面卡住了,AI想继续操作但页面无响应;你不设断点,又没法在函数执行的瞬间观察状态。

我的经验是把断点策略从“硬断点”改成“条件日志”。CDP的Debugger.setBreakpointByUrl可以设置条件表达式,命中条件时执行一段打印语句但不断下:

// 条件断点示例 { "url": "app.js", "lineNumber": 142, "condition": "arguments[0] && arguments[0].sign ? (console.log('SIGN_ARG', arguments[0]), false) : false" }

注意condition的最后一个表达式必须是false,这样断点命中后不会暂停,只是通过副作用打印日志。这种方式对页面运行的影响极小,AI能继续操作而不会卡死。

4.3 混淆代码太强,还原算法不可行怎么办

必须要承认,MCP+Skill不是万能的。JavaScript的混淆手段越来越先进,比如控制流平坦化(Control Flow Flattening)、不透明谓词(Opaque Predicate),这些技术人为逆向都费劲,让AI自动还原确实过于乐观了。

我的应对策略是三层降级。第一层,尝试用现有AST工具和反混淆脚本恢复可读性;第二层,如果恢复失败,但函数行为清晰(输入输出确定、无随机状态依赖),就直接在浏览器环境里把该函数保存为幂等函数,用Node的vm模块引用来实现复现,不去还原内部逻辑;第三层,如果函数依赖大量浏览器API(比如基于Canvas指纹或WebGL渲染结果做种子),那就干脆放弃本地Node复现,改正面“浏览器沙箱”方案——起一个Playwright持久化上下文,每次需要签名时就在这个真实浏览器环境里执行目标函数。

有趣的是,第三层方案在实践中往往比第一层更“稳”。因为很多混淆样本抗静态分析的能力很强,但抗动态执行的能力很差——你只要保证执行环境跟线上一致,它能给出正确结果就行。

4.4 常见问题速查表

症状可能原因排查方法
Playwright MCP截图全白headless模式下Canvas指纹被检测使用--headed并指定固定viewport
请求参数在Hook后不变目标函数被缓存,或加密在Service Worker层完成强制刷新并注册Service Worker检查
本地复现与线上结果不一致环境差异(时区、UA、Web Crypto API)对比输入向量,逐函数二分定位差异点
Debugger MCP搜索无结果JS文件不在主线程加载(Web Worker)注册Debugger.scriptParsed监听所有target
同一个Skill执行结果差异大Skill工作流里步骤描述模糊给Skill补充具体参数和示例输出

4.5 Skill执行速度慢的优化解法

最后分享一个提升Skill执行速度的小技巧。当Skill里包含大量步骤时,AI往往会每一步都调用MCP工具重新获取一遍数据,但实际上很多数据是重复的。比如先调用截取页面截图,再调用获取所有链接,再调用获取Console日志,这三个操作其实可以合并成一个“一次CDP会话返回多个维度”的工具。

我在自建MCP Server里做了这样一个聚合工具batch_collect:

server.tool( "batch_collect", "一次采集页面快照、请求列表、console日志和执行环境指纹", async () => { const [snapshot, requests, logs, ua] = await Promise.all([ cdp.send("Page.captureScreenshot"), cdp.send("Network.getResponseBody"), cdp.send("Log.enable"), cdp.send("Runtime.evaluate", { expression: "navigator.userAgent" }) ]); return { snapshot, requests, logs, ua }; } );

这个工具每次能省下80%的往返消耗。在长流程的自动化中,积少成多,整个Skill执行时间能从十几分钟压到两三分钟。

5. 合规边界与理性预期

写着写着想说几句掏心窝的话。前端JS逆向这行技术,能力本身是中性的。我用它来分析自家系统的前端签名逻辑、做接口自动化测试、排查线上安全问题,甚至帮团队理解历史遗留代码的加密行为,这些都是正当的工程需求。但这套能力同时也可能被滥用——比如未经授权抓取他人平台的数据做商业用途。我的建议是务必在合规范围内使用,只对你拥有权限或已获授权的目标做分析。

另外,对这套方案要有一个理性预期。MCP+Skill大幅提高了逆向效率的下限,但没改变挑战的天花板——重度混淆、WebAssembly、服务端参与签名等场景,依然需要人工介入。它适合的场景是“大多数前端项目采用的常规加密方式”,这类场景下它能帮你把几小时的工作压缩到十几分钟。而当遇到真正的对抗性安全防护时,这套流程能为你节省分析时间,但最终决策还是需要人来判断。

用了一段时间后我个人的体会是:这套方案最大的价值不是帮你省了多少时间,而是把逆向过程从“一个人脑子里的隐性经验”变成了“团队可用的显性资产”。Skill沉淀下来,新人也能快速上手;MCP把工具串起来,AI能稳定复现老师傅的操作路径。如果你团队里正好有这类工作量很大、又特别依赖个人经验的JS分析需求,不妨从一个小场景入手,先跑通一个最简单的“定位加密参数”Skill,再逐步扩展覆盖面。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 5:07:25

Sony-PMCA-RE:索尼相机USB协议层逆向与RAW数据捕获实战指南

1. 这不是“刷机工具”,而是一把打开索尼相机底层世界的物理钥匙如果你在搜索“索尼相机怎么解锁隐藏功能”“如何让A7系列支持RAW视频外录”“为什么我的DSC-RX100M7无法启用Log模式”,大概率会撞见Sony-PMCA-RE这个名字。它不像Magisk或TWRP那样被大众…

作者头像 李华
网站建设 2026/9/26 5:07:00

PyTorch新闻文本分类实战:数据清洗、RoBERTa-wwm-ext适配与避坑指南

简介:本资源是一套基于PyTorch实现的新闻文本分类系统完整工程包,面向计算机、人工智能及相关专业本科生与初阶算法学习者,聚焦自然语言处理中的文本分类任务,适用于毕业设计、课程实践与项目能力训练。资源共449个文件&#xff0…

作者头像 李华
网站建设 2026/9/26 5:05:56

VS Code Remote-SSH远程开发实战:环境一致与原子化开发

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 5:05:46

Flutter+HarmonyOS录音功能开发:状态机设计与踩坑指南

开发 EchoMusic(回声音乐)时,我第一个写完的功能就是录音控制区,因为它是整个 App 的敲门砖。可就是这个看起来只有“开始、暂停、停止”三个按钮的区域,让我返工了整整三轮:第一轮在真机上双击直接崩&…

作者头像 李华
网站建设 2026/9/26 5:05:46

TCP/IP协议栈实战:Windows网络排查与Wireshark、iperf工具详解

这周帮人排查一个“文件上传特别慢,大文件传一半就断”的问题,机房跑了两趟,交换机也看了,最后发现问题竟出在TCP重传参数和接收窗口上。类似的情况这几年遇到太多次,很多人一说TCP/IP就想起大学课本里的四层模型&…

作者头像 李华
网站建设 2026/9/26 5:05:34

字符串相加

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华