<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Soltrix Studios</title>
    <description>The latest articles on DEV Community by Soltrix Studios (@soltrix_studios).</description>
    <link>https://dev.to/soltrix_studios</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4244625%2F5bc4c487-d306-44d3-a328-ddf1eff1899a.png</url>
      <title>DEV Community: Soltrix Studios</title>
      <link>https://dev.to/soltrix_studios</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/soltrix_studios"/>
    <language>en</language>
    <item>
      <title>How We Built a 3-Layer JS + WebAssembly Crackme to Stress-Test Code Defense Against AI Decompilers</title>
      <dc:creator>Soltrix Studios</dc:creator>
      <pubDate>Sat, 14 Nov 2026 14:35:21 +0000</pubDate>
      <link>https://dev.to/soltrix_studios/how-we-built-a-3-layer-js-webassembly-crackme-to-stress-test-code-defense-against-ai-decompilers-27bg</link>
      <guid>https://dev.to/soltrix_studios/how-we-built-a-3-layer-js-webassembly-crackme-to-stress-test-code-defense-against-ai-decompilers-27bg</guid>
      <description>&lt;p&gt;Traditional JavaScript and WebAssembly obfuscators rely on lexical cosmetic tricks: renaming variables to &lt;code&gt;_0x4a2b&lt;/code&gt;, base64-encoding string arrays, and wrapping functions in simple switch statements.&lt;/p&gt;

&lt;p&gt;While those techniques slow down casual inspection in browser DevTools, modern static semantic graph extractors (&lt;code&gt;morluto/rea&lt;/code&gt;), native decompilers (&lt;code&gt;NSA Ghidra&lt;/code&gt;, &lt;code&gt;WABT wasm-decompile&lt;/code&gt;), and LLM-driven reverse-engineering agents slice through them in seconds. An LLM does not care what a variable is named; it reads the data-flow graph and reconstructs clean source code.&lt;/p&gt;

&lt;p&gt;To solve this at the compiler level, we built &lt;strong&gt;Syferit&lt;/strong&gt; (&lt;a href="https://github.com/soltrix-studios/syferit" rel="noopener noreferrer"&gt;github.com/soltrix-studios/syferit&lt;/a&gt;), an open-core (&lt;code&gt;v1.0.2&lt;/code&gt;) multi-target code defense engine for &lt;strong&gt;JavaScript/TypeScript, WebAssembly, Python, and C/C++ (LLVM)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of asking developers to take our word for it, we shipped a live 60 FPS browser game and 3-layer reverse-engineering crackme: &lt;strong&gt;SYFER-DEFENDER: The Three-Layer Override&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Play the Live 60 FPS Browser Challenge:&lt;/strong&gt; &lt;a href="https://syferit.soltrixstudios.com/challenge" rel="noopener noreferrer"&gt;https://syferit.soltrixstudios.com/challenge&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download the Offline Crackme Bundle (&lt;code&gt;.wasm&lt;/code&gt; + &lt;code&gt;.js&lt;/code&gt; + CLI):&lt;/strong&gt; &lt;a href="https://syferit.soltrixstudios.com/challenge/syferit-defender-crackme.zip" rel="noopener noreferrer"&gt;https://syferit.soltrixstudios.com/challenge/syferit-defender-crackme.zip&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect the Decompiler &amp;amp; Agent Receipts:&lt;/strong&gt; &lt;a href="https://syferit.soltrixstudios.com/#benchmarks" rel="noopener noreferrer"&gt;https://syferit.soltrixstudios.com/#benchmarks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository (&lt;code&gt;v1.0.2&lt;/code&gt;):&lt;/strong&gt; &lt;a href="https://github.com/soltrix-studios/syferit" rel="noopener noreferrer"&gt;https://github.com/soltrix-studios/syferit&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Cosmetic Obfuscation Fails Against Static Semantic Graphs
&lt;/h2&gt;

&lt;p&gt;When an automated reverse-engineering pipeline like &lt;code&gt;morluto/rea&lt;/code&gt; (&lt;code&gt;rea-agents&lt;/code&gt;) or &lt;code&gt;wasm-decompile&lt;/code&gt; analyzes a binary, it follows three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Constant &amp;amp; Literal Extraction:&lt;/strong&gt; Recovering string tables, API routes, and magic numbers from AST literals or Python &lt;code&gt;co_consts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Call-Graph &amp;amp; Object-Flow Resolution:&lt;/strong&gt; Mapping which functions call which targets and tracking property reads across objects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symbolic Simplification:&lt;/strong&gt; Using algebraic rules or SMT solvers to fold dead branches and recover the underlying &lt;code&gt;if (input === secret)&lt;/code&gt; comparison.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If a transformation is algebraically invertible, an automated pass or an LLM agent will invert it.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Syferit Hardens Code Across 3 Layers
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;SYFER-DEFENDER&lt;/strong&gt;, the game's weapon tiers and final flag decryption are locked behind three independent 128-bit credentials (&lt;code&gt;OVERRIDE ALPHA&lt;/code&gt;, &lt;code&gt;OVERRIDE BETA&lt;/code&gt;, and &lt;code&gt;OVERRIDE OMEGA&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: Non-Invertible Algebraic Invariants (&lt;code&gt;@syferit/core&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Instead of storing target values or reversible XOR masks in JavaScript, &lt;code&gt;@syferit/core&lt;/code&gt; lowers equality checks into 32-bit FNV-1a modular multiplication chains (&lt;code&gt;Math.imul&lt;/code&gt;) entangled with cyclic state-machine dispatchers.&lt;/p&gt;

&lt;p&gt;Rather than checking &lt;code&gt;x === C&lt;/code&gt;, the engine verifies that &lt;code&gt;x&lt;/code&gt; satisfies a modular residue invariant while simultaneously using the intermediate 32-bit state to compute the next basic-block jump target.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: WebAssembly MBA &amp;amp; Frame Expansion (&lt;code&gt;@syferit/wasm&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The second credential (&lt;code&gt;OVERRIDE BETA&lt;/code&gt;) is verified inside a compiled WebAssembly binary (&lt;code&gt;enclave.wasm&lt;/code&gt; -&amp;gt; &lt;code&gt;verify_beta(b0, b1, b2, b3)&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@syferit/wasm&lt;/code&gt; parses the raw &lt;code&gt;.wasm&lt;/code&gt; binary (&lt;code&gt;99 B&lt;/code&gt; -&amp;gt; &lt;code&gt;832 B&lt;/code&gt;), expands local register frames (&lt;code&gt;local i32 x 6&lt;/code&gt;), rewrites scalar checks into multi-instruction Mixed Boolean-Arithmetic (MBA) expressions, and splits execution across coupled state-machine functions in the WASM function table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: Cross-Boundary Cryptographic Coupling
&lt;/h3&gt;

&lt;p&gt;Unlocking all three credentials combines the live execution states from both &lt;code&gt;vault-engine.js&lt;/code&gt; and &lt;code&gt;enclave.wasm&lt;/code&gt; into a 384-bit stream cipher key that decrypts the final flag in memory. Patching a boolean return value (&lt;code&gt;return true&lt;/code&gt;) in DevTools will not work because the wrong mathematical residue produces garbage bytes during stream decryption.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verifiable Telemetry Receipts
&lt;/h2&gt;

&lt;p&gt;You can reproduce these exact results locally on the challenge bundle (&lt;code&gt;syferit-defender-crackme.zip&lt;/code&gt;):&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;morluto/rea&lt;/code&gt; (&lt;code&gt;rea-agents&lt;/code&gt;) Static Semantic Graph Analysis
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx rea-agents analyze-javascript-application ./dist --format json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Evidence ID:&lt;/strong&gt; &lt;code&gt;ev_e944fd12a1d8fd7b45afb4ff7ef2be85eea9262dcf32b8d000ccfee8a06cca29&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage Status:&lt;/strong&gt; &lt;code&gt;"unknown"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graph Telemetry:&lt;/strong&gt; &lt;code&gt;2,722&lt;/code&gt; nodes | &lt;code&gt;2,387&lt;/code&gt; relations | &lt;strong&gt;&lt;code&gt;788&lt;/code&gt; unresolved unknowns&lt;/strong&gt; (&lt;code&gt;324&lt;/code&gt; object-flow, &lt;code&gt;263&lt;/code&gt; call-flow, &lt;code&gt;198&lt;/code&gt; data-flow)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top Unknown Reasons:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;213x call-flow :: dynamic-call ("Unsupported MemberExpression call target.")&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;171x object-flow :: ambiguous-target ("Static read property /imul has unknown-coverage presence")&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;36x object-flow :: ambiguous-target ("Static read property /seats has unknown-coverage presence" [Architectural Gaslighting decoy])&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AST &amp;amp; CFG Fingerprint Collision:&lt;/strong&gt; Distinct state-machine functions compile to the exact same &lt;code&gt;normalized_ast_sha256&lt;/code&gt; (&lt;code&gt;e80a4d87...&lt;/code&gt;) and &lt;code&gt;control_flow_sha256&lt;/code&gt; (&lt;code&gt;79ad94b5...&lt;/code&gt;) hashes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Literals Recovered:&lt;/strong&gt; &lt;code&gt;6&lt;/code&gt; total across all 2,722 nodes, and &lt;code&gt;6/6&lt;/code&gt; are injected &lt;code&gt;EULA_TITLE_17_PROPRIETARY&lt;/code&gt; / &lt;code&gt;DMCA_TITLE_17_SEC_1201_NOTICE&lt;/code&gt; strings (&lt;code&gt;0&lt;/code&gt; secret constants recovered).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;WABT 1.0.42&lt;/code&gt; (&lt;code&gt;wasm-objdump -x -d&lt;/code&gt; / &lt;code&gt;wasm-decompile&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wasm-objdump -x -d enclave.wasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Binary Transition:&lt;/strong&gt; &lt;code&gt;99 B&lt;/code&gt; -&amp;gt; &lt;code&gt;832 B&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static Constant Scan:&lt;/strong&gt; &lt;code&gt;0&lt;/code&gt; plaintext key constants or target residues present in the binary or decompiled pseudocode.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try the Crackme or Integrate Syferit in 60 Seconds
&lt;/h2&gt;

&lt;p&gt;If you enjoy reverse engineering, grab the offline bundle or open the live browser challenge and see if you can deterministically invert the 32-bit modular arithmetic to recover &lt;code&gt;ALPHA&lt;/code&gt;, &lt;code&gt;BETA&lt;/code&gt;, and &lt;code&gt;OMEGA&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live Challenge:&lt;/strong&gt; &lt;a href="https://syferit.soltrixstudios.com/challenge" rel="noopener noreferrer"&gt;https://syferit.soltrixstudios.com/challenge&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub &amp;amp; Documentation:&lt;/strong&gt; &lt;a href="https://github.com/soltrix-studios/syferit" rel="noopener noreferrer"&gt;https://github.com/soltrix-studios/syferit&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or try the CLI directly on your own JS, WASM, Python, or C/C++ project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @syferit/cli harden ./src/vault.js -o ./dist/vault.protected.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feedback, questions, and crackme write-ups are very welcome in the comments!&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>webassembly</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
