<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://mergesort-fries.github.io/ctf-writeups/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mergesort-fries.github.io/ctf-writeups/" rel="alternate" type="text/html" /><updated>2026-03-25T02:43:00+00:00</updated><id>https://mergesort-fries.github.io/ctf-writeups/feed.xml</id><title type="html">CTF Writeups</title><subtitle>Mergesort(fries) CTF team writeups and exploits</subtitle><entry><title type="html">Not TRUe</title><link href="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/crypto/2026/03/20/picoctf-Not-TRUe/" rel="alternate" type="text/html" title="Not TRUe" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://mergesort-fries.github.io/ctf-writeups/picoctf2026/crypto/2026/03/20/picoctf-Not-TRUe</id><content type="html" xml:base="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/crypto/2026/03/20/picoctf-Not-TRUe/"><![CDATA[<h2 id="challenge">Challenge</h2>

<p><strong>Category:</strong> Crypto · <strong>Points:</strong> 200 · <strong>CTF:</strong> picoCTF 2026</p>

<p>The challenge title is a pun: <strong>Not TRUe</strong> = <strong>NTRU</strong>, the lattice-based public-key cryptosystem. We are given the NTRU public key <code class="language-plaintext highlighter-rouge">h</code>, six ciphertexts <code class="language-plaintext highlighter-rouge">ct</code>, and the system parameters <code class="language-plaintext highlighter-rouge">N=48</code>, <code class="language-plaintext highlighter-rouge">p=3</code>, <code class="language-plaintext highlighter-rouge">q=509</code>.</p>

<hr />

<h2 id="analysis">Analysis</h2>

<p>NTRU key generation works as follows:</p>

<ol>
  <li>Pick two small ternary polynomials <code class="language-plaintext highlighter-rouge">f</code>, <code class="language-plaintext highlighter-rouge">g</code> with coefficients in <code class="language-plaintext highlighter-rouge">{-1, 0, 1}</code> in the ring <code class="language-plaintext highlighter-rouge">R = Z[x]/(x^N - 1)</code>.</li>
  <li>Compute the public key <code class="language-plaintext highlighter-rouge">h = p · g · f⁻¹ (mod q)</code>.</li>
</ol>

<p>Encryption of a binary message polynomial <code class="language-plaintext highlighter-rouge">m</code> with a random ternary blinding polynomial <code class="language-plaintext highlighter-rouge">r</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>c = r * h + m  (mod q)
</code></pre></div></div>

<p>Decryption recovers <code class="language-plaintext highlighter-rouge">m</code> via:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a = f * c  (mod q)   [center-lifted to (-q/2, q/2)]
m = f_p⁻¹ * a  (mod p)
</code></pre></div></div>

<p>The security of NTRU rests on the hardness of finding the short vector <code class="language-plaintext highlighter-rouge">(f, g)</code> from <code class="language-plaintext highlighter-rouge">h</code>. With <code class="language-plaintext highlighter-rouge">N = 48</code>, this instance is <strong>very small</strong> — making it directly vulnerable to the classic NTRU lattice attack via LLL reduction.</p>

<p>The ratio <code class="language-plaintext highlighter-rouge">δ = N / log₂(q) ≈ 48 / 9 ≈ 5.3</code> is far below the threshold at which LLL starts to struggle, so the attack is trivial.</p>

<hr />

<h2 id="the-attack">The Attack</h2>

<h3 id="step-1--build-the-ntru-lattice">Step 1 — Build the NTRU Lattice</h3>

<p>Construct the 2N × 2N matrix:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>M = [ I   H ]
    [ 0  qI ]
</code></pre></div></div>

<p>where <code class="language-plaintext highlighter-rouge">H</code> is the <strong>circulant matrix</strong> of <code class="language-plaintext highlighter-rouge">h</code> (each row is a cyclic shift of the previous), and <code class="language-plaintext highlighter-rouge">I</code> is the N×N identity matrix.</p>

<p>Any vector of the form <code class="language-plaintext highlighter-rouge">(f, g)</code> satisfies <code class="language-plaintext highlighter-rouge">f·H ≡ g (mod q)</code>, so <code class="language-plaintext highlighter-rouge">(f, g)</code> lies in the row space of <code class="language-plaintext highlighter-rouge">M</code>. Because <code class="language-plaintext highlighter-rouge">f</code> and <code class="language-plaintext highlighter-rouge">g</code> have small ternary coefficients, <code class="language-plaintext highlighter-rouge">(f, g)</code> is an unusually <strong>short</strong> lattice vector — exactly the kind LLL is designed to find.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">circulant</span><span class="p">(</span><span class="n">h</span><span class="p">):</span>
    <span class="k">return</span> <span class="p">[</span><span class="n">h</span><span class="p">[</span><span class="o">-</span><span class="n">i</span><span class="p">:]</span> <span class="o">+</span> <span class="n">h</span><span class="p">[:</span><span class="o">-</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">)]</span>

<span class="n">H</span> <span class="o">=</span> <span class="n">circulant</span><span class="p">(</span><span class="n">h_list</span><span class="p">)</span>

<span class="n">M</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">0</span><span class="p">]</span><span class="o">*</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">N</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">N</span><span class="p">)]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>   <span class="n">M</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">i</span><span class="p">]</span>     <span class="o">=</span> <span class="mi">1</span>        <span class="c1"># top-left: I
</span><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span> <span class="n">M</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="o">+</span><span class="n">N</span><span class="p">]</span> <span class="o">=</span> <span class="n">H</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span>  <span class="c1"># top-right: H
</span><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>   <span class="n">M</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="n">N</span><span class="p">][</span><span class="n">i</span><span class="o">+</span><span class="n">N</span><span class="p">]</span> <span class="o">=</span> <span class="n">q</span>        <span class="c1"># bottom-right: qI
</span></code></pre></div></div>

<h3 id="step-2--lll-reduction">Step 2 — LLL Reduction</h3>

<p>Run LLL on <code class="language-plaintext highlighter-rouge">M</code> using <code class="language-plaintext highlighter-rouge">fpylll</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">fpylll</span> <span class="kn">import</span> <span class="n">IntegerMatrix</span><span class="p">,</span> <span class="n">LLL</span>

<span class="n">A</span> <span class="o">=</span> <span class="n">IntegerMatrix</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">N</span><span class="p">,</span> <span class="mi">2</span><span class="o">*</span><span class="n">N</span><span class="p">)</span>
<span class="c1"># ... populate A from M ...
</span><span class="n">LLL</span><span class="p">.</span><span class="n">reduction</span><span class="p">(</span><span class="n">A</span><span class="p">)</span>
</code></pre></div></div>

<p>After reduction, the short rows of the basis are candidates for <code class="language-plaintext highlighter-rouge">(f, g)</code>.</p>

<h3 id="step-3--extracting-f-g">Step 3 — Extracting (f, g)</h3>

<p>A subtle but important observation: the recovered vector has <code class="language-plaintext highlighter-rouge">g</code>-coefficients that are <strong>multiples of 3</strong> (i.e. <code class="language-plaintext highlighter-rouge">p</code>). This happens because LLL found <code class="language-plaintext highlighter-rouge">(f, p·g)</code> rather than <code class="language-plaintext highlighter-rouge">(f, g)</code> directly — a known artifact when the lattice has multiple equally short vectors. Dividing through by <code class="language-plaintext highlighter-rouge">p</code> gives the true <code class="language-plaintext highlighter-rouge">g</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">reduced_basis</span><span class="p">:</span>
    <span class="n">f_cand</span> <span class="o">=</span> <span class="n">row</span><span class="p">[:</span><span class="n">N</span><span class="p">]</span>
    <span class="n">g_cand</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="n">N</span><span class="p">:]</span>
    <span class="k">if</span> <span class="nb">all</span><span class="p">(</span><span class="n">x</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">g_cand</span><span class="p">)</span> <span class="ow">and</span> <span class="ow">not</span> <span class="nb">all</span><span class="p">(</span><span class="n">x</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">g_cand</span><span class="p">):</span>
        <span class="n">g_real</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="o">//</span> <span class="mi">3</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">g_cand</span><span class="p">]</span>
        <span class="c1"># Verify: f * h ≡ p*g (mod q)
</span>        <span class="k">if</span> <span class="n">poly_mul_mod</span><span class="p">(</span><span class="n">f_cand</span><span class="p">,</span> <span class="n">h_list</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">N</span><span class="p">)</span> <span class="o">==</span> <span class="p">[</span><span class="n">p</span><span class="o">*</span><span class="n">x</span> <span class="o">%</span> <span class="n">q</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">g_real</span><span class="p">]:</span>
            <span class="n">f</span><span class="p">,</span> <span class="n">g</span> <span class="o">=</span> <span class="n">f_cand</span><span class="p">,</span> <span class="n">g_real</span>
            <span class="k">break</span>
</code></pre></div></div>

<h3 id="step-4--decrypt">Step 4 — Decrypt</h3>

<p>With <code class="language-plaintext highlighter-rouge">f</code> recovered, decrypt each ciphertext:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">decrypt</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">f</span><span class="p">,</span> <span class="n">f_p_inv</span><span class="p">):</span>
    <span class="n">a</span> <span class="o">=</span> <span class="n">poly_mul_mod</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">N</span><span class="p">)</span>
    <span class="n">a</span> <span class="o">=</span> <span class="n">center_lift</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">q</span><span class="p">)</span>           <span class="c1"># map to (-q/2, q/2)
</span>    <span class="n">a_modp</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="o">%</span> <span class="n">p</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">a</span><span class="p">]</span>
    <span class="k">return</span> <span class="n">poly_mul_mod</span><span class="p">(</span><span class="n">f_p_inv</span><span class="p">,</span> <span class="n">a_modp</span><span class="p">,</span> <span class="n">p</span><span class="p">,</span> <span class="n">N</span><span class="p">)</span>
</code></pre></div></div>

<p>Each of the 6 ciphertexts decrypts to a 48-element binary polynomial (coefficients in <code class="language-plaintext highlighter-rouge">{0, 1}</code>). Concatenating all 6 × 48 = 288 bits and decoding as 8-bit ASCII gives the flag.</p>

<hr />

<h2 id="full-solve-script">Full Solve Script</h2>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">fpylll</span> <span class="kn">import</span> <span class="n">IntegerMatrix</span><span class="p">,</span> <span class="n">LLL</span>

<span class="n">N</span> <span class="o">=</span> <span class="mi">48</span>
<span class="n">p</span> <span class="o">=</span> <span class="mi">3</span>
<span class="n">q</span> <span class="o">=</span> <span class="mi">509</span>

<span class="n">h_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">311</span><span class="p">,</span> <span class="mi">273</span><span class="p">,</span> <span class="mi">153</span><span class="p">,</span> <span class="mi">392</span><span class="p">,</span> <span class="mi">105</span><span class="p">,</span> <span class="mi">426</span><span class="p">,</span> <span class="mi">45</span><span class="p">,</span> <span class="mi">159</span><span class="p">,</span> <span class="mi">421</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">404</span><span class="p">,</span> <span class="mi">38</span><span class="p">,</span> <span class="mi">313</span><span class="p">,</span> <span class="mi">480</span><span class="p">,</span> <span class="mi">37</span><span class="p">,</span> <span class="mi">195</span><span class="p">,</span>
          <span class="mi">82</span><span class="p">,</span> <span class="mi">382</span><span class="p">,</span> <span class="mi">236</span><span class="p">,</span> <span class="mi">230</span><span class="p">,</span> <span class="mi">340</span><span class="p">,</span> <span class="mi">66</span><span class="p">,</span> <span class="mi">199</span><span class="p">,</span> <span class="mi">121</span><span class="p">,</span> <span class="mi">279</span><span class="p">,</span> <span class="mi">273</span><span class="p">,</span> <span class="mi">271</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">270</span><span class="p">,</span> <span class="mi">227</span><span class="p">,</span> <span class="mi">26</span><span class="p">,</span> <span class="mi">253</span><span class="p">,</span>
          <span class="mi">213</span><span class="p">,</span> <span class="mi">408</span><span class="p">,</span> <span class="mi">323</span><span class="p">,</span> <span class="mi">263</span><span class="p">,</span> <span class="mi">283</span><span class="p">,</span> <span class="mi">168</span><span class="p">,</span> <span class="mi">88</span><span class="p">,</span> <span class="mi">164</span><span class="p">,</span> <span class="mi">383</span><span class="p">,</span> <span class="mi">247</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">313</span><span class="p">,</span> <span class="mi">150</span><span class="p">,</span> <span class="mi">170</span><span class="p">,</span> <span class="mi">89</span><span class="p">,</span> <span class="mi">235</span><span class="p">]</span>

<span class="n">ct</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">[</span><span class="mi">231</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">348</span><span class="p">,</span> <span class="mi">205</span><span class="p">,</span> <span class="mi">488</span><span class="p">,</span> <span class="mi">156</span><span class="p">,</span> <span class="mi">57</span><span class="p">,</span> <span class="mi">466</span><span class="p">,</span> <span class="mi">295</span><span class="p">,</span> <span class="mi">240</span><span class="p">,</span> <span class="mi">87</span><span class="p">,</span> <span class="mi">285</span><span class="p">,</span> <span class="mi">165</span><span class="p">,</span> <span class="mi">208</span><span class="p">,</span> <span class="mi">343</span><span class="p">,</span> <span class="mi">426</span><span class="p">,</span>
     <span class="mi">410</span><span class="p">,</span> <span class="mi">36</span><span class="p">,</span> <span class="mi">190</span><span class="p">,</span> <span class="mi">110</span><span class="p">,</span> <span class="mi">187</span><span class="p">,</span> <span class="mi">28</span><span class="p">,</span> <span class="mi">237</span><span class="p">,</span> <span class="mi">262</span><span class="p">,</span> <span class="mi">508</span><span class="p">,</span> <span class="mi">111</span><span class="p">,</span> <span class="mi">451</span><span class="p">,</span> <span class="mi">311</span><span class="p">,</span> <span class="mi">128</span><span class="p">,</span> <span class="mi">449</span><span class="p">,</span> <span class="mi">476</span><span class="p">,</span> <span class="mi">434</span><span class="p">,</span>
     <span class="mi">159</span><span class="p">,</span> <span class="mi">98</span><span class="p">,</span> <span class="mi">488</span><span class="p">,</span> <span class="mi">399</span><span class="p">,</span> <span class="mi">314</span><span class="p">,</span> <span class="mi">499</span><span class="p">,</span> <span class="mi">427</span><span class="p">,</span> <span class="mi">325</span><span class="p">,</span> <span class="mi">299</span><span class="p">,</span> <span class="mi">250</span><span class="p">,</span> <span class="mi">457</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="mi">423</span><span class="p">,</span> <span class="mi">210</span><span class="p">,</span> <span class="mi">271</span><span class="p">],</span>
    <span class="p">[</span><span class="mi">177</span><span class="p">,</span> <span class="mi">217</span><span class="p">,</span> <span class="mi">101</span><span class="p">,</span> <span class="mi">452</span><span class="p">,</span> <span class="mi">354</span><span class="p">,</span> <span class="mi">388</span><span class="p">,</span> <span class="mi">158</span><span class="p">,</span> <span class="mi">437</span><span class="p">,</span> <span class="mi">435</span><span class="p">,</span> <span class="mi">484</span><span class="p">,</span> <span class="mi">452</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">142</span><span class="p">,</span> <span class="mi">47</span><span class="p">,</span> <span class="mi">66</span><span class="p">,</span> <span class="mi">229</span><span class="p">,</span>
     <span class="mi">131</span><span class="p">,</span> <span class="mi">325</span><span class="p">,</span> <span class="mi">423</span><span class="p">,</span> <span class="mi">394</span><span class="p">,</span> <span class="mi">337</span><span class="p">,</span> <span class="mi">356</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">56</span><span class="p">,</span> <span class="mi">197</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">456</span><span class="p">,</span> <span class="mi">186</span><span class="p">,</span> <span class="mi">430</span><span class="p">,</span> <span class="mi">286</span><span class="p">,</span> <span class="mi">68</span><span class="p">,</span> <span class="mi">106</span><span class="p">,</span>
     <span class="mi">271</span><span class="p">,</span> <span class="mi">257</span><span class="p">,</span> <span class="mi">351</span><span class="p">,</span> <span class="mi">76</span><span class="p">,</span> <span class="mi">508</span><span class="p">,</span> <span class="mi">383</span><span class="p">,</span> <span class="mi">360</span><span class="p">,</span> <span class="mi">318</span><span class="p">,</span> <span class="mi">426</span><span class="p">,</span> <span class="mi">113</span><span class="p">,</span> <span class="mi">84</span><span class="p">,</span> <span class="mi">451</span><span class="p">,</span> <span class="mi">436</span><span class="p">,</span> <span class="mi">206</span><span class="p">,</span> <span class="mi">63</span><span class="p">,</span> <span class="mi">151</span><span class="p">],</span>
    <span class="p">[</span><span class="mi">212</span><span class="p">,</span> <span class="mi">400</span><span class="p">,</span> <span class="mi">162</span><span class="p">,</span> <span class="mi">356</span><span class="p">,</span> <span class="mi">300</span><span class="p">,</span> <span class="mi">494</span><span class="p">,</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">333</span><span class="p">,</span> <span class="mi">230</span><span class="p">,</span> <span class="mi">477</span><span class="p">,</span> <span class="mi">484</span><span class="p">,</span> <span class="mi">174</span><span class="p">,</span> <span class="mi">26</span><span class="p">,</span> <span class="mi">65</span><span class="p">,</span> <span class="mi">175</span><span class="p">,</span> <span class="mi">107</span><span class="p">,</span>
     <span class="mi">158</span><span class="p">,</span> <span class="mi">496</span><span class="p">,</span> <span class="mi">168</span><span class="p">,</span> <span class="mi">318</span><span class="p">,</span> <span class="mi">350</span><span class="p">,</span> <span class="mi">497</span><span class="p">,</span> <span class="mi">261</span><span class="p">,</span> <span class="mi">281</span><span class="p">,</span> <span class="mi">388</span><span class="p">,</span> <span class="mi">193</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">294</span><span class="p">,</span> <span class="mi">180</span><span class="p">,</span> <span class="mi">390</span><span class="p">,</span> <span class="mi">116</span><span class="p">,</span> <span class="mi">252</span><span class="p">,</span>
     <span class="mi">169</span><span class="p">,</span> <span class="mi">411</span><span class="p">,</span> <span class="mi">69</span><span class="p">,</span> <span class="mi">257</span><span class="p">,</span> <span class="mi">496</span><span class="p">,</span> <span class="mi">302</span><span class="p">,</span> <span class="mi">86</span><span class="p">,</span> <span class="mi">320</span><span class="p">,</span> <span class="mi">405</span><span class="p">,</span> <span class="mi">436</span><span class="p">,</span> <span class="mi">156</span><span class="p">,</span> <span class="mi">462</span><span class="p">,</span> <span class="mi">219</span><span class="p">,</span> <span class="mi">486</span><span class="p">,</span> <span class="mi">349</span><span class="p">,</span> <span class="mi">494</span><span class="p">],</span>
    <span class="p">[</span><span class="mi">275</span><span class="p">,</span> <span class="mi">256</span><span class="p">,</span> <span class="mi">300</span><span class="p">,</span> <span class="mi">153</span><span class="p">,</span> <span class="mi">467</span><span class="p">,</span> <span class="mi">412</span><span class="p">,</span> <span class="mi">380</span><span class="p">,</span> <span class="mi">353</span><span class="p">,</span> <span class="mi">435</span><span class="p">,</span> <span class="mi">232</span><span class="p">,</span> <span class="mi">450</span><span class="p">,</span> <span class="mi">490</span><span class="p">,</span> <span class="mi">136</span><span class="p">,</span> <span class="mi">136</span><span class="p">,</span> <span class="mi">86</span><span class="p">,</span> <span class="mi">326</span><span class="p">,</span>
     <span class="mi">93</span><span class="p">,</span> <span class="mi">19</span><span class="p">,</span> <span class="mi">208</span><span class="p">,</span> <span class="mi">89</span><span class="p">,</span> <span class="mi">474</span><span class="p">,</span> <span class="mi">163</span><span class="p">,</span> <span class="mi">70</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">56</span><span class="p">,</span> <span class="mi">261</span><span class="p">,</span> <span class="mi">145</span><span class="p">,</span> <span class="mi">499</span><span class="p">,</span> <span class="mi">49</span><span class="p">,</span> <span class="mi">403</span><span class="p">,</span> <span class="mi">331</span><span class="p">,</span> <span class="mi">315</span><span class="p">,</span> <span class="mi">49</span><span class="p">,</span>
     <span class="mi">472</span><span class="p">,</span> <span class="mi">66</span><span class="p">,</span> <span class="mi">156</span><span class="p">,</span> <span class="mi">26</span><span class="p">,</span> <span class="mi">481</span><span class="p">,</span> <span class="mi">412</span><span class="p">,</span> <span class="mi">258</span><span class="p">,</span> <span class="mi">503</span><span class="p">,</span> <span class="mi">44</span><span class="p">,</span> <span class="mi">275</span><span class="p">,</span> <span class="mi">222</span><span class="p">,</span> <span class="mi">164</span><span class="p">,</span> <span class="mi">356</span><span class="p">,</span> <span class="mi">212</span><span class="p">,</span> <span class="mi">57</span><span class="p">],</span>
    <span class="p">[</span><span class="mi">354</span><span class="p">,</span> <span class="mi">310</span><span class="p">,</span> <span class="mi">412</span><span class="p">,</span> <span class="mi">400</span><span class="p">,</span> <span class="mi">67</span><span class="p">,</span> <span class="mi">433</span><span class="p">,</span> <span class="mi">363</span><span class="p">,</span> <span class="mi">209</span><span class="p">,</span> <span class="mi">80</span><span class="p">,</span> <span class="mi">244</span><span class="p">,</span> <span class="mi">473</span><span class="p">,</span> <span class="mi">239</span><span class="p">,</span> <span class="mi">409</span><span class="p">,</span> <span class="mi">446</span><span class="p">,</span> <span class="mi">356</span><span class="p">,</span> <span class="mi">193</span><span class="p">,</span>
     <span class="mi">191</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span> <span class="mi">443</span><span class="p">,</span> <span class="mi">79</span><span class="p">,</span> <span class="mi">371</span><span class="p">,</span> <span class="mi">63</span><span class="p">,</span> <span class="mi">444</span><span class="p">,</span> <span class="mi">285</span><span class="p">,</span> <span class="mi">316</span><span class="p">,</span> <span class="mi">488</span><span class="p">,</span> <span class="mi">176</span><span class="p">,</span> <span class="mi">44</span><span class="p">,</span> <span class="mi">393</span><span class="p">,</span> <span class="mi">401</span><span class="p">,</span> <span class="mi">504</span><span class="p">,</span> <span class="mi">106</span><span class="p">,</span>
     <span class="mi">111</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">491</span><span class="p">,</span> <span class="mi">208</span><span class="p">,</span> <span class="mi">279</span><span class="p">,</span> <span class="mi">403</span><span class="p">,</span> <span class="mi">83</span><span class="p">,</span> <span class="mi">226</span><span class="p">,</span> <span class="mi">271</span><span class="p">,</span> <span class="mi">244</span><span class="p">,</span> <span class="mi">358</span><span class="p">,</span> <span class="mi">473</span><span class="p">,</span> <span class="mi">436</span><span class="p">,</span> <span class="mi">208</span><span class="p">,</span> <span class="mi">457</span><span class="p">,</span> <span class="mi">81</span><span class="p">],</span>
    <span class="p">[</span><span class="mi">246</span><span class="p">,</span> <span class="mi">291</span><span class="p">,</span> <span class="mi">361</span><span class="p">,</span> <span class="mi">460</span><span class="p">,</span> <span class="mi">247</span><span class="p">,</span> <span class="mi">229</span><span class="p">,</span> <span class="mi">400</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">38</span><span class="p">,</span> <span class="mi">189</span><span class="p">,</span> <span class="mi">460</span><span class="p">,</span> <span class="mi">347</span><span class="p">,</span> <span class="mi">384</span><span class="p">,</span> <span class="mi">327</span><span class="p">,</span> <span class="mi">246</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span>
     <span class="mi">7</span><span class="p">,</span> <span class="mi">141</span><span class="p">,</span> <span class="mi">135</span><span class="p">,</span> <span class="mi">182</span><span class="p">,</span> <span class="mi">496</span><span class="p">,</span> <span class="mi">160</span><span class="p">,</span> <span class="mi">259</span><span class="p">,</span> <span class="mi">424</span><span class="p">,</span> <span class="mi">496</span><span class="p">,</span> <span class="mi">137</span><span class="p">,</span> <span class="mi">28</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">169</span><span class="p">,</span> <span class="mi">97</span><span class="p">,</span> <span class="mi">251</span><span class="p">,</span> <span class="mi">269</span><span class="p">,</span>
     <span class="mi">316</span><span class="p">,</span> <span class="mi">68</span><span class="p">,</span> <span class="mi">360</span><span class="p">,</span> <span class="mi">426</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">150</span><span class="p">,</span> <span class="mi">498</span><span class="p">,</span> <span class="mi">398</span><span class="p">,</span> <span class="mi">270</span><span class="p">,</span> <span class="mi">130</span><span class="p">,</span> <span class="mi">447</span><span class="p">,</span> <span class="mi">36</span><span class="p">,</span> <span class="mi">500</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="mi">48</span><span class="p">,</span> <span class="mi">114</span><span class="p">],</span>
<span class="p">]</span>

<span class="c1"># --- Polynomial arithmetic mod (x^N - 1) ---
</span>
<span class="k">def</span> <span class="nf">poly_mul_mod</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">mod</span><span class="p">,</span> <span class="n">N</span><span class="p">):</span>
    <span class="n">result</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">N</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span> <span class="k">continue</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>
            <span class="n">result</span><span class="p">[(</span><span class="n">i</span><span class="o">+</span><span class="n">j</span><span class="p">)</span> <span class="o">%</span> <span class="n">N</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">result</span><span class="p">[(</span><span class="n">i</span><span class="o">+</span><span class="n">j</span><span class="p">)</span> <span class="o">%</span> <span class="n">N</span><span class="p">]</span> <span class="o">+</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">*</span><span class="n">b</span><span class="p">[</span><span class="n">j</span><span class="p">])</span> <span class="o">%</span> <span class="n">mod</span>
    <span class="k">return</span> <span class="n">result</span>

<span class="k">def</span> <span class="nf">poly_inv_mod</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">mod</span><span class="p">,</span> <span class="n">N</span><span class="p">):</span>
    <span class="s">"""Invert f in Z_mod[x]/(x^N - 1) via extended Euclidean algorithm."""</span>
    <span class="k">def</span> <span class="nf">deg</span><span class="p">(</span><span class="n">p</span><span class="p">):</span>
        <span class="n">d</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span>
        <span class="k">while</span> <span class="n">d</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">p</span><span class="p">[</span><span class="n">d</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span> <span class="n">d</span> <span class="o">-=</span> <span class="mi">1</span>
        <span class="k">return</span> <span class="n">d</span>
    <span class="k">def</span> <span class="nf">divmod_poly</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">m</span><span class="p">):</span>
        <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="nb">list</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>
        <span class="n">da</span><span class="p">,</span> <span class="n">db</span> <span class="o">=</span> <span class="n">deg</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="n">deg</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">da</span> <span class="o">&lt;</span> <span class="n">db</span><span class="p">:</span> <span class="k">return</span> <span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">a</span>
        <span class="n">q</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="n">da</span> <span class="o">-</span> <span class="n">db</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
        <span class="n">bi</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">b</span><span class="p">[</span><span class="n">db</span><span class="p">]),</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">m</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">da</span> <span class="o">-</span> <span class="n">db</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="n">db</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span> <span class="k">continue</span>
            <span class="n">c</span> <span class="o">=</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="n">db</span><span class="p">]</span> <span class="o">*</span> <span class="n">bi</span> <span class="o">%</span> <span class="n">m</span>
            <span class="n">q</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">c</span>
            <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">db</span><span class="o">+</span><span class="mi">1</span><span class="p">):</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="n">j</span><span class="p">]</span> <span class="o">-</span> <span class="n">c</span><span class="o">*</span><span class="n">b</span><span class="p">[</span><span class="n">j</span><span class="p">])</span> <span class="o">%</span> <span class="n">m</span>
        <span class="k">return</span> <span class="n">q</span><span class="p">,</span> <span class="n">a</span>
    <span class="k">def</span> <span class="nf">mul_poly</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">m</span><span class="p">):</span>
        <span class="n">r</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="n">deg</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="o">+</span> <span class="n">deg</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">deg</span><span class="p">(</span><span class="n">a</span><span class="p">)</span><span class="o">+</span><span class="mi">1</span><span class="p">):</span>
            <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">deg</span><span class="p">(</span><span class="n">b</span><span class="p">)</span><span class="o">+</span><span class="mi">1</span><span class="p">):</span> <span class="n">r</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">r</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="n">j</span><span class="p">]</span> <span class="o">+</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">*</span><span class="n">b</span><span class="p">[</span><span class="n">j</span><span class="p">])</span> <span class="o">%</span> <span class="n">m</span>
        <span class="k">return</span> <span class="n">r</span>
    <span class="k">def</span> <span class="nf">sub_poly</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">m</span><span class="p">,</span> <span class="n">L</span><span class="p">):</span>
        <span class="n">r</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">*</span><span class="n">L</span>
        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">min</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">a</span><span class="p">),</span><span class="n">L</span><span class="p">)):</span> <span class="n">r</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">%</span> <span class="n">m</span>
        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">min</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">b</span><span class="p">),</span><span class="n">L</span><span class="p">)):</span> <span class="n">r</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">r</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">-</span> <span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="o">%</span> <span class="n">m</span>
        <span class="k">return</span> <span class="n">r</span>

    <span class="n">mpoly</span> <span class="o">=</span> <span class="p">[(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="n">mod</span><span class="p">]</span> <span class="o">+</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">*</span><span class="p">(</span><span class="n">N</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="p">[</span><span class="mi">1</span><span class="p">]</span>
    <span class="n">fp</span> <span class="o">=</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="o">%</span> <span class="n">mod</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">f</span><span class="p">]</span> <span class="o">+</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span>
    <span class="n">old_r</span><span class="p">,</span> <span class="n">r</span> <span class="o">=</span> <span class="n">fp</span><span class="p">[:],</span> <span class="n">mpoly</span><span class="p">[:]</span>
    <span class="n">old_s</span><span class="p">,</span> <span class="n">s</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">*</span><span class="p">(</span><span class="n">N</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">*</span><span class="p">(</span><span class="n">N</span><span class="o">+</span><span class="mi">2</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">500</span><span class="p">):</span>
        <span class="k">if</span> <span class="nb">all</span><span class="p">(</span><span class="n">c</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">r</span><span class="p">):</span> <span class="k">break</span>
        <span class="k">if</span> <span class="n">deg</span><span class="p">(</span><span class="n">old_r</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span> <span class="k">break</span>
        <span class="n">qp</span><span class="p">,</span> <span class="n">nr</span> <span class="o">=</span> <span class="n">divmod_poly</span><span class="p">(</span><span class="n">old_r</span><span class="p">,</span> <span class="n">r</span><span class="p">,</span> <span class="n">mod</span><span class="p">)</span>
        <span class="n">ns</span> <span class="o">=</span> <span class="n">sub_poly</span><span class="p">(</span><span class="n">old_s</span><span class="p">,</span> <span class="n">mul_poly</span><span class="p">(</span><span class="n">qp</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="n">mod</span><span class="p">),</span> <span class="n">mod</span><span class="p">,</span> <span class="n">N</span><span class="o">+</span><span class="mi">2</span><span class="p">)</span>
        <span class="n">old_r</span><span class="p">,</span> <span class="n">r</span> <span class="o">=</span> <span class="n">r</span><span class="p">,</span> <span class="n">nr</span>
        <span class="n">old_s</span><span class="p">,</span> <span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="p">,</span> <span class="n">ns</span>
    <span class="k">if</span> <span class="n">deg</span><span class="p">(</span><span class="n">old_r</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">old_r</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span> <span class="k">return</span> <span class="bp">None</span>
    <span class="n">gi</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">old_r</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">mod</span><span class="p">)</span>
    <span class="n">inv</span> <span class="o">=</span> <span class="p">[(</span><span class="n">c</span><span class="o">*</span><span class="n">gi</span><span class="p">)</span> <span class="o">%</span> <span class="n">mod</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">old_s</span><span class="p">[:</span><span class="n">N</span><span class="p">]]</span>
    <span class="c1"># verify
</span>    <span class="k">if</span> <span class="n">poly_mul_mod</span><span class="p">(</span><span class="n">f</span><span class="p">[:</span><span class="n">N</span><span class="p">],</span> <span class="n">inv</span><span class="p">,</span> <span class="n">mod</span><span class="p">,</span> <span class="n">N</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">:</span> <span class="k">return</span> <span class="bp">None</span>
    <span class="k">return</span> <span class="n">inv</span>

<span class="k">def</span> <span class="nf">center_lift</span><span class="p">(</span><span class="n">coeffs</span><span class="p">,</span> <span class="n">q</span><span class="p">):</span>
    <span class="k">return</span> <span class="p">[</span><span class="n">c</span> <span class="k">if</span> <span class="n">c</span> <span class="o">&lt;=</span> <span class="n">q</span><span class="o">//</span><span class="mi">2</span> <span class="k">else</span> <span class="n">c</span> <span class="o">-</span> <span class="n">q</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="p">[</span><span class="n">x</span> <span class="o">%</span> <span class="n">q</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">coeffs</span><span class="p">]]</span>

<span class="k">def</span> <span class="nf">decrypt</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">f</span><span class="p">,</span> <span class="n">fp_inv</span><span class="p">):</span>
    <span class="n">a</span> <span class="o">=</span> <span class="n">center_lift</span><span class="p">(</span><span class="n">poly_mul_mod</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">N</span><span class="p">),</span> <span class="n">q</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">poly_mul_mod</span><span class="p">(</span><span class="n">fp_inv</span><span class="p">,</span> <span class="p">[</span><span class="n">x</span> <span class="o">%</span> <span class="n">p</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">a</span><span class="p">],</span> <span class="n">p</span><span class="p">,</span> <span class="n">N</span><span class="p">)</span>

<span class="c1"># --- Build and reduce the NTRU lattice ---
</span>
<span class="k">def</span> <span class="nf">circulant</span><span class="p">(</span><span class="n">h</span><span class="p">):</span>
    <span class="k">return</span> <span class="p">[</span><span class="n">h</span><span class="p">[</span><span class="o">-</span><span class="n">i</span><span class="p">:]</span> <span class="o">+</span> <span class="n">h</span><span class="p">[:</span><span class="o">-</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">)]</span>

<span class="n">H</span> <span class="o">=</span> <span class="n">circulant</span><span class="p">(</span><span class="n">h_list</span><span class="p">)</span>
<span class="n">A</span> <span class="o">=</span> <span class="n">IntegerMatrix</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">N</span><span class="p">,</span> <span class="mi">2</span><span class="o">*</span><span class="n">N</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span> <span class="n">A</span><span class="p">[</span><span class="n">i</span><span class="p">,</span> <span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span> <span class="n">A</span><span class="p">[</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="o">+</span><span class="n">N</span><span class="p">]</span> <span class="o">=</span> <span class="n">H</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span> <span class="n">A</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="n">N</span><span class="p">,</span> <span class="n">i</span><span class="o">+</span><span class="n">N</span><span class="p">]</span> <span class="o">=</span> <span class="n">q</span>

<span class="n">LLL</span><span class="p">.</span><span class="n">reduction</span><span class="p">(</span><span class="n">A</span><span class="p">)</span>

<span class="c1"># --- Extract (f, g): look for rows where g is divisible by p ---
</span>
<span class="n">f_key</span> <span class="o">=</span> <span class="bp">None</span>
<span class="k">for</span> <span class="n">row_idx</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">N</span><span class="p">):</span>
    <span class="n">row</span> <span class="o">=</span> <span class="p">[</span><span class="n">A</span><span class="p">[</span><span class="n">row_idx</span><span class="p">,</span> <span class="n">j</span><span class="p">]</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">N</span><span class="p">)]</span>
    <span class="n">f_cand</span><span class="p">,</span> <span class="n">g_cand</span> <span class="o">=</span> <span class="n">row</span><span class="p">[:</span><span class="n">N</span><span class="p">],</span> <span class="n">row</span><span class="p">[</span><span class="n">N</span><span class="p">:]</span>
    <span class="k">if</span> <span class="nb">all</span><span class="p">(</span><span class="n">x</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">f_cand</span><span class="p">):</span> <span class="k">continue</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="nb">all</span><span class="p">(</span><span class="nb">abs</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="mi">3</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">f_cand</span><span class="p">):</span> <span class="k">continue</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="nb">all</span><span class="p">(</span><span class="n">x</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">g_cand</span><span class="p">):</span> <span class="k">continue</span>
    <span class="n">g_real</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="o">//</span> <span class="mi">3</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">g_cand</span><span class="p">]</span>
    <span class="k">if</span> <span class="nb">all</span><span class="p">(</span><span class="n">x</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">g_real</span><span class="p">):</span> <span class="k">continue</span>
    <span class="k">if</span> <span class="n">poly_mul_mod</span><span class="p">(</span><span class="n">f_cand</span><span class="p">,</span> <span class="n">h_list</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">N</span><span class="p">)</span> <span class="o">==</span> <span class="p">[</span><span class="n">p</span><span class="o">*</span><span class="n">x</span> <span class="o">%</span> <span class="n">q</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">g_real</span><span class="p">]:</span>
        <span class="n">f_key</span> <span class="o">=</span> <span class="n">f_cand</span>
        <span class="k">break</span>

<span class="c1"># Compute f inverse mod p
</span><span class="n">f_modp</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="o">%</span> <span class="n">p</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">f_key</span><span class="p">]</span>
<span class="n">fp_inv</span> <span class="o">=</span> <span class="n">poly_inv_mod</span><span class="p">(</span><span class="n">f_modp</span><span class="p">,</span> <span class="n">p</span><span class="p">,</span> <span class="n">N</span><span class="p">)</span>

<span class="c1"># --- Decrypt and decode ---
</span>
<span class="n">bits</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">ct</span><span class="p">:</span>
    <span class="n">bits</span><span class="p">.</span><span class="n">extend</span><span class="p">(</span><span class="n">decrypt</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">f_key</span><span class="p">,</span> <span class="n">fp_inv</span><span class="p">))</span>

<span class="n">flag</span> <span class="o">=</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">(</span>
    <span class="nb">chr</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">bits</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span><span class="o">+</span><span class="mi">8</span><span class="p">]),</span> <span class="mi">2</span><span class="p">))</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">bits</span><span class="p">),</span> <span class="mi">8</span><span class="p">)</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">bits</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span><span class="o">+</span><span class="mi">8</span><span class="p">])</span> <span class="o">==</span> <span class="mi">8</span>
<span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"[+] FLAG:"</span><span class="p">,</span> <span class="n">flag</span><span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="output">Output</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[+] FLAG: picoCTF{th4ts_s0_N0t_TRU3_38a83032}
</code></pre></div></div>

<hr />

<h2 id="key-takeaways">Key Takeaways</h2>

<p><strong>Why the attack works at N=48:</strong> The Gaussian heuristic predicts the shortest vector in this lattice has norm ≈ <code class="language-plaintext highlighter-rouge">√(2N) · q^(1/2)</code> ≈ <code class="language-plaintext highlighter-rouge">√96 · 22.6 ≈ 222</code>. The real <code class="language-plaintext highlighter-rouge">(f, g)</code> has norm <code class="language-plaintext highlighter-rouge">√(‖f‖² + ‖g‖²)</code> ≈ <code class="language-plaintext highlighter-rouge">√(2 · N · 0.5)</code> ≈ <code class="language-plaintext highlighter-rouge">√48 ≈ 7</code> (for ternary polynomials of weight ~N/3). This is dramatically shorter than the Gaussian heuristic — LLL finds it immediately.</p>

<p><strong>The p·g artifact:</strong> LLL returned <code class="language-plaintext highlighter-rouge">g</code>-coefficients that were multiples of 3. This is because the circulant structure of the lattice introduces short linear combinations of <code class="language-plaintext highlighter-rouge">(f, g)</code> and <code class="language-plaintext highlighter-rouge">p</code>-multiples thereof. Always verify the candidate using <code class="language-plaintext highlighter-rouge">f·h ≡ p·g (mod q)</code> rather than just checking coefficient magnitudes.</p>

<p><strong>Secure NTRU parameters</strong> require N in the range of 509–1277, not 48. At N=48, this is a toy instance.</p>

<hr />

<h2 id="flag">Flag</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>picoCTF{th4ts_s0_N0t_TRU3_38a83032}
</code></pre></div></div>]]></content><author><name>matcha / Ding Jie</name></author><category term="picoctf2026" /><category term="crypto" /><summary type="html"><![CDATA[Lattice attack on NTRU — recovering the private key (f, g) from the public key h using LLL reduction on the NTRU lattice, then decrypting 6 ciphertexts to recover a binary-encoded flag.]]></summary></entry><entry><title type="html">Binary Digits</title><link href="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/forensics/2026/03/20/picoctf-binary-digits/" rel="alternate" type="text/html" title="Binary Digits" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://mergesort-fries.github.io/ctf-writeups/picoctf2026/forensics/2026/03/20/picoctf-binary-digits</id><content type="html" xml:base="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/forensics/2026/03/20/picoctf-binary-digits/"><![CDATA[<h2 id="challenge">Challenge</h2>

<p><strong>Category:</strong> Forensics · <strong>Points:</strong> 100 · <strong>CTF:</strong> picoCTF 2026</p>

<p>We are given a long string of binary digits and need to extract the flag from it.</p>

<hr />

<h2 id="analysis">Analysis</h2>

<p>The file contains a raw binary string — a long sequence of <code class="language-plaintext highlighter-rouge">0</code>s and <code class="language-plaintext highlighter-rouge">1</code>s. The key insight is that every 8 bits maps to one byte, meaning the whole string is just binary-encoded file data.</p>

<hr />

<h2 id="approach">Approach</h2>

<p>Convert the binary string to bytes, write it out, and check what kind of file it is:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">sys</span>

<span class="n">input_file</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">output_file</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">2</span> <span class="k">else</span> <span class="s">"output.bin"</span>

<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">input_file</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">bits</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">().</span><span class="n">strip</span><span class="p">().</span><span class="n">replace</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">,</span> <span class="s">''</span><span class="p">).</span><span class="n">replace</span><span class="p">(</span><span class="s">' '</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>

<span class="n">data</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">bits</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span><span class="o">+</span><span class="mi">8</span><span class="p">],</span> <span class="mi">2</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">bits</span><span class="p">),</span> <span class="mi">8</span><span class="p">))</span>

<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">output_file</span><span class="p">,</span> <span class="s">'wb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Done: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="si">}</span><span class="s"> bytes written to </span><span class="si">{</span><span class="n">output_file</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>

<p>Running this produces a <code class="language-plaintext highlighter-rouge">.jpg</code> image file. Opening it reveals the flag printed in the image.</p>

<hr />

<h2 id="key-takeaway">Key Takeaway</h2>

<p>Binary encoding isn’t encryption — it’s trivial to reverse. Any file can be represented as a binary string and decoded back in one line. Always check file headers (<code class="language-plaintext highlighter-rouge">file output.bin</code>) to identify the format before assuming it’s plain text.</p>

<hr />

<h2 id="flag">Flag</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>picoCTF{h1dd3n_1n_th3_b1n4ry_2f96e9a1}
</code></pre></div></div>]]></content><author><name>matcha / Ding Jie</name></author><category term="picoctf2026" /><category term="forensics" /><summary type="html"><![CDATA[Extracting hidden data encoded in binary from an image or file.]]></summary></entry><entry><title type="html">ClusterRSA</title><link href="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/crypto/2026/03/20/picoctf-clusterrsa/" rel="alternate" type="text/html" title="ClusterRSA" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://mergesort-fries.github.io/ctf-writeups/picoctf2026/crypto/2026/03/20/picoctf-clusterrsa</id><content type="html" xml:base="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/crypto/2026/03/20/picoctf-clusterrsa/"><![CDATA[<h2 id="challenge">Challenge</h2>

<p><strong>Category:</strong> Crypto · <strong>Points:</strong> 400 · <strong>CTF:</strong> picoCTF 2026</p>

<hr />

<h2 id="analysis">Analysis</h2>

<p>We’re given a large <code class="language-plaintext highlighter-rouge">n</code> and ciphertext <code class="language-plaintext highlighter-rouge">c</code>. The key observation is that <code class="language-plaintext highlighter-rouge">n</code> is a product of <strong>four</strong> primes rather than the usual two — making it weaker since each prime is smaller and factorisable.</p>

<hr />

<h2 id="approach">Approach</h2>

<p><strong>Step 1 — Factorise n</strong></p>

<p>Since <code class="language-plaintext highlighter-rouge">n</code> is very large but composed of four smaller primes, paste it into <a href="https://factordb.com">factordb.com</a>. It returns:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>n = p1 · p2 · p3 · p4

p1 = 9671406556917033397931773
p2 = 9671406556917033398314601
p3 = 9671406556917033398439721
p4 = 9671406556917033398454847
</code></pre></div></div>

<p><strong>Step 2 — Compute φ(n)</strong></p>

<p>For multi-prime RSA with factors p1, p2, p3, p4:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>φ(n) = (p1 - 1)(p2 - 1)(p3 - 1)(p4 - 1)
</code></pre></div></div>

<p><strong>Step 3 — Recover d and decrypt</strong></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">Crypto.Util.number</span> <span class="kn">import</span> <span class="n">long_to_bytes</span>

<span class="n">p1</span> <span class="o">=</span> <span class="mi">9671406556917033397931773</span>
<span class="n">p2</span> <span class="o">=</span> <span class="mi">9671406556917033398314601</span>
<span class="n">p3</span> <span class="o">=</span> <span class="mi">9671406556917033398439721</span>
<span class="n">p4</span> <span class="o">=</span> <span class="mi">9671406556917033398454847</span>

<span class="n">n</span> <span class="o">=</span> <span class="n">p1</span> <span class="o">*</span> <span class="n">p2</span> <span class="o">*</span> <span class="n">p3</span> <span class="o">*</span> <span class="n">p4</span>
<span class="n">e</span> <span class="o">=</span> <span class="mi">65537</span>  <span class="c1"># standard public exponent
</span><span class="n">c</span> <span class="o">=</span> <span class="c1"># paste c here
</span>
<span class="n">phi</span> <span class="o">=</span> <span class="p">(</span><span class="n">p1</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">p2</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">p3</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">p4</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">d</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">phi</span><span class="p">)</span>
<span class="n">m</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">d</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">long_to_bytes</span><span class="p">(</span><span class="n">m</span><span class="p">))</span>
</code></pre></div></div>

<hr />

<h2 id="key-takeaway">Key Takeaway</h2>

<p>Multi-prime RSA (more than 2 factors) makes individual primes smaller and easier to find in factorisation databases. Standard RSA should use exactly two large, independently generated primes.</p>

<hr />

<h2 id="flag">Flag</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>picoCTF{mul71_rsa_c5d0a11c}
</code></pre></div></div>]]></content><author><name>matcha / Ding Jie</name></author><category term="picoctf2026" /><category term="crypto" /><summary type="html"><![CDATA[Multi-prime RSA with four factors — factoring n via factordb then computing phi from all four primes.]]></summary></entry><entry><title type="html">Forensics Git 1</title><link href="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/forensics/2026/03/20/picoctf-forensics-git-1/" rel="alternate" type="text/html" title="Forensics Git 1" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://mergesort-fries.github.io/ctf-writeups/picoctf2026/forensics/2026/03/20/picoctf-forensics-git-1</id><content type="html" xml:base="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/forensics/2026/03/20/picoctf-forensics-git-1/"><![CDATA[<h2 id="challenge">Challenge</h2>

<p><strong>Category:</strong> Forensics · <strong>Points:</strong> 300 · <strong>CTF:</strong> picoCTF 2026</p>

<p>The challenge gives a <code class="language-plaintext highlighter-rouge">.gz</code> file containing a disk image with multiple partitions.</p>

<hr />

<h2 id="analysis">Analysis</h2>

<p>Decompress the image first:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">gunzip </span>diskimage.gz
</code></pre></div></div>

<p>Running <code class="language-plaintext highlighter-rouge">fdisk -l diskimage</code> reveals three partitions — two Linux partitions (<code class="language-plaintext highlighter-rouge">disk.img1</code> at 300M and <code class="language-plaintext highlighter-rouge">disk.img3</code> at 467M) and a swap partition. Standard mounting failed because the image contains partitions rather than a raw filesystem, so we used 7-Zip to extract the individual partition files.</p>

<hr />

<h2 id="approach">Approach</h2>

<p><strong>Step 1 — Mount the first partition</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> /tmp/disk
<span class="nb">sudo </span>mount <span class="nt">-o</span> loop 0.img /tmp/disk
</code></pre></div></div>

<p>Search for any <code class="language-plaintext highlighter-rouge">.git</code> directories:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>find /tmp/disk <span class="nt">-name</span> <span class="s2">".git"</span> <span class="nt">-type</span> d 2&gt;/dev/null
</code></pre></div></div>

<p>Nothing found, so move to the next partition.</p>

<p><strong>Step 2 — Mount the larger partition</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> /tmp/disk
<span class="nb">sudo </span>mount <span class="nt">-o</span> loop 3.img /tmp/disk
<span class="nb">sudo </span>find /tmp/disk <span class="nt">-name</span> <span class="s2">".git"</span> <span class="nt">-type</span> d 2&gt;/dev/null
</code></pre></div></div>

<p>This revealed a repo at <code class="language-plaintext highlighter-rouge">/tmp/disk/home/ctf-player/Code/secrets.git</code>.</p>

<p><strong>Step 3 — Search Git history for the flag</strong></p>

<p>Git never truly deletes committed data — even after a file is removed, it stays in the object store. We searched all commit history:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /tmp/disk/home/ctf-player/Code/secrets.git
git log <span class="nt">--all</span> <span class="nt">-p</span> | <span class="nb">grep</span> <span class="s2">"picoCTF{"</span>
</code></pre></div></div>

<p>The flag was buried in an old commit.</p>

<hr />

<h2 id="key-takeaway">Key Takeaway</h2>

<p>Even after <code class="language-plaintext highlighter-rouge">git rm</code>, data persists in the object store. Always scrub sensitive commits with <code class="language-plaintext highlighter-rouge">git filter-branch</code> or <code class="language-plaintext highlighter-rouge">git filter-repo</code> before pushing to a public repo.</p>

<hr />

<h2 id="flag">Flag</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>picoCTF{g17_r3m3mb3r5_d4ddf904}
</code></pre></div></div>]]></content><author><name>matcha / Ding Jie</name></author><category term="picoctf2026" /><category term="forensics" /><summary type="html"><![CDATA[Recovering deleted data from a Git repository's object store inside a disk image.]]></summary></entry><entry><title type="html">Hashgate</title><link href="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/web/2026/03/20/picoctf-hashgate/" rel="alternate" type="text/html" title="Hashgate" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://mergesort-fries.github.io/ctf-writeups/picoctf2026/web/2026/03/20/picoctf-hashgate</id><content type="html" xml:base="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/web/2026/03/20/picoctf-hashgate/"><![CDATA[<h2 id="challenge">Challenge</h2>

<p><strong>Category:</strong> Web · <strong>Points:</strong> 100 · <strong>CTF:</strong> picoCTF 2026</p>

<p>We’re given a login page at <code class="language-plaintext highlighter-rouge">crystal-peak.picoctf.net</code>.</p>

<hr />

<h2 id="analysis">Analysis</h2>

<p><strong>Step 1 — Inspect the page source</strong></p>

<p>Opening DevTools immediately reveals a comment in the HTML:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Email: guest@picoctf.org Password: guest --&gt;</span>
</code></pre></div></div>

<p>Logging in with those credentials works and lands us on a user profile page. The URL looks like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/profile/user/53a1320cb5d2f56130ad5222f93da374
</code></pre></div></div>

<p>That long hex string is an MD5 hash. The question is — what’s being hashed?</p>

<p><strong>Step 2 — Identify the hash input</strong></p>

<p>MD5 of a predictable value is a classic mistake. Given this is a CTF with sequential users, the most likely input is a <strong>numeric user ID</strong>. The guest hash <code class="language-plaintext highlighter-rouge">53a1320cb5d2f56130ad5222f93da374</code> can be cracked or we can just brute force nearby IDs to find admin.</p>

<hr />

<h2 id="approach">Approach</h2>

<p>Brute force a range of numeric IDs, hash each one with MD5, and try accessing the profile URL:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">import</span> <span class="nn">hashlib</span>

<span class="n">base</span> <span class="o">=</span> <span class="s">"http://crystal-peak.picoctf.net:60927/profile/user/"</span>

<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2981</span><span class="p">,</span> <span class="mi">3021</span><span class="p">):</span>
    <span class="n">h</span> <span class="o">=</span> <span class="n">hashlib</span><span class="p">.</span><span class="n">md5</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">).</span><span class="n">encode</span><span class="p">()).</span><span class="n">hexdigest</span><span class="p">()</span>
    <span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">base</span> <span class="o">+</span> <span class="n">h</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">r</span><span class="p">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"ID </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s"> → </span><span class="si">{</span><span class="n">h</span><span class="si">}</span><span class="s"> ✓"</span><span class="p">)</span>
        <span class="k">print</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">)</span>
        <span class="k">break</span>
</code></pre></div></div>

<p>One of the IDs in that range resolves to the admin profile, which prints:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Welcome, admin! Here is the flag: picoCTF{id0r_unl0ck_c642ae68}
</code></pre></div></div>

<p>This is an <strong>IDOR</strong> (Insecure Direct Object Reference) — the profile endpoint has no authorisation check, so any user can access any profile just by knowing (or guessing) the hash.</p>

<hr />

<h2 id="key-takeaway">Key Takeaway</h2>

<p>Two vulnerabilities combined here: credentials leaked in an HTML comment, and profile URLs using MD5 of a sequential integer — trivially brute forceable. Profile endpoints must check that the authenticated user owns the requested resource, not just that the hash is valid.</p>

<hr />

<h2 id="flag">Flag</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>picoCTF{id0r_unl0ck_c642ae68}
</code></pre></div></div>]]></content><author><name>matcha / Ding Jie</name></author><category term="picoctf2026" /><category term="web" /><summary type="html"><![CDATA[Credentials leaked in HTML source, profile URLs use MD5-hashed numeric IDs — brute force the admin's ID to access their profile and get the flag.]]></summary></entry><entry><title type="html">MultiCode</title><link href="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/misc/2026/03/20/picoctf-multicode/" rel="alternate" type="text/html" title="MultiCode" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://mergesort-fries.github.io/ctf-writeups/picoctf2026/misc/2026/03/20/picoctf-multicode</id><content type="html" xml:base="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/misc/2026/03/20/picoctf-multicode/"><![CDATA[<h2 id="challenge">Challenge</h2>

<p><strong>Category:</strong> General Skills · <strong>Points:</strong> 200 · <strong>CTF:</strong> picoCTF 2026</p>

<p>We’re given a single encoded string and told it has gone through multiple layers of encoding. No hints on which ones or in what order.</p>

<hr />

<h2 id="analysis">Analysis</h2>

<p>The string looks like Base64 at first glance — but decoding it once doesn’t give plaintext. It’s clearly nested. Rather than guessing the order manually, throwing it into <strong>CyberChef’s Magic operation</strong> lets it auto-detect the encoding stack.</p>

<p>Magic identified the sequence as:</p>

<ol>
  <li><strong>From Base64</strong></li>
  <li><strong>From Hex</strong></li>
  <li><strong>URL Decode</strong></li>
  <li><strong>ROT13</strong></li>
</ol>

<hr />

<h2 id="approach">Approach</h2>

<p>Replicate the chain in CyberChef with these operations in order:</p>

<table>
  <thead>
    <tr>
      <th>Step</th>
      <th>Operation</th>
      <th>Settings</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>From Base64</td>
      <td>Alphabet: <code class="language-plaintext highlighter-rouge">A-Za-z0-9+/=</code>, Remove non-alphabet chars: ✓</td>
    </tr>
    <tr>
      <td>2</td>
      <td>From Hex</td>
      <td>Delimiter: Auto</td>
    </tr>
    <tr>
      <td>3</td>
      <td>URL Decode</td>
      <td>Treat <code class="language-plaintext highlighter-rouge">+</code> as space: ✓</td>
    </tr>
    <tr>
      <td>4</td>
      <td>ROT13</td>
      <td>Rotate upper + lower case, Amount: 13</td>
    </tr>
  </tbody>
</table>

<p>Pasting the input string and running the chain outputs the flag directly.</p>

<hr />

<h2 id="key-takeaway">Key Takeaway</h2>

<p>When a challenge says “multiple encodings” with no further hints, CyberChef’s Magic operation is the fastest way to fingerprint the stack. Each encoding layer on its own is trivially reversible — the only challenge is identifying the correct order.</p>

<hr />

<h2 id="flag">Flag</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>picoCTF{nested_enc0ding_66b54257}
</code></pre></div></div>]]></content><author><name>matcha / Ding Jie</name></author><category term="picoctf2026" /><category term="misc" /><summary type="html"><![CDATA[A string encoded through four nested layers — Base64, Hex, URL encoding, and ROT13. CyberChef Magic sniffs out the sequence.]]></summary></entry><entry><title type="html">No FA</title><link href="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/web/2026/03/20/picoctf-no-fa/" rel="alternate" type="text/html" title="No FA" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://mergesort-fries.github.io/ctf-writeups/picoctf2026/web/2026/03/20/picoctf-no-fa</id><content type="html" xml:base="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/web/2026/03/20/picoctf-no-fa/"><![CDATA[<h2 id="challenge">Challenge</h2>

<p><strong>Category:</strong> Web · <strong>Points:</strong> 200 · <strong>CTF:</strong> picoCTF 2026</p>

<hr />

<h2 id="analysis">Analysis</h2>

<p>Reviewing the source code reveals two vulnerabilities:</p>

<ol>
  <li><strong>Unsalted password hashes</strong> — passwords are hashed without a salt, making them crackable with rainbow tables or hashcat</li>
  <li><strong>No rate limiter on OTP</strong> — the two-factor authentication endpoint accepts unlimited guesses, enabling brute force</li>
</ol>

<hr />

<h2 id="approach">Approach</h2>

<p><strong>Step 1 — Find a target account</strong></p>

<p>Browse the database for a high-value account. The <code class="language-plaintext highlighter-rouge">admin</code> account has 2FA enabled — a good target.</p>

<p><strong>Step 2 — Crack the password hash</strong></p>

<p>The admin’s hash cracks to <code class="language-plaintext highlighter-rouge">apple@123</code>. Log in with:</p>
<ul>
  <li>Username: <code class="language-plaintext highlighter-rouge">admin</code></li>
  <li>Password: <code class="language-plaintext highlighter-rouge">apple@123</code></li>
</ul>

<p>This lands on the 2FA page.</p>

<p><strong>Step 3 — Brute force the OTP</strong></p>

<p>Since there’s no rate limiter, use the browser console to automate OTP guesses:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;=</span> <span class="mi">999999</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">otp</span> <span class="o">=</span> <span class="nb">String</span><span class="p">(</span><span class="nx">i</span><span class="p">).</span><span class="nx">padStart</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">);</span>
    <span class="kd">let</span> <span class="nx">resp</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/verify-otp</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
        <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">headers</span><span class="p">:</span> <span class="p">{</span><span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">},</span>
        <span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span><span class="na">otp</span><span class="p">:</span> <span class="nx">otp</span><span class="p">})</span>
    <span class="p">});</span>
    <span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">resp</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">OTP found:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">otp</span><span class="p">);</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The correct OTP is found and the flag is returned.</p>

<hr />

<h2 id="key-takeaway">Key Takeaway</h2>

<p>Always salt password hashes (use bcrypt, argon2, or scrypt). Always rate-limit OTP endpoints — a 6-digit OTP has only 1,000,000 possible values and can be brute forced in minutes without throttling.</p>

<hr />

<h2 id="flag">Flag</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>picoCTF{n0_r4t3_n0_4uth_7bd3c284}
</code></pre></div></div>]]></content><author><name>matcha / Ding Jie</name></author><category term="picoctf2026" /><category term="web" /><summary type="html"><![CDATA[Cracking an unsalted password hash and brute-forcing an OTP with no rate limiter to bypass 2FA.]]></summary></entry><entry><title type="html">Old Sessions</title><link href="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/web/2026/03/20/picoctf-old-sessions/" rel="alternate" type="text/html" title="Old Sessions" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://mergesort-fries.github.io/ctf-writeups/picoctf2026/web/2026/03/20/picoctf-old-sessions</id><content type="html" xml:base="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/web/2026/03/20/picoctf-old-sessions/"><![CDATA[<h2 id="challenge">Challenge</h2>

<p><strong>Category:</strong> Web · <strong>Points:</strong> 100 · <strong>CTF:</strong> picoCTF 2026</p>

<p>URL: <code class="language-plaintext highlighter-rouge">http://dolphin-cove.picoctf.net:52077/sessions</code></p>

<p>We are given a sessions endpoint and an example session:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>session:VccD5CnHjooYuqCNfiWBvI-kybC0pays_8QFBWE3wjs
{'_permanent': True, 'key': 'admin'}
</code></pre></div></div>

<hr />

<h2 id="analysis">Analysis</h2>

<p>The session data suggests:</p>

<ul>
  <li>The application uses <strong>client-side session storage</strong></li>
  <li>The session contains <code class="language-plaintext highlighter-rouge">_permanent: True</code> and <code class="language-plaintext highlighter-rouge">key: admin</code></li>
  <li>This is a strong indicator the session is encoded (likely Base64) and <strong>not securely signed</strong></li>
</ul>

<p>If the server trusts this session value directly, we can <strong>forge our own session</strong>.</p>

<p><img src="/ctf-writeups/assets/images/old-sessions-cookie.png" alt="Session cookie in browser devtools" /></p>

<hr />

<h2 id="approach">Approach</h2>

<p><strong>Goal:</strong> Modify the session so that <code class="language-plaintext highlighter-rouge">key = admin</code></p>

<ol>
  <li>Inspect how the session is stored (cookie or URL)</li>
  <li>Decode the session value</li>
  <li>Modify the JSON data</li>
  <li>Re-encode it</li>
  <li>Send it back to the server</li>
</ol>

<hr />

<h2 id="exploit">Exploit</h2>

<p>If the session is Base64 encoded:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">base64</span>

<span class="n">data</span> <span class="o">=</span> <span class="sa">b</span><span class="s">"{'_permanent': True, 'key': 'admin'}"</span>
<span class="n">encoded</span> <span class="o">=</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64encode</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">encoded</span><span class="p">)</span>
</code></pre></div></div>

<p>Swap the session cookie with the forged value and reload the page.</p>

<hr />

<h2 id="flag">Flag</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>picoCTF{s3t_s3ss10n_3xp1rat10n5_53a328ed}
</code></pre></div></div>]]></content><author><name>matcha / Ding Jie</name></author><category term="picoctf2026" /><category term="web" /><summary type="html"><![CDATA[Forging a Flask client-side session token to escalate privileges to admin.]]></summary></entry><entry><title type="html">Related Messages</title><link href="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/crypto/2026/03/20/picoctf-related-messages/" rel="alternate" type="text/html" title="Related Messages" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://mergesort-fries.github.io/ctf-writeups/picoctf2026/crypto/2026/03/20/picoctf-related-messages</id><content type="html" xml:base="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/crypto/2026/03/20/picoctf-related-messages/"><![CDATA[<h2 id="challenge">Challenge</h2>

<p><strong>Category:</strong> Crypto · <strong>Points:</strong> 200 · <strong>CTF:</strong> picoCTF 2026</p>

<p>The sender encrypted a message, made a typo, and re-encrypted the corrected version under the same RSA key. We are given both ciphertexts.</p>

<hr />

<h2 id="analysis">Analysis</h2>

<p>The typo was <code class="language-plaintext highlighter-rouge">z</code> (<code class="language-plaintext highlighter-rouge">0x7a</code>) instead of <code class="language-plaintext highlighter-rouge">}</code> (<code class="language-plaintext highlighter-rouge">0x7d</code>), a difference of exactly <strong>3</strong>. This means:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>m1 = m2 - 3
</code></pre></div></div>

<p>Both plaintexts were encrypted under the same key, leaking a linear relationship — the perfect setup for a Franklin-Reiter attack.</p>

<hr />

<h2 id="the-attack">The Attack</h2>

<p>Franklin-Reiter’s related message attack exploits the fact that if two plaintexts satisfy a known linear relation, both are roots of polynomials that share a common factor:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>f1(x) = x^e - c1
f2(x) = (x + 3)^e - c2
</code></pre></div></div>

<p>Both share the root <code class="language-plaintext highlighter-rouge">m1</code>, so:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcd(f1, f2)  over ℤ_N[x]  →  (x - m1)
</code></pre></div></div>

<p>This directly reveals the plaintext — <strong>no factoring of N required</strong>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sage.all</span> <span class="kn">import</span> <span class="o">*</span>

<span class="c1"># Given values
</span><span class="n">N</span> <span class="o">=</span> <span class="c1"># paste N here
</span><span class="n">e</span> <span class="o">=</span> <span class="c1"># paste e here
</span><span class="n">c1</span> <span class="o">=</span> <span class="c1"># paste c1 here
</span><span class="n">c2</span> <span class="o">=</span> <span class="c1"># paste c2 here
</span>
<span class="n">P</span><span class="p">.</span><span class="o">&lt;</span><span class="n">x</span><span class="o">&gt;</span> <span class="o">=</span> <span class="n">PolynomialRing</span><span class="p">(</span><span class="n">Zmod</span><span class="p">(</span><span class="n">N</span><span class="p">))</span>
<span class="n">f1</span> <span class="o">=</span> <span class="n">x</span><span class="o">^</span><span class="n">e</span> <span class="o">-</span> <span class="n">c1</span>
<span class="n">f2</span> <span class="o">=</span> <span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mi">3</span><span class="p">)</span><span class="o">^</span><span class="n">e</span> <span class="o">-</span> <span class="n">c2</span>

<span class="c1"># GCD gives (x - m1)
</span><span class="n">g</span> <span class="o">=</span> <span class="n">gcd</span><span class="p">(</span><span class="n">f1</span><span class="p">,</span> <span class="n">f2</span><span class="p">)</span>
<span class="n">m1</span> <span class="o">=</span> <span class="o">-</span><span class="n">g</span><span class="p">.</span><span class="n">monic</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">print</span><span class="p">(</span><span class="n">long_to_bytes</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">m1</span><span class="p">)))</span>
</code></pre></div></div>

<hr />

<h2 id="key-takeaway">Key Takeaway</h2>

<p>Never reuse an RSA key to encrypt related messages. Even a 3-byte difference is enough to completely break the encryption with Franklin-Reiter. Use hybrid encryption (RSA + AES) or randomised padding (OAEP) instead.</p>

<hr />

<h2 id="flag">Flag</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>picoCTF{m3ssage_w1th_typ0}
</code></pre></div></div>]]></content><author><name>matcha / Ding Jie</name></author><category term="picoctf2026" /><category term="crypto" /><summary type="html"><![CDATA[Franklin-Reiter related message attack on RSA — exploiting two ciphertexts encrypted under the same key where plaintexts differ by a known constant.]]></summary></entry><entry><title type="html">Rogue Tower</title><link href="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/forensics/2026/03/20/picoctf-rogue-tower/" rel="alternate" type="text/html" title="Rogue Tower" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://mergesort-fries.github.io/ctf-writeups/picoctf2026/forensics/2026/03/20/picoctf-rogue-tower</id><content type="html" xml:base="https://mergesort-fries.github.io/ctf-writeups/picoctf2026/forensics/2026/03/20/picoctf-rogue-tower/"><![CDATA[<h2 id="challenge">Challenge</h2>

<p><strong>Category:</strong> Forensics · <strong>Points:</strong> 300 · <strong>CTF:</strong> picoCTF 2026</p>

<hr />

<h2 id="analysis">Analysis</h2>

<p>Filtering for HTTP traffic in the capture, there are GET requests from several IP addresses. Inspecting each one reveals which belongs to the rogue tower:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">10.100.156.251</code> → IMSI: <code class="language-plaintext highlighter-rouge">310410316992912</code>, CELL: <code class="language-plaintext highlighter-rouge">15606</code> — <strong>legitimate</strong></li>
  <li><code class="language-plaintext highlighter-rouge">10.100.50.122</code> → IMSI: <code class="language-plaintext highlighter-rouge">310410308555787</code>, CELL: <code class="language-plaintext highlighter-rouge">92058</code> — <strong>unauthorised tower</strong></li>
</ul>

<hr />

<h2 id="approach">Approach</h2>

<p><strong>Step 1 — Isolate rogue tower traffic</strong></p>

<p>Filter to just the rogue tower’s POST requests and compile the data it sent. This gives us the following Base64-looking string:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>QFFWWnZjfkxCCFJABmhbBFxUakEFQAtFb1xXVgEHAAQBRQ==
</code></pre></div></div>

<p><strong>Step 2 — Identify the encryption</strong></p>

<p>The string is clearly encoded. Hint 3 states: <em>“The encryption key is derived from the victim device’s IMSI.”</em></p>

<p>This narrows the brute force significantly — we try XOR with different substrings of the IMSI <code class="language-plaintext highlighter-rouge">310410308555787</code> against the ciphertext.</p>

<p><strong>Step 3 — Brute force the XOR key</strong></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">base64</span>
<span class="kn">import</span> <span class="nn">itertools</span>

<span class="n">ciphertext</span> <span class="o">=</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64decode</span><span class="p">(</span><span class="s">"QFFWWnZjfkxCCFJABmhbBFxUakEFQAtFb1xXVgEHAAQBRQ=="</span><span class="p">)</span>
<span class="n">imsi</span> <span class="o">=</span> <span class="s">"310410308555787"</span>

<span class="k">for</span> <span class="n">length</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">imsi</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">start</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">imsi</span><span class="p">)</span> <span class="o">-</span> <span class="n">length</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
        <span class="n">key</span> <span class="o">=</span> <span class="n">imsi</span><span class="p">[</span><span class="n">start</span><span class="p">:</span><span class="n">start</span> <span class="o">+</span> <span class="n">length</span><span class="p">].</span><span class="n">encode</span><span class="p">()</span>
        <span class="n">pt</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">(</span><span class="n">c</span> <span class="o">^</span> <span class="n">key</span><span class="p">[</span><span class="n">i</span> <span class="o">%</span> <span class="nb">len</span><span class="p">(</span><span class="n">key</span><span class="p">)]</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">c</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">ciphertext</span><span class="p">))</span>
        <span class="k">if</span> <span class="sa">b</span><span class="s">'picoCTF'</span> <span class="ow">in</span> <span class="n">pt</span><span class="p">:</span>
            <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Key: </span><span class="si">{</span><span class="n">key</span><span class="si">}</span><span class="s"> → </span><span class="si">{</span><span class="n">pt</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>

<p>The correct substring of the IMSI XOR-decrypts the ciphertext to reveal the flag.</p>

<hr />

<h2 id="key-takeaway">Key Takeaway</h2>

<p>Rogue cell towers (IMSI catchers / Stingrays) capture device identifiers and can intercept traffic. XOR encryption with a predictable key (like a device’s own IMSI) provides essentially no security.</p>

<hr />

<h2 id="flag">Flag</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>picoCTF{r0gu3_c3ll_t0w3r_dbc40831}
</code></pre></div></div>]]></content><author><name>matcha / Ding Jie</name></author><category term="picoctf2026" /><category term="forensics" /><summary type="html"><![CDATA[Identifying a rogue cell tower from captured network traffic and decrypting its exfiltrated data.]]></summary></entry></feed>