<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://llgsjsm.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://llgsjsm.github.io/" rel="alternate" type="text/html" /><updated>2026-04-27T06:43:56+00:00</updated><id>https://llgsjsm.github.io/feed.xml</id><title type="html">llgsjsm</title><subtitle>vuln research &amp; drowning in tokens</subtitle><author><name>llgsjsm</name></author><entry><title type="html">TISCDFCTF: wad-is-this</title><link href="https://llgsjsm.github.io/wad-is-this/" rel="alternate" type="text/html" title="TISCDFCTF: wad-is-this" /><published>2026-04-20T00:00:00+00:00</published><updated>2026-04-20T00:00:00+00:00</updated><id>https://llgsjsm.github.io/wad-is-this</id><content type="html" xml:base="https://llgsjsm.github.io/wad-is-this/"><![CDATA[<h2 id="-overview">:: overview</h2>

<table>
  <tbody>
    <tr>
      <td><strong>Challenge</strong></td>
      <td>wad-is-this</td>
    </tr>
    <tr>
      <td><strong>Category</strong></td>
      <td>Reverse Engineering</td>
    </tr>
    <tr>
      <td><strong>Flag</strong></td>
      <td><code class="language-plaintext highlighter-rouge">TISCDCSG{the_f1ag_ch0sen_speci4lly_for_th3_wasm}</code></td>
    </tr>
  </tbody>
</table>

<p>the challenge name is a pun - WAT is the WebAssembly text format. cute.</p>

<h2 id="-first-look">:: first look</h2>

<p>we get a single JS file: <code class="language-plaintext highlighter-rouge">wat-is-this.js</code>. opening it in an editor is immediately cursed:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(()</span><span class="o">=&gt;</span><span class="p">(</span>
    <span class="nx">ᚠ</span><span class="o">=+</span><span class="p">[],</span>
    <span class="nx">ᚢ</span><span class="o">=+!!</span><span class="p">[],</span>
    <span class="nx">ᚦ</span><span class="o">=</span><span class="nx">ᚢ</span><span class="o">+</span><span class="nx">ᚢ</span><span class="p">,</span>
    <span class="nx">ᚨ</span><span class="o">=</span><span class="nx">ᚦ</span><span class="o">+</span><span class="nx">ᚦ</span><span class="p">,</span>
    <span class="p">...</span>
</code></pre></div></div>

<p>the entire file is written with Elder Futhark runic characters as variable names - a variant of JSFuck where <code class="language-plaintext highlighter-rouge">0</code> and <code class="language-plaintext highlighter-rouge">1</code> are derived from <code class="language-plaintext highlighter-rouge">+[]</code> and <code class="language-plaintext highlighter-rouge">+!![]</code>, and everything else is built up from there. the actual logic is buried inside a massive string that gets <code class="language-plaintext highlighter-rouge">eval</code>‘d.</p>

<p>the file is ~2.6MB of this.</p>

<h2 id="-deobfuscation">:: deobfuscation</h2>

<p>the runic encoding is just cosmetic obfuscation on top of JSFuck-style arithmetic. the pattern:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">ᚠ</span><span class="o">=+</span><span class="p">[],</span>       <span class="c1">// 0</span>
<span class="nx">ᚢ</span><span class="o">=+!!</span><span class="p">[],</span>     <span class="c1">// 1</span>
<span class="nx">ᚦ</span><span class="o">=</span><span class="nx">ᚢ</span><span class="o">+</span><span class="nx">ᚢ</span><span class="p">,</span>      <span class="c1">// 2</span>
<span class="nx">ᚨ</span><span class="o">=</span><span class="nx">ᚦ</span><span class="o">+</span><span class="nx">ᚦ</span><span class="p">,</span>      <span class="c1">// 4</span>
<span class="nx">ᚱ</span><span class="o">=</span><span class="nx">ᚨ</span><span class="o">+</span><span class="nx">ᚨ</span><span class="p">,</span>      <span class="c1">// 8</span>
<span class="p">...</span>
</code></pre></div></div>

<p>all variables are just integer constants. with find/replace substitution and some cleanup you get <code class="language-plaintext highlighter-rouge">deobfuscated.js</code>, which shows the real structure: the script builds two big <code class="language-plaintext highlighter-rouge">Uint8Array</code>s (<code class="language-plaintext highlighter-rouge">_c1</code> and <code class="language-plaintext highlighter-rouge">_d2</code>) used as lookup tables, then dynamically decrypts and instantiates three WebAssembly modules.</p>

<p>the decryption uses a small block cipher built from the lookup tables - each WASM binary is stored encrypted inside the JS and decrypted at runtime before <code class="language-plaintext highlighter-rouge">WebAssembly.instantiate</code> is called.</p>

<h2 id="-extracting-the-wasm">:: extracting the WASM</h2>

<p>the patched version adds:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">_Mod</span><span class="o">=</span><span class="nx">WebAssembly</span><span class="p">.</span><span class="nx">Module</span><span class="p">,</span> <span class="nx">_Inst</span><span class="o">=</span><span class="nx">WebAssembly</span><span class="p">.</span><span class="nx">Instance</span><span class="p">;</span>
</code></pre></div></div>

<p>and instruments the WASM write path to dump the decrypted bytes to disk:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span><span class="p">(</span><span class="nx">wb</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="o">&amp;&amp;</span><span class="nx">wb</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">===</span><span class="mh">0x61</span><span class="o">&amp;&amp;</span><span class="nx">wb</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="o">===</span><span class="mh">0x73</span><span class="o">&amp;&amp;</span><span class="nx">wb</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="o">===</span><span class="mh">0x6d</span><span class="p">){</span>
    <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">fs</span><span class="dl">"</span><span class="p">).</span><span class="nf">writeFileSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">wasm_</span><span class="dl">"</span><span class="o">+</span><span class="p">(</span><span class="nx">globalThis</span><span class="p">.</span><span class="nx">__wn</span><span class="o">++</span><span class="p">)</span><span class="o">+</span><span class="dl">"</span><span class="s2">.wasm</span><span class="dl">"</span><span class="p">,</span> <span class="nx">Buffer</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">wb</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>running it drops three files: <code class="language-plaintext highlighter-rouge">wasm_1.wasm</code>, <code class="language-plaintext highlighter-rouge">wasm_2.wasm</code>, <code class="language-plaintext highlighter-rouge">wasm_3.wasm</code>.</p>

<h2 id="-wasm-analysis">:: wasm analysis</h2>

<p><strong>wasm_1</strong> - the primitive layer<br />
exports: <code class="language-plaintext highlighter-rouge">cmix</code>, <code class="language-plaintext highlighter-rouge">xs</code>, <code class="language-plaintext highlighter-rouge">memory</code><br />
the <code class="language-plaintext highlighter-rouge">start</code> function runs on instantiation and uses an xorshift PRNG (<code class="language-plaintext highlighter-rouge">xs</code>) seeded with a constant to initialize three lookup tables in memory:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">[64]</code> - 256-byte S-box (primary substitution)</li>
  <li><code class="language-plaintext highlighter-rouge">[320]</code> - 256-byte S-box (secondary substitution)</li>
  <li><code class="language-plaintext highlighter-rouge">[576]</code> - 16 x i32 key schedule words</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">cmix</code> is the mixing primitive: <code class="language-plaintext highlighter-rouge">(a + b) rotl 7 xor (b rotl 5)</code>.</p>

<p><strong>wasm_2</strong> - the check functions<br />
imports <code class="language-plaintext highlighter-rouge">cmix</code> and <code class="language-plaintext highlighter-rouge">memory</code> from wasm_1, exports: <code class="language-plaintext highlighter-rouge">derive</code>, <code class="language-plaintext highlighter-rouge">transcript</code>, <code class="language-plaintext highlighter-rouge">check_a</code> through <code class="language-plaintext highlighter-rouge">check_c</code>, <code class="language-plaintext highlighter-rouge">cross_ac</code>, <code class="language-plaintext highlighter-rouge">cross_ab</code>, <code class="language-plaintext highlighter-rouge">check_tr</code>, <code class="language-plaintext highlighter-rouge">transcript2</code></p>

<p><code class="language-plaintext highlighter-rouge">derive</code> walks 48 bytes at memory[0] with the key schedule and writes 48 derived bytes to memory[640].</p>

<p><code class="language-plaintext highlighter-rouge">transcript</code> and friends each compute a 32-bit hash of memory regions, mixing through <code class="language-plaintext highlighter-rouge">cmix</code> and the S-boxes. each function focuses on a different byte range and rotation of the running state.</p>

<p><strong>wasm_3</strong> - the final checker<br />
imports everything from wasm_1 and wasm_2. the key function is <code class="language-plaintext highlighter-rouge">check(ptr, len)</code>:</p>

<pre><code class="language-wat">; must be exactly 48 bytes
local.get 1
global.get 0   ; 48
i32.ne
if ... return 0 ...

; hash the input, derive key material
call derive
call transcript → t

; check all six conditions (OR'd together - any nonzero means wrong)
(688[i32] XOR 712[i32] XOR t rotl 0) XOR check_a  →  OR into acc
(692[i32] XOR 716[i32] XOR t rotl 5) XOR check_b  →  OR into acc
(696[i32] XOR 720[i32] XOR t rotl 11) XOR check_c  →  OR into acc
...

; plus transcript2 XOR 800[i32]

acc == 0  →  return 1  (correct)
</code></pre>

<p>the constants at memory[688..736] and [712..736] are hardcoded in wasm_1’s data sections, placed there at startup. the checks are essentially: “does the hash of your password, mixed through our S-box chain, match all six hardcoded values simultaneously?”</p>

<h2 id="-getting-the-flag">:: getting the flag</h2>

<p>since all checks are independent 32-bit comparisons that get OR’d, and the derive/transcript functions are purely deterministic, this is a one-way verifier; you can’t algebraically invert it.</p>

<p>the approach: patch <code class="language-plaintext highlighter-rouge">check</code> to print the value of each check result at each step, then look for what input makes them all zero. since the flag format is <code class="language-plaintext highlighter-rouge">TISCDCSG{...}</code> and the length must be exactly 48 we know the prefix. running the patched checker with the known prefix and brute-forcing the unknown segment (or using the fact that the check functions each only depend on a small slice of the derived bytes) lets you recover it chunk by chunk.</p>

<p>alternatively: patch the <code class="language-plaintext highlighter-rouge">check</code> export to always return 1 and just try to get the script to print the flag - but the flag is never stored plaintext, it’s the password itself. the correct password <strong>is</strong> the flag, so:</p>

<blockquote>
  <p>if <code class="language-plaintext highlighter-rouge">check(input, 48) == 1</code>, then <code class="language-plaintext highlighter-rouge">input</code> is the flag.</p>
</blockquote>

<p>running the solver gives: <strong><code class="language-plaintext highlighter-rouge">TISCDCSG{the_f1ag_ch0sen_speci4lly_for_th3_wasm}</code></strong></p>

<h2 id="-tldr">:: tldr</h2>

<ol>
  <li>runic JSFuck → deobfuscate to get the real script</li>
  <li>patch JS to dump the three dynamically-decrypted WASM blobs</li>
  <li><code class="language-plaintext highlighter-rouge">wasm2wat</code> each blob to read the logic</li>
  <li>wasm_1 builds S-boxes via xorshift, wasm_2 implements hash checks, wasm_3 is the final verifier</li>
  <li>the correct 48-byte password is itself the flag</li>
</ol>]]></content><author><name>llgsjsm</name></author><category term="ctf" /><summary type="html"><![CDATA[a runic-obfuscated JavaScript challenge that hides 3 WebAssembly modules implementing a custom password checker]]></summary></entry><entry><title type="html">CVE-2026-3008: Format String Injection in Notepad++ via nativeLang.xml</title><link href="https://llgsjsm.github.io/cve-2026-3008/" rel="alternate" type="text/html" title="CVE-2026-3008: Format String Injection in Notepad++ via nativeLang.xml" /><published>2026-04-14T00:00:00+00:00</published><updated>2026-04-14T00:00:00+00:00</updated><id>https://llgsjsm.github.io/cve-2026-3008</id><content type="html" xml:base="https://llgsjsm.github.io/cve-2026-3008/"><![CDATA[<p>a format string injection vulnerability in Notepad++ 8.9.3 allows an attacker to cause a reliable crash (DoS) or leak stack/register contents via a malicious language pack</p>

<p><a href="https://github.com/llgsjsm/cve-2026-3008">github: llgsjsm/cve-2026-3008</a></p>

<p><img src="/images/cve-2026-3008-banner.png" alt="banner" /></p>

<h2 id="-summary">:: summary</h2>

<p>on a casual thursday, i decided to pass some time by looking into some popular applications instead of doom scrolling my life away. notepad++ was one of them, we get on with tinkering shall we??</p>

<table>
  <tbody>
    <tr>
      <td><strong>Product</strong></td>
      <td>Notepad++</td>
    </tr>
    <tr>
      <td><strong>Version</strong></td>
      <td>8.9.3 (x64, installer and portable)</td>
    </tr>
    <tr>
      <td><strong>Attack Vector</strong></td>
      <td>Malicious language pack (<code class="language-plaintext highlighter-rouge">nativeLang.xml</code>)</td>
    </tr>
    <tr>
      <td><strong>Impact</strong></td>
      <td>Reliable DoS (crash) + information disclosure (stack/register leak)</td>
    </tr>
  </tbody>
</table>

<h2 id="-adversarial-pov">:: adversarial pov</h2>

<p>i can just distribute the malicious nativeLang.xml and pass it off as a legitimate language pack in the npp community forums. when the user unknowingly load up his npp, and tries to search a matching term – it will crash every. single. time.</p>

<p><strong>best part? a normal user wouldnt even know why it crashes</strong></p>

<p>asides causing mischief from crashing however, this exploit can expose registers/stack values. with a write primitive you can… (or maybe not)</p>

<p><img src="/images/cve-2026-3008-stack_leak.png" alt="stack information leak" /></p>

<h2 id="-affected-component">:: affected component</h2>

<p><strong>function:</strong> <code class="language-plaintext highlighter-rouge">sub_1400916C0</code> — Find Results panel initializer<br />
<strong>vulnerable instruction:</strong> <code class="language-plaintext highlighter-rouge">0x140091E6D</code><br />
<strong>triggered by:</strong> any search operation that produces results (Find All, Find in Files, Mark All)</p>

<p><img src="/images/cve-2026-3008-decompiled1.png" alt="decompiled component" /></p>

<h2 id="-root-cause">:: root cause</h2>

<p>sub_140099E60 retrieves an attacker-controlled string from nativeLang.xml and places it in v38.</p>

<p><code class="language-plaintext highlighter-rouge">wsprintfW</code> is then called with v38 as the format string argument, not as a data argument — so any format specifiers in the XML value (<code class="language-plaintext highlighter-rouge">%s</code>, <code class="language-plaintext highlighter-rouge">%x</code>) are interpreted by wsprintfW.</p>

<p><img src="/images/cve-2026-3008-pseudocode.png" alt="root-cause" /></p>

<p>the string originates from the <code class="language-plaintext highlighter-rouge">&lt;find-result-hits&gt;</code> attribute in <code class="language-plaintext highlighter-rouge">nativeLang.xml</code> with <strong>no validation</strong> at any point in the data flow:
<img src="/images/cve-2026-3008-dataflow.svg" alt="dataflow" /></p>
<h2 id="-impact">:: impact</h2>

<p><strong>crash (denial of service):</strong> the payload <code class="language-plaintext highlighter-rouge">%s%s%s%s%s%s%s%s</code> causes an immediate access violation since <code class="language-plaintext highlighter-rouge">wsprintfW</code> dereferences junk register/stack values as <code class="language-plaintext highlighter-rouge">WCHAR*</code> pointers, hitting an invalid address. tested and confirmed on notepad++ 8.9.3 x64</p>

<p><strong>information disclosure:</strong> <code class="language-plaintext highlighter-rouge">%x</code> and <code class="language-plaintext highlighter-rouge">%08lx</code> specifiers leak stack and register contents into the Find Results panel tab. heres the output for <code class="language-plaintext highlighter-rouge">%08lx</code>:</p>

<p><img src="/images/cve-2026-3008-stack_leak2.png" alt="data-leak" /></p>

<p><code class="language-plaintext highlighter-rouge">wsprintfW</code> does not support <code class="language-plaintext highlighter-rouge">%n</code>, so there is no write primitive hence theres no avenue for code execution (sad)</p>

<h2 id="-poc-or-didnt-happen">:: poc or didnt happen</h2>

<p>create <code class="language-plaintext highlighter-rouge">nativeLang.xml</code> with this payload:
<img src="/images/cve-2026-3008-nativelang.png" alt="poc" /></p>

<p>place it at:</p>
<ul>
  <li><strong>Portable:</strong> <code class="language-plaintext highlighter-rouge">&lt;npp_directory&gt;\nativeLang.xml</code></li>
  <li><strong>Installer:</strong> <code class="language-plaintext highlighter-rouge">%APPDATA%\Notepad++\nativeLang.xml</code></li>
</ul>

<p>then open Notepad++, search for any text, and click <strong>Find All in Current Document</strong>. Notepad++ crashes immediately</p>

<h2 id="-timeline">:: timeline</h2>

<table>
  <thead>
    <tr>
      <th>Date</th>
      <th>Event</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>2026-04-09</td>
      <td>format string injection confirmed - <code class="language-plaintext highlighter-rouge">%s</code> crash, <code class="language-plaintext highlighter-rouge">%lx</code> info disclosure</td>
    </tr>
    <tr>
      <td>2026-04-10</td>
      <td>figured <code class="language-plaintext highlighter-rouge">wsprintfW</code> has hardcoded 1024-char output limit</td>
    </tr>
    <tr>
      <td>2026-04-10</td>
      <td>submitted vulnerability to CSA</td>
    </tr>
    <tr>
      <td>2026-04-16</td>
      <td>CSA reached out to Notepad++</td>
    </tr>
    <tr>
      <td>2026-04-27</td>
      <td>CVE-2026-3008 assigned</td>
    </tr>
  </tbody>
</table>]]></content><author><name>llgsjsm</name></author><category term="cve" /><summary type="html"><![CDATA[format string injection in Notepad++ 8.9.3 via a malicious nativeLang.xml - DoS and stack/register leak.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://llgsjsm.github.io/images/cve-2026-3008-banner.png" /><media:content medium="image" url="https://llgsjsm.github.io/images/cve-2026-3008-banner.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">hello:WORLD</title><link href="https://llgsjsm.github.io/hello-world/" rel="alternate" type="text/html" title="hello:WORLD" /><published>2025-12-14T00:00:00+00:00</published><updated>2025-12-14T00:00:00+00:00</updated><id>https://llgsjsm.github.io/hello-world</id><content type="html" xml:base="https://llgsjsm.github.io/hello-world/"><![CDATA[<p>welcome to my info dumps. will be documenting my work in vulnerability research and probably writing some cybersecurity concepts worth putting out there</p>

<h2 id="whats-this-about">whats this about??</h2>

<ul>
  <li><strong>vulnerability writeups</strong> — breaking down bugs I’ve found or studied, how they work</li>
  <li><strong>security concepts</strong> — deeper looks at topics like new cves, techniques or really something interesting</li>
  <li><strong>AI</strong> — yes the fourth (fifth?) industrial revolution. im gonna dabble in ai alot so might as well document them right?</li>
</ul>]]></content><author><name>llgsjsm</name></author><category term="meta" /><summary type="html"><![CDATA[welcome to my info dumps. will be documenting my work in vulnerability research and probably writing some cybersecurity concepts worth putting out there]]></summary></entry></feed>