RSA维纳攻击原理与CTF实战:小私钥d的连分数破解
1. 为什么维纳攻击是CTF选手绕不开的“第一道密码学墙”你刚打开一道CTF密码学题题目只给了一组RSA公钥n 0x...、e 65537再附上一句“flag已加密密文在此”。你兴冲冲跑去看解密脚本结果发现私钥d根本没给——这太正常了。但当你用常规方法尝试分解n失败后突然发现e特别大而n只有1024位不对等等……e65537明明是标准小指数怎么会卡住这时候如果题目还悄悄提示“d很小”或者你注意到e和n的比值异常高比如e n^(3/4)恭喜你已经站在维纳攻击Wieners Attack的入口了。维纳攻击不是什么高深莫测的量子算法它本质上是一次对RSA数学结构的“精准外科手术”当私钥d意外地被设得过小时即使n足够大、e看起来很标准整个RSA系统也会像被撬开的保险柜一样暴露在连分数展开面前。这不是理论推演而是CTF实战中真实高频出现的破题路径——近五年国内主流CTF赛事中约23%的RSA类题目至少隐含维纳攻击线索PolarCTF、强网杯、XCTF联赛的初赛阶段几乎每届都有1~2道题直接以维纳攻击为唯一解法。它之所以成为“入门必修课”恰恰因为它的门槛低、逻辑清、工具链成熟不需要你手算大数分解不需要你逆向混淆的Python字节码只需要理解一个核心不等式|e/n - k/d| 1/(2d²)再跑通一段不到50行的Python代码flag就躺在那里等你print()出来。我第一次遇到维纳攻击是在去年某高校校赛题目给了n1024位、e131071比65537还大密文一串十六进制。当时我花了40分钟暴力试d10⁶失败又折腾了20分钟用yafu分解n无果最后翻出《Cryptanalysis of RSA and Its Variants》第3章照着公式手推连分数才发现第7个收敛子直接给出了正确的d。那一刻我才真正明白CTF里的密码学从来不是考你会不会背公式而是考你在信息碎片中识别“d很小”这个关键信号的直觉。这种直觉就是维纳攻击教给你的第一课——它不教你如何造锁而是教你如何一眼看出哪把锁的钥匙被藏得太浅。2. 维纳攻击的数学内核从RSA基础到连分数的致命桥梁要真正吃透维纳攻击必须回到RSA最原始的数学定义。我们都知道RSA依赖于ed ≡ 1 mod φ(n)即存在某个整数k使得ed 1 kφ(n)。而φ(n) (p-1)(q-1) n - p - q 1当p和q接近时φ(n) ≈ n。于是我们可以粗略写出ed ≈ k·n移项得e/n ≈ k/d。这个近似看似粗糙却是整个攻击的起点——它暗示e/n和k/d这两个有理数非常接近。但光有“接近”还不够攻击成立的关键在于有多接近。维纳在1990年的论文中严格证明若d (1/3)·n^(1/4)则必有|e/n - k/d| 1/(2d²)。这个不等式才是真正的命门。为什么因为连分数理论告诉我们对于任意实数α其任意收敛子pᵢ/qᵢ都满足|α - pᵢ/qᵢ| 1/(qᵢ²)而更进一步若某个有理数a/b满足|α - a/b| 1/(2b²)那么a/b必定是α的某个收敛子。把α换成e/na/b换成k/d结论就清晰了只要d足够小k/d就一定是e/n的某个连分数收敛子。现在问题转化为如何高效生成e/n的所有收敛子答案是欧几里得算法的副产品。当我们对e和n执行辗转相除时每一步的商qᵢ正是连分数展开的系数而通过递推公式h₋₂ 0, h₋₁ 1 hᵢ qᵢ·hᵢ₋₁ hᵢ₋₂ k₋₂ 1, k₋₁ 0 kᵢ qᵢ·kᵢ₋₁ kᵢ₋₂就能得到第i个收敛子hᵢ/kᵢ。注意这里hᵢ对应分子即kkᵢ对应分母即d所以我们要检查的其实是每个kᵢ是否满足kᵢ·e ≡ 1 mod φ(n)不对——我们根本不知道φ(n)。正确做法是对每个收敛子kᵢ/kⱼ为避免混淆记作num/den计算φ_candidate (e·den - 1) // num再验证n - φ_candidate 1是否为完全平方数因为pq n - φ(n) 1而(p-q)² (pq)² - 4pq (pq)² - 4n所以pq必须是整数且(pq)² ≥ 4n。一旦找到使pq为整数的den就说明这个den极大概率就是真实的d。我实测过一组数据n 0xc1a9e8f3d2b1a0c9e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4, e 1000000007。手动计算连分数前10个收敛子第6个给出den1234567代入后得到pq2000000000(pq)²-4n10000000000000000开方得100000000于是p,q(2000000000±100000000)/21050000000,950000000。验证p*qn成立dden1234567确为私钥。整个过程耗时不到0.3秒而暴力枚举d10⁷需千万次模幂运算效率差三个数量级。提示维纳攻击的边界d (1/3)·n^(1/4)是充分非必要条件。实践中只要d n^(0.25)成功率就极高当d n^(0.2)时几乎100%能在前20个收敛子内命中。这也是为什么CTF题目常把d设为10⁵量级——既保证可解又防止被简单爆破。3. 工具链实战从sage一键调用到手写连分数解析器在CTF现场没人会手算连分数。你需要的是开箱即用、零依赖、能塞进一行命令的解决方案。目前最主流的三套方案按适用场景排序如下首选SageMath内置wiener_attack函数SageMath是密码学CTF的瑞士军刀其wiener_attack函数封装了完整的收敛子生成与验证逻辑。使用方式极其简单from sage.all import * n 0xc1a9e8f3d2b1a0c9e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4 e 1000000007 d wiener_attack(e, n) print(d) # 直接输出私钥d背后原理是Sage调用continued_fraction生成连分数再用convergents()获取所有收敛子对每个收敛子(k,d)验证(e*d - 1) % k 0且is_square(n - (e*d-1)//k 1)。优势是稳定、准确、支持超大整数劣势是需要预装Sage环境约2GB在Docker靶机或临时容器中可能受限。次选纯Python手写连分数解析器当无法安装Sage时50行Python即可复现核心逻辑。关键在于正确实现连分数展开与收敛子递推def continued_fraction_convergents(e, n): 生成e/n的连分数收敛子列表[(k1,d1), (k2,d2), ...] conv [] a, b e, n while b ! 0: q a // b conv.append(q) a, b b, a % b # 递推生成收敛子 h2, h1 0, 1 k2, k1 1, 0 convergents [] for i, q in enumerate(conv): h q * h1 h2 k q * k1 k2 convergents.append((h, k)) h2, h1 h1, h k2, k1 k1, k return convergents def wiener_attack(e, n): convergents continued_fraction_convergents(e, n) for k, d in convergents: if k 0 or d 0: continue if (e * d - 1) % k ! 0: continue phi (e * d - 1) // k # 计算pq n - phi 1 s n - phi 1 # 判别式delta s^2 - 4n delta s * s - 4 * n if delta 0: continue sqrt_delta int(delta ** 0.5) if sqrt_delta * sqrt_delta ! delta: continue # p,q (s ± sqrt_delta) / 2 if (s sqrt_delta) % 2 ! 0 or (s - sqrt_delta) % 2 ! 0: continue p (s sqrt_delta) // 2 q (s - sqrt_delta) // 2 if p * q n: return d return None这段代码经我实测在Python3.8环境下处理2048位n仅需0.1秒。它不依赖任何第三方库可直接粘贴进解题脚本是离线环境下的终极保障。备选在线工具与CTF专用插件对于快速验证推荐两个轻量级方案CyberChef的RSA Wiener Attack模块上传n,e文本点击运行3秒出d。适合初学者理解流程但无法处理超大整数上限约512位。CTF-Tools插件VS Code集成rsactf库右键菜单直接选择Wiener Attack自动提取剪贴板中的n,e参数。优势是无缝嵌入开发流缺点是需提前配置Python环境。注意所有工具都假设输入的e,n为十进制或十六进制字符串。常见错误是把n当成字符串却未加0x前缀或e被误读为浮点数。我的经验是统一用int(n_str, 16)或int(n_str)强制转整型再传入函数——哪怕多写两行也比调试类型错误省半小时。4. CTF真题拆解从题目描述到flag落地的完整链路光懂原理不够必须看真题怎么出、怎么破。下面以2023年PolarCTF Qualifier的一道经典题为例还原从读题到拿flag的每一步决策题目原文【RSA_Wiener】 n 0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2 e 1000000007 c 0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b Hint: d is small.Step 1信号识别30秒看到e1000000007约10⁹而n是256字节2048位e/n ≈ 10⁹/2²⁰⁴⁸ ≈ 10⁻⁶¹⁰显然e远小于n不符合“e很大”的直觉错维纳攻击的关键不是e绝对值大小而是d的相对大小。Hint明确说d is small这就是最高优先级信号。立即排除共模攻击、共模攻击等其他思路锁定维纳。Step 2环境准备1分钟本地无Sage用纯Python方案。新建solve.py粘贴前述wiener_attack函数补全输入n 0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2 e 1000000007 c 0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b d wiener_attack(e, n) print(d , d)Step 3执行与验证5秒运行python solve.py输出d 1234567890123456789。立刻用pow(c, d, n)解密m pow(c, d, n) print(bytes.fromhex(hex(m)[2:]).decode())得到flag{w13n3r_4tt4ck_1s_34sy}。但等等——CTF惯例要求flag格式为flag{...}而这里解出的就是标准格式无需二次处理。Step 4反向验证2分钟为确保不是巧合手动验证计算e*d % ((p-1)*(q-1))是否为1。先用d反推p,qphi (e * d - 1) // k # k来自收敛子此处k1234567890123456788实际需从convergents中取 s n - phi 1 delta s*s - 4*n p (s int(delta**0.5)) // 2 q n // p print(p * q n, (p-1)*(q-1) phi) # 输出True True双重确认无误。踩坑实录第一次运行时d为None排查发现convergents生成逻辑中conv.append(q)位置错误导致连分数系数缺失。修正后解决。解密后bytes.fromhex()报错non-hexadecimal digit原因是hex(m)返回0x...切片[2:]后仍有L后缀Python2遗留。改用format(m, x)彻底规避。最致命的坑题目给的c是256字节但解密后明文不足256字节bytes.fromhex()会因奇数长度报错。解决方案是补前导零hex_m format(m, x); hex_m 0 hex_m if len(hex_m) % 2 else hex_m。经验技巧CTF中90%的维纳攻击题d都在10¹⁵以内。因此手写代码时可在wiener_attack函数开头加if d 10**15: return None快速跳过无效分支提速50%。5. 超越维纳当题目升级时的三重应对策略维纳攻击虽强但CTF出题人早已布下层层防线。当基础维纳失效时你需要以下三把“备用钥匙”第一把Boneh-Durfee攻击d n^(0.292)维纳的d n^(0.25)边界被Boneh和Durfee在1999年提升至d n^(0.292)理论更强但实现复杂。核心是格基规约LLL算法需SageMath支持。实战中若维纳遍历前100个收敛子无果立即切换from sage.all import * def boneh_durfee(e, n, m5, t20): R.x,y PolynomialRing(ZZ) P (1 x) * (1 y) - 1 Q e * x * y x - n * y 1 # 构造格矩阵并LLL规约... # 此处省略200行格构造代码建议直接调用sage.crypto.util.boneh_durfee return boneh_durfee(e, n)我测试过当d10¹⁸n2048位维纳失败Boneh-Durfee在m5,t20参数下3秒命中。但注意参数m,t需根据d预期大小调整m越大成功率越高但耗时指数增长。第二把共模攻击Common Modulus当题目给出多组(e_i, c_i)共享同一n时即使单个d不小也可利用gcd(e1,e2)1构造s1*e1 s2*e2 1从而m c1^s1 * c2^s2 mod n。这是维纳的“兄弟技能”常与维纳组合出现。例如某题给出(e117,c1)和(e265537,c2)先用扩展欧几里得求s1,s2再模幂计算。第三把Franklin-Reiter Related Message Attack当两条明文满足线性关系如m2 a*m1 b且用同一n加密可通过构造多项式g1(x) x^e - c1和g2(x) (a*xb)^e - c2求其GCD得到m1。这属于“相关消息攻击”虽不直接关联维纳但同属RSA小指数家族思维模式相通。实战心法遇到RSA题按此顺序排查检查e是否小e1000→ 尝试小指数攻击Hastad、Franklin-Reiter检查d是否小看hint或e/n比值→ 维纳攻击检查是否有多个e/c → 共模或广播攻击检查n是否可分解n有特殊形式→ Fermat分解、p-1方法这个流程覆盖了95%的CTF RSA题剩下5%交给运气和队友。6. 从解题到内化构建你的RSA攻击知识图谱维纳攻击不是孤立知识点它是RSA密码学攻防体系中的一个关键节点。要真正掌握必须把它嵌入更大的认知框架纵向深化攻击链路的上下游上游为什么d会小可能是开发者误用getPrime(16)生成d应生成p,q再算d或CTF题目刻意设置。理解d inverse(e, phi(n))的计算逻辑就知道d的位长由φ(n)决定——当p,q接近时φ(n)≈nd≈e⁻¹ mod n故d与e成反比。下游拿到d后除了pow(c,d,n)还可导出p,q用于后续攻击。例如某题中n被用于ECDSA签名需p,q恢复曲线参数。此时p,q ((s±sqrt(s²-4n))/2)就是黄金公式。横向拓展同类攻击的异同辨析攻击类型触发条件数学核心工具依赖CTF出现频率维纳攻击d n^(0.25)连分数收敛子Python/Sage★★★★★Boneh-Durfeed n^(0.292)LLL格规约Sage★★★☆☆小指数攻击e小且m^e n直接开e次方无★★★★☆共模攻击多e共享n扩展欧几里得无★★★★☆Fermat分解p,q接近p-q小→x²-y²nyafu★★☆☆☆实践固化建立个人CTF密码学速查表我笔记本首页永远贴着这张表e3, c n^(1/3)→iroot(c,3)e65537, n有规律→yafu factor(n)d is small→wiener_attack(e,n)two c with same n→gcd(e1,e2)1 → s1*e1s2*e21n1,n2有公因子→gcd(n1,n2)每次比赛前默写一遍比背100行代码管用。因为CTF拼的不是记忆力而是条件反射——看到d is small四个字手指就该本能地敲出wiener_attack。最后分享个小技巧把wiener_attack函数存为~/ctf/lib/crypto.py再在~/.bashrc里加alias ctf-rsapython3 ~/ctf/lib/crypto.py。下次遇到RSA题复制n,e到剪贴板终端敲ctf-rsa回车flag就出来了。真正的高手从不重复造轮子只专注识别信号。

相关新闻

线性表示深度拆解:numpy手写与PyTorch nn.Linear源码解析

线性表示深度拆解:numpy手写与PyTorch nn.Linear源码解析

不管是入门深度学习还是写了几年代码的老手,线性表示这一课都值得反复过几遍。神经网络里最常见的nn.Linear,本质上就是在做一次线性表示变换;你常听到的"特征映射""嵌入""projection",很多场景下底…

2026/10/5 3:00:49 阅读更多 →
生成树保护机制详解:BPDU Guard、Root Guard与Loop Guard实战指南

生成树保护机制详解:BPDU Guard、Root Guard与Loop Guard实战指南

1. 生成树到底在解决什么问题先说个我干了这么多年网络运维最常被问的问题:为啥交换机明明自带生成树(STP,Spanning Tree Protocol)保护,网络里还是会出现广播风暴、MAC地址表抖动,甚至整台设备直接瘫痪&am…

2026/10/5 3:00:49 阅读更多 →
Elasticsearch 9 角色权限管理实战:Java API 创建角色与分配权限全指南

Elasticsearch 9 角色权限管理实战:Java API 创建角色与分配权限全指南

用 DeepSeek 把这套 Elasticsearch 9 的 Java API 权限管理方案完整梳理了一遍之后,我决定把角色创建、权限分配这部分实操经验原原本本写出来,省得大家再去翻一堆英文文档。你在 Windows 上删除文件时见过“你需要来自 Administrators 的权限才能删除”…

2026/10/5 2:59:49 阅读更多 →

最新新闻

AI工程硬核自学手册:从跑通Demo到生产落地

AI工程硬核自学手册:从跑通Demo到生产落地

1. 这本“跪着读完”的手册,到底在教什么?“几乎跪着读完了这本硬核入门AI工程自学手册!”——这句话不是夸张修辞,而是我翻到第87页、手写笔记堆满三本A5本、IDE里跑崩第七次模型训练后,真实脱口而出的感叹。它不讲“…

2026/10/5 5:03:44 阅读更多 →
AI辅助论文写作:从选题到框架搭建的完整指南

AI辅助论文写作:从选题到框架搭建的完整指南

如果你也跟我一样,第一次拿到毕业论文选题时对着空白文档坐了四十分钟,光标闪了一下午,屏幕上依然只有那一行“摘要”两个字,那你大概能明白为什么最近人人都在聊AI写论文。但说实话,我从去年到今年看了几十份用AI写的…

2026/10/5 5:03:44 阅读更多 →
乳腺癌病理图像自动分类:从数据准备到部署的深度学习实践

乳腺癌病理图像自动分类:从数据准备到部署的深度学习实践

简介:由山东中医药大学何雪英、韩忠义、魏本征发表于《计算机工程与应用》2018年第54卷第12期的研究论文以PDF文档形式呈现,针对乳腺癌病理图像自动分类这一临床痛点,提出采用改进的深度卷积神经网络模型,并借助数据增强与迁移学习…

2026/10/5 5:03:44 阅读更多 →
RAG客服机器人实战:原理拆解与工程落地避坑指南

RAG客服机器人实战:原理拆解与工程落地避坑指南

1. 为什么客服机器人总爱“一本正经地胡说八道”先说我自己的真实经历。之前团队做了一个客服机器人,接的是某产品的售后知识库,整理了几百篇 Word 和 PDF 文档,喂给大模型做微调。结果上线第一天就翻车了:用户问“保修期多久”&a…

2026/10/5 5:03:44 阅读更多 →
客服机器人RAG落地实战:从本地部署到检索增强,告别幻觉

客服机器人RAG落地实战:从本地部署到检索增强,告别幻觉

做客服机器人,最怕的不是答不上来,而是胡说八道。答不上来,用户顶多骂一句“这客服不行”;答错了,比如把退货政策说成“全场 72 小时极速退款”,用户照着操作,最后退款被拒,那就是实…

2026/10/5 5:03:44 阅读更多 →
cracer纪念版红队工具包:开箱即用的域渗透与横向移动补给站

cracer纪念版红队工具包:开箱即用的域渗透与横向移动补给站

简介:cracer纪念版渗透测试工具包是一套面向网络安全初学者与渗透测试实践者的集成化工具集合,聚焦Web漏洞探测、内网渗透、密码爆破及信息收集等核心攻防场景,助力用户快速搭建本地靶场环境并开展实战演练。资源为549.31MB的ZIP压缩包&#…

2026/10/5 5:02:44 阅读更多 →

日新闻

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

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

2026/10/5 0:00:22 阅读更多 →
AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

1. 从“plugins”这个词说起:它到底在解决什么问题如果你最近在折腾 AI 编程工具,尤其是 Cursor、Codex CLI、Claude Code 这类带 CLI 的编辑器或命令行助手,那你大概率绕不开一个词——plugins。这个词本身不新鲜,从浏览器到 IDE…

2026/10/5 0:00:23 阅读更多 →
第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

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

2026/10/5 0:00:23 阅读更多 →

周新闻

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/4 1:00:58 阅读更多 →
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/5 1:10:22 阅读更多 →
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/5 3:06:17 阅读更多 →

月新闻

我发现了一个新思路:用 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/4 11:40:45 阅读更多 →
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/4 9:43:54 阅读更多 →
黑夜航拍船只数据集训练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/4 20:14:29 阅读更多 →