検索
連載

Use After FreeとヒープスプレーBeyond Zero-day Attacks(4)(2/3 ページ)

ヒープオーバーフローと別の手法でヒープ破壊を行うUser After Freeと呼ばれる攻撃と、Use After Freeと一緒に使われることが多い、ヒープスプレーについて紹介します

Share
Tweet
LINE
Hatena

任意のコードを実行させるために必要なこと

 リスト1ではアクセス違反で異常終了しましたが、上手にデータをセットすることで、任意のシェルコードが実行できるかもしれません。

 具体的なコードについては、文献1、文献2を参照していただきたいのですが、リスト1では、以下のコードでアクセス違反が発生しています。

7D4F2531 8B01 MOV EAX,DWORD PTR DS:[ECX]    # ECX = 0xAAAAAAAA

7D4F2533 FF50 34 CALL DWORD PTR DS:[EAX+34]

リスト 2 アクセス違反を発生させたコード

 ECXの値0xAAAAAAAAにアクセスをしようとしますが、このアドレスがプロセスからアクセスできない領域であったため、アクセス違反となっています。

 次の命令を見ると、ECX(おそらくは、オブジェクトの先頭)から、34バイト目のアドレス(おそらくは仮想関数)を呼び出す命令になっています(図3)。


図 3 アクセス違反の発生と仮想関数の呼び出し

 もし、0xAAAAAAAAを有効なアドレスに設定し、ECX、EAXの値を制御することができれば、シェルコードを走らせることができそうです。

 シェルコードを走らせるために必要な条件を検討します(図4)。

  1. ECXに上書きをしたヒープ内のアドレス(Pointer #1)を読み込ませる
  2. そのアドレス(Pointer #1)にはオブジェクトの先頭に相当するPointer #2がセットされている必要があり、Pointer #2も上書きしたヒープ内に割り当てられている
  3. シェルコードを上書きしたヒープ内に書き込む
  4. Pointer #2+34バイト目に、シェルコードの先頭アドレスをセットする

 以上の条件を満たせば、シェルコードを動かすことができると考えられますが、ヒープは動的なメモリ領域なので、確実にこの操作を行うことは難しいものがあり、単純なやり方では安定した攻撃は行えません。


図4 シェルコードを走らせるために必要な項目

ヒープスプレーの利用

 文献1、文献2では、この問題を回避するために、ヒープスプレーを使ったアプローチが紹介されています。

 この例では、まずUser After Freeを行う前に、ヒープ領域を確保し、870400バイトの0x0c0dの後にシェルコードを連結したデータを100個ヒープ上に展開します。

function HeapSpray()
{
  Array2 = new Array();
  var Shellcode = unescape( '%ucccc%ucccc'); // 仮の攻撃用のシェルコード
  var SprayValue = unescape('%u0c0d');   // スプレーして埋める用のNOPコード
  do { SprayValue += SprayValue } while( SprayValue.length < 870400 );
  for (j = 0; j < 100; j++) Array2[j] = SprayValue + Shellcode;
}
リスト 3 ヒープスプレーの例(文献1、2)

 リスト1では解放されたヒープの上書きに0xAAAAを使っていましたが、0x0c0dで上書きをします。

function FOverwrite()
{
  buffer = "\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d";
  for (i = 0; i < Array1.length; i++)
  {
    Array1[i].data = buffer;
  }
  var t = Element1.srcElement;  // Access the pointer to the deleted object, trigger crash
}
リスト 4 解放されたヒープ領域の上書き(文献1、2)

 これにより、リスト 1では 0xAAAAAAAAだったECXの値が、「0x0c0d0c0d」になります。

 つまり、リスト 3でヒープスプレーを行ったヒープ領域に、アドレス「0x0c0d0c0d」が含まれていれば、”MOV EAX,DWORD PTR DS:[ECX]”(リスト2)でアクセス違反にならずに、その内容をEAXにセットすることができます。

 この際に、Pointer #2に相当するアドレス「0x0c0d0c0d」の内容は、同じく「0x0c0d0c0d」が書き込まれているため、オブジェクトの先頭アドレスとして、「0x0c0d0c0d」がセットされます。さらに“CALL DWORD PTR DS:[EAX+34]”では、「0x0c0d0c2f」番地の内容、(またしても)「0x0c0d0c0d」が呼び出されます。

 「0x0c0d0c0d」は特別なコードで、マシン語として逆アセンブルをすると次の命令を意味します。

OR AL, 0D
リスト 5 「0x0c0d0c0d」の逆アセンブル

 この命令は、EAX(AL)レジスター以外のレジスターには影響を与えず、メモリアクセスもないためアクセス違反を起こしません。つまり、NOP(No Operation: 何もしない命令)と同等の命令に相当します。

 ここまでに解説したように、“CALL DWORD PTR DS:[EAX+34]”では、「0x0c0d0c0d」が呼び出され、アドレス(ポインター)として解釈されてきた「0x0c0d0c0d」が、マシン語して解釈されます。マシン語としての「0x0c0d0c0d」の連続は、プログラムの動作に影響を与えずに命令を実行し続けます。そして、データの最後にシェルコードを配置すれば、安全にシェルコードを実行することができます。

 このサンプルでは、アドレスとして有効で、マシン語しても意味を持つ、「0x0c0d0c0d」を利用することで、Use After Freeとヒープスプレーを組み合わせ、安定した攻撃ができるようになっています。


図5 「0x0c0d0c0d」を使ったUse After Freeとヒープスプレー

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る