dsl-vm-reverse について
JavaScript 製のカスタム DSL 仮想マシンやリスク制御エンジンを解析するリバースエンジニアリング向けスキルです。標準 WASM や通常のバンドルと区別しながら、ファイル判定、opcode と定数テーブルの分析、エクスポート関数の追跡、必要に応じたランタイム注入を進めます。
主な機能
- 01カスタム DSL VM の特徴判定
- 02変数マッピングと opcode の抽出
- 03定数テーブル参照の分析
- 04エクスポート関数と呼び出し経路の追跡
- 05静的解析を補うランタイム注入
- 06標準 WASM と通常の JavaScript の区別
活用シーン
- 01不審な圧縮 JavaScript ファイルの分類
- 02カスタムインタープリターの命令構造の分析
- 03リスク制御エンジンのエクスポート関数の特定
- 04静的解析で不足する場合のランタイムデータ取得
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ zhaoxuya520/ reverse-skill --skill dsl-vm-reverseインストール後、Claude Code で/dsl-vm-reverseを入力して読み込まれたか確認します。
詳細
🔄 DSL カスタム仮想マシンリバースエンジニアリング(DSL VM Reverse Engineering)
ACTION REQUIRED(読了後すぐに実行)
NOW: 現在のタスクがカスタム JS opcode VM / リスクコントロールエンジンであり、標準 WASM や通常の webpack ではないことを確認するNOW:scope.mdが整うまでcase-init;オフラインサンプルはoffline/labを使用ACT: 「3. 汎用リバースエンジニアリングワークフロー」の Phase 1 からファイル分類を実施し、ディレクトリで止まらない
JavaScript ベースで実装されたカスタム WASM 仮想マシン / リスクコントロールエンジンをリバースエンジニアリングするために使用します
目次
- 1. 適用範囲
- 2. DSL VM の識別特徴
- 3. 汎用リバースエンジニアリングワークフロー
- 4. Opcode の抽出と分類
- 5. ランタイムキャプチャ方案
- 6. よくあるステータスコード
- 7. Skill セルフチェックリスト
1. 適用範囲
対象ファイルが以下の いずれかの特徴 に該当する場合に本 skill を使用します:
| # | 特徴 | 説明 |
|---|---|---|
| 1 | IIFE 冒頭 + 大量の1文字変数名 | !function(){var U=void 0,y=parseInt,E0=Function,...} |
| 2 | DG() または類似の関数に switch-case ループが含まれる | インタプリタのメインループ、d[7]&31 で opcode をデコード |
| 3 | 大きなファイル(500KB+)だがゼロバイト占有率 < 1% | 非標準 WASM、純粋な JS |
| 4 | C[number] 定数テーブル参照を含む | C[9][xxx] 関数テーブル/文字列テーブル |
| 5 | 単一行に圧縮されたコード | 583KB の単一行、難読化された変数名 |
除外ルール
| 条件 | 本 skill の対象外 | 移動先 |
|---|---|---|
ファイルが \x00asm で始まる | 標準 WASM バイナリ | reverse-engineering/languages.md |
ファイルが Uint8Array([0,97,115,109]) を含み WASM マジックワードがある | WASM 埋め込み | .wasm を抽出後、IDA/Ghidra へ |
標準 Webpack バンドル(function(e,t,n){...}) | 通常の JS | js-reverse/ |
| ゼロバイト占有率 > 20% | WASM バイナリ | reverse-engineering/languages.md |
2. DSL VM の識別特徴
コードの特徴
// 特征 1: IIFE 入口,单字母变量映射数字常量
!function(){
var U=void 0, y=parseInt, E0=Function, AN=Uint8Array;
var E=15, l=10, m=12, x=16, S=13, $=11;
// 数字常量映射为变量名,替代原始数字
...
}
// 特征 2: 解释器主循环 DG()
function DG(C, d, ...) {
var d = []; // 数组模拟 WASM stack/locals
for (d[7] = x; d[7] !== U;) {
var aE = d[7] & 31; // 低 5 位 = opcode
var O = d[7] >> 5 & 31; // 高 5 位 = sub-operation
switch (aE) {
case 0: /* ... */ d[7] = 612; break;
case 1: /* ... */
// ... N 个 case
}
}
}
// 特征 3: 常量表 C[9] 存储函数索引和字符串
// C[9][0] = ["pc"] → 函数参数描述
// C[9][667] = "string" → 字符串常量
// C[9][x] = number → 函数索引
// 特征 4: W(C[index], null, ...) 调用模式
// W = Function.prototype.call.bind(call)
// 所有内置函数通过 C[index] 索引调用
// 特征 5: 指令编码格式
// d[7] = opcode(bit 0-4) | subop(bit 5-9) | operand(bit 10+)
Opcode のエンコード形式
各命令は 32 ビット整数としてエンコードされます:
bit 0-4: opcode (0-N)
bit 5-9: sub-operation (0-31)
bit 10-31: operand/立即数
解码:
aE = d[7] & 31 → opcode
O = d[7] >> 5 & 31 → sub-operation
d[other] = d[7] >> 10 → operand
3. 汎用リバースエンジニアリングワークフロー
Phase 1: ファイル分類(5 分)
# 检查是否为 DSL VM
python3 << 'EOF'
with open('target.js', 'rb') as f:
head = f.read(100)
# 1. 检查 WASM 魔术字
if head[:4] == b'\x00asm':
print("标准 WASM 二进制")
exit()
# 2. 检查零字节占比
data = open('target.js', 'rb').read()
zero_pct = data.count(b'\x00') / len(data) * 100
print(f"零字节占比: {zero_pct:.1f}%")
if zero_pct > 20:
print("WASM 二进制")
elif head[:2] == b'!f':
# 检查单字母变量模式
if b'var U=void 0' in head or b'U=void 0,y=parseInt' in head:
print("→ DSL VM!")
else:
print("普通 JS IIFE")
EOF
Phase 2: 変数マッピングテーブルの抽出(10 分)
import re
with open('target.js', 'r', errors='replace') as f:
s = f.read()
# 提取开头 2000 字符的 var X=数字 映射
mappings = re.findall(r'var\s+(\w+)\s*=\s*(\d+)', s[:2000])
print('常量映射:')
for name, val in mappings:
print(f" {name:4s} = {val:3d} (0x{int(val):02x})")
Phase 3: Opcode の抽出と分類(15 分)
# 1. 提取所有 case
all_cases = re.findall(r'case\s+(\d+):', s)
unique = sorted(set(int(c) for c in all_cases))
print(f"总 case: {len(all_cases)} 个")
print(f"唯一 opcode: {len(unique)} 个: {unique}")
# 2. 分类每个 opcode
for op in unique:
idx = s.find(f'case {op}:')
snippet = s[idx:idx+200]
if 'd[7]=' in snippet:
op_type = 'BRANCH'
elif 'return' in snippet:
op_type = 'RETURN'
elif 'W(C[' in snippet:
op_type = 'CALL'
elif 'new' in snippet:
op_type = 'ALLOC'
elif 'try' in snippet or 'catch' in snippet:
op_type = 'EXCEPTION'
else:
op_type = 'ARITH/STORE'
print(f" opcode {op:2d}: {op_type}")
Phase 4: 定数テーブルの分析(30 分)
const_refs = re.findall(r'C\[9\]\[(\d+)\]', s)
unique_refs = sorted(set(int(x) for x in const_refs))
print(f"C[9] 引用: {len(unique_refs)} 个索引")
print(f"范围: {min(unique_refs)} - {max(unique_refs)}")
# 对每个引用分析上下文
for ref in unique_refs[:20]:
idx = s.find(f'C[9][{ref}]')
ctx = s[max(0,idx-50):idx+80]
clean = ''.join(c if c.isprintable() else ' ' for c in ctx)
print(f" C[9][{ref}] → {clean}")
Phase 5: エクスポート関数の追跡(1〜2 時間)
エクスポート関数(例: getToken)は以下のパスで特定します:
1. 找 AWSCInner.register() 或类似注册调用
2. 确定注册的模块和工厂函数
3. 找工厂函数返回的对象 → 导出函数定义位置
4. 若函数名不在 JS 中 → 在 C[9] 常量表中作字节码存储
5. 追踪调用链:
AWSCInner._modules['fy'].getToken()
→ W(C[函数索引], null, ...)
→ DG() 解释器执行编码后的指令序列
Phase 6: ランタイムインジェクション(純粋な静的解析では不十分な場合)
// 注入最小 AWSC 兼容环境
const fakeEnv = {
AWSCInner: {
_modules: {},
register(name, moduleName, factory) {
this._modules[moduleName] = factory();
}
}
};
// 执行 DSL VM 代码
dslVmCode();
// 获取导出
const token = fakeEnv.AWSCInner._modules['fy'].getToken({});
4. Opcode の抽出と分類
参考 opcode 対照表(既存事例に基づく)
| Opcode | 操作タイプ | 特徴 |
|---|---|---|
| 0 | BRANCH | d[7]=xxx 無条件ジャンプ |
| 1 | CALL | W(C[Y],null,function(){...}) 埋め込み関数呼び出し |
| 2 | ARITH | d[4]=0, d[7]=72 変数代入 |
| 3 | ARITH | d[0]=d[1][C[x]], d[5]=d[0]<d[3] 比較演算 |
| 4 | STORE | d[8]=d[5]in d[4] プロパティアクセス/存在チェック |
| 5 | ARITH | d[8]=d[4]-d[8] 算術演算 |
| 6 | RETURN | return gV, throw 戻り値/例外スロー |
| 7 | ALLOC | d[6]=[], d[6][C[8]](...) push 操作 |
| 8 | BRANCH | d[7]=d[k]?512:425 条件ジャンプ |
| 9 | STRING | d[6][C[t]]=d[m], new fh(...) 正規表現 |
| 10 | ALLOC | 関数引数の準備、コールスタックの作成 |
| 11 | STRING | new fh("\\s",d[5]) 正規表現マッチング |
| 12 | STORE | P[d[9]]=d[4][C[H]](d[3]) データ転送 |
| 13 | CALL | C[9][113]=d[9] モジュール初期化 |
| 14 | STRING | d[8]=d[9]+d[m] 文字列連結 |
| 15 | RETURN | return EL; 関数リターン |
| 16 | ALLOC | var r,P,Z,B... ローカル変数宣言 |
| 17 | ALLOC | (Z=[])[C[8]](69,T,445) 静的配列の初期化 |
| 18 | TABLE | 関数テーブル/型テーブルの初期化 |
| 19 | EXCEPTION | try{for(var RK=x;... try-catch ループ |
| 20 | DOM | Is[d[o]] DOM 操作 |
| 21 | STORE | グローバル/オブジェクトプロパティの安全な取得 |
| 22 | STRING | new fh(r,v) 文字列/正規表現処理 |
| 23 | BRANCH | try...catch 安全な取得 + 条件ジャンプ |
| 24 | CALL | W(C[2],null,8,z,FL) 多引数関数呼び出し |
| 25 | EXCEPTION | try{...}catch(C){...} 例外キャプチャ + ジャンプ |
5. ランタイムキャプチャ方案
方案 A: Selenium + CDP ネイティブイベント(推奨、成功率が最も高い)
from selenium import webdriver
driver = webdriver.Chrome()
# 注入反检测
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": r"""
Object.defineProperty(navigator, 'webdriver', {get: () => false});
Object.defineProperty(navigator, 'plugins', {get: () => [1,2,3,4,5]});
Object.defineProperty(navigator, 'languages', {get: () => ['zh-CN','zh','en']});
"""
})
# 发送 CDP 原生鼠标事件
driver.execute_cdp_cmd("Input.dispatchMouseEvent", {
"type": "mousePressed",
"x": 549.5, "y": 441.2,
"button": "left", "buttons": 1,
"clickCount": 1, "pointerType": "mouse"
})
方案 B: Playwright ヘッドレスブラウザ
const { chromium } = require('playwright');
async function run() {
const browser = await chromium.launch();
const page = await browser.newPage();
// 拦截网络请求
await page.route('**/api/**', async route => {
await route.continue_();
});
await page.goto('https://target-page.com');
// 等待 DSL VM 初始化
await page.waitForFunction(() => {
return window.AWSCInner &&
window.AWSCInner._modules &&
window.AWSCInner._modules['fy'];
});
// 执行操作
await page.mouse.move(500, 400);
await page.mouse.down();
// ... 操作序列
await page.mouse.up();
}
方案 C: 純粋なプロトコル検証(成功率が極めて低い)
DSL VM が生成する token は通常、ブラウザコンテキストと強く結びついています(TLS JA3 フィンガープリント、IP、Cookie、リクエストヘッダーなど)。ブラウザから切り離すと、サーバー側でコンテキストの不一致を検出できます。純粋なプロトコル方案は推奨しません。
6. よくあるステータスコード
| Code | 意味 | 対応 |
|---|---|---|
| 0 | 検証通過 ✅ | sessionId + sig を取り出す |
| 300 | リスクコントロールによるブロック | ブロックされ、通過不可 |
| 8778 | 検証失敗、リトライが必要 | 操作をリトライ |
| 8776 | 操作が速すぎる、リトライが必要 | 遅延を追加してリトライ |
| 69634 | 汎用失敗 | パラメータが正しいか確認 |
7. Skill セルフチェックリスト
- DSL VM の識別(IIFE + 1文字変数 + DG() インタプリタ)を完了したか?
- 変数マッピングテーブル(
var X=数字)を抽出したか? - opcode リストを抽出して分類したか?
- 定数テーブル C[9] の参照範囲を分析したか?
- エクスポート関数の登録ポイントを特定したか?
- 純粋な静的解析では不十分な場合、ランタイムインジェクション方案を試したか?
- タスク完了後に field-journal へ書き戻したか?
- 新しいツール/新しいシナリオを発見 → routing.md を更新したか?
ルーティング登録
| タイプ | ルート |
|---|---|
| 対象タイプ: WASM / DSL VM / カスタム命令セット | reverse-engineering/dsl-vm-reverse/SKILL.md |
| ユーザー意図: "DSL VM / リスクコントロールエンジンのリバースエンジニアリング" | 本 skill |
| ツールチェーン: Playwright / Selenium CDP | ブラウザインジェクション方案 |
パスの交差
DSL VM 逆向路径:
reverse-engineering/dsl-vm-reverse/ → Phase 1-6 工作流
↓ 若需要捕获运行时数据
browser-automation/ → Playwright/Selenium CDP
↓ 若需要分析 API 协议层
js-reverse/ → Observe→Capture→Rebuild
コメント
Markdown 対応