Windows での画像解析のファジング、パート 1: カラー プロファイル

画像の解析とレンダリングは、最新のオペレーティング システム (OS) の基本機能です。画像解析は簡単にアクセスできる攻撃対象領域であり、そのような機能でのリモート コード実行や情報漏えいにつながる脆弱性は、攻撃者にとって価値があります。このマルチパート ブログ シリーズでは、Windows OS に組み込まれている画像パーサーと関連するファイル形式をレビューします。具体的には、ハーネスの作成、コーパスの検索、脆弱性を見つけるためのファジングについて説明します。このシリーズのパート 1 では、カラー プロファイルに注目しています。これは、画像フォーマット自体ではなく、画像に定期的に埋め込まれているものです。

ICC カラー プロファイルとは何ですか?

ウィキペディアは、 ICC カラー プロファイルの十分すぎる説明を提供しています(ICC). プロファイルは、デバイスのソースまたはターゲットの色空間とプロファイル接続空間 (PCS) の間のマッピングを定義することにより、特定のデバイスまたは表示要件の色属性を記述します. この PCS は CIELAB (L*a*b*) のいずれかです.または CIEXYZ. マッピングは、補間が適用されるテーブルを使用して指定するか、変換用の一連のパラメーターを介して指定できます。

簡単に言えば、ICC カラー プロファイルは、画像に埋め込まれ、ICC がサポートするソフトウェアが画像を処理するたびに解析されるバイナリ ファイルです。

仕様

ICC の仕様は約 100 ページあり、ざっと目を通しやすいはずです。仕様を読むと、ファイル形式、さまざまな種類のカラー プロファイル、および色変換の背後にある計算をよりよく理解できます。さらに、ファイル形式の内部構造を理解することで、ファジングの最適化、適切なコーパスの選択、ファジング辞書の作成に使用できる情報が得られます。

Windows のカラー マネージメントの歴史

Windows は、Windows 95 で Image Color Management (ICM) バージョン 1.0 の出荷を開始し、Windows 98 以降ではバージョン 2.0 を出荷しました。 Windows Vista 以降では、Windows Color System (WCS) 1.0 に対する大幅なオーバーホールが行われました。 ICC カラー プロファイルはバイナリ ファイルですが、WCS カラー プロファイルはファイル形式として XML を使用します。このブログ投稿では、ICC カラー プロファイルに焦点を当てます。

Microsoft には、サポートされている Windows APIのリストがあります。 OpenColorProfileなど、明らかに名前が付けられた API のいくつかを調べると、MSCMS.dll に実装されていることがわかります。この DLL は一般的なエントリ ポイントであり、Microsoft の Color Management Module (CMM) および Adobe の CMM などのサード パーティの CMM のロードをサポートします。 Microsoft の CMM (ICM) は、system32 ディレクトリに ICM32.dll として存在します。

ICM32
図 1: ICM32

Windows の CMM は、Windows 95 の時代にサードパーティによって作成されたものであり、現在でも多かれ少なかれ同じコードで出荷されています (数十年にわたるセキュリティ修正が適用されています)。このような古いモジュールを見ると、新しい脆弱性を発見できる可能性があります。しかし、これは小規模なモジュールでもあり、内部の製品セキュリティ チームと外部の研究者の両方による複数回のレビューとファジングを経た可能性があり、私の希望はある程度低下しています。 ICM32 の最近の脆弱性を探すと、Project Zero と ZDI の研究者による 2017 年から 2018 年にかけて複数のバグが見つかりましたが、2019 年以降は比較的沈黙しています。

ハーネスを作る

MSDN には ICM API のリストがありますが、Windows で ICC 関連の操作に使用される API シーケンスを見つける必要があります。 API シーケンスを見つける方法の 1 つは、Windows DLL と EXE の逆アセンブリを検索して、使用されているカラー プロファイル API を見つけることです。もう 1 つのアプローチは、Little CMS (LCMS) などのオープン ソース カラー マネージメント システムのハーネスを見つけることです。これらは両方とも、カラー プロファイルを開いて色の変換を作成する機能を備えた非常に小さな API セットを指すことになります。

この情報を考慮して、単純な初期ハーネスが作成されました。

#include <stdio.h>
#include <Windows.h>
#include <Icm.h>

#pragma comment(lib, "mscms.lib")

int main(int argc, char** argv)
{
    char dstProfilePath[] = "sRGB Color Space Profile.icm";
    tagPROFILE destinationProfile;
    HPROFILE   hDstProfile = nullptr;   

    destinationProfile.dwType = PROFILE_FILENAME;
    destinationProfile.pProfileData = dstProfilePath;
    destinationProfile.cbDataSize = (strlen(dstProfilePath) + 1);

    hDstProfile = OpenColorProfileA(&destinationProfile, PROFILE_READ,
        FILE_SHARE_READ, OPEN_EXISTING);
    if (nullptr == hDstProfile)
    {
        return -1;
    }   

    tagPROFILE sourceProfile;
    HPROFILE   hSrcProfile = nullptr;
    HTRANSFORM hColorTransform = nullptr;     

    DWORD dwIntent[] = { INTENT_PERCEPTUAL, INTENT_PERCEPTUAL };
    HPROFILE hProfileList[2];   

    sourceProfile.dwType = PROFILE_FILENAME;
    sourceProfile.pProfileData = argv[1];
    sourceProfile.cbDataSize = (strlen(argv[1]) + 1);

    hSrcProfile = OpenColorProfileA(&sourceProfile, PROFILE_READ,
        FILE_SHARE_READ, OPEN_EXISTING);
    if (nullptr == hSrcProfile)
    {
        return -1;
    }   

    hProfileList[0] = hSrcProfile;
    hProfileList[1] = hDstProfile;

    hColorTransform = CreateMultiProfileTransform(
        hProfileList,
        2,
        dwIntent,
        2,
        USE_RELATIVE_COLORIMETRIC | BEST_MODE,
        INDEX_DONT_CARE
    );

    if (nullptr == hColorTransform)
    {
        return -1;
    }   

    DeleteColorTransform(hColorTransform);
    CloseColorProfile(hSrcProfile);
    CloseColorProfile(hDstProfile);
    return 0;
}

リスト 1: ハーネス

コーパスと辞書のハンティング

複数のカラー プロファイルを提供するサイトは、インターネット上で見つけることができます。カラー プロファイルのもう 1 つの主なソースは画像です。多くの画像ファイルにはカラー プロファイルが含まれていますが、カラー プロファイルをスタンドアロン ファイルにダンプするには、プログラミングやツールが必要です。

仕様に目を通すだけで、7 つの異なるカラー プロファイルすべてから少なくとも 1 つのサンプルがコーパスに含まれていることを確認できます。これをコード カバレッジ情報と共に使用して、ファジング用のコーパスの最初のセットを準備できます。

ファザーが追加のコード パスを見つけるのに役立つディクショナリは、仕様をくまなく調べて一意のタグ名と値のリストを作成することで準備できます。 LCMS などでのオープンソースのファジングの試みから辞書を見つけることもできます。

ファジング

16 コアのマシンを使用して、最初のコーパス セットでハーネスをファジングしました。 MSCMS.dll と ICM32.dll からのコード カバレッジ情報は、ファザーのフィードバックとして使用されました。クラッシュは数日以内に現れ始めました。

CVE-2020-1117 — InitNamedColorProfileData のヒープ オーバーフロー

次のクラッシュは、境界外を読み取ろうとしているときにicm32!SwapShortOffsetで発生します。

0:000>r
rax=0000023690497000 rbx=0000000000000000 rcx=00000000000000ff
rdx=000000000000ffff rsi=0000023690496f00 rdi=0000023690496fee
rip=00007ffa46bf3790 rsp=000000c2a56ff5a8 rbp=0000000000000001
r8=0000000000000014 r9=0000023690497002 r10=0000000000000014
r11=0000000000000014 r12=000000c2a56ff688 r13=0000023690492de0
r14=000000000000000a r15=000000004c616220
iopl=0 nv up eing nz acpecy
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000293
icm32!SwapShortOffset+0x10:
00007ffa`46bf3790 0fb610 movzx edx,byte ptr [rax] ds:00000236`90497000=??

0:000> !ヒープ -p -a @rax
アドレス 0000023690497000 が見つかりました
_DPH_HEAP_ROOT @ 23690411000
使用中の割り当て (DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
23690412b60: 23690496f00 100 - 23690496000 2000
00007ffa51644807 ntdll!RtlDebugAllocateHeap+0x000000000000003f
00007ffa515f49d6 ntdll!RtlpAllocateHeap+0x0000000000077ae6
00007ffa5157babb ntdll!RtlpAllocateHeapInternal+0x00000000000001cb
00007ffa51479da0 msvcrt!malloc+0x0000000000000070
00007ffa46bf3805 icm32!SmartNewPtr+0x0000000000000011
00007ffa46bf37c8 icm32!SmartNewPtrClear+0x0000000000000014
00007ffa46c02d05 icm32!InitNamedColorProfileData+0x0000000000000085
00007ffa46bf6e39 icm32!Create_LH_ProfileSet+0x0000000000004e15
00007ffa46bf1973 icm32!PrepareCombiLUTs+0x0000000000000117
00007ffa46bf1814 icm32!CMMConcatInitPrivate+0x00000000000001f4
00007ffa46bf12a1 icm32!CWConcatColorWorld4MS+0x0000000000000075
00007ffa46bf11f4 icm32!CMCreateMultiProfileTransformInternal+0x00000000000000e8
00007ffa46bf1039 icm32!CMCreateMultiProfileTransform+0x0000000000000029
00007ffa48f16e6c mscms!CreateMultiProfileTransform+0x000000000000024c
00007ff774651191ldr+0x0000000000001191
00007ff7746514b4ldr+0x00000000000014b4
00007ffa505a7bd4 KERNEL32!BaseThreadInitThunk+0x0000000000000014
00007ffa515aced1 ntdll!RtlUserThreadStart+0x0000000000000021

リスト 2: クラッシュ情報

icm32!SwapShortOffsetは unsigned short 値を読み取り、それらをbswapして同じ場所に保存するため、読み取りプリミティブと書き込みプリミティブの両方がクラッシュします。

unsigned __int16 *__fastcall SwapShortOffset(void *sourceBuff, unsigned int offset, unsigned int len)
{
unsigned __int16 *endBuff; // r9
unsigned __int16 *結果; // ラックス

endBuff = (sourceBuff + len);
for (結果 = (sourceBuff + オフセット); 結果 < endBuff; ++結果)
*結果 = _byteswap_ushort(*結果); // 読み取り、bswap、および書き込み
結果を返します。
}

リスト 3: 逆コンパイルされた SwapShortOffset

クラッシュ関数icm32!SwapShortOffsetは、バグの根本原因をすぐには示しません。そのためには、 icm32!InitNamedColorProfileDataまで 1 回呼び出す必要があります。

__int64 __fastcall InitNamedColorProfileData(__int64 a1, void *hProfile, int a3, _DWORD *a4)
{


errCode = CMGetPartialProfileElement(hProfile, ‘ncl2’, 0, pBuffSize, 0i64); // ncl2 要素のサイズを取得
if (エラーコード)
errCode を返します。
minSize = pBuffSize[0];
もし ( pBuffSize[0] < 0x55 )
最小サイズ = 0x55;
pBuffSize[0] = minSize;
outBuff = SmartNewPtrClear(minSize, &errCode); // ncl2 のバッファを割り当てます


errCode = CMGetPartialProfileElement(hProfile, ‘ncl2’, 0, pBuffSize, outBuff); // ncl2 要素をバッファに読み込む
if (!errCode)
{


totalSizeToRead = カウント * totalDeviceCoord;
if ( totalSizeToRead < 0xFFFFFFFFFFFFFFAEui64 && totalSizeToRead + 0x51 <= pBuffSize[0] ) // totalSizeToRead + 0x51 <= 要素サイズ?
{
currPtr = outBuff + 0x54; // 間違ったオフセット 0x54 が使用されています


行う
{
SwapShortOffset((currPtr + 0x20), 0, 6u);

– カウント;
}while(カウント)

リスト 4: 逆コンパイルされた InitNamedColorProfileData

ここで、コードは「ncl2」タグ/要素を読み取り、ファイルからストリームのサイズを取得しようとします。バッファーが割り当てられ、要素 ‘ncl2’ の完全なコンテンツを読み取るためにもう一度同じ呼び出しが行われます。このバッファーは、デバイス座標のカウントと数を見つけるために解析され、読み取り/書き込みがバッファー サイズで終了することを確認することによって値が検証されます。ここでの脆弱性は、検証に使用されるオフセット (0x51) が、バッファー ポインターを進めるために使用されるオフセット (0x54) よりも小さいことです。このエラーは、3 バイトの範囲外の読み取りおよび書き込みを提供します。

これに対する修正は非常に簡単でした。検証オフセットを 0x54 に変更することで、Microsoft はこのバグを修正しました。

その他の脆弱性

以前の脆弱性を見ると、サイズの読み取り、割り当て、およびコンテンツの読み取りにCMGetPartialProfileElement関数を使用するパターンが見られます。この種のパターンは、制約のないサイズやサイズにオフセットを追加する際の整数オーバーフローなどのバグを引き起こす可能性があります。私はこの機能を追求し、そのようなインスタンスが ICM32.dll 内に存在するかどうかを確認することにしました。

未チェックのオフセット アクセスを持つ 3 つのインスタンスを見つけました: CMConvIndexToNameProfileCMConvNameToIndexProfile 、およびCMGetNamedProfileInfoProfile 。これらの関数はすべて、エクスポートおよび文書化された MSCMS 関数 (それぞれConvertIndexToColorNameCMConvertColorNameToIndex 、およびGetNamedProfileInfo ) からアクセスできます。

__int64 __fastcall CMConvIndexToNameProfile(HPROFILE hProfile, __int64 a2, __int64 a3, unsigned int a4)
{


errCode = CMGetPartialProfileElement(hProfile, ‘ncl2’, 0, pBuffSize, 0i64); // 読み取りサイズ
if (!errCode)
{
allocBuff = SmartNewPtr(pBuffSize[0], &errCode);
if (!errCode)
{
errCode = CMGetPartialProfileElement(hProfile, ‘ncl2’, 0, pBuffSize, allocBuff); // バッファに読み込む
if (!errCode)
{
SwapLongOffset((allocBuff + 12), 0, 4u); // 12 > *pBuffSize ?
SwapLongOffset((allocBuff + 16), v12, v13);

リスト 5: 逆コンパイルされた CMConvIndexToNameProfile

CMConvIndexToNameProfileと他の 2 つの関数で発見されたバグは、「 ncl2 」要素の最小長チェックがなく、オフセット 12 と 16 が読み取りと書き込みの両方で直接アクセスされることです。 allocBufferの値が 12 より小さい。

Microsoft は、これらの機能を使用する Windows バイナリがないため、これら 3 つの脆弱性を直ちに修正しないことを決定しました。これとは別に、これらの API を使用している Windows またはサードパーティ ソフトウェアは見つかりませんでした。

結論

このブログ シリーズのパート 1 では、カラー プロファイルを調査し、ハーネスを作成し、コーパスを探し、複数の脆弱性を発見しました。第 2 部では、比較的あまり話題にされていない脆弱性クラスである初期化されていないメモリについて取り上げます。

参照: https://www.mandiant.com/resources/blog/fuzzing-image-parsing-in-windows-color-profiles

コメント

タイトルとURLをコピーしました