AG-TECH CORPAG-TECH CORP

ENGLISH

JavaScriptコードを保護する:The Making of JSDefender

 

数年前、お客様はJavaScriptで精巧かつデリケートなアプリケーションを構築していると私たちに話し始め、それらを保護するためのソリューションがあるかどうか尋ねました。今日で言うDashO for Java and AndroidDotfuscator for.NETのようなものです。そのような問い合わせが多くなり、私たちは2018年の後半、今こそがJavaや.NET向けに提供している製品と同レベルのJaveScript向け保護プログラムを提供する時だと判断しました。私たちはそのような製品を提供するにあたって、いくつかのオプションを用意しました。私たちはまず、現存するオープンソースパッケージを適用して製品をビルドするか、製品を一からスクラッチ開発するかを見極めました。およそ20年間、DashOやDotfuscatorをビルドする過程で培ったバイトコードプロテクション技術の経験がありましたが、私たちは新たにJSDefenderに適用するソースコード保護/難読化の技術においては、これまでと全く異なるアプローチが必要だろうと当初は考えていました。しかしながら、度重なる研究やプロトタイプ開発によって、Javaや.NETの領域で培ったその幅広い経験こそが、今回の新しいJavaScript向けの製品を一からデザイン、開発することへの非常にユニークな基盤になるであろうことを実感しました。

 

本稿では、JSDefenderを開発する過程で培ったいくつかの経験や教訓を皆さんに紹介します。

オープンソース難読化エンジンの使用について

私たちのエンジニアチームは長年開発してきた技術を一から再構成することを望んではいませんでした。私たちは細心の注意を払いながら、いくつかのポピュラーなJavaScript向けオープンソース難読化フレームワークをテストしました。最たる理由として、私たちが設定する目標は、あくまで既存のソリューションをサポートし、それらに貢献することだったからです。私たちはそれらオープンソースを選定し、製品を信頼できるプロフェッショナルなものとするのに必要な機能を追加することを計画しました。

 

しかし、時が経過するにつれて、私たちはより多くの困難に直面し続けました。なぜなら、よく管理されたプロジェクトはわずかしか見つからず、それらの大部分は、品質の期待に応えられない問題がありました。

 

最も期待できたプロジェクトは、Timofey Kachalov氏によって開発されたJavaScript Obfuscatorでした。私たちが初めてそのプロジェクトを評価した際、Timofeyはもはやそのプロジェクトに投資する余裕がなかったことが主な理由ではありましたが、所有権を第三者に手渡そうとしているまさにその時でした。そのプロジェクトは、当初数十もの明らかな問題を抱えており、その大部分は数か月の間、公のままでした。私たちはTimofeyが尽力したJavaScript Obfuscatorの所有権を取得すべきか、慎重に熟慮しました。

  1. コードベースでは、良い印象を持っていたが、所有権を獲得して問題点を修復し、いくつかの足りない部分を新たに実装することは、多くのリソースを費やすのではないかと考えていました。
  2. パフォーマンスの劣化はJavaScript保護プログラムの開発においては非常に重大な問題です。一つの解決策として、部分的なコードプロテクションがありますが、残念なことにJavaScript Obfuscatorはそのようなことを考慮してデザインされたものではありませんでした。したがって、私たちはアーキテクチャを変更せずにそれらの変更を加えることは非常に費用のかかるものだと判断しました。
  3. Timofeyのプロジェクトにおけるユニットテストの回数(約1200回)は、解決すべき問題の本質を見極め、他の製品と比較するにはあまりに少なすぎることは私たちの直感が物語っていました。私たちはさらに精密なテストを行ったとき、いったいどのような問題が見つかるだろうかということを懸念していたのです。

この一連の評価プロセスを通して、私たちはJavaScript難読化の本質や深層を知るに至りました。そして、私たちのアイディアを試すいくつかの小さいプロトタイプをビルトしました。私たちは最も効果的かつプロジェクトを前進させる策は製品を一から創り上げるスクラッチ開発だと実感したのです。

私たちのJavaScriptプロテクションエンジン開発について

JavaScript保護ツールを開発するとして、どのプログラミング言語を使うか?その答えはもちろんJavaScriptかTypeScriptです。

C#はJavaScriptプロテクションに適している?

上記でJavaScript保護プロテクションに適した言語はJavaScriptかTypeScriptだと示したにもかかわらず、私たちは反直感的にC#で開発をスタートさせました。私たちのエンジニアチームの中に構文解析を使用して抽象構文木を変形させる経験を有していたスタッフがいたのです。彼はその技術を用いてJavaScriptコードを構文解析し、難読化するシンプルなプロトタイプモデルをC#で完成させました。私たちが従来から培っているバイトコード難読化スキルは保護されたコードのパフォーマンスに細心の注意を払うべきであることを暗に示していました。私たちはC#で書かれたプロトタイプをパフォーマンス低下の中でも特にコードを難読化する過程で発生するパフォーマンスの低下を測る指標として使用しました。

 

当初、あらかじめTypeScriptへ移行することを見越したC#プロトタイプのPoC(概念実証)は過度の無駄のように映りました。しかしながら、結果的に私たちは私たちの考えやコンセプトを早い段階でテストするという貴重な数週間を得たのです。C#を用いていくつかのJavaScriptを保護する実験を行った間、私たちのチームは完成形とも言えるTypeScriptを用いた製品の開発準備を始めていました。私たちはC#を用いた難読化ツール(主にJavaScriptの構文木変換の重要な要素となった)の一連の開発を通して、様々なことを学び、またそれはTypeScriptに移行したこの後の開発にも非常に重要な教訓となり、活かされました。

 

数ある教訓の中からひとつを選んでご紹介しましょう。

Babel(コード変換ツール)

私たちはJavaScriptコードを入力する際にBabelツールセットを使用することを決定しました。Babelの背景にはアジャイルコミュニティが存在し、ソースコードをパース、分析するためのすべての機能を提供しています。ECMAScript標準の進歩、アップデートに伴い、Babelのコミュニティもすぐにそれに追従するであろうことは私たちにとって明らかなことでした。C#を用いた開発の経験は、私たちに数十あるBabelのサービスがJavaScriptコードの保護に利用できるであろうことを認識させるに至りました。しかし、より一般的な目的で設計されたサービスで具体的な変換ルーチンを便乗させるため、それらを構築するとエンジンの速度が低下することもわかりました。すなわちエンジンのパフォーマンス低下を招くのです。私たちの最終決定は、Babelパーサのみを使用し、開発当初から必要としていたコード変換を行うことでした。その決定は、より速く、高い信頼性をもった、私たちが扱いやすく、実装も容易な製品の開発を実現させたのです。そしてもちろん、私たちの製品の他社技術/製品への依存度は限りなくゼロに近いものとなっています。

 

1年3か月後、JSDfender v2をリリースしようと準備しようとしていた頃、私たちは依然として下した決定は正しいものだと信じていました。プロジェクトの開始からの継続的なリファクタリングの後、クリーンなコードと低い技術的負債がありました。 他のパッケージのセキュリティ修正の影響を受けることはほとんどありません。(これまでのところ、それらのほとんどすべてが、ビルドプロセスで使用するパッケージに含まれています)。

適切な機能の組み合わせへのフォーカス

コードを安全に保護するための観測から始まり、これまでJavaScript難読化について語ってきましたが、その保護するプログラムコードさえも結局はリバースエンジニアリングされてしまうのです。もしもクラッカーが確たる知識やツール、時間を確保していれば、プログラムを復号化や脱難読化、さらには解析から完全に保護するプログラムを作成することはできません。しかしながら、適切なプロテクションさえあれば、コードの部分的な解析にかかる時間を飛躍的に増長させることができます。攻撃者が1分でできる作業を1時間かかる作業にしたり、1時間で完了させられる作業を数日や数か月さえ要するものにさせたりと、効果は絶大です。

適切なプロテクションの選定

私たちはあくまで人間ですので、やはりコメントや直感的な識別子、自己記述的なメソッド名、緻密な制御フロー等の様々な情報にあふれた簡単なコードを読みたいものですよね。コードに意義づけをすることは常に読み手の理解を容易くします。むしろ、コードの意義づけをしないということは、プログラム本体の詳細や意味を理解し、精通することを阻む事態を招きます。

 

活用可能なJavaScript保護プログラムはごく少数ですが、それらすべては同様のアプローチで開発されています。それらのプログラムはセマンティクスをソースコードから極力取り除き、私たちが直感的にコードを理解することやリバースエンジニアリングを実行されることを防ぐことに重きを置いています。

 

JavaScriptは性質上、独特なプログラミング言語と言えます。構造的にはセマンティクスを取り除くための24個もの変換技術が提供されています。それらの変換はコードをシンプルなものから精巧な複雑性をもつものへと変化させることを意図しています。もしも、これを読んでいる貴方がJavaScriptソースコードを変容させる特定の変換技術を選択するとき、以下の命題に対する解を常に用意しておかなくてはなりません。

  1. ソースコードの長さをどれくらい増長させるか?
  2. 甚大なパフォーマンスの劣化を招く可能性はあるか?
  3. 言語変換後の復元は可能か?
  4. ツールを用いたリバースエンジニアリングを実行される危険性はあるか?
  5. 難読化された言語をなにか特定の読みやすいコードへと復元することはどのくらい容易なものなのか?
  6. 使用するにあたってどの程度のフラストレーションがユーザにかかるのか?
  7. コード変換は実行中のコードを阻害することはあるか?
  8. コード変換技術を実装、テスト、サポートするすべての過程において、作業は容易なものであるか?

80-20ルール(パレートの法則)

JSDefenderの開発過程において、私たちはJSDefenderに搭載する最も理想的且つ効果的な機能構成を決定するために、JScramblerやNw.js、obfuscator.io(Github向けのJavaScript難読化ツールのオンライン版)等のいくつかの競合製品をテストしました。我々のチームはすべてのコード変換技術を実装する気でいましたが、80-20ルールを用いて最終的な決定を下しました。私たちは慎重に数ある候補の中から最も高機能で効率的なプロテクションを提供しているものを12個選定しました。

 

80-20ルールは私たちをまた別の方向に導きました。私たちが特に難読化技術のパフォーマンスを計測していた時、部分的なプロテクションを使用した場合に得られる桁違いのパフォーマンスの結果のみを最終的に注視していました。言い換えれば、もしパフォーマンス自体が懸念事項であったとしても、JSDefenderはコードの中でも重要な意義を持つ部分、もしくは攻撃に対して脆弱な部分等、クリティカルな一部分(20%)を保護し、それ以外の残された大部分(80%)はそっくりそのまま、手つかずの状態で残されます。その究極とも言える割合の匙加減は個々人に委ねられます。このような理由から私たちは当初からJSDefenderを高精度な構成を持つ部分的なプロテクションというコンセプトでデザインしたのです。なので、私たちが新たなプロテクト技術を開発したり、既存のものを修正したりする際も、部分的なプロテクションという側面を顧みる必要がありません。私たちが変換プログラムにアルゴリズムを実装する際にはエンジンが変換プログラムにコード入力する際にどの部分を保護、マネージし、どの部分を残すかという要素を考えればよいのです。

バンドル

JavaScriptコードを保護することはソースファイルをただ難読化するのみの処理に比べ、多くの要素を必要とします。多くの開発チームは各々特色のあるJavaScript UI フレームワークを使用しています。WebpackやMetro、Rollup、Expo等に代表されるモジュールバンドラーはJavaScriptモジュールをいくつかのwebチャンクと組み合わせます。保護プログラムへバンドルする技術には製品により多くの価値を付与する特別な技術が存在します。私たちはJSDefenderの意図や構成に見合う技術を継続的に調査、検証しています。

開発・サポートサイドに提供する品質

まったく新しい保護プログラムエンジン開発の際、最も大きな議論の的となるのは、オープンソースJavaScript難読化フレームワークの不確実性への懸念です。「コードはしっかりと動作するだろうか?」という単純な懸念ではないのです。(関連する懸念としてそれらをしっかりと検証できるかどうかということも含みます!)本質的に考えなければならないことは、それらオープンソースのフレームワークをどのようにして我々が最終的に期待している製品の品質にマッチさせられるか、また開発のアプローチに組み込めるかという事柄です。

 

JSDefender開発チームは高性能なソフトウェアは技術的負債を最小限に留めることでのみユーザに提供できるものだと信じていました。 自動で行われるテスト、継続的なリファクタリング、コードのレビューとプログラミングへの反映、そして定期的に行われる知見、知識の相互共有が私たちの日々の業務の大部分を占めていました。

  いくつかのオープンソースプロジェクトのソースコードを研究していた時、私たちはその中の一部は非常に高い品質を有していたことは認めていましたが、その一方で、私たちのベストとも言える仕事を提供したり、それらのプロジェクトを使ったアプローチを実現したりすることに関して、十分たる確証を得られるだろうという考えはありませんでした。

増やしたい要素をより増やすか?それとも減らすべき要素をより減らすか?(Pain or Gain?)

C#の概念実証テストをしていた際のエピソードをお話ししましょう。

 

元々、私たちはC#のプロジェクトをJavaScriptコードプロテクションにおいてコストを抑えた実用最小限製品(以下MVP)とするつもりでした。数十ものMVPと早い段階で関わるうちに、私たちはただ最小限の(自動化された)テストのみを伴うシンプルなコードを作成できるだろうという考えに辿り着きました。結局、私たちは最初のデモプログラム作成とフィードバックを終えたのち、MVPという概念を捨て去ろうという方針を定めました。それから、私たちはしっかりとした基盤をもって技術的負債を少なく抑えるコードの作成を再度スタートさせました!

 

しかし、MVPに関する作業をしていた2日目に、JavaScriptはテスト工程を開発の後半フェーズに延期していい状況ではないことは明らかになりました。抽象構文木の最適な解析をマネージメントできるという確たる自信なくして、私たちが求めるような早さや信頼性をもって理想的なデモプログラムを作成することはまったくの不可能だったのです!最初の週の終わりまでに、私たちは300回近い回数のオートメーションテストをこなしました。私たちが最初にMVPのデモプログラムを作成した時はその数は倍の600回でした。

 

外部的な視点からみると、わざわざC#のプロジェクトを完成させた後にTypeScriptに移行することは不要であり、リソースの無駄であると思うかもしれません。開発チームは実際のコードだけでなく、テストケースまで一緒くたに新たなプログラミング言語(TypeScript)に移植しなければなりませんでした。しかし私たちはその一連の作業を違った角度から見ていました。C#プロトタイプの作成を通して、私たちは初めてTypeScriptのコードラインを書く前に、非常に有益であり、重要な事柄を2つほど学ぶことができました。

  1. 特定のコード変換をどのように行うべきか
  2. それらのコードがしっかりと想定通りに動いているかを証明するためになにをテストすべきか

これらの教訓を得たことにより、TypeScriptのコードベースを作成(あえて“移植”とは言いません)するのに20人日しかかかりませんでした。2週間で、我々が実施する自動テストは延べ10000件を超えます。

膨大な量のテスト実行

サイズの問題は、やはり付きまとうものです。短いJavaScriptコードに対する単純な単体テストで難読化変換の正確さをテストするだけでは不十分であることはわかっていました。私たちはプロテクションのテストを実際のプログラム上で実行した際、いくつかの異なるアプローチを採用しました。その中から3つほどご紹介しましょう。

 

まず、私たちが採用した方針はドッグフーディング(社員が自社製品や自社サービスを日常的に利用すること)でした。最初の主要なテストはJSDefenderそのもののテストでした。私たちの開発の最終工程のひとつがJSDefenderのソースをJSDefenderそのものでプロテクトすることなのです。私たちはWebpackを利用し、コードは今日の難読化プログラムソースをビルドする際に多く使われるWebpackプラグインを内包しています。もしバグがあった場合、JSDefenderは起動時に動作をしない可能性が非常に高いです。

 

2つ目に、私たちはWebブラウザで実行可能なサイズの大きいJavaScriptファイルをいくつか保有していました。私たちは数十もの方式でそれらをプロテクトし、さらにそれらがブラウザ上でどのように動作するかをテストしました。私たちはそれらをパフォーマンス劣化の頻度を測る統計と共に計測したのです。私たちのチームメンバーの1人はビンテージのコンピューターのファンでもあります。彼はJavaScript版のZXスペクトラムエミュレータを独自に作成しました。(ところでこのZXスペクトラムエミュレータはSatya Nadella氏によってコンピュータープログラミングを学ぶために使われた1980年代のマイクロコンピューターだということをご存知の方はいらっしゃいますか?)

 

コードの容量は1.5MBです。およそ40秒ごとにテストプログラム上で動作し、いくつかの要素を記録します。最初のテストはパフォーマンスの劣化が少なく、成功と言えた一方で、2回目のテストは甚大な性能上の欠陥を示すものでした。

 

 

 

3つ目に、私たちは実際のWebサイトを使ったテストを少数のパートナーと共に実施しました。その中のひとつはscrummate.com(JSDefender開発チームのマネージメントで使用したサイト)です。当該サイトから許可を頂き、私たちはサイト上のいくつかのコアJavaScriptファイル(数メガバイト)をダウンロードし、プロテクトしました。そして、ブラウザのプラグインを使いながら、私たちはオリジナルのJavaScriptファイルへのリクエストをテストマシン上で既にプロテクトされているものにリダイレクトさせました。この方式をもって、私たちはそのプログラムの実際の動作をこの目で見て、精査することができました。

オンプレミスツールvsクラウドサービス

私たちはクラウドサービスの時代を生きていますので、クラウド上でJavaScriptプロテクションサービスを提供することは一見してよいアイディアのように感じます。事実、私たちはJSDefenderをオンプレミス環境とクラウド環境のどちらにも実装可能なようにデザインしています。例えば、私たちが提供する無料のオンラインJSDefenderデモはオンプレミス環境で動作するJSDefenderとまったく同じプロテクションエンジンを使用しています。開発において、インテグレーションがしにくく、はたまたすべての保護・言語変換に実装できないので、実際のプロジェクトへの利用を皆さんは控えるかもしれません。しかしながら、私たちはこのオンプレミス環境のプログラムを使用することこそが、JSDefenderがJavaScriptを保護しているという感覚をいち早く掴む最適な道だと考えています。

 

いくつかのJavaScriptプロテクト製品(業務用も含む)は主にクラウドサービスです。これは保護されていない丸裸のコードを離れた場所に転送しなければならないことを意味しており、サービスそのものやプロセス、さらにはオンラインストア等さえもがプロテクトされたコードに変換される前のセンシティブな状態のまま読み込めてしまうことになります。オンプレミス環境のサービスに比べ、クラウドサービスを利用する際はより多くのレイヤーのリスクを覚悟しなくてはなりません。また、ユーザが信用するサービスプロバイダは以下のような条件がなくてはいけません。

  1. 元のソースコードを保持しません(将来のテストのためにも)、または保持している場合でも、あなたが安心できるようにアクセス制御と保持ポリシーが文書化されています。
  2. 提供するサービスの適切な箇所にデータ漏洩やクラッキングを防止、完治、緩和するための確実なコントロールが施されている
  3. 公の法やルールにしっかりと従っている

エンジニアチームと開発チームは私たちのチームと同じように、時にはミスをするものです。そのような小さなミスがユーザの保護されていないコードを漏洩したらどうなるでしょう?簡単に言えば、私たちはむしろ、多くのお客様が自分たちの保護されていないコードをどこかの信用されていないような不確実なサービスに送信することは到底ないことを認識しているのです。

 

なので、私たちはオンラインのコードプロテクトサービスを使用することが有益且つ合理的であることを理解し、認める段階においても、JSDefenderをオンプレミス環境で使用することを強くお勧めします。仮に、私たちのエンジニアチームがミスを犯したとしても、発生するバグに与える脅威はオンラインで発生するものよりはるかに少ないものになります。なぜなら、私たちのお客様はセンシティブなコードを自らの環境で保持する確たるインフラやポリシー、コントロールを既に有しているからです。

 

全機能利用可能な期間限定のトライアルをダウンロードすることでJSDefenderをすぐに使い始めることができます。トライアルには製品のフルサポートが付与されています。

 

JSDefenderはPreEmptive Protectionチーム、グループ、またはエンタープライズライセンスで利用可能で、既にそれらのライセンスを有しているお客様は無料で使用することができます。スタンドアロン環境での価格設定も可能です。ご興味ありましたら、御見積依頼をお願いします。

 

JSDefenderのオンラインデモバージョンは登録不要ですぐにお使いになれます!

一覧に戻る

Contactお問い合わせ

お気軽にお問い合わせください。

お問い合わせ

    必須会社名

    個人のお客様は「個人」と入力してください。

    必須お名前
    必須メールアドレス
    必須メールアドレス(確認)
    必須ライセンス ありなし
    ダウンロード目的