広告 プログラミング

Javascriptの難読化ツール「JavaScript Obfuscator」の紹介【オプションの解説あり】

※本ページには、プロモーション(広告)が含まれています。

悩んでいる人
悩んでいる人

JavascriptでWebアプリケーションを実装しているが、無断で再利用される可能性がある。

このため、極力人手では解釈できないように変換した上で、配布したい。

こんなお悩みを解決します。

私は、JavascriptでWebアプリケーションを作成しており、Webブラウザで動作するコードを公開したことがあります。

その際に、無断利用や実装に基づく不正利用等を防ぐため、「難読化」という対応を行っています。

今回は、Javascriptのソースコードを難読化する「JavaScript Obfuscator」について解説します。

Webブラウザで動作するコードの実装例とともに解説するので、興味がある方はぜひ最後までご覧ください。

リンク先が有料サイトに変わっていたため、ブラウザ上で動作するアプリを作成しました。

注意点

今回、紹介するツールは、以下に示す注意点を理解した上で、利用する必要があります

  • 元のコードに戻すことは困難なため、オリジナルのソースコードは別途保存しておく必要があります
  • 難読化のため、設定に依っては不要なコードが挿入されるため、パフォーマンスが低下する可能性があります
    パフォーマンス低下が許容できない場合、設定の見直しが必要です。

概要

「JavaScript Obfuscator」は、以下のいずれかの方法で利用できます。

  • Node.js
  • Browser
  • Web Site

ここでは、環境構築が不要で容易に利用できるWeb Siteでの使い方について解説します。

また、以下のリンクにアクセスすると、「JavaScript Obfuscator」が利用可能なページが表示されます。

https://obfuscator.io

該当ツール

「JavaScript Obfuscator」のサイトが有料化されていたため、Web上で動作するツールを作成しました。

ゆると
ゆると

ファイルアップロードによる難読化はできませんが、コードを貼り付ける形式であればブラウザ上で難読化できます。

ゆると
ゆると


Basic Settings

Domain lock
Source Map
Strings Transformations
String Array Indexes Type
String Array Wrappers
String Array Encoding


Force Transform Strings
Reserved Strings
Identifiers Transformations
Identifiers Dictionary
Rename Property
Reserved Names
Other Transformations

使い方

ここでは、サンプルとして、以下のようなJavascriptのコードを難読化します。

'use strict';

// sample.js
(function () {
    const greeting = (message) => {
        const output = `Hello ${message}!`;

        console.log(output);
    };

    greeting('World');
    // output: Hello World!
}());

該当ページにアクセス

先ほど示したページにアクセスします。

「JavaScript Obfuscator」のページ

コードをアップロード

上記のソースコードをファイルに保存し、アップロードします。

ここでは、sample.jsとして保存し、アップロードしました。

sample.jsのアップロード

変換

「Obfuscate」を押下し、Javascriptのコードを難読化します。

Javascriptのコードの難読化を実行

変換された結果からコードが難読化されていることが確認できます。

難読化されたJavascriptコード

動作確認

変換されたコードをHTMLファイルに反映し、Browserで確認します。ここでは、sample.htmlとして保存しました。

<!DOCTYPE HTML>
<html>
    <head>
        <title>Sample</title>
    </head>
    <body>
        <script>
'use strict';(function(_0x25f9f3,_0x1f03cc){const _0x7f9672=_0x2bc0,_0x446e71=_0x25f9f3();while(!![]){try{const _0x57d923=parseInt(_0x7f9672(0xd8))/0x1*(parseInt(_0x7f9672(0xd9))/0x2)+-parseInt(_0x7f9672(0xd3))/0x3+parseInt(_0x7f9672(0xd5))/0x4+parseInt(_0x7f9672(0xd4))/0x5+parseInt(_0x7f9672(0xd7))/0x6+parseInt(_0x7f9672(0xcf))/0x7+-parseInt(_0x7f9672(0xd1))/0x8;if(_0x57d923===_0x1f03cc)break;else _0x446e71['push'](_0x446e71['shift']());}catch(_0x58b7ad){_0x446e71['push'](_0x446e71['shift']());}}}(_0x2614,0x24ac0));function _0x2bc0(_0x2714c9,_0x433280){const _0x26140c=_0x2614();return _0x2bc0=function(_0x2bc08e,_0x8f304){_0x2bc08e=_0x2bc08e-0xcf;let _0xe27042=_0x26140c[_0x2bc08e];return _0xe27042;},_0x2bc0(_0x2714c9,_0x433280);}(function(){const _0x33b02f=_0x2bc0,_0x2e5c1b=_0xcaf888=>{const _0x36b83a=_0x2bc0,_0x79a47e=_0x36b83a(0xd0)+_0xcaf888+'!';console[_0x36b83a(0xd6)](_0x79a47e);};_0x2e5c1b(_0x33b02f(0xd2));}());function _0x2614(){const _0x5b7404=['1884752KcCkQl','World','475902axtzks','1372630CRDSgi','77656Sjkfiu','log','203946fYgrmA','1136cbhfks','4SBCCmA','1499631FlDRlX','Hello\x20'];_0x2614=function(){return _0x5b7404;};return _0x2614();}
        </script>
    </body>
</html>

開発者モード(Google Chromeの場合、F12を押下)を開き、consoleの出力結果を確認します。

出力結果確認

期待通り、「Hello World!」が出力されていることが確認できます。

オプションの設定

先程は、初期設定のまま利用しましたが、いくつかオプションがあります。

ここでは、2022年11月時点で設定できるオプションについて解説します。

設定できるオプション一覧

設定できるオプションとして以下のものがあります。

設定可能なオプション(part1)
設定可能なオプション(part2)

オプション設定例

オプションの詳細を解説する前に、私が考える設定例を提示します。

難読化する際に以下の2点を考慮してオプションを設定しています。

  • 関数呼び出しによるパフォーマンス低下を極力防ぐ
    極力パフォーマンスを維持したまま難読化したいため。
  • 連想配列のキーは変更しない
    呼び出し先でキーの情報を利用することがあるため。

上記を踏まえて設定した結果を示します。

設定例(その1)
設定例(その2)

また、連想配列形式で示すと、以下のようになります。

{
  domainLock: [],
  stringArrayEncoding: [
    'none',
  ],
  stringArrayIndexesType: [
    'hexadecimal-number',
  ],
  forceTransformStrings: [],
  reservedStrings: [],
  identifiersDictionary: [],
  reservedNames: [],
  target: 'browser',
  seed: 0,
  disableConsoleOutput: false,
  selfDefending: true,
  ignoreImports: false,
  debugProtection: false,
  debugProtectionInterval: 0,
  domainLockRedirectUrl: "about:blank",
  sourceMap: false,
  sourceMapMode: 'separate',
  sourceMapSourcesMode: 'sources-content',
  sourceMapBaseUrl: '',
  sourceMapFileName: '',
  stringArray: true,
  stringArrayRotate: true,
  stringArrayShuffle: true,
  stringArrayThreshold: 0.8,
  stringArrayIndexShift: true,
  stringArrayCallsTransform: false,
  stringArrayCallsTransformThreshold: 0.5,
  stringArrayWrappersCount: 1,
  stringArrayWrappersType: 'variable',
  stringArrayWrappersParametersMaxCount: 2,
  stringArrayWrappersChainedCalls: true,
  splitStrings: true,
  splitStringsChunkLength: 7,
  unicodeEscapeSequence: true,
  identifierNamesGenerator: 'hexadecimal',
  identifiersPrefix: '',
  renameGlobals: false,
  renameProperties: false,
  renamePropertiesMode: 'safe',
  compact: true,
  simplify: true,
  transformObjectKeys: false,
  numbersToExpressions: true,
  controlFlowFlattening: true,
  controlFlowFlatteningThreshold: 0.3,
  deadCodeInjection: true,
  deadCodeInjectionThreshold: 0.2
}

JSON版は、以下のようになります。

{
  "domainLock": [],
  "stringArrayEncoding": [
    "none"
  ],
  "stringArrayIndexesType": [
    "hexadecimal-number"
  ],
  "forceTransformStrings": [],
  "reservedStrings": [],
  "identifiersDictionary": [],
  "reservedNames": [],
  "target": "browser",
  "seed": 0,
  "disableConsoleOutput": false,
  "selfDefending": true,
  "ignoreImports": false,
  "debugProtection": false,
  "debugProtectionInterval": 0,
  "domainLockRedirectUrl": "about:blank",
  "sourceMap": false,
  "sourceMapMode": "separate",
  "sourceMapSourcesMode": "sources-content",
  "sourceMapBaseUrl": "",
  "sourceMapFileName": "",
  "stringArray": true,
  "stringArrayRotate": true,
  "stringArrayShuffle": true,
  "stringArrayThreshold": 0.8,
  "stringArrayIndexShift": true,
  "stringArrayCallsTransform": false,
  "stringArrayCallsTransformThreshold": 0.5,
  "stringArrayWrappersCount": 1,
  "stringArrayWrappersType": "variable",
  "stringArrayWrappersParametersMaxCount": 2,
  "stringArrayWrappersChainedCalls": true,
  "splitStrings": true,
  "splitStringsChunkLength": 7,
  "unicodeEscapeSequence": true,
  "identifierNamesGenerator": "hexadecimal",
  "identifiersPrefix": "",
  "renameGlobals": false,
  "renameProperties": false,
  "renamePropertiesMode": "safe",
  "compact": true,
  "simplify": true,
  "transformObjectKeys": false,
  "numbersToExpressions": true,
  "controlFlowFlattening": true,
  "controlFlowFlatteningThreshold": 0.3,
  "deadCodeInjection": true,
  "deadCodeInjectionThreshold": 0.2
}

オプション詳細

Webページの構成に合わせて順番に解説していきます。

また、一部、私の意見が取り込まれています。私の意見に該当する箇所は赤色の下線で表現しております。

Global

ここでは、難読化時の一般的な設定を行います。

Options Preset

難読化時のプリセット(前もって設定された値)を指定できます。

設定値としては、Default、Low、Medium、Highの4種類から選択でき、それぞれ以下のような違いがあります。

設定値内容
Defaultデフォルトの設定で、高いパフォーマンスが期待できます。
Low難読化度合いは低くなりますが、難読化しないときと同等程度のパフォーマンスとなります。
Medium中程度の難読化度合いとなりますが、難読化しない場合よりもパフォーマンスは低下します。
High高い難読化度合いとなりますが、難読化しない場合よりもはるかにパフォーマンスは低下します。
Presetの設定値・内容

基本的に、Defaultを選択しておけばよいと思います。

Target

option名:target

難読化したコードの実行環境を指定できます。

設定値としては、browser、browser-no-eval、nodeの3種類から選択でき、それぞれ以下のような違いがあります。

設定値内容選択基準
browserbrowserで動作するJavascriptコードを出力します。下記のいずれにも該当しない場合
browser-no-evalbrowserとほとんど同じですが、evalが含まれるコードを出力しません。browser上でeval関数を利用したくない場合
nodebrowserと同じ結果となりますが、一部のブラウザ固有のオプション(window.locationなど)は使用できません。Node.jsで実行するコードを難読化する場合
Targetの設定値・内容・選択基準
Seed

option名:seed

乱数発生器のシードを設定します。

同じ乱数のシードを用いて変換することで、再現性のある難読化結果となります。

「0」を指定した場合、乱数生成器はシードなしで機能します。

これは、(現実的な利用範囲では)出力結果が毎回異なることを意味します。

Disable Console Output

option名:disableConsoleOutput

下記のスクリプトの呼び出しを無効にします。

注意点:コンソールへの出力が一切され無くなります。

  • console.log
  • console.info
  • console.error
  • console.warn
  • console.debug
  • console.exception
  • console.trace
Self Defending

option名:selfDefending

コードの自動フォーマットに対する対策が有効になります。

このオプションを有効にして難読化したコードに対し、自動フォーマットを適用すると期待通り機能しなくなります。

このため、より解読が困難なコードとなります。

このオプションを適用する場合の注意点として、以下の2点が挙げられます。

  • 難読化後のコードは、基本的に変更できなくなります。(上記の自動フォーマット対策が機能するため)
  • このオプションを有効にした場合、compactオプション(Other Transformations参照)も有効になります。
Debug Protection・Debug Protection Interval

option名:debugProtection、debugProtectionInterval

開発者モードを利用しづらくします。

具体的にはdebugger関数が定期的に呼び出され、開発者モードにおいて実行中の処理が一時停止します。

debugger関数を呼び出す間隔は、「Debug Protection Interval(単位はmsec)」で設定できます。

注意点:このオプションを適用する場合、ブラウザがフリーズする可能性があります。

Ignore Imports

option名:ignoreImports

importの難読化を除外します。

ほとんどの場合、Node.jsで難読化する際にこのオプションが関係します。

Domain lock・Domain Lock Redirect Url

前提:Targetとしてnodeを指定している

option名:domainLock、domainLockRedirectUrl

難読化されたソース コードを特定のドメインやサブドメインでのみ実行できるようにします。

指定されたドメインで実行されていない場合、「Domain Lock Redirect Url」に指定されたページにリダイレクトします。

また、ドメインは複数していでき、www.example.comでのみ有効にしたい場合、以下のように設定します。

www.example.com

また、複数のサブドメイン(www.example.comsub.example.com)で利用したい場合、以下のように簡略化して設定できます。

.example.com
Enable Source Map・Source Map Mode

option名:sourceMap、sourceMapMode

難読化されたコードのソース マップの生成を有効にします。

また、Source Map Modeの設定により、Source Mapの生成方法を指定できます。

設定値内容注意点
inline各.jsファイルの末尾にソース マップを追加します。開発者モードで難読化したコードのデバッグが可能となるため、本番環境で適用した場合、第三者に解読される恐れがあります。
separateソース マップを含む対応する '.map' ファイルを生成します。末尾にソースマップファイルの情報を付与するため、Source Map Base URL・Source Map File Nameの設定が必要になります。
Enable Source Map・Source Map Modeの設定値・内容・注意点
Source Map Base URL・Source Map File Name

前提:Source Map Modeとしてseparateを指定している

option名:sourceMapBaseUrl、sourceMapFileName

ベースURLをソース マップのインポートURLに設定します。

また、出力時のソースマップのファイル名を設定します。

Strings Transformations

文字列の変換方法について規定します。

String Array

option名:stringArray

文字列リテラルを削除し、特別な配列に配置します。

例えば、var m = "Hello World";は、以下のように置き換えられます。

// 変換前
var m = "Hello World";
// 変換後
var m = _0x12c456[0x1];
String Array Rotate

前提:Strin Arrayが有効である

option名:stringArrayRotate

難読化により生成された固定位置もしくはランダム位置のString Arrayの要素をシフトさせます。

これにより、削除された文字列の順序を元の場所に一致させることが難しくなります。

String Array Shuffle

前提:Strin Arrayが有効である

option名:stringArrayShuffle

String Arrayの要素をシャッフルします。

String Array Threshold

前提:Strin Arrayが有効である

option名:stringArrayThreshold

文字列リテラルがString Arrayに挿入される確率を設定できます。

設定値が1に近いほど多くの文字列リテラルが削除され、String Arrayとして扱われます。

また、0を設定すると、String Arrayを無効にする(文字列リテラルの削除が行われなくなる)ことと等価になります。

String Array Index Shift

前提:Strin Arrayが有効である

option名:stringArrayIndexShift

すべての文字列配列呼び出しに対して、追加のインデックス シフトを有効にします。

String Array Indexes Type

前提:Strin Arrayが有効かつString Array Index Shiftが有効である

option名:stringArrayIndexesType

文字列配列呼び出しインデックスのタイプを制御できます。

設定値としては、hexadecimal-numberhexadecimal-numeric-stringの2種類から選択でき、それぞれ以下のような違いがあります。

設定値内容
hexadecimal-number文字配列の呼び出しインデックスを16進数に変換します。
hexadecimal-numeric-string 文字列配列呼び出しインデックスを16進数の数値文字列に変換します。
String Array Indexes Typeの設定値・内容
String Array Calls Transform・String Array Calls Transform Threshold

前提:Strin Arrayが有効である

option名:stringArrayCallsTransform、stringArrayCallsTransformThreshold

String Arrayに対する呼び出し時の変換を有効にします。

これらの呼び出し時のすべての引数は、「String Array Calls Transform Threshold」の値に依存して異なるオブジェクトとして抽出されます。

これにより、文字列配列への呼び出しを自動的に見つけることがさらに難しくなります。

String Array Wrappers Count

前提:Strin Arrayが有効である

option名:stringArrayWrappersCount

String Arrayに対し、各ルートまたは関数スコープ内のWrapper関数の数を設定します。

String Array Wrappers Type

前提:Strin Arrayが有効である

option名:stringArrayWrappersType

String Arrayに対し、Wrapper関数の種別を設定できます。

設定値としては、variablefunctionの2種類から選択でき、それぞれ以下のような違いがあります。

設定値内容
variable各スコープの先頭に変数ラッパーを追加します。高速パフォーマンスで動作します。
function 各スコープ内のランダムな位置に関数ラッパーを追加します。variableよりもパフォーマンスが低下しますが、より厳密な難読化が提供されます。
String Array Wrappers Typeの設定値・内容
String Array Wrappers Parameters Maximum Count

前提:Strin Arrayが有効かつString Array Wrappers Typeがfunctionである

option名:stringArrayWrappersParametersMaxCount

String Arrayに対し、Wrapper関数のパラメータの最大数を制御できます。

デフォルトおよび最小値は2です。推奨値は、2から5のいずれかとなります。

String Array Wrappers Chained Calls

前提:Strin Arrayが有効である

option名:stringArrayWrappersChainedCalls

String Arrayに対し、Wrapper関数間の連鎖呼び出しを有効にします。

String Array Encoding

前提:Strin Arrayが有効である

注意点:このオプションを使用すると、スクリプトが遅くなる可能性があります。

option名:stringArrayEncoding

base64rc4を用いて、String Arrayのすべての文字列リテラルをエンコードし、実行時に出力するための特別なコードを挿入します。

設定値としては、nonebase64rc4の3種類から選択でき、それぞれ以下のような違いがあります。

設定値内容
none文字列リテラルをエンコードしません。
base64base64を使用して、文字列リテラルをエンコードします。
rc4rc4を使用して、文字列リテラルをエンコードします。base64よりも30%~50%ほどパフォーマンスが低下しますが、初期値を取得するのがより難しくなります。
String Array Encodingの設定値・内容

特別な理由がない限り、noneを設定しておけばよいと思います。

Split Strings・Split Strings Chunk Length

option名:splitStrings、splitStringsChunkLength

文字列リテラルを「Split Strings Chunk Length」で指定される長さに分割します。

Unicode Escape Sequence

option名:unicodeEscapeSequence

Unicode Escape Sequenceへの文字列変換を有効または無効にできます。

このオプションを有効にすることにより、コードサイズが大幅に増加し、文字列を簡単に復号できます。小さなソース コードに対してのみ、このオプションを有効にすることをお勧めします。

Force Transform Strings

option名:forceTransformStrings

渡されたRegExpパターンと一致する文字列リテラルの強制変換を有効にします。

制約:「String Array Threshold」により変換されるべきではない文字列にのみ影響を与えます。

注意点:このオプションは、「Reserved String」よりも優先して実行されますが、conditional comments(IEの独自拡張である条件分岐コメント)よりも優先度は低くなります。

Reserved Strings

option名:reservedStrings

渡されたRegExpパターンと一致する文字列リテラルの変換を無効にします。

Identifiers Transformations

識別子の変換方法について規定します。

Identifier Names Generator・Identifiers Dictionary

option名:identifierNamesGenerator、identifiersDictionary

識別子名ジェネレータを設定します。

設定値としては、dictionaryhexadecimalmangledmangled-shuffledの4種類から選択でき、それぞれ以下のような違いがあります。

設定値内容
dictionaryIdentifiers Dictionaryのリストから識別子名を生成します。
hexadecimal_0xabc123のような識別子名を生成します。
mangleda, b, cなどの短い識別子名を生成します。
mangled-shuffledmangledと同じですが、アルファベットがシャッフルされます。
Identifier Names Generatorの設定値・内容
Identifiers Prefix

option名:identifiersPrefix

すべてのグローバル識別子のプレフィックスを設定します。

このオプションは、複数のファイルを難読化する場合に使用します。

注意点:グローバル識別子間の競合を回避するのに利用されるため、プレフィックスはファイルごとに異なる必要があります。

Rename Globals

option名:renameGlobals

グローバル変数名とグローバル関数名を難読化対象に含めます。

注意点:このオプションはコードを壊す可能性があります。

このオプションは通常、無効のままで問題ないと思います。

Rename Properties・Rename Properties Mode・Reserved Names

option名:renameProperties、renamePropertiesMode、reservedNames

プロパティ名の名前変更を有効にします。

すべての組み込みDOMプロパティとコアJavaScriptクラスのプロパティは無視されます。

注意点:このオプションはコードを壊す可能性があります。

プロパティ名の名前変更のモードは、safeunsafeの2種類から選択でき、それぞれ以下のような違いがあります。

設定値内容
safe実行時エラーを防ぐために、より安全な方法でプロパティの名前を変更します。このため、一部のプロパティが名前変更から除外されます。
unsafe安全でない方法でプロパティの名前を変更します。
Rename Properties Modeの設定値・内容

制約:名前を変更したプロパティ名のフォーマットを設定するために、Identifier Names Generatorを使用します。

また、名前を変更するプロパティを制御するには、Reserved Namesを使用します。

このオプションは通常、無効のままで問題ないと思います。

Other Transformations

その他の変換方法について規定します。

Compact

option名:compact

難読化した結果を1行のコードで出力します。

Simplify

option名:simplify

簡素化による追加のコード難読化を有効にします。

Transform Object Keys

option名:transformObjectKeys

オブジェクト キーの変換を有効にします。

Numbers To Expressions

option名:numbersToExpressions

数式への数値変換を有効にします。

Control Flow Flattening・Control Flow Flattening Threshold

option名:controlFlowFlattening、controlFlowFlatteningThreshold

コード制御フローのフラット化を有効にします。

制御フローの平坦化は、プログラムの理解を妨げるソース コードの構造変換です。

注意点:このオプションは、実行速度が最大1.5倍遅くなるため、パフォーマンスに大きく影響します。

「Control Flow Flattening Threshold」は、制御フローの平坦化により影響を受けるノードの割合を設定するために使われます。

Dead Code Injection・Dead Code Injection Threshold

option名:deadCodeInjection、deadCodeInjectionThreshold

デッドコードのランダムブロックが難読化されたコードに追加されます。

制約:String Arrayが強制的に有効になります。

注意点:難読化されたコードのサイズが大幅に増加します(最大200%)。このため、難読化されたコードのサイズが問題にならない場合にのみ使用してください。

「Dead Code Injection Threshold」は、Dead Code Injectionの影響を受けるノードの割合を設定するために使われます。

まとめ

今回は、Javascriptの難読化ツールである「JavaScript Obfuscator」について解説しました。

オプションの説明が大半を占めていますが、ツールを利用する際に参考になると思います。

Javascriptで実装したコードをWeb上で公開する際は、今回のツールを利用し、第三者に解読されないよう、対策をしておくと良いです。

スポンサードリンク

-プログラミング
-,