実例で学ぶJavaScriptのリバースエンジニアリング

F5エコシステム | 2019年1月02日

flatmap-streamペイロードA

11月、npmパッケージevent-streamが、悪意のある依存関係flatmap-streamを介して悪用されました。この一連の出来事については、こちらにまとめられています。本記事では、この事例をケーススタディとしてJavaScriptのリバースエンジニアリングを解説します。flatmap-streamに関連する3つのペイロードは、解説しやすいほどシンプルでありながら、興味深い程度には複雑です。本記事を理解するうえで、このインシデントの背景を把握しておくことは必須ではありません。ただし、詳細をある程度ご存じでなければ分かりにくい前提に基づいて説明する場合があります。

ほとんどのJavaScriptのリバースエンジニアリングは、デスクトップOSで実行するバイナリ実行ファイルよりも簡単です。何しろ、ソースが目の前にあるからです。しかし、理解しにくいように設計されたJavaScriptコードには、その意図を隠すため、複数回にわたって難読化が施されることがよくあります。この難読化の一部には「ミニファイ」と呼ばれる手法が使われます。これは、容量を節約するためにソース全体のバイト数を可能な限り削減する処理です。具体的には、変数名を1文字の識別子に短縮したり、trueのような式を、より短く同等の!0などに変換したりします。ミニファイは、Webブラウザを起源とするJavaScriptのエコシステムにほぼ固有のものです。ツールの流用によりnodeパッケージでも見られることがありますが、セキュリティ対策を目的としたものではありません。一般的なミニファイや難読化の手法を基本的なレベルで元に戻すには、Shapeのunminifyツールをご確認ください。専用の難読化処理は、難読化用に設計されたツールで行われる場合もあれば、開発者が手作業で行う場合もあります。

最初のステップは、解析対象のソースを分離して入手することです。flatmap-streamパッケージは、特定の1バージョン(バージョン0.1.1)だけに悪意のあるペイロードを含め、それ以外は無害に見えるよう意図的に作られていました。ソースの変更点は、バージョン0.1.2とバージョン0.1.1の差分を取るか、2つのタブでそれぞれのURLを開いて交互に切り替えるだけでも、すぐに確認できます。以降、この記事では追加されたソースを「ペイロードA」と呼びます。以下に、整形したペイロードAのソースを示します。

事例で学ぶリバースエンジニアリング:画像1

まず何よりも、悪意のあるコードは絶対に実行しないでください(隔離環境を除く)。私は、ShiftのパーサーおよびJavaScript変換ツール群を使用してコードを動的にリファクタリングする独自ツールを作成しましたが、この記事の手順を試すだけであれば、Visual Studio CodeなどのIDEを使用できます。

JavaScriptをリバースエンジニアリングする際は、頭の中で同時に処理する情報を最小限に抑えることが重要です。そのためには、すぐには役立たない式や文を取り除くとともに、自動または手動で最適化されたコードについて、DRY化を元に戻します。JavaScriptを静的に解析し、頭の中で実行を追跡するため、思考上のスタックが深くなるほど、処理を追えなくなる可能性が高まります。

最も簡単な方法の1つは、3行目と4行目のように、requireやprocessなどのグローバルプロパティが代入されている変数を、ミニファイ前の状態に戻すことです。

例で学ぶリバースエンジニアリング:画像2

これは、リファクタリング機能を備えた任意のIDEで実行できます(通常は、名前を変更する識別子にカーソルを合わせて「F2」キーを押します)。その後、16進文字列を単にデコードしているように見える関数eの定義が確認できます。

例で学ぶリバースエンジニアリング:画像3

最初に注目すべきコード行では、関数eが文字列「2e2f746573742f64617461」をデコードした結果から得られるファイルをインポートしているようです。

例で学ぶリバースエンジニアリング:画像4

意図的に難読化されたJavaScriptでは、一見しただけで特に不審な文字列やプロパティに気付かれないよう、リテラル文字列の値を隠すことが極めて一般的です。ほとんどの開発者は、これが非常に低いハードルにすぎないと認識しているため、簡単に元に戻せるエンコーディングがよく使われますが、ここでも同様です。e関数は16進文字列を単純に反転するだけであり、オンラインツールを使って手動で処理することも、独自の補助関数を使うこともできます。e関数の動作を理解していると確信していても、悪意のあるファイル内の入力を使って実行することは、関数を抽出した場合でも避けることを推奨します。攻撃者が、そのデータによって引き起こされるセキュリティ脆弱性を発見していないという保証はないためです。

この文字列を反転すると、配布されたnpmパッケージ内にあるデータファイル「'./test/data'」がスクリプトに含まれていることがわかります。

例で学ぶリバースエンジニアリング:画像5

nをdataに名前変更し、e(n[2])からe(n[9])までの呼び出しを難読化解除すると、ここで扱っているものの全体像がより明確になってきます。

例で学ぶリバースエンジニアリング:画像6

これらの文字列が隠されていた理由も容易に理解できます。単純なflatmapライブラリ内で復号に関する記述が見つかれば、何かが明らかにおかしいとすぐに気付かれてしまうためです。

ここから、このスクリプトがnode.jsの「crypto」ライブラリをインポートしていることが分かります。また、APIを調べると、createDecipherの第2引数(ここではo)が復号に使用するパスワードであることが分かります。そこで、この引数と後続の戻り値を、APIに基づいて分かりやすい名前に変更できます。パズルの新たな手掛かりを見つけるたびに、たとえ些細に思える変数名の変更であっても、リファクタリングやコメントによって記録に残すことが重要です。見慣れないコードを何時間も追っていると、どこまで調べたか分からなくなったり、気が散ったり、誤ったリファクタリングのために前の段階へ戻る必要が生じたりすることは珍しくありません。リファクタリング中にgitでチェックポイントを保存することも有用ですが、その判断はお任せします。コードは次のようになります。e関数は使用されなくなったため削除し、if (!o) {...という文も分析に役立たないため削除しています。

事例で学ぶリバースエンジニアリング:画像7

また、fをnewModuleInstanceに変更したことにもお気づきでしょう。この程度の短いコードではそれほど重要ではありませんが、数百行に及ぶ可能性があるコードでは、すべてをできる限り明確にすることが重要です。

これでペイロードAの難読化はほぼ解除されたため、コードを順に確認しながら、その動作を把握できます。

3行目で外部データをインポートします。

例を用いたリバースエンジニアリング:画像8

4行目では、環境変数からパスワードを取得します。process.envを使用するとNodeスクリプト内から変数にアクセスできます。また、npm_package_descriptionは、Nodeのパッケージマネージャーであるnpmが、package.jsonファイルで定義されたスクリプトの実行時に設定する変数です。

例で学ぶリバースエンジニアリング:画像9

5行目では、npm_package_descriptionの値をパスワードとして使用するdecipherインスタンスを作成します。つまり、暗号化されたペイロードを復号できるのは、このスクリプトがnpm経由で実行され、かつpackage.jsonに特定のdescriptionフィールドを持つ特定のプロジェクトに対して実行されている場合に限られます。これはかなり難しそうです。

例で学ぶリバースエンジニアリング:画像10

6行目と7行目では、外部ファイルの最初の要素を復号し、変数「decrypted」に格納します

例で学ぶリバースエンジニアリング:画像11

8~11行目では、新しいモジュールを作成し、復号したデータを文書化されていないメソッド _compile に渡します。次に、このモジュールは外部データファイルの2番目の要素をエクスポートします。module.exports は、あるモジュールのデータを別のモジュールに公開するためのNode.jsの仕組みです。そのため、newModuleInstance.exports(data[1]) は、外部データファイル内にある2つ目の暗号化ペイロードを公開しています。

例で学ぶリバースエンジニアリング:画像12

この時点で、どこかのpackage.jsonに記載されたパスワードでのみ復号でき、復号されたデータが_compileメソッドに渡される暗号化データが手元にあります。ここで問題が残ります。パスワードが不明なデータをどうやって復号するのでしょうか。これは簡単な問題ではありません。aes256暗号化を容易に総当たり攻撃で破れるなら、npmパッケージの乗っ取りどころではない問題が生じます。幸い、候補となるパスワードがまったく不明なわけではなく、どこかのpackage.jsonに入力された文字列のいずれかです。package.jsonファイルは、もともとnpmパッケージのメタデータ用ファイル形式として生まれたため、まずは公式npmレジストリから調べるのがよいでしょう。幸い、すべてのパッケージメタデータをストリームとして取得できるnpmパッケージがあります。

実例で学ぶリバースエンジニアリング:画像13

対象ファイルがnpmパッケージ内にあるとは限りません。npm以外の多くのプロジェクトでも、Node.jsベースのツールの設定を保存するためにpackage.jsonが使用されており、package.jsonのdescriptionはバージョンによって変わる可能性がありますが、手始めとしては適切です。このペイロードは複数のキーで復号でき、その結果、意味不明な文字列が生成される可能性があるため、総当たり処理中に復号したペイロードを検証する方法が必要です。ここで扱っているのは、Module.prototype._compileに渡され、そこからさらに渡される vm.runInThisContextへの入力です。そのため、出力はJavaScriptであると合理的に推測でき、任意のJavaScriptパーサーを使用してデータを検証できます。パスワードが一致しない場合、または一致してもパーサーがエラーをスローした場合は、次のpackage.jsonに進む必要があります。幸い、Shape SecurityはJavaScript環境とJava環境で使用できる独自のJavaScriptパーサー群を開発しています。使用した総当たりスクリプトはこちらです。

例で学ぶリバースエンジニアリング:画像14

これを92.1秒間実行し、740,543個のパッケージを処理した結果、パスワード「A Secure Bitcoin Wallet」が得られました。このパスワードを使用すると、以下のペイロードを正常にデコードできます。

例で学ぶリバースエンジニアリング:画像15

これは幸運でした。途方もない総当たり攻撃になりかねない問題でしたが、実際には100万回未満の試行で済みました。問題のキーを含む影響対象のパッケージは、ビットコインウォレットCopayのクライアントアプリケーションでした。次の2つのペイロードでは、アプリケーション自体をさらに詳しく調べます。対象のアプリケーションがビットコインの保管を主な目的としていることから、この先の展開はおそらく想像できるでしょう。

このようなトピックにご興味があり、残り2つのペイロードや今後の攻撃に関する分析もお読みになりたい場合は、ぜひこの記事に「いいね」するか、Twitterでお知らせください:@jsoverson。

共有
タグ: 2021

関連するブログ記事

F5 Application Study Tool:OpenTelemetryによるF5 BIG-IP環境の監視
F5エコシステム | 2025/04/15

F5 Application Study Tool:OpenTelemetryによるF5 BIG-IP環境の監視

F5の新しいオープンソースツールは、BIG-IP環境を詳細に可視化し、データを追跡するとともに、強力でカスタマイズ可能なインサイトを得るための基盤を提供します。

F5 BIG-IPでAI防御を実現
F5エコシステム | 2024/11/13

F5 BIG-IPでAI防御を実現

F5 BIG-IP Advanced WAF、BIG-IP APM、BIG-IP SSL Orchestratorが、生成AIアプリ向けに包括的なセキュリティソリューションを提供する仕組みをご覧ください。

F5 Advanced WAFによるGraphQL APIの保護
F5エコシステム | 2021/08/31

F5 Advanced WAFによるGraphQL APIの保護

ここ数年、多くの組織がデジタルトランスフォーメーションを進めており、COVID-19のパンデミックによってその動きは加速しています。

アプリケーションサービスの現状 通信業界版:リーダーとしての台頭
F5エコシステム | 2019/02/19

アプリケーションサービスの現状 通信業界版:リーダーとしての台頭

通信ネットワークが進化し、さまざまな業界の幅広いユースケースに対応するなか、通信業界の回答者がテクノロジーリーダーとしての存在感を高めていることがわかります。

デジタルトランスフォーメーションは、単なるペーパーレス化ではない
F5エコシステム | 2017/07/10

デジタルトランスフォーメーションは、単なるペーパーレス化ではない

デジタルトランスフォーメーションは、単なるペーパーレス化ではありません。真のモダナイゼーションにアプリケーションアーキテクチャとセキュリティの再構築が必要な理由を、F5ブログでご覧ください。

やめましょう。もう本当にやめましょう。HTTPSはHTTPより高速ではありません。
F5エコシステム | 2016/09/19

やめましょう。もう本当にやめましょう。HTTPSはHTTPより高速ではありません。

もうやめましょう。HTTPSはHTTPより高速ではありません。プロトコルのパフォーマンスに関する微妙な違いと、暗号化トラフィックを適切に最適化する方法については、F5ブログをご覧ください。