Python逆向工程入门:从PyInstaller打包EXE到源码还原实战指南
1. 从“黑盒”到“白盒”逆向工程到底是什么如果你刚接触编程或者只是用Python写过一些脚本听到“逆向工程”这个词可能会觉得有点神秘甚至有点“黑客”的感觉。其实没那么玄乎你可以把它想象成“拆解一个已经组装好的乐高模型”。别人给了你一个成品你不知道它是怎么拼出来的手头也没有说明书。逆向工程要做的就是把这个成品一点点拆开研究每个零件的形状、颜色以及它们是如何咬合在一起的最终目的可能是为了理解它的结构、修复一个损坏的零件或者用同样的零件自己重新拼一个。在Python的世界里这个“成品”通常是一个打包好的可执行文件比如.exe或者是一堆你看不懂的.pyc字节码文件。而“说明书”就是原始的、可读的Python源代码。逆向的目标就是从这些“成品”中尽可能还原出那份“说明书”。这个过程对于安全研究人员分析恶意软件、开发者调试没有源码的第三方库、或者学习他人优秀的代码封装技巧都至关重要。我刚开始接触这块时也走了不少弯路比如对着一个用PyInstaller打包的单文件exe发愁不知道从哪里下手。这篇内容就是把我这些年从纯小白摸索过来的一套基础流程和踩过的坑系统地梳理给你。我们不涉及任何灰色或非法领域纯粹从技术学习和研究的角度出发掌握这项“拆解”技能。2. 逆向目标识别你面对的是什么“打包方式”动手之前第一件事不是急着找工具而是先搞清楚你要逆向的“目标”是什么形态。Python程序分发的常见形式决定了你后续要采用的工具链和步骤这一步判断错了后面会全盘皆输。2.1 最常见的形态PyInstaller打包的EXE这是目前Windows平台上分发Python独立应用最主流的方式。开发者用PyInstaller或Py2exe、cx_Freeze等工具将Python解释器、依赖库和你的脚本一起打包成一个或几个可执行文件。用户双击就能运行无需安装Python环境。如何识别文件图标很多PyInstaller打包的EXE会使用一个默认的、有点像三条斜杠的图标如果你搜索“pyinstaller默认图标”就能看到。当然开发者可以自定义图标所以这只是一个线索。使用工具探测最可靠的方法是使用Detect It Easy简称DiE或PEiD这类PE文件识别工具。把它们拖进去如果显示“PyInstaller”或“Python”相关字样那就没跑了。字符串分析用文本编辑器如Notepad的十六进制模式或者Strings工具提取文件中的所有可读字符串。如果能看到大量像pyi-windows-manifest-filename、PyInstaller、.pyc等字样基本可以确定。注意PyInstaller有两种打包模式--onefile单文件和默认的--onedir单目录。单文件模式在运行时会将所有内容解压到临时目录这给逆向增加了提取的步骤但核心原理相同。2.2 另一种形态.pyc字节码文件有时你拿到手的不是EXE而是一些.pyc或.pyo文件。这是Python解释器将.py源文件编译后生成的字节码文件。它比源代码抽象但比机器码高级包含了Python虚拟机PVM可以执行的指令。在Linux/Unix环境下分发的Python程序或者某些“加密”后的Python代码其实只是编译成了字节码你可能会直接遇到它们。如何识别文件扩展名就是.pyc或.pyo。文件头部通常有固定的魔数Magic Number用于标识生成该字节码的Python版本。例如Python 3.7的魔数开头是0x420d0d0a。这个信息对后续反编译至关重要因为不同版本的字节码结构有差异。2.3 其他形态与混淆除了以上两种还有更复杂的情况打包进安装程序有些商业软件会用Inno Setup、NSIS等安装程序将Python程序包进去你需要先运行安装程序或直接解压安装包才能找到核心的EXE或PYC文件。代码混淆Obfuscation开发者会使用工具对代码进行混淆比如变量名替换成无意义的字符、插入垃圾代码、控制流扁平化等增加逆向难度。这在一些所谓的“外挂”或“破解补丁”中很常见。嵌套与加密如网络热词中提到的“vt嵌套架构”可能指程序使用了多层虚拟机或壳如VMP, Themida进行保护这属于非常高级的逆向领域远超小白入门范畴我们这里不展开。对于纯小白我们优先攻克前两种最普遍的情况。假设我们现在面对的是一个典型的、由PyInstaller打包的单文件EXE。3. 逆向第一步解包与资源提取拿到一个PyInstaller打包的EXE我们的第一个目标不是直接看到Python代码而是要把这个“压缩包”里的东西解压出来。关键是要找到打包进去的.pyc文件。3.1 工具准备pyinstxtractor这里首推的工具是pyinstxtractor。它是一个Python脚本专门用于解包PyInstaller生成的可执行文件。你不需要安装直接下载pyinstxtractor.py脚本即可。操作步骤确保你的电脑上安装了Python环境这是必须的。下载pyinstxtractor.py脚本。打开命令行CMD或PowerShell导航到你的EXE文件和pyinstxtractor.py所在的目录。执行命令python pyinstxtractor.py 你的程序名.exe执行后会发生什么脚本会运行并在当前目录生成一个名为你的程序名.exe_extracted的文件夹。这个文件夹就是解包后的内容。3.2 分析解包目录寻找关键文件进入解包后的文件夹你会看到很多文件。其中对我们最重要的有几个PYZ-00.pyz这是一个ZIP格式的归档文件里面包含了程序依赖的所有第三方库site-packages里的内容编译后的.pyc文件。你可以用任何ZIP解压工具如7-Zip打开它。程序名没有扩展名这是一个与你的EXE同名的文件注意不是.exe。这个文件极其重要它包含了你的主脚本以及可能被打包在一起的其他自定义模块编译后的字节码。它的结构比较特殊。其他一些.dll、.pyd文件是Python解释器和C扩展模块。一个关键的坑文件头缺失直接使用反编译工具如uncompyle6去处理解包出来的.pyc文件包括从PYZ里解压出来的和那个同名无扩展名文件很可能会失败报错“magic number mismatch”或“decompilation failed”。这是因为PyInstaller为了节省空间和加快加载在打包时移除了.pyc文件的标准头部信息包括魔数和时间戳。标准的.pyc文件结构是[4字节魔数] [4字节时间戳] [字节码数据]。 而PyInstaller打包后的.pyc只保留了[字节码数据]部分。所以我们需要进行下一步修复文件头。4. 核心环节修复与反编译.pyc文件这是将字节码还原为可读源代码的关键一步。4.1 修复.pyc文件头我们需要手动为提取出来的、没有头的.pyc文件补上正确的文件头。这需要两个信息魔数Magic Number对应生成此字节码的Python版本。时间戳Timestamp通常可以设为0或者从其他渠道获取有时可以从EXE文件的编译时间推断或者找一个同版本Python生成的任意.pyc文件借用其时间戳。如何确定魔数从EXE中寻找在解包目录里有时会有一个名为struct的文件。用十六进制编辑器打开它文件开头可能就是魔数。从PYZ归档中寻找解压PYZ-00.pyz里面库文件的.pyc可能保留了完整的头不一定取决于PyInstaller版本和选项。找一个有头的文件用十六进制编辑器查看前4个字节。经验猜测根据程序发布的大致时间猜测其使用的Python版本如2020年后多为Python 3.7。然后可以创建一个同版本Python的虚拟环境随便写个test.py运行后生成test.pyc用十六进制编辑器查看其魔数。使用工具辅助有一些脚本可以尝试自动识别和修复但手动操作更可靠。手动修复步骤以主程序文件为例假设我们解包后得到的主程序文件叫myapp无扩展名我们确定其Python版本为3.8魔数为0x550d0d0a。用十六进制编辑器如HxD,010 Editor打开myapp。在文件的最开头插入8个字节。前4个字节是魔数0x550d0d0a注意字节序通常是小端序在编辑器中可能显示为0A 0D 0D 55后4个字节是时间戳可以全部填0x00。将文件另存为myapp.pyc。对于从PYZ里解压出来的库文件.pyc如果反编译失败也需要进行同样的头修复操作。4.2 使用反编译工具还原源代码文件头修复后我们就可以使用反编译工具了。目前最主流、最活跃的工具是uncompyle6。安装与使用pip install uncompyle6反编译一个文件uncompyle6 -o . myapp.pyc这个命令会将myapp.pyc反编译后的源代码输出到当前目录文件名通常是myapp.py。 如果你想直接输出到终端查看uncompyle6 myapp.pyc可能遇到的问题与解决版本不匹配uncompyle6对高版本Python如3.9的字节码支持可能不完善。如果失败可以尝试使用decompyle3uncompyle6的一个分支持续更新或pycdc。反编译结果混乱如果代码被混淆过反编译出来的变量名会是a,b,c1这种无意义的名字函数逻辑也可能被拆得支离破碎。这时就需要你结合上下文进行人工分析和重命名考验的是代码阅读和理解能力。部分文件反编译失败有些.pyc文件可能因为优化级别.pyo或其他原因无法完美反编译。你可以尝试用dis模块反汇编字节码来辅助分析import dis, marshal with open(failed_file.pyc, rb) as f: f.read(8) # 跳过我们刚加的文件头 code marshal.load(f) dis.dis(code)dis.dis会输出人类可读性稍差但信息完整的字节码指令有助于理解程序逻辑。5. 静态分析与动态调试让代码“跑起来”看成功反编译出源代码哪怕是混淆后的逆向工作只完成了一半。阅读静态代码往往很吃力尤其是逻辑复杂的程序。这时就需要让程序“动起来”在关键点观察它的行为。5.1 静态字符串与常量分析在反编译出的代码中搜索所有的字符串常量。这常常能发现URL、API接口用于网络请求。密钥、密码硬编码虽然不安全但一些简单程序里确实存在。调试信息、日志标签帮助你理解程序的功能模块。错误信息可以帮你理解某些函数或条件判断的用途。用文本编辑器的全局搜索功能查找http://、https://、password、key、token等关键词。5.2 动态调试Debugging这是逆向工程中非常强大的一环。我们的目标是将解包、修复并反编译后的代码在受控的环境中重新运行和调试。步骤环境重建根据反编译代码中显示的import语句使用pip安装所有依赖库。注意版本尽量与程序原本的环境一致。代码修补反编译的代码可能缺少一些运行必要的元素如__main__入口或者存在一些因反编译不完美导致的小语法错误。你需要像一个医生一样仔细阅读错误信息对其进行最小化的修补使其能够运行。这可能包括修正缩进、补全缺失的括号、注释掉一些非核心的检查代码等。使用调试器使用Python自带的pdb或者更强大的IDE如PyCharm、VSCode的图形化调试功能。设置断点在你认为关键的函数入口、条件判断、循环处设置断点。单步执行一行一行地执行代码观察每一步之后变量的值如何变化。观察变量查看局部变量和全局变量的值特别是那些从用户输入、文件或网络获取的数据是如何被处理的。堆栈查看当程序调用多层函数时查看调用堆栈理解执行流程。一个实用技巧打日志Print Debugging如果调试器用起来不顺手或者程序有反调试机制最朴素的print()大法依然有效。在关键函数里插入print语句输出传入的参数、中间计算结果、函数返回值。重新运行程序通过日志来追踪程序流和数据流。这对于分析算法逻辑比如热词中提到的“山姆 逆向 md5”特别有效你可以看到原始数据是如何一步步变成最终的MD5哈希值的。5.3 处理常见的保护与干扰反调试程序可能会检测是否被调试如检查sys.gettrace()如果发现就改变行为或直接退出。应对方法包括修改程序代码绕过检测或者使用更底层的调试器如x64dbg对EXE进行调试。代码流混淆大量的while循环、switch-case和goto通过if模拟使得代码逻辑跳转极其混乱。这需要极大的耐心配合动态调试记录下每个分支的实际执行路径慢慢理清。字符串加密程序中的字符串可能不是明文而是在运行时通过一个解密函数动态生成的。你需要在动态调试时在这个解密函数执行完后查看其返回值或者直接修改代码将解密后的结果打印出来。6. 逆向实战一个简单的模拟案例为了把上面的流程串起来我们模拟一个极其简单的场景。假设我们有一个用Python 3.7编写并通过PyInstaller打包成单文件EXE的小程序secret_calc.exe。它的功能是输入一个名字输出一段特殊的计算后的字符串。我们的目标找出它的计算逻辑。步骤演练识别目标使用DiE工具查看secret_calc.exe显示为PyInstaller打包。解包使用python pyinstxtractor.py secret_calc.exe得到secret_calc.exe_extracted文件夹。寻找主字节码在解包目录中找到secret_calc文件无扩展名。修复文件头我们知道它是Python 3.7程序。查表或自己生成一个3.7的.pyc文件得知魔数为0x420d0d0a。用HxD打开secret_calc在文件开头插入8字节0A 0D 0D 42 00 00 00 00小端序魔数零时间戳另存为secret_calc.pyc。反编译运行uncompyle6 -o . secret_calc.pyc得到secret_calc.py。分析代码打开secret_calc.py发现核心函数如下已去混淆def process_name(name): key 0xAB result [] for char in name: result.append(chr(ord(char) ^ key)) key (key 1) 0xFF return .join(result) if __name__ __main__: user_input input(Enter your name: ) secret process_name(user_input) print(fYour secret code is: {secret})理解逻辑静态阅读可知这是一个简单的异或加密。它对输入的每个字符与一个初始值为0xAB的密钥进行异或并且每处理一个字符密钥就自增1并限制在0-255范围内。动态验证我们可以直接修改这个Python脚本或者用交互式Python环境调用process_name函数输入不同的值验证我们的理解是否正确。例如process_name(‘A’)会输出一个特定字符。任务完成我们成功逆向出了程序的“核心算法”。原来它所谓的“特殊计算”就是一个可预测的流异或加密。这个案例非常简单但完整走通了从EXE到源代码再到理解逻辑的闭环。真实世界的程序会比这复杂成千上万倍可能涉及网络通信、图形界面、多线程、复杂的加密算法等但基础流程是相通的解包 - 提取并修复字节码 - 反编译 - 静态分析 - 动态调试 - 理解逻辑。7. 工具链、资源与学习路径工欲善其事必先利其器。除了上面提到的核心工具一个顺手的逆向环境能事半功倍。基础工具清单Python环境必备。建议使用Anaconda或Miniconda管理多个Python版本以应对不同版本字节码。文本/十六进制编辑器Notepad、Sublime Text、VSCode用于看代码HxD、010 Editor用于二进制文件分析。查壳/识别工具Detect It Easy (DiE)快速识别文件类型和打包器。反编译工具uncompyle6/decompyle3/pycdc各有优劣可以都备着。Python调试器pdb命令行或PyCharm/VSCode图形化。打包文件分析7-Zip用于解压PYZ等归档文件。学习资源与社区官方文档PyInstaller、uncompyle6的官方文档或GitHub页面了解其特性和限制。逆向工程社区像看雪论坛、吾爱破解等安全社区有大量高质量的逆向实战帖可以从简单的CrackMe练起。Python字节码学习Pythondis模块的官方文档理解字节码指令集。这是阅读反汇编输出的基础。计算机基础了解基本的PE文件结构、汇编语言至少能看懂大概、加密算法常识这些知识会在你深入时提供巨大帮助。给纯小白的衷心建议逆向工程是一个需要极强耐心、细心和逻辑思维能力的领域。不要指望一蹴而就。从最简单的、自己用PyInstaller打包的小程序开始逆向因为你有源代码可以对照能最直观地看到“打包-解包-反编译”过程中发生了什么变化。然后去挑战一些专门为初学者设计的“CrackMe”程序。记住核心目的不是破解软件而是通过这个过程深刻理解程序是如何运行的编译器/解释器做了什么数据是如何流动和变化的。这个过程对你编写更健壮、更安全的代码有着不可估量的提升作用。我最开始的时候为了修复一个.pyc文件头折腾了一整天但当你第一次成功看到反编译出的源代码时那种豁然开朗的感觉是无与伦比的。

相关新闻

终极指南:D3KeyHelper - 暗黑3玩家的自动化宏工具完全教程

终极指南:D3KeyHelper - 暗黑3玩家的自动化宏工具完全教程

终极指南:D3KeyHelper - 暗黑3玩家的自动化宏工具完全教程 【免费下载链接】D3keyHelper D3KeyHelper是一个有图形界面,可自定义配置的暗黑3鼠标宏工具。 项目地址: https://gitcode.com/gh_mirrors/d3/D3keyHelper 还在为暗黑3中重复的技能按键感…

2026/9/25 11:48:39 阅读更多 →
CMAQ空气质量模型从零到一:安装配置、运行调试与结果分析全指南

CMAQ空气质量模型从零到一:安装配置、运行调试与结果分析全指南

1. 项目概述:从零到一掌握CMAQ如果你正在研究空气质量模拟,或者你的课题、项目需要评估污染物在大气中的传输、转化和沉降过程,那么CMAQ(Community Multiscale Air Quality)模型几乎是一个绕不开的工具。作为一个开源的…

2026/10/2 13:50:43 阅读更多 →
编译器优化屏障在多线程编程中的关键作用

编译器优化屏障在多线程编程中的关键作用

1. 编译器优化屏障的本质作用 编译器优化屏障(Compiler Memory Barrier)是编程中一个关键但常被忽视的概念。简单来说,它就像高速公路上的收费站,强制让所有车辆停下来重新排序后再放行。在代码执行过程中,现代编译器会…

2026/10/5 7:18:23 阅读更多 →

最新新闻

前端图片翻转避坑指南:CSS transform与canvas重绘的选型与实现

前端图片翻转避坑指南:CSS transform与canvas重绘的选型与实现

简介:面向网页前端初学者的图片翻转示例包,围绕 JavaScript 操作 CSS transform、事件监听与 DOM 样式切换展开,演示点击图片实现 180 度翻转并再次点击恢复原状的效果,适合学习交互设计与动效实现的开发者参考,也适合…

2026/10/10 15:44:24 阅读更多 →
MIPSsim实验报告精讲:指令语义、数据通路与三个汇编程序的避坑要点

MIPSsim实验报告精讲:指令语义、数据通路与三个汇编程序的避坑要点

简介:面向计算机系统结构课程学习的 MIPS 指令系统实验报告资源,适合高校本科生或自学处理器架构的初学者。报告基于 MIPSsim 模拟器,覆盖 MIPS 指令语义、寄存器与内存操作、64 位整数求补等典型实验场景,能帮助读者快速掌握 MIP…

2026/10/10 15:44:24 阅读更多 →
告别手改配置:CC Switch 让 Claude Code 模型切换像切输入法一样简单

告别手改配置:CC Switch 让 Claude Code 模型切换像切输入法一样简单

1. 手动改配置的痛苦,相信你也有过先说个场景,你应该也经历过:Claude Code 用得好好的,突然想试试某个新出的开源模型,或者团队里有人分享了一个调优过的模型服务地址,你第一反应是去翻~/.claude底下的配置…

2026/10/10 15:44:24 阅读更多 →
CodeIgniter 4 仓库开发规范完全指南:兼容性策略、编码标准与 FrankenPHP Worker Mode

CodeIgniter 4 仓库开发规范完全指南:兼容性策略、编码标准与 FrankenPHP Worker Mode

后端Web框架 【免费下载链接】CodeIgniter4 Open Source PHP Framework (originally from EllisLab) 项目地址: https://gitcode.com/gh_mirrors/co/CodeIgniter4 点击查看 免费下载 CodeIgniter 4 是一个成熟的 PHP 开源全栈框架,其生产代码位于 syste…

2026/10/10 15:44:23 阅读更多 →
编译原理实验核心:词法分析、语法分析与符号表实战指南

编译原理实验核心:词法分析、语法分析与符号表实战指南

简介:电子科技大学编译原理实验完整代码包,面向高校计算机专业学生及编译原理自学者,用于深入理解词法分析、语法分析、语义分析等核心概念,解决理论学习与实验实践脱节的问题,也是课程设计或期末实验的极佳参考。压缩…

2026/10/10 15:44:23 阅读更多 →
编译原理课程实验全流程解析:从词法分析到目标代码优化

编译原理课程实验全流程解析:从词法分析到目标代码优化

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

2026/10/10 15:43:22 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/10 10:38:42 阅读更多 →