<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>crypto &amp;mdash; Vance&#39;s Notes</title>
    <link>https://blog.ylhuang.com/tag:crypto</link>
    <description>🔗 https://vance.ylhuang.com/</description>
    <pubDate>Mon, 22 Jun 2026 21:45:55 +0200</pubDate>
    <item>
      <title>ImaginaryCTF 2025</title>
      <link>https://blog.ylhuang.com/imaginaryctf-2025</link>
      <description>&lt;![CDATA[Between September 5th, 2025 and September 7th, 2025, I worked on ImaginaryCTF, a Capture the Flag competition together with the Kernel Sanders #CTF team at the University of Florida (#UF). I had a lot of fun with the challenges and wanted to thank the creators.&#xA;&#xA;For those of you who have never heard of CTFs, they are online cybersecurity competitions which happen almost every week and contain a variety of categories, such as&#xA;&#xA; rev (reverse engineering)&#xA; pwn (binary exploitation)&#xA; crypto (mathematics and cryptography)&#xA; web (web exploitation)&#xA; forensics (finding information from files)&#xA; misc (challenges that don&#39;t fit into any other categories)&#xA;&#xA;I managed to solve two challeges, redacted and certificate&#xA;&#xA;Redacted&#xA;This is a #crypto challenge. In CTFs, crypto challenges allow players to apply their skills in cryptography and math to figure out an encoded message.&#xA;&#xA;In this challenge, we&#39;ve been given a XORed output and redacted key from CyberChef. CyberChef is an online platform commonly used for crypto CTFs. It contains multiple tools on various ciphers (XOR, Vigenere) and encodings (Base64, hex).&#xA;&#xA;The challenge description is&#xA;wait, i thought XORing something with itself gives all 0s???&#xA;&#xA;We&#39;re given the following image as the challenge:&#xA;CTF redacted image&#xA;&#xA;Manually transcribing the output yields&#xA;65 6c ce 6b c1 75 61 7e 53 66 c9 52 d8 6c 6a 53 6e 6e de 52 df 63 6d 7e 75 7f ce 64 d5 63 73&#xA;&#xA;Here&#39;s a couple of basic things we know. First, the flag is 31 bytes long, including the ictf{ header. Next, the XOR algorithm is not behaving as expected, because XORing an input with itself usually returns 0.&#xA;&#xA;Let&#39;s try to recreate this in CyberChef. &#xA;We don&#39;t know the key, but we can put dummy characters in as a placeholder. We notice that the first 2 characters in our output match the expected key.&#xA;CTF redacted potential solutions&#xA;&#xA;From this point, we can try to brute-force the rest of the key, but that won&#39;t get us anywhere. We need to try a different method.&#xA;&#xA;Since XOR is a symmetric operation, we notice that we are able to recover the first 5 bytes of the key by XORing the output with the input.&#xA;&#xA;The first 5 bytes are&#xA;0c 0f ba 0d ba&#xA;&#xA;We also notice that one of the bytes of the key must be 0e because the last byte of the ciphertext is 73 and the last character of the plaintext is &#39;}&#39;. We found this using Python:&#xA;print(hex(ord(&#39;}&#39;) ^ 0x73))&#xA;&#xA;Now, we keep adding bytes until we get to what appears to be the correct key length, ensuring the last byte remains unchanged as }. We notice that adding 3 bytes appears to yield the correct length and a partially decrypted ciphertext. So the &#34;key&#34; we have so far is:&#xA;0c 0f ba 0d ba 00 0e 00&#xA;with 00 as the 2 padding bytes. Here&#39;s what we see in CyberChef&#xA;&#xA;We also notice that CyberChef interprets text entered into the &#34;HEX&#34; XOR field by dropping all invalid characters. Since it seems like the flag only contains alphabetical characters, we have 6*6=36 possibilities for the key now (letters a-f for each byte). We can perform a manual brute force for these 36 possibilities.&#xA;&#xA;After performing this simple brute-force, our key is now&#xA;0c 0f ba 0d ba 0d 0e 0c&#xA;The decrypted ciphertext&#xA;&#xA;And the recovered plaintext is&#xA;ictf{xorisbadbadencryption}&#xA;&#xA;Certificate&#xA;This is a #web challenge. In CTFs, web challenges have some type of website vulnerable to attacking.&#xA;&#xA;The challenge description is&#xA;As a thank you for playing our CTF, we&#39;re giving out participation certificates! Each one comes with a custom flag, but I bet you can&#39;t get the flag belonging to Eth007!&#xA;We visit the website (https://eth007.me/cert/). Obviously, the first thing we&#39;ll do is try to get the Eth007&#39;s certificate.&#xA;&#xA;The name is redacted&#xA;&#xA;We enter &#34;Eth007&#34; into the &#34;Name&#34; field. However, the name is REDACTED instead. We also don&#39;t see a flag anywhere on the website, despite the challenge description assuring that we&#39;d get one.&#xA;&#xA;No network traffic in sight&#xA;&#xA;We then open up the browser dev tools (by pressing F12) and observe that there is no network traffic happening, even when the name is changed. So it is likely that whatever is responsible for the redaction is running client-side as JavaScript.&#xA;&#xA;The flag has been located&#xA;&#xA;Looking through the source code, we first notice that the flag is embedded within a desc/desc tag in the SVG. We&#39;re not sure what the algorithm used to generate it is, but we&#39;ll treat it as a black box for now.&#xA;&#xA;Searching through the source code for &#34;Eth007&#34; yields this interesting&#xA;function:&#xA;function renderPreview(){&#xA;  var name = nameInput.value.trim();&#xA;  if (name == &#34;Eth007&#34;) {&#xA;    name = &#34;REDACTED&#34;&#xA;  } &#xA;  const svg = buildCertificateSVG({&#xA;    participant: name || &#34;Participant Name&#34;,&#xA;    affiliation: affInput.value.trim() || &#34;Participant&#34;,&#xA;    date: dateInput.value,&#xA;    styleKey: styleSelect.value&#xA;  });&#xA;  svgHolder.innerHTML = svg;&#xA;  svgHolder.dataset.currentSvg = svg;&#xA;}&#xA;&#xA;We modify the function by removing the check for whether the name matches Eth007. &#xA;function renderPreview(){&#xA;  var name = nameInput.value.trim();&#xA;  const svg = buildCertificateSVG({&#xA;    participant: name || &#34;Participant Name&#34;,&#xA;    affiliation: affInput.value.trim() || &#34;Participant&#34;,&#xA;    date: dateInput.value,&#xA;    styleKey: styleSelect.value&#xA;  });&#xA;  svgHolder.innerHTML = svg;&#xA;  svgHolder.dataset.currentSvg = svg;&#xA;}&#xA;Overwriting the function&#xA;&#xA;Then, we overwrite the original function by typing it into the browser DevTools console. Now, we can find the flag by looking in the source code for the desc tag:&#xA;descictf{7b4b3965}/desc&#xA;&#xA;We got the flag&#xA;&#xA;That&#39;s all for now. I&#39;ll be back with more next time!&#xA;&#xA;Revision 1 (2025-09-08): Fixed the broken CyberChef links.&#xA;&#xA;Questions or suggestions? Reach out to me at vance.ylhuang.com!]]&gt;</description>
      <content:encoded><![CDATA[<p>Between September 5th, 2025 and September 7th, 2025, I worked on <a href="https://2025.imaginaryctf.org/">ImaginaryCTF</a>, a Capture the Flag competition together with the Kernel Sanders <a href="https://blog.ylhuang.com/tag:CTF" class="hashtag"><span>#</span><span class="p-category">CTF</span></a> team at the University of Florida (<a href="https://blog.ylhuang.com/tag:UF" class="hashtag"><span>#</span><span class="p-category">UF</span></a>). I had a lot of fun with the challenges and wanted to thank the creators.</p>

<p>For those of you who have never heard of CTFs, they are online cybersecurity competitions which happen almost every week and contain a variety of categories, such as</p>
<ul><li>rev (reverse engineering)</li>
<li>pwn (binary exploitation)</li>
<li>crypto (mathematics and cryptography)</li>
<li>web (web exploitation)</li>
<li>forensics (finding information from files)</li>
<li>misc (challenges that don&#39;t fit into any other categories)</li></ul>

<p>I managed to solve two challeges, <code>redacted</code> and <code>certificate</code></p>

<h2 id="redacted" id="redacted">Redacted</h2>

<p>This is a <a href="https://blog.ylhuang.com/tag:crypto" class="hashtag"><span>#</span><span class="p-category">crypto</span></a> challenge. In CTFs, crypto challenges allow players to apply their skills in cryptography and math to figure out an encoded message.</p>

<p>In this challenge, we&#39;ve been given a XORed output and redacted key from <a href="https://cyberchef.org/">CyberChef</a>. CyberChef is an online platform commonly used for crypto CTFs. It contains multiple tools on various ciphers (XOR, Vigenere) and encodings (Base64, hex).</p>

<p>The challenge description is</p>

<pre><code>wait, i thought XORing something with itself gives all 0s???
</code></pre>

<p>We&#39;re given the following image as the challenge:
<img src="https://images.ylhuang.com/20250905-imaginaryctf/01-redacted.png" alt="CTF redacted image"></p>

<p>Manually transcribing the output yields</p>

<pre><code>65 6c ce 6b c1 75 61 7e 53 66 c9 52 d8 6c 6a 53 6e 6e de 52 df 63 6d 7e 75 7f ce 64 d5 63 73
</code></pre>

<p>Here&#39;s a couple of basic things we know. First, the flag is 31 bytes long, including the <code>ictf{</code> header. Next, the XOR algorithm is not behaving as expected, because XORing an input with itself usually returns 0.</p>

<p>Let&#39;s try to <a href="https://cyberchef.org/#recipe=XOR%28%7B%27option%27%3A%27Hex%27%2C%27string%27%3A%27ictf%7B%27%7D%2C%27Standard%27%2Cfalse%29To_Hex%28%27Space%27%2C0%29&amp;input=aWN0ZnsxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Nn0">recreate this in CyberChef</a>.
We don&#39;t know the key, but we can put dummy characters in as a placeholder. We notice that the first 2 characters in our output match the expected key.
<img src="https://images.ylhuang.com/20250905-imaginaryctf/02-trying-key.png" alt="CTF redacted potential solutions"></p>

<p>From this point, we can try to brute-force the rest of the key, but that won&#39;t get us anywhere. We need to try a different method.</p>

<p>Since XOR is a symmetric operation, we notice that we are able to recover the first 5 bytes of the key <a href="https://cyberchef.org/#recipe=XOR%28%7B%27option%27%3A%27Hex%27%2C%27string%27%3A%2765%206c%20ce%206b%20c1%2075%2061%207e%2053%2066%20c9%2052%20d8%206c%206a%2053%206e%206e%20de%2052%20df%2063%206d%207e%2075%207f%20ce%2064%20d5%2063%2073%27%7D%2C%27Standard%27%2Cfalse%29To_Hex%28%27Space%27%2C0%29&amp;input=aWN0Zns">by XORing the output with the input</a>.</p>

<p>The first 5 bytes are</p>

<pre><code>0c 0f ba 0d ba
</code></pre>

<p>We also notice that one of the bytes of the key must be <code>0e</code> because the last byte of the ciphertext is 73 and the last character of the plaintext is &#39;}&#39;. We <a href="https://ato.pxeger.com/run?1=m72soLIkIz9vwYKlpSVpuha7C4oy80o0MlIrNPKLUjTUa9U1FeIUDCrMjTU1ISqgCmEaAA">found this using Python</a>:</p>

<pre><code class="language-python">print(hex(ord(&#39;}&#39;) ^ 0x73))
</code></pre>

<p>Now, we keep adding bytes until we get to what appears to be the correct key length, ensuring the last byte remains unchanged as <code>}</code>. We notice that adding 3 bytes appears to yield the correct length and a partially decrypted ciphertext. So the “key” we have so far is:</p>

<pre><code>0c 0f ba 0d ba 00 0e 00
</code></pre>

<p>with <code>00</code> as the 2 padding bytes. <a href="https://cyberchef.org/#recipe=From_Hex%28%27Auto%27%29XOR%28%7B%27option%27%3A%27Hex%27%2C%27string%27%3A%270c%200f%20ba%200d%20ba%2000%200e%2000%27%7D%2C%27Standard%27%2Cfalse%29&amp;input=NjUgNmMgY2UgNmIgYzEgNzUgNjEgN2UgNTMgNjYgYzkgNTIgZDggNmMgNmEgNTMgNmUgNmUgZGUgNTIgZGYgNjMgNmQgN2UgNzUgN2YgY2UgNjQgZDUgNjMgNzM">Here&#39;s what we see in CyberChef</a></p>

<p>We also notice that CyberChef interprets text entered into the “HEX” XOR field by dropping all invalid characters. Since it seems like the flag only contains alphabetical characters, we have 6*6=36 possibilities for the key now (letters a-f for each byte). We can perform a manual brute force for these 36 possibilities.</p>

<p>After performing this simple brute-force, <a href="https://cyberchef.org/#recipe=From_Hex%28%27Auto%27%29XOR%28%7B%27option%27%3A%27Hex%27%2C%27string%27%3A%270c%200f%20ba%200d%20ba%200d%200e%200c%27%7D%2C%27Standard%27%2Cfalse%29&amp;input=NjUgNmMgY2UgNmIgYzEgNzUgNjEgN2UgNTMgNjYgYzkgNTIgZDggNmMgNmEgNTMgNmUgNmUgZGUgNTIgZGYgNjMgNmQgN2UgNzUgN2YgY2UgNjQgZDUgNjMgNzM">our key is now</a></p>

<pre><code>0c 0f ba 0d ba 0d 0e 0c
</code></pre>

<p><img src="https://images.ylhuang.com/20250905-imaginaryctf/03-mostly-decrypted.png" alt="The decrypted ciphertext"></p>

<p>And the recovered plaintext is</p>

<pre><code>ictf{xor_is_bad_bad_encryption}
</code></pre>

<h2 id="certificate" id="certificate">Certificate</h2>

<p>This is a <a href="https://blog.ylhuang.com/tag:web" class="hashtag"><span>#</span><span class="p-category">web</span></a> challenge. In CTFs, web challenges have some type of website vulnerable to attacking.</p>

<p>The challenge description is</p>

<pre><code>As a thank you for playing our CTF, we&#39;re giving out participation certificates! Each one comes with a custom flag, but I bet you can&#39;t get the flag belonging to Eth007!
</code></pre>

<p>We visit the website (<a href="https://eth007.me/cert/">https://eth007.me/cert/</a>). Obviously, the first thing we&#39;ll do is try to get the Eth007&#39;s certificate.</p>

<p><img src="https://images.ylhuang.com/20250905-imaginaryctf/04-web-redacted.png" alt="The name is redacted"></p>

<p>We enter “Eth007” into the “Name” field. However, the name is REDACTED instead. We also don&#39;t see a flag anywhere on the website, despite the challenge description assuring that we&#39;d get one.</p>

<p><img src="https://images.ylhuang.com/20250905-imaginaryctf/05-web-devtools-empty.png" alt="No network traffic in sight"></p>

<p>We then open up the browser dev tools (by pressing F12) and observe that there is no network traffic happening, even when the name is changed. So it is likely that whatever is responsible for the redaction is running client-side as JavaScript.</p>

<p><img src="https://images.ylhuang.com/20250905-imaginaryctf/06-flag-found.png" alt="The flag has been located"></p>

<p>Looking through the source code, we first notice that the flag is embedded within a <code>&lt;desc&gt;&lt;/desc&gt;</code> tag in the SVG. We&#39;re not sure what the algorithm used to generate it is, but we&#39;ll treat it as a black box for now.</p>

<p>Searching through the source code for “Eth007” yields this interesting
function:</p>

<pre><code class="language-javascript">function renderPreview(){
  var name = nameInput.value.trim();
  if (name == &#34;Eth007&#34;) {
    name = &#34;REDACTED&#34;
  } 
  const svg = buildCertificateSVG({
    participant: name || &#34;Participant Name&#34;,
    affiliation: affInput.value.trim() || &#34;Participant&#34;,
    date: dateInput.value,
    styleKey: styleSelect.value
  });
  svgHolder.innerHTML = svg;
  svgHolder.dataset.currentSvg = svg;
}
</code></pre>

<p>We modify the function by removing the check for whether the name matches <code>Eth007</code>.</p>

<pre><code class="language-javascript">function renderPreview(){
  var name = nameInput.value.trim();
  const svg = buildCertificateSVG({
    participant: name || &#34;Participant Name&#34;,
    affiliation: affInput.value.trim() || &#34;Participant&#34;,
    date: dateInput.value,
    styleKey: styleSelect.value
  });
  svgHolder.innerHTML = svg;
  svgHolder.dataset.currentSvg = svg;
}
</code></pre>

<p><img src="https://images.ylhuang.com/20250905-imaginaryctf/07-overwriting-function.png" alt="Overwriting the function"></p>

<p>Then, we overwrite the original function by typing it into the browser DevTools console. Now, we can find the flag by looking in the source code for the <code>desc</code> tag:</p>

<pre><code>&lt;desc&gt;ictf{7b4b3965}&lt;/desc&gt;
</code></pre>

<p><img src="https://images.ylhuang.com/20250905-imaginaryctf/08-flag-acquired.png" alt="We got the flag"></p>

<p>That&#39;s all for now. I&#39;ll be back with more next time!</p>

<p>Revision 1 (2025-09-08): Fixed the broken CyberChef links.</p>

<p>Questions or suggestions? Reach out to me at <a href="https://vance.ylhuang.com/">vance.ylhuang.com</a>!</p>
]]></content:encoded>
      <guid>https://blog.ylhuang.com/imaginaryctf-2025</guid>
      <pubDate>Mon, 08 Sep 2025 20:03:19 +0000</pubDate>
    </item>
  </channel>
</rss>