<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>VMV</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://tsvmv.github.io/</id>
  <link href="https://tsvmv.github.io/" rel="alternate"/>
  <link href="https://tsvmv.github.io/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, VMV</rights>
  <subtitle>VMV的个人技术博客，专注CTF、网络安全、漏洞分析、AI大模型、Linux运维、编程开发等领域的技术分享与实战记录</subtitle>
  <title>VMV 的博客</title>
  <updated>2026-11-04T11:56:13.947Z</updated>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="CTF" scheme="https://tsvmv.github.io/categories/CTF/"/>
    <category term="CTF" scheme="https://tsvmv.github.io/tags/CTF/"/>
    <category term="Pwn" scheme="https://tsvmv.github.io/tags/Pwn/"/>
    <category term="Docker" scheme="https://tsvmv.github.io/tags/Docker/"/>
    <category term="工具" scheme="https://tsvmv.github.io/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="自动化" scheme="https://tsvmv.github.io/tags/%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
    <content>
      <![CDATA[<blockquote><p>项目地址：<a href="https://github.com/TSVMV/pwnhunter">github.com&#x2F;TSVMV&#x2F;pwnhunter</a> · 仅用于 CTF 训练平台与授权环境</p></blockquote><p>做 PWN 题的时间分配大概是这样的：<code>checksec</code> 查保护、cyclic 算偏移、认 libc 版本、套模板写 exp、本地调通、远程打 flag——这些<strong>套路化操作占了七成时间</strong>，真正考验思路的”这个洞怎么利用”只占三成。</p><p>于是我把那七成做成了引擎。pwnhunter 是一个 CTF PWN 自动解题引擎：给定题目附件（ELF &#x2F; libc &#x2F; 源码）与远程实例地址，自动完成侦察、漏洞探测、exploit 生成、本地验证与远程 flag 获取，产出可以直接人工接管的 pwntools 脚本和解题报告。</p><span id="more"></span><h2 id="五步流水线"><a href="#五步流水线" class="headerlink" title="五步流水线"></a>五步流水线</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">附件 ─► triage ─► 动态探针 ─► 策略注册表(feasible→build) ─► 本地验证(容器) ─► 远程收割</span><br><span class="line">                     │                                            │</span><br><span class="line">                     └── 失败自分析 ◄──────────────────────────────┘</span><br></pre></td></tr></table></figure><p><strong>① triage（侦察）</strong>：保护机制、架构、符号表、libc 版本识别，先建立题目的完整档案。</p><p><strong>② 动态探针</strong>：不只是静态看。引擎会在 gdb 里跑真实进程——cyclic 算溢出偏移、canary smash-oracle 验证是否有金丝雀、格式化字符串扫描测回显漏洞。探针产出的数据直接喂给下一步。</p><p><strong>③ 策略引擎</strong>：一张策略注册表，每条策略先判 <code>feasible</code>（当前保护组合下是否可行）再 <code>build</code>（生成 exploit）。可行就构建，不可行换下一条，不会浪费时间在不可能的路上。</p><p><strong>④ 本地验证</strong>：所有动态执行都在 Docker 引擎容器里完成（gdb 调试、编译、被测进程），exploit 先在容器里对本地靶机验证通过才算数。</p><p><strong>⑤ 远程收割</strong>：验证过的 exp 打远程实例，拿 flag。失败了会自动产出 <code>analysis.md</code>——探针数据 + 规则化的下一步建议，像队友交接文档一样可直接人工接管。</p><h2 id="已实现的九类策略"><a href="#已实现的九类策略" class="headerlink" title="已实现的九类策略"></a>已实现的九类策略</h2><table><thead><tr><th>策略</th><th>说明</th></tr></thead><tbody><tr><td>ret2win</td><td>经典返回到 win 函数</td></tr><tr><td>ret2magic</td><td>局部变量魔数覆盖</td></tr><tr><td>shellcode-callbuf &#x2F; shellcode-bss</td><td>shellcode 注入两种落点</td></tr><tr><td>pie-partial</td><td>1 字节窗口判定 + 4bit ASLR 爆破</td></tr><tr><td>ret2plt</td><td>借 PLT 泄漏再打</td></tr><tr><td>ret2libc</td><td>泄漏 libc 基址构造 ROP</td></tr><tr><td>fmtstr-got</td><td>格式化字符串改 GOT</td></tr><tr><td>canary-fmtstr</td><td>格式化串泄漏 canary 后组合利用</td></tr><tr><td>canary-brute</td><td>fork 服务器单连接逐字节爆破</td></tr></tbody></table><p>除了策略本身，还有一层<strong>通用机制</strong>在所有策略下生效：</p><ul><li><strong>gdb 断点成功判据</strong>——win 函数不回显 flag 也能通过控制流验证成功与否；</li><li><strong>栈对齐自愈</strong>——<code>movaps</code> 引发的 16 字节对齐崩溃自动补 <code>ret</code> 平衡；</li><li><strong>recvn 防 <code>\n</code> 截断</strong>——泄漏数据不会被换行提前掐断；</li><li><strong>flag 判定与 exploit 生成彻底解耦</strong>——判定逻辑独立，不会因为 flag 格式差异误判 exp 失败。</li></ul><h2 id="自愈与失败自分析"><a href="#自愈与失败自分析" class="headerlink" title="自愈与失败自分析"></a>自愈与失败自分析</h2><p>自动解题最怕的是”跑不通就死”。pwnhunter 有三层兜底：</p><ol><li><strong>源码附件编译矩阵</strong>：有源码的题自动编译 PIE±、canary± 的变体，哪个能复现远程行为用哪个；</li><li><strong>远程偏移盲测重试</strong>：本地偏移和远程对不上时自动在远程重测；</li><li><strong>libc 不匹配远程探针</strong>：给的 libc 和远程不符时，用探针结果自行修正。</li></ol><p>失败也不白跑——每次 solve 产出 <code>runs/&lt;题名&gt;-&lt;时间戳&gt;/</code>：<code>triage.json</code>、<code>exploit.py</code>、各策略产物、<code>transcript.log</code>，失败时另有 <code>analysis.md</code>（探针数据 + 建议下一步），成功时是 <code>writeup.md</code> + <code>flag.txt</code>。</p><h2 id="黑盒模式：没有附件也能测"><a href="#黑盒模式：没有附件也能测" class="headerlink" title="黑盒模式：没有附件也能测"></a>黑盒模式：没有附件也能测</h2><p><code>blackbox</code> 子命令只凭远程端点做盲测：banner 指纹、回显行为、格式化字符串探测、崩溃偏移。对一次性服务（连一次就断的）会如实报告预言机不可用，不会假装测过。</p><h2 id="怎么用"><a href="#怎么用" class="headerlink" title="怎么用"></a>怎么用</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 依赖：Docker Desktop、Python 3.10+（pwntools/capstone/pyelftools 等）</span></span><br><span class="line">python -m pip install -e . --no-deps</span><br><span class="line">python -m pwnhunter <span class="built_in">env</span> build      <span class="comment"># 构建引擎镜像（Ubuntu 22.04 + pwntools + gdb + gcc + AFL++）</span></span><br><span class="line"></span><br><span class="line">python -m pwnhunter web --open             <span class="comment"># 本地面板：任务队列 / run 历史 / 失败分析</span></span><br><span class="line">python -m pwnhunter exp ./pwn -o exp.py    <span class="comment"># 一键出 EXP（不发起攻击）</span></span><br><span class="line">python -m pwnhunter solve ./pwn --remote h:p --libc ./libc.so.6   <span class="comment"># 全自动解题</span></span><br><span class="line">python -m pwnhunter blackbox --remote h:p  <span class="comment"># 纯黑盒盲测</span></span><br><span class="line">python -m pwnhunter triage ./pwn           <span class="comment"># 只看保护与符号报告</span></span><br><span class="line">python -m pwnhunter <span class="built_in">env</span> destroy            <span class="comment"># 一键清理全部 Docker 资源</span></span><br></pre></td></tr></table></figure><p>所有 Docker 资源打 <code>pwnhunter=1</code> 标签，<code>env destroy</code> 一键清理，不留垃圾。国内网络内置了镜像源（Docker 1panel &#x2F; apt aliyun &#x2F; pip tsinghua），构建不走玄学网络。</p><h2 id="测试怎么做的"><a href="#测试怎么做的" class="headerlink" title="测试怎么做的"></a>测试怎么做的</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">python tests/test_solve.py     <span class="comment"># 7 类自建靶场端到端（远程拿 flag）</span></span><br><span class="line">python tests/test_real.py      <span class="comment"># ROP Emporium 真题 2 道</span></span><br><span class="line">python tests/test_blackbox.py  <span class="comment"># 黑盒盲探 2 道</span></span><br></pre></td></tr></table></figure><p>自建靶场覆盖九类策略的典型组合，真题用 ROP Emporium 交叉验证，黑盒模式单独成套。</p><h2 id="路线图"><a href="#路线图" class="headerlink" title="路线图"></a>路线图</h2><p>PIE-aware fmtstr · SROP · 栈迁移 · ret2dlresolve · ORW&#x2F;seccomp · BROP · 堆自动化（glibc arena 模型）· 远程 banner 指纹自动选编译变体</p><p>堆自动化是大头，glibc 的 arena 模型值得单开一篇博客细说，敬请期待。</p><h2 id="边界声明"><a href="#边界声明" class="headerlink" title="边界声明"></a>边界声明</h2><ul><li>引擎<strong>仅用于 CTF 训练平台与授权环境</strong>，攻击动作只对 <code>solve</code> 显式给出的远程地址发起；</li><li>本地面板只监听 <code>127.0.0.1</code>；</li><li>平台对接脚本（如 QingCen 平台驱动）的凭据一律走环境变量，不落盘。</li></ul><p>工具代码已开源：<a href="https://github.com/TSVMV/pwnhunter">github.com&#x2F;TSVMV&#x2F;pwnhunter</a>，欢迎交流，Issue&#x2F;PR 均可。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/11/01/pwnhunter-auto-pwn-engine/</id>
    <link href="https://tsvmv.github.io/2026/11/01/pwnhunter-auto-pwn-engine/"/>
    <published>2026-11-01T04:00:00.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>项目地址：<a href="https://github.com/TSVMV/pwnhunter">github.com&#x2F;TSVMV&#x2F;pwnhunter</a> · 仅用于 CTF 训练平台与授权环境</p>
</blockquote>
<p>做 PWN 题的时间分配大概是这样的：<code>checksec</code> 查保护、cyclic 算偏移、认 libc 版本、套模板写 exp、本地调通、远程打 flag——这些<strong>套路化操作占了七成时间</strong>，真正考验思路的”这个洞怎么利用”只占三成。</p>
<p>于是我把那七成做成了引擎。pwnhunter 是一个 CTF PWN 自动解题引擎：给定题目附件（ELF &#x2F; libc &#x2F; 源码）与远程实例地址，自动完成侦察、漏洞探测、exploit 生成、本地验证与远程 flag 获取，产出可以直接人工接管的 pwntools 脚本和解题报告。</p>]]>
    </summary>
    <title>PwnHunter：我把 CTF PWN 题做成了自动解题引擎</title>
    <updated>2026-11-04T11:56:13.947Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="运维开发" scheme="https://tsvmv.github.io/categories/%E8%BF%90%E7%BB%B4%E5%BC%80%E5%8F%91/"/>
    <category term="Cloudflare" scheme="https://tsvmv.github.io/tags/Cloudflare/"/>
    <category term="Cloudflare Pages" scheme="https://tsvmv.github.io/tags/Cloudflare-Pages/"/>
    <category term="Vue" scheme="https://tsvmv.github.io/tags/Vue/"/>
    <category term="Hexo" scheme="https://tsvmv.github.io/tags/Hexo/"/>
    <category term="网站搭建" scheme="https://tsvmv.github.io/tags/%E7%BD%91%E7%AB%99%E6%90%AD%E5%BB%BA/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>想给班级做一个文化展示网站，或者想搭一个属于自己的博客，第一反应往往是”是不是要买服务器、买域名、学运维”。其实不用。借助 Cloudflare Pages，你可以完全免费地得到一个带全球 CDN、自动 HTTPS、支持自定义域名的网站，而且部署过程比想象中简单得多。</p><p>本文会带你用同一套 Cloudflare Pages 的思路，从零搭建两个站点：</p><ul><li>一个<strong>班级文化网站</strong>：用 Vue 3 + Vite 写成，用来展示班级风采、成员、荣誉和活动纪事；</li><li>一个<strong>个人博客</strong>：用 Hexo + Butterfly 写成，用来沉淀技术文章。</li></ul><p>两者都不需要服务器，push 到 GitHub 就会自动构建、自动上线。</p><span id="more"></span><h2 id="一、为什么选择-Cloudflare"><a href="#一、为什么选择-Cloudflare" class="headerlink" title="一、为什么选择 Cloudflare"></a>一、为什么选择 Cloudflare</h2><p>在开始动手之前，先说清楚为什么推荐 Cloudflare Pages，而不是别的方案。</p><p>Cloudflare Pages 的核心优势：</p><ul><li><strong>免费</strong>：静态站点托管免费，没有服务器费用，也没有按流量计费的负担；</li><li><strong>全球 CDN</strong>：全球几百个节点，静态资源就近分发，访问速度快；</li><li><strong>自动 HTTPS</strong>：绑定自定义域名后自动签发并续期证书，不用自己配置；</li><li><strong>与 GitHub 深度集成</strong>：push 代码即自动构建部署，不用本地打包上传；</li><li><strong>预览部署</strong>：每个分支、每次提交都能生成一个独立的预览地址，方便改版对比；</li><li><strong>支持 Cloudflare Workers</strong>：需要动态能力时，可以在静态站点旁边挂一个 Worker 处理接口，同一个域名下就能用。</li></ul><p>和 GitHub Pages 相比，Cloudflare Pages 的优势主要在<strong>访问速度</strong>和<strong>动态扩展能力</strong>上。GitHub Pages 在国内的访问体验经常不稳定，而 Cloudflare 的节点覆盖面更广。实际使用中，很多人的做法是<strong>同一份源码同时部署到两个平台</strong>，互为备份，后文会专门讲这一点。</p><h2 id="二、准备工作"><a href="#二、准备工作" class="headerlink" title="二、准备工作"></a>二、准备工作</h2><p>整个流程需要准备三样东西。</p><h3 id="2-1-GitHub-账号"><a href="#2-1-GitHub-账号" class="headerlink" title="2.1 GitHub 账号"></a>2.1 GitHub 账号</h3><p>所有代码都托管在 GitHub 上，同时它也是自动部署的触发源。</p><ol><li>访问 <a href="https://github.com/">https://github.com</a> 注册账号；</li><li>建议取一个简短好记的用户名，因为它会出现在站点地址里。</li></ol><h3 id="2-2-Cloudflare-账号"><a href="#2-2-Cloudflare-账号" class="headerlink" title="2.2 Cloudflare 账号"></a>2.2 Cloudflare 账号</h3><ol><li>访问 <a href="https://dash.cloudflare.com/sign-up">https://dash.cloudflare.com/sign-up</a> 注册；</li><li>邮箱验证后即可使用，免费套餐足够。</li></ol><h3 id="2-3-域名（可选）"><a href="#2-3-域名（可选）" class="headerlink" title="2.3 域名（可选）"></a>2.3 域名（可选）</h3><p>不绑定域名也能用，Cloudflare 会分配一个 <code>项目名.pages.dev</code> 的免费地址。如果你希望有 <code>banji.example.com</code> 这样的地址，就需要一个域名。</p><p>域名可以在任意注册商购买，购买后把 DNS 托管到 Cloudflare，就能在 Pages 里绑定自定义域。没有域名也完全不影响先把网站跑起来，可以后面再补。</p><h3 id="2-4-本地环境"><a href="#2-4-本地环境" class="headerlink" title="2.4 本地环境"></a>2.4 本地环境</h3><p>本地需要 Node.js 和 Git：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git --version</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">node -v</span><br><span class="line">npm -v</span><br></pre></td></tr></table></figure><p>如果 <code>node -v</code> 没有输出，去 <a href="https://nodejs.org/">https://nodejs.org</a> 下载 LTS 版本安装即可。写这篇文章时使用 Node 20 比较稳妥，Cloudflare 的构建环境默认也是 Node 20。</p><h2 id="三、方案一：班级文化网站（Vue-3-Vite）"><a href="#三、方案一：班级文化网站（Vue-3-Vite）" class="headerlink" title="三、方案一：班级文化网站（Vue 3 + Vite）"></a>三、方案一：班级文化网站（Vue 3 + Vite）</h2><p>班级文化网站适合用<strong>前端框架</strong>来做，因为它的页面结构比较固定、组件复用多，而且做出来的效果比纯 HTML 好看很多。这里用 Vue 3 + Vite。</p><h3 id="3-1-创建项目"><a href="#3-1-创建项目" class="headerlink" title="3.1 创建项目"></a>3.1 创建项目</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm create vite@latest class-site -- --template vue</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> class-site</span><br><span class="line">npm install</span><br></pre></td></tr></table></figure><p>此时目录结构大致如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">class-site/</span><br><span class="line">├── index.html            # 入口 HTML</span><br><span class="line">├── package.json</span><br><span class="line">├── vite.config.js        # Vite 配置</span><br><span class="line">├── public/               # 静态资源，构建后原样拷贝</span><br><span class="line">│   └── photos/           # 照片等</span><br><span class="line">└── src/</span><br><span class="line">    ├── main.js           # 应用入口</span><br><span class="line">    ├── App.vue           # 根组件</span><br><span class="line">    └── components/       # 各个区块组件</span><br></pre></td></tr></table></figure><h3 id="3-2-拆分页面区块"><a href="#3-2-拆分页面区块" class="headerlink" title="3.2 拆分页面区块"></a>3.2 拆分页面区块</h3><p>一个班级文化网站通常包含这些区块，建议一个区块一个组件：</p><table><thead><tr><th>组件</th><th>作用</th></tr></thead><tbody><tr><td><code>HeroSection.vue</code></td><td>首屏，班级名称、班训</td></tr><tr><td><code>AboutSection.vue</code></td><td>班级简介</td></tr><tr><td><code>TeachersSection.vue</code></td><td>师者风采</td></tr><tr><td><code>GallerySection.vue</code></td><td>班级风采相册</td></tr><tr><td><code>MembersSection.vue</code></td><td>班级成员 &#x2F; 名字墙</td></tr><tr><td><code>HonorsSection.vue</code></td><td>荣誉墙</td></tr><tr><td><code>TimelineSection.vue</code></td><td>班级纪事</td></tr><tr><td><code>AboutSiteSection.vue</code></td><td>关于本站</td></tr></tbody></table><p>在 <code>App.vue</code> 里把它们按顺序拼起来即可。这样做的好处是：改某一个区块不会影响其他部分，后期增删板块也非常方便。</p><h3 id="3-3-本地开发与构建"><a href="#3-3-本地开发与构建" class="headerlink" title="3.3 本地开发与构建"></a>3.3 本地开发与构建</h3><p>启动本地开发服务器：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run dev</span><br></pre></td></tr></table></figure><p>浏览器打开终端里提示的地址（一般是 <code>http://localhost:5173</code>）即可实时预览，改代码会自动热更新。</p><p>确认没问题后，构建生产版本：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run build</span><br></pre></td></tr></table></figure><p>构建产物会输出到 <code>dist/</code> 目录。<strong>部署时用的就是这个 <code>dist/</code></strong>，而不是源码目录。</p><h3 id="3-4-部署到-Cloudflare-Pages"><a href="#3-4-部署到-Cloudflare-Pages" class="headerlink" title="3.4 部署到 Cloudflare Pages"></a>3.4 部署到 Cloudflare Pages</h3><p>有两种方式，任选其一。</p><p><strong>方式 A：Git 集成（推荐）</strong></p><ol><li>在 Cloudflare 控制台进入 <strong>Workers &amp; Pages -&gt; Create -&gt; Pages -&gt; Connect to Git</strong>；</li><li>授权 GitHub，选择你的仓库；</li><li>填写构建配置：<ul><li>Framework preset：<code>Vue</code>（或 <code>None</code>）</li><li>Build command：<code>npm run build</code></li><li>Build output directory：<code>dist</code></li></ul></li><li>点击保存并部署。</li></ol><p>之后每次 push 到指定分支，Cloudflare 都会自动重新构建部署。</p><p><strong>方式 B：GitHub Actions + Wrangler</strong></p><p>如果不想在 Cloudflare 后台点来点去，也可以在仓库里放一个 workflow，用 Wrangler 自动部署：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Deploy</span> <span class="string">to</span> <span class="string">Cloudflare</span> <span class="string">Pages</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">main</span></span><br><span class="line">  <span class="attr">workflow_dispatch:</span></span><br><span class="line"></span><br><span class="line"><span class="attr">concurrency:</span></span><br><span class="line">  <span class="attr">group:</span> <span class="string">pages-deploy</span></span><br><span class="line">  <span class="attr">cancel-in-progress:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">deploy:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">拉取代码</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">安装</span> <span class="string">Node</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/setup-node@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">node-version:</span> <span class="string">&quot;20&quot;</span></span><br><span class="line">          <span class="attr">cache:</span> <span class="string">&quot;npm&quot;</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">安装依赖</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">npm</span> <span class="string">ci</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">构建</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">npm</span> <span class="string">run</span> <span class="string">build</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">部署到</span> <span class="string">Cloudflare</span> <span class="string">Pages</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">cloudflare/wrangler-action@v3</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">apiToken:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.CLOUDFLARE_API_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">accountId:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.CLOUDFLARE_ACCOUNT_ID</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">command:</span> <span class="string">pages</span> <span class="string">deploy</span> <span class="string">dist</span> <span class="string">--project-name=class-site</span> <span class="string">--branch=main</span></span><br></pre></td></tr></table></figure><p>其中 <code>CLOUDFLARE_API_TOKEN</code> 和 <code>CLOUDFLARE_ACCOUNT_ID</code> 要配置到仓库的 <strong>Settings -&gt; Secrets and variables -&gt; Actions</strong> 里。</p><ul><li><code>CLOUDFLARE_ACCOUNT_ID</code>：在 Cloudflare 控制台右下角或 Workers 概览页可以看到；</li><li><code>CLOUDFLARE_API_TOKEN</code>：在 <strong>My Profile -&gt; API Tokens -&gt; Create Token</strong> 创建，模板选 <strong>Edit Cloudflare Workers</strong> 或自定义带 <code>Cloudflare Pages: Edit</code> 权限的令牌。</li></ul><blockquote><p><strong>安全提醒</strong>：API Token 只能放在 GitHub Secrets 或 Cloudflare 环境变量里，绝对不要写进代码提交到仓库。</p></blockquote><h2 id="四、方案二：个人博客（Hexo-Butterfly）"><a href="#四、方案二：个人博客（Hexo-Butterfly）" class="headerlink" title="四、方案二：个人博客（Hexo + Butterfly）"></a>四、方案二：个人博客（Hexo + Butterfly）</h2><p>博客和班级网站的思路完全一致，区别只是”构建产物”由不同的框架生成。博客推荐 <strong>Hexo + Butterfly 主题</strong>：Markdown 写作、静态输出、主题好看。</p><p>搭建 Hexo 博客的详细步骤可以看我之前那篇《如何从0用GitHub搭建自己的博客》，这里只强调<strong>部署到 Cloudflare Pages</strong> 的差异：</p><ol><li>构建命令是 <code>npx hexo generate</code>（简写 <code>npx hexo g</code>），而不是 <code>npm run build</code>；</li><li>构建产物目录是 <code>public/</code>，而不是 <code>dist/</code>；</li><li>如果主题是作为 npm 包安装的，构建前要先 <code>npm ci</code> 安装依赖。</li></ol><p>对应的 Wrangler 部署命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npx hexo generate</span><br><span class="line">npx wrangler pages deploy public --project-name=my-blog --branch=main</span><br></pre></td></tr></table></figure><p>放到 GitHub Actions 里，和上面班级网站的 workflow 几乎一样，只需把构建命令和输出目录换掉。</p><h2 id="五、绑定自定义域名与-DNS"><a href="#五、绑定自定义域名与-DNS" class="headerlink" title="五、绑定自定义域名与 DNS"></a>五、绑定自定义域名与 DNS</h2><p>用 <code>xxx.pages.dev</code> 的默认地址也能访问，但绑定自己的域名会更正式、更好记。</p><h3 id="5-1-把域名托管到-Cloudflare"><a href="#5-1-把域名托管到-Cloudflare" class="headerlink" title="5.1 把域名托管到 Cloudflare"></a>5.1 把域名托管到 Cloudflare</h3><ol><li>登录 Cloudflare，点击 <strong>Add a site</strong>，输入你的域名；</li><li>选择 Free 套餐；</li><li>Cloudflare 会给出两个名称服务器（Name Server）地址，形如 <code>xxx.ns.cloudflare.com</code>；</li><li>到你的域名注册商后台，把域名的 DNS 服务器改成这两个地址；</li><li>等待生效（通常几分钟到几小时）。</li></ol><blockquote><p><strong>注意</strong>：一定要等域名的 NS 真正指向 Cloudflare 之后，再在 Pages 里绑定自定义域，否则会一直提示验证失败。可以用 <code>nslookup -type=ns 你的域名</code> 来确认 NS 是否已经切换。</p></blockquote><h3 id="5-2-在-Pages-里绑定"><a href="#5-2-在-Pages-里绑定" class="headerlink" title="5.2 在 Pages 里绑定"></a>5.2 在 Pages 里绑定</h3><ol><li>进入你的 Pages 项目 -&gt; <strong>Custom domains -&gt; Set up a custom domain</strong>；</li><li>输入域名，例如 <code>banji.example.com</code>；</li><li>如果域名已经托管在同一个 Cloudflare 账号下，Cloudflare 会自动添加一条 CNAME 记录并签发证书，直接点确认即可；</li><li>稍等片刻，用浏览器访问你的域名验证。</li></ol><p>如果是 <code>www</code> 子域或裸域，处理方式略有不同，Cloudflare 会自动给出对应的目标记录，按提示操作就行。</p><h2 id="六、同一份源码，双平台部署"><a href="#六、同一份源码，双平台部署" class="headerlink" title="六、同一份源码，双平台部署"></a>六、同一份源码，双平台部署</h2><p>前面提到，Cloudflare Pages 和 GitHub Pages 可以同时用。好处是：</p><ul><li>国内访问走 Cloudflare，速度更稳；</li><li>GitHub Pages 作为备份，两个平台互不影响；</li><li>同一份源码，两个站点，内容自动保持一致。</li></ul><p>做法是在 <code>.github/workflows/</code> 下放两个 workflow，一个部署 Cloudflare Pages（见 3.4），一个部署 GitHub Pages。它们都在 <code>push</code> 到 <code>main</code> 时触发。</p><p>需要注意一个关键点：<strong>两个站点的域名不同</strong>。Hexo 里站点域名写在 <code>_config.yml</code> 的 <code>url</code> 字段，如果共用一份配置，就会导致其中一个平台的链接指向另一个平台。</p><p>解决办法是用一份<strong>覆盖配置</strong>。例如新增 <code>_config.cloudflare.yml</code>：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">url:</span> <span class="string">https://你的博客域名</span></span><br></pre></td></tr></table></figure><p>Cloudflare 的构建命令改成同时加载两个配置：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx hexo generate --config _config.yml,_config.cloudflare.yml</span><br></pre></td></tr></table></figure><p>后面的配置会覆盖前面的同名字段，这样 GitHub Pages 用 <code>_config.yml</code> 里的 <code>url</code>，Cloudflare 用覆盖后的 <code>url</code>，两边互不干扰。</p><p>Vue 的班级网站没有这个问题，因为它的站点地址基本是纯静态跳转，不依赖 <code>url</code> 配置，直接两个平台部署同一份产物即可。</p><h2 id="七、进阶玩法"><a href="#七、进阶玩法" class="headerlink" title="七、进阶玩法"></a>七、进阶玩法</h2><p>静态站点搭好之后，如果还想加一点动态能力，Cloudflare Workers 是很好的补充。它和 Pages 在同一个账号里，可以在同一个域名下提供接口。</p><p>一些常见场景：</p><ul><li><strong>夜间自动关闭</strong>：写一个 Worker，在指定时间段（比如 23:00 到次日 06:00）返回维护页面，其他时间正常放行。适合不希望半夜有人访问的班级站点；</li><li><strong>访问统计</strong>：用 Workers 或 Cloudflare 自带的 Web Analytics 统计访问量，不需要引入第三方统计脚本；</li><li><strong>留言墙 &#x2F; 表单</strong>：前端提交到 Worker 接口，Worker 再把数据写入存储。不过要注意，Cloudflare 的 KV、D1 等存储需要单独创建并绑定，仅靠 Pages 权限是拿不到的；</li><li><strong>图片压缩与防盗链</strong>：结合 Cloudflare 的图片转换和规则功能。</li></ul><h3 id="7-1-缓存优化"><a href="#7-1-缓存优化" class="headerlink" title="7.1 缓存优化"></a>7.1 缓存优化</h3><p>静态站点的图片、JS、CSS 可以设置长缓存，配合文件名哈希实现”内容变则 URL 变”。在 Pages 项目根目录放一个 <code>_headers</code> 文件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/assets/*</span><br><span class="line">  Cache-Control: public, max-age=31536000, immutable</span><br></pre></td></tr></table></figure><p>这样带哈希的静态资源会被长期缓存，二次访问几乎瞬间加载。</p><h3 id="7-2-关于国内访问"><a href="#7-2-关于国内访问" class="headerlink" title="7.2 关于国内访问"></a>7.2 关于国内访问</h3><p>Cloudflare 默认分配的 <code>pages.dev</code> 域名在国内可能不稳定。如果对国内访问有要求，可以：</p><ol><li>绑定自己的域名（自定义域的访问通常比 <code>pages.dev</code> 稳定一些）；</li><li>使用 Cloudflare 的优选 IP 思路优化解析；</li><li>同时保留 GitHub Pages 作为备用入口。</li></ol><p>需要说明的是，任何方案都无法保证在所有网络环境下都稳定，双平台部署是性价比最高的兜底方式。</p><h2 id="八、常见问题"><a href="#八、常见问题" class="headerlink" title="八、常见问题"></a>八、常见问题</h2><p><strong>Q1：部署成功但页面 404？</strong><br>检查构建输出目录填对没有。Vue 是 <code>dist</code>，Hexo 是 <code>public</code>。填错目录会导致部署了一个空站点。</p><p><strong>Q2：改了代码但线上没更新？</strong><br>先看 GitHub Actions 或 Cloudflare 的构建日志有没有报错；再确认浏览器缓存，可以强刷或换无痕窗口。如果是自定义域，还要确认 DNS 已经指向 Cloudflare。</p><p><strong>Q3：自定义域一直验证失败？</strong><br>多半是域名的 NS 还没切到 Cloudflare。先在域名注册商处确认 NS 已修改，等待生效后再绑定。</p><p><strong>Q4：构建时提示找不到主题或依赖？</strong><br>Hexo 的主题如果通过 npm 安装，必须在构建前执行 <code>npm ci</code>。GitHub Actions 里要单独加一步安装依赖。</p><p><strong>Q5：能不能直接在本地打包后上传？</strong><br>可以，用 <code>npx wrangler pages deploy 产物目录</code> 手动上传。但推荐用 Git 集成或 Actions，这样每次改动都能自动部署，也保留了完整的部署记录。</p><h2 id="九、小结"><a href="#九、小结" class="headerlink" title="九、小结"></a>九、小结</h2><p>用 Cloudflare Pages 搭建班级文化网站和个人博客，本质上都是同一件事：<strong>把一份源码构建成静态文件，交给 Cloudflare 托管，push 即上线</strong>。区别只在于用什么框架生成这些文件：</p><ul><li>班级网站用 Vue 3 + Vite，输出 <code>dist/</code>；</li><li>个人博客用 Hexo + Butterfly，输出 <code>public/</code>。</li></ul><p>再配合自定义域名、双平台部署和少量 Workers 进阶能力，就能得到一个免费、稳定、好维护的站点。</p><p>如果你也想给自己的班级做一个这样的网站，不妨从最简单的 Vue 项目开始，先把首屏跑起来，再一块一块地往里加内容。做出来之后，你会发现在没有服务器的情况下，也能拥有一个像模像样的站点。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/28/cloudflare-class-site-blog/</id>
    <link href="https://tsvmv.github.io/2026/09/28/cloudflare-class-site-blog/"/>
    <published>2026-09-28T12:30:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>想给班级做一个文化展示网站，或者想搭一个属于自己的博客，第一反应往往是”是不是要买服务器、买域名、学运维”。其实不用。借助 Cloudflare Pages，你可以完全免费地得到一个带全球 CDN、自动 HTTPS、支持自定义域名的网站，而且部署过程比想象中简单得多。</p>
<p>本文会带你用同一套 Cloudflare Pages 的思路，从零搭建两个站点：</p>
<ul>
<li>一个<strong>班级文化网站</strong>：用 Vue 3 + Vite 写成，用来展示班级风采、成员、荣誉和活动纪事；</li>
<li>一个<strong>个人博客</strong>：用 Hexo + Butterfly 写成，用来沉淀技术文章。</li>
</ul>
<p>两者都不需要服务器，push 到 GitHub 就会自动构建、自动上线。</p>]]>
    </summary>
    <title>如何用 Cloudflare 搭建班级文化网站 / 个人博客</title>
    <updated>2026-11-04T11:56:13.945Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="运维开发" scheme="https://tsvmv.github.io/categories/%E8%BF%90%E7%BB%B4%E5%BC%80%E5%8F%91/"/>
    <category term="安全" scheme="https://tsvmv.github.io/tags/%E5%AE%89%E5%85%A8/"/>
    <category term="工具" scheme="https://tsvmv.github.io/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="代码审计" scheme="https://tsvmv.github.io/tags/%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/"/>
    <category term="静态分析" scheme="https://tsvmv.github.io/tags/%E9%9D%99%E6%80%81%E5%88%86%E6%9E%90/"/>
    <category term="Go" scheme="https://tsvmv.github.io/tags/Go/"/>
    <content>
      <![CDATA[<p>写代码的时候，有些函数调用本身就是危险信号——strcpy、system、eval、硬编码的密钥、弱加密算法。CodeQL 和 SonarQube 功能强，但部署重、要联网、对个人项目来说太重了。</p><p>vscseny 是一个本地静态代码扫描器，Go 写的，下载二进制直接跑，不需要运行时。指向你的项目目录，它报告危险调用、弱加密、硬编码密钥和常见错误，按严重程度排序。</p><span id="more"></span><h2 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h2><p>完全离线运行，代码不会上传到任何地方。做安全工具这一点很重要——扫描自己项目的代码不应该经过第三方服务器。</p><p>Go 编译成单二进制，Windows 和 Linux 都有，下载解压就能用，不需要装 Python、Node 或者任何运行时。</p><p>stdout 是终端时自动打开交互 TUI，可以过滤、排序、翻页、直接在编辑器里打开问题文件。管道输出时是纯文本报告，方便脚本化。也支持 JSON 输出给其他工具消费。</p><h2 id="检测什么"><a href="#检测什么" class="headerlink" title="检测什么"></a>检测什么</h2><p>规则按类别分组，严重程度分 critical&#x2F;high&#x2F;medium&#x2F;low：</p><p>注入类：system、popen、eval、exec 类调用，SQL 拼接，命令拼接。</p><p>弱加密：MD5、SHA1 用于安全场景，DES、3DES、ECB 模式，硬编码密钥和 IV，随机数用 math&#x2F;rand 而不是 crypto&#x2F;rand。</p><p>危险函数：strcpy、strcat、sprintf、gets 等缓冲区溢出风险函数，未检查的返回值。</p><p>硬编码：密码、API key、token、私钥出现在源码里。</p><p>权限和路径：chmod 777、硬编码临时路径、路径拼接未过滤（路径遍历风险）。</p><h2 id="用法"><a href="#用法" class="headerlink" title="用法"></a>用法</h2><figure class="highlight console"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">扫描项目，终端自动开 TUI</span></span><br><span class="line">vscseny /path/to/project</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">强制 TUI</span></span><br><span class="line">vscseny /path/to/project --tui</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">纯文本报告</span></span><br><span class="line">vscseny /path/to/project --report</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">JSON 输出</span></span><br><span class="line">vscseny /path/to/project --json</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">列出所有规则</span></span><br><span class="line">vscseny --list-rules</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">扫描单个文件</span></span><br><span class="line">vscseny src/app.py</span><br></pre></td></tr></table></figure><p>TUI 里的命令：<code>f critical</code> 按严重程度过滤，<code>c injection</code> 按类别过滤，<code>m strcpy</code> 匹配文件&#x2F;规则&#x2F;代码，<code>sort sev</code> 按严重程度排序，<code>&lt;number&gt;</code> 查看具体问题详情，<code>o</code> 在 $EDITOR 里打开当前问题文件。</p><p>项目地址：<a href="https://github.com/TSVMV/vscseny">github.com&#x2F;TSVMV&#x2F;vscseny</a>，Releases 页面下载 Windows&#x2F;Linux 二进制。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/25/vscseny-tool/</id>
    <link href="https://tsvmv.github.io/2026/09/25/vscseny-tool/"/>
    <published>2026-09-25T04:00:00.000Z</published>
    <summary>
      <![CDATA[<p>写代码的时候，有些函数调用本身就是危险信号——strcpy、system、eval、硬编码的密钥、弱加密算法。CodeQL 和 SonarQube 功能强，但部署重、要联网、对个人项目来说太重了。</p>
<p>vscseny 是一个本地静态代码扫描器，Go 写的，下载二进制直接跑，不需要运行时。指向你的项目目录，它报告危险调用、弱加密、硬编码密钥和常见错误，按严重程度排序。</p>]]>
    </summary>
    <title>vscseny：本地静态代码扫描，你的代码有没有危险调用</title>
    <updated>2026-11-04T11:56:13.949Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="运维开发" scheme="https://tsvmv.github.io/categories/%E8%BF%90%E7%BB%B4%E5%BC%80%E5%8F%91/"/>
    <category term="安全" scheme="https://tsvmv.github.io/tags/%E5%AE%89%E5%85%A8/"/>
    <category term="运维" scheme="https://tsvmv.github.io/tags/%E8%BF%90%E7%BB%B4/"/>
    <category term="Linux" scheme="https://tsvmv.github.io/tags/Linux/"/>
    <category term="蓝队" scheme="https://tsvmv.github.io/tags/%E8%93%9D%E9%98%9F/"/>
    <category term="基线" scheme="https://tsvmv.github.io/tags/%E5%9F%BA%E7%BA%BF/"/>
    <content>
      <![CDATA[<p>管服务器的时候，经常需要快速判断一台机器安不安全。网上的基线检查工具不少，但要么依赖一堆第三方库，要么输出英文报告看不懂，要么会偷偷改你系统配置。</p><p>vllixn 是我写的只读 Linux 主机安全体检工具。一条命令跑完，输出中文报告、分类雷达图和 0-100 评分。</p><span id="more"></span><h2 id="检查范围"><a href="#检查范围" class="headerlink" title="检查范围"></a>检查范围</h2><p>九大分类，覆盖 Linux 主机安全的主要面：</p><p>账户：UID 0 别名、空口令、系统账户可登录 shell、重复 UID、sudo NOPASSWD、authorized_keys 权限、口令有效期、默认 umask、PAM 口令复杂度、登录失败锁定。</p><p>SSH：root 登录、口令认证、空口令、端口转发、X11 转发、MaxAuthTries、协议 1、LoginGraceTime、ClientAliveInterval、登录用户范围、HostbasedAuthentication。含 Include 子配置的解析。</p><p>文件权限：&#x2F;etc&#x2F;passwd、shadow、sudoers 等敏感文件的权限检查，系统目录和家目录全局可写，高风险 SUID&#x2F;SGID。</p><p>网络：对外监听端口（明文协议和数据服务单独定级），nftables&#x2F;iptables&#x2F;ufw&#x2F;firewalld 活动规则。</p><p>服务：systemd 开机启用的高风险服务，&#x2F;etc&#x2F;rc.local，cron&#x2F;at 访问控制。</p><p>内核：网络加固（syncookies、IPv6 重定向）、信息泄露、ASLR、文件系统保护，四组 sysctl 参数。</p><p>补丁：依据 apt&#x2F;dnf 更新日志判断超过 60 天未安装更新。</p><p>启动：GRUB 配置权限、高危内核模块黑名单、core dump 限制。</p><p>审计：SELinux&#x2F;AppArmor 运行状态、auditd、时间同步、日志持久化。</p><h2 id="评分"><a href="#评分" class="headerlink" title="评分"></a>评分</h2><p>每个未通过项按严重程度扣分：严重 -25、高 -12、中 -5、低 -2，下限 0 分。</p><p>等级：90 优秀 &#x2F; 75 良好 &#x2F; 60 一般 &#x2F; 40 较差 &#x2F; 其余危险。</p><p>跳过的检查项不计分。HTML 报告额外提供各分类独立雷达图，一眼看出短板在哪。</p><h2 id="只读"><a href="#只读" class="headerlink" title="只读"></a>只读</h2><p>全程只读，不写任何系统配置。只读取文件元数据和白名单内的只读命令（ss、sysctl、systemctl、nft、iptables、ufw、firewall-cmd、getenforce、auditctl、timedatectl 等）。</p><p>非 root 也能跑，需要读 &#x2F;etc&#x2F;shadow、防火墙规则的检查会标记为跳过并说明原因。</p><h2 id="基线对比"><a href="#基线对比" class="headerlink" title="基线对比"></a>基线对比</h2><p>可以和上一次报告对比，看哪些问题是新增的、哪些已经修了。持续跟踪加固进展很方便。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 本机体检</span></span><br><span class="line">vllixn</span><br><span class="line"></span><br><span class="line"><span class="comment"># 导出 HTML 报告（含雷达图，纯 SVG 无 JS）</span></span><br><span class="line">vllixn --html report.html</span><br><span class="line"></span><br><span class="line"><span class="comment"># 忽略误报项</span></span><br><span class="line">vllixn --ignore ssh.x11_forwarding</span><br><span class="line"></span><br><span class="line"><span class="comment"># 与上次报告对比</span></span><br><span class="line">vllixn --baseline last.json --html now.html</span><br><span class="line"></span><br><span class="line"><span class="comment"># 对挂载的镜像目录做离线分析</span></span><br><span class="line">vllixn --root /mnt/target-image</span><br></pre></td></tr></table></figure><p>纯 Python 标准库，零第三方依赖。pip install vllixn 就能用。</p><p>项目地址：<a href="https://github.com/TSVMV/vllixn">github.com&#x2F;TSVMV&#x2F;vllixn</a>。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/25/vllixn-tool/</id>
    <link href="https://tsvmv.github.io/2026/09/25/vllixn-tool/"/>
    <published>2026-09-25T03:30:00.000Z</published>
    <summary>
      <![CDATA[<p>管服务器的时候，经常需要快速判断一台机器安不安全。网上的基线检查工具不少，但要么依赖一堆第三方库，要么输出英文报告看不懂，要么会偷偷改你系统配置。</p>
<p>vllixn 是我写的只读 Linux 主机安全体检工具。一条命令跑完，输出中文报告、分类雷达图和 0-100 评分。</p>]]>
    </summary>
    <title>vllixn：一条命令给 Linux 主机做安全体检</title>
    <updated>2026-11-04T11:56:13.949Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="系统&amp;内核" scheme="https://tsvmv.github.io/categories/%E7%B3%BB%E7%BB%9F-%E5%86%85%E6%A0%B8/"/>
    <category term="逆向" scheme="https://tsvmv.github.io/tags/%E9%80%86%E5%90%91/"/>
    <category term="pwn" scheme="https://tsvmv.github.io/tags/pwn/"/>
    <category term="二进制" scheme="https://tsvmv.github.io/tags/%E4%BA%8C%E8%BF%9B%E5%88%B6/"/>
    <category term="trace" scheme="https://tsvmv.github.io/tags/trace/"/>
    <category term="Unicorn" scheme="https://tsvmv.github.io/tags/Unicorn/"/>
    <content>
      <![CDATA[<p>调试二进制的时候，经常会想”刚才那一步寄存器是什么样的”。strace 能告诉你发生了什么系统调用，但没法回答某一帧的完整状态——寄存器、内存、调用栈，以及它和前后帧的关系。gdb 能单步，但往前退一步几乎不可能。</p><p>cinema（放映机）就是解决这个问题的：把二进制的执行过程录下来，任意一帧都可以瞬间回去看。</p><span id="more"></span><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>基于 Unicorn Engine 做指令级事件采集。程序跑的时候，每条指令执行前后的状态都被记录下来。但如果每条指令都存完整快照，百万级事件的轨迹文件会大到没法用。</p><p>所以用了快照 + dirty page 追踪：间隔几何增长地做快照（快照数是对数级于事件数），中间帧通过从最近快照回放事件来恢复。事件回放不重新执行指令，只是重放记录的状态变化，所以很快。</p><p>这意味着任意帧 seek 都是 O(log n) 的，反向单步也能做。</p><h2 id="能做什么"><a href="#能做什么" class="headerlink" title="能做什么"></a>能做什么</h2><p>record：录制一个静态 ELF x86_64 二进制的执行过程，输出 .ctrace 轨迹文件（FNV-1a 校验，百万级事件量级）。</p><p>info：查看轨迹元信息——入口地址、事件数、快照数、基页数、镜像哈希、最终输出。</p><p>trace：按事件类型过滤查看（比如只看 syscall）。</p><p>frame：查看任意一帧的完整状态——所有寄存器、指定内存区域的内容、当前输出、正在执行的指令。</p><p>tui：Textual 交互界面，可以翻帧、搜索、过滤。</p><p>export：导出 HTML 静态报告，方便分享。</p><figure class="highlight console"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">cinema record hello_static --out hello.ctrace</span></span><br><span class="line">recorded hello.ctrace (144021 bytes)</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">cinema info hello.ctrace</span></span><br><span class="line">entry        0x401000</span><br><span class="line">events       28</span><br><span class="line">final frame  28</span><br><span class="line">snapshots    0</span><br><span class="line">base pages   35</span><br><span class="line">  exit(0)</span><br><span class="line">  output: &#x27;hello, winVpwn\n&#x27;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">cinema frame hello.ctrace 13 --mem 0x402000:32</span></span><br><span class="line">       frame 13</span><br><span class="line">rip     0x40101e</span><br><span class="line">rax     0x1</span><br><span class="line">rsi     0x402000</span><br><span class="line">rdx     0xf</span><br><span class="line">memory 0x402000: 68 65 6c 6c 6f 2c 20 77 69 6e 56 70 77 6e 0a 00 ...</span><br></pre></td></tr></table></figure><h2 id="当前边界"><a href="#当前边界" class="headerlink" title="当前边界"></a>当前边界</h2><p>内核面向静态 ET_EXEC x86_64 镜像和确定性 syscall 集合（write、exit、exit_group）。文件、网络、信号、动态链接还在后续阶段。CI 只在 Linux 上验证。</p><p>被录制的二进制在 Unicorn 沙箱中运行，不触碰宿主文件系统和网络，运行权限不高于宿主进程。用于 CTF 题目、教学、以及自己拥有的软件。</p><p>复用了 winVpwn 的 ELF 加载和 syscall 分发。</p><p>项目地址：<a href="https://github.com/TSVMV/cinema">github.com&#x2F;TSVMV&#x2F;cinema</a>。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/25/cinema-tool/</id>
    <link href="https://tsvmv.github.io/2026/09/25/cinema-tool/"/>
    <published>2026-09-25T03:00:00.000Z</published>
    <summary>
      <![CDATA[<p>调试二进制的时候，经常会想”刚才那一步寄存器是什么样的”。strace 能告诉你发生了什么系统调用，但没法回答某一帧的完整状态——寄存器、内存、调用栈，以及它和前后帧的关系。gdb 能单步，但往前退一步几乎不可能。</p>
<p>cinema（放映机）就是解决这个问题的：把二进制的执行过程录下来，任意一帧都可以瞬间回去看。</p>]]>
    </summary>
    <title>cinema：把二进制执行过程变成可回放的电影</title>
    <updated>2026-11-04T11:56:13.945Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="系统&amp;内核" scheme="https://tsvmv.github.io/categories/%E7%B3%BB%E7%BB%9F-%E5%86%85%E6%A0%B8/"/>
    <category term="密码学" scheme="https://tsvmv.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="RSA" scheme="https://tsvmv.github.io/tags/RSA/"/>
    <category term="AES" scheme="https://tsvmv.github.io/tags/AES/"/>
    <category term="ECC" scheme="https://tsvmv.github.io/tags/ECC/"/>
    <category term="攻击" scheme="https://tsvmv.github.io/tags/%E6%94%BB%E5%87%BB/"/>
    <category term="Python" scheme="https://tsvmv.github.io/tags/Python/"/>
    <content>
      <![CDATA[<p>做 CTF 密码学题的时候，经常需要自己实现一些原语或者攻击。pycryptodome 能加密解密，但攻击（padding oracle、共模攻击、Wiener 这些）得自己写。每次比赛翻以前的 exp 复制粘贴，时间长了就想把这些东西整理成一个库。</p><p>crypto-gu 就是这么来的。纯 Python 标准库，零第三方依赖，把生产形态的密码学原语和确定性攻击实现放在同一个内核里。</p><span id="more"></span><h2 id="原语层"><a href="#原语层" class="headerlink" title="原语层"></a>原语层</h2><p>哈希：SHA-1、SHA-256、SHA-512、MD5、BLAKE2b、BLAKE2s、HMAC。还有长度扩展攻击的实现。</p><p>KDF：PBKDF2、HKDF（extract&#x2F;expand）、scrypt。</p><p>对称加密：AES-128&#x2F;192&#x2F;256 分组原语，ECB&#x2F;CBC&#x2F;CFB&#x2F;OFB&#x2F;CTR 五种模式。ChaCha20 流密码。Poly1305 MAC。ChaCha20-Poly1305 和 AES-GCM 两个 AEAD。</p><p>非对称：RSA 密钥生成和教科书式原语，配合攻击层做因子恢复。PKCS#1 里的 MGF1、RSAES-OAEP、RSASSA-PSS，按 RFC 8017 实现。</p><p>数论：素性检验、模逆、CRT、Tonelli-Shanks、Pollard rho&#x2F;p-1、Fermat 分解、BSGS。</p><p>ECC：素域椭圆曲线点运算（短 Weierstrass）、点阶、曲线阶、EC BSGS。</p><p>随机数：MT19937 生成器和从输出反推内部状态（untemper）。</p><p>编码和填充：hex、base32&#x2F;58&#x2F;64&#x2F;85、XOR、Morse、BCD、PKCS#7。</p><p>常量时间：无提前退出的比较和无分支 select。CPython 没法提供严格的常量时间保证，这里保持诚实定位。</p><h2 id="攻击层"><a href="#攻击层" class="headerlink" title="攻击层"></a>攻击层</h2><p>这是这个库和别的密码学库最大的区别——攻击和原语放在一起。</p><p>AES 攻击：ECB byte-at-a-time、CBC padding oracle、最后一轮 DFA 密钥恢复。</p><p>RSA 攻击：Wiener（d 过小）、Fermat 分解、Pollard p-1、共模攻击、Hastad 广播攻击。</p><p>ECC 攻击：Pohlig-Hellman（光滑阶群）、invalid-curve 小子群恢复。</p><p>时序攻击：从逐位计时恢复模幂指数、位分类。</p><p>攻击层只做确定性恢复（利用结构弱点），不做密钥空间暴力枚举。</p><h2 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h2><p>每个原语都有权威测试向量背书，取自 RFC&#x2F;NIST 原文，或者由 hashlib、OpenSSL、cryptography、pycryptodome 实测生成，逐条嵌入测试。这一点很重要——自己实现的 AES 如果没有 NIST 测试向量验证，你根本不知道它对不对。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 快速上手</span></span><br><span class="line"><span class="keyword">from</span> crypto_gu.hashes <span class="keyword">import</span> sha256</span><br><span class="line"><span class="keyword">from</span> crypto_gu.symmetric <span class="keyword">import</span> aes_gcm</span><br><span class="line"><span class="keyword">from</span> crypto_gu.attacks.rsa <span class="keyword">import</span> wiener</span><br><span class="line"></span><br><span class="line">sha256(<span class="string">b&quot;message&quot;</span>).<span class="built_in">hex</span>()</span><br><span class="line"></span><br><span class="line">key = <span class="built_in">bytes</span>(<span class="built_in">range</span>(<span class="number">32</span>))</span><br><span class="line">nonce = <span class="string">b&quot;\x00&quot;</span> * <span class="number">12</span></span><br><span class="line">sealed = aes_gcm.encrypt(key, nonce, <span class="string">b&quot;payload&quot;</span>, <span class="string">b&quot;header&quot;</span>)</span><br><span class="line"></span><br><span class="line">wiener(n, e)  <span class="comment"># d 过小时由 (n, e) 恢复私钥</span></span><br></pre></td></tr></table></figure><p>项目地址：<a href="https://github.com/TSVMV/crypto-gu">github.com&#x2F;TSVMV&#x2F;crypto-gu</a>。Python &gt;&#x3D; 3.11，pip install 就能用。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/25/crypto-gu-tool/</id>
    <link href="https://tsvmv.github.io/2026/09/25/crypto-gu-tool/"/>
    <published>2026-09-25T02:30:00.000Z</published>
    <summary>
      <![CDATA[<p>做 CTF 密码学题的时候，经常需要自己实现一些原语或者攻击。pycryptodome 能加密解密，但攻击（padding oracle、共模攻击、Wiener 这些）得自己写。每次比赛翻以前的 exp 复制粘贴，时间长了就想把这些东西整理成一个库。</p>
<p>crypto-gu 就是这么来的。纯 Python 标准库，零第三方依赖，把生产形态的密码学原语和确定性攻击实现放在同一个内核里。</p>]]>
    </summary>
    <title>crypto-gu：纯标准库密码学工具箱，原语和攻击放在同一个内核</title>
    <updated>2026-11-04T11:56:13.945Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="运维开发" scheme="https://tsvmv.github.io/categories/%E8%BF%90%E7%BB%B4%E5%BC%80%E5%8F%91/"/>
    <category term="逆向" scheme="https://tsvmv.github.io/tags/%E9%80%86%E5%90%91/"/>
    <category term="二进制" scheme="https://tsvmv.github.io/tags/%E4%BA%8C%E8%BF%9B%E5%88%B6/"/>
    <category term="ELF" scheme="https://tsvmv.github.io/tags/ELF/"/>
    <category term="PE" scheme="https://tsvmv.github.io/tags/PE/"/>
    <category term="工具" scheme="https://tsvmv.github.io/tags/%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<p>做逆向的时候，拿到一个二进制第一件事就是搞清楚它的骨架——哪些节区、入口在哪、依赖什么库、开了哪些加固。readelf 和 objdump 能用，但输出是纯文本，节区在文件里占多大比例、内存里怎么映射，全靠脑补。</p><p>所以写了 elfpeek，把 ELF 和 PE 解析成一张可视化结构图。</p><span id="more"></span><h2 id="能看什么"><a href="#能看什么" class="headerlink" title="能看什么"></a>能看什么</h2><p>文件总览：格式、位宽、端序、类型、机器架构、入口地址。PE 还会显示映像基址和子系统。</p><p>加固特性：ELF 这边查 PIE、NX、RELRO（完整还是部分）、Canary、FORTIFY、有没有 RWX 段。PE 这边查 ASLR、DEP、CFG、高熵 VA、SEHOP。这些是拿到二进制第一眼看的东西，加没加保护直接决定后续打法。</p><p>文件布局：水平条带，每个节区按文件大小比例着色，一眼看出哪个节区最大、有没有异常膨胀的段（可能藏了东西）。</p><p>内存布局：按运行时虚拟地址排序，标注地址区间和权限（RWX）。这个对 pwn 特别有用——哪里可写、哪里可执行，一目了然。</p><p>节区表：名称、偏移、地址、大小、标志，附节区熵值。高熵标红，提示可能加壳或压缩。</p><p>动态依赖：ELF 查 DT_NEEDED 共享库，PE 查导入动态库。</p><p>符号和函数：ELF 导入导出函数摘要，PE 导入导出函数列表。</p><p>字符串：可打印字符串提取，按 URL、IP、路径、共享库分类。</p><h2 id="怎么用"><a href="#怎么用" class="headerlink" title="怎么用"></a>怎么用</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 终端摘要</span></span><br><span class="line">elfpeek /bin/true</span><br><span class="line"></span><br><span class="line"><span class="comment"># 导出自包含 HTML 结构图（纯 SVG，无 JS，可截图存档）</span></span><br><span class="line">elfpeek /bin/true --html report.html</span><br><span class="line"></span><br><span class="line"><span class="comment"># 导出 JSON</span></span><br><span class="line">elfpeek /bin/true --json report.json</span><br></pre></td></tr></table></figure><p>HTML 报告是自包含的，纯 SVG 画布局图，不依赖任何外部资源，直接发给别人或者截图存档都行。</p><h2 id="实现上的选择"><a href="#实现上的选择" class="headerlink" title="实现上的选择"></a>实现上的选择</h2><p>纯 Python 标准库，零第三方依赖。用 struct 手工解析 ELF 头、程序头表、节区头表、动态段和符号表，32&#x2F;64 位、大小端自适应。PE 那边同样手工解析 DOS&#x2F;COFF&#x2F;可选头、节区、导入表导出表，含 RVA 到文件偏移换算。</p><p>只读解析，不加载不执行二进制里的任何代码。做安全工具这一点很重要——分析恶意样本时不能把样本跑起来。</p><p>项目地址：<a href="https://github.com/TSVMV/elfpeek">github.com&#x2F;TSVMV&#x2F;elfpeek</a>，pip install elfpeek 就能用。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/25/elfpeek-tool/</id>
    <link href="https://tsvmv.github.io/2026/09/25/elfpeek-tool/"/>
    <published>2026-09-25T02:00:00.000Z</published>
    <summary>
      <![CDATA[<p>做逆向的时候，拿到一个二进制第一件事就是搞清楚它的骨架——哪些节区、入口在哪、依赖什么库、开了哪些加固。readelf 和 objdump 能用，但输出是纯文本，节区在文件里占多大比例、内存里怎么映射，全靠脑补。</p>
<p>所以写了 elfpeek，把 ELF 和 PE 解析成一张可视化结构图。</p>]]>
    </summary>
    <title>elfpeek：把 ELF/PE 二进制拆成一张结构图</title>
    <updated>2026-11-04T11:56:13.946Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="CTF" scheme="https://tsvmv.github.io/categories/CTF/"/>
    <category term="CTF" scheme="https://tsvmv.github.io/tags/CTF/"/>
    <category term="密码学" scheme="https://tsvmv.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="WP" scheme="https://tsvmv.github.io/tags/WP/"/>
    <content>
      <![CDATA[<h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>这套题从 01 做到 55（中间跳了 41、42），工具链、古典密码、数论、分组密码、伪随机数、RSA、椭圆曲线、侧信道都过了一遍。写的时候就是边做边记，卡哪写哪。</p><p>几个反复用到的东西先写这儿，后面就不重复了。</p><p>模运算不用解释。模逆元就是模世界里的除法，<code>pow(a, -1, m)</code> 一行搞定，前提是 gcd(a,m)&#x3D;1。字节和十六进制来回转是日常：<code>bytes.fromhex()</code>、<code>int.to_bytes()</code>、pycryptodome 的 <code>long_to_bytes</code> &#x2F; <code>bytes_to_long</code>。异或记住 <code>a ^ b ^ b == a</code>，流密码的命根子。</p><p>chall.txt 的解析有个固定套路——前面带中文说明行，直接 exec 会炸，逐行抠：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br></pre></td></tr></table></figure><p>后面大量 exp 都用这个模式。环境就 <code>pip install pycryptodome sympy gmpy2</code>，05–07 用 SageMath 更顺手，没装也能做。</p><span id="more"></span><hr><h1 id="一、入门（01–08）"><a href="#一、入门（01–08）" class="headerlink" title="一、入门（01–08）"></a>一、入门（01–08）</h1><h2 id="01-欢迎来到密文大陆"><a href="#01-欢迎来到密文大陆" class="headerlink" title="01 欢迎来到密文大陆"></a>01 欢迎来到密文大陆</h2><p>ROT13。打开 chall.txt，英文字母被打乱了但数字没动，位置变了字符还在——这就是移位密码。挨个试一下发现整体后移了 13 位。26 个字母移 13 再移 13 正好回来，所以加解密同一个操作。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp01.py</span></span><br><span class="line">abc = <span class="string">&quot;abcdefghijklmnopqrstuvwxyz&quot;</span></span><br><span class="line">t = <span class="built_in">str</span>.maketrans(abc + abc.upper(),</span><br><span class="line">                   abc[<span class="number">13</span>:] + abc[:<span class="number">13</span>] + abc.upper()[<span class="number">13</span>:] + abc.upper()[:<span class="number">13</span>])</span><br><span class="line">ct = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>).read()</span><br><span class="line"><span class="built_in">print</span>(ct.translate(t))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{h3x_w31c0me_bdf64294}</code></p><p><code>str.maketrans</code> 配 <code>translate</code> 比手写循环省心。移位数不知道的话，1 到 25 全试一遍看哪个出人话。</p><h2 id="02-巨树的年轮"><a href="#02-巨树的年轮" class="headerlink" title="02 巨树的年轮"></a>02 巨树的年轮</h2><p>仿射密码。这次不只移位还乘了个系数：<code>c = (a*x + b) mod m</code>。题目把参数给得很清楚——字符域是可打印 ASCII 32 到 126 共 95 个字符，m&#x3D;95，字符编号 <code>x = ord(ch) - 32</code>，a&#x3D;29，b&#x3D;17。</p><p>解密就是反着来：<code>x = (c - b) * a^-1 mod 95</code>。前提是 a 和 m 互素才有逆元，29 和 95 没公因子，<code>pow(29, -1, 95)</code> 直接用。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp02.py</span></span><br><span class="line">a, b, mod = <span class="number">29</span>, <span class="number">17</span>, <span class="number">95</span></span><br><span class="line">a_inv = <span class="built_in">pow</span>(a, -<span class="number">1</span>, mod)</span><br><span class="line"></span><br><span class="line">ct = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>).read().strip()</span><br><span class="line">pt = <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="keyword">for</span> ch <span class="keyword">in</span> ct:</span><br><span class="line">    x = (<span class="built_in">ord</span>(ch) - <span class="number">32</span> - b) * a_inv % mod</span><br><span class="line">    pt += <span class="built_in">chr</span>(x + <span class="number">32</span>)</span><br><span class="line"><span class="built_in">print</span>(pt)</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{b1g_1nt_b1g_p0wer_30293ea3}</code></p><p>a 和 m 不互素的话不同明文会撞到同一个密文，解不回来，出题人选参数必须避开。</p><h2 id="03-青铜试炼"><a href="#03-青铜试炼" class="headerlink" title="03 青铜试炼"></a>03 青铜试炼</h2><p>这题没什么攻击，纯考你会不会把三个库串起来。chall.txt 给了 p、a、s、ct，题面把密钥怎么造写得明明白白：先算 a 模 p 的逆元，把”逆元的十进制字符串 + 盐 s”扔进 SHA-256，取摘要十六进制串前 16 个字符当 AES 密钥，ECB 解密。</p><p>照着做就行。翻车全在类型上：逆元是 int，拼字符串前要 str()；哈希输入要 .encode()；hexdigest() 出来是字符串，取前 16 字符再 encode 成 bytes 才能当密钥。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp03.py</span></span><br><span class="line"><span class="keyword">import</span> gmpy2, hashlib</span><br><span class="line"><span class="keyword">from</span> Crypto.Cipher <span class="keyword">import</span> AES</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.Padding <span class="keyword">import</span> unpad</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">p, a = <span class="built_in">int</span>(ns[<span class="string">&quot;p&quot;</span>], <span class="number">16</span>), <span class="built_in">int</span>(ns[<span class="string">&quot;a&quot;</span>], <span class="number">16</span>)   <span class="comment"># 这题数字是十六进制</span></span><br><span class="line">s = ns[<span class="string">&quot;s&quot;</span>]</span><br><span class="line">ct = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct&quot;</span>])</span><br><span class="line"></span><br><span class="line">inv = gmpy2.invert(a, p)</span><br><span class="line">key = hashlib.sha256((<span class="built_in">str</span>(inv) + s).encode()).hexdigest()[:<span class="number">16</span>].encode()</span><br><span class="line"><span class="built_in">print</span>(unpad(AES.new(key, AES.MODE_ECB).decrypt(ct), <span class="number">16</span>).decode())</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{lib_1s_y0ur_bl4de_e2788ba8}</code></p><p>做密码题一半的报错来自 int&#x2F;str&#x2F;bytes 互转搞错。写之前先想清楚每步输入输出什么类型，能少调半天。</p><h2 id="04-试炼塔第一层"><a href="#04-试炼塔第一层" class="headerlink" title="04 试炼塔第一层"></a>04 试炼塔第一层</h2><p>列置换。前面几题都在改字符，这题一个字符不改，只把位置打乱。</p><p>加密是把明文按行填进宽度为 key 长度的矩阵，然后按 key 的字母序一列一列读出来。key 是 KEY，字典序 E&lt;K&lt;Y，先读 E 对应的第 1 列，再第 0 列，最后第 2 列。解密倒着走，按同样列顺序把密文切回各列再按行拼回去。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp04.py</span></span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line">raw = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read()</span><br><span class="line">key = re.search(<span class="string">r&quot;key\s*=\s*(\w+)&quot;</span>, raw).group(<span class="number">1</span>)</span><br><span class="line">ct = raw.split(<span class="string">&quot;ct = &quot;</span>, <span class="number">1</span>)[<span class="number">1</span>].strip()          <span class="comment"># ct 跨了两行，整段读</span></span><br><span class="line"></span><br><span class="line">order = <span class="built_in">sorted</span>(<span class="built_in">range</span>(<span class="built_in">len</span>(key)), key=<span class="keyword">lambda</span> i: key[i])   <span class="comment"># [1, 0, 2]</span></span><br><span class="line">ncols = <span class="built_in">len</span>(key)</span><br><span class="line">nrows = -(-<span class="built_in">len</span>(ct) // ncols)                   <span class="comment"># 上取整</span></span><br><span class="line">padded = ct.ljust(nrows * ncols, <span class="string">&quot;#&quot;</span>)</span><br><span class="line"></span><br><span class="line">cols, idx = &#123;&#125;, <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> c <span class="keyword">in</span> order:</span><br><span class="line">    cols[c] = padded[idx:idx + nrows]</span><br><span class="line">    idx += nrows</span><br><span class="line"></span><br><span class="line">pt = <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="keyword">for</span> r <span class="keyword">in</span> <span class="built_in">range</span>(nrows):</span><br><span class="line">    <span class="keyword">for</span> c <span class="keyword">in</span> <span class="built_in">range</span>(ncols):</span><br><span class="line">        <span class="keyword">if</span> r &lt; <span class="built_in">len</span>(cols[c]):</span><br><span class="line">            pt += cols[c][r]</span><br><span class="line"><span class="built_in">print</span>(pt.rstrip(<span class="string">&quot;#&quot;</span>))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{c01umn4r_tr4nsp0s1t10n_{9c2f8e17}}</code></p><p>踩了两个坑：一是 ct 断成两行，只读第一行得到一堆乱序字符；二是补齐的 # 最后要去掉。<code>sorted(range(n), key=...)</code> 对下标排序这个写法记住。</p><h2 id="05-长阶递推"><a href="#05-长阶递推" class="headerlink" title="05 长阶递推"></a>05 长阶递推</h2><p>FLAG 第 i 个字节当第 i 个质数的指数，全乘起来得 N。<code>N = 2^b1 * 3^b2 * 5^b3 * ...</code>，b1 是第一个字节的 ASCII 值。</p><p>还原靠算术基本定理，分解 N 读指数就行。N 四千多位看着吓人，但质因数全是小质数（大的都在指数上），sympy factorint 秒出，SageMath 里 factor(N) 一样。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp05.py</span></span><br><span class="line"><span class="keyword">import</span> re, sys</span><br><span class="line">sys.set_int_max_str_digits(<span class="number">100000</span>)     <span class="comment"># Python 3.11 默认限 4300 位，先放开</span></span><br><span class="line"><span class="keyword">from</span> sympy <span class="keyword">import</span> factorint</span><br><span class="line"></span><br><span class="line">lines = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read().splitlines()</span><br><span class="line">i = <span class="built_in">next</span>(k <span class="keyword">for</span> k, l <span class="keyword">in</span> <span class="built_in">enumerate</span>(lines) <span class="keyword">if</span> l.startswith(<span class="string">&quot;N = &quot;</span>))</span><br><span class="line">N = <span class="built_in">int</span>(re.sub(<span class="string">r&quot;\D&quot;</span>, <span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>.join(lines[i:])))   <span class="comment"># N 跨行，拼起来</span></span><br><span class="line"></span><br><span class="line">fac = factorint(N)</span><br><span class="line">primes = [<span class="number">2</span>,<span class="number">3</span>,<span class="number">5</span>,<span class="number">7</span>,<span class="number">11</span>,<span class="number">13</span>,<span class="number">17</span>,<span class="number">19</span>,<span class="number">23</span>,<span class="number">29</span>,<span class="number">31</span>,<span class="number">37</span>,<span class="number">41</span>,<span class="number">43</span>,<span class="number">47</span>,<span class="number">53</span>,<span class="number">59</span>,<span class="number">61</span>,<span class="number">67</span>,<span class="number">71</span>,<span class="number">73</span>,<span class="number">79</span>,<span class="number">83</span>,<span class="number">89</span>,<span class="number">97</span>,<span class="number">101</span>,<span class="number">103</span>,<span class="number">107</span>,<span class="number">109</span>,<span class="number">113</span>,<span class="number">127</span>]</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(fac[p] <span class="keyword">for</span> p <span class="keyword">in</span> primes <span class="keyword">if</span> p <span class="keyword">in</span> fac))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{s4ge_zz_f4ct0r_{41c2c039}}</code></p><p>三个坑：Python 3.11 起整数转字符串默认限 4300 位不放开直接报错；N 跨行要拼；说明文字里也出现过 “N”，定位要从以 <code>N = </code> 开头那行开始。大整数能不能分解看质因子结构，其次才是位数。</p><h2 id="06-异界投影"><a href="#06-异界投影" class="headerlink" title="06 异界投影"></a>06 异界投影</h2><p>和 02 一个模子，模 95 换成模素数 p。加密 <code>c = (a*t + b) mod p</code>，解密 <code>(c - b) * a^-1 mod p</code>，没新东西。这题主要是引入 GF(p)——模素数 p 的算术世界里加减乘除全通，叫”域”。SageMath 里写 <code>GF(p)((x-b)/a)</code>，除号自动就是乘逆元。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp06.py</span></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">p, a, b = <span class="built_in">int</span>(ns[<span class="string">&quot;p&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;a&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;b&quot;</span>])</span><br><span class="line">ct = <span class="built_in">eval</span>(ns[<span class="string">&quot;ct&quot;</span>])</span><br><span class="line">ainv = <span class="built_in">pow</span>(a, -<span class="number">1</span>, p)</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(((x - b) * ainv) % p <span class="keyword">for</span> x <span class="keyword">in</span> ct))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{s4ge_gf16_pr0j3ct_{2a0297a4}}</code></p><p>以后看到模运算语境里的 <code>/</code>，就是 <code>pow(除数, -1, p)</code>。</p><h2 id="07-三角之钥"><a href="#07-三角之钥" class="headerlink" title="07 三角之钥"></a>07 三角之钥</h2><p>密文 <code>b = A * x (mod p)</code>，x 每个分量一个明文字节。解方程组就行，中学高斯消元搬过来，就两处要改：除法换乘逆元，所有加减乘取模。</p><p>SageMath 一行：<code>Matrix(GF(p), A).solve_right(vector(GF(p), b))</code>。手写也不长：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp07.py</span></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">p, A, b = <span class="built_in">int</span>(ns[<span class="string">&quot;p&quot;</span>]), <span class="built_in">eval</span>(ns[<span class="string">&quot;A&quot;</span>]), <span class="built_in">eval</span>(ns[<span class="string">&quot;b&quot;</span>])</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">solve_mod</span>(<span class="params">A, b, p</span>):</span><br><span class="line">    n = <span class="built_in">len</span>(A)</span><br><span class="line">    M = [row[:] <span class="keyword">for</span> row <span class="keyword">in</span> A]</span><br><span class="line">    v = b[:]</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(n):</span><br><span class="line">        <span class="keyword">for</span> k <span class="keyword">in</span> <span class="built_in">range</span>(i, n):       <span class="comment"># 选第 i 列非零行当主元</span></span><br><span class="line">            <span class="keyword">if</span> M[k][i] % p:</span><br><span class="line">                M[i], M[k] = M[k], M[i]</span><br><span class="line">                v[i], v[k] = v[k], v[i]</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">        inv = <span class="built_in">pow</span>(M[i][i], -<span class="number">1</span>, p)</span><br><span class="line">        M[i] = [(x * inv) % p <span class="keyword">for</span> x <span class="keyword">in</span> M[i]]</span><br><span class="line">        v[i] = (v[i] * inv) % p</span><br><span class="line">        <span class="keyword">for</span> k <span class="keyword">in</span> <span class="built_in">range</span>(n):</span><br><span class="line">            <span class="keyword">if</span> k != i <span class="keyword">and</span> M[k][i]:</span><br><span class="line">                f = M[k][i]</span><br><span class="line">                M[k] = [(M[k][j] - f * M[i][j]) % p <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(n)]</span><br><span class="line">                v[k] = (v[k] - f * v[i]) % p</span><br><span class="line">    <span class="keyword">return</span> v</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(solve_mod(A, b, p)))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{s4ge_m4trix_s0lver_{1e674f01}}</code></p><p>模 p 高斯消元后面 ECC 那几章要用，自己写一遍比较踏实。</p><h2 id="08-古老约定"><a href="#08-古老约定" class="headerlink" title="08 古老约定"></a>08 古老约定</h2><p>每组数据 <code>(t*k, t*l)</code>，k、l 互素。gcd 的性质 <code>gcd(t*k, t*l) = t * gcd(k, l) = t</code>，因为 gcd(k,l)&#x3D;1。每对数字求个 gcd，明文字节自己跳出来。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp08.py</span></span><br><span class="line"><span class="keyword">import</span> ast, math</span><br><span class="line"></span><br><span class="line">pairs = ast.literal_eval(<span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read().split(<span class="string">&quot;pairs = &quot;</span>)[<span class="number">1</span>].strip())</span><br><span class="line">a, b = pairs</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(math.gcd(x, y) <span class="keyword">for</span> x, y <span class="keyword">in</span> <span class="built_in">zip</span>(a, b)))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{ex_gcd_1s_p0w3rful_{aa0139c6}}</code></p><p>题目提到扩展欧几里得（egcd），求 gcd 的同时给出 u、v 满足 <code>u*a + v*b = gcd(a,b)</code>。背一份，38 题共模攻击直接用：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">egcd</span>(<span class="params">a, b</span>):</span><br><span class="line">    <span class="keyword">if</span> b == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> a, <span class="number">1</span>, <span class="number">0</span></span><br><span class="line">    g, x, y = egcd(b, a % b)</span><br><span class="line">    <span class="keyword">return</span> g, y, x - (a // b) * y</span><br></pre></td></tr></table></figure><p>读数据用 <code>ast.literal_eval</code> 代替 <code>eval</code>，只解析字面量，安全点。</p><hr><h1 id="二、数论（09–14）"><a href="#二、数论（09–14）" class="headerlink" title="二、数论（09–14）"></a>二、数论（09–14）</h1><h2 id="09-互质之墙"><a href="#09-互质之墙" class="headerlink" title="09 互质之墙"></a>09 互质之墙</h2><p>每个密文元素 <code>x = t^-1 (mod p)</code>，明文字节被换成了自己的逆元。互逆是对称的——t 的逆元是 x，x 的逆元就是 t。每个密文再求一次逆元就回来了。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp09.py</span></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">p = <span class="built_in">int</span>(ns[<span class="string">&quot;p&quot;</span>])</span><br><span class="line">ct = <span class="built_in">eval</span>(ns[<span class="string">&quot;ct&quot;</span>])</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(<span class="built_in">pow</span>(x, -<span class="number">1</span>, p) <span class="keyword">for</span> x <span class="keyword">in</span> ct))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{inv_m0d_1s_k3y_{a5abaac7}}</code></p><p><code>pow(x, -1, p)</code> 抛 ValueError 说明 x 和 p 不互素，这本身就是提示。</p><h2 id="10-费马的秘密"><a href="#10-费马的秘密" class="headerlink" title="10 费马的秘密"></a>10 费马的秘密</h2><p>加密 <code>ci = t^(p-2) mod p</code>，看着唬人其实就是求逆元。费马小定理说 p 是素数且 t 不是 p 的倍数时 <code>t^(p-1) ≡ 1 (mod p)</code>，两边除 t 得 <code>t^(p-2) ≡ t^-1 (mod p)</code>。求 p-2 次幂和求逆元是一回事。</p><p>逆元的逆元是自己，对每个 ci 再算一次 <code>ci^(p-2)</code> 就行：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp10.py</span></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">p = <span class="built_in">int</span>(ns[<span class="string">&quot;p&quot;</span>])</span><br><span class="line">ct = <span class="built_in">eval</span>(ns[<span class="string">&quot;ct&quot;</span>])</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(<span class="built_in">pow</span>(c, p - <span class="number">2</span>, p) <span class="keyword">for</span> c <span class="keyword">in</span> ct))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{ferm4t_s4ys_1nvert_{a639e9e9}}</code></p><p><code>a^(p-2) ≡ a^-1 (mod p)</code> 记住，素数域的题里常出。</p><h2 id="11-欧拉的馈赠"><a href="#11-欧拉的馈赠" class="headerlink" title="11 欧拉的馈赠"></a>11 欧拉的馈赠</h2><p>迷你 RSA：<code>c = t^e mod n</code>，n &#x3D; p*q，p、q 接近。</p><p>费马小定理要求模数是素数，欧拉推广到任意模数。φ(n) 是 1 到 n 里和 n 互素的数的个数，n&#x3D;pq 时 <code>φ(n) = (p-1)(q-1)</code>。欧拉定理说 gcd(t,n)&#x3D;1 时 <code>t^φ(n) ≡ 1 (mod n)</code>。</p><p>RSA 就是挑个 e 使 <code>e*d ≡ 1 (mod φ(n))</code>，于是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(t^e)^d = t^(e*d) = t^(k*φ(n)+1) = (t^φ(n))^k * t ≡ t (mod n)</span><br></pre></td></tr></table></figure><p>想算 d 必须知道 φ(n)，想知道 φ(n) 必须分解 n。这就是 RSA 安全的全部来源。</p><p>p、q 接近时用费马分解：<code>n = a² - b²</code>，a 约 √n。从 ⌈√n⌉ 开始试 a，看 <code>a² - n</code> 是不是完全平方数。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp11.py</span></span><br><span class="line"><span class="keyword">import</span> math</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n, e, ct = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>]), <span class="built_in">eval</span>(ns[<span class="string">&quot;ct&quot;</span>])</span><br><span class="line"></span><br><span class="line">a = math.isqrt(n) + <span class="number">1</span></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    b2 = a * a - n</span><br><span class="line">    b = math.isqrt(b2)</span><br><span class="line">    <span class="keyword">if</span> b * b == b2:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">    a += <span class="number">1</span></span><br><span class="line">p, q = a - b, a + b</span><br><span class="line"></span><br><span class="line">d = <span class="built_in">pow</span>(e, -<span class="number">1</span>, (p - <span class="number">1</span>) * (q - <span class="number">1</span>))</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(<span class="built_in">pow</span>(c, d, n) <span class="keyword">for</span> c <span class="keyword">in</span> ct))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{ph1_eul3r_1s_tr1cky_{175f7b6d}}</code></p><p>“分解 → φ → d → 解密”这条流水线是 RSA 整章的地基，后面十几题全是变体。求平方根用 <code>math.isqrt</code>，浮点 sqrt 在大数上会精度出错。</p><h2 id="12-余数的密语"><a href="#12-余数的密语" class="headerlink" title="12 余数的密语"></a>12 余数的密语</h2><p>CRT。明文拼成大整数 x，分别模三个两两互素的模数得到三个余数。知道几个模世界里的余数，在 <code>m1*m2*m3</code> 范围内唯一确定 x：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">x = ( a1*M1*t1 + a2*M2*t2 + a3*M3*t3 ) mod M</span><br><span class="line">    M = m1*m2*m3, Mi = M/mi, ti = Mi 模 mi 的逆元</span><br></pre></td></tr></table></figure><p>为什么成立？拿第一项看，<code>M1*t1 ≡ 1 (mod m1)</code>，这一项模 m1 等于 a1；另外两项都含 M1，模 m1 全是 0。每个方程自己那项满足、其他项放过，加起来全对。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp12.py</span></span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line">raw = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read()</span><br><span class="line">m1, m2, m3 = [<span class="built_in">int</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> re.search(<span class="string">r&quot;^m1, m2, m3 = (.+)$&quot;</span>, raw, re.M).group(<span class="number">1</span>).split(<span class="string">&quot;,&quot;</span>)]</span><br><span class="line">a1, a2, a3 = [<span class="built_in">int</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> re.search(<span class="string">r&quot;^a1, a2, a3 = (.+)$&quot;</span>, raw, re.M).group(<span class="number">1</span>).split(<span class="string">&quot;,&quot;</span>)]</span><br><span class="line"></span><br><span class="line">M = m1 * m2 * m3</span><br><span class="line">x = (a1 * (M//m1) * <span class="built_in">pow</span>(M//m1, -<span class="number">1</span>, m1)</span><br><span class="line">   + a2 * (M//m2) * <span class="built_in">pow</span>(M//m2, -<span class="number">1</span>, m2)</span><br><span class="line">   + a3 * (M//m3) * <span class="built_in">pow</span>(M//m3, -<span class="number">1</span>, m3)) % M</span><br><span class="line"><span class="built_in">print</span>(x.to_bytes((x.bit_length() + <span class="number">7</span>) // <span class="number">8</span>, <span class="string">&quot;big&quot;</span>))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{cr1t_s1lm3r_m0rf3v_{bfd3c20a}}</code></p><p>sympy 有现成的 <code>crt()</code>。CRT 后面反复用——39 题广播攻击、50 题 Pohlig-Hellman 都靠它合回结果。模数两两互素是前提，不互素公式直接失效。</p><h2 id="13-原根之杖"><a href="#13-原根之杖" class="headerlink" title="13 原根之杖"></a>13 原根之杖</h2><p><code>y = g^t mod p</code>，g 是 p 的原根，t 是明文字节。离散对数一般很难，但这题 t 只有 256 种取值——预计算 g^0 到 g^255 存字典，拿密文查表完事。</p><p>原根为什么重要？元素 g 的”阶”是最小的 k 使 <code>g^k ≡ 1 (mod p)</code>。阶恰好 p-1 就叫原根，保证 g^0 到 g^(p-2) 两两不同，每个字节唯一加密唯一还原。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp13.py</span></span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line">raw = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read()</span><br><span class="line">p = <span class="built_in">int</span>(re.search(<span class="string">r&quot;^p = (\d+)$&quot;</span>, raw, re.M).group(<span class="number">1</span>))</span><br><span class="line">g = <span class="built_in">int</span>(re.search(<span class="string">r&quot;^g = (\d+)$&quot;</span>, raw, re.M).group(<span class="number">1</span>))</span><br><span class="line">ct = <span class="built_in">eval</span>(re.search(<span class="string">r&quot;^ct = \[(.*)\]$&quot;</span>, raw, re.M).group(<span class="number">0</span>).split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)[<span class="number">1</span>])</span><br><span class="line"></span><br><span class="line">table = &#123;<span class="built_in">pow</span>(g, i, p): i <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">256</span>)&#125;</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(table[y] <span class="keyword">for</span> y <span class="keyword">in</span> ct))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{pr1m4t3_r00t_0rd3r_{5eacf3b2}}</code></p><p>离散对数难度看指数空间大小。256 查表就行，2^256 就得靠算法了。</p><h2 id="14-离散之梯"><a href="#14-离散之梯" class="headerlink" title="14 离散之梯"></a>14 离散之梯</h2><p>BSGS。和 13 同款加密，这次正经用 BSGS 解。把指数劈两半：<code>t = i*m + j</code>，代进 <code>y = g^t</code> 移项：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">y * (g^-m)^i = g^j</span><br></pre></td></tr></table></figure><p>左边从 y 出发每次乘 g^-m 最多走 m 步，右边 g^j 只有 m 个先算好存表。两边撞上时 <code>t = i*m + j</code>。时间空间都是 O(√p)。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp14.py</span></span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line">raw = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read()</span><br><span class="line">p = <span class="built_in">int</span>(re.search(<span class="string">r&quot;^p = (\d+)$&quot;</span>, raw, re.M).group(<span class="number">1</span>))</span><br><span class="line">g = <span class="built_in">int</span>(re.search(<span class="string">r&quot;^g = (\d+)$&quot;</span>, raw, re.M).group(<span class="number">1</span>))</span><br><span class="line">ct = <span class="built_in">eval</span>(re.search(<span class="string">r&quot;^ct = \[(.*)\]$&quot;</span>, raw, re.M).group(<span class="number">0</span>).split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)[<span class="number">1</span>])</span><br><span class="line"></span><br><span class="line">m = <span class="number">16</span>                                        <span class="comment"># 本题 t &lt; 256，取 16 够</span></span><br><span class="line">tbl = &#123;<span class="built_in">pow</span>(g, j, p): j <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(m)&#125;     <span class="comment"># baby steps</span></span><br><span class="line">factor = <span class="built_in">pow</span>(g, -m, p)                        <span class="comment"># giant step 乘子</span></span><br><span class="line"></span><br><span class="line">msg = []</span><br><span class="line"><span class="keyword">for</span> y <span class="keyword">in</span> ct:</span><br><span class="line">    cur = y</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(m):</span><br><span class="line">        <span class="keyword">if</span> cur <span class="keyword">in</span> tbl:</span><br><span class="line">            msg.append(i * m + tbl[cur])</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">        cur = cur * factor % p</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(msg))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{bsgs_1s_s0_f4st_{ce81f699}}</code></p><p>√n 这个量级记住：40 位的群 BSGS 瞬间出解，256 位的还是没戏。ECC 章节（50–52）会把它当子工具反复用。</p><hr><h1 id="三、古典密码变奏（15–20）"><a href="#三、古典密码变奏（15–20）" class="headerlink" title="三、古典密码变奏（15–20）"></a>三、古典密码变奏（15–20）</h1><h2 id="15-序的迷宫"><a href="#15-序的迷宫" class="headerlink" title="15 序的迷宫"></a>15 序的迷宫</h2><p>栅栏密码。明文沿锯齿写在 k 行：第 1 个放第 0 行，第 2 个放第 1 行，第 3 个又回第 0 行……写完逐行读出。k&#x3D;2 时偶数位全进第 0 行，奇数位进第 1 行。加密结果 &#x3D; 偶位串 + 奇位串，解密就是前一半填回偶数位、后一半填回奇数位。</p><p>踩了个坑：ct 跨行要整段读。还有更坑的——明文其实是”说明句子 + FLAG”两段分别独立加密后拼在一起的，整段一起解会得到正确句子配上乱码 flag。对每个可能的分段点把尾部单独做栅栏还原，看到 <code>flag{</code> 就是它。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp15.py</span></span><br><span class="line">raw = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read()</span><br><span class="line">ct = raw.split(<span class="string">&quot;ct = &quot;</span>, <span class="number">1</span>)[<span class="number">1</span>].replace(<span class="string">&quot;\n&quot;</span>, <span class="string">&quot;&quot;</span>).strip()</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">zigdec</span>(<span class="params">s</span>):</span><br><span class="line">    n = <span class="built_in">len</span>(s)</span><br><span class="line">    half = (n + <span class="number">1</span>) // <span class="number">2</span></span><br><span class="line">    r0, r1 = s[:half], s[half:]</span><br><span class="line">    pt = <span class="string">&quot;&quot;</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(n):</span><br><span class="line">        <span class="keyword">if</span> i % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">            pt += r0[i // <span class="number">2</span>] <span class="keyword">if</span> i // <span class="number">2</span> &lt; <span class="built_in">len</span>(r0) <span class="keyword">else</span> <span class="string">&quot;&quot;</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            pt += r1[i // <span class="number">2</span>] <span class="keyword">if</span> i // <span class="number">2</span> &lt; <span class="built_in">len</span>(r1) <span class="keyword">else</span> <span class="string">&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> pt</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> b <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(ct)):</span><br><span class="line">    d = zigdec(ct[b:])</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot;flag&#123;&quot;</span> <span class="keyword">in</span> d:</span><br><span class="line">        <span class="built_in">print</span>(d)</span><br><span class="line">        <span class="keyword">break</span></span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{r41l_f3nc3_c1ph3r_{8b3a5d2c}}</code></p><p>解出来半对半乱的时候，第一反应分段加密。</p><h2 id="16-异或之镜"><a href="#16-异或之镜" class="headerlink" title="16 异或之镜"></a>16 异或之镜</h2><p>单字节 XOR。密文一个单字节密钥逐字节 XOR，密钥只有 256 种可能，全试一遍哪个以 <code>flag{</code> 开头就是对的。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp16.py</span></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">ct = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct&quot;</span>])</span><br><span class="line"><span class="keyword">for</span> k <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">256</span>):</span><br><span class="line">    m = <span class="built_in">bytes</span>(b ^ k <span class="keyword">for</span> b <span class="keyword">in</span> ct)</span><br><span class="line">    <span class="keyword">if</span> m.startswith(<span class="string">b&quot;flag&#123;&quot;</span>):</span><br><span class="line">        <span class="built_in">print</span>(<span class="built_in">hex</span>(k), m.decode())</span><br><span class="line">        <span class="keyword">break</span></span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{x0r_s1ngl3_byt3_brut3_{d1c4e7b9}}</code></p><p>flag 格式本身就是校验条件。没有前缀线索就给候选解打”可打印字符占比”分取最高。</p><h2 id="17-移位齿轮"><a href="#17-移位齿轮" class="headerlink" title="17 移位齿轮"></a>17 移位齿轮</h2><p>希尔密码。02 题仿射一次处理一个字符，希尔一次处理一组：两个字符拼成向量乘 2×2 矩阵 K 再 mod 95。分组密码最原始的雏形，AES 一次处理 16 字节精神上就是它的巨型后代。</p><p>解密要 K 的逆矩阵。2×2 公式手算友好：<code>K^-1 = det(K)^-1 * adj(K)</code>，伴随矩阵主对角互换副对角变号。<code>det(K) = 3*5 - 3*2 = 9</code>，和 95 互素逆元存在。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp17.py</span></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">K = <span class="built_in">eval</span>(ns[<span class="string">&quot;K&quot;</span>])</span><br><span class="line">ct = ns[<span class="string">&quot;ct&quot;</span>]</span><br><span class="line">mod = <span class="number">95</span></span><br><span class="line"></span><br><span class="line">det = (K[<span class="number">0</span>][<span class="number">0</span>] * K[<span class="number">1</span>][<span class="number">1</span>] - K[<span class="number">0</span>][<span class="number">1</span>] * K[<span class="number">1</span>][<span class="number">0</span>]) % mod</span><br><span class="line">dinv = <span class="built_in">pow</span>(det, -<span class="number">1</span>, mod)</span><br><span class="line">K_inv = [[ K[<span class="number">1</span>][<span class="number">1</span>] * dinv % mod, -K[<span class="number">0</span>][<span class="number">1</span>] * dinv % mod],</span><br><span class="line">         [-K[<span class="number">1</span>][<span class="number">0</span>] * dinv % mod,  K[<span class="number">0</span>][<span class="number">0</span>] * dinv % mod]]</span><br><span class="line"></span><br><span class="line">vals = [<span class="built_in">ord</span>(c) - <span class="number">32</span> <span class="keyword">for</span> c <span class="keyword">in</span> ct]</span><br><span class="line">pt = <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="built_in">len</span>(vals), <span class="number">2</span>):</span><br><span class="line">    v = vals[i:i + <span class="number">2</span>]</span><br><span class="line">    pt += <span class="built_in">chr</span>((K_inv[<span class="number">0</span>][<span class="number">0</span>]*v[<span class="number">0</span>] + K_inv[<span class="number">0</span>][<span class="number">1</span>]*v[<span class="number">1</span>]) % mod + <span class="number">32</span>)</span><br><span class="line">    pt += <span class="built_in">chr</span>((K_inv[<span class="number">1</span>][<span class="number">0</span>]*v[<span class="number">0</span>] + K_inv[<span class="number">1</span>][<span class="number">1</span>]*v[<span class="number">1</span>]) % mod + <span class="number">32</span>)</span><br><span class="line"><span class="built_in">print</span>(pt)</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{h1ll_c1ph3r_m4tr1x_{7a9f2c4e}}</code></p><p>逆矩阵存在当且仅当行列式和模数互素，和仿射密码 a 要与 m 互素一个道理的矩阵版。</p><h2 id="18-编码双面人"><a href="#18-编码双面人" class="headerlink" title="18 编码双面人"></a>18 编码双面人</h2><p>ROT47。ROT13 只动 26 个字母，ROT47 作用域扩大到全部可打印 ASCII：从 <code>!</code>(33) 到 <code>~</code>(126) 共 94 个字符统一后移 47 位。94 是 47 的两倍，自逆，再做一遍还原。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp18.py</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">rot47</span>(<span class="params">s</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;&quot;</span>.join(<span class="built_in">chr</span>(<span class="number">33</span> + (<span class="built_in">ord</span>(c) - <span class="number">33</span> + <span class="number">47</span>) % <span class="number">94</span>)</span><br><span class="line">                   <span class="keyword">if</span> <span class="number">33</span> &lt;= <span class="built_in">ord</span>(c) &lt;= <span class="number">126</span> <span class="keyword">else</span> c</span><br><span class="line">                   <span class="keyword">for</span> c <span class="keyword">in</span> s)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(rot47(<span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>).read()))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{r0t47_a5cii_sh1ft_{3b6d9a1f}}</code></p><p>密文里连数字都”乱”了就往 ROT47 想，ROT13 只管字母。</p><h2 id="19-凯撒的变奏"><a href="#19-凯撒的变奏" class="headerlink" title="19 凯撒的变奏"></a>19 凯撒的变奏</h2><p>维吉尼亚。凯撒只有一个移位量频率分析一打就穿，维吉尼亚用循环密钥引入多个移位：第 1 个移 K 的量、第 2 个移 E、第 3 个移 Y、第 4 个回 K……同一个字母在不同位置被移不同量，单字母统计失效。</p><p>key 给了，逐字符减密钥流就行。编号基准还是 32，字符和密钥都先减 32 再 mod 95。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp19.py</span></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">key, ct = ns[<span class="string">&quot;key&quot;</span>], ns[<span class="string">&quot;ct&quot;</span>]</span><br><span class="line">msg = <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="keyword">for</span> i, c <span class="keyword">in</span> <span class="built_in">enumerate</span>(ct):</span><br><span class="line">    shift = <span class="built_in">ord</span>(key[i % <span class="built_in">len</span>(key)]) - <span class="number">32</span></span><br><span class="line">    msg += <span class="built_in">chr</span>((<span class="built_in">ord</span>(c) - <span class="number">32</span> - shift) % <span class="number">95</span> + <span class="number">32</span>)</span><br><span class="line"><span class="built_in">print</span>(msg)</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{v1g3n3r3_c1ph3r_m4th_{c71c32e4}}</code></p><p>把”减法密钥流”换成”XOR 密钥流”维吉尼亚就变成流密码了。key 未知时先猜密钥长度（Kasiski 或重合指数），按位置分组退化成凯撒做频率分析。</p><h2 id="20-字母的指纹"><a href="#20-字母的指纹" class="headerlink" title="20 字母的指纹"></a>20 字母的指纹</h2><p>频率分析。连偏移量都不给，只说每个可打印字符偏移了固定位数。95 个字符域偏移最多 94 种，暴力全试人工挑也行，但频率分析更省事——英文里空格、e、t 出现频率远高于其他。统计密文里哪个字符最多，大概率对应原文空格（编号 32），两个编号一减偏移就出来了。</p><p>这题算出来偏移 7：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp20.py</span></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">ct = ns[<span class="string">&quot;ct&quot;</span>]</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;&quot;</span>.join(<span class="built_in">chr</span>((<span class="built_in">ord</span>(c) - <span class="number">32</span> - <span class="number">7</span>) % <span class="number">95</span> + <span class="number">32</span>) <span class="keyword">for</span> c <span class="keyword">in</span> ct))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 偏移未知时通用爆破，人工挑可读的：</span></span><br><span class="line"><span class="comment"># for k in range(1, 95):</span></span><br><span class="line"><span class="comment">#     print(k, &quot;&quot;.join(chr((ord(c)-32-k) % 95 + 32) for c in ct))</span></span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{frequ3ncy_4n4lys1s_{bccd9e92}}</code></p><p>建议写个自动打分版（英文字符占比最高的 k 胜出），以后凯撒题一键解决。</p><hr><h1 id="四、分组密码（21–25）"><a href="#四、分组密码（21–25）" class="headerlink" title="四、分组密码（21–25）"></a>四、分组密码（21–25）</h1><h2 id="21-镜中块"><a href="#21-镜中块" class="headerlink" title="21 镜中块"></a>21 镜中块</h2><p>AES-ECB。明文切 16 字节一块，密钥控制下多轮替换移位混淆输出密文块。ECB 是最原始的工作模式——每块独立加密互不掺和。</p><p>独立意味着相同明文块加密出相同密文块，密文里的图案就是明文的图案。ECB 企鹅图就是这么来的。key 直接给了，逐块解密 unpad 就行。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp21.py</span></span><br><span class="line"><span class="keyword">from</span> Crypto.Cipher <span class="keyword">import</span> AES</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.Padding <span class="keyword">import</span> unpad</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">key = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;key&quot;</span>])</span><br><span class="line">ct = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct&quot;</span>])</span><br><span class="line"><span class="built_in">print</span>(unpad(AES.new(key, AES.MODE_ECB).decrypt(ct), <span class="number">16</span>).decode())</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{ecb_l34k5_bl0ck_b0und4ry_{af4dd734}}</code></p><p><code>AES.new(key, 模式) → .decrypt(ct) → unpad(..., 16)</code> 三步以后每题都写。</p><h2 id="22-翻转之咒"><a href="#22-翻转之咒" class="headerlink" title="22 翻转之咒"></a>22 翻转之咒</h2><p>CBC。每块明文先和前一块密文异或再加密，第一块用 IV 顶上。解密反过来：先 ECB 解出中间值，再异或前一块密文。</p><p>链式结构有个经典性质：改密文第 i 块某一位，解密后第 i 块明文报废，但第 i+1 块对应位被精确翻转——<code>P_{i+1} = D(C_{i+1}) XOR C_i</code>，C_i 的位翻转原样传给 P_{i+1}。这就是比特翻转攻击，全程不要密钥。</p><p>这题要求手写 CBC 解密：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp22.py</span></span><br><span class="line"><span class="keyword">from</span> Crypto.Cipher <span class="keyword">import</span> AES</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.Padding <span class="keyword">import</span> unpad</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">key, iv, ct = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;key&quot;</span>]), <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;iv&quot;</span>]), <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct&quot;</span>])</span><br><span class="line"></span><br><span class="line">ecb = AES.new(key, AES.MODE_ECB)</span><br><span class="line">prev, pt = iv, <span class="string">b&quot;&quot;</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="built_in">len</span>(ct), <span class="number">16</span>):</span><br><span class="line">    blk = ecb.decrypt(ct[i:i+<span class="number">16</span>])                       <span class="comment"># 先解</span></span><br><span class="line">    pt += <span class="built_in">bytes</span>(a ^ b <span class="keyword">for</span> a, b <span class="keyword">in</span> <span class="built_in">zip</span>(blk, prev))       <span class="comment"># 后异或</span></span><br><span class="line">    prev = ct[i:i+<span class="number">16</span>]</span><br><span class="line"><span class="built_in">print</span>(unpad(pt, <span class="number">16</span>).decode())</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{cb1t_fl1p_ch4ng3s_m3ss4g3_{69cbfed8}}</code></p><p>CBC 只有保密性没有完整性，改一位接收方察觉不了，现实里要配 HMAC 或直接用 GCM。</p><h2 id="23-填充预言"><a href="#23-填充预言" class="headerlink" title="23 填充预言"></a>23 填充预言</h2><p>这版直接给了 key，标准 CBC 解密拿 flag。但延伸知识值得记——Padding Oracle。</p><p>PKCS#7 缺几字节补几个”几”。解密端检查填充合不合法，”合法&#x2F;非法”这一比特就是个预言机。攻击场景：没密钥但能提交密文观察服务端反应。构造伪造块拼在目标密文块前，改伪造块最后一字节从 0 试到 255 直到服务端说”填充合法”——那一刻 <code>F[15] ^ I[15] == 0x01</code>，中间值 I[15] 到手。接着构造 <code>0x02 0x02</code> 推出 I[14]……逐字节向左吃掉整块，再异或原前块密文得明文。每字节最多 256 次查询，不要密钥。老版 ASP.NET 的 CVE-2010-3332 就是这个。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp23.py</span></span><br><span class="line"><span class="keyword">from</span> Crypto.Cipher <span class="keyword">import</span> AES</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.Padding <span class="keyword">import</span> unpad</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">key, iv, ct = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;key&quot;</span>]), <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;iv&quot;</span>]), <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct&quot;</span>])</span><br><span class="line"><span class="built_in">print</span>(unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(ct), <span class="number">16</span>).decode())</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{padd1ng_0r4cl3_cbc_bl0ck_{4d199582}}</code></p><p>任何能区分两种状态的旁路信号（填充报错、时间差、响应长短）都可能被利用。</p><h2 id="24-重复的钥匙"><a href="#24-重复的钥匙" class="headerlink" title="24 重复的钥匙"></a>24 重复的钥匙</h2><p>CTR 的 nonce 重用。CTR 把分组密码当流密码：密钥流 &#x3D; E(key, nonce‖计数器)，密文 &#x3D; 明文 XOR 密钥流。密钥流只由 key、nonce、counter 决定，和明文无关。同一个 nonce 加密两段明文产出的密钥流一模一样，两式相减密钥流消失：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ct_known ^ ct_flag = (known ^ ks) ^ (flag ^ ks) = known ^ flag</span><br></pre></td></tr></table></figure><p>已知 known，先 <code>ks = known ^ ct_known</code> 还原密钥流，再 <code>flag = ct_flag ^ ks</code>。两步异或。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp24.py</span></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">known = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;known&quot;</span>])</span><br><span class="line">ct_known = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct_known&quot;</span>])</span><br><span class="line">ct_flag = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct_flag&quot;</span>])</span><br><span class="line"></span><br><span class="line">ks = <span class="built_in">bytes</span>(a ^ b <span class="keyword">for</span> a, b <span class="keyword">in</span> <span class="built_in">zip</span>(known, ct_known))</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(a ^ b <span class="keyword">for</span> a, b <span class="keyword">in</span> <span class="built_in">zip</span>(ct_flag, ks)).decode())</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{ctr_n0nc3_r3us3_{d996f10a}}</code></p><p>流密码铁律：密钥流绝不重复。nonce 重用就是密钥流重用，CTR、OFB、RC4 都一样。看到”已知明文 + 对应密文 + 目标密文”三件套，异或三连走起。</p><h2 id="25-流之断章"><a href="#25-流之断章" class="headerlink" title="25 流之断章"></a>25 流之断章</h2><p>OFB。和 CTR 一样当流密码用，但密钥流造法不同：CTR 加密计数器，OFB 加密上一块密钥流——<code>ks_1 = E(IV)</code>，<code>ks_i = E(ks_{i-1})</code>，一条自反馈链。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp25.py</span></span><br><span class="line"><span class="keyword">from</span> Crypto.Cipher <span class="keyword">import</span> AES</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">key, iv, ct = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;key&quot;</span>]), <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;iv&quot;</span>]), <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct&quot;</span>])</span><br><span class="line"></span><br><span class="line">ecb = AES.new(key, AES.MODE_ECB)</span><br><span class="line">ks, pt = iv, <span class="string">b&quot;&quot;</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="built_in">len</span>(ct), <span class="number">16</span>):</span><br><span class="line">    ks = ecb.encrypt(ks)</span><br><span class="line">    pt += <span class="built_in">bytes</span>(a ^ b <span class="keyword">for</span> a, b <span class="keyword">in</span> <span class="built_in">zip</span>(ct[i:i+<span class="number">16</span>], ks))</span><br><span class="line"><span class="built_in">print</span>(pt.decode())</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{0fb_sync_l0ss_{f7b9789b}}</code></p><p>flag 里 sync_loss 是说 OFB 传输出错几字节后必须重新同步。21–25 做完四种模式都手写了一遍，再看库函数的 mode 参数就不是黑盒了。</p><hr><h1 id="五、弱算法与密钥流重用（26–29）"><a href="#五、弱算法与密钥流重用（26–29）" class="headerlink" title="五、弱算法与密钥流重用（26–29）"></a>五、弱算法与密钥流重用（26–29）</h1><h2 id="26-AES弱密钥的陷阱"><a href="#26-AES弱密钥的陷阱" class="headerlink" title="26 AES弱密钥的陷阱"></a>26 AES弱密钥的陷阱</h2><p>key 就是 16 字节全零，简单到离谱。AES 本身没被实用破解过，但密钥一眼可见。最脆弱的从来是密钥选取不是算法。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp26.py</span></span><br><span class="line"><span class="keyword">from</span> Crypto.Cipher <span class="keyword">import</span> AES</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">key = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;key_hex&quot;</span>])</span><br><span class="line">ct = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct_hex&quot;</span>])</span><br><span class="line"><span class="built_in">print</span>(AES.new(key, AES.MODE_ECB).decrypt(ct))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{aes_w34k_k3y_z3r0_f1ll_8c2f4a}</code></p><p>“key + ct 都给了”的题先直接解密，确认不是白送的再考虑上攻击。</p><h2 id="27-三重锁的中途"><a href="#27-三重锁的中途" class="headerlink" title="27 三重锁的中途"></a>27 三重锁的中途</h2><p>2DES 中间相遇。<code>c = E_{k2}(E_{k1}(m))</code>，直觉上密钥空间 2^112。MITM 砍回去：枚举 k1 算 <code>E_{k1}(m)</code> 存表，枚举 k2 算 <code>D_{k2}(c)</code> 查表，命中就是候选密钥对。从 2^112 时间降到 2^57 时间加 2^57 空间——空间换时间，和 14 题 BSGS 一个思想。</p><p>这题密钥设计更狠：每把 DES 密钥单字节重复 8 次各只有 256 种，总共 2^16 秒出。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp27.py</span></span><br><span class="line"><span class="keyword">from</span> Crypto.Cipher <span class="keyword">import</span> DES</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">dk</span>(<span class="params">b</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">bytes</span>([b]) * <span class="number">8</span></span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">pt = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;known_plaintext_hex&quot;</span>])</span><br><span class="line">ct = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;known_ciphertext_hex&quot;</span>])</span><br><span class="line">ct_flag = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct_flag_hex&quot;</span>])</span><br><span class="line"></span><br><span class="line">table = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> b <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">256</span>):</span><br><span class="line">    table[DES.new(dk(b), DES.MODE_ECB).encrypt(pt)] = b</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> b <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">256</span>):</span><br><span class="line">    mid = DES.new(dk(b), DES.MODE_ECB).decrypt(ct)</span><br><span class="line">    <span class="keyword">if</span> mid <span class="keyword">in</span> table:</span><br><span class="line">        k1, k2 = table[mid], b</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">flag = DES.new(dk(k1), DES.MODE_ECB).decrypt(</span><br><span class="line">       DES.new(dk(k2), DES.MODE_ECB).decrypt(ct_flag))</span><br><span class="line"><span class="built_in">print</span>(flag)</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{m1tm_m33t_1n_th3_m1ddl3_5f7e2b}</code></p><p>能上 MITM 的条件：加密能拆成两段独立密钥控制的复合，中间值可比较。2DES 正因此被废弃，3DES 用 EDE 三层才躲过。</p><h2 id="28-RC4的偏颇"><a href="#28-RC4的偏颇" class="headerlink" title="28 RC4的偏颇"></a>28 RC4的偏颇</h2><p>RC4 密钥流重用。死穴和 24 题 CTR 一模一样——同一密钥永远产出同一密钥流。重用时密文异或等于明文异或。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp28.py</span></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">pt_known = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;known_plaintext_hex&quot;</span>])</span><br><span class="line">ct_known = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct_known_hex&quot;</span>])</span><br><span class="line">ct_flag = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct_flag_hex&quot;</span>])</span><br><span class="line"></span><br><span class="line">ks = <span class="built_in">bytes</span>(a ^ b <span class="keyword">for</span> a, b <span class="keyword">in</span> <span class="built_in">zip</span>(ct_known, pt_known))</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(a ^ b <span class="keyword">for</span> a, b <span class="keyword">in</span> <span class="built_in">zip</span>(ct_flag, ks)).decode())</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{rc4_k3ystr34m_b1as_4b1d9c}</code></p><p>flag 里 bias 是 RC4 另一个毛病：输出初始字节有统计偏置，WEP 就是被这个偏置加 IV 重用联手打死的。RC4 已被 RFC 7465 禁用于 TLS。</p><h2 id="29-复用的密语"><a href="#29-复用的密语" class="headerlink" title="29 复用的密语"></a>29 复用的密语</h2><p>OTP 复用。一次一密是唯一被证明无条件安全的方案：密钥和明文等长、完全随机、用一次就扔。三条缺一不可，尤其”只用一次”。复用瞬间 <code>c1 ^ c2 = m1 ^ m2</code>，密钥消失，从信息论不可破跌到两段明文互相出卖。历史上苏联外交密电复用密钥本被 VENONA 计划读了个底朝天。</p><p>解法和 24、28 一样，注意这题 known_plaintext 是明文原文直接 encode：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp29.py</span></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">known = ns[<span class="string">&quot;known_plaintext&quot;</span>].encode()</span><br><span class="line">ct_known = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct_known_hex&quot;</span>])</span><br><span class="line">ct_flag = <span class="built_in">bytes</span>.fromhex(ns[<span class="string">&quot;ct_flag_hex&quot;</span>])</span><br><span class="line"></span><br><span class="line">ks = <span class="built_in">bytes</span>(a ^ b <span class="keyword">for</span> a, b <span class="keyword">in</span> <span class="built_in">zip</span>(ct_known, known))</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(a ^ b <span class="keyword">for</span> a, b <span class="keyword">in</span> <span class="built_in">zip</span>(ct_flag, ks)).decode())</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{0tp_r3us3_l34k5_x0r_x0r_d6e8a3}</code></p><p>24、28、29 同一漏洞换三件马甲（CTR、RC4、OTP），模板一字不差。多段密文共用密钥流时可以 crib dragging：猜个常见词在异或结果里滑动找碰撞。</p><hr><h1 id="六、伪随机数攻防（30–33）"><a href="#六、伪随机数攻防（30–33）" class="headerlink" title="六、伪随机数攻防（30–33）"></a>六、伪随机数攻防（30–33）</h1><h2 id="30-反馈之链"><a href="#30-反馈之链" class="headerlink" title="30 反馈之链"></a>30 反馈之链</h2><p>LFSR + Berlekamp-Massey。LFSR 输出满足线性递推，整个序列只由递推式决定——“线性”就是它脆弱的根源。BM 算法只用 2L 个连续比特就能恢复最短递推式，之后想生成多少后续比特都行。</p><p>题目给了前 160 比特和用后续密钥流 XOR 的密文。跑 BM 拿递推式，接着生成拼字节 XOR：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp30.py</span></span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line">raw = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read()</span><br><span class="line">seq = [<span class="built_in">int</span>(b) <span class="keyword">for</span> b <span class="keyword">in</span> re.search(<span class="string">r&quot;= ([01]+)&quot;</span>, raw).group(<span class="number">1</span>)]</span><br><span class="line">ct = <span class="built_in">bytes</span>.fromhex(re.search(<span class="string">r&quot;ct_flag_hex = ([0-9a-f]+)&quot;</span>, raw).group(<span class="number">1</span>))</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">berlekamp_massey</span>(<span class="params">s</span>):</span><br><span class="line">    C, B = [<span class="number">1</span>], [<span class="number">1</span>]</span><br><span class="line">    L, m, b = <span class="number">0</span>, <span class="number">1</span>, <span class="number">1</span></span><br><span class="line">    <span class="keyword">for</span> N <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(s)):</span><br><span class="line">        d = s[N]</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, L + <span class="number">1</span>):</span><br><span class="line">            d ^= C[i] &amp; s[N - i]</span><br><span class="line">        <span class="keyword">if</span> d == <span class="number">0</span>:</span><br><span class="line">            m += <span class="number">1</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            T = C[:]</span><br><span class="line">            coef = d * <span class="built_in">pow</span>(b, -<span class="number">1</span>, <span class="number">2</span>) % <span class="number">2</span></span><br><span class="line">            <span class="keyword">if</span> <span class="built_in">len</span>(C) &lt; <span class="built_in">len</span>(B) + m:</span><br><span class="line">                C += [<span class="number">0</span>] * (<span class="built_in">len</span>(B) + m - <span class="built_in">len</span>(C))</span><br><span class="line">            <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(B)):</span><br><span class="line">                C[j + m] ^= coef &amp; B[j]</span><br><span class="line">            <span class="keyword">if</span> <span class="number">2</span> * L &lt;= N:</span><br><span class="line">                L, B, b, m = N + <span class="number">1</span> - L, T, d, <span class="number">1</span></span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                m += <span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> L, C</span><br><span class="line"></span><br><span class="line">L, C = berlekamp_massey(seq)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;递推长度 L =&quot;</span>, L)</span><br><span class="line"></span><br><span class="line">bits = seq[:]</span><br><span class="line"><span class="keyword">while</span> <span class="built_in">len</span>(bits) &lt; <span class="built_in">len</span>(seq) + <span class="built_in">len</span>(ct) * <span class="number">8</span>:</span><br><span class="line">    nxt = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, L + <span class="number">1</span>):</span><br><span class="line">        nxt ^= C[i] &amp; bits[-i]</span><br><span class="line">    bits.append(nxt)</span><br><span class="line"></span><br><span class="line">ksbits = bits[<span class="built_in">len</span>(seq):<span class="built_in">len</span>(seq) + <span class="built_in">len</span>(ct) * <span class="number">8</span>]</span><br><span class="line">ks = <span class="built_in">bytes</span>(<span class="built_in">int</span>(<span class="string">&quot;&quot;</span>.join(<span class="built_in">map</span>(<span class="built_in">str</span>, ksbits[i*<span class="number">8</span>:(i+<span class="number">1</span>)*<span class="number">8</span>])), <span class="number">2</span>) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(ct)))</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(a ^ b <span class="keyword">for</span> a, b <span class="keyword">in</span> <span class="built_in">zip</span>(ct, ks)))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{lfsr_bm_p0lyn0m1al_r3c0v3r_a3c9d1}</code></p><p>BM 代码存工具箱，”给序列猜递推”直接套。LFSR 想变安全要引入非线性（A5&#x2F;1 三个 LFSR 不规则钟控），但线性结构终究会被代数攻击撕开。</p><h2 id="31-线性之钟"><a href="#31-线性之钟" class="headerlink" title="31 线性之钟"></a>31 线性之钟</h2><p>LCG 参数恢复。<code>X_{n+1} = (a*X_n + c) mod m</code>，C 语言 rand() 就是这个家族。完全线性，三个连续输出解出全部参数：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">X2 - X1 ≡ a * (X1 - X0)  (mod m)</span><br><span class="line">a = (X2 - X1) * (X1 - X0)^-1 (mod m)</span><br><span class="line">c = X1 - a*X0 (mod m)</span><br></pre></td></tr></table></figure><p>拿到 (a, c, m) 从最后一个已知输出继续递推，后面”随机数”全是已知的。本题模数 2^31，实测 (X1-X0) 和 m 互素逆元直接求。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp31.py</span></span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line">raw = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read()</span><br><span class="line">m = <span class="built_in">int</span>(re.search(<span class="string">r&quot;modulus_m = (\d+)&quot;</span>, raw).group(<span class="number">1</span>))</span><br><span class="line">X = [<span class="built_in">int</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> re.search(<span class="string">r&quot;outputs = (.+)&quot;</span>, raw).group(<span class="number">1</span>).split(<span class="string">&quot;,&quot;</span>)]</span><br><span class="line">M = [<span class="built_in">int</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> re.search(<span class="string">r&quot;more_outputs = (.+)&quot;</span>, raw).group(<span class="number">1</span>).split(<span class="string">&quot;,&quot;</span>)]</span><br><span class="line">ct = <span class="built_in">bytes</span>.fromhex(re.search(<span class="string">r&quot;ct_flag_hex = ([0-9a-f]+)&quot;</span>, raw).group(<span class="number">1</span>))</span><br><span class="line"></span><br><span class="line">a = (X[<span class="number">2</span>] - X[<span class="number">1</span>]) * <span class="built_in">pow</span>(X[<span class="number">1</span>] - X[<span class="number">0</span>], -<span class="number">1</span>, m) % m</span><br><span class="line">c = (X[<span class="number">1</span>] - a * X[<span class="number">0</span>]) % m</span><br><span class="line"></span><br><span class="line">state = M[-<span class="number">1</span>]</span><br><span class="line">ks = <span class="string">b&quot;&quot;</span></span><br><span class="line"><span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(ct)):</span><br><span class="line">    state = (a * state + c) % m</span><br><span class="line">    ks += <span class="built_in">bytes</span>([state &amp; <span class="number">0xff</span>])</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(x ^ k <span class="keyword">for</span> x, k <span class="keyword">in</span> <span class="built_in">zip</span>(ct, ks)))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{lcg_p4r4m_r3c0v3r_1in34r_f8b4a2}</code></p><p>(X1-X0) 和 m 不互素要先约公因子再解。名字带”线性”的生成器（LCG、LFSR、矩阵法）都默认可被代数攻击，抽样模拟没问题，密钥生成等于裸奔。</p><h2 id="32-梅森的低语"><a href="#32-梅森的低语" class="headerlink" title="32 梅森的低语"></a>32 梅森的低语</h2><p>MT19937 状态恢复。Python random 基于它，内部状态 624 个 32 位整数，每个输出前做一次 tempering——四层移位和掩码 XOR。tempering 每一层可逆，逆着变换回去（untemper）就还原状态字。攒齐 624 个等于完整克隆生成器。</p><p>untemper 每层要逐位迭代还原（右移 XOR 的逆不能一步做出来）：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp32.py</span></span><br><span class="line"><span class="keyword">import</span> re, random, struct</span><br><span class="line"></span><br><span class="line">raw = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read()</span><br><span class="line">rand32 = <span class="built_in">eval</span>(re.search(<span class="string">r&quot;rand32 = (\[.*?\])&quot;</span>, raw, re.S).group(<span class="number">1</span>))</span><br><span class="line">next_output = <span class="built_in">float</span>(re.search(<span class="string">r&quot;next_output = ([\d.]+)&quot;</span>, raw).group(<span class="number">1</span>))</span><br><span class="line">ct = <span class="built_in">bytes</span>.fromhex(re.search(<span class="string">r&quot;ct_flag_hex = ([0-9a-f]+)&quot;</span>, raw).group(<span class="number">1</span>))</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">unshift_right</span>(<span class="params">y, shift</span>):</span><br><span class="line">    x = y</span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">32</span> // shift + <span class="number">1</span>):</span><br><span class="line">        x = y ^ (x &gt;&gt; shift)</span><br><span class="line">    <span class="keyword">return</span> x</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">unshift_left</span>(<span class="params">y, shift, mask</span>):</span><br><span class="line">    x = y</span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">32</span> // shift + <span class="number">1</span>):</span><br><span class="line">        x = y ^ ((x &lt;&lt; shift) &amp; mask)</span><br><span class="line">    <span class="keyword">return</span> x &amp; <span class="number">0xffffffff</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">untemper</span>(<span class="params">y</span>):</span><br><span class="line">    y = unshift_right(y, <span class="number">18</span>)</span><br><span class="line">    y = unshift_left(y, <span class="number">15</span>, <span class="number">0xefc60000</span>)</span><br><span class="line">    y = unshift_left(y, <span class="number">7</span>, <span class="number">0x9d2c5680</span>)</span><br><span class="line">    y = unshift_right(y, <span class="number">11</span>)</span><br><span class="line">    <span class="keyword">return</span> y</span><br><span class="line"></span><br><span class="line">mt = [untemper(x) <span class="keyword">for</span> x <span class="keyword">in</span> rand32[:<span class="number">624</span>]]</span><br><span class="line">r = random.Random()</span><br><span class="line">r.setstate((<span class="number">3</span>, <span class="built_in">tuple</span>(mt + [<span class="number">624</span>]), <span class="literal">None</span>))</span><br><span class="line"></span><br><span class="line"><span class="keyword">assert</span> r.random() == next_output          <span class="comment"># next_output 是 float，用 random() 消耗对齐</span></span><br><span class="line">ks = <span class="string">b&quot;&quot;</span>.join(struct.pack(<span class="string">&quot;&gt;d&quot;</span>, r.random()) <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>((<span class="built_in">len</span>(ct) + <span class="number">7</span>) // <span class="number">8</span>))[:<span class="built_in">len</span>(ct)]</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(x ^ k <span class="keyword">for</span> x, k <span class="keyword">in</span> <span class="built_in">zip</span>(ct, ks)))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{mt19937_st4t3_r3c0v3r_0f_624_c9d7e5}</code></p><p>624 这个数背下来。setstate 格式 <code>(3, tuple(624个状态字+[624]), None)</code>。这题出题脚本用 <code>random()</code> 输出 double（每个 8 字节 IEEE-754 大端）当密钥流，next_output 本身就是 float——random() 内部占 53 个随机位（约 2 个字）。解出来对不上先检查出题脚本是 getrandbits 还是 random()，消耗方式和取字节方式都要跟着来。</p><h2 id="33-预言之舌"><a href="#33-预言之舌" class="headerlink" title="33 预言之舌"></a>33 预言之舌</h2><p>和 32 同源：给 624 个 getrandbits(32) 历史输出，密钥流由后续随机数生成。untemper 克隆状态后按出题脚本同款方式取字节：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp33.py</span></span><br><span class="line"><span class="keyword">import</span> re, random</span><br><span class="line"></span><br><span class="line">raw = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read()</span><br><span class="line">prev = <span class="built_in">eval</span>(re.search(<span class="string">r&quot;prev_randbits32 = (\[.*?\])&quot;</span>, raw, re.S).group(<span class="number">1</span>))</span><br><span class="line">ct = <span class="built_in">bytes</span>.fromhex(re.search(<span class="string">r&quot;ct_flag_hex = ([0-9a-f]+)&quot;</span>, raw).group(<span class="number">1</span>))</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">unshift_right</span>(<span class="params">y, shift</span>):</span><br><span class="line">    x = y</span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">32</span> // shift + <span class="number">1</span>):</span><br><span class="line">        x = y ^ (x &gt;&gt; shift)</span><br><span class="line">    <span class="keyword">return</span> x</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">unshift_left</span>(<span class="params">y, shift, mask</span>):</span><br><span class="line">    x = y</span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">32</span> // shift + <span class="number">1</span>):</span><br><span class="line">        x = y ^ ((x &lt;&lt; shift) &amp; mask)</span><br><span class="line">    <span class="keyword">return</span> x &amp; <span class="number">0xffffffff</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">untemper</span>(<span class="params">y</span>):</span><br><span class="line">    y = unshift_right(y, <span class="number">18</span>)</span><br><span class="line">    y = unshift_left(y, <span class="number">15</span>, <span class="number">0xefc60000</span>)</span><br><span class="line">    y = unshift_left(y, <span class="number">7</span>, <span class="number">0x9d2c5680</span>)</span><br><span class="line">    y = unshift_right(y, <span class="number">11</span>)</span><br><span class="line">    <span class="keyword">return</span> y</span><br><span class="line"></span><br><span class="line">mt = [untemper(x) <span class="keyword">for</span> x <span class="keyword">in</span> prev[:<span class="number">624</span>]]</span><br><span class="line">r = random.Random()</span><br><span class="line">r.setstate((<span class="number">3</span>, <span class="built_in">tuple</span>(mt + [<span class="number">624</span>]), <span class="literal">None</span>))</span><br><span class="line"></span><br><span class="line">ks = <span class="string">b&quot;&quot;</span>.join(r.getrandbits(<span class="number">32</span>).to_bytes(<span class="number">4</span>, <span class="string">&quot;big&quot;</span>) <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>((<span class="built_in">len</span>(ct) + <span class="number">3</span>) // <span class="number">4</span>))[:<span class="built_in">len</span>(ct)]</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">bytes</span>(x ^ k <span class="keyword">for</span> x, k <span class="keyword">in</span> <span class="built_in">zip</span>(ct, ks)))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{py_r4nd0m_br34k_unt3mp3r_3f6a8b}</code></p><p>30 到 33 做完一个感觉：伪随机数的”随机”只对不知道内部状态的人生效。安全场景一律 <code>secrets</code> 或 <code>os.urandom</code>。</p><hr><h1 id="七、RSA：从分解到代数结构（34–40）"><a href="#七、RSA：从分解到代数结构（34–40）" class="headerlink" title="七、RSA：从分解到代数结构（34–40）"></a>七、RSA：从分解到代数结构（34–40）</h1><p>从这章开始套路高度统一：n 能分解就分解，分解出 p、q 后永远那三步——<code>phi = (p-1)(q-1)</code>，<code>d = pow(e, -1, phi)</code>，明文 <code>pow(c, d, n)</code>。每题真正的新东西只是”怎么把 n 拆开”或”不拆 n 怎么把 m 要回来”。先备好：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> long_to_bytes, inverse</span><br></pre></td></tr></table></figure><h2 id="34-试除之刃"><a href="#34-试除之刃" class="headerlink" title="34 试除之刃"></a>34 试除之刃</h2><p>n 只有 10 位数（约 30 bit），sympy factorint 眨眼出结果。密文逐字节加密成整数列表，解出来 long_to_bytes 拼接。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp34.py</span></span><br><span class="line"><span class="keyword">import</span> sympy</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> inverse, long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n, e = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>])</span><br><span class="line">ct = [<span class="built_in">int</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> ns[<span class="string">&quot;ct_bytes&quot;</span>].split(<span class="string">&quot;,&quot;</span>)]</span><br><span class="line"></span><br><span class="line">p, q = sympy.factorint(n).keys()</span><br><span class="line">d = inverse(e, (p - <span class="number">1</span>) * (q - <span class="number">1</span>))</span><br><span class="line"><span class="built_in">print</span>(<span class="string">b&quot;&quot;</span>.join(long_to_bytes(<span class="built_in">pow</span>(c, d, n)) <span class="keyword">for</span> c <span class="keyword">in</span> ct))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{rsa_tr14l_d1v1s10n_sm411_n_7c3f1a}</code></p><p>RSA 全押在 n 难分解上，n 一小整座大厦塌。真实密钥至少 2048 位。</p><h2 id="35-费马的近邻"><a href="#35-费马的近邻" class="headerlink" title="35 费马的近邻"></a>35 费马的近邻</h2><p>11 题讲过原理：p、q 接近时 <code>n = a² - b²</code>，从 ⌈√n⌉ 向上试 a 直到 <code>a²-n</code> 是完全平方。这次 n 160 位，费马法几步就中。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp35.py</span></span><br><span class="line"><span class="keyword">import</span> math</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> inverse, long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n, e, c = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;c&quot;</span>])</span><br><span class="line"></span><br><span class="line">a = math.isqrt(n)</span><br><span class="line"><span class="keyword">if</span> a * a &lt; n:</span><br><span class="line">    a += <span class="number">1</span></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    b2 = a * a - n</span><br><span class="line">    b = math.isqrt(b2)</span><br><span class="line">    <span class="keyword">if</span> b * b == b2:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">    a += <span class="number">1</span></span><br><span class="line">p, q = a - b, a + b</span><br><span class="line"></span><br><span class="line">d = inverse(e, (p - <span class="number">1</span>) * (q - <span class="number">1</span>))</span><br><span class="line"><span class="built_in">print</span>(long_to_bytes(<span class="built_in">pow</span>(c, d, n)))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{rsa_f3rm4t_cl0s3_pr1m3s_4e8b2d}</code></p><p>拿到 RSA 题先用费马试十几步，成本极低，命中了白捡。</p><h2 id="36-p减一的巧取"><a href="#36-p减一的巧取" class="headerlink" title="36 p减一的巧取"></a>36 p减一的巧取</h2><p>Pollard p-1。p-1 是光滑数（所有质因子都很小）时能挖出来。算 <code>a = 2^(B!) mod n</code>，B! 里包含 p-1 所有质因子，所以 <code>2^(B!) ≡ 1 (mod p)</code>，于是 <code>gcd(a-1, n)</code> 把 p 分离出来——q-1 不光滑时 gcd 只会掉出 p。</p><p>不用真算 B!，按 i&#x3D;2,3,4,… 逐次自乘 i 再取模，每隔一段 gcd 检查一次。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp36.py</span></span><br><span class="line"><span class="keyword">from</span> math <span class="keyword">import</span> gcd</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> inverse, long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n, e = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>])</span><br><span class="line">ct = [<span class="built_in">int</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> ns[<span class="string">&quot;ct_bytes&quot;</span>].split(<span class="string">&quot;,&quot;</span>)]</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pollard_pm1</span>(<span class="params">n, B=<span class="number">100000</span></span>):</span><br><span class="line">    a = <span class="number">2</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">2</span>, B + <span class="number">1</span>):</span><br><span class="line">        a = <span class="built_in">pow</span>(a, i, n)</span><br><span class="line">        <span class="keyword">if</span> i % <span class="number">1000</span> == <span class="number">0</span>:</span><br><span class="line">            g = gcd(a - <span class="number">1</span>, n)</span><br><span class="line">            <span class="keyword">if</span> <span class="number">1</span> &lt; g &lt; n:</span><br><span class="line">                <span class="keyword">return</span> g</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">p = pollard_pm1(n)</span><br><span class="line">q = n // p</span><br><span class="line">d = inverse(e, (p - <span class="number">1</span>) * (q - <span class="number">1</span>))</span><br><span class="line"><span class="built_in">print</span>(<span class="string">b&quot;&quot;</span>.join(long_to_bytes(<span class="built_in">pow</span>(c, d, n)) <span class="keyword">for</span> c <span class="keyword">in</span> ct))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{rsa_p0ll4rd_pm1_sm00th_a1c7e3}</code></p><p>B 从小往大试，太大会慢，且 a-1 ≡ 0 (mod n) 时反而失败。</p><h2 id="37-随机漫步的rho"><a href="#37-随机漫步的rho" class="headerlink" title="37 随机漫步的rho"></a>37 随机漫步的rho</h2><p>Pollard rho。不依赖任何特殊结构，通用分解。用伪随机序列 <code>x_{i+1} = x_i² + c (mod n)</code> 在模 p 下走，生日悖论约 n^(1&#x2F;4) 步内碰撞，Floyd 判环（一个走一步一个走两步）找 <code>gcd(|x-y|, n)</code> 得因子。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp37.py</span></span><br><span class="line"><span class="keyword">from</span> math <span class="keyword">import</span> gcd</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> inverse, long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n, e = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>])</span><br><span class="line">ct = [<span class="built_in">int</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> ns[<span class="string">&quot;ct_bytes&quot;</span>].split(<span class="string">&quot;,&quot;</span>)]</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pollard_rho</span>(<span class="params">n</span>):</span><br><span class="line">    <span class="keyword">if</span> n % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="number">2</span></span><br><span class="line">    x = y = <span class="number">2</span></span><br><span class="line">    c = <span class="number">1</span></span><br><span class="line">    d = <span class="number">1</span></span><br><span class="line">    <span class="keyword">while</span> d == <span class="number">1</span>:</span><br><span class="line">        x = (x * x + c) % n</span><br><span class="line">        y = (y * y + c) % n</span><br><span class="line">        y = (y * y + c) % n</span><br><span class="line">        d = gcd(<span class="built_in">abs</span>(x - y), n)</span><br><span class="line">        <span class="keyword">if</span> d == n:</span><br><span class="line">            x = y = <span class="number">2</span></span><br><span class="line">            c += <span class="number">1</span></span><br><span class="line">            d = <span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> d</span><br><span class="line"></span><br><span class="line">p = pollard_rho(n)</span><br><span class="line">q = n // p</span><br><span class="line">d = inverse(e, (p - <span class="number">1</span>) * (q - <span class="number">1</span>))</span><br><span class="line"><span class="built_in">print</span>(<span class="string">b&quot;&quot;</span>.join(long_to_bytes(<span class="built_in">pow</span>(c, d, n)) <span class="keyword">for</span> c <span class="keyword">in</span> ct))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{rsa_p0ll4rd_rh0_m1ddl3_s1z3_9d5b2f}</code></p><p>分解工具箱按规模选：试除（&lt;2^40）→ 费马（p、q 接近）→ p-1（光滑）→ rho（64 位左右通用）→ ECM&#x2F;GNFS（更大）。实战先上 sympy.factorint 或 yafu，不行再手写。</p><h2 id="38-共模的裂痕"><a href="#38-共模的裂痕" class="headerlink" title="38 共模的裂痕"></a>38 共模的裂痕</h2><p>共模攻击。同一明文用同一个 n、两个互素指数 e1、e2 加密发给两处。egcd 找到 <code>s*e1 + t*e2 = 1</code>（08 题的 egcd 在这里兑现），于是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c1^s * c2^t ≡ m^(s*e1 + t*e2) = m  (mod n)</span><br></pre></td></tr></table></figure><p>不用分解 n 不用私钥，代数上直接把明文配平出来。s、t 一负一正很正常，负指数用模逆元处理。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp38.py</span></span><br><span class="line"><span class="keyword">import</span> sympy</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n, e1, e2 = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e1&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e2&quot;</span>])</span><br><span class="line">c1, c2 = <span class="built_in">int</span>(ns[<span class="string">&quot;c1&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;c2&quot;</span>])</span><br><span class="line"></span><br><span class="line">s, t, g = sympy.gcdex(<span class="built_in">int</span>(e1), <span class="built_in">int</span>(e2))</span><br><span class="line">s, t = <span class="built_in">int</span>(s), <span class="built_in">int</span>(t)</span><br><span class="line"></span><br><span class="line">m = <span class="built_in">pow</span>(c1, s, n) <span class="keyword">if</span> s &gt;= <span class="number">0</span> <span class="keyword">else</span> <span class="built_in">pow</span>(<span class="built_in">pow</span>(c1, -<span class="number">1</span>, n), -s, n)</span><br><span class="line">m = m * (<span class="built_in">pow</span>(c2, t, n) <span class="keyword">if</span> t &gt;= <span class="number">0</span> <span class="keyword">else</span> <span class="built_in">pow</span>(<span class="built_in">pow</span>(c2, -<span class="number">1</span>, n), -t, n)) % n</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(long_to_bytes(m))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{rsa_c0mm0n_m0dulu5_4tt4ck_b2f6c4}</code></p><p>每个用户必须有独立模数，共享 n 是协议设计大忌。</p><h2 id="39-三处广播"><a href="#39-三处广播" class="headerlink" title="39 三处广播"></a>39 三处广播</h2><p>Håstad 广播攻击。同一明文（无填充）e&#x3D;3 发给三个不同模数。三组密文 CRT 合并成一个数 x，<code>m³</code> 小于三模数之积，x 就是 m³ 的真值——直接开整数立方根。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp39.py</span></span><br><span class="line"><span class="keyword">import</span> sympy</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n1, n2, n3 = <span class="built_in">int</span>(ns[<span class="string">&quot;n1&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;n2&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;n3&quot;</span>])</span><br><span class="line">c1, c2, c3 = <span class="built_in">int</span>(ns[<span class="string">&quot;c1&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;c2&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;c3&quot;</span>])</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">crt</span>(<span class="params">rs, ms</span>):</span><br><span class="line">    M = <span class="number">1</span></span><br><span class="line">    <span class="keyword">for</span> m <span class="keyword">in</span> ms:</span><br><span class="line">        M *= m</span><br><span class="line">    x = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> r, m <span class="keyword">in</span> <span class="built_in">zip</span>(rs, ms):</span><br><span class="line">        x += r * (M // m) * <span class="built_in">pow</span>(M // m, -<span class="number">1</span>, m)</span><br><span class="line">    <span class="keyword">return</span> x % M</span><br><span class="line"></span><br><span class="line">x = crt([c1, c2, c3], [n1, n2, n3])</span><br><span class="line">m = sympy.integer_nthroot(x, <span class="number">3</span>)[<span class="number">0</span>]</span><br><span class="line"><span class="built_in">print</span>(long_to_bytes(m))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{rsa_broadc4st_1o3_3xp0n3nt_7d4a8c}</code></p><p>k 个接收者时需要 <code>m^e &lt; n1*...*nk</code>。防御就是 OAEP 填充。</p><h2 id="40-同态的欺骗"><a href="#40-同态的欺骗" class="headerlink" title="40 同态的欺骗"></a>40 同态的欺骗</h2><p>RSA 乘法同态：<code>E(m1) * E(m2) = E(m1*m2) mod n</code>。教科书 RSA 无填充时这是个可利用的代数结构。这题 n 只有 128 位直接分解，FLAG 分三段加密逐段解密拼接。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp40.py</span></span><br><span class="line"><span class="keyword">import</span> sympy</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> inverse</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n, e = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>])</span><br><span class="line">chunks = <span class="built_in">eval</span>(ns[<span class="string">&quot;ct_chunks&quot;</span>])</span><br><span class="line"></span><br><span class="line">p, q = sympy.factorint(n).keys()</span><br><span class="line">d = inverse(e, (p - <span class="number">1</span>) * (q - <span class="number">1</span>))</span><br><span class="line">flag = <span class="string">b&quot;&quot;</span>.join(<span class="built_in">pow</span>(c, d, n).to_bytes(<span class="number">15</span>, <span class="string">&quot;big&quot;</span>).lstrip(<span class="string">b&quot;\x00&quot;</span>) <span class="keyword">for</span> c <span class="keyword">in</span> chunks)</span><br><span class="line"><span class="built_in">print</span>(flag)</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{rsa_mult1pl1c4t1v3_h0m0m0rp4_b9c3e7}</code></p><p>34–40 教科书弱点基本集齐：小 n、近素数、光滑 p-1、共模、低指数广播、无填充同态。现实 RSA 还能用靠的是 OAEP、大模数、独立参数。</p><hr><h1 id="八、RSA-进阶（43–49）"><a href="#八、RSA-进阶（43–49）" class="headerlink" title="八、RSA 进阶（43–49）"></a>八、RSA 进阶（43–49）</h1><p>这章 n 拆不掉没关系，出题人留了别的门。工具备好：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> inverse, long_to_bytes</span><br><span class="line"><span class="keyword">from</span> math <span class="keyword">import</span> gcd</span><br></pre></td></tr></table></figure><h2 id="43-密钥的暗门"><a href="#43-密钥的暗门" class="headerlink" title="43 密钥的暗门"></a>43 密钥的暗门</h2><p>题面说 p 和 q 之间藏着线性关系，仔细读发现 <code>q = 3p + 2</code>。条件一出分解降级成解方程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">n = p * q = p * (3p + 2) = 3p² + 2p</span><br></pre></td></tr></table></figure><p>一元二次方程，判别式 <code>Δ = 4 + 12n</code> 必须是完全平方数（出题人埋的验证条件）。开出来 <code>p = (-2 + √Δ) / 6</code>，n 一千位也瞬间瓦解。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp43.py</span></span><br><span class="line"><span class="keyword">import</span> math</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> inverse, long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n, e, c = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;c&quot;</span>])</span><br><span class="line"></span><br><span class="line">s = math.isqrt(<span class="number">4</span> + <span class="number">12</span> * n)</span><br><span class="line"><span class="keyword">assert</span> s * s == <span class="number">4</span> + <span class="number">12</span> * n</span><br><span class="line">p = (-<span class="number">2</span> + s) // <span class="number">6</span></span><br><span class="line">q = n // p</span><br><span class="line"></span><br><span class="line">d = inverse(e, (p - <span class="number">1</span>) * (q - <span class="number">1</span>))</span><br><span class="line"><span class="built_in">print</span>(long_to_bytes(<span class="built_in">pow</span>(c, d, n)))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{rsa_b4ckd00r_l1n34r_h1dd3n_6f9a2c}</code></p><p>RSA 题先读题面，任何”p 与 q 的关系”提示（线性、接近、相等、差为偶数）都是出题人递出来的钥匙。</p><h2 id="44-连分数的逼近"><a href="#44-连分数的逼近" class="headerlink" title="44 连分数的逼近"></a>44 连分数的逼近</h2><p>Wiener 攻击。d 太小（<code>d &lt; n^0.25 / 3</code>）时，连分数把 e&#x2F;n 逼近成 k&#x2F;d。e&#x2F;n 的连分数渐进分数序列里必然藏着一项 k&#x2F;d，逐个验证揪出 d。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp44.py</span></span><br><span class="line"><span class="keyword">import</span> math</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> inverse, long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n, e, c = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;c&quot;</span>])</span><br><span class="line"></span><br><span class="line">cf = []</span><br><span class="line">a, b = e, n</span><br><span class="line"><span class="keyword">while</span> b:</span><br><span class="line">    cf.append(a // b)</span><br><span class="line">    a, b = b, a % b</span><br><span class="line"></span><br><span class="line">p0, q0 = <span class="number">1</span>, <span class="number">0</span></span><br><span class="line">p1, q1 = cf[<span class="number">0</span>], <span class="number">1</span></span><br><span class="line"><span class="keyword">for</span> a <span class="keyword">in</span> cf[<span class="number">1</span>:]:</span><br><span class="line">    p0, q0, p1, q1 = p1, q1, a * p1 + p0, a * q1 + q0</span><br><span class="line">    k, d = p1, q1</span><br><span class="line">    <span class="keyword">if</span> k == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">continue</span></span><br><span class="line">    <span class="keyword">if</span> (e * d - <span class="number">1</span>) % k:</span><br><span class="line">        <span class="keyword">continue</span></span><br><span class="line">    phi = (e * d - <span class="number">1</span>) // k</span><br><span class="line">    s = n - phi + <span class="number">1</span></span><br><span class="line">    t = math.isqrt(s * s - <span class="number">4</span> * n)</span><br><span class="line">    <span class="keyword">if</span> t * t == s * s - <span class="number">4</span> * n:</span><br><span class="line">        p = (s + t) // <span class="number">2</span></span><br><span class="line">        q = (s - t) // <span class="number">2</span></span><br><span class="line">        <span class="keyword">if</span> p * q == n:</span><br><span class="line">            <span class="built_in">print</span>(long_to_bytes(<span class="built_in">pow</span>(c, d, n)))</span><br><span class="line">            <span class="keyword">break</span></span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{w13n3r_l0w_d_c0nt1nu3d_frac_8d1b4e}</code></p><p>拿到 RSA 题先看 d 有没有给、e 是不是巨大。e 接近 n 而 n 不太大时多半在诱你做 Wiener。</p><h2 id="45-小根的秘密"><a href="#45-小根的秘密" class="headerlink" title="45 小根的秘密"></a>45 小根的秘密</h2><p><code>c = m^e mod n</code>。明文 m 非常小、<code>m^e</code> 根本不超过 n 时，取模是多余的，c 就是 m^e 的真值。直接开 e 次根，不用分解 n 不用私钥。特征：c 的长度明显短于 n 一大截。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp45.py</span></span><br><span class="line"><span class="keyword">import</span> sympy</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n, e, c = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;c&quot;</span>])</span><br><span class="line"></span><br><span class="line">m = sympy.integer_nthroot(c, e)[<span class="number">0</span>]</span><br><span class="line"><span class="built_in">print</span>(long_to_bytes(m))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{sm411_3_m3ss4ge_cub3_r00t_4c19d7}</code></p><p>开根必须用整数版 <code>integer_nthroot</code>，浮点 <code>c ** (1/e)</code> 精度不够大数会错。</p><h2 id="46-私钥的残片"><a href="#46-私钥的残片" class="headerlink" title="46 私钥的残片"></a>46 私钥的残片</h2><p>给了 p 的高位（hex 字符串），只缺低 16 位。低位枚举 2^16&#x3D;65536 种逐个拼起来试整除 n，整除的就是 p。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp46.py</span></span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> inverse, long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n, e, c = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;c&quot;</span>])</span><br><span class="line">p_high_hex = ns[<span class="string">&quot;p_high_hex&quot;</span>]</span><br><span class="line"></span><br><span class="line">lead = <span class="built_in">int</span>(p_high_hex, <span class="number">16</span>)</span><br><span class="line"><span class="keyword">for</span> lo <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">65536</span>):</span><br><span class="line">    cand = (lead &lt;&lt; <span class="number">16</span>) + lo</span><br><span class="line">    <span class="keyword">if</span> n % cand == <span class="number">0</span>:</span><br><span class="line">        p = cand</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">q = n // p</span><br><span class="line">d = inverse(e, (p - <span class="number">1</span>) * (q - <span class="number">1</span>))</span><br><span class="line"><span class="built_in">print</span>(long_to_bytes(<span class="built_in">pow</span>(c, d, n)))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{p4rt14l_k3y_l34k_h1gh_b1ts_1f4a63}</code></p><p>知道高位能枚举低位，知道低位当然也能枚举高位。中间缺一段就要上 Coppersmith 格攻击了。</p><h2 id="47-故障的讯息"><a href="#47-故障的讯息" class="headerlink" title="47 故障的讯息"></a>47 故障的讯息</h2><p>RSA-CRT 故障注入。CRT 加速时签名分别算 <code>S_p = m^dp mod p</code> 和 <code>S_q = m^dq mod q</code> 再合并。某一半出故障，错误签名 S’ 和正确签名 S 在 mod p 下一致、mod q 下不一致。<code>gcd(S - S&#39;, n)</code> 直接把出错的素因子分离出来。chall.txt 同时给了 s_correct 和 s_faulty。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp47.py</span></span><br><span class="line"><span class="keyword">from</span> math <span class="keyword">import</span> gcd</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> inverse, long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k.strip()] = v.strip()      <span class="comment"># 有些行 = 前有多个空格，再 strip</span></span><br><span class="line"></span><br><span class="line">n, e, c = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;c&quot;</span>])</span><br><span class="line">s_ok = <span class="built_in">int</span>(ns[<span class="string">&quot;s_correct&quot;</span>])</span><br><span class="line">s_bad = <span class="built_in">int</span>(ns[<span class="string">&quot;s_faulty&quot;</span>])</span><br><span class="line"></span><br><span class="line">p = gcd(s_ok - s_bad, n)</span><br><span class="line">q = n // p</span><br><span class="line"></span><br><span class="line">d = inverse(e, (p - <span class="number">1</span>) * (q - <span class="number">1</span>))</span><br><span class="line"><span class="built_in">print</span>(long_to_bytes(<span class="built_in">pow</span>(c, d, n)))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{rsa_crt_fault_1nj3ct10n_p_4e2b8c}</code></p><p>这在真实世界真发生过——Chrome 的 RSA 签名就有类似漏洞。防御是签名后校验 <code>S^e ≡ m</code>，不通过就拒绝输出。</p><h2 id="48-重复的随机数"><a href="#48-重复的随机数" class="headerlink" title="48 重复的随机数"></a>48 重复的随机数</h2><p>共享素数。两个模数 n1、n2 看着都大，但生成脚本偷懒用了同一个随机数源，导致共享一个素因子。<code>gcd(n1, n2)</code> 一步抠出来。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp48.py</span></span><br><span class="line"><span class="keyword">from</span> math <span class="keyword">import</span> gcd</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> inverse, long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n1, n2, e = <span class="built_in">int</span>(ns[<span class="string">&quot;n1&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;n2&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>])</span><br><span class="line">c = <span class="built_in">int</span>(ns[<span class="string">&quot;c&quot;</span>])</span><br><span class="line"></span><br><span class="line">p = gcd(n1, n2)</span><br><span class="line">q = n1 // p</span><br><span class="line"></span><br><span class="line">d = inverse(e, (p - <span class="number">1</span>) * (q - <span class="number">1</span>))</span><br><span class="line"><span class="built_in">print</span>(long_to_bytes(<span class="built_in">pow</span>(c, d, n1)))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{sh4r3d_pr1m3_gcd_n1_n2_d4f9a7}</code></p><p>给了两个以上 RSA 模数第一件事永远互相 gcd 一轮。历史上 Debian 弱熵事件让海量公钥共享素数，真实世界被批量破过。</p><h2 id="49-有偏的随机数"><a href="#49-有偏的随机数" class="headerlink" title="49 有偏的随机数"></a>49 有偏的随机数</h2><p>素数生成被限制在极小窗口：<code>p = base + x</code>，x 只有 32768 种可能。随机性名存实亡。枚举 x 试整除就行。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp49.py</span></span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> inverse, long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line"></span><br><span class="line">n, e, c = <span class="built_in">int</span>(ns[<span class="string">&quot;n&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;e&quot;</span>]), <span class="built_in">int</span>(ns[<span class="string">&quot;c&quot;</span>])</span><br><span class="line">base = <span class="built_in">int</span>(ns[<span class="string">&quot;base&quot;</span>])</span><br><span class="line">window = <span class="built_in">int</span>(ns[<span class="string">&quot;window&quot;</span>])</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(window):</span><br><span class="line">    <span class="keyword">if</span> n % (base + x) == <span class="number">0</span>:</span><br><span class="line">        p = base + x</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">q = n // p</span><br><span class="line">d = inverse(e, (p - <span class="number">1</span>) * (q - <span class="number">1</span>))</span><br><span class="line"><span class="built_in">print</span>(long_to_bytes(<span class="built_in">pow</span>(c, d, n)))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{b1a5ed_pr1m3_w1nd0w_0f_2_17_c6e4d1}</code></p><p>flag 里 <code>0f_2_17</code> 指生成器用了 <code>getPrime(2)</code> 之类的小参数。随机数熵一旦被压缩 RSA 就等于明文。</p><hr><h1 id="九、椭圆曲线与实现层攻击（50–55）"><a href="#九、椭圆曲线与实现层攻击（50–55）" class="headerlink" title="九、椭圆曲线与实现层攻击（50–55）"></a>九、椭圆曲线与实现层攻击（50–55）</h1><p>椭圆曲线题公共工具先立好：点加法、倍点、标量乘法、BSGS。曲线参数从 chall.txt 读（p、a、b、生成元 P、目标点 Q &#x3D; d*P）。50–53 共用：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">ecc_add</span>(<span class="params">p, a, P, Q</span>):</span><br><span class="line">    <span class="keyword">if</span> P <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        <span class="keyword">return</span> Q</span><br><span class="line">    <span class="keyword">if</span> Q <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        <span class="keyword">return</span> P</span><br><span class="line">    x1, y1 = P</span><br><span class="line">    x2, y2 = Q</span><br><span class="line">    <span class="keyword">if</span> x1 == x2 <span class="keyword">and</span> (y1 + y2) % p == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line">    <span class="keyword">if</span> P == Q:</span><br><span class="line">        lam = (<span class="number">3</span> * x1 * x1 + a) * <span class="built_in">pow</span>(<span class="number">2</span> * y1, -<span class="number">1</span>, p) % p</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        lam = (y2 - y1) * <span class="built_in">pow</span>((x2 - x1) % p, -<span class="number">1</span>, p) % p</span><br><span class="line">    x3 = (lam * lam - x1 - x2) % p</span><br><span class="line">    y3 = (lam * (x1 - x3) - y1) % p</span><br><span class="line">    <span class="keyword">return</span> (x3, y3)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">ecc_mul</span>(<span class="params">p, a, k, P</span>):</span><br><span class="line">    R = <span class="literal">None</span></span><br><span class="line">    <span class="keyword">while</span> k:</span><br><span class="line">        <span class="keyword">if</span> k &amp; <span class="number">1</span>:</span><br><span class="line">            R = ecc_add(p, a, R, P)</span><br><span class="line">        P = ecc_add(p, a, P, P)</span><br><span class="line">        k &gt;&gt;= <span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> R</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">bsgs</span>(<span class="params">p, a, G, Q, r</span>):</span><br><span class="line">    m = math.isqrt(r) + <span class="number">1</span></span><br><span class="line">    table = &#123;&#125;</span><br><span class="line">    cur = <span class="literal">None</span></span><br><span class="line">    <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(m):</span><br><span class="line">        table[cur] = j</span><br><span class="line">        cur = ecc_add(p, a, cur, G)</span><br><span class="line">    mG = ecc_mul(p, a, m, G)</span><br><span class="line">    neg = <span class="literal">None</span> <span class="keyword">if</span> mG <span class="keyword">is</span> <span class="literal">None</span> <span class="keyword">else</span> (mG[<span class="number">0</span>], (-mG[<span class="number">1</span>]) % p)</span><br><span class="line">    cur = Q</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(m):</span><br><span class="line">        <span class="keyword">if</span> cur <span class="keyword">in</span> table:</span><br><span class="line">            <span class="keyword">return</span> (i * m + table[cur]) % r</span><br><span class="line">        cur = ecc_add(p, a, cur, neg)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">None</span></span><br></pre></td></tr></table></figure><p>BSGS 思想和 14 题完全一致，只是”加法”从模乘换成了曲线点加法。</p><h2 id="50-光滑的阶"><a href="#50-光滑的阶" class="headerlink" title="50 光滑的阶"></a>50 光滑的阶</h2><p>Pohlig-Hellman。群阶 N 是光滑数，分解后全是小素因子幂。把大问题拆成小问题：对每个素因子幂 q^e，把 P 和 Q 都乘以 N&#x2F;q^e 投影到阶为 q^e 的子群，BSGS 解出 d mod q^e，CRT 合并成完整 d。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp50.py</span></span><br><span class="line"><span class="keyword">import</span> math</span><br><span class="line"><span class="keyword">from</span> Crypto.Util.number <span class="keyword">import</span> long_to_bytes</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line">p = <span class="built_in">int</span>(ns[<span class="string">&quot;p&quot;</span>]); a = <span class="built_in">int</span>(ns[<span class="string">&quot;a&quot;</span>])</span><br><span class="line">P = <span class="built_in">eval</span>(ns[<span class="string">&quot;P&quot;</span>]); Q = <span class="built_in">eval</span>(ns[<span class="string">&quot;Q&quot;</span>])</span><br><span class="line">N = <span class="built_in">int</span>(ns[<span class="string">&quot;group_order&quot;</span>])</span><br><span class="line">facts = <span class="built_in">eval</span>(ns[<span class="string">&quot;order_factorization&quot;</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- ecc_add / ecc_mul / bsgs 粘贴上方 ---</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">crt</span>(<span class="params">rs, ms</span>):</span><br><span class="line">    M = <span class="number">1</span></span><br><span class="line">    <span class="keyword">for</span> m <span class="keyword">in</span> ms:</span><br><span class="line">        M *= m</span><br><span class="line">    x = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> r, m <span class="keyword">in</span> <span class="built_in">zip</span>(rs, ms):</span><br><span class="line">        x += r * (M // m) * <span class="built_in">pow</span>(M // m, -<span class="number">1</span>, m)</span><br><span class="line">    <span class="keyword">return</span> x % M</span><br><span class="line"></span><br><span class="line">res, mods = [], []</span><br><span class="line"><span class="keyword">for</span> q, e <span class="keyword">in</span> facts.items():</span><br><span class="line">    mod = q ** e</span><br><span class="line">    G = ecc_mul(p, a, N // mod, P)</span><br><span class="line">    H = ecc_mul(p, a, N // mod, Q)</span><br><span class="line">    x = bsgs(p, a, G, H, mod)</span><br><span class="line">    res.append(x)</span><br><span class="line">    mods.append(mod)</span><br><span class="line"></span><br><span class="line">d = crt(res, mods)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;d =&quot;</span>, d)</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{poh1ig_he11man_sm00th_o1der_3f8b2a}</code></p><p>群阶最大素因子小（2^20 内）ECDLP 瞬间可解。ECC 要求 #E 是大素数就是这个原因。</p><h2 id="51-MOV的桥梁"><a href="#51-MOV的桥梁" class="headerlink" title="51 MOV的桥梁"></a>51 MOV的桥梁</h2><p>MOV 攻击。超奇异曲线嵌入度 k 很小（这题 k&#x3D;2），Weil&#x2F;Tate 配对把 ECDLP 搬到 F_{p²} 乘法群里。题目的 chall.txt 已把配对算好，直接给了乘法群里的 g、h&#x3D;g^x，在 F_{p²} 里跑 BSGS。F_{p²} 元素用 (u,v)&#x3D;u+v*i（i²&#x3D;-1）表示。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp51.py</span></span><br><span class="line"><span class="keyword">import</span> math</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line">p = <span class="built_in">int</span>(ns[<span class="string">&quot;p&quot;</span>])</span><br><span class="line">g = <span class="built_in">eval</span>(ns[<span class="string">&quot;g&quot;</span>]); h = <span class="built_in">eval</span>(ns[<span class="string">&quot;h&quot;</span>])</span><br><span class="line">order = <span class="built_in">int</span>(ns[<span class="string">&quot;order(g)&quot;</span>]) <span class="keyword">if</span> <span class="string">&quot;order(g)&quot;</span> <span class="keyword">in</span> ns <span class="keyword">else</span> <span class="built_in">eval</span>(ns[<span class="string">&quot;order_g&quot;</span>])</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">f2_mul</span>(<span class="params">A, B</span>):</span><br><span class="line">    a, b = A</span><br><span class="line">    c, d = B</span><br><span class="line">    <span class="keyword">return</span> ((a * c - b * d) % p, (a * d + b * c) % p)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">f2_pow</span>(<span class="params">A, k</span>):</span><br><span class="line">    R = (<span class="number">1</span>, <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">while</span> k:</span><br><span class="line">        <span class="keyword">if</span> k &amp; <span class="number">1</span>:</span><br><span class="line">            R = f2_mul(R, A)</span><br><span class="line">        A = f2_mul(A, A)</span><br><span class="line">        k &gt;&gt;= <span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> R</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">bsgs_f2</span>(<span class="params">g, h, r</span>):</span><br><span class="line">    m = math.isqrt(r) + <span class="number">1</span></span><br><span class="line">    table = &#123;&#125;</span><br><span class="line">    cur = (<span class="number">1</span>, <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(m):</span><br><span class="line">        table[cur] = j</span><br><span class="line">        cur = f2_mul(cur, g)</span><br><span class="line">    gm = f2_pow(g, m)</span><br><span class="line">    a0, b0 = gm</span><br><span class="line">    inv = <span class="built_in">pow</span>((a0 * a0 + b0 * b0) % p, -<span class="number">1</span>, p)</span><br><span class="line">    ginv = (a0 * inv % p, (-b0) * inv % p)</span><br><span class="line">    cur = h</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(m):</span><br><span class="line">        <span class="keyword">if</span> cur <span class="keyword">in</span> table:</span><br><span class="line">            <span class="keyword">return</span> (i * m + table[cur]) % r</span><br><span class="line">        cur = f2_mul(cur, ginv)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">x = bsgs_f2(g, h, order)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;x =&quot;</span>, x)</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{mov_emb3dd1ng_k2_p41r1ng_5c9d1f}</code></p><p>曲线选错（超奇异、小嵌入度）把”困难”的 ECDLP 降级成”容易”的 DLP。P-256 这种标准曲线嵌入度大得吓人，MOV 打不动。</p><h2 id="52-无效的点"><a href="#52-无效的点" class="headerlink" title="52 无效的点"></a>52 无效的点</h2><p>无效曲线攻击。点加法公式只用到 a 完全不碰 b，所以 a 一样的两条曲线（y²&#x3D;x³+2 和 y²&#x3D;x³+3）加法规则一样。服务端只校验坐标存在不校验点在原曲线上，提交小阶曲线上的点（G1 阶 8191、G2 阶 16381），让服务端做标量乘法，返回的就是 d mod 8191 和 d mod 16381。两个小答案 BSGS 秒解 CRT 合并。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp52.py</span></span><br><span class="line"><span class="keyword">import</span> math, re</span><br><span class="line"></span><br><span class="line">raw = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read()</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">ev</span>(<span class="params">pat</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">eval</span>(re.search(pat, raw).group(<span class="number">1</span>))</span><br><span class="line"></span><br><span class="line">p = <span class="built_in">int</span>(re.search(<span class="string">r&quot;p = (\d+)&quot;</span>, raw).group(<span class="number">1</span>))</span><br><span class="line">G1 = ev(<span class="string">r&quot;G1.*?= \(([-\d, ]+)\)&quot;</span>)</span><br><span class="line">R1 = ev(<span class="string">r&quot;R1 = d\*G1 = \(([-\d, ]+)\)&quot;</span>)</span><br><span class="line">G2 = ev(<span class="string">r&quot;G2.*?= \(([-\d, ]+)\)&quot;</span>)</span><br><span class="line">R2 = ev(<span class="string">r&quot;R2 = d\*G2 = \(([-\d, ]+)\)&quot;</span>)</span><br><span class="line">r1, r2 = <span class="number">8191</span>, <span class="number">16381</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># --- ecc_add / ecc_mul / bsgs 粘贴上方，两条曲线 a 都是 0 ---</span></span><br><span class="line"></span><br><span class="line">d1 = bsgs(p, <span class="number">0</span>, G1, R1, r1)</span><br><span class="line">d2 = bsgs(p, <span class="number">0</span>, G2, R2, r2)</span><br><span class="line"></span><br><span class="line">M = r1 * r2</span><br><span class="line">d = (d1 * r2 * <span class="built_in">pow</span>(r2, -<span class="number">1</span>, r1) + d2 * r1 * <span class="built_in">pow</span>(r1, -<span class="number">1</span>, r2)) % M</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;d =&quot;</span>, d)</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{1nval1d_curv3_sm411_subgr0up_1b7d9e}</code></p><p>协议必须验证传入点确实在既定曲线上，只查”坐标合法”是经典实现漏洞。</p><h2 id="53-异常之曲线"><a href="#53-异常之曲线" class="headerlink" title="53 异常之曲线"></a>53 异常之曲线</h2><p>Smart 攻击。<code>#E(F_p) = p</code> 的曲线叫异常曲线，Smart&#x2F;Semaev 通过 p-adic 提升把曲线群映射成加法群，ECDLP 变普通整除问题多项式时间可解。这题 p&#x3D;7 极小，直接暴力累加 P 直到等于 Q。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp53.py</span></span><br><span class="line"><span class="keyword">import</span> math</span><br><span class="line"></span><br><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br><span class="line">p = <span class="built_in">int</span>(ns[<span class="string">&quot;p&quot;</span>]); a = <span class="built_in">int</span>(ns[<span class="string">&quot;a&quot;</span>])</span><br><span class="line">P = <span class="built_in">eval</span>(ns[<span class="string">&quot;P&quot;</span>]); Q = <span class="built_in">eval</span>(ns[<span class="string">&quot;Q&quot;</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- ecc_add 粘贴上方 ---</span></span><br><span class="line"></span><br><span class="line">cur = <span class="literal">None</span></span><br><span class="line">x = <span class="literal">None</span></span><br><span class="line"><span class="keyword">for</span> k <span class="keyword">in</span> <span class="built_in">range</span>(p):</span><br><span class="line">    <span class="keyword">if</span> cur == Q:</span><br><span class="line">        x = k</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">    cur = ecc_add(p, a, cur, P)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;x =&quot;</span>, x)</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{an0ma1us_curv3_sm4rt_p3rl_h0ng_a6e0c1}</code></p><p>异常曲线配小 p 就是白送。标准曲线生成后必须验证 <code>#E != p</code> 且非超奇异。</p><h2 id="54-差分故障"><a href="#54-差分故障" class="headerlink" title="54 差分故障"></a>54 差分故障</h2><p>AES 差分故障分析（DFA）。最后一轮没有 MixColumns，注入到第 9 轮输出的单字节故障经过 ShiftRows 后只污染一个输出字节。手里同时有正确密文和 16 个故障密文（每个位置故障一次），对每个位置枚举最后一轮密钥字节 K，用 S 盒前后关系筛唯一候选：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">c  = SBOX[x]   ^ K   （正确）</span><br><span class="line">cf = SBOX[x^1] ^ K   （故障）</span><br></pre></td></tr></table></figure><p>由 <code>c[j] ^ K</code> 反查 S 盒得 x，再验证 <code>SBOX[x^1] == cf[j] ^ K</code>。单个位置可能 2~4 个候选，凑齐 16 字节后用已知明文 + 密钥调度一致性筛出唯一主密钥。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp54.py</span></span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"><span class="keyword">from</span> itertools <span class="keyword">import</span> product</span><br><span class="line"><span class="keyword">from</span> Crypto.Cipher <span class="keyword">import</span> AES</span><br><span class="line"></span><br><span class="line">SBOX = <span class="built_in">bytes</span>([</span><br><span class="line"><span class="number">0x63</span>,<span class="number">0x7c</span>,<span class="number">0x77</span>,<span class="number">0x7b</span>,<span class="number">0xf2</span>,<span class="number">0x6b</span>,<span class="number">0x6f</span>,<span class="number">0xc5</span>,<span class="number">0x30</span>,<span class="number">0x01</span>,<span class="number">0x67</span>,<span class="number">0x2b</span>,<span class="number">0xfe</span>,<span class="number">0xd7</span>,<span class="number">0xab</span>,<span class="number">0x76</span>,</span><br><span class="line"><span class="number">0xca</span>,<span class="number">0x82</span>,<span class="number">0xc9</span>,<span class="number">0x7d</span>,<span class="number">0xfa</span>,<span class="number">0x59</span>,<span class="number">0x47</span>,<span class="number">0xf0</span>,<span class="number">0xad</span>,<span class="number">0xd4</span>,<span class="number">0xa2</span>,<span class="number">0xaf</span>,<span class="number">0x9c</span>,<span class="number">0xa4</span>,<span class="number">0x72</span>,<span class="number">0xc0</span>,</span><br><span class="line"><span class="number">0xb7</span>,<span class="number">0xfd</span>,<span class="number">0x93</span>,<span class="number">0x26</span>,<span class="number">0x36</span>,<span class="number">0x3f</span>,<span class="number">0xf7</span>,<span class="number">0xcc</span>,<span class="number">0x34</span>,<span class="number">0xa5</span>,<span class="number">0xe5</span>,<span class="number">0xf1</span>,<span class="number">0x71</span>,<span class="number">0xd8</span>,<span class="number">0x31</span>,<span class="number">0x15</span>,</span><br><span class="line"><span class="number">0x04</span>,<span class="number">0xc7</span>,<span class="number">0x23</span>,<span class="number">0xc3</span>,<span class="number">0x18</span>,<span class="number">0x96</span>,<span class="number">0x05</span>,<span class="number">0x9a</span>,<span class="number">0x07</span>,<span class="number">0x12</span>,<span class="number">0x80</span>,<span class="number">0xe2</span>,<span class="number">0xeb</span>,<span class="number">0x27</span>,<span class="number">0xb2</span>,<span class="number">0x75</span>,</span><br><span class="line"><span class="number">0x09</span>,<span class="number">0x83</span>,<span class="number">0x2c</span>,<span class="number">0x1a</span>,<span class="number">0x1b</span>,<span class="number">0x6e</span>,<span class="number">0x5a</span>,<span class="number">0xa0</span>,<span class="number">0x52</span>,<span class="number">0x3b</span>,<span class="number">0xd6</span>,<span class="number">0xb3</span>,<span class="number">0x29</span>,<span class="number">0xe3</span>,<span class="number">0x2f</span>,<span class="number">0x84</span>,</span><br><span class="line"><span class="number">0x53</span>,<span class="number">0xd1</span>,<span class="number">0x00</span>,<span class="number">0xed</span>,<span class="number">0x20</span>,<span class="number">0xfc</span>,<span class="number">0xb1</span>,<span class="number">0x5b</span>,<span class="number">0x6a</span>,<span class="number">0xcb</span>,<span class="number">0xbe</span>,<span class="number">0x39</span>,<span class="number">0x4a</span>,<span class="number">0x4c</span>,<span class="number">0x58</span>,<span class="number">0xcf</span>,</span><br><span class="line"><span class="number">0xd0</span>,<span class="number">0xef</span>,<span class="number">0xaa</span>,<span class="number">0xfb</span>,<span class="number">0x43</span>,<span class="number">0x4d</span>,<span class="number">0x33</span>,<span class="number">0x85</span>,<span class="number">0x45</span>,<span class="number">0xf9</span>,<span class="number">0x02</span>,<span class="number">0x7f</span>,<span class="number">0x50</span>,<span class="number">0x3c</span>,<span class="number">0x9f</span>,<span class="number">0xa8</span>,</span><br><span class="line"><span class="number">0x51</span>,<span class="number">0xa3</span>,<span class="number">0x40</span>,<span class="number">0x8f</span>,<span class="number">0x92</span>,<span class="number">0x9d</span>,<span class="number">0x38</span>,<span class="number">0xf5</span>,<span class="number">0xbc</span>,<span class="number">0xb6</span>,<span class="number">0xda</span>,<span class="number">0x21</span>,<span class="number">0x10</span>,<span class="number">0xff</span>,<span class="number">0xf3</span>,<span class="number">0xd2</span>,</span><br><span class="line"><span class="number">0xcd</span>,<span class="number">0x0c</span>,<span class="number">0x13</span>,<span class="number">0xec</span>,<span class="number">0x5f</span>,<span class="number">0x97</span>,<span class="number">0x44</span>,<span class="number">0x17</span>,<span class="number">0xc4</span>,<span class="number">0xa7</span>,<span class="number">0x7e</span>,<span class="number">0x3d</span>,<span class="number">0x64</span>,<span class="number">0x5d</span>,<span class="number">0x19</span>,<span class="number">0x73</span>,</span><br><span class="line"><span class="number">0x60</span>,<span class="number">0x81</span>,<span class="number">0x4f</span>,<span class="number">0xdc</span>,<span class="number">0x22</span>,<span class="number">0x2a</span>,<span class="number">0x90</span>,<span class="number">0x88</span>,<span class="number">0x46</span>,<span class="number">0xee</span>,<span class="number">0xb8</span>,<span class="number">0x14</span>,<span class="number">0xde</span>,<span class="number">0x5e</span>,<span class="number">0x0b</span>,<span class="number">0xdb</span>,</span><br><span class="line"><span class="number">0xe0</span>,<span class="number">0x32</span>,<span class="number">0x3a</span>,<span class="number">0x0a</span>,<span class="number">0x49</span>,<span class="number">0x06</span>,<span class="number">0x24</span>,<span class="number">0x5c</span>,<span class="number">0xc2</span>,<span class="number">0xd3</span>,<span class="number">0xac</span>,<span class="number">0x62</span>,<span class="number">0x91</span>,<span class="number">0x95</span>,<span class="number">0xe4</span>,<span class="number">0x79</span>,</span><br><span class="line"><span class="number">0xe7</span>,<span class="number">0xc8</span>,<span class="number">0x37</span>,<span class="number">0x6d</span>,<span class="number">0x8d</span>,<span class="number">0xd5</span>,<span class="number">0x4e</span>,<span class="number">0xa9</span>,<span class="number">0x6c</span>,<span class="number">0x56</span>,<span class="number">0xf4</span>,<span class="number">0xea</span>,<span class="number">0x65</span>,<span class="number">0x7a</span>,<span class="number">0xae</span>,<span class="number">0x08</span>,</span><br><span class="line"><span class="number">0xba</span>,<span class="number">0x78</span>,<span class="number">0x25</span>,<span class="number">0x2e</span>,<span class="number">0x1c</span>,<span class="number">0xa6</span>,<span class="number">0xb4</span>,<span class="number">0xc6</span>,<span class="number">0xe8</span>,<span class="number">0xdd</span>,<span class="number">0x74</span>,<span class="number">0x1f</span>,<span class="number">0x4b</span>,<span class="number">0xbd</span>,<span class="number">0x8b</span>,<span class="number">0x8a</span>,</span><br><span class="line"><span class="number">0x70</span>,<span class="number">0x3e</span>,<span class="number">0xb5</span>,<span class="number">0x66</span>,<span class="number">0x48</span>,<span class="number">0x03</span>,<span class="number">0xf6</span>,<span class="number">0x0e</span>,<span class="number">0x61</span>,<span class="number">0x35</span>,<span class="number">0x57</span>,<span class="number">0xb9</span>,<span class="number">0x86</span>,<span class="number">0xc1</span>,<span class="number">0x1d</span>,<span class="number">0x9e</span>,</span><br><span class="line"><span class="number">0xe1</span>,<span class="number">0xf8</span>,<span class="number">0x98</span>,<span class="number">0x11</span>,<span class="number">0x69</span>,<span class="number">0xd9</span>,<span class="number">0x8e</span>,<span class="number">0x94</span>,<span class="number">0x9b</span>,<span class="number">0x1e</span>,<span class="number">0x87</span>,<span class="number">0xe9</span>,<span class="number">0xce</span>,<span class="number">0x55</span>,<span class="number">0x28</span>,<span class="number">0xdf</span>,</span><br><span class="line"><span class="number">0x8c</span>,<span class="number">0xa1</span>,<span class="number">0x89</span>,<span class="number">0x0d</span>,<span class="number">0xbf</span>,<span class="number">0xe6</span>,<span class="number">0x42</span>,<span class="number">0x68</span>,<span class="number">0x41</span>,<span class="number">0x99</span>,<span class="number">0x2d</span>,<span class="number">0x0f</span>,<span class="number">0xb0</span>,<span class="number">0x54</span>,<span class="number">0xbb</span>,<span class="number">0x16</span>])</span><br><span class="line">SINV = [<span class="number">0</span>] * <span class="number">256</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">256</span>):</span><br><span class="line">    SINV[SBOX[i]] = i</span><br><span class="line"></span><br><span class="line">data = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read()</span><br><span class="line">pt_hex = data.split(<span class="string">&quot;pt_hex = &quot;</span>)[<span class="number">1</span>].split()[<span class="number">0</span>]</span><br><span class="line">c_hex = data.split(<span class="string">&quot;c_hex = &quot;</span>)[<span class="number">1</span>].split()[<span class="number">0</span>]</span><br><span class="line">faults_src = data.split(<span class="string">&quot;faults = &#123;&quot;</span>)[<span class="number">1</span>].split(<span class="string">&quot;&#125;&quot;</span>)[<span class="number">0</span>]</span><br><span class="line">faults = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> m <span class="keyword">in</span> re.finditer(<span class="string">r&quot;(\d+):\s*([0-9a-f]+)&quot;</span>, faults_src):</span><br><span class="line">    faults[<span class="built_in">int</span>(m.group(<span class="number">1</span>))] = <span class="built_in">bytes</span>.fromhex(m.group(<span class="number">2</span>))</span><br><span class="line">ct_flag = <span class="built_in">bytes</span>.fromhex(data.split(<span class="string">&quot;ct_flag_hex = &quot;</span>)[<span class="number">1</span>].split()[<span class="number">0</span>])</span><br><span class="line"></span><br><span class="line">pt = <span class="built_in">bytes</span>.fromhex(pt_hex)</span><br><span class="line">c = <span class="built_in">bytes</span>.fromhex(c_hex)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">out_for_pos</span>(<span class="params">pos</span>):</span><br><span class="line">    r = pos % <span class="number">4</span></span><br><span class="line">    col = pos // <span class="number">4</span></span><br><span class="line">    <span class="keyword">return</span> r + <span class="number">4</span> * ((col - r) % <span class="number">4</span>)</span><br><span class="line"></span><br><span class="line">cands_pos = []</span><br><span class="line"><span class="keyword">for</span> pos <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">16</span>):</span><br><span class="line">    cf = faults[pos]</span><br><span class="line">    out = out_for_pos(pos)</span><br><span class="line">    cands = []</span><br><span class="line">    <span class="keyword">for</span> K <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">256</span>):</span><br><span class="line">        x = SINV[c[out] ^ K]</span><br><span class="line">        <span class="keyword">if</span> SBOX[x ^ <span class="number">1</span>] == cf[out] ^ K:</span><br><span class="line">            cands.append(K)</span><br><span class="line">    cands_pos.append((out, cands))</span><br><span class="line"></span><br><span class="line">Rcon = [<span class="number">0x01</span>, <span class="number">0x02</span>, <span class="number">0x04</span>, <span class="number">0x08</span>, <span class="number">0x10</span>, <span class="number">0x20</span>, <span class="number">0x40</span>, <span class="number">0x80</span>, <span class="number">0x1b</span>, <span class="number">0x36</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">inv_keyschedule</span>(<span class="params">k10</span>):</span><br><span class="line">    words = [<span class="literal">None</span>] * <span class="number">44</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">4</span>):</span><br><span class="line">        words[<span class="number">40</span> + i] = <span class="built_in">list</span>(k10[i*<span class="number">4</span>:i*<span class="number">4</span>+<span class="number">4</span>])</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">43</span>, <span class="number">3</span>, -<span class="number">1</span>):</span><br><span class="line">        <span class="keyword">if</span> i % <span class="number">4</span> == <span class="number">0</span>:</span><br><span class="line">            t = words[i - <span class="number">1</span>][:]</span><br><span class="line">            t = t[<span class="number">1</span>:] + [t[<span class="number">0</span>]]</span><br><span class="line">            t = [SBOX[b] <span class="keyword">for</span> b <span class="keyword">in</span> t]</span><br><span class="line">            t[<span class="number">0</span>] ^= Rcon[i // <span class="number">4</span> - <span class="number">1</span>]</span><br><span class="line">            words[i - <span class="number">4</span>] = [words[i][j] ^ t[j] <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">4</span>)]</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            words[i - <span class="number">4</span>] = [words[i][j] ^ words[i - <span class="number">1</span>][j] <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">4</span>)]</span><br><span class="line">    <span class="keyword">return</span> <span class="string">b&quot;&quot;</span>.join(<span class="built_in">bytes</span>(w) <span class="keyword">for</span> w <span class="keyword">in</span> words[:<span class="number">4</span>])</span><br><span class="line"></span><br><span class="line">found = <span class="literal">None</span></span><br><span class="line"><span class="keyword">for</span> combo <span class="keyword">in</span> product(*[cds <span class="keyword">for</span> _, cds <span class="keyword">in</span> cands_pos]):</span><br><span class="line">    k10 = <span class="built_in">bytearray</span>(<span class="number">16</span>)</span><br><span class="line">    <span class="keyword">for</span> (out, _), K <span class="keyword">in</span> <span class="built_in">zip</span>(cands_pos, combo):</span><br><span class="line">        k10[out] = K</span><br><span class="line">    master = inv_keyschedule(<span class="built_in">bytes</span>(k10))</span><br><span class="line">    <span class="keyword">if</span> AES.new(master, AES.MODE_ECB).encrypt(pt) == c:</span><br><span class="line">        found = master</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">assert</span> found <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>, <span class="string">&quot;未找到匹配密钥&quot;</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;主密钥:&quot;</span>, found.<span class="built_in">hex</span>())</span><br><span class="line"><span class="built_in">print</span>(AES.new(found, AES.MODE_ECB).decrypt(ct_flag))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{a3s_d1ff_fault_1nj3ct10n_c0nt30nd_9c4e1b}</code></p><p>DFA 不碰数学弱点，靠物理注入让芯片算错。现代防御是解密后做一致性校验加随机掩码。</p><h2 id="55-时间的窃听"><a href="#55-时间的窃听" class="headerlink" title="55 时间的窃听"></a>55 时间的窃听</h2><p>RSA 时序侧信道。平方-乘模幂按 d 每位分支：位是 1 就多做一次乘法，执行时间变长。每次模幂计时，每位采样 10 次取平均压噪声，平均耗时偏高（约 103）的位是 1，偏低（约 100）的位是 0。逐位拼出 64 位 d。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># exp55.py</span></span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line">raw = <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>).read()</span><br><span class="line"></span><br><span class="line">bits = []</span><br><span class="line">pat = re.<span class="built_in">compile</span>(<span class="string">r&quot;bit(\d+):\s*(.+)&quot;</span>)</span><br><span class="line">rows = [(<span class="built_in">int</span>(m.group(<span class="number">1</span>)), [<span class="built_in">float</span>(x) <span class="keyword">for</span> x <span class="keyword">in</span> m.group(<span class="number">2</span>).split()])</span><br><span class="line">        <span class="keyword">for</span> m <span class="keyword">in</span> pat.finditer(raw)]</span><br><span class="line">rows.sort()</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> idx, vals <span class="keyword">in</span> rows:</span><br><span class="line">    avg = <span class="built_in">sum</span>(vals) / <span class="built_in">len</span>(vals)</span><br><span class="line">    bits.append(<span class="number">1</span> <span class="keyword">if</span> avg &gt; <span class="number">101.5</span> <span class="keyword">else</span> <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">d = <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> b <span class="keyword">in</span> bits:</span><br><span class="line">    d = (d &lt;&lt; <span class="number">1</span>) | b</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;d =&quot;</span>, <span class="built_in">hex</span>(d))</span><br></pre></td></tr></table></figure><p>FLAG：<code>flag{t1m1ng_s1d3_ch4nn3l_k0ch3r_d3f9a2c}</code></p><p>算法数学上完全正确，秘密却从时间、功耗、电磁波里漏走。Kocher 的时序攻击打穿过 RSA&#x2F;智能卡。防御就是恒定时间算法加随机盲化。</p><hr><h1 id="收尾"><a href="#收尾" class="headerlink" title="收尾"></a>收尾</h1><p>从 01 刷到 55（跳了 41、42），工具链、数论、古典密码、分组密码、伪随机数、RSA、椭圆曲线、侧信道全过了一遍。回头说点具体的。</p><p>chall.txt 解析和 hex&#x2F;bytes 互换看着琐碎，但占了前三题全部内容，后面每道题都要先过这关。”已知明文特征 + 小密钥空间 &#x3D; 爆破”是最常用的起手式——单字节 XOR、凯撒、频率分析、小窗口枚举，本质都是一个套路。</p><p>RSA 那块翻来覆去就是”分解 n 或者绕过分解”：近素数用费马，p-1 光滑用 Pollard p-1，中间大小用 rho，读题面找 p&#x2F;q 的代数关系，e 太大用 Wiener，明文太小直接开根，高位泄露就枚举低位。拿到题先把 n 的大小、e 的大小、有没有给部分参数扫一遍，方向基本就定了。</p><p>伪随机数那四题做完一个感受：LFSR、LCG、MT19937 全是纸老虎，只要拿到足够多的连续输出，内部状态直接被克隆。安全场景别用 random 模块，secrets 或 os.urandom 才对。</p><p>ECC 和侧信道是另一个量级的东西。Pohlig-Hellman 就是把大问题用 CRT 拆成小问题；无效曲线攻击的关键观察是加法公式不碰 b；DFA 和时序攻击告诉你算法再安全，实现和物理层照样漏。</p><p>这套题做下来最大的收获是条件反射：看到密文先想”数据长什么样、有没有泄露部分、算法有没有弱点”。三个问题答完，大半题目方向就有了，剩下就是写 exp 的事。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/21/qingcen-crypto-wp-full/</id>
    <link href="https://tsvmv.github.io/2026/09/21/qingcen-crypto-wp-full/"/>
    <published>2026-09-21T09:30:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>这套题从 01 做到 55（中间跳了 41、42），工具链、古典密码、数论、分组密码、伪随机数、RSA、椭圆曲线、侧信道都过了一遍。写的时候就是边做边记，卡哪写哪。</p>
<p>几个反复用到的东西先写这儿，后面就不重复了。</p>
<p>模运算不用解释。模逆元就是模世界里的除法，<code>pow(a, -1, m)</code> 一行搞定，前提是 gcd(a,m)&#x3D;1。字节和十六进制来回转是日常：<code>bytes.fromhex()</code>、<code>int.to_bytes()</code>、pycryptodome 的 <code>long_to_bytes</code> &#x2F; <code>bytes_to_long</code>。异或记住 <code>a ^ b ^ b == a</code>，流密码的命根子。</p>
<p>chall.txt 的解析有个固定套路——前面带中文说明行，直接 exec 会炸，逐行抠：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ns = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> <span class="built_in">open</span>(<span class="string">&quot;chall.txt&quot;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="string">&quot; = &quot;</span> <span class="keyword">in</span> line:</span><br><span class="line">        k, v = line.strip().split(<span class="string">&quot; = &quot;</span>, <span class="number">1</span>)</span><br><span class="line">        ns[k] = v</span><br></pre></td></tr></table></figure>

<p>后面大量 exp 都用这个模式。环境就 <code>pip install pycryptodome sympy gmpy2</code>，05–07 用 SageMath 更顺手，没装也能做。</p>]]>
    </summary>
    <title>青岑密码学题集 WP</title>
    <updated>2026-11-04T11:56:13.948Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="CTF" scheme="https://tsvmv.github.io/categories/CTF/"/>
    <category term="CTF" scheme="https://tsvmv.github.io/tags/CTF/"/>
    <category term="算法" scheme="https://tsvmv.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <category term="字符串" scheme="https://tsvmv.github.io/tags/%E5%AD%97%E7%AC%A6%E4%B8%B2/"/>
    <content>
      <![CDATA[<p>后缀数组（Suffix Array, SA）是字符串处理的另一大杀器。和 SAM 相比，SA 写起来简单一些，功能也覆盖了大部分字符串问题：不同子串个数、最长公共子串、出现次数统计、模式匹配。</p><p>这篇文章讲倍增法构造 SA，以及 LCP 数组的应用。</p><span id="more"></span><h2 id="1-什么是后缀数组"><a href="#1-什么是后缀数组" class="headerlink" title="1. 什么是后缀数组"></a>1. 什么是后缀数组</h2><p>对字符串 S，后缀数组 SA[i] 表示”排名第 i 的后缀的起始位置”。</p><p>比如 S &#x3D; “ababa”：</p><ul><li>后缀 0: ababa</li><li>后缀 1: baba</li><li>后缀 2: aba</li><li>后缀 3: ba</li><li>后缀 4: a</li></ul><p>按字典序排序：</p><ul><li>a (后缀4)</li><li>aba (后缀2)</li><li>ababa (后缀0)</li><li>ba (后缀3)</li><li>baba (后缀1)</li></ul><p>所以 SA &#x3D; [4, 2, 0, 3, 1]。</p><h2 id="2-倍增法构造"><a href="#2-倍增法构造" class="headerlink" title="2. 倍增法构造"></a>2. 倍增法构造</h2><h3 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h3><p>直接排序所有后缀 O(n² log n) 太慢。倍增法利用”长度为 2^k 的前缀排序结果”来加速。</p><ol><li>先按首字符排序（长度 1）</li><li>然后按长度 2 排序：每个后缀的前两个字符 &#x3D; 首字符 + 第二个字符</li><li>然后按长度 4 排序：前四个字符 &#x3D; 前两个字符 + 接下来两个字符</li><li>…每次翻倍，直到 2^k ≥ n</li></ol><p>关键：第 k 轮排序时，每个后缀的”关键字”是一个二元组 (rank[i], rank[i+2^(k-1)])。这个二元组的比较可以直接用之前的 rank 数组，不需要真正比较字符串。</p><h3 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">build_sa</span>(<span class="params">s</span>):</span><br><span class="line">    n = <span class="built_in">len</span>(s)</span><br><span class="line">    sa = <span class="built_in">list</span>(<span class="built_in">range</span>(n))</span><br><span class="line">    rank = [<span class="built_in">ord</span>(c) <span class="keyword">for</span> c <span class="keyword">in</span> s]</span><br><span class="line">    k = <span class="number">1</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> k &lt; n:</span><br><span class="line">        <span class="comment"># 按 (rank[i], rank[i+k]) 排序</span></span><br><span class="line">        sa.sort(key=<span class="keyword">lambda</span> i: (rank[i], rank[i+k] <span class="keyword">if</span> i+k &lt; n <span class="keyword">else</span> -<span class="number">1</span>))</span><br><span class="line">        </span><br><span class="line">        new_rank = [<span class="number">0</span>] * n</span><br><span class="line">        new_rank[sa[<span class="number">0</span>]] = <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, n):</span><br><span class="line">            prev, curr = sa[i-<span class="number">1</span>], sa[i]</span><br><span class="line">            prev_key = (rank[prev], rank[prev+k] <span class="keyword">if</span> prev+k &lt; n <span class="keyword">else</span> -<span class="number">1</span>)</span><br><span class="line">            curr_key = (rank[curr], rank[curr+k] <span class="keyword">if</span> curr+k &lt; n <span class="keyword">else</span> -<span class="number">1</span>)</span><br><span class="line">            new_rank[curr] = new_rank[prev] + (<span class="number">1</span> <span class="keyword">if</span> prev_key != curr_key <span class="keyword">else</span> <span class="number">0</span>)</span><br><span class="line">        </span><br><span class="line">        rank = new_rank</span><br><span class="line">        <span class="keyword">if</span> rank[sa[-<span class="number">1</span>]] == n - <span class="number">1</span>:</span><br><span class="line">            <span class="keyword">break</span>  <span class="comment"># 全部不同了，提前结束</span></span><br><span class="line">        k *= <span class="number">2</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> sa</span><br></pre></td></tr></table></figure><h2 id="3-LCP-数组"><a href="#3-LCP-数组" class="headerlink" title="3. LCP 数组"></a>3. LCP 数组</h2><p>LCP（Longest Common Prefix）数组记录”排名相邻的两个后缀的最长公共前缀长度”。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">LCP[i] = lcp(sa[i], sa[i+1])</span><br></pre></td></tr></table></figure><h3 id="用-Kasai-算法-O-n-求-LCP"><a href="#用-Kasai-算法-O-n-求-LCP" class="headerlink" title="用 Kasai 算法 O(n) 求 LCP"></a>用 Kasai 算法 O(n) 求 LCP</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">build_lcp</span>(<span class="params">s, sa</span>):</span><br><span class="line">    n = <span class="built_in">len</span>(s)</span><br><span class="line">    rank = [<span class="number">0</span>] * n</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(n):</span><br><span class="line">        rank[sa[i]] = i</span><br><span class="line">    </span><br><span class="line">    lcp = [<span class="number">0</span>] * (n - <span class="number">1</span>)</span><br><span class="line">    h = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(n):</span><br><span class="line">        <span class="keyword">if</span> rank[i] &gt; <span class="number">0</span>:</span><br><span class="line">            j = sa[rank[i] - <span class="number">1</span>]</span><br><span class="line">            <span class="keyword">while</span> i + h &lt; n <span class="keyword">and</span> j + h &lt; n <span class="keyword">and</span> s[i + h] == s[j + h]:</span><br><span class="line">                h += <span class="number">1</span></span><br><span class="line">            lcp[rank[i] - <span class="number">1</span>] = h</span><br><span class="line">            <span class="keyword">if</span> h &gt; <span class="number">0</span>:</span><br><span class="line">                h -= <span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> lcp</span><br></pre></td></tr></table></figure><h2 id="4-经典应用"><a href="#4-经典应用" class="headerlink" title="4. 经典应用"></a>4. 经典应用</h2><h3 id="4-1-不同子串个数"><a href="#4-1-不同子串个数" class="headerlink" title="4.1 不同子串个数"></a>4.1 不同子串个数</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">答案 = n*(n+1)/2 - sum(LCP)</span><br></pre></td></tr></table></figure><p>所有子串总数 &#x3D; n*(n+1)&#x2F;2，减去重复的部分（LCP 数组的和）。</p><h3 id="4-2-最长重复子串"><a href="#4-2-最长重复子串" class="headerlink" title="4.2 最长重复子串"></a>4.2 最长重复子串</h3><p>LCP 数组的最大值就是最长重复子串的长度。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">max_lcp = <span class="built_in">max</span>(lcp)</span><br><span class="line">position = sa[lcp.index(max_lcp)]</span><br><span class="line"><span class="built_in">print</span>(s[position:position+max_lcp])</span><br></pre></td></tr></table></figure><h3 id="4-3-出现次数最多的子串"><a href="#4-3-出现次数最多的子串" class="headerlink" title="4.3 出现次数最多的子串"></a>4.3 出现次数最多的子串</h3><p>对每个 i，如果 LCP[i] ≥ L，说明 sa[i] 和 sa[i+1] 的公共前缀长度 ≥ L。用单调栈求 LCP 的”最大矩形”，就能找到每个子串的最大出现次数。</p><h3 id="4-4-模式匹配"><a href="#4-4-模式匹配" class="headerlink" title="4.4 模式匹配"></a>4.4 模式匹配</h3><p>给一个模式 P，在 S 中找 P 出现的位置。用二分查找在 SA 中定位：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">find_pattern</span>(<span class="params">s, sa, pattern</span>):</span><br><span class="line">    <span class="keyword">import</span> bisect</span><br><span class="line">    n = <span class="built_in">len</span>(s)</span><br><span class="line">    m = <span class="built_in">len</span>(pattern)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 二分找左边界</span></span><br><span class="line">    lo, hi = <span class="number">0</span>, n - <span class="number">1</span></span><br><span class="line">    <span class="keyword">while</span> lo &lt; hi:</span><br><span class="line">        mid = (lo + hi) // <span class="number">2</span></span><br><span class="line">        <span class="keyword">if</span> s[sa[mid]:sa[mid]+m] &gt;= pattern:</span><br><span class="line">            hi = mid</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            lo = mid + <span class="number">1</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> s[sa[lo]:sa[lo]+m] != pattern:</span><br><span class="line">        <span class="keyword">return</span> -<span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> sa[lo]</span><br></pre></td></tr></table></figure><h2 id="5-SA-vs-SAM"><a href="#5-SA-vs-SAM" class="headerlink" title="5. SA vs SAM"></a>5. SA vs SAM</h2><table><thead><tr><th>对比项</th><th>后缀数组 SA</th><th>后缀自动机 SAM</th></tr></thead><tbody><tr><td>构造时间</td><td>O(n log n)</td><td>O(n)</td></tr><tr><td>空间</td><td>O(n)</td><td>O(n)</td></tr><tr><td>不同子串</td><td>O(n) 统计</td><td>O(n) 统计</td></tr><tr><td>最长重复子串</td><td>LCP 最大值</td><td>状态 maxlen</td></tr><tr><td>模式匹配</td><td>O(m log n)</td><td>O(m)</td></tr><tr><td>实现难度</td><td>简单</td><td>中等</td></tr><tr><td>可扩展性</td><td>较弱</td><td>强</td></tr></tbody></table><h2 id="6-常见坑"><a href="#6-常见坑" class="headerlink" title="6. 常见坑"></a>6. 常见坑</h2><ol><li><strong>字符映射</strong>：如果字符不是小写字母，先映射到 0~n-1</li><li><strong>rank 越界</strong>：i+k ≥ n 时第二个关键字设为 -1（比任何字符都小）</li><li><strong>提前终止</strong>：当所有 rank 都不同时，可以提前结束倍增</li><li><strong>LCP 长度</strong>：LCP 数组长度是 n-1，别越界</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>后缀数组是字符串处理的基础工具。和 SAM 二选一就行，两者功能重叠度很高。SA 写起来更直观，SAM 功能更强。建议先学 SA 理解后缀排序的思想，再学 SAM 理解 endpos 等价类。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/16/suffix-array-sa/</id>
    <link href="https://tsvmv.github.io/2026/09/16/suffix-array-sa/"/>
    <published>2026-09-16T15:00:00.000Z</published>
    <summary>
      <![CDATA[<p>后缀数组（Suffix Array, SA）是字符串处理的另一大杀器。和 SAM 相比，SA 写起来简单一些，功能也覆盖了大部分字符串问题：不同子串个数、最长公共子串、出现次数统计、模式匹配。</p>
<p>这篇文章讲倍增法构造 SA，以及 LCP 数组的应用。</p>]]>
    </summary>
    <title>后缀数组(SA)详解—从倍增法到 LCP 应用</title>
    <updated>2026-11-04T11:56:13.948Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="CTF" scheme="https://tsvmv.github.io/categories/CTF/"/>
    <category term="CTF" scheme="https://tsvmv.github.io/tags/CTF/"/>
    <category term="密码学" scheme="https://tsvmv.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="数论" scheme="https://tsvmv.github.io/tags/%E6%95%B0%E8%AE%BA/"/>
    <content>
      <![CDATA[<p>“有物不知其数，三三数之剩二，五五数之剩三，七七数之剩二，问物几何？”——《孙子算经》</p><p>这就是中国剩余定理（CRT）的起源。在 CTF 密码学中，CRT 无处不在：RSA 加速解密、共模攻击、广播攻击、Elliptic Curve 点合并……不会 CRT，密码学题基本做不动。</p><span id="more"></span><h2 id="1-定理内容"><a href="#1-定理内容" class="headerlink" title="1. 定理内容"></a>1. 定理内容</h2><p>给定一组同余方程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">x ≡ a₁ (mod m₁)</span><br><span class="line">x ≡ a₂ (mod m₂)</span><br><span class="line">...</span><br><span class="line">x ≡ aₖ (mod mₖ)</span><br></pre></td></tr></table></figure><p>如果 m₁, m₂, …, mₖ 两两互质，则存在唯一解 mod M &#x3D; m₁ × m₂ × … × mₖ。</p><h2 id="2-标准-CRT-实现"><a href="#2-标准-CRT-实现" class="headerlink" title="2. 标准 CRT 实现"></a>2. 标准 CRT 实现</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">crt</span>(<span class="params">remainders, moduli</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    remainders: [a1, a2, ..., ak]</span></span><br><span class="line"><span class="string">    moduli:     [m1, m2, ..., mk]  两两互质</span></span><br><span class="line"><span class="string">    返回 x mod M</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    M = <span class="number">1</span></span><br><span class="line">    <span class="keyword">for</span> m <span class="keyword">in</span> moduli:</span><br><span class="line">        M *= m</span><br><span class="line">    </span><br><span class="line">    result = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> a, m <span class="keyword">in</span> <span class="built_in">zip</span>(remainders, moduli):</span><br><span class="line">        Mi = M // m</span><br><span class="line">        <span class="comment"># Mi 在 mod m 下的逆元</span></span><br><span class="line">        inv = <span class="built_in">pow</span>(Mi, -<span class="number">1</span>, m)</span><br><span class="line">        result += a * Mi * inv</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> result % M</span><br></pre></td></tr></table></figure><h2 id="3-扩展-CRT（模数不互质）"><a href="#3-扩展-CRT（模数不互质）" class="headerlink" title="3. 扩展 CRT（模数不互质）"></a>3. 扩展 CRT（模数不互质）</h2><p>实际题目中模数经常不互质。这时候需要用扩展 CRT。</p><p>思路：两两合并。先解前两个方程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">x ≡ a₁ (mod m₁)</span><br><span class="line">x ≡ a₂ (mod m₂)</span><br></pre></td></tr></table></figure><p>设 x &#x3D; a₁ + k·m₁，代入第二个方程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">a₁ + k·m₁ ≡ a₂ (mod m₂)</span><br><span class="line">k·m₁ ≡ (a₂ - a₁) (mod m₂)</span><br></pre></td></tr></table></figure><p>设 g &#x3D; gcd(m₁, m₂)。如果 (a₂-a₁) 不能被 g 整除，无解。否则解出 k mod (m₂&#x2F;g)，合并为新的同余方程。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> math <span class="keyword">import</span> gcd</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">ext_gcd</span>(<span class="params">a, b</span>):</span><br><span class="line">    <span class="keyword">if</span> b == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> a, <span class="number">1</span>, <span class="number">0</span></span><br><span class="line">    g, x, y = ext_gcd(b, a % b)</span><br><span class="line">    <span class="keyword">return</span> g, y, x - (a // b) * y</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">crt_merge</span>(<span class="params">a1, m1, a2, m2</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;合并 x ≡ a1 (mod m1) 和 x ≡ a2 (mod m2)&quot;&quot;&quot;</span></span><br><span class="line">    g, p, q = ext_gcd(m1, m2)</span><br><span class="line">    <span class="keyword">if</span> (a2 - a1) % g != <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">None</span>  <span class="comment"># 无解</span></span><br><span class="line">    lcm = m1 // g * m2</span><br><span class="line">    k = ((a2 - a1) // g * p) % (m2 // g)</span><br><span class="line">    x = (a1 + k * m1) % lcm</span><br><span class="line">    <span class="keyword">return</span> x, lcm</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">ext_crt</span>(<span class="params">remainders, moduli</span>):</span><br><span class="line">    a, m = remainders[<span class="number">0</span>], moduli[<span class="number">0</span>]</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="built_in">len</span>(remainders)):</span><br><span class="line">        merged = crt_merge(a, m, remainders[i], moduli[i])</span><br><span class="line">        <span class="keyword">if</span> merged <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line">        a, m = merged</span><br><span class="line">    <span class="keyword">return</span> a % m</span><br></pre></td></tr></table></figure><h2 id="4-CTF-实战：RSA-CRT-加速"><a href="#4-CTF-实战：RSA-CRT-加速" class="headerlink" title="4. CTF 实战：RSA CRT 加速"></a>4. CTF 实战：RSA CRT 加速</h2><p>RSA 中，解密 m &#x3D; c^d mod n。如果知道 p 和 q，可以用 CRT 加速：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">dp = d mod (p-1)</span><br><span class="line">dq = d mod (q-1)</span><br><span class="line">mp = c^dp mod p</span><br><span class="line">mq = c^dq mod q</span><br><span class="line">m = CRT(mp, p, mq, q)</span><br></pre></td></tr></table></figure><p>比直接算 c^d mod n 快 4 倍左右。</p><h2 id="5-CTF-实战：RSA-共模攻击"><a href="#5-CTF-实战：RSA-共模攻击" class="headerlink" title="5. CTF 实战：RSA 共模攻击"></a>5. CTF 实战：RSA 共模攻击</h2><p>如果两组 (e1, n) 和 (e2, n) 用同一个 n 加密同一条明文 m：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">c1 = m^e1 mod n</span><br><span class="line">c2 = m^e2 mod n</span><br></pre></td></tr></table></figure><p>找 s1, s2 使得 s1·e1 + s2·e2 &#x3D; 1（扩展欧几里得），则：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">m = c1^s1 · c2^s2 mod n</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">common_modulus_attack</span>(<span class="params">c1, c2, e1, e2, n</span>):</span><br><span class="line">    g, s1, s2 = ext_gcd(e1, e2)</span><br><span class="line">    <span class="comment"># g 应该 = 1</span></span><br><span class="line">    m = (<span class="built_in">pow</span>(c1, s1, n) * <span class="built_in">pow</span>(c2, s2, n)) % n</span><br><span class="line">    <span class="keyword">if</span> s1 &lt; <span class="number">0</span>:</span><br><span class="line">        m = <span class="built_in">pow</span>(m, -<span class="number">1</span>, n)  <span class="comment"># 负指数要取逆</span></span><br><span class="line">    <span class="keyword">return</span> m</span><br></pre></td></tr></table></figure><h2 id="6-CTF-实战：RSA-广播攻击"><a href="#6-CTF-实战：RSA-广播攻击" class="headerlink" title="6. CTF 实战：RSA 广播攻击"></a>6. CTF 实战：RSA 广播攻击</h2><p>如果 e&#x3D;3 且同一条明文 m 用三个不同的模数 n1, n2, n3 加密：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">c1 = m³ mod n1</span><br><span class="line">c2 = m³ mod n2</span><br><span class="line">c3 = m³ mod n3</span><br></pre></td></tr></table></figure><p>用 CRT 合并 c1, c2, c3 和 n1, n2, n3，得到 m³ mod (n1·n2·n3)。因为 m &lt; n1, n2, n3，所以 m³ &lt; n1·n2·n3，直接开立方根就得到 m。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">broadcast_attack</span>(<span class="params">c1, c2, c3, n1, n2, n3</span>):</span><br><span class="line">    <span class="comment"># CRT 合并</span></span><br><span class="line">    M = n1 * n2 * n3</span><br><span class="line">    N1, N2, N3 = M // n1, M // n2, M // n3</span><br><span class="line">    m3 = (c1 * N1 * <span class="built_in">pow</span>(N1, -<span class="number">1</span>, n1) + </span><br><span class="line">          c2 * N2 * <span class="built_in">pow</span>(N2, -<span class="number">1</span>, n2) + </span><br><span class="line">          c3 * N3 * <span class="built_in">pow</span>(N3, -<span class="number">1</span>, n3)) % M</span><br><span class="line">    <span class="comment"># 开立方根</span></span><br><span class="line">    <span class="keyword">return</span> integer_cbrt(m3)</span><br></pre></td></tr></table></figure><h2 id="7-CTF-实战：CRT-拆分-ECC-点"><a href="#7-CTF-实战：CRT-拆分-ECC-点" class="headerlink" title="7. CTF 实战：CRT 拆分 ECC 点"></a>7. CTF 实战：CRT 拆分 ECC 点</h2><p>椭圆曲线中，如果知道点 P 在多个子群上的投影，可以用 CRT 合并回来。这在 Smart 攻击和 Pohlig-Hellman 中常用。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>CRT 是数论密码学的核心工具。记住：</p><ol><li><strong>标准 CRT</strong>：模数两两互质，直接套公式</li><li><strong>扩展 CRT</strong>：模数不互质，两两合并</li><li><strong>RSA 中</strong>：共模攻击、广播攻击、CRT 加速解密</li><li><strong>ECC 中</strong>：Pohlig-Hellman 的 CRT 合并</li></ol><p>实际做题时，sympy 有现成的 <code>crp_crt</code> 函数，但理解原理比调库重要。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/16/chinese-remainder-theorem/</id>
    <link href="https://tsvmv.github.io/2026/09/16/chinese-remainder-theorem/"/>
    <published>2026-09-16T14:30:00.000Z</published>
    <summary>
      <![CDATA[<p>“有物不知其数，三三数之剩二，五五数之剩三，七七数之剩二，问物几何？”——《孙子算经》</p>
<p>这就是中国剩余定理（CRT）的起源。在 CTF 密码学中，CRT 无处不在：RSA 加速解密、共模攻击、广播攻击、Elliptic Curve 点合并……不会 CRT，密码学题基本做不动。</p>]]>
    </summary>
    <title>中国剩余定理(CRT)详解—从孙子定理到 RSA 应用</title>
    <updated>2026-11-04T11:56:13.945Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="CTF" scheme="https://tsvmv.github.io/categories/CTF/"/>
    <category term="CTF" scheme="https://tsvmv.github.io/tags/CTF/"/>
    <category term="算法" scheme="https://tsvmv.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <category term="数据结构" scheme="https://tsvmv.github.io/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    <content>
      <![CDATA[<p>线段树是竞赛算法中最常用的数据结构之一。它把数组按区间分治组织，让单点修改、区间查询、区间修改都能在 O(log n) 时间完成。CTF 中的 Misc、逆向、甚至部分 Pwn 题都可能用到。</p><p>这篇文章从基础线段树讲起，到懒标记（Lazy Propagation）的区间修改，最后讲几个经典建模。</p><span id="more"></span><h2 id="1-为什么需要线段树"><a href="#1-为什么需要线段树" class="headerlink" title="1. 为什么需要线段树"></a>1. 为什么需要线段树</h2><p>先想一个问题：给一个数组 a[1..n]，需要支持两种操作：</p><ol><li>修改某个元素 a[i] &#x3D; v</li><li>查询区间 [l, r] 的和</li></ol><p>朴素做法：修改 O(1)，查询 O(n)。n&#x3D;10^6 时查询太慢。</p><p>前缀和：修改 O(n)，查询 O(1)。修改太多又太慢。</p><p>线段树：修改 O(log n)，查询 O(log n)。两种操作都快。</p><h2 id="2-基础结构"><a href="#2-基础结构" class="headerlink" title="2. 基础结构"></a>2. 基础结构</h2><p>线段树把数组组织成一棵完全二叉树。每个节点代表一个区间：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">         [1, 8]</span><br><span class="line">        /       \</span><br><span class="line">    [1,4]       [5,8]</span><br><span class="line">    /    \      /    \</span><br><span class="line"> [1,2] [3,4] [5,6] [7,8]</span><br><span class="line"> / \   / \   / \   / \</span><br><span class="line">1  2  3  4  5  6  7  8</span><br></pre></td></tr></table></figure><p>叶子节点是单个元素，内部节点是子区间的聚合值（和、最大值、最小值等）。</p><p>用数组存储：节点 i 的左儿子是 2i，右儿子是 2i+1。</p><h2 id="3-构建与查询"><a href="#3-构建与查询" class="headerlink" title="3. 构建与查询"></a>3. 构建与查询</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SegmentTree</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, data</span>):</span><br><span class="line">        <span class="variable language_">self</span>.n = <span class="built_in">len</span>(data)</span><br><span class="line">        <span class="variable language_">self</span>.size = <span class="number">1</span></span><br><span class="line">        <span class="keyword">while</span> <span class="variable language_">self</span>.size &lt; <span class="variable language_">self</span>.n:</span><br><span class="line">            <span class="variable language_">self</span>.size *= <span class="number">2</span></span><br><span class="line">        <span class="variable language_">self</span>.tree = [<span class="number">0</span>] * (<span class="number">2</span> * <span class="variable language_">self</span>.size)</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 填充叶子</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="variable language_">self</span>.n):</span><br><span class="line">            <span class="variable language_">self</span>.tree[<span class="variable language_">self</span>.size + i] = data[i]</span><br><span class="line">        <span class="comment"># 构建内部节点</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="variable language_">self</span>.size - <span class="number">1</span>, <span class="number">0</span>, -<span class="number">1</span>):</span><br><span class="line">            <span class="variable language_">self</span>.tree[i] = <span class="variable language_">self</span>.tree[<span class="number">2</span>*i] + <span class="variable language_">self</span>.tree[<span class="number">2</span>*i+<span class="number">1</span>]</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">query</span>(<span class="params">self, l, r</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;查询 [l, r) 的和&quot;&quot;&quot;</span></span><br><span class="line">        l += <span class="variable language_">self</span>.size</span><br><span class="line">        r += <span class="variable language_">self</span>.size</span><br><span class="line">        result = <span class="number">0</span></span><br><span class="line">        <span class="keyword">while</span> l &lt; r:</span><br><span class="line">            <span class="keyword">if</span> l % <span class="number">2</span> == <span class="number">1</span>:</span><br><span class="line">                result += <span class="variable language_">self</span>.tree[l]</span><br><span class="line">                l += <span class="number">1</span></span><br><span class="line">            <span class="keyword">if</span> r % <span class="number">2</span> == <span class="number">1</span>:</span><br><span class="line">                r -= <span class="number">1</span></span><br><span class="line">                result += <span class="variable language_">self</span>.tree[r]</span><br><span class="line">            l //= <span class="number">2</span></span><br><span class="line">            r //= <span class="number">2</span></span><br><span class="line">        <span class="keyword">return</span> result</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">update</span>(<span class="params">self, pos, val</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;单点修改 a[pos] = val&quot;&quot;&quot;</span></span><br><span class="line">        pos += <span class="variable language_">self</span>.size</span><br><span class="line">        <span class="variable language_">self</span>.tree[pos] = val</span><br><span class="line">        pos //= <span class="number">2</span></span><br><span class="line">        <span class="keyword">while</span> pos &gt;= <span class="number">1</span>:</span><br><span class="line">            <span class="variable language_">self</span>.tree[pos] = <span class="variable language_">self</span>.tree[<span class="number">2</span>*pos] + <span class="variable language_">self</span>.tree[<span class="number">2</span>*pos+<span class="number">1</span>]</span><br><span class="line">            pos //= <span class="number">2</span></span><br></pre></td></tr></table></figure><h2 id="4-懒标记（Lazy-Propagation）"><a href="#4-懒标记（Lazy-Propagation）" class="headerlink" title="4. 懒标记（Lazy Propagation）"></a>4. 懒标记（Lazy Propagation）</h2><p>上面的基础线段树只支持单点修改。如果要做区间修改（如把 [l, r] 所有元素加 v），朴素做法是逐点修改，O(n)。</p><p>懒标记的思路：<strong>先不更新子节点，在当前节点上记一个”待传递”的标记。</strong> 等到必须访问子节点时，再把标记往下传。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LazySegmentTree</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, data</span>):</span><br><span class="line">        <span class="variable language_">self</span>.n = <span class="built_in">len</span>(data)</span><br><span class="line">        <span class="variable language_">self</span>.size = <span class="number">1</span></span><br><span class="line">        <span class="keyword">while</span> <span class="variable language_">self</span>.size &lt; <span class="variable language_">self</span>.n:</span><br><span class="line">            <span class="variable language_">self</span>.size *= <span class="number">2</span></span><br><span class="line">        <span class="variable language_">self</span>.tree = [<span class="number">0</span>] * (<span class="number">2</span> * <span class="variable language_">self</span>.size)</span><br><span class="line">        <span class="variable language_">self</span>.<span class="keyword">lazy</span> = [<span class="number">0</span>] * (<span class="number">2</span> * <span class="variable language_">self</span>.size)</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="variable language_">self</span>.n):</span><br><span class="line">            <span class="variable language_">self</span>.tree[<span class="variable language_">self</span>.size + i] = data[i]</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="variable language_">self</span>.size - <span class="number">1</span>, <span class="number">0</span>, -<span class="number">1</span>):</span><br><span class="line">            <span class="variable language_">self</span>.tree[i] = <span class="variable language_">self</span>.tree[<span class="number">2</span>*i] + <span class="variable language_">self</span>.tree[<span class="number">2</span>*i+<span class="number">1</span>]</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">push_down</span>(<span class="params">self, node, l, r</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;把懒标记从 node 传到子节点&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.<span class="keyword">lazy</span>[node] != <span class="number">0</span>:</span><br><span class="line">            mid = (l + r) // <span class="number">2</span></span><br><span class="line">            left_len = mid - l + <span class="number">1</span></span><br><span class="line">            right_len = r - mid</span><br><span class="line">            </span><br><span class="line">            <span class="variable language_">self</span>.tree[<span class="number">2</span>*node] += <span class="variable language_">self</span>.<span class="keyword">lazy</span>[node] * left_len</span><br><span class="line">            <span class="variable language_">self</span>.<span class="keyword">lazy</span>[<span class="number">2</span>*node] += <span class="variable language_">self</span>.<span class="keyword">lazy</span>[node]</span><br><span class="line">            </span><br><span class="line">            <span class="variable language_">self</span>.tree[<span class="number">2</span>*node+<span class="number">1</span>] += <span class="variable language_">self</span>.<span class="keyword">lazy</span>[node] * right_len</span><br><span class="line">            <span class="variable language_">self</span>.<span class="keyword">lazy</span>[<span class="number">2</span>*node+<span class="number">1</span>] += <span class="variable language_">self</span>.<span class="keyword">lazy</span>[node]</span><br><span class="line">            </span><br><span class="line">            <span class="variable language_">self</span>.<span class="keyword">lazy</span>[node] = <span class="number">0</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">range_add</span>(<span class="params">self, ql, qr, val, node=<span class="number">1</span>, l=<span class="number">0</span>, r=<span class="literal">None</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;区间 [ql, qr) 每个元素加 val&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">if</span> r <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            r = <span class="variable language_">self</span>.size</span><br><span class="line">        <span class="keyword">if</span> ql &lt;= l <span class="keyword">and</span> r &lt;= qr:</span><br><span class="line">            <span class="variable language_">self</span>.tree[node] += val * (r - l)</span><br><span class="line">            <span class="variable language_">self</span>.<span class="keyword">lazy</span>[node] += val</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        <span class="variable language_">self</span>.push_down(node, l, r)</span><br><span class="line">        mid = (l + r) // <span class="number">2</span></span><br><span class="line">        <span class="keyword">if</span> ql &lt; mid:</span><br><span class="line">            <span class="variable language_">self</span>.range_add(ql, qr, val, <span class="number">2</span>*node, l, mid)</span><br><span class="line">        <span class="keyword">if</span> qr &gt; mid:</span><br><span class="line">            <span class="variable language_">self</span>.range_add(ql, qr, val, <span class="number">2</span>*node+<span class="number">1</span>, mid, r)</span><br><span class="line">        <span class="variable language_">self</span>.tree[node] = <span class="variable language_">self</span>.tree[<span class="number">2</span>*node] + <span class="variable language_">self</span>.tree[<span class="number">2</span>*node+<span class="number">1</span>]</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">range_query</span>(<span class="params">self, ql, qr, node=<span class="number">1</span>, l=<span class="number">0</span>, r=<span class="literal">None</span></span>):</span><br><span class="line">        <span class="keyword">if</span> r <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            r = <span class="variable language_">self</span>.size</span><br><span class="line">        <span class="keyword">if</span> ql &lt;= l <span class="keyword">and</span> r &lt;= qr:</span><br><span class="line">            <span class="keyword">return</span> <span class="variable language_">self</span>.tree[node]</span><br><span class="line">        <span class="variable language_">self</span>.push_down(node, l, r)</span><br><span class="line">        mid = (l + r) // <span class="number">2</span></span><br><span class="line">        result = <span class="number">0</span></span><br><span class="line">        <span class="keyword">if</span> ql &lt; mid:</span><br><span class="line">            result += <span class="variable language_">self</span>.range_query(ql, qr, <span class="number">2</span>*node, l, mid)</span><br><span class="line">        <span class="keyword">if</span> qr &gt; mid:</span><br><span class="line">            result += <span class="variable language_">self</span>.range_query(ql, qr, <span class="number">2</span>*node+<span class="number">1</span>, mid, r)</span><br><span class="line">        <span class="keyword">return</span> result</span><br></pre></td></tr></table></figure><h2 id="5-经典应用"><a href="#5-经典应用" class="headerlink" title="5. 经典应用"></a>5. 经典应用</h2><h3 id="5-1-区间最大值"><a href="#5-1-区间最大值" class="headerlink" title="5.1 区间最大值"></a>5.1 区间最大值</h3><p>把节点的聚合函数从”求和”改成”取最大值”：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 构建</span></span><br><span class="line"><span class="variable language_">self</span>.tree[i] = <span class="built_in">max</span>(<span class="variable language_">self</span>.tree[<span class="number">2</span>*i], <span class="variable language_">self</span>.tree[<span class="number">2</span>*i+<span class="number">1</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查询：返回 max 而不是 sum</span></span><br></pre></td></tr></table></figure><h3 id="5-2-区间第-K-小（主席树）"><a href="#5-2-区间第-K-小（主席树）" class="headerlink" title="5.2 区间第 K 小（主席树）"></a>5.2 区间第 K 小（主席树）</h3><p>主席树是可持久化线段树，支持查询任意区间第 K 小。CTF 中偶尔用到。</p><h3 id="5-3-逆序对计数"><a href="#5-3-逆序对计数" class="headerlink" title="5.3 逆序对计数"></a>5.3 逆序对计数</h3><p>用线段树统计逆序对：从左到右扫描数组，对每个元素 a[i]，查询已经出现过的比 a[i] 大的元素个数。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">count = <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(n):</span><br><span class="line">    count += seg_tree.query(a[i] + <span class="number">1</span>, MAX_VALUE)</span><br><span class="line">    seg_tree.update(a[i], <span class="number">1</span>)</span><br></pre></td></tr></table></figure><h2 id="6-常见坑"><a href="#6-常见坑" class="headerlink" title="6. 常见坑"></a>6. 常见坑</h2><ol><li><strong>数组大小</strong>：线段树数组要开 4n，不是 2n。递归实现时尤其要注意。</li><li><strong>懒标记传递时机</strong>：只有在需要访问子节点时才 push_down，不要每次都传。</li><li><strong>区间开闭</strong>：注意 [l, r] 是闭区间还是半开区间，不同人写法不一样，别搞混了。</li><li><strong>离散化</strong>：如果值域很大（如 10^9），先把所有要用到的值离散化到 1..m。</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>线段树是竞赛算法的基本功。掌握了它，你就有了处理”区间查询 + 单点&#x2F;区间修改”类问题的标准武器。关键理解：</p><ol><li><strong>分治结构</strong>：每个节点管一个区间</li><li><strong>懒标记</strong>：延迟更新，摊还复杂度</li><li><strong>聚合函数</strong>：和、最大值、最小值、GCD 都能套</li></ol><p>写线段树的模板要烂熟于心，考场上 5 分钟就能敲出来。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/16/segment-tree-guide/</id>
    <link href="https://tsvmv.github.io/2026/09/16/segment-tree-guide/"/>
    <published>2026-09-16T14:00:00.000Z</published>
    <summary>
      <![CDATA[<p>线段树是竞赛算法中最常用的数据结构之一。它把数组按区间分治组织，让单点修改、区间查询、区间修改都能在 O(log n) 时间完成。CTF 中的 Misc、逆向、甚至部分 Pwn 题都可能用到。</p>
<p>这篇文章从基础线段树讲起，到懒标记（Lazy Propagation）的区间修改，最后讲几个经典建模。</p>]]>
    </summary>
    <title>线段树详解—从单点修改到区间操作</title>
    <updated>2026-11-04T11:56:13.948Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="CTF" scheme="https://tsvmv.github.io/categories/CTF/"/>
    <category term="CTF" scheme="https://tsvmv.github.io/tags/CTF/"/>
    <category term="密码学" scheme="https://tsvmv.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="数论" scheme="https://tsvmv.github.io/tags/%E6%95%B0%E8%AE%BA/"/>
    <content>
      <![CDATA[<p>大数分解是 RSA 密码学的命门。CTF 密码学题里，给你一个几百位的 n 让你分解，你总不能从 2 试到 √n 吧？这时候就需要 Miller-Rabin 判断是不是质数，Pollard’s Rho 做概率分解。</p><p>这篇文章把这两个算法讲透，附上可直接用的 Python 实现。</p><span id="more"></span><h2 id="1-Miller-Rabin-素性测试"><a href="#1-Miller-Rabin-素性测试" class="headerlink" title="1. Miller-Rabin 素性测试"></a>1. Miller-Rabin 素性测试</h2><h3 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h3><p>费马小定理：如果 p 是质数，那么对任意 a，a^(p-1) ≡ 1 (mod p)。</p><p>反过来，如果 a^(n-1) ≢ 1 (mod n)，那 n 一定不是质数。这就是 Miller-Rabin 的基础。</p><p>但费马小定理的逆命题不成立——存在 Carmichael 数（如 561），它们是合数但满足所有 a 的费马条件。Miller-Rabin 加了一层二次检测来排除这些伪证。</p><h3 id="算法步骤"><a href="#算法步骤" class="headerlink" title="算法步骤"></a>算法步骤</h3><p>给定 n，把 n-1 写成 d * 2^s：</p><ol><li>随机选一个 a ∈ [2, n-2]</li><li>计算 x &#x3D; a^d mod n</li><li>如果 x&#x3D;1 或 x&#x3D;n-1，通过本轮测试</li><li>否则重复 s-1 次：<ul><li>x &#x3D; x² mod n</li><li>如果 x &#x3D; n-1，通过本轮测试，跳出</li></ul></li><li>如果循环结束都没通过，n 一定是合数</li><li>通过多轮测试，n 是合数的概率是 (1&#x2F;4)^k</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">from</span> math <span class="keyword">import</span> gcd</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">miller_rabin</span>(<span class="params">n, k=<span class="number">40</span></span>):</span><br><span class="line">    <span class="keyword">if</span> n &lt; <span class="number">2</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">    <span class="keyword">if</span> n == <span class="number">2</span> <span class="keyword">or</span> n == <span class="number">3</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">if</span> n % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 写成 d * 2^s = n - 1</span></span><br><span class="line">    d = n - <span class="number">1</span></span><br><span class="line">    s = <span class="number">0</span></span><br><span class="line">    <span class="keyword">while</span> d % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">        d //= <span class="number">2</span></span><br><span class="line">        s += <span class="number">1</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 对几个固定的小基数，对 2^64 以内的数是确定性的</span></span><br><span class="line">    bases = [<span class="number">2</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">7</span>, <span class="number">11</span>, <span class="number">13</span>, <span class="number">17</span>, <span class="number">19</span>, <span class="number">23</span>, <span class="number">29</span>, <span class="number">31</span>, <span class="number">37</span>]</span><br><span class="line">    <span class="comment"># 大数用随机基数</span></span><br><span class="line">    <span class="keyword">if</span> n &gt; <span class="number">331</span>:</span><br><span class="line">        bases = [random.randint(<span class="number">2</span>, n-<span class="number">2</span>) <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(k)]</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> a <span class="keyword">in</span> bases:</span><br><span class="line">        <span class="keyword">if</span> a &gt;= n:</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        x = <span class="built_in">pow</span>(a, d, n)</span><br><span class="line">        <span class="keyword">if</span> x == <span class="number">1</span> <span class="keyword">or</span> x == n - <span class="number">1</span>:</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(s - <span class="number">1</span>):</span><br><span class="line">            x = <span class="built_in">pow</span>(x, <span class="number">2</span>, n)</span><br><span class="line">            <span class="keyword">if</span> x == n - <span class="number">1</span>:</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span></span><br></pre></td></tr></table></figure><h3 id="确定性测试范围"><a href="#确定性测试范围" class="headerlink" title="确定性测试范围"></a>确定性测试范围</h3><p>对不同范围的 n，只需要固定几个基数就能 100% 确定：</p><table><thead><tr><th>n 的范围</th><th>必测基数</th></tr></thead><tbody><tr><td>&lt; 2,047</td><td>{2}</td></tr><tr><td>&lt; 1,373,653</td><td>{2, 3}</td></tr><tr><td>&lt; 9,080,191</td><td>{31, 73}</td></tr><tr><td>&lt; 4,759,123,141</td><td>{2, 7, 61}</td></tr><tr><td>&lt; 2^64</td><td>{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}</td></tr></tbody></table><h2 id="2-Pollard’s-Rho-分解"><a href="#2-Pollard’s-Rho-分解" class="headerlink" title="2. Pollard’s Rho 分解"></a>2. Pollard’s Rho 分解</h2><h3 id="原理-1"><a href="#原理-1" class="headerlink" title="原理"></a>原理</h3><p>Pollard’s Rho 是一个概率算法，比试除法快得多。核心思路：</p><ol><li>用伪随机函数 f(x) &#x3D; (x² + c) mod n 生成序列</li><li>序列中有两个数 x, y，如果 gcd(|x-y|, n) 是 n 的非平凡因子 d</li><li>用 Floyd 环检测找重复值，避免存储整个序列</li></ol><h3 id="算法步骤-1"><a href="#算法步骤-1" class="headerlink" title="算法步骤"></a>算法步骤</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">pollards_rho</span>(<span class="params">n</span>):</span><br><span class="line">    <span class="keyword">if</span> n % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="number">2</span></span><br><span class="line">    <span class="keyword">if</span> n % <span class="number">3</span> == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="number">3</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        c = random.randint(<span class="number">1</span>, n - <span class="number">1</span>)</span><br><span class="line">        f = <span class="keyword">lambda</span> x: (<span class="built_in">pow</span>(x, <span class="number">2</span>, n) + c) % n</span><br><span class="line">        </span><br><span class="line">        x, y, d = <span class="number">2</span>, <span class="number">2</span>, <span class="number">1</span></span><br><span class="line">        <span class="keyword">while</span> d == <span class="number">1</span>:</span><br><span class="line">            x = f(x)</span><br><span class="line">            y = f(f(y))</span><br><span class="line">            d = gcd(<span class="built_in">abs</span>(x - y), n)</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> d != n:</span><br><span class="line">            <span class="keyword">return</span> d</span><br></pre></td></tr></table></figure><h3 id="递归分解"><a href="#递归分解" class="headerlink" title="递归分解"></a>递归分解</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">factorize</span>(<span class="params">n</span>):</span><br><span class="line">    factors = []</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">_factor</span>(<span class="params">n</span>):</span><br><span class="line">        <span class="keyword">if</span> n == <span class="number">1</span>:</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        <span class="keyword">if</span> miller_rabin(n):</span><br><span class="line">            factors.append(n)</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        d = pollards_rho(n)</span><br><span class="line">        _factor(d)</span><br><span class="line">        _factor(n // d)</span><br><span class="line">    </span><br><span class="line">    _factor(n)</span><br><span class="line">    factors.sort()</span><br><span class="line">    <span class="keyword">return</span> factors</span><br></pre></td></tr></table></figure><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 分解一个 RSA 模数（假设 n = p * q）</span></span><br><span class="line">n = <span class="number">12345678901234567890123456789</span></span><br><span class="line">factors = factorize(n)</span><br><span class="line"><span class="built_in">print</span>(factors)</span><br><span class="line"><span class="comment"># [123457, 999999937] 之类的</span></span><br></pre></td></tr></table></figure><h2 id="3-CTF-实战：RSA-分解"><a href="#3-CTF-实战：RSA-分解" class="headerlink" title="3. CTF 实战：RSA 分解"></a>3. CTF 实战：RSA 分解</h2><p>CTF RSA 题中，如果 n 不算特别大（2048 位以下），Pollard’s Rho 几秒就能分解：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 典型 RSA 题目</span></span><br><span class="line">n = 0x... <span class="comment"># 题目给的大整数</span></span><br><span class="line">e = <span class="number">65537</span></span><br><span class="line">c = 0x...</span><br><span class="line"></span><br><span class="line"><span class="comment"># 分解 n = p * q</span></span><br><span class="line">factors = factorize(n)</span><br><span class="line">p, q = factors[<span class="number">0</span>], factors[<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 计算私钥</span></span><br><span class="line">phi = (p - <span class="number">1</span>) * (q - <span class="number">1</span>)</span><br><span class="line">d = <span class="built_in">pow</span>(e, -<span class="number">1</span>, phi)</span><br><span class="line">m = <span class="built_in">pow</span>(c, d, n)</span><br><span class="line"><span class="built_in">print</span>(m.to_bytes((m.bit_length() + <span class="number">7</span>) // <span class="number">8</span>, <span class="string">&#x27;big&#x27;</span>))</span><br></pre></td></tr></table></figure><h2 id="4-CTF-实战：Fermat-分解"><a href="#4-CTF-实战：Fermat-分解" class="headerlink" title="4. CTF 实战：Fermat 分解"></a>4. CTF 实战：Fermat 分解</h2><p>如果 n &#x3D; p * q 且 p 和 q 很接近，可以用 Fermat 分解：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">fermat_factor</span>(<span class="params">n</span>):</span><br><span class="line">    a = <span class="built_in">int</span>(n ** <span class="number">0.5</span>)</span><br><span class="line">    b2 = a * a - n</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        b = <span class="built_in">int</span>(b2 ** <span class="number">0.5</span>)</span><br><span class="line">        <span class="keyword">if</span> b * b == b2:</span><br><span class="line">            <span class="keyword">return</span> a - b, a + b</span><br><span class="line">        a += <span class="number">1</span></span><br><span class="line">        b2 = a * a - n</span><br></pre></td></tr></table></figure><p>如果 p 和 q 只差几百，这个方法比 Pollard’s Rho 还快。</p><h2 id="5-复杂度"><a href="#5-复杂度" class="headerlink" title="5. 复杂度"></a>5. 复杂度</h2><table><thead><tr><th>算法</th><th>复杂度</th><th>适用场景</th></tr></thead><tbody><tr><td>试除法</td><td>O(√n)</td><td>n &lt; 10^12</td></tr><tr><td>Fermat</td><td>O(</td><td>p-q</td></tr><tr><td>Pollard’s Rho</td><td>O(n^(1&#x2F;4))</td><td>RSA 2048 位以内</td></tr><tr><td>ECM</td><td>亚指数</td><td>中等大小因子</td></tr><tr><td>GNFS</td><td>亚指数</td><td>RSA 1024+ 位</td></tr></tbody></table><p>Python 的 Pollard’s Rho 对 1024 位 RSA 模数大约需要几秒到几分钟。2048 位就别想了，那是超算干的事。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Miller-Rabin + Pollard’s Rho 是 CTF 密码学的标配工具。记住：</p><ol><li><strong>先 Miller-Rabin 判断</strong>是不是质数</li><li><strong>Pollard’s Rho 找非平凡因子</strong></li><li><strong>递归分解</strong>到所有质因子</li><li><strong>CTF 中</strong>，n 通常不会太大，Python 版本足够用</li></ol><p>实际做题时，建议直接用 sympy 的 <code>factorint</code>，它内部已经优化了 Pollard’s Rho 和其他算法。但理解原理很重要——题目经常会在分解方法上设坑。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/16/miller-rabin-pollard-rho/</id>
    <link href="https://tsvmv.github.io/2026/09/16/miller-rabin-pollard-rho/"/>
    <published>2026-09-16T13:30:00.000Z</published>
    <summary>
      <![CDATA[<p>大数分解是 RSA 密码学的命门。CTF 密码学题里，给你一个几百位的 n 让你分解，你总不能从 2 试到 √n 吧？这时候就需要 Miller-Rabin 判断是不是质数，Pollard’s Rho 做概率分解。</p>
<p>这篇文章把这两个算法讲透，附上可直接用的 Python 实现。</p>]]>
    </summary>
    <title>Miller-Rabin 素性测试与 Pollard's Rho 分解详解</title>
    <updated>2026-11-04T11:56:13.946Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="系统&amp;内核" scheme="https://tsvmv.github.io/categories/%E7%B3%BB%E7%BB%9F-%E5%86%85%E6%A0%B8/"/>
    <category term="逆向" scheme="https://tsvmv.github.io/tags/%E9%80%86%E5%90%91/"/>
    <category term="安全" scheme="https://tsvmv.github.io/tags/%E5%AE%89%E5%85%A8/"/>
    <category term="Windows" scheme="https://tsvmv.github.io/tags/Windows/"/>
    <content>
      <![CDATA[<p>做逆向分析的人都绕不开反调试。CTF Pwn 题里经常埋各种反调试，商业软件更是把反调试做成了一套体系。你不会反反调试，连主函数都看不到。</p><p>这篇文章按”从用户态到内核态、从简单到复杂”的顺序，梳理 Windows 下常见的反调试手段和对应的绕过方法。不是教程式的罗列，而是讲清楚每个手段的原理——原理懂了，变种一眼就能看出来。</p><span id="more"></span><h2 id="1-最简单的：检查-BeingDebugged-标志位"><a href="#1-最简单的：检查-BeingDebugged-标志位" class="headerlink" title="1. 最简单的：检查 BeingDebugged 标志位"></a>1. 最简单的：检查 BeingDebugged 标志位</h2><h3 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h3><p>PEB（Process Environment Block）的偏移 0x02 处有一个 <code>BeingDebugged</code> 字节。如果进程被调试器附加，这个字节为 1。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">; 经典反调试代码</span><br><span class="line">mov eax, fs:[30h]        ; 读 PEB 基址（TEB-&gt;ProcessEnvironmentBlock）</span><br><span class="line">movzx eax, byte ptr [eax+2]  ; BeingDebugged</span><br><span class="line">test eax, eax</span><br><span class="line">jnz being_debugged</span><br></pre></td></tr></table></figure><h3 id="绕过"><a href="#绕过" class="headerlink" title="绕过"></a>绕过</h3><p>方法一：运行时改 PEB。在调试器中把 BeingDebugged 字节改为 0。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># x64dbg 中直接在命令行执行</span></span><br><span class="line"><span class="comment"># 把 PEB.BeingDebugged 设为 0</span></span><br></pre></td></tr></table></figure><p>方法二：Hook NtQueryInformationProcess。很多反调试代码不直接读 PEB，而是通过这个 API 查 ProcessDebugPort。Hook 它返回正常值。</p><p>方法三：用 ScyllaHide 这类插件，一键 patch 所有常见的反调试检测点。</p><h2 id="2-NtGlobalFlag"><a href="#2-NtGlobalFlag" class="headerlink" title="2. NtGlobalFlag"></a>2. NtGlobalFlag</h2><h3 id="原理-1"><a href="#原理-1" class="headerlink" title="原理"></a>原理</h3><p>PEB 偏移 0x68（64位）处的 <code>NtGlobalFlag</code>。被调试时通常是 <code>0x70</code>（FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS）。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">mov eax, fs:[30h]</span><br><span class="line">mov eax, [eax+68h]   ; NtGlobalFlag</span><br><span class="line">and eax, 0x70</span><br><span class="line">jnz being_debugged</span><br></pre></td></tr></table></figure><h3 id="绕过-1"><a href="#绕过-1" class="headerlink" title="绕过"></a>绕过</h3><p>直接把这个字节 patch 成 0。或者在程序运行前用注册表设置 <code>NtGlobalFlag</code> 为非调试值。</p><h2 id="3-CheckRemoteDebuggerPresent"><a href="#3-CheckRemoteDebuggerPresent" class="headerlink" title="3. CheckRemoteDebuggerPresent"></a>3. CheckRemoteDebuggerPresent</h2><h3 id="原理-2"><a href="#原理-2" class="headerlink" title="原理"></a>原理</h3><p>调用 <code>CheckRemoteDebuggerPresent(GetCurrentProcess(), &amp;bDebugged)</code>，系统会通过内部逻辑判断有没有调试器。</p><h3 id="绕过-2"><a href="#绕过-2" class="headerlink" title="绕过"></a>绕过</h3><p>这个 API 内部调用 <code>NtQueryInformationProcess</code>，所以 Hook <code>NtQueryInformationProcess</code> 就能同时绕过。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Frida hook 示例</span></span><br><span class="line">Interceptor.attach(Module.findExportByName(<span class="string">&#x27;kernel32.dll&#x27;</span>, <span class="string">&#x27;CheckRemoteDebuggerPresent&#x27;</span>), &#123;</span><br><span class="line">    onLeave: function(retval) &#123;</span><br><span class="line">        // 把 pbDebuggerPresent 写为 FALSE</span><br><span class="line">        var args = this.context;</span><br><span class="line">        // ... 修改输出参数</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="4-时间检测"><a href="#4-时间检测" class="headerlink" title="4. 时间检测"></a>4. 时间检测</h2><h3 id="原理-3"><a href="#原理-3" class="headerlink" title="原理"></a>原理</h3><p>调试器会打断程序执行，导致两段代码之间的时间间隔异常长。反调试代码测量两段代码之间的时间差，如果超过阈值就判定被调试。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">DWORD t1 = GetTickCount();</span><br><span class="line"><span class="comment">// ... 被调试时这里会停下来</span></span><br><span class="line">DWORD t2 = GetTickCount();</span><br><span class="line"><span class="keyword">if</span> (t2 - t1 &gt; <span class="number">1000</span>) &#123;</span><br><span class="line">    ExitProcess(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>更精确的用 <code>QueryPerformanceCounter</code> 或 <code>rdtsc</code> 指令。</p><h3 id="绕过-3"><a href="#绕过-3" class="headerlink" title="绕过"></a>绕过</h3><ul><li><strong>硬件断点</strong>：用条件断点而不是 INT3，单步执行时时间差不会太大</li><li><strong>Hook 时间 API</strong>：Hook <code>GetTickCount</code>、<code>QueryPerformanceCounter</code>，让它返回伪造的时间</li><li><strong>patch 比较</strong>：直接把 <code>jg</code> 改成 <code>jmp</code> 或 nop 掉</li></ul><h2 id="5-INT3-检测"><a href="#5-INT3-检测" class="headerlink" title="5. INT3 检测"></a>5. INT3 检测</h2><h3 id="原理-4"><a href="#原理-4" class="headerlink" title="原理"></a>原理</h3><p>调试器常用 <code>int 3</code>（0xCC）设断点。反调试代码扫描自己的代码段，看有没有 0xCC 字节。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">bool</span> has_int3 = <span class="literal">false</span>;</span><br><span class="line">DWORD old;</span><br><span class="line">VirtualProtect(code_start, code_size, PAGE_EXECUTE_READWRITE, &amp;old);</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; code_size; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (code_start[i] == <span class="number">0xCC</span>) &#123;</span><br><span class="line">        has_int3 = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">VirtualProtect(code_start, code_size, old, &amp;old);</span><br></pre></td></tr></table></figure><h3 id="绕过-4"><a href="#绕过-4" class="headerlink" title="绕过"></a>绕过</h3><ul><li>用硬件断点（DR0-DR7），不修改代码</li><li>扫描到 0xCC 后 patch 成原来的字节（调试时注意备份）</li><li>用条件断点代替 INT3</li></ul><h2 id="6-硬件断点检测"><a href="#6-硬件断点检测" class="headerlink" title="6. 硬件断点检测"></a>6. 硬件断点检测</h2><h3 id="原理-5"><a href="#原理-5" class="headerlink" title="原理"></a>原理</h3><p>硬件断点设置在 DR0-DR7 寄存器中。反调试代码读取线程上下文，看 DR0-DR3 是否非零。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">CONTEXT ctx;</span><br><span class="line">ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;</span><br><span class="line">GetThreadContext(GetCurrentThread(), &amp;ctx);</span><br><span class="line"><span class="keyword">if</span> (ctx.Dr0 != <span class="number">0</span> || ctx.Dr1 != <span class="number">0</span> || ctx.Dr2 != <span class="number">0</span> || ctx.Dr3 != <span class="number">0</span>) &#123;</span><br><span class="line">    ExitProcess(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="绕过-5"><a href="#绕过-5" class="headerlink" title="绕过"></a>绕过</h3><ul><li>Hook <code>GetThreadContext</code>，把 DR 寄存器清零后返回</li><li>不使用硬件断点，用软件断点（但要处理 INT3 检测）</li><li>用 ScyllaHide 自动处理</li></ul><h2 id="7-父进程检测"><a href="#7-父进程检测" class="headerlink" title="7. 父进程检测"></a>7. 父进程检测</h2><h3 id="原理-6"><a href="#原理-6" class="headerlink" title="原理"></a>原理</h3><p>正常情况下，程序的父进程是 explorer.exe（双击运行）或 cmd.exe（命令行启动）。如果父进程是 x64dbg、ollydbg 等调试器，就说明被调试了。</p><p>通过 NtQueryInformationProcess 的 ProcessBasicInformation 可以拿到父进程 PID。</p><h3 id="绕过-6"><a href="#绕过-6" class="headerlink" title="绕过"></a>绕过</h3><ul><li>Hook 父进程 PID 查询</li><li>用调试器启动时伪装父进程（ScyllaHide 支持）</li><li>patch 比较逻辑</li></ul><h2 id="8-异常处理反调试"><a href="#8-异常处理反调试" class="headerlink" title="8. 异常处理反调试"></a>8. 异常处理反调试</h2><h3 id="原理-7"><a href="#原理-7" class="headerlink" title="原理"></a>原理</h3><p>利用 SEH（结构化异常处理）和调试器的交互：</p><ul><li>程序故意触发异常（如 <code>int 3</code>、除零、非法指令）</li><li>如果被调试，异常会先被调试器捕获，程序自己的 SEH 处理不到</li><li>如果没被调试，SEH 正常处理异常</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">__try &#123;</span><br><span class="line">    __asm <span class="type">int</span> <span class="number">3</span></span><br><span class="line">&#125; __except (EXCEPTION_EXECUTE_HANDLER) &#123;</span><br><span class="line">    <span class="comment">// 没被调试</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 如果被调试，int 3 被调试器捕获，这里不会执行</span></span><br></pre></td></tr></table></figure><h3 id="绕过-7"><a href="#绕过-7" class="headerlink" title="绕过"></a>绕过</h3><ul><li>在调试器中设置”忽略异常”，让异常传给程序自己的 handler</li><li>x64dbg：Options → Exceptions → 勾选忽略 INT3</li></ul><h2 id="9-内核态反调试"><a href="#9-内核态反调试" class="headerlink" title="9. 内核态反调试"></a>9. 内核态反调试</h2><p>商业软件常用驱动级反调试：</p><ul><li><strong>注册回调</strong>：驱动注册 <code>PsSetCreateProcessNotifyRoutine</code> 等回调，监控调试器进程</li><li><strong>隐藏调试端口</strong>：修改 EPROCESS 的 DebugPort 为 NULL</li><li><strong>检测调试对象</strong>：检查 EPROCESS 中的 DebugObject 指针</li><li><strong>硬件断点在内核态检测</strong>：读 KTRAP_FRAME 中的 DR 寄存器</li></ul><h3 id="绕过-8"><a href="#绕过-8" class="headerlink" title="绕过"></a>绕过</h3><ul><li>用 PatchGuard 兼容的方式修改内核结构（有风险）</li><li>用虚拟机（VMware + VT-x），驱动在虚拟机里看不到宿主机调试器</li><li>用 Hyper-V 级别的调试（KD），很多驱动级反调试检测不到内核调试</li></ul><h2 id="10-反反调试工具"><a href="#10-反反调试工具" class="headerlink" title="10. 反反调试工具"></a>10. 反反调试工具</h2><h3 id="10-1-ScyllaHide"><a href="#10-1-ScyllaHide" class="headerlink" title="10.1 ScyllaHide"></a>10.1 ScyllaHide</h3><p>x64dbg&#x2F;ollydbg 的插件，自动 patch 上百个常见的反调试检测点。基本开了它，80% 的反调试就废了。</p><h3 id="10-2-TitanEngine"><a href="#10-2-TitanEngine" class="headerlink" title="10.2 TitanEngine"></a>10.2 TitanEngine</h3><p>用 TitanEngine 作为调试引擎，它本身内置了反反调试支持。</p><h3 id="10-3-Frida"><a href="#10-3-Frida" class="headerlink" title="10.3 Frida"></a>10.3 Frida</h3><p>用 Frida 动态 hook 各种 API，适合对付没有固定模式的反调试：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Hook NtQueryInformationProcess</span></span><br><span class="line"><span class="keyword">var</span> <span class="title class_">NtQueryInformationProcess</span> = <span class="title class_">Module</span>.<span class="title function_">findExportByName</span>(<span class="string">&#x27;ntdll.dll&#x27;</span>, <span class="string">&#x27;NtQueryInformationProcess&#x27;</span>);</span><br><span class="line"><span class="title class_">Interceptor</span>.<span class="title function_">attach</span>(<span class="title class_">NtQueryInformationProcess</span>, &#123;</span><br><span class="line">    <span class="attr">onEnter</span>: <span class="keyword">function</span>(<span class="params">args</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">infoClass</span> = args[<span class="number">1</span>].<span class="title function_">toInt32</span>();</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">onLeave</span>: <span class="keyword">function</span>(<span class="params">retval</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">infoClass</span> === <span class="number">7</span>) &#123; <span class="comment">// ProcessDebugPort</span></span><br><span class="line">            <span class="comment">// 把输出写为 0（无调试端口）</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="11-实战流程"><a href="#11-实战流程" class="headerlink" title="11. 实战流程"></a>11. 实战流程</h2><p>拿到一个有反调试的程序，一般按这个顺序：</p><ol><li><strong>先跑一下</strong>：看有没有提示”检测到调试器”之类的弹窗</li><li><strong>开 ScyllaHide</strong>：一键 patch 常见检测点</li><li><strong>找反调试代码</strong>：用 x64dbg 的”反调试”插件自动扫描</li><li><strong>静态分析</strong>：看代码段里有没有 <code>fs:[30h]</code>、<code>NtQueryInformationProcess</code>、<code>rdtsc</code> 等特征</li><li><strong>动态 trace</strong>：从入口点单步走，遇到奇怪的调用就进去看</li><li><strong>patch</strong>：找到检测点后，把条件跳转 nop 掉或反转</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>反调试的本质是<strong>利用调试器和正常运行时的行为差异</strong>。理解了这个本质，所有变种都能归到这几类：</p><ul><li>读调试状态标志（PEB、NtGlobalFlag、调试端口）</li><li>测时间差（调试器打断执行）</li><li>扫描代码（INT3、硬件断点）</li><li>利用异常机制（SEH 被调试器截获）</li><li>查父进程&#x2F;进程列表</li></ul><p>反反调试没有银弹。最简单的办法是开 ScyllaHide，复杂情况就靠 Frida hook 和手动 patch。多练几个 CTF Reverse 题，反调试见多了就熟了。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/16/anti-debug-reverse-engineering/</id>
    <link href="https://tsvmv.github.io/2026/09/16/anti-debug-reverse-engineering/"/>
    <published>2026-09-16T12:30:00.000Z</published>
    <summary>
      <![CDATA[<p>做逆向分析的人都绕不开反调试。CTF Pwn 题里经常埋各种反调试，商业软件更是把反调试做成了一套体系。你不会反反调试，连主函数都看不到。</p>
<p>这篇文章按”从用户态到内核态、从简单到复杂”的顺序，梳理 Windows 下常见的反调试手段和对应的绕过方法。不是教程式的罗列，而是讲清楚每个手段的原理——原理懂了，变种一眼就能看出来。</p>]]>
    </summary>
    <title>反调试与反反调试技巧详解—从原理到实战</title>
    <updated>2026-11-04T11:56:13.945Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="CTF" scheme="https://tsvmv.github.io/categories/CTF/"/>
    <category term="CTF" scheme="https://tsvmv.github.io/tags/CTF/"/>
    <category term="算法" scheme="https://tsvmv.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <category term="图论" scheme="https://tsvmv.github.io/tags/%E5%9B%BE%E8%AE%BA/"/>
    <content>
      <![CDATA[<p>网络流是图论中最实用的算法之一。最大流最小割定理把”求最大流”和”求最小割”等价起来，于是大量看似和流没关系的问题——二分图匹配、边连通度、选边方案、拆点建模——都能套上网络流的框架。</p><p>Dinic 算法是实际竞赛中最高效的最大流算法，复杂度 O(V²E)，在实际图中远快于理论上界。这篇文章把 Dinic 的原理讲透，然后落地到建模套路。</p><span id="more"></span><h2 id="1-流网络的基本概念"><a href="#1-流网络的基本概念" class="headerlink" title="1. 流网络的基本概念"></a>1. 流网络的基本概念</h2><p>一个流网络是一个有向图 G&#x3D;(V,E)，每条边有容量 c(u,v)，有一个源点 s 和汇点 t。</p><p><strong>流 f</strong> 满足三个条件：</p><ol><li><strong>容量限制</strong>：0 ≤ f(u,v) ≤ c(u,v)</li><li><strong>流量守恒</strong>：除 s 和 t 外，所有点的流入量 &#x3D; 流出量</li><li><strong>斜对称性</strong>：f(u,v) &#x3D; -f(v,u)</li></ol><p><strong>残量网络</strong>：对每条边 (u,v)，残量 r(u,v) &#x3D; c(u,v) - f(u,v)。残量网络中还可以有反向边 r(v,u) &#x3D; f(u,v)——这允许”反悔”。</p><p><strong>增广路</strong>：残量网络中从 s 到 t 的路径。找到一条增广路，把路上的最小残量加上去，就得到了新的流。Ford-Fulkerson 方法就是反复找增广路，直到找不到为止。</p><h2 id="2-Dinic-算法的两个核心"><a href="#2-Dinic-算法的两个核心" class="headerlink" title="2. Dinic 算法的两个核心"></a>2. Dinic 算法的两个核心</h2><p>Dinic 比朴素 Ford-Fulkerson 快，靠两个优化：</p><h3 id="2-1-BFS-分层（Level-Graph）"><a href="#2-1-BFS-分层（Level-Graph）" class="headerlink" title="2.1 BFS 分层（Level Graph）"></a>2.1 BFS 分层（Level Graph）</h3><p>先 BFS 残量网络，只走残量 &gt; 0 的边，给每个点标上距离 s 的层数。只有从第 i 层到第 i+1 层的边才允许在下一步 DFS 中使用。</p><p>这样做的好处：DFS 只会沿着”更靠近 t”的方向走，不会走回头路。</p><h3 id="2-2-DFS-阻塞流（Blocking-Flow）"><a href="#2-2-DFS-阻塞流（Blocking-Flow）" class="headerlink" title="2.2 DFS 阻塞流（Blocking Flow）"></a>2.2 DFS 阻塞流（Blocking Flow）</h3><p>在分层图上做 DFS，一次把所有能走的增广路都走完，而不是只走一条。这叫”阻塞流”。</p><p>每轮 BFS 分层 + DFS 阻塞流后，s 到 t 的最短距离严格增加。最多 V 轮，每轮 DFS 总 O(VE)，所以总 O(V²E)。</p><h2 id="3-完整实现"><a href="#3-完整实现" class="headerlink" title="3. 完整实现"></a>3. 完整实现</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> deque</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Dinic</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, n</span>):</span><br><span class="line">        <span class="variable language_">self</span>.n = n</span><br><span class="line">        <span class="variable language_">self</span>.graph = [[] <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(n)]  <span class="comment"># 邻接表</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">add_edge</span>(<span class="params">self, u, v, cap</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;加边 u-&gt;v 容量 cap，同时加反向边 0&quot;&quot;&quot;</span></span><br><span class="line">        <span class="variable language_">self</span>.graph[u].append([v, cap, <span class="built_in">len</span>(<span class="variable language_">self</span>.graph[v])])</span><br><span class="line">        <span class="variable language_">self</span>.graph[v].append([u, <span class="number">0</span>, <span class="built_in">len</span>(<span class="variable language_">self</span>.graph[u]) - <span class="number">1</span>])</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">bfs</span>(<span class="params">self, s, t, level</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;构建分层图，返回 t 是否可达&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(level)):</span><br><span class="line">            level[i] = -<span class="number">1</span></span><br><span class="line">        level[s] = <span class="number">0</span></span><br><span class="line">        q = deque([s])</span><br><span class="line">        <span class="keyword">while</span> q:</span><br><span class="line">            u = q.popleft()</span><br><span class="line">            <span class="keyword">for</span> v, cap, rev <span class="keyword">in</span> <span class="variable language_">self</span>.graph[u]:</span><br><span class="line">                <span class="keyword">if</span> cap &gt; <span class="number">0</span> <span class="keyword">and</span> level[v] &lt; <span class="number">0</span>:</span><br><span class="line">                    level[v] = level[u] + <span class="number">1</span></span><br><span class="line">                    q.append(v)</span><br><span class="line">        <span class="keyword">return</span> level[t] &gt;= <span class="number">0</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">dfs</span>(<span class="params">self, u, t, flow, level, iter_ptr</span>):</span><br><span class="line">        <span class="keyword">if</span> u == t:</span><br><span class="line">            <span class="keyword">return</span> flow</span><br><span class="line">        <span class="keyword">while</span> iter_ptr[u] &lt; <span class="built_in">len</span>(<span class="variable language_">self</span>.graph[u]):</span><br><span class="line">            edge = <span class="variable language_">self</span>.graph[u][iter_ptr[u]]</span><br><span class="line">            v, cap, rev = edge</span><br><span class="line">            <span class="keyword">if</span> cap &gt; <span class="number">0</span> <span class="keyword">and</span> level[u] &lt; level[v]:</span><br><span class="line">                pushed = <span class="variable language_">self</span>.dfs(v, t, <span class="built_in">min</span>(flow, cap), level, iter_ptr)</span><br><span class="line">                <span class="keyword">if</span> pushed &gt; <span class="number">0</span>:</span><br><span class="line">                    edge[<span class="number">1</span>] -= pushed           <span class="comment"># 正向边减</span></span><br><span class="line">                    <span class="variable language_">self</span>.graph[v][rev][<span class="number">1</span>] += pushed  <span class="comment"># 反向边加</span></span><br><span class="line">                    <span class="keyword">return</span> pushed</span><br><span class="line">            iter_ptr[u] += <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">max_flow</span>(<span class="params">self, s, t</span>):</span><br><span class="line">        flow = <span class="number">0</span></span><br><span class="line">        level = [-<span class="number">1</span>] * <span class="variable language_">self</span>.n</span><br><span class="line">        <span class="keyword">while</span> <span class="variable language_">self</span>.bfs(s, t, level):</span><br><span class="line">            iter_ptr = [<span class="number">0</span>] * <span class="variable language_">self</span>.n</span><br><span class="line">            <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">                pushed = <span class="variable language_">self</span>.dfs(s, t, <span class="built_in">float</span>(<span class="string">&#x27;inf&#x27;</span>), level, iter_ptr)</span><br><span class="line">                <span class="keyword">if</span> pushed == <span class="number">0</span>:</span><br><span class="line">                    <span class="keyword">break</span></span><br><span class="line">                flow += pushed</span><br><span class="line">            level = [-<span class="number">1</span>] * <span class="variable language_">self</span>.n</span><br><span class="line">        <span class="keyword">return</span> flow</span><br></pre></td></tr></table></figure><h2 id="4-最小割"><a href="#4-最小割" class="headerlink" title="4. 最小割"></a>4. 最小割</h2><p>最大流 &#x3D; 最小割（Max-Flow Min-Cut Theorem）。</p><p>最小割是把点集分成 S（含 s）和 T（含 t），使得从 S 到 T 的所有边的容量之和最小。</p><p>求最小割的边：跑完最大流后，在残量网络中从 s 做 BFS，所有能到达的点属于 S。跨 S-T 的原始边就是割边。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">min_cut</span>(<span class="params">self, s, t</span>):</span><br><span class="line">    <span class="variable language_">self</span>.max_flow(s, t)</span><br><span class="line">    visited = [<span class="literal">False</span>] * <span class="variable language_">self</span>.n</span><br><span class="line">    q = deque([s])</span><br><span class="line">    visited[s] = <span class="literal">True</span></span><br><span class="line">    <span class="keyword">while</span> q:</span><br><span class="line">        u = q.popleft()</span><br><span class="line">        <span class="keyword">for</span> v, cap, rev <span class="keyword">in</span> <span class="variable language_">self</span>.graph[u]:</span><br><span class="line">            <span class="keyword">if</span> cap &gt; <span class="number">0</span> <span class="keyword">and</span> <span class="keyword">not</span> visited[v]:</span><br><span class="line">                visited[v] = <span class="literal">True</span></span><br><span class="line">                q.append(v)</span><br><span class="line">    cut_edges = []</span><br><span class="line">    <span class="keyword">for</span> u <span class="keyword">in</span> <span class="built_in">range</span>(<span class="variable language_">self</span>.n):</span><br><span class="line">        <span class="keyword">if</span> visited[u]:</span><br><span class="line">            <span class="keyword">for</span> v, cap, rev <span class="keyword">in</span> <span class="variable language_">self</span>.graph[u]:</span><br><span class="line">                <span class="keyword">if</span> <span class="keyword">not</span> visited[v]:</span><br><span class="line">                    cut_edges.append((u, v))</span><br><span class="line">    <span class="keyword">return</span> cut_edges</span><br></pre></td></tr></table></figure><h2 id="5-建模套路一：二分图最大匹配"><a href="#5-建模套路一：二分图最大匹配" class="headerlink" title="5. 建模套路一：二分图最大匹配"></a>5. 建模套路一：二分图最大匹配</h2><p>二分图最大匹配等价于最大流：</p><ul><li>源点 s 连到左边所有点，容量 1</li><li>左边点连到右边匹配点，容量 1</li><li>右边所有点连到汇点 t，容量 1</li><li>最大流 &#x3D; 最大匹配数</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 左边 n 个点，右边 m 个点，s=0，t=n+m+1</span></span><br><span class="line">dinic = Dinic(n + m + <span class="number">2</span>)</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(n):</span><br><span class="line">    dinic.add_edge(<span class="number">0</span>, i + <span class="number">1</span>, <span class="number">1</span>)        <span class="comment"># s -&gt; 左边</span></span><br><span class="line"><span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(m):</span><br><span class="line">    dinic.add_edge(n + <span class="number">1</span> + j, n + m + <span class="number">1</span>, <span class="number">1</span>)  <span class="comment"># 右边 -&gt; t</span></span><br><span class="line"><span class="keyword">for</span> u, v <span class="keyword">in</span> edges:</span><br><span class="line">    dinic.add_edge(u + <span class="number">1</span>, n + <span class="number">1</span> + v, <span class="number">1</span>)  <span class="comment"># 左边 -&gt; 右边</span></span><br><span class="line"><span class="built_in">print</span>(dinic.max_flow(<span class="number">0</span>, n + m + <span class="number">1</span>))</span><br></pre></td></tr></table></figure><h2 id="6-建模套路二：最大权闭合子图"><a href="#6-建模套路二：最大权闭合子图" class="headerlink" title="6. 建模套路二：最大权闭合子图"></a>6. 建模套路二：最大权闭合子图</h2><p>选一些点，每个点有权值（正或负），要求选了一个点就必须选它依赖的点。求最大权值和。</p><p>建图：</p><ul><li>正权点连 s，容量 &#x3D; 权值</li><li>负权点连 t，容量 &#x3D; -权值</li><li>依赖关系 u-&gt;v，容量 &#x3D; INF</li><li>答案 &#x3D; 所有正权之和 - 最小割</li></ul><h2 id="7-建模套路三：边连通度"><a href="#7-建模套路三：边连通度" class="headerlink" title="7. 建模套路三：边连通度"></a>7. 建模套路三：边连通度</h2><p>无向图中，最少删多少条边让 s 和 t 不连通？</p><p>把每条无向边拆成两个方向的有向边，容量都是 1，然后跑 s 到 t 的最大流。答案就是最大流。</p><h2 id="8-常用优化"><a href="#8-常用优化" class="headerlink" title="8. 常用优化"></a>8. 常用优化</h2><h3 id="8-1-当前弧优化（Current-Arc）"><a href="#8-1-当前弧优化（Current-Arc）" class="headerlink" title="8.1 当前弧优化（Current Arc）"></a>8.1 当前弧优化（Current Arc）</h3><p>上面实现中的 <code>iter_ptr</code> 就是当前弧优化。它记录每个点下一次该试哪条边，避免重复试已经走不通的边。这是 Dinic 能跑得飞快的关键。</p><h3 id="8-2-容量缩放"><a href="#8-2-容量缩放" class="headerlink" title="8.2 容量缩放"></a>8.2 容量缩放</h3><p>对大图，可以用 capacity scaling：先只考虑容量 ≥ Δ 的边，跑完最大流后把 Δ 减半。这样可以减少无效搜索。</p><h3 id="8-3-ISAP-优化"><a href="#8-3-ISAP-优化" class="headerlink" title="8.3 ISAP 优化"></a>8.3 ISAP 优化</h3><p>ISAP 是 Dinic 的变种，只做一次 BFS，之后用 gap 优化动态修改层数。在某些图上比 Dinic 快 2-3 倍。</p><h2 id="9-复杂度参考"><a href="#9-复杂度参考" class="headerlink" title="9. 复杂度参考"></a>9. 复杂度参考</h2><table><thead><tr><th>图类型</th><th>Dinic 实际运行</th></tr></thead><tbody><tr><td>二分图匹配（V&#x3D;10⁴, E&#x3D;10⁵）</td><td>&lt; 0.1s</td></tr><tr><td>一般图（V&#x3D;10⁴, E&#x3D;10⁵）</td><td>0.2-0.5s</td></tr><tr><td>稠密图（V&#x3D;10³, E&#x3D;10⁵）</td><td>1-2s</td></tr><tr><td>网格图（V&#x3D;10⁵, E&#x3D;4×10⁵）</td><td>1-3s</td></tr></tbody></table><p>Python 版本比 C++ 慢 10-50 倍，大规模图建议用 C++ 或 pypy。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Dinic 是网络流的标配算法。掌握它的关键是理解三个东西：</p><ol><li><strong>残量网络</strong>（为什么要有反向边）</li><li><strong>BFS 分层</strong>（为什么要按层走）</li><li><strong>当前弧优化</strong>（为什么不会重复试已经废的边）</li></ol><p>建模比算法本身更重要。看到问题先想：能不能转化成选点&#x2F;选边&#x2F;匹配&#x2F;连通问题？能的话就往网络流上靠。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/16/dinic-max-flow-algorithm/</id>
    <link href="https://tsvmv.github.io/2026/09/16/dinic-max-flow-algorithm/"/>
    <published>2026-09-16T12:00:00.000Z</published>
    <summary>
      <![CDATA[<p>网络流是图论中最实用的算法之一。最大流最小割定理把”求最大流”和”求最小割”等价起来，于是大量看似和流没关系的问题——二分图匹配、边连通度、选边方案、拆点建模——都能套上网络流的框架。</p>
<p>Dinic 算法是实际竞赛中最高效的最大流算法，复杂度 O(V²E)，在实际图中远快于理论上界。这篇文章把 Dinic 的原理讲透，然后落地到建模套路。</p>]]>
    </summary>
    <title>Dinic 网络流算法详解—从原理到竞赛实战</title>
    <updated>2026-11-04T11:56:13.945Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="CTF" scheme="https://tsvmv.github.io/categories/CTF/"/>
    <category term="CTF" scheme="https://tsvmv.github.io/tags/CTF/"/>
    <category term="密码学" scheme="https://tsvmv.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="数论" scheme="https://tsvmv.github.io/tags/%E6%95%B0%E8%AE%BA/"/>
    <content>
      <![CDATA[<p>二次剩余（Quadratic Residue）是 CTF 密码学里的高频考点。RSA 中解密需要求模 n 的平方根，ECDSA 签名验证涉及二次剩余判定， even 简单的”猜数”题都可能藏着 Legendre 符号的套路。</p><p>这篇文章从定义出发，讲清楚什么是二次剩余、怎么判定、怎么求平方根，最后落地到 CTF 中的常见应用。</p><span id="more"></span><h2 id="1-什么是二次剩余"><a href="#1-什么是二次剩余" class="headerlink" title="1. 什么是二次剩余"></a>1. 什么是二次剩余</h2><p>给定奇质数 p 和整数 a，如果存在 x 使得：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">x² ≡ a (mod p)</span><br></pre></td></tr></table></figure><p>则称 a 是模 p 的<strong>二次剩余</strong>（Quadratic Residue, QR），否则称为<strong>二次非剩余</strong>（Quadratic Non-Residue, QNR）。</p><p>举个例子，p&#x3D;7：</p><ul><li>1²&#x3D;1, 2²&#x3D;4, 3²&#x3D;2, 4²&#x3D;2, 5²&#x3D;4, 6²&#x3D;1</li><li>所以模 7 的二次剩余是 {1, 2, 4}，非剩余是 {3, 5, 6}</li></ul><p>注意 x 和 -x 给出同一个平方值，所以模 p 有 (p-1)&#x2F;2 个二次剩余和 (p-1)&#x2F;2 个二次非剩余。</p><h2 id="2-Legendre-符号"><a href="#2-Legendre-符号" class="headerlink" title="2. Legendre 符号"></a>2. Legendre 符号</h2><p>定义 Legendre 符号：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">(a|p) =  1  如果 a 是模 p 的二次剩余且 a ≠ 0</span><br><span class="line">(a|p) = -1  如果 a 是模 p 的二次非剩余</span><br><span class="line">(a|p) =  0  如果 a ≡ 0 (mod p)</span><br></pre></td></tr></table></figure><p>Euler 判别法给出了计算方法：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(a|p) ≡ a^((p-1)/2) (mod p)</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">legendre</span>(<span class="params">a, p</span>):</span><br><span class="line">    result = <span class="built_in">pow</span>(a, (p - <span class="number">1</span>) // <span class="number">2</span>, p)</span><br><span class="line">    <span class="keyword">if</span> result == p - <span class="number">1</span>:</span><br><span class="line">        <span class="keyword">return</span> -<span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> result  <span class="comment"># 0 或 1</span></span><br></pre></td></tr></table></figure><h2 id="3-高斯互反律"><a href="#3-高斯互反律" class="headerlink" title="3. 高斯互反律"></a>3. 高斯互反律</h2><p>二次互反律是数论中最优美的定理之一：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(p|q) * (q|p) = (-1)^((p-1)/2 * (q-1)/2)</span><br></pre></td></tr></table></figure><p>换句话说：</p><ul><li>如果 p 或 q ≡ 1 (mod 4)，则 (p|q) &#x3D; (q|p)</li><li>如果 p 和 q 都 ≡ 3 (mod 4)，则 (p|q) &#x3D; -(q|p)</li></ul><p>辅助律：</p><ul><li>(2|p) &#x3D; 1 当 p ≡ ±1 (mod 8)</li><li>(2|p) &#x3D; -1 当 p ≡ ±3 (mod 8)</li></ul><h2 id="4-Tonelli-Shanks-算法：求模平方根"><a href="#4-Tonelli-Shanks-算法：求模平方根" class="headerlink" title="4. Tonelli-Shanks 算法：求模平方根"></a>4. Tonelli-Shanks 算法：求模平方根</h2><p>知道 a 是二次剩余后，怎么求 x 使得 x² ≡ a (mod p)？这就是 Tonelli-Shanks 算法。</p><h3 id="4-1-特殊情况：p-≡-3-mod-4"><a href="#4-1-特殊情况：p-≡-3-mod-4" class="headerlink" title="4.1 特殊情况：p ≡ 3 (mod 4)"></a>4.1 特殊情况：p ≡ 3 (mod 4)</h3><p>最简单的情况。直接公式：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">x ≡ a^((p+1)/4) (mod p)</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">sqrt_mod_congruent_3</span>(<span class="params">a, p</span>):</span><br><span class="line">    <span class="keyword">assert</span> p % <span class="number">4</span> == <span class="number">3</span></span><br><span class="line">    x = <span class="built_in">pow</span>(a, (p + <span class="number">1</span>) // <span class="number">4</span>, p)</span><br><span class="line">    <span class="keyword">assert</span> (x * x - a) % p == <span class="number">0</span></span><br><span class="line">    <span class="keyword">return</span> x</span><br></pre></td></tr></table></figure><h3 id="4-2-一般情况：Tonelli-Shanks"><a href="#4-2-一般情况：Tonelli-Shanks" class="headerlink" title="4.2 一般情况：Tonelli-Shanks"></a>4.2 一般情况：Tonelli-Shanks</h3><p>对于任意奇质数 p，算法步骤：</p><ol><li>把 p-1 写成 Q * 2^S</li><li>找一个二次非剩余 z</li><li>初始化 M&#x3D;S, c&#x3D;z^(2^Q), t&#x3D;a^Q, R&#x3D;a^((Q+1)&#x2F;2)</li><li>循环：<ul><li>如果 t&#x3D;0，返回 0</li><li>如果 t&#x3D;1，返回 R</li><li>找最小的 i (0 &lt; i &lt; M) 使得 t^(2^i) &#x3D; 1</li><li>设 b &#x3D; c^(2^(M-i-1))</li><li>更新 M&#x3D;i, c&#x3D;b², t&#x3D;t<em>b², R&#x3D;R</em>b</li></ul></li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">tonelli_shanks</span>(<span class="params">n, p</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;求 x 使得 x² ≡ n (mod p)，p 是奇质数&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">assert</span> legendre(n, p) == <span class="number">1</span>, <span class="string">&quot;n 不是二次剩余&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 特殊情况</span></span><br><span class="line">    <span class="keyword">if</span> p % <span class="number">4</span> == <span class="number">3</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">pow</span>(n, (p + <span class="number">1</span>) // <span class="number">4</span>, p)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 分解 p-1 = Q * 2^S</span></span><br><span class="line">    Q = p - <span class="number">1</span></span><br><span class="line">    S = <span class="number">0</span></span><br><span class="line">    <span class="keyword">while</span> Q % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">        Q //= <span class="number">2</span></span><br><span class="line">        S += <span class="number">1</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 找二次非剩余</span></span><br><span class="line">    z = <span class="number">2</span></span><br><span class="line">    <span class="keyword">while</span> legendre(z, p) != -<span class="number">1</span>:</span><br><span class="line">        z += <span class="number">1</span></span><br><span class="line">    </span><br><span class="line">    M = S</span><br><span class="line">    c = <span class="built_in">pow</span>(z, Q, p)</span><br><span class="line">    t = <span class="built_in">pow</span>(n, Q, p)</span><br><span class="line">    R = <span class="built_in">pow</span>(n, (Q + <span class="number">1</span>) // <span class="number">2</span>, p)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        <span class="keyword">if</span> t == <span class="number">1</span>:</span><br><span class="line">            <span class="keyword">return</span> R</span><br><span class="line">        <span class="keyword">if</span> t == <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 找最小的 i</span></span><br><span class="line">        i = <span class="number">0</span></span><br><span class="line">        temp = t</span><br><span class="line">        <span class="keyword">while</span> temp != <span class="number">1</span>:</span><br><span class="line">            temp = <span class="built_in">pow</span>(temp, <span class="number">2</span>, p)</span><br><span class="line">            i += <span class="number">1</span></span><br><span class="line">        </span><br><span class="line">        b = <span class="built_in">pow</span>(c, <span class="number">1</span> &lt;&lt; (M - i - <span class="number">1</span>), p)</span><br><span class="line">        M = i</span><br><span class="line">        c = <span class="built_in">pow</span>(b, <span class="number">2</span>, p)</span><br><span class="line">        t = (t * c) % p</span><br><span class="line">        R = (R * b) % p</span><br></pre></td></tr></table></figure><h2 id="5-CTF-实战：RSA-解密"><a href="#5-CTF-实战：RSA-解密" class="headerlink" title="5. CTF 实战：RSA 解密"></a>5. CTF 实战：RSA 解密</h2><p>RSA 中，如果知道私钥 d，解密就是 m &#x3D; c^d mod n。但如果题目给了 e&#x3D;3 且 m 很小，m³ &lt; n，就可以直接开三次方根。类似地，e&#x3D;2 时就是开平方根。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># RSA 低加密指数攻击，e=3</span></span><br><span class="line"><span class="comment"># m³ ≡ c (mod n)，但 m³ &lt; n，所以 m³ = c（无模约简）</span></span><br><span class="line"><span class="comment"># 直接开立方根</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">integer_cbrt</span>(<span class="params">n</span>):</span><br><span class="line">    <span class="keyword">if</span> n &lt; <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> -integer_cbrt(-n)</span><br><span class="line">    <span class="keyword">if</span> n == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">    x = <span class="built_in">int</span>(<span class="built_in">round</span>(n ** (<span class="number">1</span>/<span class="number">3</span>)))</span><br><span class="line">    <span class="keyword">while</span> (x+<span class="number">1</span>)**<span class="number">3</span> &lt;= n:</span><br><span class="line">        x += <span class="number">1</span></span><br><span class="line">    <span class="keyword">while</span> x**<span class="number">3</span> &gt; n:</span><br><span class="line">        x -= <span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> x</span><br></pre></td></tr></table></figure><h2 id="6-CTF-实战：二次剩余编码"><a href="#6-CTF-实战：二次剩余编码" class="headerlink" title="6. CTF 实战：二次剩余编码"></a>6. CTF 实战：二次剩余编码</h2><p>有一种隐写术叫”二次剩余编码”：把信息嵌入到模 p 的二次剩余&#x2F;非剩余序列中。解密时只需要对每个块算 Legendre 符号，1 表示 QR，0 表示 QNR。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">decode_quadratic_residue</span>(<span class="params">data, p</span>):</span><br><span class="line">    bits = []</span><br><span class="line">    <span class="keyword">for</span> block <span class="keyword">in</span> data:</span><br><span class="line">        <span class="keyword">if</span> legendre(block, p) == <span class="number">1</span>:</span><br><span class="line">            bits.append(<span class="number">1</span>)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            bits.append(<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> bits</span><br></pre></td></tr></table></figure><h2 id="7-CTF-实战：Tonelli-Shanks-求-RSA-明文"><a href="#7-CTF-实战：Tonelli-Shanks-求-RSA-明文" class="headerlink" title="7. CTF 实战：Tonelli-Shanks 求 RSA 明文"></a>7. CTF 实战：Tonelli-Shanks 求 RSA 明文</h2><p>在 RSA 共模攻击、wiener 攻击等场景中，有时需要对模合数 n 求平方根。如果 n &#x3D; p*q 且知道 p 和 q，可以分别在 mod p 和 mod q 下求平方根，然后用 CRT 合并。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">sqrt_mod_n</span>(<span class="params">a, p, q</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;n = p*q，求 x 使得 x² ≡ a (mod n)&quot;&quot;&quot;</span></span><br><span class="line">    <span class="comment"># 分别求 mod p 和 mod q 的平方根</span></span><br><span class="line">    xp = tonelli_shanks(a % p, p)</span><br><span class="line">    xq = tonelli_shanks(a % q, q)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># CRT 合并，有四个解</span></span><br><span class="line">    <span class="comment"># x ≡ xp (mod p), x ≡ xq (mod q)</span></span><br><span class="line">    <span class="comment"># x ≡ xp (mod p), x ≡ -xq (mod q)</span></span><br><span class="line">    <span class="comment"># x ≡ -xp (mod p), x ≡ xq (mod q)</span></span><br><span class="line">    <span class="comment"># x ≡ -xp (mod p), x ≡ -xq (mod q)</span></span><br><span class="line">    <span class="keyword">return</span> crt([xp, xq], [p, q])</span><br></pre></td></tr></table></figure><h2 id="8-雅可比符号"><a href="#8-雅可比符号" class="headerlink" title="8. 雅可比符号"></a>8. 雅可比符号</h2><p>当模数不是质数时，用雅可比符号推广 Legendre 符号。雅可比符号 (a|n) 定义为 n 的所有质因子的 Legendre 符号乘积。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">jacobi</span>(<span class="params">a, n</span>):</span><br><span class="line">    <span class="keyword">assert</span> n &gt; <span class="number">0</span> <span class="keyword">and</span> n % <span class="number">2</span> == <span class="number">1</span></span><br><span class="line">    a %= n</span><br><span class="line">    result = <span class="number">1</span></span><br><span class="line">    <span class="keyword">while</span> a != <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">while</span> a % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">            a //= <span class="number">2</span></span><br><span class="line">            <span class="keyword">if</span> n % <span class="number">8</span> <span class="keyword">in</span> (<span class="number">3</span>, <span class="number">5</span>):</span><br><span class="line">                result = -result</span><br><span class="line">        a, n = n, a</span><br><span class="line">        <span class="keyword">if</span> a % <span class="number">4</span> == <span class="number">3</span> <span class="keyword">and</span> n % <span class="number">4</span> == <span class="number">3</span>:</span><br><span class="line">            result = -result</span><br><span class="line">        a %= n</span><br><span class="line">    <span class="keyword">return</span> result <span class="keyword">if</span> n == <span class="number">1</span> <span class="keyword">else</span> <span class="number">0</span></span><br></pre></td></tr></table></figure><p>雅可比符号可以高效计算，但它不告诉你 a 是否是模 n 的二次剩余——只有当 n 是质数时才等价于 Legendre 符号。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>二次剩余是数论密码学的基础组件。掌握 Legendre 符号判定和 Tonelli-Shanks 求根，能应对 CTF 中大量的密码学题目。关键记住：</p><ol><li><strong>p ≡ 3 (mod 4)</strong>：直接公式 x &#x3D; a^((p+1)&#x2F;4)</li><li><strong>一般情况</strong>：Tonelli-Shanks 算法</li><li><strong>合数模</strong>：分解后 CRT 合并</li><li><strong>CTF 套路</strong>：低指数开方、二次剩余隐写、RSA 变体攻击</li></ol>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/16/quadratic-residue-tonelli-shanks/</id>
    <link href="https://tsvmv.github.io/2026/09/16/quadratic-residue-tonelli-shanks/"/>
    <published>2026-09-16T11:30:00.000Z</published>
    <summary>
      <![CDATA[<p>二次剩余（Quadratic Residue）是 CTF 密码学里的高频考点。RSA 中解密需要求模 n 的平方根，ECDSA 签名验证涉及二次剩余判定， even 简单的”猜数”题都可能藏着 Legendre 符号的套路。</p>
<p>这篇文章从定义出发，讲清楚什么是二次剩余、怎么判定、怎么求平方根，最后落地到 CTF 中的常见应用。</p>]]>
    </summary>
    <title>二次剩余与 Tonelli-Shanks 算法详解</title>
    <updated>2026-11-04T11:56:13.948Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="随笔" scheme="https://tsvmv.github.io/categories/%E9%9A%8F%E7%AC%94/"/>
    <category term="随笔" scheme="https://tsvmv.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <category term="吐槽" scheme="https://tsvmv.github.io/tags/%E5%90%90%E6%A7%BD/"/>
    <content>
      <![CDATA[<p>NBNB，关系户这一块，带手机不管（别对号入座</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/16/zhengwang-guanxihu/</id>
    <link href="https://tsvmv.github.io/2026/09/16/zhengwang-guanxihu/"/>
    <published>2026-09-16T06:00:00.000Z</published>
    <summary>
      <![CDATA[<p>NBNB，关系户这一块，带手机不管（别对号入座</p>]]>
    </summary>
    <title>服了某中学</title>
    <updated>2026-11-04T11:56:13.949Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="运维开发" scheme="https://tsvmv.github.io/categories/%E8%BF%90%E7%BB%B4%E5%BC%80%E5%8F%91/"/>
    <category term="编程" scheme="https://tsvmv.github.io/tags/%E7%BC%96%E7%A8%8B/"/>
    <category term="Rust" scheme="https://tsvmv.github.io/tags/Rust/"/>
    <category term="系统编程" scheme="https://tsvmv.github.io/tags/%E7%B3%BB%E7%BB%9F%E7%BC%96%E7%A8%8B/"/>
    <content>
      <![CDATA[<p>很多人对 Rust 的印象停留在”安全、 borrow checker、学习曲线陡峭”。但在安全工具开发领域，Rust 真正的价值是：<strong>它让你能写 C 级别的底层代码，同时不 segfault。</strong></p><p>你写过 pwntools 就知道 Python 做性能敏感的事情有多痛苦——解析大二进制文件、批量 fuzz、hook 系统调用，Python 永远慢半拍。Rust 刚好填了这个坑：编译成原生机器码，零成本抽象，内存安全，还有成熟的 FFI 可以和 C 库互操作。</p><p>这篇文章不讲 Rust 语法基础，直接讲写安全工具时最需要的东西：unsafe、FFI、进程注入、内存操作。</p><span id="more"></span><h2 id="1-为什么安全工具要用-Rust"><a href="#1-为什么安全工具要用-Rust" class="headerlink" title="1. 为什么安全工具要用 Rust"></a>1. 为什么安全工具要用 Rust</h2><p>先对比一下几个选项：</p><table><thead><tr><th>语言</th><th>性能</th><th>内存安全</th><th>开发效率</th><th>适合场景</th></tr></thead><tbody><tr><td>C</td><td>最快</td><td>无</td><td>低</td><td>内核模块、驱动</td></tr><tr><td>C++</td><td>快</td><td>部分</td><td>中</td><td>大型逆向框架</td></tr><tr><td>Python</td><td>慢</td><td>有</td><td>高</td><td>脚本、exp</td></tr><tr><td><strong>Rust</strong></td><td><strong>快</strong></td><td><strong>有</strong></td><td><strong>中高</strong></td><td><strong>安全工具、CTF 框架</strong></td></tr><tr><td>Go</td><td>中</td><td>有</td><td>高</td><td>后端服务、爬虫</td></tr></tbody></table><p>Rust 做安全工具的核心优势：</p><ul><li><strong>内存安全</strong>：写 parser、反序列化、hook 代码时不用担心 use-after-free</li><li><strong>零成本抽象</strong>：高阶函数、迭代器、泛型不影响性能</li><li><strong>cross-compile</strong>：交叉编译到 Windows&#x2F;macOS&#x2F;Linux 非常方便</li><li><strong>FFI</strong>：可以直接链接 C 库（libbfd、capstone、unicorn）</li><li><strong>cargo</strong>：包管理和构建系统吊打 make&#x2F;cmake</li></ul><h2 id="2-unsafe：什么时候需要它"><a href="#2-unsafe：什么时候需要它" class="headerlink" title="2. unsafe：什么时候需要它"></a>2. unsafe：什么时候需要它</h2><p>Rust 默认的安全检查覆盖了 99% 的场景。但写安全工具时，你经常需要：</p><ul><li>操作原始指针（读进程内存）</li><li>调用 C 库函数（libc、unicorn）</li><li>实现 trait 时绕过 borrow checker</li><li>解析二进制格式时做位操作</li></ul><p>这些都需要 <code>unsafe</code>。记住一个原则：<strong>unsafe 是给抽象层用的，不是给业务代码用的。</strong></p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 安全的 unsafe 用法：把不安全的操作封装在安全的 API 后面</span></span><br><span class="line"><span class="keyword">use</span> std::ptr;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">MemoryReader</span> &#123;</span><br><span class="line">    addr: *<span class="keyword">const</span> <span class="type">u8</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span> <span class="title class_">MemoryReader</span> &#123;</span><br><span class="line">    <span class="comment">/// 安全的构造函数：检查指针非空</span></span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">new</span>(addr: *<span class="keyword">const</span> <span class="type">u8</span>) <span class="punctuation">-&gt;</span> <span class="type">Option</span>&lt;<span class="keyword">Self</span>&gt; &#123;</span><br><span class="line">        <span class="keyword">if</span> addr.<span class="title function_ invoke__">is_null</span>() &#123;</span><br><span class="line">            <span class="literal">None</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="title function_ invoke__">Some</span>(<span class="keyword">Self</span> &#123; addr &#125;)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/// 安全的读取 API：返回 Result 而不是直接 panic</span></span><br><span class="line">    <span class="comment">/// # Safety</span></span><br><span class="line">    <span class="comment">/// 调用者必须保证 addr 指向至少 size 个有效字节</span></span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">unsafe</span> <span class="keyword">fn</span> <span class="title function_">read_bytes</span>(&amp;<span class="keyword">self</span>, size: <span class="type">usize</span>) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;<span class="type">Vec</span>&lt;<span class="type">u8</span>&gt;, std::io::Error&gt; &#123;</span><br><span class="line">        <span class="keyword">if</span> size == <span class="number">0</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="title function_ invoke__">Ok</span>(<span class="type">Vec</span>::<span class="title function_ invoke__">new</span>());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">buf</span> = <span class="built_in">vec!</span>[<span class="number">0u8</span>; size];</span><br><span class="line">        ptr::<span class="title function_ invoke__">copy_nonoverlapping</span>(<span class="keyword">self</span>.addr, buf.<span class="title function_ invoke__">as_mut_ptr</span>(), size);</span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(buf)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关键点：</p><ul><li>unsafe 块里的代码不保证安全，但<strong>封装它的函数应该是安全的</strong></li><li>用 <code># Safety</code> 文档注释说明调用者需要保证什么</li><li>能不 unsafe 就不 unsafe</li></ul><h2 id="3-FFI：调用-C-库"><a href="#3-FFI：调用-C-库" class="headerlink" title="3. FFI：调用 C 库"></a>3. FFI：调用 C 库</h2><p>写安全工具最常见的需求是调用现成的 C 库。Rust 的 FFI（Foreign Function Interface）非常成熟。</p><h3 id="3-1-声明外部函数"><a href="#3-1-声明外部函数" class="headerlink" title="3.1 声明外部函数"></a>3.1 声明外部函数</h3><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 链接 libc 的 read 函数</span></span><br><span class="line"><span class="keyword">extern</span> <span class="string">&quot;C&quot;</span> &#123;</span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">read</span>(fd: <span class="type">i32</span>, buf: *<span class="keyword">mut</span> c_void, count: size_t) <span class="punctuation">-&gt;</span> <span class="type">isize</span>;</span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">write</span>(fd: <span class="type">i32</span>, buf: *<span class="keyword">const</span> c_void, count: size_t) <span class="punctuation">-&gt;</span> <span class="type">isize</span>;</span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">close</span>(fd: <span class="type">i32</span>) <span class="punctuation">-&gt;</span> <span class="type">i32</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> libc::&#123;c_void, size_t&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">safe_read</span>(fd: <span class="type">i32</span>, buf: &amp;<span class="keyword">mut</span> [<span class="type">u8</span>]) <span class="punctuation">-&gt;</span> <span class="type">isize</span> &#123;</span><br><span class="line">    <span class="keyword">unsafe</span> &#123; <span class="title function_ invoke__">read</span>(fd, buf.<span class="title function_ invoke__">as_mut_ptr</span>() <span class="keyword">as</span> *<span class="keyword">mut</span> c_void, buf.<span class="title function_ invoke__">len</span>()) &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-链接系统库"><a href="#3-2-链接系统库" class="headerlink" title="3.2 链接系统库"></a>3.2 链接系统库</h3><p>在 <code>Cargo.toml</code> 中声明：</p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[dependencies]</span></span><br><span class="line"><span class="attr">libc</span> = <span class="string">&quot;0.2&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="section">[build-dependencies]</span></span><br><span class="line"><span class="attr">cc</span> = <span class="string">&quot;1.0&quot;</span></span><br></pre></td></tr></table></figure><p><code>build.rs</code> 中告诉 cargo 链接系统库：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;cargo:rustc-link-lib=dylib=bfd&quot;</span>);   <span class="comment">// 链接 libbfd</span></span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;cargo:rustc-link-lib=dylib=opcodes&quot;</span>); <span class="comment">// 链接 libopcodes</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-调用-Unicorn-Engine"><a href="#3-3-调用-Unicorn-Engine" class="headerlink" title="3.3 调用 Unicorn Engine"></a>3.3 调用 Unicorn Engine</h3><p>Unicorn 是 CTF pwn 最常用的 CPU 模拟器，Rust 有官方绑定：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> unicorn_engine::&#123;Unicorn, Arch, Mode&#125;;</span><br><span class="line"><span class="keyword">use</span> unicorn_engine::RegisterX86;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">emulate_shellcode</span>() <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;(), <span class="type">Box</span>&lt;<span class="keyword">dyn</span> std::error::Error&gt;&gt; &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">uc</span> = Unicorn::<span class="title function_ invoke__">new</span>(Arch::X86, Mode::MODE_64)?;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 映射内存</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">addr</span> = <span class="number">0x1000_0000</span>;</span><br><span class="line">    uc.<span class="title function_ invoke__">mem_map</span>(addr, <span class="number">2</span> * <span class="number">1024</span> * <span class="number">1024</span>, unicorn_engine::PROT_ALL)?;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 写入 shellcode</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">shellcode</span>: <span class="type">Vec</span>&lt;<span class="type">u8</span>&gt; = hex::<span class="title function_ invoke__">decode</span>(<span class="string">&quot;4831ff4831f64831d20f05&quot;</span>)?; <span class="comment">// xor rdi,rdi; xor rsi,rsi; xor rdx,rdx; syscall</span></span><br><span class="line">    uc.<span class="title function_ invoke__">mem_write</span>(addr, &amp;shellcode)?;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 设置寄存器</span></span><br><span class="line">    uc.<span class="title function_ invoke__">reg_write</span>(RegisterX86::RAINTO, <span class="number">60</span>)?; <span class="comment">// exit syscall</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 开始模拟</span></span><br><span class="line">    uc.<span class="title function_ invoke__">emu_start</span>(addr, addr + shellcode.<span class="title function_ invoke__">len</span>() <span class="keyword">as</span> <span class="type">u64</span>, <span class="number">0</span>, <span class="number">0</span>)?;</span><br><span class="line">    </span><br><span class="line">    <span class="title function_ invoke__">Ok</span>(())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="4-解析二进制格式"><a href="#4-解析二进制格式" class="headerlink" title="4. 解析二进制格式"></a>4. 解析二进制格式</h2><p>写 ELF&#x2F;PE parser 是 CTF 工具开发的基本功。Rust 的 <code>byteorder</code> 和 <code>binread</code> crate 让这个过程非常优雅。</p><h3 id="4-1-手动解析-ELF-头"><a href="#4-1-手动解析-ELF-头" class="headerlink" title="4.1 手动解析 ELF 头"></a>4.1 手动解析 ELF 头</h3><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> byteorder::&#123;ByteOrder, LittleEndian&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Debug)]</span></span><br><span class="line"><span class="meta">#[repr(C)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">Elf64Header</span> &#123;</span><br><span class="line">    <span class="keyword">pub</span> e_ident: [<span class="type">u8</span>; <span class="number">16</span>],</span><br><span class="line">    <span class="keyword">pub</span> e_type: <span class="type">u16</span>,</span><br><span class="line">    <span class="keyword">pub</span> e_machine: <span class="type">u16</span>,</span><br><span class="line">    <span class="keyword">pub</span> e_version: <span class="type">u32</span>,</span><br><span class="line">    <span class="keyword">pub</span> e_entry: <span class="type">u64</span>,</span><br><span class="line">    <span class="keyword">pub</span> e_phoff: <span class="type">u64</span>,</span><br><span class="line">    <span class="keyword">pub</span> e_shoff: <span class="type">u64</span>,</span><br><span class="line">    <span class="keyword">pub</span> e_flags: <span class="type">u32</span>,</span><br><span class="line">    <span class="keyword">pub</span> e_ehsize: <span class="type">u16</span>,</span><br><span class="line">    <span class="keyword">pub</span> e_phentsize: <span class="type">u16</span>,</span><br><span class="line">    <span class="keyword">pub</span> e_phnum: <span class="type">u16</span>,</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span> <span class="title class_">Elf64Header</span> &#123;</span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">parse</span>(data: &amp;[<span class="type">u8</span>]) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;<span class="keyword">Self</span>, &amp;<span class="symbol">&#x27;static</span> <span class="type">str</span>&gt; &#123;</span><br><span class="line">        <span class="keyword">if</span> data.<span class="title function_ invoke__">len</span>() &lt; <span class="number">64</span> || &amp;data[..<span class="number">4</span>] != <span class="string">b&quot;\x7fELF&quot;</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="title function_ invoke__">Err</span>(<span class="string">&quot;不是有效的 ELF 文件&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(<span class="keyword">Self</span> &#123;</span><br><span class="line">            e_ident: data[..<span class="number">16</span>].<span class="title function_ invoke__">try_into</span>().<span class="title function_ invoke__">unwrap</span>(),</span><br><span class="line">            e_type: LittleEndian::<span class="title function_ invoke__">read_u16</span>(&amp;data[<span class="number">16</span>..<span class="number">18</span>]),</span><br><span class="line">            e_machine: LittleEndian::<span class="title function_ invoke__">read_u16</span>(&amp;data[<span class="number">18</span>..<span class="number">20</span>]),</span><br><span class="line">            e_version: LittleEndian::<span class="title function_ invoke__">read_u32</span>(&amp;data[<span class="number">20</span>..<span class="number">24</span>]),</span><br><span class="line">            e_entry: LittleEndian::<span class="title function_ invoke__">read_u64</span>(&amp;data[<span class="number">24</span>..<span class="number">32</span>]),</span><br><span class="line">            e_phoff: LittleEndian::<span class="title function_ invoke__">read_u64</span>(&amp;data[<span class="number">32</span>..<span class="number">40</span>]),</span><br><span class="line">            e_shoff: LittleEndian::<span class="title function_ invoke__">read_u64</span>(&amp;data[<span class="number">40</span>..<span class="number">48</span>]),</span><br><span class="line">            e_flags: LittleEndian::<span class="title function_ invoke__">read_u32</span>(&amp;data[<span class="number">48</span>..<span class="number">52</span>]),</span><br><span class="line">            e_ehsize: LittleEndian::<span class="title function_ invoke__">read_u16</span>(&amp;data[<span class="number">52</span>..<span class="number">54</span>]),</span><br><span class="line">            e_phentsize: LittleEndian::<span class="title function_ invoke__">read_u16</span>(&amp;data[<span class="number">54</span>..<span class="number">56</span>]),</span><br><span class="line">            e_phnum: LittleEndian::<span class="title function_ invoke__">read_u16</span>(&amp;data[<span class="number">56</span>..<span class="number">58</span>]),</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-用-goblin-crate"><a href="#4-2-用-goblin-crate" class="headerlink" title="4.2 用 goblin crate"></a>4.2 用 goblin crate</h3><p><code>goblin</code> 是 Rust 生态最好的二进制解析库，支持 ELF&#x2F;PE&#x2F;Mach-O：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> goblin::elf::&#123;Elf, program_header::PT_LOAD&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">analyze_elf</span>(data: &amp;[<span class="type">u8</span>]) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;(), <span class="type">Box</span>&lt;<span class="keyword">dyn</span> std::error::Error&gt;&gt; &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">elf</span> = Elf::<span class="title function_ invoke__">parse</span>(data)?;</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;入口点: &#123;:#x&#125;&quot;</span>, elf.entry);</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;架构: &#123;:?&#125;&quot;</span>, elf.header.e_machine);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> <span class="variable">ph</span> <span class="keyword">in</span> elf.program_headers.<span class="title function_ invoke__">iter</span>() &#123;</span><br><span class="line">        <span class="keyword">if</span> ph.p_type == PT_LOAD &#123;</span><br><span class="line">            <span class="built_in">println!</span>(</span><br><span class="line">                <span class="string">&quot;LOAD: vaddr=&#123;:#x&#125; memsz=&#123;:#x&#125; flags=&#123;&#125;&quot;</span>,</span><br><span class="line">                ph.p_vaddr, ph.p_memsz, ph.p_flags</span><br><span class="line">            );</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> <span class="variable">sym</span> <span class="keyword">in</span> elf.syms.<span class="title function_ invoke__">iter</span>() &#123;</span><br><span class="line">        <span class="keyword">if</span> !sym.st_name.<span class="title function_ invoke__">is_empty</span>() &#123;</span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">let</span> <span class="variable">Ok</span>(name) = elf.strtab.<span class="title function_ invoke__">get_at</span>(sym.st_name) &#123;</span><br><span class="line">                <span class="built_in">println!</span>(<span class="string">&quot;符号: &#123;&#125; @ &#123;:#x&#125;&quot;</span>, name, sym.st_value);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="title function_ invoke__">Ok</span>(())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="5-进程内存读取"><a href="#5-进程内存读取" class="headerlink" title="5. 进程内存读取"></a>5. 进程内存读取</h2><p>写调试器或内存扫描工具时，需要读取其他进程的内存。Linux 上通过 <code>/proc/&lt;pid&gt;/mem</code>。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> std::io::&#123;Read, Seek, SeekFrom&#125;;</span><br><span class="line"><span class="keyword">use</span> std::fs::File;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">ProcessMemory</span> &#123;</span><br><span class="line">    mem_file: File,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span> <span class="title class_">ProcessMemory</span> &#123;</span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">attach</span>(pid: <span class="type">u32</span>) <span class="punctuation">-&gt;</span> std::io::<span class="type">Result</span>&lt;<span class="keyword">Self</span>&gt; &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">mem_file</span> = File::<span class="title function_ invoke__">open</span>(<span class="built_in">format!</span>(<span class="string">&quot;/proc/&#123;&#125;/mem&quot;</span>, pid))?;</span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(<span class="keyword">Self</span> &#123; mem_file &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">read</span>&lt;T&gt;(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, addr: <span class="type">usize</span>) <span class="punctuation">-&gt;</span> std::io::<span class="type">Result</span>&lt;T&gt; &#123;</span><br><span class="line">        <span class="keyword">self</span>.mem_file.<span class="title function_ invoke__">seek</span>(SeekFrom::<span class="title function_ invoke__">Start</span>(addr <span class="keyword">as</span> <span class="type">u64</span>))?;</span><br><span class="line">        <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">buf</span> = std::mem::MaybeUninit::&lt;T&gt;::<span class="title function_ invoke__">uninit</span>();</span><br><span class="line">        <span class="keyword">unsafe</span> &#123;</span><br><span class="line">            std::ptr::<span class="title function_ invoke__">copy_nonoverlapping</span>(</span><br><span class="line">                &amp;<span class="keyword">mut</span> buf <span class="keyword">as</span> *<span class="keyword">mut</span> _ <span class="keyword">as</span> *<span class="keyword">mut</span> <span class="type">u8</span>,</span><br><span class="line">                buf.<span class="title function_ invoke__">as_mut_ptr</span>() <span class="keyword">as</span> *<span class="keyword">mut</span> <span class="type">u8</span>,</span><br><span class="line">                std::mem::size_of::&lt;T&gt;(),</span><br><span class="line">            );</span><br><span class="line">            <span class="keyword">self</span>.mem_file.<span class="title function_ invoke__">read_exact</span>(std::slice::<span class="title function_ invoke__">from_raw_parts_mut</span>(</span><br><span class="line">                buf.<span class="title function_ invoke__">as_mut_ptr</span>() <span class="keyword">as</span> *<span class="keyword">mut</span> <span class="type">u8</span>,</span><br><span class="line">                std::mem::size_of::&lt;T&gt;(),</span><br><span class="line">            ))?;</span><br><span class="line">            <span class="title function_ invoke__">Ok</span>(buf.<span class="title function_ invoke__">assume_init</span>())</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">read_bytes</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, addr: <span class="type">usize</span>, size: <span class="type">usize</span>) <span class="punctuation">-&gt;</span> std::io::<span class="type">Result</span>&lt;<span class="type">Vec</span>&lt;<span class="type">u8</span>&gt;&gt; &#123;</span><br><span class="line">        <span class="keyword">self</span>.mem_file.<span class="title function_ invoke__">seek</span>(SeekFrom::<span class="title function_ invoke__">Start</span>(addr <span class="keyword">as</span> <span class="type">u64</span>))?;</span><br><span class="line">        <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">buf</span> = <span class="built_in">vec!</span>[<span class="number">0u8</span>; size];</span><br><span class="line">        <span class="keyword">self</span>.mem_file.<span class="title function_ invoke__">read_exact</span>(&amp;<span class="keyword">mut</span> buf)?;</span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(buf)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="6-写一个简单的-PE-注入器"><a href="#6-写一个简单的-PE-注入器" class="headerlink" title="6. 写一个简单的 PE 注入器"></a>6. 写一个简单的 PE 注入器</h2><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> std::process::Child;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">inject_shellcode</span>(child: &amp;<span class="keyword">mut</span> Child, shellcode: &amp;[<span class="type">u8</span>]) <span class="punctuation">-&gt;</span> std::io::<span class="type">Result</span>&lt;()&gt; &#123;</span><br><span class="line">    <span class="keyword">use</span> libc::&#123;c_void, iovec, process_vm_writev&#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 在目标进程中分配内存</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">remote_mem</span> = <span class="keyword">unsafe</span> &#123;</span><br><span class="line">        libc::<span class="title function_ invoke__">mmap</span>(</span><br><span class="line">            std::ptr::<span class="title function_ invoke__">null_mut</span>(),</span><br><span class="line">            shellcode.<span class="title function_ invoke__">len</span>(),</span><br><span class="line">            libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC,</span><br><span class="line">            libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,</span><br><span class="line">            -<span class="number">1</span>,</span><br><span class="line">            <span class="number">0</span>,</span><br><span class="line">        )</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> remote_mem == libc::MAP_FAILED &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_ invoke__">Err</span>(std::io::Error::<span class="title function_ invoke__">last_os_error</span>());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 写入 shellcode</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">local_iov</span> = iovec &#123;</span><br><span class="line">        iov_base: shellcode.<span class="title function_ invoke__">as_ptr</span>() <span class="keyword">as</span> *<span class="keyword">mut</span> c_void,</span><br><span class="line">        iov_len: shellcode.<span class="title function_ invoke__">len</span>(),</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">remote_iov</span> = iovec &#123;</span><br><span class="line">        iov_base: remote_mem,</span><br><span class="line">        iov_len: shellcode.<span class="title function_ invoke__">len</span>(),</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">unsafe</span> &#123;</span><br><span class="line">        <span class="title function_ invoke__">process_vm_writev</span>(child.<span class="title function_ invoke__">id</span>() <span class="keyword">as</span> <span class="type">i32</span>, &amp;local_iov, <span class="number">1</span>, &amp;remote_iov, <span class="number">1</span>, <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 创建远程线程执行 shellcode</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="title function_ invoke__">Ok</span>(())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="7-性能优化"><a href="#7-性能优化" class="headerlink" title="7. 性能优化"></a>7. 性能优化</h2><p>写安全工具时经常要处理大量数据（fuzz 输出、网络包、磁盘文件）。Rust 的性能优化点：</p><h3 id="7-1-零拷贝"><a href="#7-1-零拷贝" class="headerlink" title="7.1 零拷贝"></a>7.1 零拷贝</h3><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 不好：每次都分配新 Vec</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">parse_lines_slow</span>(data: &amp;[<span class="type">u8</span>]) <span class="punctuation">-&gt;</span> <span class="type">Vec</span>&lt;&amp;<span class="type">str</span>&gt; &#123;</span><br><span class="line">    data.<span class="title function_ invoke__">split</span>(|&amp;b| b == <span class="string">b&#x27;<span class="char escape_">\n</span>&#x27;</span>)</span><br><span class="line">        .<span class="title function_ invoke__">map</span>(|line| std::<span class="type">str</span>::<span class="title function_ invoke__">from_utf8</span>(line).<span class="title function_ invoke__">unwrap</span>())</span><br><span class="line">        .<span class="title function_ invoke__">collect</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 好：返回引用，不分配</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">parse_lines_fast</span>(data: &amp;[<span class="type">u8</span>]) <span class="punctuation">-&gt;</span> <span class="keyword">impl</span> <span class="title class_">Iterator</span>&lt;Item = &amp;<span class="type">str</span>&gt; &#123;</span><br><span class="line">    data.<span class="title function_ invoke__">split</span>(|&amp;b| b == <span class="string">b&#x27;<span class="char escape_">\n</span>&#x27;</span>)</span><br><span class="line">        .<span class="title function_ invoke__">filter_map</span>(|line| std::<span class="type">str</span>::<span class="title function_ invoke__">from_utf8</span>(line).<span class="title function_ invoke__">ok</span>())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-2-并行处理"><a href="#7-2-并行处理" class="headerlink" title="7.2 并行处理"></a>7.2 并行处理</h3><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> rayon::prelude::*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">analyze_files</span>(files: <span class="type">Vec</span>&lt;PathBuf&gt;) <span class="punctuation">-&gt;</span> <span class="type">Vec</span>&lt;AnalysisResult&gt; &#123;</span><br><span class="line">    files.<span class="title function_ invoke__">par_iter</span>()  <span class="comment">// 自动并行</span></span><br><span class="line">        .<span class="title function_ invoke__">map</span>(|path| <span class="title function_ invoke__">analyze_single_file</span>(path))</span><br><span class="line">        .<span class="title function_ invoke__">collect</span>()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-3-避免-clone"><a href="#7-3-避免-clone" class="headerlink" title="7.3 避免 clone"></a>7.3 避免 clone</h3><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 不好</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">process</span>(data: <span class="type">Vec</span>&lt;<span class="type">u8</span>&gt;) &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">copy</span> = data.<span class="title function_ invoke__">clone</span>();</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 好：传引用</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">process</span>(data: &amp;[<span class="type">u8</span>]) &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="8-工程实践"><a href="#8-工程实践" class="headerlink" title="8. 工程实践"></a>8. 工程实践</h2><h3 id="8-1-错误处理"><a href="#8-1-错误处理" class="headerlink" title="8.1 错误处理"></a>8.1 错误处理</h3><p>安全工具不能随便 panic。用 <code>thiserror</code> 定义错误类型：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#[derive(Debug, thiserror::Error)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">ToolError</span> &#123;</span><br><span class="line">    <span class="meta">#[error(<span class="string">&quot;ELF 解析失败: &#123;0&#125;&quot;</span>)]</span></span><br><span class="line">    <span class="title function_ invoke__">ElfParse</span>(<span class="meta">#[from]</span> goblin::error::Error),</span><br><span class="line">    </span><br><span class="line">    <span class="meta">#[error(<span class="string">&quot;IO 错误: &#123;0&#125;&quot;</span>)]</span></span><br><span class="line">    <span class="title function_ invoke__">Io</span>(<span class="meta">#[from]</span> std::io::Error),</span><br><span class="line">    </span><br><span class="line">    <span class="meta">#[error(<span class="string">&quot;内存映射失败&quot;</span>)]</span></span><br><span class="line">    MmapFailed,</span><br><span class="line">    </span><br><span class="line">    <span class="meta">#[error(<span class="string">&quot;不支持的架构: &#123;0&#125;&quot;</span>)]</span></span><br><span class="line">    <span class="title function_ invoke__">UnsupportedArch</span>(<span class="type">String</span>),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">type</span> <span class="title class_">Result</span>&lt;T&gt; = std::result::<span class="type">Result</span>&lt;T, ToolError&gt;;</span><br></pre></td></tr></table></figure><h3 id="8-2-CLI-工具"><a href="#8-2-CLI-工具" class="headerlink" title="8.2 CLI 工具"></a>8.2 CLI 工具</h3><p>用 <code>clap</code> 写命令行工具：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> clap::&#123;Parser, Subcommand&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Parser)]</span></span><br><span class="line"><span class="meta">#[command(name = <span class="string">&quot;mytool&quot;</span>)]</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Cli</span> &#123;</span><br><span class="line">    <span class="meta">#[command(subcommand)]</span></span><br><span class="line">    command: Commands,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Subcommand)]</span></span><br><span class="line"><span class="keyword">enum</span> <span class="title class_">Commands</span> &#123;</span><br><span class="line">    <span class="comment">/// 分析 ELF 文件</span></span><br><span class="line">    Analyze &#123;</span><br><span class="line">        <span class="comment">/// 目标文件路径</span></span><br><span class="line">        file: <span class="type">String</span>,</span><br><span class="line">        <span class="comment">/// 详细输出</span></span><br><span class="line">        <span class="meta">#[arg(short, long)]</span></span><br><span class="line">        verbose: <span class="type">bool</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">/// 注入 shellcode</span></span><br><span class="line">    Inject &#123;</span><br><span class="line">        <span class="comment">/// 目标 PID</span></span><br><span class="line">        pid: <span class="type">u32</span>,</span><br><span class="line">        <span class="comment">/// shellcode 文件</span></span><br><span class="line">        shellcode: <span class="type">String</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Rust 在安全工具开发中的定位很清晰：<strong>比 Python 快，比 C 安全，比 Go 更底层。</strong> 写 CTF exp、逆向框架、fuzzer、调试器，Rust 都是很好的选择。</p><p>入门路径建议：</p><ol><li>先学 Rust 基础语法（所有权、借用、生命周期）</li><li>写几个小工具：ELF parser、PE parser、简单的 disassembler</li><li>学习 FFI 和 unsafe 的正确用法</li><li>读优秀的 Rust 安全工具源码：goblin、capstone-rs、unicorn-rs</li></ol><p>Rust 的学习曲线确实陡，但一旦过了那个坎，你会发现写底层代码变得前所未有的安心——编译器帮你把所有内存安全问题都挡住了，你只需要专注于逻辑本身。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/16/rust-systems-programming/</id>
    <link href="https://tsvmv.github.io/2026/09/16/rust-systems-programming/"/>
    <published>2026-09-16T05:00:00.000Z</published>
    <summary>
      <![CDATA[<p>很多人对 Rust 的印象停留在”安全、 borrow checker、学习曲线陡峭”。但在安全工具开发领域，Rust 真正的价值是：<strong>它让你能写 C 级别的底层代码，同时不 segfault。</strong></p>
<p>你写过 pwntools 就知道 Python 做性能敏感的事情有多痛苦——解析大二进制文件、批量 fuzz、hook 系统调用，Python 永远慢半拍。Rust 刚好填了这个坑：编译成原生机器码，零成本抽象，内存安全，还有成熟的 FFI 可以和 C 库互操作。</p>
<p>这篇文章不讲 Rust 语法基础，直接讲写安全工具时最需要的东西：unsafe、FFI、进程注入、内存操作。</p>]]>
    </summary>
    <title>Rust 系统编程入门—从 unsafe 到安全工具开发</title>
    <updated>2026-11-04T11:56:13.948Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="CTF" scheme="https://tsvmv.github.io/categories/CTF/"/>
    <category term="CTF" scheme="https://tsvmv.github.io/tags/CTF/"/>
    <category term="密码学" scheme="https://tsvmv.github.io/tags/%E5%AF%86%E7%A0%81%E5%AD%A6/"/>
    <category term="数论" scheme="https://tsvmv.github.io/tags/%E6%95%B0%E8%AE%BA/"/>
    <content>
      <![CDATA[<p>椭圆曲线密码学（ECC）是现代密码学的基石之一。Bitcoin 用 secp256k1，TLS 用 secp256r1，Signal 用 X25519，WhatsApp 用 Curve25519。CTF 密码学题里，ECC 更是常客——从小白友好的非ce点攻击，到高难度的 SMART 攻击、无效曲线攻击、MOV 攻击，变种繁多。</p><p>这篇文章从零开始讲 ECC 的数学原理，然后落地到 CTF 中常见的攻击手法和解题套路。</p><span id="more"></span><h2 id="1-什么是椭圆曲线"><a href="#1-什么是椭圆曲线" class="headerlink" title="1. 什么是椭圆曲线"></a>1. 什么是椭圆曲线</h2><p>在密码学中，椭圆曲线定义在有限域上，形如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">y² = x³ + ax + b  (mod p)</span><br></pre></td></tr></table></figure><p>其中 p 是一个大质数，a、b 满足 <code>4a³ + 27b² ≠ 0 (mod p)</code>（防止曲线有奇点）。</p><p>这条曲线上的所有点 (x, y)，加上一个无穷远点 O（相当于”零元”），构成一个<strong>阿贝尔群</strong>。群运算定义为”点加法”。</p><h2 id="2-点加法的几何意义"><a href="#2-点加法的几何意义" class="headerlink" title="2. 点加法的几何意义"></a>2. 点加法的几何意义</h2><p>给定曲线上两点 P 和 Q，P+Q 的几何做法：</p><ol><li>过 P 和 Q 画一条直线</li><li>这条直线和曲线交于第三个点 R’</li><li>把 R’ 关于 x 轴翻折，得到 R &#x3D; P + Q</li></ol><p>如果 P &#x3D; Q（两倍点），那直线换成 P 点的切线。</p><p>代数公式（a ≠ 0 的一般情况）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">如果 P ≠ Q：</span><br><span class="line">  λ = (y_Q - y_P) / (x_Q - x_P)  (mod p)</span><br><span class="line">如果 P = Q：</span><br><span class="line">  λ = (3x_P² + a) / (2y_P)       (mod p)</span><br><span class="line">  </span><br><span class="line">x_R = λ² - x_P - x_Q  (mod p)</span><br><span class="line">y_R = λ(x_P - x_R) - y_P  (mod p)</span><br></pre></td></tr></table></figure><p>注意除法是模逆元——分母必须和 p 互质。如果分母为 0，结果就是无穷远点 O。</p><h2 id="3-标量乘法与离散对数"><a href="#3-标量乘法与离散对数" class="headerlink" title="3. 标量乘法与离散对数"></a>3. 标量乘法与离散对数</h2><p>定义 kP &#x3D; P + P + … + P（k 次）。这就是椭圆曲线上的”乘法”。</p><ul><li><strong>正向计算</strong>：给 k 和 P，算 kP。用 double-and-add 算法，O(log k) 次点加，很快。</li><li><strong>逆向计算</strong>：给 P 和 Q &#x3D; kP，求 k。这就是<strong>椭圆曲线离散对数问题（ECDLP）</strong>。</li></ul><p>ECDLP 目前没有已知的多项式时间经典算法。最著名的暴力方法是 Pollard’s rho，复杂度 O(√n)，n 是群的阶。对于 256 位曲线，√n ≈ 2¹²⁸，计算上不可行。这就是 ECC 安全性的基础。</p><h2 id="4-CTF-中-ECC-的常见曲线"><a href="#4-CTF-中-ECC-的常见曲线" class="headerlink" title="4. CTF 中 ECC 的常见曲线"></a>4. CTF 中 ECC 的常见曲线</h2><table><thead><tr><th>曲线</th><th>参数</th><th>特点</th></tr></thead><tbody><tr><td>secp256k1</td><td>Bitcoin 用</td><td>a&#x3D;0, b&#x3D;7</td></tr><tr><td>secp256r1 (NIST P-256)</td><td>TLS 用</td><td>NIST 标准</td></tr><tr><td>Curve25519</td><td>X25519 用</td><td>Montgomery 形式</td></tr><tr><td>secp112r1 &#x2F; secp128r1</td><td>CTF 出题常用</td><td>小曲线，可暴力</td></tr></tbody></table><p>CTF 出题人喜欢用小曲线，因为群的阶小，方便演示攻击。比如 n &#x3D; 10007 这种规模，直接暴力枚举 k 就行了。</p><h2 id="5-基础实现（Python）"><a href="#5-基础实现（Python）" class="headerlink" title="5. 基础实现（Python）"></a>5. 基础实现（Python）</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Curve</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, p, a, b</span>):</span><br><span class="line">        <span class="variable language_">self</span>.p = p</span><br><span class="line">        <span class="variable language_">self</span>.a = a</span><br><span class="line">        <span class="variable language_">self</span>.b = b</span><br><span class="line">        <span class="variable language_">self</span>.O = (<span class="literal">None</span>, <span class="literal">None</span>)  <span class="comment"># 无穷远点</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">add</span>(<span class="params">self, P, Q</span>):</span><br><span class="line">        <span class="keyword">if</span> P == <span class="variable language_">self</span>.O:</span><br><span class="line">            <span class="keyword">return</span> Q</span><br><span class="line">        <span class="keyword">if</span> Q == <span class="variable language_">self</span>.O:</span><br><span class="line">            <span class="keyword">return</span> P</span><br><span class="line">        </span><br><span class="line">        x1, y1 = P</span><br><span class="line">        x2, y2 = Q</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> x1 == x2 <span class="keyword">and</span> (y1 + y2) % <span class="variable language_">self</span>.p == <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="variable language_">self</span>.O</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> P == Q:</span><br><span class="line">            lam = (<span class="number">3</span> * x1 * x1 + <span class="variable language_">self</span>.a) * <span class="built_in">pow</span>(<span class="number">2</span> * y1, -<span class="number">1</span>, <span class="variable language_">self</span>.p) % <span class="variable language_">self</span>.p</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            lam = (y2 - y1) * <span class="built_in">pow</span>(x2 - x1, -<span class="number">1</span>, <span class="variable language_">self</span>.p) % <span class="variable language_">self</span>.p</span><br><span class="line">        </span><br><span class="line">        x3 = (lam * lam - x1 - x2) % <span class="variable language_">self</span>.p</span><br><span class="line">        y3 = (lam * (x1 - x3) - y1) % <span class="variable language_">self</span>.p</span><br><span class="line">        <span class="keyword">return</span> (x3, y3)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">mul</span>(<span class="params">self, k, P</span>):</span><br><span class="line">        result = <span class="variable language_">self</span>.O</span><br><span class="line">        addend = P</span><br><span class="line">        <span class="keyword">while</span> k &gt; <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">if</span> k &amp; <span class="number">1</span>:</span><br><span class="line">                result = <span class="variable language_">self</span>.add(result, addend)</span><br><span class="line">            addend = <span class="variable language_">self</span>.add(addend, addend)</span><br><span class="line">            k &gt;&gt;= <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> result</span><br></pre></td></tr></table></figure><h2 id="6-攻击一：小阶群暴力枚举"><a href="#6-攻击一：小阶群暴力枚举" class="headerlink" title="6. 攻击一：小阶群暴力枚举"></a>6. 攻击一：小阶群暴力枚举</h2><p>最基础的攻击。如果曲线的阶 n 很小（比如 n &lt; 10⁶），直接枚举 k 就行了。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 已知 P 和 Q = kP，求 k</span></span><br><span class="line"><span class="keyword">for</span> k <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, n):</span><br><span class="line">    <span class="keyword">if</span> curve.mul(k, P) == Q:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;k = <span class="subst">&#123;k&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">break</span></span><br></pre></td></tr></table></figure><p>CTF 题里如果给了一个只有几百个点的曲线，别想太多，直接暴力。</p><h2 id="7-攻击二：Pohlig-Hellman-算法"><a href="#7-攻击二：Pohlig-Hellman-算法" class="headerlink" title="7. 攻击二：Pohlig-Hellman 算法"></a>7. 攻击二：Pohlig-Hellman 算法</h2><p>如果群的阶 n 可以分解为小素数因子的乘积（n &#x3D; q₁ × q₂ × … × qₖ），可以用 Pohlig-Hellman 算法把大的离散对数问题分解为每个素因子上的小问题，最后用中国剩余定理（CRT）合并。</p><p><strong>前提</strong>：n 的最大素因子要小。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> sympy <span class="keyword">import</span> factorint</span><br><span class="line"><span class="keyword">from</span> CRT <span class="keyword">import</span> crt  <span class="comment"># 中国剩余定理</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pohlig_hellman</span>(<span class="params">curve, P, Q, n</span>):</span><br><span class="line">    factors = factorint(n)</span><br><span class="line">    residues = []</span><br><span class="line">    moduli = []</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> q, e <span class="keyword">in</span> factors.items():</span><br><span class="line">        <span class="comment"># 在子群中求解（简化版，实际要处理 q^e）</span></span><br><span class="line">        qj = q</span><br><span class="line">        Pj = curve.mul(n // qj, P)</span><br><span class="line">        Qj = curve.mul(n // qj, Q)</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 在 qj 阶子群中暴力</span></span><br><span class="line">        k_j = <span class="number">0</span></span><br><span class="line">        tmp = curve.O</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(qj):</span><br><span class="line">            <span class="keyword">if</span> tmp == Qj:</span><br><span class="line">                k_j = i</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            tmp = curve.add(tmp, Pj)</span><br><span class="line">        </span><br><span class="line">        residues.append(k_j)</span><br><span class="line">        moduli.append(qj)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> crt(residues, moduli)</span><br></pre></td></tr></table></figure><p><strong>防御</strong>：选择 n 有一个大素因子（至少 200 位以上）的曲线。secp256k1 等标准曲线都满足这个条件。</p><h2 id="8-攻击三：无效曲线攻击（Invalid-Curve-Attack）"><a href="#8-攻击三：无效曲线攻击（Invalid-Curve-Attack）" class="headerlink" title="8. 攻击三：无效曲线攻击（Invalid Curve Attack）"></a>8. 攻击三：无效曲线攻击（Invalid Curve Attack）</h2><p>这是 ECC 最经典的攻击之一。核心思想：<strong>如果你能让受害者在一条”坏曲线”上做点乘，而坏曲线上的离散对数是好解的，你就能反推出私钥。</strong></p><p>攻击流程：</p><ol><li>攻击者构造一条椭圆曲线 E’：y² &#x3D; x³ + ax + b’（换一个 b’）</li><li>受害者以为在正常曲线 E 上工作，但实际在 E’ 上计算了点乘</li><li>E’ 的阶可以被选择成一个光滑数（smooth number），Pohlig-Hellman 直接破解</li><li>攻击者反推出私钥</li></ol><p>关键点：点 (x, y) 是否在曲线上只取决于 y² &#x3D;&#x3D; x³ + ax + b。如果服务端<strong>不验证点是否在曲线上</strong>，攻击者就可以随意构造在另一条曲线上的点发过去。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 攻击演示</span></span><br><span class="line"><span class="comment"># 服务端用的曲线：y² = x³ + 7 (secp256k1)</span></span><br><span class="line"><span class="comment"># 攻击者构造新曲线：y² = x³ + b&#x27;</span></span><br><span class="line"><span class="comment"># 选一个点 P&#x27; 在新曲线上</span></span><br><span class="line"><span class="comment"># 发 P&#x27; 给服务端，服务端算 k*P&#x27;</span></span><br><span class="line"><span class="comment"># 因为 P&#x27; 不在原曲线上，服务端的点加公式仍然适用（代数上一样）</span></span><br><span class="line"><span class="comment"># 但 P&#x27; 所在的新曲线阶是光滑的，用 Pohlig-Hellman 破解</span></span><br><span class="line"></span><br><span class="line">b_prime = <span class="number">12345</span>  <span class="comment"># 攻击者选的 b&#x27;</span></span><br><span class="line">attack_curve = Curve(p, a, b_prime)</span><br><span class="line">P_prime = (x, y)  <span class="comment"># 攻击者构造的点，满足 y² = x³ + a*x + b_prime (mod p)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 服务端算 k*P_prime，返回 Q&#x27;</span></span><br><span class="line"><span class="comment"># 攻击者在 attack_curve 上用 Pohlig-Hellman 求 k</span></span><br></pre></td></tr></table></figure><p><strong>防御</strong>：服务端必须验证收到的点是否在曲线上：<code>assert y² == x³ + ax + b (mod p)</code>。这一行代码就能挡住整个攻击。</p><h2 id="9-攻击四：Smart’s-Attack（异常曲线攻击）"><a href="#9-攻击四：Smart’s-Attack（异常曲线攻击）" class="headerlink" title="9. 攻击四：Smart’s Attack（异常曲线攻击）"></a>9. 攻击四：Smart’s Attack（异常曲线攻击）</h2><p>Smart’s Attack 针对的是<strong>超奇异曲线</strong>或<strong>异常曲线</strong>（anomalous curve）——即群的阶等于 p 的曲线。</p><p>对于异常曲线，离散对数可以在多项式时间内求解（Smart 1997, Satch 1997）。核心思路是利用曲线的 p 阶特性，把离散对数问题降级为整数环上的问题。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 异常曲线：#E(F_p) = p</span></span><br><span class="line"><span class="comment"># 此时 E(F_p) 同构于 F_p 的加法群</span></span><br><span class="line"><span class="comment"># 离散对数问题变得平凡</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检测方法：计算 #E(F_p)，看是否等于 p</span></span><br></pre></td></tr></table></figure><p><strong>防御</strong>：使用经过标准验证的曲线参数（NIST、SECG 推荐的曲线），不要自己随便选参数。</p><h2 id="10-攻击五：MOV-攻击"><a href="#10-攻击五：MOV-攻击" class="headerlink" title="10. 攻击五：MOV 攻击"></a>10. 攻击五：MOV 攻击</h2><p>MOV 攻击把椭圆曲线上的离散对数问题<strong>规约</strong>到有限域乘法群中的离散对数问题，然后用 Index Calculus 算法求解。</p><p>当曲线的嵌入度（embedding degree）很小的时候，MOV 攻击有效。嵌入度 k 是最小的正整数使得 pᵏ ≡ 1 (mod n)，其中 n 是曲线的阶。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">MOV 规约：</span><br><span class="line">1. 找一个 P 的 n 阶点，且 P 在 F_&#123;p^k&#125; 上</span><br><span class="line">2. 用 Weil/Tate 配对把 (P, kP) 映射到 F_&#123;p^k&#125; 中的两个元素</span><br><span class="line">3. 在 F_&#123;p^k&#125; 中求解普通离散对数</span><br></pre></td></tr></table></figure><p>如果 k 很小（比如 k&#x3D;2 或 3），F_{p^k} 的规模不大，Index Calculus 可以有效求解。</p><p><strong>防御</strong>：选择嵌入度大的曲线。标准曲线都经过验证，嵌入度足够大。</p><h2 id="11-ECDH-密钥交换"><a href="#11-ECDH-密钥交换" class="headerlink" title="11. ECDH 密钥交换"></a>11. ECDH 密钥交换</h2><p>ECC 最常见的应用是 ECDH（椭圆曲线 Diffie-Hellman）密钥交换：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Alice 选私钥 a，公钥 A = aG</span><br><span class="line">Bob   选私钥 b，公钥 B = bG</span><br><span class="line"></span><br><span class="line">Alice 算 S = aB = abG</span><br><span class="line">Bob   算 S = bA = abG</span><br><span class="line"></span><br><span class="line">双方得到相同的共享密钥 S</span><br></pre></td></tr></table></figure><p>中间人攻击：如果攻击者能替换公钥，就能冒充双方。这就是为什么需要证书认证（TLS 的做法）。</p><h2 id="12-CTF-实战模板"><a href="#12-CTF-实战模板" class="headerlink" title="12. CTF 实战模板"></a>12. CTF 实战模板</h2><p>遇到 ECC 题目的一般分析流程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">1. 看曲线参数：p、a、b、G、n</span><br><span class="line">2. 检查 n 是否可分解（factorint）</span><br><span class="line">   - 如果 n 光滑 → Pohlig-Hellman</span><br><span class="line">   - 如果 n == p → Smart&#x27;s Attack</span><br><span class="line">3. 检查是否验证点在曲线上</span><br><span class="line">   - 不验证 → Invalid Curve Attack</span><br><span class="line">4. 检查嵌入度</span><br><span class="line">   - 嵌入度小 → MOV Attack</span><br><span class="line">5. 以上都不行</span><br><span class="line">   - 标准曲线 → 没救了，找别的漏洞</span><br><span class="line">   - 小曲线 → 暴力枚举</span><br></pre></td></tr></table></figure><p>常用工具：</p><ul><li><strong>SageMath</strong>：内置 ECC 运算、Pohlig-Hellman、MOV 攻击</li><li><strong>ecc.py</strong>：自己写的轻量级 ECC 库</li><li><strong>Racket &#x2F; Python</strong>：纯实现</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ECC 是 CTF 密码学的核心考点。理解点加法的几何意义和代数公式是基础，掌握五种常见攻击（暴力、Pohlig-Hellman、Invalid Curve、Smart、MOV）就能应付绝大多数 ECC 题目。</p><p>关键提醒：</p><ol><li><strong>永远用标准曲线</strong>——自己选参数大概率有后门</li><li><strong>永远验证点在曲线上</strong>——一行代码挡住 Invalid Curve</li><li><strong>n 必须有大素因子</strong>——挡住 Pohlig-Hellman</li><li><strong>嵌入度必须大</strong>——挡住 MOV</li></ol><p>写 ECC 题的时候，先拿 SageMath 算一遍曲线参数，看看有没有明显弱点，再决定攻击方向。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/16/ecc-cryptography-guide/</id>
    <link href="https://tsvmv.github.io/2026/09/16/ecc-cryptography-guide/"/>
    <published>2026-09-16T04:00:00.000Z</published>
    <summary>
      <![CDATA[<p>椭圆曲线密码学（ECC）是现代密码学的基石之一。Bitcoin 用 secp256k1，TLS 用 secp256r1，Signal 用 X25519，WhatsApp 用 Curve25519。CTF 密码学题里，ECC 更是常客——从小白友好的非ce点攻击，到高难度的 SMART 攻击、无效曲线攻击、MOV 攻击，变种繁多。</p>
<p>这篇文章从零开始讲 ECC 的数学原理，然后落地到 CTF 中常见的攻击手法和解题套路。</p>]]>
    </summary>
    <title>椭圆曲线密码学(ECC)详解—从数学原理到 CTF 密码学实战</title>
    <updated>2026-11-04T11:56:13.946Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="CTF" scheme="https://tsvmv.github.io/categories/CTF/"/>
    <category term="CTF" scheme="https://tsvmv.github.io/tags/CTF/"/>
    <category term="算法" scheme="https://tsvmv.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <category term="字符串" scheme="https://tsvmv.github.io/tags/%E5%AD%97%E7%AC%A6%E4%B8%B2/"/>
    <content>
      <![CDATA[<p>后缀自动机（Suffix Automaton，简称 SAM）是处理字符串问题最强大的数据结构之一。它能在 O(n) 时间内构造出一个状态数 O(n) 的自动机，之后可以在线性时间内回答大量字符串相关的问题：不同子串个数、最长重复子串、出现次数最多的子串、子串出现次数、最小表示法……在 CTF 的 Misc 和逆向题中，SAM 经常用来做字符串匹配、密码分析、文本去重等任务。</p><p>很多人觉得 SAM 难，其实核心就两条：<strong>endpos 等价类</strong>和<strong>后缀链接</strong>。把这两个概念搞懂了，剩下的都是工程细节。</p><span id="more"></span><h2 id="1-为什么需要-SAM"><a href="#1-为什么需要-SAM" class="headerlink" title="1. 为什么需要 SAM"></a>1. 为什么需要 SAM</h2><p>先想一个朴素问题：给一个长度为 n 的字符串，统计它有多少个不同的子串。</p><p>最暴力的做法是枚举所有起点和终点，插入一个 set，O(n²) 个串，每个串 O(n) 比较，总 O(n³)。优化一下用哈希，O(n²)。</p><p>但如果 n 是 10⁵ 呢？O(n²) 直接 TLE。</p><p>SAM 的做法是：构造 SAM 本身 O(n)，构造完之后，<strong>不同子串个数 &#x3D; 所有状态的 (len[v] - len[link[v]]) 之和</strong>。O(n) 构造，O(n) 统计。这就是 SAM 的威力。</p><h2 id="2-endpos-等价类"><a href="#2-endpos-等价类" class="headerlink" title="2. endpos 等价类"></a>2. endpos 等价类</h2><p>定义：对于字符串 S 的一个子串 t，endpos(t) 表示 t 在 S 中所有出现位置的右端点集合。</p><p>举个例子，S &#x3D; “abcabx”：</p><ul><li>子串 “ab” 出现在位置 1 和 4，endpos &#x3D; {1, 4}（0-indexed 的话是 {1, 4}）</li><li>子串 “b” 出现在位置 2 和 5，endpos &#x3D; {2, 5}</li><li>子串 “bc” 出现在位置 3，endpos &#x3D; {3}</li></ul><p>关键观察：<strong>如果两个子串的 endpos 相同，那么它们要么互为后缀关系，要么一个不出现另一个就不出现。</strong> 换句话说，同一个 endpos 类里的所有子串，形成了一条从短到长的后缀链。</p><p>SAM 的每个状态代表一个 endpos 等价类。每个状态 v 记录：</p><ul><li><code>len[v]</code>：这个类中最长的子串长度</li><li><code>link[v]</code>：后缀链接，指向另一个 endpos 类（代表更短的后缀）</li><li><code>next[v][c]</code>：转移，加一个字符 c 后跳到哪个状态</li></ul><p>同一个状态 v 中，所有子串的长度范围是 (len[link[v]], len[v]]，且它们都是最长子串的后缀。所以这个状态代表的子串数量是 <code>len[v] - len[link[v]]</code>。</p><h2 id="3-构造过程：在线增量构造"><a href="#3-构造过程：在线增量构造" class="headerlink" title="3. 构造过程：在线增量构造"></a>3. 构造过程：在线增量构造</h2><p>SAM 的构造是<strong>在线</strong>的——逐个字符加入，每次加入后维护好自动机。</p><p>设当前已经有一个 SAM，现在要加字符 c。我们维护一个 <code>last</code> 指针，指向整个字符串对应的状态。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">1. 创建新状态 cur，len[cur] = len[last] + 1</span><br><span class="line">2. 从 last 开始沿后缀链接向上跳，</span><br><span class="line">   对每个没有 c 转移的状态 p，设置 next[p][c] = cur</span><br><span class="line">3. 找到第一个有 c 转移的状态 p，设 q = next[p][c]</span><br><span class="line">4. 如果不存在这样的 p：</span><br><span class="line">   - link[cur] = 根节点(0)，结束</span><br><span class="line">5. 如果 len[q] == len[p] + 1：</span><br><span class="line">   - link[cur] = q，结束</span><br><span class="line">6. 否则需要拆分：</span><br><span class="line">   - 创建 clone 状态 clone，复制 q 的转移和 link</span><br><span class="line">   - len[clone] = len[p] + 1</span><br><span class="line">   - 从 p 开始沿后缀链接，把所有指向 q 的 c 转移改为指向 clone</span><br><span class="line">   - link[q] = clone，link[cur] = clone</span><br><span class="line">7. last = cur</span><br></pre></td></tr></table></figure><p>上面的第 6 步是 SAM 最 tricky 的部分。为什么要拆？因为 q 这个状态里混了两类子串：一类长度 ≤ len[p]+1，它们的 endpos 和 clone 一样；另一类长度 &gt; len[p]+1，它们的 endpos 不同。拆分就是把这两类分开。</p><h2 id="4-完整实现（Python）"><a href="#4-完整实现（Python）" class="headerlink" title="4. 完整实现（Python）"></a>4. 完整实现（Python）</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SAM</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>.<span class="built_in">len</span> = [<span class="number">0</span>]        <span class="comment"># 每个状态的最长子串长度</span></span><br><span class="line">        <span class="variable language_">self</span>.link = [-<span class="number">1</span>]      <span class="comment"># 后缀链接</span></span><br><span class="line">        <span class="variable language_">self</span>.<span class="built_in">next</span> = [&#123;&#125;]      <span class="comment"># 转移表</span></span><br><span class="line">        <span class="variable language_">self</span>.last = <span class="number">0</span>         <span class="comment"># 当前末尾状态</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">extend</span>(<span class="params">self, c</span>):</span><br><span class="line">        <span class="comment"># c 是字符，可以是单个字符或整数</span></span><br><span class="line">        cur = <span class="built_in">len</span>(<span class="variable language_">self</span>.<span class="built_in">len</span>)</span><br><span class="line">        <span class="variable language_">self</span>.<span class="built_in">len</span>.append(<span class="variable language_">self</span>.<span class="built_in">len</span>[<span class="variable language_">self</span>.last] + <span class="number">1</span>)</span><br><span class="line">        <span class="variable language_">self</span>.link.append(<span class="number">0</span>)</span><br><span class="line">        <span class="variable language_">self</span>.<span class="built_in">next</span>.append(&#123;&#125;)</span><br><span class="line">        <span class="variable language_">self</span>.cnt.append(<span class="number">1</span>)   <span class="comment"># 每个新状态至少出现一次</span></span><br><span class="line">        </span><br><span class="line">        p = <span class="variable language_">self</span>.last</span><br><span class="line">        <span class="keyword">while</span> p != -<span class="number">1</span> <span class="keyword">and</span> c <span class="keyword">not</span> <span class="keyword">in</span> <span class="variable language_">self</span>.<span class="built_in">next</span>[p]:</span><br><span class="line">            <span class="variable language_">self</span>.<span class="built_in">next</span>[p][c] = cur</span><br><span class="line">            p = <span class="variable language_">self</span>.link[p]</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> p == -<span class="number">1</span>:</span><br><span class="line">            <span class="variable language_">self</span>.link[cur] = <span class="number">0</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            q = <span class="variable language_">self</span>.<span class="built_in">next</span>[p][c]</span><br><span class="line">            <span class="keyword">if</span> <span class="variable language_">self</span>.<span class="built_in">len</span>[p] + <span class="number">1</span> == <span class="variable language_">self</span>.<span class="built_in">len</span>[q]:</span><br><span class="line">                <span class="variable language_">self</span>.link[cur] = q</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                clone = <span class="built_in">len</span>(<span class="variable language_">self</span>.<span class="built_in">len</span>)</span><br><span class="line">                <span class="variable language_">self</span>.<span class="built_in">len</span>.append(<span class="variable language_">self</span>.<span class="built_in">len</span>[p] + <span class="number">1</span>)</span><br><span class="line">                <span class="variable language_">self</span>.link.append(<span class="variable language_">self</span>.link[q])</span><br><span class="line">                <span class="variable language_">self</span>.<span class="built_in">next</span>.append(<span class="variable language_">self</span>.<span class="built_in">next</span>[q].copy())</span><br><span class="line">                <span class="variable language_">self</span>.cnt.append(<span class="number">0</span>)  <span class="comment"># clone 不是新前缀，cnt 初始为 0</span></span><br><span class="line">                </span><br><span class="line">                <span class="keyword">while</span> p != -<span class="number">1</span> <span class="keyword">and</span> <span class="variable language_">self</span>.<span class="built_in">next</span>[p].get(c, -<span class="number">1</span>) == q:</span><br><span class="line">                    <span class="variable language_">self</span>.<span class="built_in">next</span>[p][c] = clone</span><br><span class="line">                    p = <span class="variable language_">self</span>.link[p]</span><br><span class="line">                <span class="variable language_">self</span>.link[q] = clone</span><br><span class="line">                <span class="variable language_">self</span>.link[cur] = clone</span><br><span class="line">        </span><br><span class="line">        <span class="variable language_">self</span>.last = cur</span><br><span class="line">    </span><br><span class="line"><span class="meta">    @classmethod</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">build</span>(<span class="params">cls, s</span>):</span><br><span class="line">        sam = cls()</span><br><span class="line">        <span class="keyword">for</span> c <span class="keyword">in</span> s:</span><br><span class="line">            sam.extend(c)</span><br><span class="line">        <span class="keyword">return</span> sam</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">count_distinct_substrings</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;不同子串个数&quot;&quot;&quot;</span></span><br><span class="line">        total = <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> v <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="built_in">len</span>(<span class="variable language_">self</span>.<span class="built_in">len</span>)):</span><br><span class="line">            total += <span class="variable language_">self</span>.<span class="built_in">len</span>[v] - <span class="variable language_">self</span>.<span class="built_in">len</span>[<span class="variable language_">self</span>.link[v]]</span><br><span class="line">        <span class="keyword">return</span> total</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_occurrence_count</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;统计每个状态对应子串的出现次数&quot;&quot;&quot;</span></span><br><span class="line">        <span class="comment"># 按 len 降序排列</span></span><br><span class="line">        order = <span class="built_in">sorted</span>(<span class="built_in">range</span>(<span class="built_in">len</span>(<span class="variable language_">self</span>.<span class="built_in">len</span>)), key=<span class="keyword">lambda</span> x: -<span class="variable language_">self</span>.<span class="built_in">len</span>[x])</span><br><span class="line">        <span class="keyword">for</span> v <span class="keyword">in</span> order:</span><br><span class="line">            <span class="keyword">if</span> <span class="variable language_">self</span>.link[v] != -<span class="number">1</span>:</span><br><span class="line">                <span class="variable language_">self</span>.cnt[<span class="variable language_">self</span>.link[v]] += <span class="variable language_">self</span>.cnt[v]</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.cnt</span><br></pre></td></tr></table></figure><h2 id="5-CTF-实战：不同子串个数"><a href="#5-CTF-实战：不同子串个数" class="headerlink" title="5. CTF 实战：不同子串个数"></a>5. CTF 实战：不同子串个数</h2><p>最基础的应用。给一个超长字符串（比如题目里 dump 出来的一段日志或密文），问有多少个不同的子串。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">s = <span class="string">&quot;ababa&quot;</span> * <span class="number">100000</span></span><br><span class="line">sam = SAM.build(s)</span><br><span class="line"><span class="built_in">print</span>(sam.count_distinct_substrings())</span><br><span class="line"><span class="comment"># 直接 O(n) 搞定，n=500000 也不会炸</span></span><br></pre></td></tr></table></figure><h2 id="6-CTF-实战：出现次数最多的子串"><a href="#6-CTF-实战：出现次数最多的子串" class="headerlink" title="6. CTF 实战：出现次数最多的子串"></a>6. CTF 实战：出现次数最多的子串</h2><p>在密码分析题中，经常需要找出一段文本中出现频率最高的特定模式。SAM 可以帮你快速统计每个子串的出现次数。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">sam = SAM.build(text)</span><br><span class="line">cnt = sam.get_occurrence_count()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 找到出现次数最多的子串长度</span></span><br><span class="line">max_occur = <span class="built_in">max</span>(cnt)</span><br><span class="line"><span class="comment"># 遍历所有状态，找到对应长度的子串</span></span><br><span class="line"><span class="keyword">for</span> v <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="built_in">len</span>(sam.<span class="built_in">len</span>)):</span><br><span class="line">    <span class="keyword">if</span> cnt[v] == max_occur:</span><br><span class="line">        length_range = (sam.<span class="built_in">len</span>[sam.link[v]], sam.<span class="built_in">len</span>[v]]</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;状态 <span class="subst">&#123;v&#125;</span>: 长度 <span class="subst">&#123;length_range&#125;</span> 的子串出现 <span class="subst">&#123;max_occur&#125;</span> 次&quot;</span>)</span><br></pre></td></tr></table></figure><h2 id="7-CTF-实战：子串匹配"><a href="#7-CTF-实战：子串匹配" class="headerlink" title="7. CTF 实战：子串匹配"></a>7. CTF 实战：子串匹配</h2><p>给一个文本 T 和一个模式 P，判断 P 是否是 T 的子串。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">match</span>(<span class="params">sam, pattern</span>):</span><br><span class="line">    v = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> c <span class="keyword">in</span> pattern:</span><br><span class="line">        <span class="keyword">if</span> c <span class="keyword">not</span> <span class="keyword">in</span> sam.<span class="built_in">next</span>[v]:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">        v = sam.<span class="built_in">next</span>[v][c]</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line">sam = SAM.build(<span class="string">&quot;hello world&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="keyword">match</span>(sam, <span class="string">&quot;world&quot;</span>))   <span class="comment"># True</span></span><br><span class="line"><span class="built_in">print</span>(<span class="keyword">match</span>(sam, <span class="string">&quot;xyz&quot;</span>))     <span class="comment"># False</span></span><br></pre></td></tr></table></figure><p>这个方法 O(|P|)，比 KMP 还简单。而且 SAM 一旦建好，多次匹配都是 O(|P|)。</p><h2 id="8-CTF-实战：最长公共子串"><a href="#8-CTF-实战：最长公共子串" class="headerlink" title="8. CTF 实战：最长公共子串"></a>8. CTF 实战：最长公共子串</h2><p>给两个字符串 A 和 B，求它们的最长公共子串。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">longest_common_substring</span>(<span class="params">a, b</span>):</span><br><span class="line">    sam = SAM.build(a)</span><br><span class="line">    v = <span class="number">0</span></span><br><span class="line">    cur_len = <span class="number">0</span></span><br><span class="line">    best_len = <span class="number">0</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> c <span class="keyword">in</span> b:</span><br><span class="line">        <span class="keyword">while</span> v != <span class="number">0</span> <span class="keyword">and</span> c <span class="keyword">not</span> <span class="keyword">in</span> sam.<span class="built_in">next</span>[v]:</span><br><span class="line">            v = sam.link[v]</span><br><span class="line">            cur_len = sam.<span class="built_in">len</span>[v]</span><br><span class="line">        <span class="keyword">if</span> c <span class="keyword">in</span> sam.<span class="built_in">next</span>[v]:</span><br><span class="line">            v = sam.<span class="built_in">next</span>[v][c]</span><br><span class="line">            cur_len += <span class="number">1</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            cur_len = <span class="number">0</span></span><br><span class="line">        best_len = <span class="built_in">max</span>(best_len, cur_len)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> best_len</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(longest_common_substring(<span class="string">&quot;abcde&quot;</span>, <span class="string">&quot;bcdef&quot;</span>))  <span class="comment"># 3 (&quot;bcd&quot;)</span></span><br></pre></td></tr></table></figure><h2 id="9-复杂度分析"><a href="#9-复杂度分析" class="headerlink" title="9. 复杂度分析"></a>9. 复杂度分析</h2><ul><li><strong>空间</strong>：状态数 ≤ 2n - 1，转移数 ≤ 3n - 4。对 n&#x3D;10⁶ 来说，大概 200 万个状态，Python 里用 dict 存转移可能会有点慢，C++ 数组就很轻松。</li><li><strong>时间</strong>：构造 O(n)，每个查询 O(|P|)。均摊线性。</li><li><strong>常数</strong>：Python 的 dict 转移常数偏大，如果题目卡常，可以考虑用数组代替 dict（把字符映射到 0~255）。</li></ul><h2 id="10-常见坑"><a href="#10-常见坑" class="headerlink" title="10. 常见坑"></a>10. 常见坑</h2><ol><li><strong>clone 状态的 cnt 初始化为 0</strong>。clone 不是新前缀，它的出现次数要从子节点累加。</li><li><strong>后缀链接跳的时候注意 -1</strong>。根节点的 link 是 -1，循环终止条件是 <code>p == -1</code>。</li><li><strong>字符映射</strong>。如果是任意字节，记得把字符映射到 0~255，用数组存转移会快很多。</li><li><strong>多测清空</strong>。多组数据记得重新建 SAM，不要复用状态数组。</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>SAM 是字符串处理的瑞士军刀。掌握了 endpos 等价类和后缀链接这两个核心概念，所有变种问题都能推导出来。在 CTF 中，它常用于：</p><ul><li>超长文本的子串统计</li><li>密码分析中的频率统计</li><li>逆向题中的字符串匹配</li><li>Misc 题中的文本去重和模式发现</li></ul><p>写 SAM 的代码量不大，但调试起来容易出微妙的 bug。建议先在小例子上手动画一遍状态转移图，理解每一步在干什么，再写代码。</p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/16/sam-suffix-automaton/</id>
    <link href="https://tsvmv.github.io/2026/09/16/sam-suffix-automaton/"/>
    <published>2026-09-16T03:00:00.000Z</published>
    <summary>
      <![CDATA[<p>后缀自动机（Suffix Automaton，简称 SAM）是处理字符串问题最强大的数据结构之一。它能在 O(n) 时间内构造出一个状态数 O(n) 的自动机，之后可以在线性时间内回答大量字符串相关的问题：不同子串个数、最长重复子串、出现次数最多的子串、子串出现次数、最小表示法……在 CTF 的 Misc 和逆向题中，SAM 经常用来做字符串匹配、密码分析、文本去重等任务。</p>
<p>很多人觉得 SAM 难，其实核心就两条：<strong>endpos 等价类</strong>和<strong>后缀链接</strong>。把这两个概念搞懂了，剩下的都是工程细节。</p>]]>
    </summary>
    <title>后缀自动机(SAM)详解—从原理到 CTF 字符串题实战</title>
    <updated>2026-11-04T11:56:13.948Z</updated>
  </entry>
  <entry>
    <author>
      <name>VMV</name>
    </author>
    <category term="CTF" scheme="https://tsvmv.github.io/categories/CTF/"/>
    <category term="逆向" scheme="https://tsvmv.github.io/tags/%E9%80%86%E5%90%91/"/>
    <category term="CTF" scheme="https://tsvmv.github.io/tags/CTF/"/>
    <category term="Pwn" scheme="https://tsvmv.github.io/tags/Pwn/"/>
    <content>
      <![CDATA[<blockquote><p>一个基于 Unicorn 的 pwntools 兼容层，无需 WSL、虚拟机或 Docker。<br>本文基于 winVpwn 0.1.0（Stage 1）的落地实现，全部代码与测试均可在<br><a href="https://github.com/TSVMV/winVpwn">https://github.com/TSVMV/winVpwn</a> 复现，包已在 PyPI 发布：<code>pip install winvpwn</code>。</p></blockquote><h2 id="为什么会有这个东西"><a href="#为什么会有这个东西" class="headerlink" title="为什么会有这个东西"></a>为什么会有这个东西</h2><p>pwn &#x2F; CTF 分析场景里，最顺手的工具链（pwntools、checksec、各类 ELF 分析脚本）几乎<br>都是 Linux 原生的。Windows 上的研究者要么开一台虚拟机，要么启用 WSL，要么常年在<br>Docker 里折腾。这些方案解决的是”环境隔离”，代价却是三样东西：启动慢、内存大、<br>文件系统跨域麻烦。</p><p>winVpwn 换了一条路：<strong>不做环境，做兼容层</strong>。它把 Linux ELF 二进制加载进一个进程内的<br>CPU 模拟器（Unicorn Engine），把 Linux 系统调用翻译成虚拟内核调用，对外暴露一个<br>“虚拟进程 + 虚拟文件描述符 + 虚拟文件系统”。整个过程不需要任何 hypervisor 层，<br>宿主系统几乎无感。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">传统路线                     winVpwn 路线</span><br><span class="line">┌──────────────┐            ┌──────────────────┐</span><br><span class="line">│ Windows 主机  │            │ Windows 主机      │</span><br><span class="line">│  ┌──────────┐ │            │  ┌─────────────┐ │</span><br><span class="line">│  │ WSL/VM   │ │            │  │ Unicorn 沙箱 │ │</span><br><span class="line">│  │  Linux ELF│ │            │  │  Linux ELF  │ │</span><br><span class="line">│  └──────────┘ │            │  └─────────────┘ │</span><br><span class="line">└──────────────┘            └──────────────────┘</span><br><span class="line">   内核级隔离，重                 进程内模拟，轻</span><br></pre></td></tr></table></figure><h2 id="设计目标与边界"><a href="#设计目标与边界" class="headerlink" title="设计目标与边界"></a>设计目标与边界</h2><p>Stage 1 刻意收敛，目标非常具体：</p><ul><li>加载并运行<strong>静态 <code>ET_EXEC</code> x86_64</strong> ELF</li><li>只需要 <code>write</code> &#x2F; <code>exit</code> &#x2F; <code>exit_group</code> 三个系统调用就能跑通</li><li>所有 I&#x2F;O 都在虚拟空间完成，<strong>不触碰宿主文件系统与网络</strong></li><li>提供干净的 Python API 和 CLI，方便后续叠加 pwntools 语义</li></ul><p>这不是一个”什么都能跑的模拟器”，而是一个<strong>可以逐步扩大系统调用覆盖面的内核模拟<br>框架</strong>。Stage 1 先把最小可执行路径做扎实，为 Stage 2 的 <code>read</code> &#x2F; <code>open</code> &#x2F; <code>mmap</code> 等<br>高频调用铺路。</p><h2 id="架构：五层结构"><a href="#架构：五层结构" class="headerlink" title="架构：五层结构"></a>架构：五层结构</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">graph TD</span><br><span class="line">    A[&quot;Python CLI (typer/rich)&quot;] --&gt; B[&quot;PyO3 bindings (run_elf / parse_elf)&quot;]</span><br><span class="line">    B --&gt; C[&quot;cpu: Vcpu over Unicorn x86-64&quot;]</span><br><span class="line">    C --&gt; D[&quot;syscall dispatch&quot;]</span><br><span class="line">    D --&gt; E[&quot;vkernel: 虚拟进程 / 访客内存 / 输出捕获&quot;]</span><br><span class="line">    C --&gt; F[&quot;elf loader (goblin)&quot;]</span><br><span class="line">    C --&gt; G[&quot;trace recorder&quot;]</span><br></pre></td></tr></table></figure><p>各层职责单一：<code>elf</code> 负责解析与校验镜像，<code>cpu</code> 负责模拟执行循环，<code>syscall</code> 负责<br>Linux ABI 到虚拟内核的翻译，<code>vkernel</code> 维护进程上下文，<code>memory</code> 做访客地址空间的<br>簿记与重叠检测。</p><h2 id="1-ELF-加载器：把”能跑”做成”必须严谨”"><a href="#1-ELF-加载器：把”能跑”做成”必须严谨”" class="headerlink" title="1. ELF 加载器：把”能跑”做成”必须严谨”"></a>1. ELF 加载器：把”能跑”做成”必须严谨”</h2><p>加载器基于 goblin 解析，但真正的价值在<strong>校验</strong>。每一处可能的畸形输入都对应一个<br>稳定、可机器读取的错误码：</p><table><thead><tr><th>code</th><th>含义</th></tr></thead><tbody><tr><td>E001</td><td>不是 ELF（魔数错误）</td></tr><tr><td>E002</td><td>不支持的镜像格式（如 32 位）</td></tr><tr><td>E003</td><td>不支持的机器类型（Stage 1 仅 x86_64）</td></tr><tr><td>E004</td><td>不支持的 ELF 类型（如 Stage 1 的 PIE &#x2F; ET_DYN）</td></tr><tr><td>E005</td><td>程序头畸形</td></tr><tr><td>E006</td><td>镜像被截断（段超出文件范围）</td></tr><tr><td>E007</td><td>可加载段在访客地址空间重叠</td></tr></tbody></table><p>加载器依次检查：ELF 魔数 → 64 位小端 → 机器类型 → ELF 类型 → 每个 <code>PT_LOAD</code><br>段的文件范围合法性 → 段之间是否重叠。任何一个检查不通过，都会得到带错误码的<br>明确失败，而不是模拟器里的 undefined behavior。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">load_elf</span>(image: &amp;[<span class="type">u8</span>]) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;LoadedElf, ElfError&gt; &#123;</span><br><span class="line">    <span class="comment">// E001: 魔数检查</span></span><br><span class="line">    <span class="keyword">if</span> image.<span class="title function_ invoke__">len</span>() &lt; <span class="number">4</span> || &amp;image[..<span class="number">4</span>] != <span class="string">b&quot;\x7fELF&quot;</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_ invoke__">Err</span>(ElfError::NotElf);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// E002/E003/E004: 格式、机器、类型检查</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="comment">// E006: 每个 PT_LOAD 段必须完全落在文件字节内</span></span><br><span class="line">    <span class="comment">// E007: 段之间不允许重叠</span></span><br><span class="line">    <span class="title function_ invoke__">Ok</span>(LoadedElf &#123; entry, segments, is_pie &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出是一个 <code>LoadedElf</code>：入口点 + 所有 <code>PT_LOAD</code> 段（虚拟地址、内存大小、文件大小、<br>权限位、对齐）。<code>memsz &gt; filesz</code> 的尾部按惯例作为零填充的 <code>.bss</code> 处理。</p><h2 id="2-CPU-模拟：Unicorn-之上的-Vcpu"><a href="#2-CPU-模拟：Unicorn-之上的-Vcpu" class="headerlink" title="2. CPU 模拟：Unicorn 之上的 Vcpu"></a>2. CPU 模拟：Unicorn 之上的 Vcpu</h2><p><code>cpu::unicorn_engine::Vcpu</code> 封装一个 Unicorn x86-64 引擎，做三件事：</p><ol><li><strong>映射段</strong>：把每个 <code>PT_LOAD</code> 段按页对齐映射进访客空间，ELF 权限位（<code>R/W/X</code>）<br>翻译成 Unicorn 的 <code>Prot</code>，<code>.bss</code> 尾部零填充。</li><li><strong>搭栈</strong>：映射一块访客栈，并安装 Linux 风格的初始栈布局 —— RSP 处是 <code>argc</code>，<br>随后是空终止的 <code>argv</code> 数组、<code>envp</code> 数组和 <code>auxv</code>。Stage 1 用 <code>argc = 0</code>，<br>即四个清零的 qword：<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">RSP        -&gt; argc = 0</span><br><span class="line">RSP + 0x08 -&gt; argv[0] = NULL</span><br><span class="line">RSP + 0x10 -&gt; envp[0] = NULL</span><br><span class="line">RSP + 0x18 -&gt; auxv[0] = AT_NULL</span><br></pre></td></tr></table></figure></li><li><strong>挂 syscall 钩子</strong>：核心机制。在 <code>syscall</code> 指令上注册一个 <code>add_insn_sys_hook</code>，<br>指令执行到这一步时，读取 Linux x86_64 系统调用 ABI 寄存器（<code>rax</code> 为调用号，<br><code>rdi/rsi/rdx/r10/r8/r9</code> 为六个参数），构造 <code>SyscallRegs</code>，交给调度器；随后把<br>返回值写回 <code>rax</code>。</li></ol><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">uc.<span class="title function_ invoke__">add_insn_sys_hook</span>(X86Insn::SYSCALL, <span class="number">1</span>, <span class="number">0</span>, <span class="keyword">move</span> |uc| &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">rax</span> = uc.<span class="title function_ invoke__">reg_read</span>(RegisterX86::RAX).<span class="title function_ invoke__">unwrap_or</span>(<span class="number">0</span>) <span class="keyword">as</span> <span class="type">i64</span>; <span class="comment">// 调用号</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">args</span> = [</span><br><span class="line">        uc.<span class="title function_ invoke__">reg_read</span>(RegisterX86::RDI).<span class="title function_ invoke__">unwrap_or</span>(<span class="number">0</span>),</span><br><span class="line">        uc.<span class="title function_ invoke__">reg_read</span>(RegisterX86::RSI).<span class="title function_ invoke__">unwrap_or</span>(<span class="number">0</span>),</span><br><span class="line">        uc.<span class="title function_ invoke__">reg_read</span>(RegisterX86::RDX).<span class="title function_ invoke__">unwrap_or</span>(<span class="number">0</span>),</span><br><span class="line">        uc.<span class="title function_ invoke__">reg_read</span>(RegisterX86::R10).<span class="title function_ invoke__">unwrap_or</span>(<span class="number">0</span>),</span><br><span class="line">        uc.<span class="title function_ invoke__">reg_read</span>(RegisterX86::R8).<span class="title function_ invoke__">unwrap_or</span>(<span class="number">0</span>),</span><br><span class="line">        uc.<span class="title function_ invoke__">reg_read</span>(RegisterX86::R9).<span class="title function_ invoke__">unwrap_or</span>(<span class="number">0</span>),</span><br><span class="line">    ];</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">regs</span> = SyscallRegs::<span class="title function_ invoke__">from_regs</span>(rax, args, rip);</span><br><span class="line">    <span class="comment">// ... dispatch，并把 ret 写回 RAX</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>执行循环的终止条件有三条：进程通过 <code>exit</code> 主动退出、执行流落到未映射页<br>（falloff，正常路径，返回触发位置 RIP）、或超时。Unicorn 的报错信息按<br><code>READ_PROTECT / FETCH_UNMAPPED</code> 等类别分类，把它们归一为清晰的 <code>ExitReason</code>。</p><h2 id="3-Syscall-翻译：Linux-ABI-虚拟内核"><a href="#3-Syscall-翻译：Linux-ABI-虚拟内核" class="headerlink" title="3. Syscall 翻译：Linux ABI -&gt; 虚拟内核"></a>3. Syscall 翻译：Linux ABI -&gt; 虚拟内核</h2><p><code>syscall::Dispatch</code> 用一张可注册的表做路由。每个 handler 实现 <code>SyscallHandler</code><br>trait，声明自己负责的调用号、名字和实现。Stage 1 的表格：</p><table><thead><tr><th>nr</th><th>name</th><th>行为</th></tr></thead><tbody><tr><td>1</td><td><code>write</code></td><td>捕获进虚拟进程的输出缓冲区</td></tr><tr><td>60</td><td><code>exit</code></td><td>记录退出码并停止模拟</td></tr><tr><td>231</td><td><code>exit_group</code></td><td>单线程下等同 <code>exit</code></td></tr><tr><td>*</td><td>未实现</td><td>返回 <code>-ENOSYS</code> 并记入 trace</td></tr></tbody></table><p>关键设计：<strong><code>write</code> 永远不会碰宿主文件描述符</strong>。字节被追加到由虚拟进程持有的<br>内存输出缓冲区里，运行结束后通过 <code>vcpu.output()</code> 取回。这是”沙箱”属性的根本<br>来源 —— 没有把任何访客数据转发到宿主 I&#x2F;O 层。</p><p>未知调用号返回 <code>-ENOSYS</code>（errno 语义），并且每次 dispatch 都会记录一条 trace：<br>调用号、名字、六个参数、返回值、触发地址。这份 trace 既用于测试断言，也是将来<br>做执行回放 &#x2F; 行为分析的基础设施。</p><h2 id="4-虚拟内核：进程上下文与访客内存"><a href="#4-虚拟内核：进程上下文与访客内存" class="headerlink" title="4. 虚拟内核：进程上下文与访客内存"></a>4. 虚拟内核：进程上下文与访客内存</h2><p><code>vkernel</code> 层定义每个系统调用的视角：</p><ul><li><strong>进程状态</strong>：<code>Running</code> &#x2F; <code>Exited { code }</code>，跟踪生命周期。</li><li><strong>访客内存后端</strong>：抽象出 <code>GuestMemory</code>，测试环境用内存实现，执行环境用<br>Unicorn 驱动的 <code>UnicornGuest</code>。handler 只面向这个抽象，不直接碰 Unicorn API，<br>因此可以独立单元测试。</li><li><strong>输出捕获</strong>：合并 stdout&#x2F;stderr 语义的 <code>OutputCapture</code>。</li></ul><p><code>memory::mmap::MemoryMap</code> 负责访客地址空间的簿记，插入区域即检测重叠（错误<br>E101）。它<strong>纯属访客空间记账</strong>，不授予任何宿主访问能力 —— 这是隔离边界的一环。</p><h2 id="5-Python-侧：PyO3-绑定与-CLI"><a href="#5-Python-侧：PyO3-绑定与-CLI" class="headerlink" title="5. Python 侧：PyO3 绑定与 CLI"></a>5. Python 侧：PyO3 绑定与 CLI</h2><p>Rust 引擎通过 PyO3 暴露给 Python，扩展模块名 <code>winvpwn._core</code>，对外两个函数：</p><ul><li><code>run_elf(image, timeout_ms=0)</code>：加载、映射并执行，返回一个字典<br>（退出原因、退出码或 falloff RIP、捕获输出、syscall trace）。</li><li><code>parse_elf(image)</code>：只解析不执行，返回入口点、PIE 标志和可加载段列表。</li></ul><p>Python 包层面，CLI 用 Typer + Rich 实现，提供六个命令：</p><figure class="highlight console"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">winvpwn doctor                 <span class="comment"># 环境自检：版本、原生引擎、Python、平台、工具链</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">winvpwn run hello_static       <span class="comment"># 在模拟器中运行静态 ELF</span></span></span><br><span class="line">hello, winVpwn</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">winvpwn elf hello_static       <span class="comment"># 解析 ELF，展示入口点与可加载段</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">winvpwn asm <span class="string">&#x27;mov rax, 1&#x27;</span>       <span class="comment"># 基于 Keystone 汇编</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">winvpwn disasm 48c7c001000000  <span class="comment"># 基于 Capstone 反汇编</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">winvpwn version</span></span><br></pre></td></tr></table></figure><p>Python 侧的工程规格同样严格：mypy <code>--strict</code>、ruff 全部规则档、typer&#x2F;rich 依赖<br>有明确版本区间。CLI 每个命令都支持 <code>--json</code> 输出，方便脚本化。</p><h2 id="工程与发布：一条可复制的-Rust-Python-链路"><a href="#工程与发布：一条可复制的-Rust-Python-链路" class="headerlink" title="工程与发布：一条可复制的 Rust+Python 链路"></a>工程与发布：一条可复制的 Rust+Python 链路</h2><p>Stage 1 交付的不只是功能，还有一套完整的工程基建：</p><ul><li><strong>测试</strong>：30 个 Rust 单元测试 + 17 个 Python 单元&#x2F;CLI 测试。覆盖加载器校验<br>（魔数、截断、PIE 拒绝、权限映射）、栈布局、falloff 路径、errno 语义、CLI<br>输出。<code>filterwarnings = [&quot;error&quot;]</code> 把警告直接当成测试失败。</li><li><strong>质量门禁</strong>：CI 里 clippy <code>-D warnings</code>、rustfmt、ruff、mypy <code>--strict</code> 全开，<br>覆盖率要求 ≥ 80%（当前 80.61%）。</li><li><strong>发布产物</strong>：借助 maturin 的 <code>abi3</code> 能力，构建 CPython 3.11+ 通用 wheel ——<br>一个 <code>cp311-abi3</code> 的 Windows wheel 同时服务 3.11&#x2F;3.12&#x2F;3.13。发布走 GitHub<br>Actions 矩阵构建（Windows MSVC + Linux），PyPI 通过 trusted publishing 自动上传，<br>已发布 <code>winvpwn 0.1.0</code>（<code>win_amd64</code> + <code>manylinux_2_34_x86_64</code> 两个 wheel）。</li></ul><h2 id="使用边界"><a href="#使用边界" class="headerlink" title="使用边界"></a>使用边界</h2><p>winVpwn 面向<strong>授权环境</strong>：CTF 题目、教学、以及你自己拥有的软件。它不触碰宿主<br>文件系统与网络（除非显式映射），每个模拟进程的运行权限都不高于宿主进程。这使它<br>适合在 Windows 上做只读式分析、动态观察和教学演示。</p><h2 id="Stage-2-的方向"><a href="#Stage-2-的方向" class="headerlink" title="Stage 2 的方向"></a>Stage 2 的方向</h2><p>当前基础设施已经把”执行循环 + syscall 翻译 + trace 记录”这条主轴打通。接下来<br>自然的扩展顺序：</p><ol><li><strong>I&#x2F;O 类调用</strong>：<code>read</code> &#x2F; <code>open</code> &#x2F; <code>close</code> &#x2F; <code>lseek</code>，配合一个可插拔的虚拟<br>文件系统（宿主文件需显式映射才能访问）。</li><li><strong><code>mmap</code> &#x2F; <code>munmap</code></strong>：动态内存管理，解锁依赖堆分配的二进制。</li><li><strong>信号与 <code>brk</code></strong>：逼近真实进程语义，覆盖更多 libc 场景。</li><li><strong>PIE &#x2F; 动态链接</strong>：支持 <code>ET_DYN</code> 与解释器，让非静态二进制也能跑。</li><li><strong>trace 消费层</strong>：把 Stage 1 记录的 syscall trace 升级为执行回放与行为分析工具。</li></ol><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>winVpwn 的定位很明确：<strong>一个在 Windows 上原生、进程内、可逐步扩展的 Linux ELF<br>运行环境</strong>。它用一个最小但严谨的 Stage 1 验证了架构 —— 稳定的错误码、清晰的五层<br>划分、完全虚拟化的 I&#x2F;O —— 让后续的系统调用覆盖变成纯粹的增量工作。</p><p>仓库：<a href="https://github.com/TSVMV/winVpwn">https://github.com/TSVMV/winVpwn</a><br>文档：<code>docs/architecture.md</code> · <code>docs/compatibility.md</code> · <code>docs/security.md</code><br>包：<code>pip install winvpwn</code></p>]]>
    </content>
    <id>https://tsvmv.github.io/2026/09/16/winvpwn-windows-elf/</id>
    <link href="https://tsvmv.github.io/2026/09/16/winvpwn-windows-elf/"/>
    <published>2026-09-16T02:00:00.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>一个基于 Unicorn 的 pwntools 兼容层，无需 WSL、虚拟机或 Docker。<br>本文基于 winVpwn 0.1.0（Stage 1）的落地实现，全部代码与测试均可在<br><a]]>
    </summary>
    <title>winVpwn：在 Windows 上原生运行 Linux ELF 二进制</title>
    <updated>2026-11-04T11:56:13.949Z</updated>
  </entry>
</feed>
