CTF逆向实战:用IDA快速定位主函数与加密逻辑
拿到一个CTF Reverse题目很多新手的第一反应是“我该从哪里开始”。之前两篇我们讲了基础工具和汇编入门这一篇直接上实战——用IDA打开一个陌生二进制文件怎么快速找到主函数怎么把加密逻辑从大段汇编里挖出来。这套流程我在课上和实际比赛中反复验证过按下面的顺序走大部分常规题型十分钟内就能理出思路。先说清楚适用范围本篇主要针对Linux ELF和Windows PE格式的常见C/C编译产物不涉及C异常处理、混淆壳、虚拟化保护这些极端情况。但即使你遇到的是加固样本前半程的定位方法也完全适用——先找到主函数才能判断后面要不要调起动态分析。1. 拿到题目文件后的第一件事识别文件类型与加壳情况很多人拿到一个reverse文件直接拖进IDA然后对着汇编发懵。我建议先花三十秒做体检不然IDA可能加载解析错误或者你后面怎么看都别扭。1.1 文件类型判断Linux ELF、Windows PE、还是其他先在Linux终端里跑一下file命令Windows上可以用交叉工具链或者直接把后缀名当作线索file challenge输出类似ELF 64-bit LSB executable, x86-64, dynamically linked, not stripped或者PE32 executable (console) x86-64。这个信息决定了你接下来用IDA的哪个加载器以及是否需要考虑系统调用差异。判断文件类型不只是看格式还要留意静态链接还是动态链接strip没strip。动态链接的话函数名和库函数标记会保留方便你定位main静态链接则所有代码都揉在一个二进制里函数名少得可怜定位难度上升一个级别。strip过的文件丢掉了符号表IDA里函数名会变成sub_401000这种但通过字符串引用和交叉引用依然能找回主流程。1.2 查壳使用工具确认是否加壳决定后续静态分析策略加壳的手法和混淆不太一样壳至少会改变入口点、加密原始代码段IDA直接加载只能看到壳的启动代码完全看不到真实的程序逻辑。我用得最多的是 Detect It EasyDIE它跨平台识别速度快对常见编译器特征和加壳特征覆盖面都很广。命令行模式diec challengeWindows下也可以用Exeinfo PE会给出更详细的壳类型提示。如果是UPX、ASPack这类压缩壳优先尝试脱壳。UPX可以直接upx -d challenge一键还原但如果壳版本被改过或加了反脱壳校验脱完壳运行不了这时可以考虑内存转储或者手动ESP定律脱壳。1.3 查壳的附加意义识别编译器类型方便后续过滤库函数DIE不仅能报壳还能识别出是GCC、Clang还是MSVC编译。GCC和Clang的启动代码路径很相似MSVC则有自己的入口包装。知道编译器之后后面“跳过库函数找main”这步会快很多。注意静态编译的Go、Rust二进制函数名风格完全不同千万别用C语言的套路去找。如果DIE报的是Go或者Rust建议单独换一套思路这一篇暂时不展开。2. 用IDA打开目标文件加载选项与视图选择确认没加壳之后就可以正式用IDA加载了。很多时候问题出在IDA加载时的一两个选项没选对后面分析就得绕远路。2.1 IDA加载流程主要选项说明打开IDA选择New拖入文件会出现加载对话框。对于ELF通常要选择“ELF for x86-64 (Linux)”PE同理。下方有“Load new segment”和“Kernel options”一般保持默认。比较关键的是“Processor type”和“Input file”加载方式。大部分情况IDA都能自动识别但如果你想把分析选项调成更激进的自动分析可以在“Analysis”里勾选“Kernel options1”中的“Try to resolve all unknown names”等选项。新手不建议乱动默认就够用。加载完成后IDA会生成.i64数据库文件。这个文件保存了你所有注释、重命名、标签下次直接打开它而不是原始文件。2.2 视图模式反汇编视图、伪代码视图F5的基本使用IDA最常用的两个视图是IDA View-A和Hex-Rays伪代码窗口。按F5或者菜单View-Open subviews-Pseudocode就能把汇编转换成结构化的C风格伪代码。伪代码窗口的最大价值是帮你快速梳理逻辑。但必须明确伪代码不是真实源代码它只是IDA根据汇编推断出来的近似表达涉及变量类型、数组边界、指针运算都有可能是错的。所以伪代码适合“看懂大致流程”具体算法细节还是要回到汇编确认。在伪代码窗口里你可以双击变量名或函数名跳转定义按X查看交叉引用。这些快捷键一定要熟练后面每一步都在用。2.3 常见问题IDA不识别函数、跳转表等怎么处理有些代码由编译器优化成跳转表switch语句的大型case构建IDA初次分析可能只显示一串边界跳转没有解析成函数逻辑。这种情况先选中跳转表所在区域按P手动创建函数或者用AltP编辑函数属性强制指定。还有一类坑是IDA把主函数识别成了库函数导致你按F5显示的是库代码。解决方法是点函数名按AltP修改函数特征取消勾选“Library function”再按F5重新生成伪代码。3. 快速定位主函数几条实用的入口路径主函数是所有逻辑的源头。找到它你就有了一个稳定的坐标顺着它往下翻阅所有调用。下面几条路径按推荐顺序排。3.1 认识启动函数与库函数标志从入口点进入main的常规路径对于GCC编译的动态链接程序入口点在_start它调用__libc_start_main传入真正的main函数地址。在IDA里双击_start进入入口代码往下找__libc_start_main那一行调用它通常是call ___libc_start_main在调用之前的参数设置里lea rdi, main或者mov rdi, offset main就是主函数地址。直接双击跳转过去。用这个方式定位main的准确率接近百分之百前提是程序是C/C编译且动态链接。如果是静态链接__libc_start_main也会被编译进去但名称可能保留你可通过搜索main符号如果有或者用下一条字符串引用法。3.2 根据字符串交叉引用定位主函数绝大多数CTF题目会在主函数附近打印提示信息比如“Input your flag:”、“Wrong! Try again!!”等。这是最直接的线索。按ShiftF12打开Strings窗口找到可疑提示字符串双击进入。这时IDA会跳转到该字符串所在的数据区域再按X查看交叉引用就会跳到引用这个字符串的代码位置。如果引用处在一个函数内部向左看函数起始地址这就是关键函数往往就是main或者main调用的子函数。这个方法对你快速判断逻辑所在有奇效。实际题目里字符串“Congratulations”这种就是胜利出口顺着交叉引用往回走就能定位到校验和比较点再逆着数据流向上追踪加密过程。3.3 根据函数列表筛选识别编译器模板函数缩小范围如果文件被strip过函数名全是sub_...几百个函数排在那里。怎么快速锁定候选函数首先是看函数图按空格键切换Graph Overview那些有较大基本块、引用了大量字符串、调用密集的函数往往就是核心。然后按CtrlF搜索函数名或者用AltB搜索二进制特征比如一串固定的比较指令。另一个技巧是看函数的大小。一个典型main函数通常几百字节而加密逻辑所在函数可能几十到几百行反汇编。在Functions窗口按size排序排除掉那些只有几个字节的小函数剩下的中等长度函数重点看。3.4 动态调试确认主函数附加到进程或设置断点静态定位偶尔会有偏差尤其是存在内联函数时主函数边界会被优化打乱。这时直接用调试器验证最靠谱。在IDA里按F9运行调试或者选择菜单Debugger-Start process。如果程序在等待输入暂停时调用栈最外层栈帧就是main。更简单的办法用gdb启动在输入提示处CtrlC中断然后bt查看调用栈栈顶往下的第一个非库函数就是main。动态调试还能直接看到明文输入和加密后的中间值这对后文定位加密逻辑帮助极大。4. 定位加密逻辑的核心思路从数据流与特征出发找到主函数只是第一步真正的难点在于从主函数里分辨出哪个函数在加密、哪个函数只是处理格式、哪个函数执行比较。4.1 先找输入点和输出点scanf、read、printf等常见I/O函数在主函数伪代码中通常会有明显的输入调用scanf(%s, buf)或者read(0, buf, n)。这是数据流的起点。同时找输出函数printf(correct)或puts(flag)是校验成功的终点。输入点到输出点之间经过的所有数据变换都可能是加密算法。先从输入点开始逐函数追踪缓冲区变量的使用情况。举例如果伪代码里是scanf(%s, input); for ( i 0; i strlen(input); i ) input[i] ^ key[i % 8]; if ( strcmp(input, expected) 0 ) puts(Correct);那加密逻辑就在主函数内部。如果加密被抽到子函数sub_401100(input)那你就需要进入那个子函数逆向。4.2 追踪数据流转从输入缓冲区到比较函数之间的处理过程在伪代码里变量input在scanf之后传给谁每个被调用函数都做了什么这是核心脉络。建议把每个处理步骤用注释记录下来比如“这里异或了0x22”、“这里是换表”等。比较常出现在两个位置直接strcmp(input, encoded_string)比较说明前面是单向加密加密结果与内置常量比对。通过函数遍历字节逐位比较例如for (i0;ilen;i) if (input[i] ! enc[i]) break;这种。不管是哪种最后一步通常都是与一段硬编码字节比对。那串硬编码字节就是密文输入经过逆算法还原后要与它一致才能通过校验。4.3 识别加密/压缩特征函数常见AES、XOR、TEA、RC4等特征CTF Reverse里最常见的加密算法就那么几种看到特征就能快速确认XOR加密伪代码中大量出现^运算密钥可以是常量、固定数组或动态生成。识别出XOR后只需要找到密钥和密文即可完成大部分推导。TEA/XTEA特征是一堆移位、、加减常量0x9E3779B9。看到这个常量直接能确定是TEA系列。RC4函数中有S盒初始化常见有256字节的置换数组以及两轮循环交换。AES特征明显有S盒一个256字节的常量表以及多轮addroundkey、subbytes、mixcolumns。不过不要一上来就往这上面靠。CTF里很多出题人会魔改原算法比如修改XOR密钥生成方式、调换TEA轮数等你需要结合具体代码确认。特征识别只是帮你从“完全陌生”缩小到“有方向”。4.4 字符串和常量识别密钥、S盒、魔数的提示在字符串窗口除了提示语句还会出现Base64表、AES S盒、RC4 S盒等一副常量数组。如果看到一串明显有序的可见字符ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/那就是Base64的字符表。类似的还有十六进制查找表、凯撒密码表。一个技巧在IDA中选中一段数据按A强制转为字符串按H转为十六进制显示。有些算法会把关键常量放在.rodata段双击地址跳到数据区域就能看到完整字节。5. 实战演练一个典型的CTF逆向题完整分析理论部分说完来做一个简化但真实的例子。假设题目是一个64位Linux ELF运行后要求输入一段字符串输出“Wrong”或“Correct”。5.1 题目描述与elf文件加载文件名为simplere无壳动态链接未strip。加载到IDA入口在_start自动定位后我们直接看main函数。按ShiftF12打开字符串窗口看到Please input:、Correct!、Try again。双击Please input:按X跳到引用它的函数这个函数正是main。5.2 找主函数通过字符串引用快速到达跳转到main后F5得到伪代码大致如下int __cdecl main(int argc, const char **argv, const char **envp) { char v4[32]; unsigned int v5; unsigned int v6; int result; ... puts(Please input:); scanf(%s, v4); v5 strlen(v4); if ( v5 0 ) goto LABEL_fail; v6 0; while ( v6 v5 ) { v4[v6] ^ 0x33; v6; } if ( strcmp(v4, eq][?aP\\[VW_) 0 ) puts(Correct!); else puts(Try again); ... }伪代码里没有显示返回部分但不影响理解输入字符串存到v4每个字节与0x33异或再与内置字符串比较。只要找到密文逆运算就是答案。5.3 定位加密逻辑逐步分析伪代码在这个例子中加密逻辑就是XOR循环但很多题目会封装成函数。假设上面的循环被提取为__int64 __fastcall enc(char *a1, size_t len) { int i; for (i 0; i len; i) a1[i] ^ key[i % 4]; return 0; }那你要跳到enc函数体里看key的值。在伪代码窗口双击key跳到数据定义看它的每个字节。例如key[] {0x31, 0x32, 0x33, 0x34}。然后拿到内置密文这里密文是可见字符串eq][?aP\\[VW_的原始字节。为了准确到数据窗口看它的十六进制例如65 71 5d 5b 3f 61 50 5c 5b 56 57 5f如果key是四字节循环那么还原时逐字节异或65^3154 - T 71^3243 - C 5d^336e - n 5b^346f - o ...最终得到TClike_Flag之类的结果。实际CTF题目中得到的字符串就是flag。5.4 还原算法并求flag如果算法是XOR且异或两次等于原值直接写个Python脚本跑一遍flag enc bytes.fromhex(65715d5b3f61505c5b56575f.replace( , )) key bytes([0x31, 0x32, 0x33, 0x34]) for i, b in enumerate(enc): flag chr(b ^ key[i % len(key)]) print(flag)输出即为答案。如果是TEA或RC4这种需要解密算法那就根据密钥、密文调用现成的标准库脚本跑出来。这就是逆向题的基本闭环输入进判断出口逆向处理得到通解。6. 常见问题与排查技巧实录这部分内容都是我在实际做题时踩过的坑整理成速查按需查看。6.1 遇到代码混淆怎么办有符号混淆和花指令两种常见情况。符号混淆指的是把函数名改成sub_A、encrypt_xxx等无意义名称这不影响逻辑你只要跟着调用关系走即可。花指令是在正常代码之间插入垃圾字节让IDA反汇编错位导致识别出多条假指令。处理方式找到花指令跳转点选中垃圾字节按U取消定义然后从正确地址按C重新建立代码。这个方法有一定的经验要求实在不行就用动态调试单步执行跳过花指令区域。6.2 伪代码显示不完整或错误原因可能是IDA对某些变长参数调用识别错误导致函数帧大小计算错误局部变量显示乱。尝试在函数头处按AltP编辑函数参数个数和调用约定设置为__fastcall或__cdecl。删掉分析缓存重新加载File-Load file-Reuse existing database。如果某个变量显示为_BYTE或char但是实际应该是数组右键变量选择“Force array of N...”手动指定。6.3 动态调试下断点无效的排查在Linux下调试时有时候IDA的调试器连不上或者下断点没反应。先确认是不是IDA远程调试配置的问题对于本地ELF文件直接选择Local debugger。断点无效的另一个常见原因是程序内部有多线程你断在主线程但比较逻辑在其他线程。遇到这种情况直接在可疑函数入口下断点而不是在外层下断点。还有一种是程序使用了反调试会检测ptrace导致调试器崩掉。碰到反调试可以先静态分析在伪代码里定位到ptrace调用把它直接patch成nop再用调试器加载。6.4 快捷键与效率技巧备忘这里列几个我常用的快捷键关键时刻能省很多时间F5转伪代码X查看交叉引用ShiftF12打开字符串窗口N重命名变量/函数强烈建议用每看明白一个函数就立刻改名;添加注释G跳转到指定地址Space切换流程图视图与反汇编视图CtrlAltT创建结构体遇到复杂结构时体验极佳U取消定义C定义代码A定义字符串K在伪代码中指定变量类型我的习惯是每分析完一个函数就按N给它改成语义化名称比如encrypt_data、check_flag然后在函数头部用分号写下功能说明。这样分析到后面时函数列表就是你自己的逻辑地图再也不用翻来翻去。结尾一点个人建议练逆向没有捷径先把上面这套流程在十道经典题上各跑三遍直到不再需要思考就能自然而然地“字符串-引用-伪代码-追踪数据流-定位算法”你就可以开始接触更复杂的加固样本和花指令。那类题目依然用这套基础打底只是额外多一步环境对抗。我个人体会是很多新手卡住不是因为不会IDA而是因为急躁想在五分钟内拿到flag。逆向更像破案线索一条条拼字段一个个还原等你愿意盯着数据逐字节核查时你会发现自己突然能看懂很多之前觉得困难的题。最后分享一个实用小技巧遇到不确定的加密流程先用数据交叉引用把输入和输出对应的缓冲区标出来再在两个缓冲区之间画一条“数据流线”你沿着这条线一个函数一个函数往下走加密逻辑自己就会浮出来。这个习惯帮我破解了不少硬骨头希望也能帮到你。

相关新闻

CTF逆向入门:汇编基础与编译流程是打通任督二脉的关键

CTF逆向入门:汇编基础与编译流程是打通任督二脉的关键

不少刚接触CTF Reverse的朋友,一上来就急着刷题、装IDA,结果看了半天反汇编窗口,除了push ebp和mov ebp, esp能混个眼熟,剩下的全是一脸懵。这篇系列分享,我特意把基础前置内容单独拆出来讲,是想说清楚一个…

2026/10/18 14:11:22 阅读更多 →
Big Sur 下 Android 模拟器无法启动?排查与修复全指南

Big Sur 下 Android 模拟器无法启动?排查与修复全指南

开头不用加了元信息,直接从标题内容开始。我需要确保全文有多个H2部分,每个部分有足够的深度,且语言自然生动,像资深开发者在分享踩坑经历。注意标题层级:不要跳级,H2编号,H3编号。内容要够5000…

2026/10/18 14:11:22 阅读更多 →
Java面试转型:JVM内存分析、并发锁降级与云原生调优实战

Java面试转型:JVM内存分析、并发锁降级与云原生调优实战

简介:这是一份专为Java中初级至中高级研发人员(1–5年经验)打造的2026大厂面试系统性备战指南,直击校招/社招中「死记硬背失效、源码追问卡壳、新趋势无准备」三大痛点。资源以PDF形式交付,共1个文件,大小1…

2026/10/18 14:11:22 阅读更多 →

最新新闻

Reflector 7.4.1.179 绿色注册版部署指南:插件集成与避坑实践

Reflector 7.4.1.179 绿色注册版部署指南:插件集成与避坑实践

简介:这是一份面向.NET开发者的反编译工具整合包,主角是经典的.NET Reflector 7.4.1.179绿色注册版,并真正集成了FileDisassembler与FileGenerator两款广受好评的插件,解决原版只能逐个查看方法、无法批量导出源码的痛点。包内共2…

2026/10/18 14:58:53 阅读更多 →
Vue权限控制实战:路由、菜单与按钮级权限完整链路

Vue权限控制实战:路由、菜单与按钮级权限完整链路

简介:这份资源面向正在开发后台管理系统的Vue前端开发者,聚焦权限控制这一核心场景,帮助解决不同角色用户只能访问被授权功能的问题。内容围绕两种主流限制方式展开:一是路由不可见,借助Vue Router的meta字段存储权限列…

2026/10/18 14:58:53 阅读更多 →
建筑施工安全技术资料如何变成项目台账?链路拆解与归档实操

建筑施工安全技术资料如何变成项目台账?链路拆解与归档实操

简介:建筑施工安全技术资料是现场安全管理的底账,而非应付检查的废纸。其核心原理是把方案、交底、检查、验收、整改五个环节串成管理链路,再按塔吊、基坑、临电等对象建立检索维度,形成“链路对象”的矩阵台账。通过统一编号、逐…

2026/10/18 14:58:53 阅读更多 →
YOLOv8骨干替换EfficientNetV2:均质架构提升小目标检测与推理性能

YOLOv8骨干替换EfficientNetV2:均质架构提升小目标检测与推理性能

1. 改造动机与方案选型1.1 YOLOv8原有骨干的瓶颈在哪里动手换主干之前,我先把YOLOv8原生CSPDarknet53的短板列了一遍。YOLOv8默认的backbone在COCO这类通用检测任务上确实够用,但一旦碰到分辨率高、小目标密集或纹理细节要求高的场景,它的问题…

2026/10/18 14:58:53 阅读更多 →
STM32F407 DCMI图像采集丢帧错位的硬件时序真相

STM32F407 DCMI图像采集丢帧错位的硬件时序真相

简介:本资源是一份基于STM32F407微控制器的高速并行12位ADC与DCMI数字摄像头接口协同开发实践方案,面向嵌入式初学者及图像采集方向进阶开发者,解决模拟信号高速采样与图像数据流同步传输的核心工程问题,适用于工业检测、智能视觉…

2026/10/18 14:58:53 阅读更多 →
《华为团队工作法》华为人才管理和薪酬激励:构建四位一体点燃员工热情的激励体系

《华为团队工作法》华为人才管理和薪酬激励:构建四位一体点燃员工热情的激励体系

本 115 页 PPT 适配企业人才管理、薪酬激励、干部管理类咨询方案与 HR 培训课件编制。基于华为人才管理体系核心实践,系统拆解人才选配、评估、干部任用、人才成长、文化锻造、薪酬激励、全面激励、健康成长八大模块。涵盖 STAR 行为面试、人才搭配四宫格、绩效能力…

2026/10/18 14:57:52 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 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/18 0:55:02 阅读更多 →
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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →