Windows での画像解析のファジング、パート 4: その他の HEIF

Figure 1: Crash details news

Windows HEIF コーデックの画像解析の脆弱性に関する議論を続けると、新しいクラッシュの分析、関数シンボルの再構築、および脆弱性の根本原因の分析、CVE-2022-24457 について検討します。この脆弱性は、Windows 10 および 11 の既定のインストールに存在し、悪意のある画像ファイルを含むフォルダーを参照するだけで脆弱性がトリガーされます。この脆弱性は、Windows が画像のサムネイルを自動的に生成しようとするときに引き起こされます。すべての脆弱性は、Mandiant による開示に続いて Microsoft によって修正されました。

クラッシュ – CVE-2022-24457

Figure 1: Crash details
図 1: クラッシュの詳細

図 1 のクラッシュは、AVX2 命令内の範囲外のメモリ書き込みです。クラッシュ関数はかなり大きく、顕著な一連の AVX2 命令が含まれていることに注意してください。逆コンパイル、操作、およびmemcpy関数への内部呼び出しを簡単に調べた後、これがmemcpyの AVX2 最適化バージョンであると推測できます。 memcpy関数内の境界外書き込みは、詳細な分析に適したクラッシュです。

その他の機能の特定

memcpyとして識別されたクラッシュ関数を使用して、次にバイナリ内の他の関数を識別しようとします。このために、コール スタックを 1 フレーム上に移動し、図 2 の逆コンパイルを確認します。この逆コンパイルには、 sub_18017dd88への呼び出しが含まれていることがわかります。これは、2 番目のパラメーターとして関数名を持つログ関数のように見えます。

図 2: デバッグ ロギング呼び出しを示す逆コンパイル
図 2: デバッグ ロギング呼び出しを示す逆コンパイル

一部のソフトウェアには、本番環境でクラッシュやパフォーマンスの問題を分析するのに役立つロギング機能が付属しています。この場合、ロギング呼び出しは、複数の関数名を再構築し、これらの関数の実装された機能を理解するのに役立ちます。これにより、脆弱性の根本原因を簡単に特定することができます。相互参照を見ると、このロギング関数への呼び出しが 5000 回を超えていることがわかります (図 3 を参照)。

図 3: ロギング機能への相互参照
図 3: ロギング機能への相互参照

多数のロギング呼び出しを考慮して、2 番目の引数から関数名を復元し、呼び出し元関数の名前を変更するスクリプトを作成します。

IDA スクリプト

このプロセスを自動化するために、IDAPython スクリプトが作成されました。以下のアルゴリズムに従って動作します。

  1. ロギング機能への相互参照 (xref) のリストを取得する
  2. 外部参照のリストから一意の呼び出し元関数を取得する
  3. 各呼び出し元を逆コンパイルします
  4. ロギング関数呼び出しを見つけて、2 番目の引数を関数名として取得します
  5. 取得した関数名で呼び出し元関数の名前を変更します

他のプロジェクトで再利用できる汎用スクリプトを作成することにしました。そのために、IDA の逆コンパイラ API を使用して、プロセッサと呼び出し規約固有のコードを回避しました。このスクリプトは、逆コンパイラ ラッパー FIDLも使用します。スクリプトを表 1 に示します。

表 1: 関数の名前変更 IDAPython スクリプト

IDC インポートから *
idaapiインポートから *
idautilsインポートから *
FIDL.decompiler_utils を du としてインポートします。

# f_name: ロギング関数名
# indx: 取得する 0 ベースの引数インデックス
def rename(f_name, indx):
f_ea = get_name_ea_simple(f_name)
f_ea == BADADDR の場合:
print(“{} のアドレスを解決できませんでした”.format(f_name))
戻る

呼び出し元 = セット ()
# 一意の呼び出し元のセットを取得する
for ref in XrefsTo(f_ea, True):
ref.iscode でない場合:
継続する

f = get_func(ref.frm)
f が None の場合:
継続する

f_ea = f.start_ea
callers.add(f_ea)

呼び出し元の caller_ea の場合:
current_fname = get_func_name(caller_ea)
# 関数名が sub_ で始まる場合にのみ名前を変更します
if current_fname.startswith(“sub_”):
c = du.find_all_calls_to_within(f_name, caller_ea)
試す:
# ロギング関数と引数を検証する
len(c) > 0 かつ len(c[0].args) > indx の場合:
f_name_str = c[0].args[indx].val
set_name(caller_ea, “{}”.format(f_name_str), SN_FORCE)
そうしないと:
print(“{} で失敗n”.format(current_fname))
を除外する:
print(“{} の例外n”.format(current_fname))

名前の変更 (‘sub_18017DD88’, 1)

IDA で名前が変更された何千もの関数により、完全な根本原因分析を行うことがかなり容易になります。静的分析には IDA を使用していますが、ほとんどの動的分析には WinDBG + Time Travel Debugging (TTD) が定期的に使用されています。 IDA プラグインFakePDBを使用して、名前を変更したシンボルを WinDBG に移植します。 FakePDB は、IDA データベースから PDB を作成します。これを WinDBG にロードして、デバッグ/トレース機能を強化できます。例を図 4 に示します。

図 4: WinDBG に移植されたシンボル
図 4: WinDBG に移植されたシンボル

バグの根本原因分析

関数msheif_store!CHEIFStreamReader::ReadItemDataの関連コードを表 2 に示します。

表 2: ReadItemData関数の脆弱なコード

/*
CHEIFItemInfoEntry::GetDataSize からの長さの計算
0x309 + 0xee7 => 0x11f0
0x11f0 + 0xfffff200 => 0x1000003f0
*/

QWORD currentOffset = 0;
QWORD の長さ = 0x1000003f0; // CHEIFItemInfoEntry::GetDataSize
status = MFCreateMemoryBuffer(長さ, allocBuff);
もし (状態 < 0)
{
//保釈
}
ながら (1)
{

// 0xee7 + 0x0 >= 0x1000003f0
if (現在のサイズ + 現在のオフセット >= 長さ)
{
//保釈
}
// クラッシュ
OptimizedMemcpy(currentOffset + allocBuff, srcBuff, currentSize);
currentOffset += currentSize;

}

賢明な読者は、可能性のある整数オーバーフロー シナリオのif条件をすぐに指摘するでしょう。ただし、この場合、 CHEIFItemInfoEntry::GetDataSizeで長さを計算する際に、そのようなシナリオは軽減されます。この脆弱性は、 MFCreateMemoryBuffer関数とそのパラメーターを詳しく調べた場合にのみ明らかになります。図 5 は、MSDN からの関数のドキュメントを示しています。

図 5: MFCreateMemoryBuffer 関数のドキュメント
図 5: MFCreateMemoryBuffer 関数のドキュメント

MFCreateMemoryBuffer関数は、長さパラメーターとして 32 ビットの DWORD を受け入れ、割り当てられたバッファーを返します。しかし、表 2 を見ると、関数に渡される長さパラメーターが 64 ビットの QWORD であることがわかります。このような場合、コンパイラは QWORD を DWORD に切り詰めることを決定します。この場合、長さ0x1000003f0ははるかに小さい0x3f0に切り捨てられます。これにより、より小さなバッファーが割り当てられ、より大きなデータがバッファーにコピーされ、境界外書き込みが発生します。

関数名CHEIFStreamReader::ReadItemDataから、 itemボックスを解析しようとしているときに脆弱性が発生したと推測されます。呼び出しをさらに遡ると、関数CItemLocationAtom::ParseAtomから長さが読み取られていることがわかります。これは、図 6 に示すilocボックスを指しています。

図 6: iloc ボックス
図 6: iloc ボックス

ボックスの内容を見ると、ボックスで指定された 3 つの長さの値 (0x309、0xEE7、および 0xFFFFF200) がすべて表示されます。ここで、 iloc仕様を調べて、これらの長さの正確な詳細を把握できます。 HEIF はISO Base Media File Format (ISOBMFF) に基づいていますが、ボックス解析アルゴリズムを使用して適切な仕様を取得することは、複雑であったり、有料であったりする傾向があります。

私たちが試すことができる別のアプローチは、 libheif やnokiatech-heif などの HEIF 画像パーサーのオープンソース実装を調べることです。 PoC ファイルをデコード ルーチンで実行すると、 ilocボックスから正確な長さの詳細が得られます (図 7 参照)。

図 7: デバッガーでの iloc 解析
図 7: デバッガーでの iloc 解析

PoC ファイルに表示される 3 つの長さはエクステント長と呼ばれます。 Microsoft の HEIF 実装は、エクステントの長さをすべて読み取り、それらを合計してから、結果の長さが API 関数MFCreateMemoryBufferによるメモリの割り当てに使用されます。この API は長さを DWORD に切り捨て、より小さいバッファーを割り当てるため、境界外書き込みが発生します。

パッチ

Microsoft は2022 年 3 月この脆弱性にパッチを適用し、全長が 0xC8000000 (~3GiB) を超える場合にエラーを出して救済しました。

結論

このブログ シリーズのパート 4 では、Microsoft の HEIF デコーダーの脆弱性を紹介し、シンボルを再構築して脆弱性の完全な根本原因分析を行う方法を示します。 HEIF コーデックで報告された最新の脆弱性のリストは、次の付録に記載されており、 Mandiant Vulnerability Disclosuresで参照されています。

付録

CVE ID

提出日

確定日

脆弱性の種類

CVE-2022-22007

2021 年 4 月 22 日

2022 年 3 月 8 日

ヒープオーバーフロー

CVE-2022-21926

2021 年 9 月 13 日

2022 年 2 月 8 日

ヒープオーバーフロー

CVE-2022-21917

2021 年 9 月 17 日

2022 年 1 月 11 日

ヒープオーバーフロー

CVE-2022-21927

2021 年 9 月 17 日

2022 年 2 月 8 日

ヒープオーバーフロー

CVE-2022-22006

2021 年 9 月 17 日

2022 年 3 月 8 日

ヒープオーバーフロー

CVE-2022-21844

2021 年 9 月 23 日

2022 年 2 月 8 日

ヒープオーバーフロー

CVE-2022-24453

2021 年 10 月 19 日

2022 年 3 月 8 日

ヒープオーバーフロー

CVE-2022-24457

2021 年 10 月 19 日

2022 年 3 月 8 日

ヒープオーバーフロー

CVE-2022-24456

2021 年 11 月 14 日

2022 年 3 月 8 日

ヒープオーバーフロー

CVE-2022-24532

2021 年 12 月 4 日

2022 年 4 月 12 日

ヒープオーバーフロー

参照: https://www.mandiant.com/resources/blog/fuzzing-image-parsing-windows-part-four

Comments

Copied title and URL