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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →