How Reverse-Skill's DSL VM Identifies and Analyzes Custom Instruction Set VMs

Reverse-Skill detects custom DSL/VM instruction sets by pattern matching for IIFE-wrapped JavaScript interpreters with single-letter variable names, switch-case opcode dispatchers, and embedded constant pools, then reconstructs the instruction semantics through runtime state capture.

The reverse-skill framework provides a structured approach to reverse-engineering obfuscated JavaScript-based virtual machines commonly found in captcha systems, DRM protections, and proprietary web applications. This article examines how the DSL VM skill specifically identifies and analyzes these custom instruction set architectures, based on the implementation in skills/reverse-engineering/dsl-vm-reverse/SKILL.md.


DSL VM Identification Features

The DSL VM skill defines seven core identification heuristics that distinguish custom instruction set VMs from standard JavaScript code. These patterns reflect common implementation choices made by developers of obfuscated execution environments.

IIFE Wrapper Detection

Custom DSL VMs typically wrap their entire interpreter in an Immediately-Invoked Function Expression to isolate scope and hide entry points. The skill looks for patterns matching !function(...){…}() or (function(){…})() at the top level of the source.

// Detection heuristic from the SKILL.md implementation
function isIIFE(source) {
  const iifePattern = /^\s*\(?function\s*\([^)]*\)\s*\{[\s\S]*\}\s*\)\s*\(/m;
  return iifePattern.test(source);
}

This isolation prevents direct inspection of internal VM state from the global scope, forcing analysts to either hook the IIFE before execution or instrument the runtime environment.

Single-Letter Variable Obfuscation

Minified or intentionally obfuscated code relies heavily on short, meaningless identifiers. The skill flags files where variables like a, b, c, x, y, z appear with high frequency and multiple reassignment patterns.

// Variable entropy analysis — short identifiers dominate DSL VMs
function hasObfuscatedVariables(source) {
  const shortVarPattern = /\b[a-zA-Z]\b/g;
  const matches = source.match(shortVarPattern) || [];
  const uniqueShortVars = new Set(matches);
  // Threshold: more than 15 unique single-letter variables suggests obfuscation
  return uniqueShortVars.size > 15;
}

This pattern indicates intentional name collision and scope minimization, hallmarks of VM implementations designed to resist static analysis.

Switch-Case Opcode Dispatcher

The defining architectural feature of a DSL VM is the central dispatcher function containing a switch statement that routes execution based on numeric opcodes. The skill identifies this through pattern matching for switch statements controlled by short variable names, often in functions named DG, _0x, or similar.

// Find the dispatcher — the heart of the VM
function findDispatcher(source) {
  const dispatcherPattern = /function\s+([A-Za-z0-9_]+)\s*\([^)]*\)\s*\{[^}]*switch\s*\(\s*([a-z])\s*\)/m;
  const match = dispatcherPattern.exec(source);
  return match ? { fnName: match[1], ctrlVar: match[2] } : null;
}

Each case in this switch implements a custom instruction: arithmetic operations, memory access, control flow, or external API calls. The dispatcher's control variable typically holds the current opcode, fetched sequentially from a bytecode array or calculated dynamically.


Opcode Table and Constant Pool Extraction

Once the dispatcher is located, the skill extracts two critical data structures that reveal the VM's instruction semantics.

Opcode Mapping Tables

DSL VMs store their instruction metadata in array or object literals that map numeric codes to implementation details. The skill searches for patterns like const O=[…] or const _0xabc={…} that appear co-located with the dispatcher.

// Extract the opcode table — reveals instruction set
function extractOpcodeTable(source) {
  const tablePattern = /const\s+([A-Za-z0-9_]+)\s*=\s*(\[[^\]]*\]|\{[^}]*\})/m;
  const match = tablePattern.exec(source);
  if (!match) return null;
  try {
    // Safe evaluation of literal arrays/objects only
    return eval(`(${match[2]})`);
  } catch (_) {
    return null;
  }
}

The extracted table typically contains:

  • String literals used for property access or API calls
  • Function references implementing complex operations
  • Numeric parameters controlling loop bounds or buffer sizes

Constant Pool Decoding

Many DSL VMs embed encoded data in large string or numeric arrays that are decoded at runtime. The skill identifies these pools by their size (often hundreds of elements) and their access patterns throughout the dispatcher.

// Detect constant pool by access frequency analysis
function findConstantPool(source) {
  const largeArrayPattern = /const\s+([A-Za-z_]\w*)\s*=\s*\[[^\]]{500,}\]/;
  const match = largeArrayPattern.exec(source);
  return match ? match[1] : null;
}

Decoding the constant pool often requires executing the VM's initialization code or reverse-engineering its decoding routine, which the skill handles through controlled runtime instrumentation.


Runtime State Capture and Analysis

Static analysis alone cannot fully reconstruct DSL VM semantics. The skill implements runtime state capture to observe the VM's internal registers during execution.

Dispatcher Instrumentation

Using Playwright or direct browser console injection, the skill wraps the dispatcher function to intercept every instruction fetch:

// Playwright-based runtime instrumentation
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  
  // Expose callback for state snapshots
  await page.exposeFunction('onDispatcher', (state) => {
    console.log('VM state:', state);
  });
  
  // Hook the dispatcher (name identified earlier)
  await page.evaluate(() => {
    const originalDG = DG;
    DG = function(...args) {
      const result = originalDG.apply(this, args);
      window.onDispatcher({
        opcode: args[0],
        registers: window._vmRegs, // Common VM register object
        stack: window._vmStack,
        result: result
      });
      return result;
    };
  });
  
  await page.goto('https://target-site.com/obfuscated-vm.js');
  // Trigger VM execution through user interaction
  await page.click('#captcha-button');
  
  await browser.close();
})();

This instrumentation captures:

  • The instruction pointer progression through the bytecode
  • Register values before and after each operation
  • Stack manipulations for function calls and local storage
  • External API calls made by the VM to the host environment

Export Reconstruction

The final analysis phase recovers the VM's externally visible interface. DSL VMs typically register their generated functions through calls like register("apiName", implementation) or similar patterns.

// Reconstruct exported API surface
function findExports(source) {
  const exportPattern = /register\(\s*['"]([^'"]+)['"]\s*,\s*([A-Za-z0-9_]+)\s*\)/g;
  const exports = {};
  let match;
  while ((match = exportPattern.exec(source)) !== null) {
    exports[match[1]] = match[2];
  }
  return exports;
}

These exports reveal how the surrounding application interacts with the VM, providing entry points for deeper analysis or alternative implementations.


Skill Routing and Execution Flow

The DSL VM skill integrates into the broader reverse-skill framework through the routing system defined in skills/routing.md and skills/MASTER-ROUTING.md.

Stage File Purpose
Routing Decision skills/routing.md Maps "DSL VM / custom JS opcode VM" labels to the DSL VM skill
Skill Definition skills/reverse-engineering/dsl-vm-reverse/SKILL.md Contains full identification heuristics and analysis procedures
Case Study skills/field-journal/2026-07-05_dsl-vm-captcha-reverse.md Documents real-world application to a 26-opcode captcha VM
Architecture docs/ARCHITECTURE.md Describes framework integration points

When triggered, the skill executes sequentially through identification, extraction, instrumentation, and reconstruction phases, producing a standardized reverse-engineering report.


Summary

  • IIFE wrappers and single-letter variables are the primary surface indicators of DSL VM obfuscation
  • Switch-case dispatchers with numeric opcode control reveal the VM's central execution mechanism
  • Opcode tables and constant pools encode the instruction semantics and embedded data
  • Runtime instrumentation through dispatcher hooking captures internal register states
  • Export reconstruction exposes the VM's external API surface for further analysis

The DSL VM skill in reverse-skill codifies these techniques into a reproducible workflow, as demonstrated in the field journal case study of a production captcha system.


Frequently Asked Questions

What makes DSL VMs different from standard JavaScript obfuscation?

DSL VMs implement custom instruction sets with their own opcodes, registers, and memory models, whereas standard obfuscation merely transforms JavaScript syntax while preserving the underlying language semantics. According to the reverse-skill source code, DSL VMs use switch-case dispatchers that interpret numeric bytecode rather than executing native JavaScript directly.

How does reverse-skill handle VMs with dynamically generated opcodes?

The runtime state capture mechanism intercepts dispatcher execution regardless of opcode generation method. By hooking the central switch-case function, the skill records actual opcode values as they are computed, handling both static bytecode arrays and dynamically calculated instruction sequences.

Can the DSL VM skill analyze WebAssembly-based VMs?

The current skill focuses on JavaScript-implemented interpreters. WebAssembly VMs require different analysis techniques involving WASM disassembly and memory inspection, which are not covered in skills/reverse-engineering/dsl-vm-reverse/SKILL.md.

What information does the opcode table extraction reveal about a protected application?

The opcode table exposes which operations the VM can perform (arithmetic, crypto, DOM manipulation) and which external APIs it can access. This reveals the functional capabilities of the protected code without requiring full deobfuscation, enabling targeted security assessments.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →