尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
VMProtect代码修复实战:三步搞定VMPDump逆向与校验对抗
VMProtect的代码修复第一次听到这话的人多半会愣一下不是有Dump工具吗对着内存镜像修修补补不就行了真上手试过就知道事情远没有想象中那么顺利。VMP保护的代码在运行时已经把原始指令拆散重组塞进一套自定义虚拟指令集里直接从内存里抠出来的东西往往缺胳膊少腿轻则入口点对不上重则导入表乱成一锅粥补一个崩两个最后整个人跟着崩溃。这篇文章不谈那种“万能破解”的路数只聊在合法场景下——比如分析恶意样本、恢复自己公司里失修的商业项目、适配老系统——怎么把VMP保护的代码修复做到能跑、能看、能继续维护同时把VMPDump这类工具在逆向流程里的作用边界讲清楚。先别急着开干有个事情必须放在最前面说清楚代码修复这件事只适用于你拥有合法权限的软件。所谓合法权限无非三种情况一是你在分析恶意软件目的是搞清楚它怎么偷数据、怎么隐藏、怎么对抗查杀属于安全研究的正当范畴二是这个程序本身就是你自己或你所在团队开发的加壳加到自己都解不开纯属生产事故需要补救三是这类商业软件已经停止维护你作为充分授权的维护方需要让它在新环境里继续运行。除此之外面向商业软件去壳、绕过授权验证、给盗版软件做兼容这些都是法律红线不能碰这篇文章也不会提供任何这方面的具体操作演示。以下所有内容都会站在防御者和修复者的角度来讲手里的样本也请确保是来源合法的测试程序。1.1 VMP为什么难搞代码虚拟化的本质想要理解代码修复为什么麻烦先得知道VMProtect到底做了什么。很多资料把VMP说成“加壳”这个说法其实不够准确。传统的加壳程序比如UPX本质是压缩加解密程序入口先执行一段壳代码把压缩的原始数据解压还原到内存然后跳转到原始入口点CPU继续执行原本的机器码。但VMProtect做的事情完全不同它会在编译时把指定的函数代码段翻译成一套自定义的字节码这套字节码只有它自己内置的虚拟机解释器能读懂。用生活化的方式类比一下普通加壳相当于你把一份中文合同锁进保险柜开锁之后合同内容还是中文谁都能读。VMProtect则是把合同翻译成了只有特定翻译官才能听懂的方言哪怕保险柜门敞开着方言录音就摆在桌上不懂方言的人听了也是白听。这个“翻译官”就是VMP的虚拟机解释器它负责在运行时逐条读取字节码、解析操作码、模拟出原指令的效果。这带来的直接后果是你在内存里看到的“代码”根本不是传统意义上的x86指令流。Dump工具从内存里抓出来的可能是密密麻麻的虚拟机字节码段以及一个循环执行解释任务的VM dispatcher循环。如果你按照常规思路直接在Dump后的样本里搜索入口点特征、修复导入表会发现很多东西对不上号因为真正的关键逻辑已经藏进虚拟机里了。这也是为什么网上很多人吐槽“Dump简单修复要命”——Dump只是拍照修复要做的是从一张严重畸形的照片里还原出完整的人脸。1.2 VMPDump这类工具到底能做什么、不能做什么VMPDump这类工具的核心能力概括起来就是三件事提取内存映像、定位原始入口点附近的代码段、辅助修复导入表。它做的事情相当于帮你把翘开的保险柜里的文件整理好摆在桌面上。但它解决不了“方言翻译”的问题虚拟机保护的核心逻辑依然是一堆字节码工具不会帮你把它们还原回能直接看懂的机器码——那是另一类反编译器的工作而且目前针对VMP虚拟机指令集的反编译器成熟度都还很有限。所以对VMPDump的定位要清醒它是修复流程的起点不是终点。很多新手最大的误区就是以为跑完Dump工具、拿到一个能双击运行的exe就万事大吉。实际工作中Dump完成后还有大量的体力活和脑力活包括定位被抽走的API调用、修复被混淆的跳转逻辑、绕过运行时校验甚至在某些场景下需要在字节码层面做动态跟踪才能弄清程序的实际行为。这篇文章接下来要讲的“三步”就是基于这个清醒认知展开的完整流程。2. 三步修复的完整框架别急着动手先建立全局观网上流传的“三步修复法”几乎都是这样写的第一步用VMPDump抓dump第二步修复dump文件的导入表第三步找到原始入口点并脱壳。这套流程不能说错但它忽略了一个前提——你真的了解这个程序在运行时的行为吗如果连程序启动后做了哪些关键调用、有没有运行时自校验都不清楚闷头去修一个dump大概率会陷入“修好一处、崩坏一处”的死循环。我自己的实践习惯把三步拆成更有操作性的版本静态分析定策略通过文件头信息、加壳参数、字符串残留等线索判断VMP的保护等级是只保护了入口点还是保护了整个关键函数段然后决定修复的优先级和重点。动态运行建基线在调试器里让程序跑起来记录系统调用序列、文件访问行为、注册表读写搞清楚程序正常启动需要哪些外部条件为后续校验修复提供参照。定向修复与回归验证基于前两步的信息对Dump后的文件做导入表修复、入口点修正、校验对抗然后反复运行验证一点一点把问题收口。这个框架的关键区别在于你是在“理解程序之后做修复”而不是“瞎猜着修”。理解先于操作看起来多花了时间实际上省了后面无数冤枉路。2.1 静态分析定策略搞清楚你面对的是哪种VMP拿到一个加了VMProtect保护的程序最忌讳的就是直接开Dump工具硬干。先花半小时做基础侦察能让你对后续的难度和心理预期有个准数。第一步用十六进制编辑器或者PE分析工具看一眼文件头。VMProtect在PE文件的资源段、区段名称上有比较明显的特征比如一些自定义区段名。但要注意VMP不同版本之间的特征差异很大新版本会故意抹掉一些容易被识别的标记不能只靠文件名判断。第二步加载到反汇编工具里看一眼入口点附近的代码密度。如果入口点只有寥寥几十行而且大段都是跳转指令说明VMP对入口点做了轻量保护主要的保护逻辑可能在后面如果入口点是一片密集的虚拟机字节码而且出现了多个复杂的控制流跳转说明整个程序的架构层已经被虚拟化覆盖修复难度会成倍上升。第三步关注程序的运行时依赖。一个加了VMP的软件如果同时使用了高强度的反调试手段比如在运行时检测调试器、检测自身代码是否被修改那你后续的Dump和修复工作就必须考虑怎么绕过这些检查。常见的做法是在调试器里先禁用或Hook掉反调试例程或者在内存里临时patch掉检测点让程序能完整跑起来。这一步非常依赖调试经验没跑过几个样本很难一下子找准反调试逻辑的位置。我实操中踩过的最典型一坑遇到一个带VMP保护的老软件想当然地认为VMPDump跑一遍就完事结果Dump出来的文件一运行就弹错误框。回头检查才发现那个程序在启动流程里嵌了一个完整性自校验会用原始文件的CRC32对比当前进程的内存数据Dump行为本身就会破坏校验值程序立刻拒绝继续跑。后来学乖了动手之前先找自校验逻辑找到之后在内存里把校验函数的返回值直接改成“通过”再回头处理Dump一个问题都没多出。2.2 动态运行建基线记录正常运行的“行为指纹”静态分析能告诉你程序大概是个什么结构但解决不了“程序在真实环境里怎么跑”的问题。VMP保护的程序很多行为是在运行时才暴露出来的它会调用哪些系统API、读写哪些文件、访问哪些注册表键值、加载哪些动态库这些信息对后续修复至关重要。建立行为基线的常用手段有两种。第一种是最朴素的“观察法”在干净的系统环境里运行程序用监视类工具记录它的文件、注册表和网络行为看看它启动时会触发哪些外部依赖。第二种是“调试器观察法”在调试器里下断点关注关键API的调用时机和参数。这两种方法结合使用能让你在心里构建一幅“程序启动路线图”。有人会疑问程序都被VMP保护了调试它还有什么意义VMP保护的是代码逻辑但它终究要跟操作系统打交道。API调用无论如何都会发生只是调用时机和传参路径被隐藏了。你在API层面观察相当于从窗户缝里看室内——虽然看不全客厅全貌但至少能看到什么人进了哪个房间、开了哪盏灯。这些信息正是后续修复导入表和绕过自校验的线索。以我做恶意样本分析的经验来说动态基线还有一个额外的好处很多时候VMP保护的恶意样本会在运行时动态解密出第二阶段的载荷而这个解密过程不可避免会调用内存分配和写入相关的API。只要在API层面做好记录第二阶段载荷落地之后就能直接在内存中找到完整解密内容根本不需要去跟虚拟机的字节码死磕。这给修复工作省了太多力气。3. 核心细节拆解导入表修复、入口点修正、自校验对抗整个修复流程里最容易出问题、也最需要耐心处理的就是这三个核心环节。分开来仔细说。3.1 导入表修复为什么Dump后的程序总在启动时崩溃VMP为了保护API调用会把原程序的导入表做深度处理有的把导入函数地址彻底抹掉改成运行时动态解析有的把IAT导入地址表内容加密只在调用时才解密。因此Dump出来的内存镜像中导入表区域往往是一片乱码或空数据。你把镜像保存为exe之后Windows加载器在初始化阶段就要读取导入表发现内容对不上直接抛异常——这就是Dump后程序秒崩的第一个原因。修复导入表的思路是重新建立一套有效的导入描述结构。传统方式是对照程序运行时实际调用的API序列在导入表里逐项添加对应的模块名和函数名。听起来简单做起来很繁琐因为VMP保护的代码里API地址不是通过正常调用指令访问的而是通过虚拟机解释器间接跳转访问的你在静态代码里根本看不到直接的API调用点。这时候就需要动态追踪信息的辅助在调试器里记录程序从启动到崩溃前这段时间里所有被调用的系统API、以及它们被调用的顺序然后依据这份记录重建导入表。这个环节我给的提示是宁可多记录、不要少记录。很多新手只记录程序前几步的API调用结果修复完导入表程序启动阶段正常了跑到后面用到一个没记录到的API又崩了还得回头重新分析。一次到位的方法是让程序完成所有启动行为——包括打开主窗口、加载插件、初始化日志——再把完整调用序列作为修复依据。3.2 入口点修正找到那扇“真实的门”常规Dump得到的内存镜像里入口点指向的是VMProtect的运行时初始化代码不是程序原本的入口。如果你把这个入口点直接保留程序能启动但会先执行一堆VMP的初始化逻辑然后才跳转到真正的业务逻辑——问题是这堆初始化逻辑里往往包含校验代码检测到内存映像与原始文件不一致又崩了。所以修复入口点这个环节核心是定位“程序原始入口”在Dump之后的偏移位置。怎么做呢经验上有几条线索如果做Dump之前你已经让程序执行完了初始化阶段跳转到了自身的代码段那么Dump时进程中EIP指令指针附近的代码极大概率就是原始入口区。如果没有Dump到EIP附近的代码还可以借助API调用的特征来定位。典型的程序入口会调用获取命令行参数、初始化进程环境这类API你可以回溯这条调用链找到对应代码段的起始位置。某些情况下VMP会保留一份未被加密的原始入口地址在内存的特定区域通过搜索特征值也能定位。入口点修正没有统一的万能公式因为它取决于Dump时的具体时机和程序自身的架构。核心策略就一句话在Dump之前让程序跑到一个你熟悉的、稳定的执行阶段让EIP落在一个你认识的地方。Dump时的EIP越靠近真实代码入口点修正就越简单。3.3 自校验对抗处理“程序检测到自己被动了手脚”前面反复提到的自校验其实是一大类技术的统称。常见的有三种文件校验型程序在某处保存了原始文件的哈希值运行时重新读取自身文件并计算哈希比对不一致就退出。内存校验型程序周期性扫描自己占用的内存区域发现代码被修改就触发异常。时间校验型程序检测从启动到特定检查点之间的CPU执行时间如果明显异常就判定有调试器介入。处理自校验的方法因类型而异。文件校验型相对好办找到校验函数给它一个恒定的返回值或者在内存里把存放哈希值的位置改掉就行内存校验型比较难缠因为你Dump、修复的过程本身就是修改内存完全避开不可能只能找监测点并逐个拆除时间校验型在现代机器上少见主要存在于老样本里。还有一个常见误触发的校验是TLS回调函数。有些VMP保护的程序在TLS回调里就做了自校验而很多分析者在Dump之前根本没有注意到TLS回调的存在。结果Dump出来的程序无论怎么修入口点、修导入表启动还是立刻退出——原因就是你动了内存TLS回调第一个检测到了。排查这种问题需要在创建进程的最早期就介入查看进程的TLS回调列表。4. 实操过程从理论到跑通的完整记录纸上谈兵到此为止接下来用一次模拟场景把上面三步走一遍。样本是什么不重要我们就假设它是一个自己公司内部的老工具加了VMProtect之后能正常运行但代码反调试、运行时校验都开着而我们需要在另外一台设备上迁移它涉及修复代码逻辑以适配新环境。4.1 阶段一运行环境搭建和行为监控先在干净的虚拟机里搭好分析环境。要注意分析环境和你平时的工作环境尽量分开避免测试过程意外影响到日常使用。第一步启动程序观察它的启动行为。我习惯同时开三件套一个API监控工具盯系统调用一个文件监控工具盯磁盘写入再加上调试器保持附加状态但不主动打断。程序启动之后记录完整调用序列尤其注意三点程序是否创建了互斥体、是否注册了窗口类、是否在启动早期就申请了一大块内存。这些往往是后续校验逻辑的锚点。第二步让程序跑完一遍主要功能。对于自研工具我会手动触发几个核心功能模块一边操作一边观察API调用序列的变化。这能让你知道程序运行过程中哪些功能模块做了额外的系统调用、哪些功能模块的代码分布在内存的哪些区域。这些信息在后续修复时都有用。第三步在程序稳定运行后暂停进程记录当前EIP位置、各个线程的栈回溯信息。这些是Dump动作之前的“基线数据”用来对照Dump之后的代码位置。4.2 阶段二Dump的核心时机选择做Dump的时间点决定了你后续修复难度的下限。经验法则是等到程序完成所有初始化动作、进入稳定运行状态之后再Dump。如果程序有主窗口等主窗口显示出来、并且界面响应正常了再Dump通常会比在启动过程中Dump得到的结果完整得多。因为启动过程中很多校验逻辑正在执行有些关键数据还没解密好这时候抓取的镜像内容是不完整的。用VMPDump执行Dump时有几个参数需要留意。第一个是是否保留原始入口点信息建议记录但不直接使用第二个是是否对内存镜像做空缺填充也就是将内存中存在但文件中没有映射的区域统一填零这能避免镜像文件出现空洞导致的加载问题第三个是内存对齐方式保持和原始PE文件一致。Dump完成之后不要急着关调试器先把进程继续跑一会儿看看Dump动作本身有没有触发程序的自校验。如果有说明自校验逻辑会检查跟内存映像相关的信息需要回到3.3节说的对抗思路去处理。4.3 阶段三修复、验证、再修复的循环拿到Dump文件之后先做最基础的一次检查用PE分析工具打开文件看区段表、入口点、导入表三个核心结构是否完整。这里我遇到最多的情况就是区段表里的虚拟地址和文件偏移对应不上。VMP在加壳时添加了自定义区段Dump之后这些区段的映射关系往往会出现偏移不改的话程序加载就会出错。修法也比较机械手动把区段表里的偏移、大小信息对齐到实际镜像内容。第二步处理导入表。先对比动态监控得到的API调用序列和Dump文件导入表里已有的导入项把缺失的补上。这一步建议使用脚本辅助纯手工在十六进制编辑器里改几十个导入项效率太低不说还容易出错。现有的一些PE编辑工具支持自动重建导入表但自动工具处理复杂样本时经常会丢三落四所以还是要配合人工比对。第三步修正入口点。根据Dump时记录的EIP位置反推入口点在Dump文件中的RVA相对虚拟地址然后修改PE头的入口点字段。修改完之后先别急着保存在调试器里加载Dump文件跑一遍启动流程确认入口代码执行的位置和预期一致。第四步是自校验对抗。修改完的Dump文件在调试器里跑一遍观察是否有异常退出分支。如果触发了自校验回到动态监控阶段记录的位置定位校验逻辑所在模块然后替换掉校验调用。整个修复过程说穿了就是四个字修、试、修、试。改一个点跑一遍看还有哪里崩再改。经验越多一次修对的概率越高但完全没有回归验证的“神修”是不存在的。5. 常见问题与排查技巧实录下面这些坑都是处理VMP样本时大家反复踩的我挑最典型的几个整理出来。每个问题后都跟着排查思路方便你遇到类似情况时快速对照。5.1 Dump后程序一启动就消失连错误框都没有这种属于“无声失败”最常见的原因是程序在初始化阶段就检测到自身进程环境异常直接静默退出。排查顺序建议如下第一确认是否存在TLS回调。用PE工具查看TLS目录如果有回调函数先在调试器里观察回调是否执行、是否包含校验逻辑。TLS回调的问题在Dump修复中最容易被忽略因为它执行得非常早早于正常的启动流程很多调试器默认不会在TLS位置中断。第二确认导入表是否完整。在程序启动早期的API调用序列里如果存在某个API是加载器必须解析但是导入表里又缺失的程序会在加载阶段直接终止。检查方法是对比Dump文件导入表和监控到的调用序列找出缺失项。第三确认是否触发了反调试检测。有些VMP程序会在入口点直接检测调试环境一旦发现被调试就退出。排查方式是在调试器里禁用调试事件或者在条件断点处跳过检测分支。5.2 程序启动正常但跑到某个功能时突然崩溃这种问题通常意味着程序的主要功能代码里有部分逻辑没有被Dump镜像覆盖到或者覆盖的时机太早、内容不完整。VMP保护的区域如果分布在多个区块而你Dump时有些区块还没解密就会出现“能力缺失”式崩溃。排查办法是回到动态监控阶段在触发对应功能之前、执行过程中、执行完之后各做一次内存快照对比这几份快照的差异定位崩溃点对应的代码在哪个区块里然后重新补Dump或单独提取该区块数据合并进去。5.3 Dump后文件体积异常膨胀甚至比原始文件大几倍这多半是内存空缺填充策略导致的。Dump工具为了保证镜像文件的逻辑连续性会把内存中的空洞区域全部填零但这些空洞原本在文件里是不存在的填进去之后文件体积自然暴增。处理方式在保存Dump时选择压缩空缺区段或者在修复导入表之后把无效的区段信息从区段表中移除再重新调整文件偏移。这一步做完体积通常会恢复到正常范围内。5.4 修复完成后程序运行一次就崩第二次运行又正常这种“薛定谔的崩溃”一般指向时间相关的校验逻辑。有些VMP保护会在特定时段内校验代码执行频率或系统时间第一次运行生成的某些状态缓存第二次运行时会重新计算比对一旦发现状态不一致程序就异常退出。处理这种问题需要在监控阶段留意程序是否写入了持久化状态文件然后修复时把状态文件对应的读写逻辑一并修正。手段上是麻烦一点但好在遇到得少。6. 工程化建议如何把修复合规地做成可持续流程单次修复别人帮你就能搞定但如果你的日常工作经常遇到VMP样本——比如你是恶意代码分析者或者负责维护一批历史商业软件——那建议把修复流程工程化别每次都用“野路子”临时对抗。第一完好的样本归档。不管是从客户拿到的自研老软件还是恶意样本都对不上号就补原包。第一次分析后把原始文件和完整分析记录都归档保存方便日后复现时做对照。特别是纯签名、哈希等元数据存档越全后续工作越省力。第二脚本化工具积累。入口点修正、导入表重建、区段对齐这些操作都有很强的重复性值得写成脚本沉淀。每次遇到新样本第一个跑的就是脚本检查而不是手工从头来过。第三合法的授权边界意识。处理任何一个样本之前用三秒钟问自己一句我有没有权限有权限就踏踏实实做没权限就坚决不碰。这不是口号而是给自己的职业安全兜底。因为VMP这类保护工具的逆向分析游离在灰色地带的边缘一旦越过所有权边界哪怕是技术高手也挡不住法律后果。最后说一个我个人的经验心得代码修复这件事真正难的不是技术操作本身而是对程序运行过程的理解程度。很多时候你卡在一个问题上周转不过去不是因为不知道某个工具怎么用而是因为没有真正想清楚程序在那一刻、那个状态下到底在做什么、为什么这么做。回过头去把动态行为摸透很多看似玄学的问题答案自己就浮出来了。这也是我一直建议“先动态建基线、再动手修”的根本原因。以后再做VMP相关的修复工作别急着把Dump工具先拉出来跑。抽十五分钟先把程序跑一遍看它到底怎么动、怎么走、怎么校验再回来做修复你会发现后面全程顺得多。这套逻辑不止适用于VMP换到其他壳和混淆器上也同样成得起来。
RELATED

相关推荐

智能社区服务小程序源码跑通与差异化改造:从能跑到能交差

智能社区服务小程序源码跑通与差异化改造:从能跑到能交差

简介:这是一套面向计算机、电子信息工程、数学等专业学生的智能社区服务小程序源码,适合正在做毕业设计、课程设计或期末大作业的学习者进行项目实战练习。项目经导师指导并认可,获得98分评价,代码经过严格调试,可直接…

📅 2026/10/22 16:18:05
Lerna多包仓库管理实战:从架构设计到CI发布全流程

Lerna多包仓库管理实战:从架构设计到CI发布全流程

1. 多包仓库的痛点与 lerna 的定位前端项目一旦超过三个,代码复用就会变成一件让人头疼的事。你可能经历过这样的场景:A 项目里写了一个工具函数,B 项目想用,复制粘贴过去之后,A 改了 B 忘了改,线上跑出两个…

📅 2026/10/22 16:18:05
MySQL常用内置函数详解:日期、字符串、数学与流程控制实战

MySQL常用内置函数详解:日期、字符串、数学与流程控制实战

写SQL写多了你就会发现,真正决定你效率高低的,很多时候不是多复杂的查询逻辑,而是你对内置函数熟不熟。就拿日期来说,业务表里存的是2024-03-15 14:30:22这种完整时间戳,可老板要看的报表按天汇总就行,你怎…

📅 2026/10/22 16:18:05
MORE NEWS

更多资讯

📰

深入解析Qwen3-30B-A3B-Instruct-2507-quantized.w8a8的INT8量化配置:为什么gate和lm_head不能量化?

【免费下载链接】Qwen3-30B-A3B-Instruct-2507-quantized.w8a8 项目地址: https://ai.gitcode.com/hf_mirrors/vllm-ascend/Qwen3-30B-A3B-Instruct-2507-quantized.w8a8 点击查看 免费下载 Qwen3-30B-A3B-Instruct-2507-quantized.w8a8 是 Qwen3-30B-A3B MoE 大模…

📰

Llama-3.2-1B-Instruct_rai_1.8.0_npu_4K 商用合规指南:MIT 许可与 Llama USE_POLICY 该怎么看?

【免费下载链接】Llama-3.2-1B-Instruct_rai_1.8.0_npu_4K 项目地址: https://ai.gitcode.com/hf_mirrors/amd/Llama-3.2-1B-Instruct_rai_1.8.0_npu_4K 点击查看 免费下载 Llama-3.2-1B-Instruct_rai_1.8.0_npu_4K 是 AMD 为 Ryzen AI NPU 优化的 1B 指令模型&…

📰

如何找到前10个客户:codex-startup-pressure-test-skill 的 first-10-customers 模式实操(Do Things That Don‘t Scale)

【免费下载链接】codex-startup-pressure-test-skill 项目地址: https://gitcode.com/gh_mirrors/co/codex-startup-pressure-test-skill 点击查看 免费下载 创业想法最难的一步不是写代码,而是找到前 10 个客户。本文带你实操 codex-startup-pressure-…

📰

C语言:指针的理解

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

📰

JAVA:基本数据类型详解

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

📰

PIC18F4515与PJ85718DM协同实现工业温控联网方案

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

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬