ドキュメント
/
レシピ
/

VM 防御のテレメトリと応答

VM 防御のテレメトリと応答

Pro
v7.1.0+

vmDefenseHook を使って VM の防御検知をバックエンドに報告し、vmDefenseReaction で各検知カテゴリの応答を調整します。完全に何も壊さないテレメトリ専用のビルドから、盗まれたバンドルに対して厳しく破壊するビルドまで対応します。

問題

VM の防御機構 - vmSelfDefendingvmDebugProtectionvmDomainLock - はローカルに作用します。デバッガ、自動化ツール、改ざんされた環境、または許可されていないドメインが検知されると、保護されたコードは動作しなくなるか、無言で自身の結果を汚染します。それは攻撃者を止めますが、デフォルトではその事実があなたに届くことはありません。バンドルがどれくらいの頻度で探られているか、どの検知器が発動したか、あるいは防御が正当なユーザーを壊していないかを知ることができません。

v7.1.0 以降、2 つのオプションがそのギャップを埋めます。どちらも防御を有効化するものではありません。すでに有効にした防御を観測し、方向付けるだけです:

  • vmDefenseHook - 防御が何かを検知するたびにシグナルオブジェクトを受け取るグローバルコールバックです。バックエンドにテレメトリを送るために使用します。
  • vmDefenseReaction - 有効な防御がどのように応答するか(ローカルで破壊する、汚染する、何もしない)を選択するカテゴリごとのマップです。

レシピ 1 - 検知をバックエンドに報告する

ステップ 1 - 難読化されたバンドルが読み込まれる前に、グローバルなフック関数を登録する

VM ランタイムとその防御は、保護されたプログラムのに実行されるため、多くの検知は起動時に発動します。フックを、難読化されたスクリプトタグより前に、ホストページの単純なグローバルとして定義します:

<script>
    // In your page, BEFORE the obfuscated script:
    window.__vmDetection = function (signal) {
        navigator.sendBeacon('/api/vm-defense', JSON.stringify(signal));
    };
</script>
<script src="/app.obfuscated.js"></script>

ステップ 2 - vmDefenseHook をそれに向ける

このオプションは、name が呼び出すグローバル関数であるオブジェクトです(aliases は任意 - 以下を参照):

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    // the hook alone enables nothing - a defense must be on for detectors to run:
    vmSelfDefending: true,
    vmDebugProtection: true,
    vmDefenseHook: { name: '__vmDetection' }
});

素の文字列形式(vmDefenseHook: '__vmDetection')は { name: '__vmDetection' } の省略形として今も受け付けられますが、非推奨です。オブジェクト形式を推奨します。

ダッシュボードでは、少なくとも 1 つの防御(vmSelfDefendingvmDebugProtection、または vmDomainLock)が有効になると、VM オプションパネルに VM Defense Hook フィールドが表示されます。

ステップ 3 - バックエンドでシグナルを受け取る

すべての検知は、単一の signal オブジェクトでフックを呼び出します:

  • source - 具体的な検知器: headlessnodeagentdomaindebuggersandboxnativeHooktiming、または integrity。v7.4.0 以降、かつての env 検知器と inspector 検知器は source: 'debugger' として報告されます。
  • category - automationdebuggersandboxdomaintamper、または integritynode ソースは category: 'debugger' として報告されます(v7.4.0 以降)。
  • scorethreshold - 検知スコアと、それが超えたしきい値

最小限の受信エンドポイントです(Express を示していますが、POST を受け付けるバックエンドであれば何でも動作します)。ボディを配列に正規化するため、以下のバッファパターンで送られるバッチ形式にも対応します:

app.post('/api/vm-defense', express.text({ type: '*/*' }), (req, res) => {
    // a signal: { source: 'headless', category: 'automation', score: 7, threshold: 4 }
    const signals = [].concat(JSON.parse(req.body));
    for (const signal of signals) {
        console.warn('vm-defense', { ...signal, ip: req.ip, ua: req.get('user-agent') });
    }
    res.sendStatus(204);
});

シグナルフィールドのリネーム(エイリアス) v7.4.0+

デフォルトの source / category の値は説明的な名前なので、コールバックを計装する人(あるいは出力を読む人)は誰でも、保護機構とどの検知器が発動したかを認識できてしまいます。aliases はシグナルフィールドを任意の不透明なトークンにリネームします。これはシグナルが発行されるに VM の内部で適用されるため、それらの名前が出力に現れることも、コールバックに到達することもありません。あなたのアプリは自身のマッピングを把握しており、トークンをバックエンドに転送します。

エイリアスはフィールドごとに設定します。それぞれが key(コールバックが受け取るプロパティ名)を取ります。文字列の名前フィールドである sourcecategoryvalues マップも取りますが、score / threshold は数値であり key のみを取ります。設定されていないエントリはデフォルトの名前を保持します。

vmDefenseHook: {
    name: '__vmDetection',
    aliases: {
        source:    { key: 'a8Qm', values: { headless: 'xP4m9Q' } },
        category:  { key: 'p3Tx', values: { automation: 'bQ7s1M' } },
        score:     { key: 's1' },
        threshold: { key: 't1' }
    }
    // the callback now receives e.g. { a8Qm: 'xP4m9Q', p3Tx: 'bQ7s1M', s1: <score>, t1: <threshold> }
}

ダッシュボードでは、Signal aliases セクションが VM Defense Hook フィールドの下に配置されます。

レシピ 2 - デフォルトの応答を調整する

vmDefenseReaction は、各検知カテゴリがどのように応答するかを設定します。これは何も有効化しません。防御そのものは vmSelfDefendingvmDebugProtectionvmDomainLock によって有効化されます。このオプションは、有効な防御がどのように応答するかを選択するだけです。カテゴリが制御の単位です。あるカテゴリ内のすべての検知器はそのカテゴリの応答を実行し、オプションがオフのカテゴリに設定された応答は単に効果を持ちません。

カテゴリ有効化する要素応答する条件
automationvmSelfDefending または vmDebugProtectionコードが人ではなくソフトウェアによって駆動されている: ヘッドレスまたは自動化されたブラウザ、スクレイピング / テストフレームワーク、あるいはページをステップ実行する AI コーディングエージェント。
debuggervmDebugProtection または vmSelfDefending誰かがデバッガまたはブラウザの開発者ツールのインスペクタを開き、実行中のコードを理解するためにステップ実行している。
sandboxvmDebugProtectionコードが本物のブラウザでまったく実行されていない: オフラインで実行・研究するために、エミュレートまたはスクリプト化された JavaScript 環境に持ち込まれている。
domainvmDomainLockコードが許可していないサイトで実行されている: vmDomainLock の許可リストにないホスト(例えば、あなたのバンドルが誰か別の人のドメインにコピーされた場合)。
tampervmSelfDefendingVM 周辺の JavaScript 環境が、それを監視または乗っ取るために改変されている。ネイティブのブラウザ組み込み関数が計装されたバージョンに置き換えられているなど。
integrityvmSelfDefending保護されたバンドル自身のコードが、生成後に編集またはパッチされている。

キーはこれら 6 つのカテゴリ名、または default(指定されていないカテゴリのフォールバック)です。値は次のとおりです:

  • break - 直ちに破壊する
  • decoy - 汚染された状態で実行を続け、無言で誤った結果を生成する
  • none - ローカルでは何もしない(テレメトリのみ)

設定しなかったカテゴリは、組み込みのデフォルトにフォールバックします:

// built-in defaults
vmDefenseReaction: {
    automation: 'break',
    debugger: 'decoy',
    sandbox: 'decoy',
    domain: 'break',
    tamper: 'break',
    integrity: 'break'
}

default は、構造上正しいことが保証されているもの(integritytamper)を含む、すべてのカテゴリに及びます。そのため { default: 'none' } は、本当に何も壊さないテレメトリ専用のビルドになります:

vmDefenseReaction: { default: 'none' } // never break - pair with vmDefenseHook
vmDefenseReaction: { automation: 'none' } // tolerate automation FPs; the rest keep their defaults (a bad domain still breaks)

ダッシュボードでは、防御が有効になると VM オプションパネルに VM Defense Reactions のセレクトが表示されます。各カテゴリは、その検知器を発行する防御がオンの間のみ編集可能です。

テレメトリから強制へ

初日から可視性と強制のどちらかを選ぶ必要はありません。防御を 2 つのビルドで段階的に展開します。1 つは報告のみを行い、次に - テレメトリがクリーンに見えたら - 応答するビルドです。

ステップ 1 - 観測専用のビルドを出荷する

使用する予定のあらゆる防御を有効にし、vmDefenseHook をエンドポイントに向け、すべての応答をオフにします。すべての検知器は依然として実行され、ヒットごとにバックエンドに報告します。ただ何も壊さないだけです:

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    vmSelfDefending: true,
    vmDebugProtection: true,
    vmDomainLock: ['example.com'],
    vmDefenseHook: '__vmDetection',
    vmDefenseReaction: { default: 'none' } // observe only
});

ステップ 2 - 収集したシグナルを確認する

ビルドが実際のトラフィックにさらされたら、正当な利用が引き起こした検知を探します。最もよくある 2 つ:

  • 自分自身のエンドツーエンドテストや稼働監視による automation のヒット - 本番でそのカテゴリを許容するのではなく、それらの成果物を防御なしでビルドします。
  • vmDomainLock の許可リストに含め忘れたステージングやプレビューのホストによる domain のヒット - そのホストを追加します。

応答を弱めるより原因を修正することを優先してください。none のままにされたカテゴリはすべて、攻撃者がもはや気にする必要のない検知器です。

ステップ 3 - 応答をオンにする

default: 'none' の上書きを削除して、組み込みのカテゴリごとの応答を適用します。切り替え全体はその 1 行だけです。あるカテゴリが排除できない誤検知を出し続ける場合は、そのカテゴリだけを none に保ち(例: vmDefenseReaction: { automation: 'none' })、残りを強制します。