Use After Freeとヒープスプレー:Beyond Zero-day Attacks(4)(2/3 ページ)
ヒープオーバーフローと別の手法でヒープ破壊を行うUser After Freeと呼ばれる攻撃と、Use After Freeと一緒に使われることが多い、ヒープスプレーについて紹介します
任意のコードを実行させるために必要なこと
リスト1ではアクセス違反で異常終了しましたが、上手にデータをセットすることで、任意のシェルコードが実行できるかもしれません。
具体的なコードについては、文献1、文献2を参照していただきたいのですが、リスト1では、以下のコードでアクセス違反が発生しています。
7D4F2531 8B01 MOV EAX,DWORD PTR DS:[ECX] # ECX = 0xAAAAAAAA
7D4F2533 FF50 34 CALL DWORD PTR DS:[EAX+34]
ECXの値0xAAAAAAAAにアクセスをしようとしますが、このアドレスがプロセスからアクセスできない領域であったため、アクセス違反となっています。
次の命令を見ると、ECX(おそらくは、オブジェクトの先頭)から、34バイト目のアドレス(おそらくは仮想関数)を呼び出す命令になっています(図3)。
もし、0xAAAAAAAAを有効なアドレスに設定し、ECX、EAXの値を制御することができれば、シェルコードを走らせることができそうです。
シェルコードを走らせるために必要な条件を検討します(図4)。
- ECXに上書きをしたヒープ内のアドレス(Pointer #1)を読み込ませる
- そのアドレス(Pointer #1)にはオブジェクトの先頭に相当するPointer #2がセットされている必要があり、Pointer #2も上書きしたヒープ内に割り当てられている
- シェルコードを上書きしたヒープ内に書き込む
- Pointer #2+34バイト目に、シェルコードの先頭アドレスをセットする
以上の条件を満たせば、シェルコードを動かすことができると考えられますが、ヒープは動的なメモリ領域なので、確実にこの操作を行うことは難しいものがあり、単純なやり方では安定した攻撃は行えません。
ヒープスプレーの利用
文献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; }
リスト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 }
これにより、リスト 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
この命令は、EAX(AL)レジスター以外のレジスターには影響を与えず、メモリアクセスもないためアクセス違反を起こしません。つまり、NOP(No Operation: 何もしない命令)と同等の命令に相当します。
ここまでに解説したように、“CALL DWORD PTR DS:[EAX+34]”では、「0x0c0d0c0d」が呼び出され、アドレス(ポインター)として解釈されてきた「0x0c0d0c0d」が、マシン語して解釈されます。マシン語としての「0x0c0d0c0d」の連続は、プログラムの動作に影響を与えずに命令を実行し続けます。そして、データの最後にシェルコードを配置すれば、安全にシェルコードを実行することができます。
このサンプルでは、アドレスとして有効で、マシン語しても意味を持つ、「0x0c0d0c0d」を利用することで、Use After Freeとヒープスプレーを組み合わせ、安定した攻撃ができるようになっています。
Copyright © ITmedia, Inc. All Rights Reserved.