概要
2021年3月21日、私たちのチームは、世界中の9000を超える学校システムで使用されている人気のある教育用ソフトウェアであるNetop Vision Proで発見したいくつかの脆弱性について開示しました。Netop社は非常に対応が早く、重要な調査結果の多くに対処するためにいくつかの更新を行ない、教育者と子供のためにより安全な製品をリリースしました。脆弱性調査プロジェクトでは、製品がどのように機能するかについての理解を深めるにつれて、追加の脅威ベクトルが明らかになり、追加の調査結果につながる可能性があります。これは、Netopの調査中に再び真実であることが証明されました。このブログでは、その継続的な調査の中で新たに確認されたNetop Vison Proバージョン9.7.2ソフトウェアのJPEG画像処理の脆弱性であるCVE-2021-36134の追加の発見に焦点を当てます。主に、WindowsDLLのブラックボックスファジング中に使用されるプロセスと手法に焦点を当てます。
バックグラウンド
ファジングはやりがいのある作業になる可能性があり、どこから始めればよいかを知っているだけでも混乱を招く可能性があります。市場にはさまざまなファザーがあり、その多くは主にLinux上のオープンソースプロジェクトを処理するために設計されています。2020年後半、GoogleのProject Zeroチームは、Jackalopeという名前の新しいファザーをリリースしました。Jackalopeはカバレッジガイド付きファザーです。つまり、テスト中にコードパスを追跡し、その情報を使用して将来の変更をガイドします。Jackalopeは、コードカバレッジにTinyInstというライブラリを利用し、コードカバレッジに関連するコマンドラインパラメータをTinyInstに直接渡すことができます。Jackalopeについて私の注意を引いたのは、それがブラックボックス、Windows、およびMacOSを念頭に設計されていることでした。これは、Windowsのブラックボックスのファジングのギャップを埋めるために構築されたもので、しばらくの間存在していたため、まだ調査する必要がある段階です。Jackalopeのリリース時には、主にWindowsで実行されるNetop Vision Proの調査に取り組んでいたため、Jackalopeをテストして、Netop VisionProに新しい脆弱性を発見できるかどうかを確認するのが妥当だと考えました。
設定
Jackalopeのドキュメントは、開始するためのセットアップとビルドのプロセスを説明するのに最適です。このセットアップでは、Windows 10の完全にパッチが適用されたシステムにショップをセットアップし、Visual Studio2019を使用してGithubリポジトリからJackalopeをコンパイルしました。わずかな時間で、セットアップをテストするときが来ました。リポジトリは、ソースを使用して構築できるテストバイナリを提供するため、ファザーの動作を理解するのに最適な場所です。コードは数百行弱ですが、図1に示すように、その動作は数行にまとめることができます。
テストコードを調べると、メモリー内に「test」という単語が見つかると、テストバイナリが単にクラッシュすることが明らかになります。無効なメモリーアドレス「NULL」に値「1」を書き込もうとすると、クラッシュが発生します。したがって、ファザーが正しく機能していることを確認するには、小さな入力コーパスを作成する必要があります。これは、「in」ディレクトリを作成し、その中に「test」という単語を含むいくつかのテキストファイルを配置することで実行できます。このテスト中にクラッシュや新しい脆弱性を探すのではなく、セットアップが期待どおりに機能していることを確認するだけです。テストの実行を図2に示します。ここでは、テストケースを実行するコマンドがJackalopeのドキュメントから取得されています。
ターゲットの選択
全体的なターゲット関数を選択するときは、最初に、アプリケーションが入力を受け取る方法と、ファザーがその入力を調整する方法を確認することが重要です。Jackalopeは、ファイルまたは共有メモリーのチャンクをターゲットに提供するように設計されています。これにより、ネットワークパケットペイロードを含むほとんどすべてのものを共有メモリーとして設定できるため、柔軟性が大幅に向上します。秘訣は、ファイルまたは共有メモリーをターゲットに渡す方法になります。Windows上の大規模なアプリケーションでは、一般的なアプローチは、ファズが必要な機能を決定し、ターゲットコードパス内の関数をエクスポートするDLLを見つけて、その関数に入力を渡すことです。エクスポートされた関数がファズへの目的のコードパスの終わりに近いほど、頭痛の種が減り、目的のコードを実行しようとしてより良い結果が得られます。
Netopで行なわれた調査を通じて、システムがどのように機能するか、およびシステムに含まれる非常に多数のDLLと、多数のエクスポートされた関数について深い洞察が得られました。レビューの結果、MeImg.dllにエクスポートされた関数MeImgLoadJpegは、開始するのに適した場所として現れました。
これがファジングの良い候補になる理由は何かというと、まず、この機能をいつ、どのように実行するかが重要です。この関数は、JPEG画像がシステムに読み込まれるたびに、生徒と教師の両方のマシンで実行されます。生徒には、例えば教師が生徒に空白画面機能を使用する場合など、画像がネットワークを介してプッシュされるときです。教師のマシンでは、教師が画像を読み込んで生徒に送信すると、この関数が呼び出されます。ここでの重要なコンポーネントは、頻繁に実行される可能性があり、入力はローカルファイルまたはネットワークファイルから取得でき、システムの両方のコンポーネントに影響を与えることです。
次に、この関数をさらに調査すると、パラメーターがわかりやすくなっています。軽い反転により、ファイルパスを取り、関数内でファイルを直接開くことが簡単にわかります。これにより、Jackalopeでのファジングが非常に簡単になります。これは、ファイルのファジングをサポートし、メモリー内のテストファイルを開いたり操作したりする必要がないためです。また、渡されるパラメーターはごくわずかであり、そのうちのひとつ(BITMAPINFOHEADER)は、Microsoftによって十分に文書化されているため、有効な呼び出しを簡単に作成できます。これは、戻りパラメーターHBITMAPにも当てはまります。これにより、成功と失敗の状態を簡単に判断できます。最後に、この関数のあいまいなコンポーネントはJPEGファイルです。JPEGは、十分に文書化された形式であり、よくファジングされた形式であるため、テストコーパスの生成とクラッシュ分析が簡単になる可能性があります。
テストハーネスの作成
ほとんどのファジング設定では、必要な構造を設定し、ターゲット関数に必要な初期化を完了するために、カスタムプログラムが必要です。これは一般にテストハーネスと呼ばれます。ファジングのターゲットがメインのバイナリまたは実行可能ファイルでない場合は常に必要です。これはほとんどの場合に当てはまる傾向があります。たとえば、Linuxで「file」コマンドのような小さな実行可能ファイルをファズしたい場合、バイナリはコマンドラインから直接入力(ファイル)を取得するため、テストハーネスは必ずしも必要ではありません。目的の状態に到達するためにセットアップは必要ありません。ただし、多くの場合、特にWindowsでは、コードのファジング部分を探すのが一般的です。コードのファジングされたデータを渡す前に、直接アクセスできないか、セットアップが必要です。ここで、テストハーネスが役立ちます。GitHubリポジトリで提供されているJackalopeの「test.cpp」ファイルを使用すると、テストハーネスを作成するときに必要なものの例を簡単に確認できます。ハーネスは、着信テストケースをファイルまたは共有メモリー入力として構成し、ターゲット関数のパラメーターを設定し、関数を呼び出し、必要に応じて、Jackalopeにクラッシュが見つかったことを示すクラッシュを作成する必要があります。
開始するには、最初にターゲット関数を含むDLLをロードする必要があります。Windowsでは、これは通常、「LoadLibrary」の呼び出しで実行されます。私たちの最終的な目的はこのDLL内の関数をテストすることのため、ロードに失敗した場合は、終了する必要があります。
DLLがメモリーにロードされたので、ターゲット関数のアドレスを取得する必要があります。これは通常、「GetProcAddress」を介して行なわれます。
次のステップであるターゲット関数のパラメーターの設定は、間違いなく最も重要であり、テストハーネスを構築する上で最も難しいステップになる可能性があります。これを正しく行なうための最善の方法は、実際のアプリケーションで呼び出されているターゲット関数の例を見つけて、テストハーネスでこの設定を模倣することです。Netopでは、この関数は他の1つの関数によってのみ呼び出されます。 図6は、MeImgLoadJpegを呼び出す関数のIDA逆コンパイルの一部を少しクリーンアップしたバージョンを示しています。
この呼び出しから、有用なクラッシュを見つけたい場合に一貫性を保つために重要ないくつかの重要なポイントを学ぶことができます。最初のパラメータ(a2)は単なるファイルパスであることがわかっています。このコードでは、ファイルパスがワイド文字の形式であることを確認する必要があります。これはWindowsファイルパスの一般的な形式だからです。2番目のパラメーター(v8)は、WindowsBITMAPINFOHEADERオブジェクトです。このコードから、「40」に設定されている「biSize」を除いて、「memset」を使用してBITMAPHEADERオブジェクトのすべてのメンバーが0に設定されていることがわかります。この関数がNetopアプリケーションで呼び出されるのはこれが唯一の場合であるため、Netopを介して利用される可能性のあるバグを見つけたい場合は、この形式に従う必要があります。値が40に設定されている理由は、テストハーネス内の目的との関連性が低くなります。しかし、見つかったクラッシュによっては、調査が必要になる場合があります。同じ原則が3番目と最後のパラメーターにも当てはまります。ここでは、ゼロにハードコードされているので、同じことをしたいと思います。他の値をテストできるかについてはもちろんですが、Netopがゼロにハードコーディングされている場合、実際には演習以外のものを渡すことはできません。図6の追加の理解を使用して、図7の以下のコードをハーネスに挿入します。
パラメータを設定したら、ファズしたい関数を呼び出すだけです。ファジングされたデータはどこにありますか? この場合、ファジングされたデータはjpegファイルになります。ファザーは、変更されたjpegファイルのファイルパスを渡します。
この次のステップは、ターゲット機能に大きく依存します。 ハーネスをクラッシュさせる(未処理の例外をスローする)方法でターゲット関数が失敗しない場合は、失敗したテストケースのクラッシュを作成する必要があります。 これは、図1のtest.cppで確認できます。この場合、ターゲット関数にはエラー処理があり、DLL内で未処理の例外が発生するすべてのケースに関心があります。DLLが未処理の例外をスローすると、テストハーネスがクラッシュします。結果として、適切に機能していることを確認するために、戻り値をチェックする必要があるだけです。これは最初のテストには適していますが、実際のファズ実行のために不要なコードを削除する必要があります。null以外の戻り値は、jpeg画像が解析されたことを意味し、nullは、処理されたエラーが発生したことを意味します。
このすべてのフレームワークが整ったら、有効なイメージを使用してハーネスを実行し、期待される結果が得られることを確認できます。
パフォーマンスに関する考慮事項
上記のテストハーネスコードは、ターゲット関数を正常に実行し、ファザー内で完全に機能しますが、パフォーマンスと結果を向上させるために、いくつかのターゲットを変更することができます。アプリケーションで最も遅い操作のひとつは画面への表示であり、これはファジングの場合に当てはまります。エラーチェックは開発に非常に役立ちます。ただし、”Result was not null” またはその逆を毎回出力すると、1秒あたりの実行数が減り、ファザーに何も追加されません。さらに、追加のコードパスを導入する可能性のある「ファズ」関数に余分なコードを導入しないことが重要です。これにより、ミューテーターは、実際にはハーネスのみであるにもかかわらず、興味深いコードの新しいパスを見つけたと考える可能性があります。結果として、「ファズ」関数は、コードを実行し、この関数以外の他のセットアップアクションを実行するために必要な最低限のものにする必要があります。
テストコーパスの選択
テストハーネスが機能するようになったので、ファザーのテストコーパスまたは入力ファイルを作成する必要があります。ファジングのためのこのステップの重要性は誇張するものではありません。ファザーによって作成されたテストケース、または提供されたものと同じくらい良いテストケースです。多くの場合、最大のコードカバレッジを生成する最小のテスト入力(および最小サイズ)のセットを作成しようとしています。
テストコーパスの選択または構築は、複雑なプロセスになる可能性があります。JPEGのような既知の一般的な形式を使用する利点のひとつは、多くのオープンソースコーパスがあることです。 GitHub上のStrongcourageコーパスは、多くのコーパスが組み合わされており、CVE-2021-36134を見つけるために使用されたテストコーパスであるため、優れたリポジトリです。Jackalopeは、入力ディレクトリを読み取るときにディレクトリを再帰的にトラバースせず、この点でエラーをスローしません。したがって、コーパスディレクトリの深さが1レベルだけであることを確認することが重要です。
結果
この基本的なアウトラインメソッドを使用し、サンプルのテストバイナリと同じ方法でJackalopeを実行すると、書き込みアクセス違反のクラッシュがMeImgLoadJpegでわずか数分で検出されます。この書き込みアクセス違反のバグは、CVE-2021-36134として提出されています。
この違反は、入力ファイルのJPEGヘッダーで提供された値を使用する代わりに、JPEG画像のデフォルトの3色コンポーネントに基づいてメモリコピーの宛先にメモリが割り当てられているために発生します。コピーは、ファイルによって提供された値を使用して、メモリ内のイメージをコピーする場所のアドレスを決定します。不正な形式の画像がデフォルトの3つではなく4つの色成分を持っていると報告し、割り当てられたメモリが実際の画像と同じサイズでない場合、書き込みアクセス違反が発生します。
冒頭のNetop社に提出した前述の広範なレポートとシステム全体の脆弱性を考慮し、分析をこれ以上進めず、バグが本当に悪用可能かどうかを判断することにしました。教師の空白の生徒画面機能を利用することで、ネットワークを介してコードパスを活用できます。このコードは生徒と教師の両方で実行されるため、教師は自分のシステムをクラッシュさせずにこの画像を読み込んで生徒に送信することはできません。攻撃者は、以前に発見および開示された脆弱性を利用して、教師をエミュレートし、この画像を生徒に送信する可能性があります。メモリコピーの宛先は画像の幅と色成分の数に基づいて計算されているため、攻撃者が「書き込み」を行なう場所を制御することは当然です。しかし、画像をさらに無効にすることなく計算できるアドレス空間を使用する必要があります。さらに、コードは、攻撃者の制御下にある画像からピクセルを書き込んでいます。結果として、これは部分的な任意の書き込みにつながる可能性があります。
結論
ファジングは、ソフトウェアの新しい脆弱性を発見するための素晴らしいツールです。ソースコードがあるとファジングが増える可能性がありますが、参入障壁と見なすべきではありません。ブラックボックスターゲットをうまくファズし、業界のセキュリティーを強化するために使用できる多くのツールとテクニックが存在します。
McAfee Advanced Threat Researchチームの目標のひとつは、今日の複雑で絶えず進化する状況における幅広い脅威を特定して明らかにすることです。Google Project ZeroチームのJackalopeとブラックボックスファジング技術を利用して、JPEG解析の脆弱性であるCVE-2021-36134がNetop Vision Pro バージョン9.7.2で発見されました。McAfeeの脆弱性情報公開ポリシーに従い、ATRチームは2021年6月25日にNetopに通知し、Netopチームと直接連携しました。このパートナーシップにより、ベンダーはこのブログで詳しく説明されている脆弱性の効果的な軽減に向けて取り組んでいます。
※本ページの内容は2021年9月27日(ET時間)更新の以下のMcAfee Enterprise Blogの内容です。
原文: Finding 0-days with Jackalope
著者: Douglas McKee
■関連サイト