x64 VMP类壳动态转储与导入表重建实战

发布时间:2026/11/2 7:55:19

x64 VMP类壳动态转储与导入表重建实战 在逆向分析这条线上摸爬滚打久了绕不开的一类硬骨头就是商业虚拟化保护壳。标题里提到的这类壳下文统一叫 VMP 类壳在 x64 平台上把原始指令翻译成一套自定义字节码交给内置的虚拟机逐条解释执行静态反汇编打开一看是一片跳转和查表控制流图像被搅碎的拼图。这种情况下再死磕静态分析投入产出比极低于是动态转储就成了绕过去的第一选择——趁程序在内存里已经把自己还原成可执行形态的那一刻把镜像整块捞出来再修复导入表、重建节表得到一个能重新丢进分析工具的样本。这篇内容讲的就是这件事动态转储的原理边界在哪、自动化工具靠什么规则做判断、实操环节有哪些必须盯住的参数、以及那些文档里不会写但一定会踩的坑。适合已经能看懂基本 PE 结构、会用调试器下断点、想把手工作业升级成稳定流程的中级分析者完全没接触过 PE 格式的朋友建议先补一轮基础再来不然下面很多参数会对不上号。全文讨论的所有操作前提都是你自己拥有合法授权的目标——自研产品的兼容性排查、授权范围内的恶意样本分析、安全课程实验环境这三类场景之外请不要动手。1. 动态转储到底在解决什么问题1.1 静态分析与虚拟化保护的结构性冲突先把冲突的本质说清楚不然后面所有工具选择都是盲目的。常规的加壳思路是压缩或加密原始区段运行时在内存里解压还原入口点跳过去就完事。虚拟化保护走的是另一条路它把原始指令逐条翻译成自定义字节码指令边界、寄存器语义、调用约定全部被重新定义字节码解释器才是真正的执行引擎。这意味着内存里从头到尾都不存在一段完整的原始代码等着你去抓——这是很多人第一次接触这类壳时的认知误区以为跟普通压缩壳一样找个 OEP 就完事。但保护壳也不可能把一切都虚拟化。原因很现实性能。完整虚拟化所有代码程序跑起来会慢到不可接受所以商业壳普遍采用选择性虚拟化策略——把开发者标记的关键函数授权校验、核心算法转成字节码其余大量代码仍然以原生指令形式存在只是被壳的加载器搬运到了内存的其它位置并做了重定位。这就留下了缝隙原生代码在内存里是真实存在的、可执行的动态转储要抓的正是这部分。所以动态转储的定位从来不是完整还原被保护的程序而是把壳没虚拟化的那部分还原出来顺带拿到一个可以继续分析的骨架。指望一次转储就把虚拟化函数还原成可读汇编那是想多了。理解这个边界后面才不会在工具失败时怀疑人生。1.2 三种切入时机与各自的取舍转储时机选错后面修复工作翻倍。实践里主要三种切法各有明确的适用面。第一种是入口点转储程序刚被壳加载完、控制权还没交给原始代码时就抓镜像。优点是时机早、壳的后续自修改动作还没开始内存布局相对干净缺点同样明显——此时导入表往往还没修复完很多区段还是压缩状态抓出来基本是个半成品后续要补的东西特别多。这种切法适合壳的加载流程简单、压缩层浅的目标。第二种是运行中转储等程序跑到某个稳定阶段比如主窗口创建之后、或者某个业务逻辑触发之后再抓。这时候壳的绝大部分还原动作已经完成原生代码区段是实打实的可执行内容导入表也基本填好了。绝大多数实战走这条路。代价是需要判断什么时候算稳定判断错了就会抓到一半还原一半没还原的镜像。第三种是断点触发式转储在壳的某个关键跳转指令上设断点命中瞬间抓取。这种方式颗粒度最细但依赖你对这个壳的加载流程有足够了解属于进阶手段。我个人的习惯是先跑第二种拿到一个能用的基线镜像如果发现某些区段明显不完整再回头用第三种针对性地抓第二次两次结果对着看缺什么补什么。1.3 合法边界必须提前划清这段放在前面说比放在最后说有用。逆向分析本身是一门正经技术恶意样本行为分析、遗留系统的兼容性排查、自研产品被第三方壳保护后排查崩溃原因、安全课程里的实验样本这些都是正当场景。但同样的技术拿去对付别人的商业软件、绕过授权机制、提取受保护的核心算法性质就完全变了。具体到操作层面我给自己定的几条线是目标必须是自有或明确授权的分析产物只在授权范围内流转不公开分发不针对授权校验相关代码做定向提取。这三条听着像套话但真出事的时候它们是你唯一能拿出来的东西。技术能力没有善恶使用场景有。2. 工具设计思路拆解自动化凭什么跑得起来2.1 从手工找 OEP 到规则化判定手工脱壳年代找原始入口点靠的是经验和直觉单步跟、看栈、盯跳转。这套方法在 x64 上效率极低因为寄存器多、调用约定不同、壳的跳转链条更长。自动化工具的核心贡献就是把这套模糊经验拆成可以逐条执行的判定规则。常见的判定维度有这么几类。一是区段属性突变壳在搬运完代码后会把某些区段的属性从可读可写可执行改成可读可执行这个属性翻转的瞬间往往就是原始代码就位的信号。二是代码特征匹配x64 下典型编译器生成的函数序言有固定形态保存非易失寄存器、调整栈指针工具会在候选地址上做模式匹配命中率高的地址优先考虑。三是控制流收敛性从当前指令往后推一段如果跳转目标全部落在同一个区段内、且没有出现明显的壳跳板特征就认为进入了正常代码区。这套规则不是百分之百准的。编译器优化等级高、或者代码经过混淆处理时特征匹配会失效壳故意在跳转链里插入假目标时收敛性判断也会被带偏。所以工具通常会把多个候选点都标出来让人工确认。看到工具输出一堆候选地址别嫌烦那恰恰是它诚实的表现。2.2 内存镜像的抓取粒度与时机控制抓多少、什么时候抓这两个参数直接决定成品质量。粒度上有三种常见做法整进程镜像全抓、按模块抓、按区段抓。全抓最省事但产物巨大且包含大量无关的运行时数据按模块抓能把主映像和系统模块分开是主流做法按区段抓最精细但需要你自己维护区段清单容易漏。时机控制上有个容易被忽略的细节抓取动作本身会短暂挂起目标进程如果此时目标正在处理定时器或者多线程同步可能触发它自己的超时检测逻辑导致抓到的镜像里某些结构处于中间状态。稳妥的做法是在抓取前先确认目标处于单线程空闲状态或者至少避开它正在做密集计算的时间窗。实际操作里我会先跑一次侦察性抓取只抓主映像的头部和一个代码区段看看规模和完整度确认没问题再做完整抓取。多花两分钟省掉后面反复重来的半小时。2.3 导入表重建为什么是最难的一环壳对导入表的处理通常是两步加载时把原始导入表抹掉或者打乱运行时再按需把真实函数地址写回各自的调用位置。这意味着转储出来的镜像里调用指令后面跟着的可能是壳自己的跳板地址而不是标准的导入描述符结构。直接把这样的镜像丢进分析工具会看到一堆指向无效地址的间接调用静态分析根本没法跟。重建的核心思路是反向推导遍历代码区段里所有形如间接调用/跳转的指令把操作数指向的地址收集起来形成一个地址集合然后遍历当前进程已加载的所有模块取每个模块的导出表在导出表里查找是否有哪一项的地址落在刚才那个集合里。匹配上的就说明这个调用位置原本导入的是哪个函数据此生成导入描述符。这里有个坑地址集合里会混进大量非导入项比如虚函数表指针、函数指针数组、运行时生成的回调。工具没法百分百区分只能靠启发式过滤——落在已知模块映像范围内的保留落在堆或者栈上的丢弃指向模块内部非导出区的也丢弃。过滤规则调得越严漏掉的导入越多调得越松误判越多。这是纯粹的权衡没有完美解。2.4 工具能力的天花板在哪把工具的定位说透能省掉很多无效折腾。这类动态转储辅助工具能做的抓取内存镜像、按规则给出候选入口点、扫描重建大部分导入表、做基础节表修复。它做不到的还原被虚拟化的函数体、修复被壳改写的控制流、还原运行时动态生成的代码、处理多层嵌套保护。我在实际使用中的体会是工具输出的成品大概能覆盖原生代码区段的七八成剩下那部分要么是壳的运行时 stub要么是动态生成的内容本来就不在可还原范围内。拿到七成可分析的代码对绝大多数分析任务来说已经够用了——你不需要读懂整个程序你需要找到那个关键函数所在的位置然后顺着调用链往下看。3. 实操流程与关键参数落地3.1 环境准备与调试器配置环境这块核心原则是隔离。分析环境独立于日常工作环境不装无关软件网络按需断开。目标进程跑起来之前先把调试器附加方式定下来启动时挂起附加还是运行中附加。前者能捕获加载早期的行为后者对反调试更友好。调试器配置上几个必调的项异常处理策略要设成把异常交还给被调试进程原因是不少壳会故意触发异常来做控制流混淆如果调试器全盘接管流程就走不下去了断点类型优先用软件断点硬件断点虽然不易被检测但数量有限x64 下只有四个关键位置才用符号路径清空避免调试器去加载系统符号拖慢附加速度。还有一个常被忽略的配置内存窗口的显示格式。默认按字节显示看结构体的时候极其痛苦。调成按指针宽度显示配合手动标注读内存布局的效率能提升一大截。3.2 定位保护壳入口并推进到目标区段拿到目标进程后第一步是确认入口点位置。调试器默认停在系统加载器交给程序的那个入口这个入口属于壳的加载器代码不是原始入口。判断方法很直接看这个入口所在的区段名和属性壳的区段命名通常有特征随机字符、非常规名字属性是可读可写可执行。接着是推进。单步跟过整个壳加载流程在 x64 上不现实动辄几万条指令。可行的做法是分区段下断点先读一遍节表对每个候选代码区段在起始地址下断点跑起来看哪个区段先被执行到。壳的加载顺序一般是从解压到重定位到修复导入最后跳到原始入口这个顺序能帮你判断当前处在哪个阶段。推进过程中要盯住两个东西栈顶内容和通用寄存器的值。原始入口被跳转到的瞬间栈上通常是加载器压入的参数结构寄存器状态符合调用约定下的初始形态。如果发现跳转目标落在一个明显是壳的跳板区一堆连续的间接跳转指令说明还没到继续跟。3.3 触发转储与镜像修复的参数计算转储动作本身不复杂难的是修复。这里必须掌握几个换算关系否则修出来的文件加载就崩。第一组是节对齐换算。内存里的区段按 SectionAlignment通常 0x1000对齐文件里按 FileAlignment通常 0x200对齐。转储出来的镜像是内存布局要转成文件布局必须先做换算RawSize Align(VirtualSize, FileAlignment)举个具体例子某区段 VirtualSize 为 0x1A000FileAlignment 为 0x200那么 RawSize 0x1A000已经对齐文件偏移按前一个节的结束位置往后接。如果 VirtualSize 是 0x1A300RawSize 就要补到 0x1A400。第二组是 RVA 到文件偏移的换算公式是FileOffset PointerToRawData (RVA - VirtualAddress)假设某区段 VirtualAddress 0x1000PointerToRawData 0x400要定位 RVA 0x1234结果就是 0x400 (0x1234 - 0x1000) 0x634。这个换算在手工修复导入表时天天要用建议直接写个小脚本备着。第三组是镜像大小与文件大小的区分。SizeOfImage 是内存中的总大小按 SectionAlignment 对齐SizeOfHeaders 是头部大小按 FileAlignment 对齐。修复时这两个数字必须和节表严格自洽任何一处对不上加载器都会在映射阶段报错。我习惯在修复完成后用工具做一次结构校验把不匹配的字段逐个揪出来。导入表修复的操作路径先定位重建出的导入描述符数组确认每个描述符里的 OriginalFirstThunk 和 FirstThunk 都指向有效数据然后把描述符数组整体搬到一个空闲区段或者追加到现有区段末尾最后更新数据目录里的导入表项指向新位置。搬移过程中注意指针宽度——x64 下所有地址字段都是 8 字节别按 4 字节处理这个错误极其常见症状是导入表前半部分正常后半部分全是乱码。3.4 转储结果的验证方法修完不等于能用必须验证。我的验证流程是三层。第一层静态验证用 PE 查看工具打开检查节表是否连续无重叠、每个节的 RawSize 和 VirtualSize 是否自洽、数据目录里的导入表和重定位表指向的 RVA 是否落在有效区段内。这一层能挡掉大部分低级错误。第二层加载验证在隔离环境里尝试加载修复后的镜像观察是加载阶段就失败还是能跑到入口。加载阶段失败基本是节表或目录项的问题能加载但入口处崩溃多半是重定位没处理——壳在搬运代码时做了重定位原始重定位信息可能已被破坏需要从转储镜像的当前状态反推或者干脆把镜像按固定基址加载来规避。第三层行为验证跑起来之后对照原程序的正常行为看关键功能是否一致。这一步不是必需但能帮你判断哪些代码还原成功、哪些还是壳的残留。4. 常见问题与排查技巧实录4.1 转储后加载失败的三类原因加载失败是最常见的第一道坎症状都是双击没反应或者提示不是有效映像但成因完全不同得分开看。第一类是头部结构错位。转储工具抓的是内存镜像头部里的 PointerToRawData 字段还停留在内存语义上直接当文件用必然错。解决方法是重新计算每个节的 RawSize 和 PointerToRawData从头顺次排列。判断依据很简单把所有节的 PointerToRawData 按升序排如果出现不连续或者小于 SizeOfHeaders 的值就是这类问题。第二类是节属性冲突。壳在运行时会把某些节改成可写可执行转储保留了这些属性静态分析工具打开时会报警告部分加载器直接拒绝。处理方式是把代码节改回可读可执行数据节改成可读可写除非你确认某段代码确实需要运行时自修改。第三类是校验和缺失或者错误。部分加载路径会校验头部的校验和字段。修法是把校验和字段清零或者按算法重算绝大多数情况下清零就够用。我踩过最坑的一次是三类问题叠在一起头部错位导致读不到正确的节表节表读错导致属性修错属性修错又让加载器在更早的阶段就退出报的错和真实原因完全对不上。教训是修复必须按头部→节表→目录项的顺序逐层验证不要跳步。4.2 导入表残缺与函数地址错乱导入表修完但函数调用错乱症状是程序能启动、跑到某个功能点就崩崩溃地址落在一个看起来合法但完全无关的模块里。这类问题的根源通常是导入描述符和实际代码里的调用位置对不上。原因有几个。一是扫描时漏了间接调用形式某些编译器生成的调用不走标准的 IAT 间接跳转而是先把函数地址加载到寄存器再 call扫描规则如果只匹配内存操作数就会漏。二是同一地址被映射到多个导出名转发导出、别名导出工具随机挑了一个名字对了但语义可能不对。三是壳故意在 IAT 里塞了假项指向壳自己的 stub这些假项被当成真导入重建出来运行时自然崩。排查手法把崩溃地址反查所属模块如果落在壳的残留 stub 区说明那一项是假导入从描述符里剔除如果落在某个系统模块的合法范围但函数不对看看是否存在同名转发导出的情况手工指定正确的那个。4.3 反调试机制对转储流程的干扰这类壳普遍带反调试表现形式五花八门检测调试器进程、检测调试寄存器、检测断点指令字节、检测时序异常、滥用异常处理制造控制流混乱。表现上就是单步走着走着程序自己退了或者断点明明设了却命不中。应对思路不是硬刚而是减少暴露面。具体做法尽量用运行中附加而不是启动挂起附加时机选在壳的初始化完成之后少用软件断点改用内存断点或者干脆用日志式跟踪对时序检测可以在调试器里关掉时间戳相关的系统调用返回值或者接受偶尔的误判多跑几次取稳定的那次结果。有个偏门但好用的技巧如果确定目标在某个时间窗内不做反调试检查把转储动作压缩在这个窗口内完成中间不暂停、不单步一次性抓完再慢慢分析。这要求你提前把操作步骤演练熟临场不犹豫。4.4 问题排查速查表症状最可能原因优先排查动作加载即失败无任何提示节表 PointerToRawData 错位按升序检查各节文件偏移连续性加载报映像无效SizeOfImage 与节表不自洽重算对齐后的总大小能启动但入口崩溃重定位信息缺失按固定基址加载验证特定功能崩溃地址在无关模块导入项错乱或假导入未剔除反查崩溃地址所属模块程序跑到一半自己退出反调试触发减少断点改用运行中附加导入表前半正常后半乱码指针宽度按 4 字节处理全字段改按 8 字节重算转储镜像某区段全零抓取时机过早延后到主循环稳定后再抓静态工具打开满屏无效跳转壳 stub 未清理手工剔除跳板区段提醒速查表里的每一条都对应过真实踩坑但症状相同不代表原因相同。排查顺序建议按结构层→数据层→行为层推进先确认文件结构合法再看导入数据正确最后才验证运行行为跳层排查会让你在错误的方向上耗费大量时间。5. 从防守方视角反看这套流程5.1 保护强度到底来自哪里做完一轮转储回头看保护壳的设计会发现它的强度并不来自把代码藏得多深而来自让还原出来的东西没法直接用。虚拟化的真正价值是提高了理解成本而不是提高了提取难度——原生代码终究要在内存里出现抓是抓得到的。真正拉开差距的是三件事一是导入表破坏的彻底程度破坏得越彻底重建的误判率越高二是反调试的密度和隐蔽性密集的检查会让调试流程变得极其低效三是自修改代码的比例运行时会重写自己的代码段导致抓取时刻不同结果不同无法通过多次抓取对齐。反过来说如果一款壳在这三点上做得马虎即使虚拟化引擎本身很复杂实际防护效果也有限。这个判断对做防护设计的人有参考价值把资源投在破坏可分析性上比投在增加虚拟化指令集的复杂度上收益更高。5.2 给开发者的几条实际建议如果你是自己产品的开发者正在考虑要不要上这类保护我给几条从分析侧反推出来的建议。第一别指望保护能挡住 determined 的分析者它挡的是成本和规模。你的目标应该是让单次分析的投入超过攻击者的收益预期而不是追求绝对不可破。第二只保护真正核心的部分。全量虚拟化带来的性能损失和稳定性风险往往超过它带来的安全收益。我见过一些产品把整个业务逻辑都塞进虚拟机结果程序启动慢了三倍崩溃率上升用户流失的损失远大于被逆向的损失。第三反调试要克制。过于激进的反调试会在用户环境里误触发——某些安全软件的行为本来就像调试器误判之后程序直接退出用户只会觉得你的软件有毛病。设置合理的检测阈值比堆检测项重要。第四也是我认为最容易被忽略的一条不要在受保护代码里硬编码敏感常量。虚拟化能提高阅读难度但内存里的常量该出现还是会出现扫一遍内存就能捞到。密钥、授权判定用的魔数这类东西该放服务端就放服务端。最后分享一个我在实际工作中养成的习惯每做完一次完整的转储和分析不管成功失败都花十分钟把过程写成流水账——用了什么时机、哪个参数、卡在哪一步、最后怎么绕过去的。这类记录攒上十几份之后你会发现自己对这类保护壳的判断速度会有质的变化很多以前要试半天的情况现在看一眼节表属性就知道该走哪条路。这个习惯比任何工具都值钱。
延伸阅读

更多相关文章

2026/11/2 7:55:19

那笔0.0211元扣费:免费Agent的自校验误判与静默成本

如果你也在自养Agent,我建议你养成一个习惯:定期翻一遍账单,别只看大头支出,专门盯着那些几毛几分的小额扣费。上周我就翻出一笔0.0211元的扣费,第一反应是“不是都用的免费模型吗,哪里来的钱?”…

2026/11/2 7:50:18

数字时代护眼全攻略:从设备设置到用眼习惯

1. 护眼需求与现状分析现代人每天平均盯着电子屏幕的时间超过8小时,这个数字还在持续增长。作为一名长期伏案工作的内容创作者,我亲身体会到眼睛干涩、视力模糊带来的困扰。去年体检时,我的视力从1.0降到了0.6,这才意识到护眼不是…

2026/11/2 9:00:30

前端工程师转AI应用开发:三个月实战学习路线图

动手写这篇之前,我先把话说在前面:网上讲“AI应用”的教程很多,但大部分要么是理论堆砌,要么是一堆装满名词的购物清单。真正想从一个写页面的前端,转成能独立搞定AI应用前端的工程师,缺的不是资料&#xf…

2026/11/2 9:00:30

VS Code高效开发:核心插件配置与优化指南

1. 为什么VS Code插件能提升开发效率?VS Code作为微软推出的轻量级代码编辑器,其核心竞争力在于强大的插件生态。通过安装特定插件,开发者可以:自动化重复性工作(如代码格式化、自动补全)集成第三方工具链&…

2026/11/2 9:00:30

马斯克称Grok 4.7智能体编码排第三,意味着什么?

1. 这条消息到底在说什么马斯克在社交平台上发了一条动态,大意是 Grok 4.7 这个版本让 xAI 在智能体编码这个细分赛道上坐到了第三的位置。消息本身很短,但信息量不小。我第一眼看到的时候,注意力没放在“第三”这个名次上,而是放…

2026/11/2 9:00:30

Stata空间计量模型实证全流程:权重矩阵、模型选择与效应分解

做空间计量这几年,被问得最多的一句话是:“我的数据里有地区维度,是不是就该直接上空间模型?”说实话,这句话背后藏着两种极端:一种是把空间计量当成高级玩具,不管三七二十一先跑一个带 W 矩阵的…

2026/11/2 9:00:30

诊所电子处方管理系统的核心功能与优化实践

1. 诊所电子处方管理系统的行业痛点与需求分析在基层医疗机构工作多年,我深刻体会到传统纸质处方带来的种种不便。记得去年流感季高峰期,我们诊所日均接诊量突破200人次,药剂师每天要处理近300张手写处方。某天下午,一位老患者拿着…

2026/11/2 8:55:27

阿里EDAS Dome:微服务治理闭环的最小可验证系统

简介:本资源是阿里EDAS(企业级分布式应用服务)的典型微服务实践Demo,面向Java开发者、微服务初学者及企业架构转型技术人员,聚焦解决分布式系统开发门槛高、服务治理复杂、部署运维难等实际问题。压缩包为16KB的ZIP文件…

2026/11/2 6:08:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/2 6:39:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

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

2026/11/2 1:04:04

工业级SPI隔离设计:硬件抗扰与信号完整性实战指南

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

2026/11/2 1:04:04

PLFM_RADAR:主动探测数据通道,构建低误报的数据感知系统

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

2026/11/2 1:04:04

工业SPI隔离设计:MAX14431与STM32F746硬件抗干扰实战

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

2026/11/2 6:08:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/2 6:39:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

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

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑