動的メモリ管理に関する脆弱性(その2)もいちど知りたい、セキュアコーディングの基本(7)(1/2 ページ)

今回は動的メモリ管理に関する脆弱性の続きです。unlinkテクニックと呼ばれる古典的な攻撃手法を紹介します。

» 2014年07月07日 18時00分 公開
[戸田洋三(JPCERT/CC) ,@IT]

Windowsのヒープメモリ攻撃手法の解説資料

 この連載では、前回から「動的メモリ管理に関する脆弱性」と題してヒープメモリの扱いに関連する話題を取り上げていますが、副読本としてちょうどよさそうな資料を見つけたので紹介します。

 これは「Heap Exploitのこれまでと現状」と題したレポートで、@ITでも連載を行っているFFRIというセキュリティ会社が、月例レポートの1つとして2013年12月に公開したものです。

 このレポートでは、Windows環境における動的メモリ管理に関する脆弱性の攻撃手法について概説しています。Windows環境におけるヒープメモリ管理や具体的な攻撃手法に興味があるなら、まずこの資料でおおざっぱなイメージをつかんでから、より詳しい情報に当たるとよいのではないかと思います。

【関連リンク】

FFRI Monthly Report 

「Heap Exploitのこれまでと現状」

http://www.ffri.jp/assets/files/monthly_research/MR201312_History%20and%20Current%20State%20of%20Heap%20Exploit_JPN.pdf


 さて、今回はヒープバッファーオーバーフローの脆弱性があるプログラムに対して行われる古典的な攻撃手法として、「unlinkテクニック」と呼ばれる手法のエッセンスを紹介します。

ヒープメモリ管理で行われる操作

 前回も少し説明しましたが、ヒープメモリはメモリ管理ライブラリによって、ある程度の大きさのブロックの集まりとして管理されます。プログラムが動作するにつれてmalloc()やfree()の呼び出しが繰り返され、やがて使用中のブロックと使用されていない(解放された)ブロックがヒープメモリ上に混在する状態になっていきます。

 このような状況を管理する仕組みとして、プログラムの読み書きに使われるメモリブロックごとに、使用中か解放済みかを表すフラグやメモリブロックのサイズ情報など、管理用の情報を一緒にまとめて扱うという方法が考えられます(図1)。

図1 ヒープメモリを管理するメモリ管理ライブラリが扱うメモリブロックの内部構造

 このようなブロックがメモリのあちこちに散在する状況で、メモリ管理ライブラリは、malloc()呼び出しに対して適切なサイズのブロックを探し出してくる必要があります。

 典型的なアプローチがリンクトリストを使ったメモリブロックの管理です。ブロックを数珠つなぎにして持っておき、割り当て依頼が来るとリンクをたどって適切なブロックを探し出します。また、メモリブロックが解放された場合にはこのリストに戻して次の割り当てに備えるわけです。

 このようなアプローチで、メモリ割り当てや解放のときにどのような操作が行われるのか、もう少し詳しく見てみましょう。

 まず、プログラムの実行開始時点では、空きメモリブロックのリストが用意されます(リストからたどれるブロックは「解放済み」のメモリブロックであるということです)。

 ここでは、各メモリブロックに付随する管理情報として、

  • size:そのブロック自体のサイズ
  • next:リスト上の次のブロックへのポインター
  • prev:リスト上の前のブロックへのポインター

を持っているものとします。図2では、3個のブロック、n1、n2、n3からなるリストを図示しています。

図2 空きメモリブロックのリスト

 malloc()呼び出しがあると、メモリ管理ライブラリは、先頭ポインターからメモリブロックを順番に調べ、適切な空きメモリブロックを返します。また、free()呼び出しが行われると、解放されたメモリブロックを再度リストにつなぎます。

 図2においてmalloc()が呼ばれ、メモリ管理ライブラリがブロックn2を選んだとしましょう。すると、ブロックn2をリストから外し、malloc()の返り値としてブロックn2(の空きメモリ部分の先頭)を指すポインターを返す必要があります。

 リストからブロックを外す手順を書き下すと、次のようになるでしょう(図3、図4)。

  • ブロックn2の「prev」ポインターから、ブロックn1の先頭アドレスを得る
  • ブロックn2の「next」ポインターから、ブロックn3の先頭アドレスを得る
  • ブロックn1の「next」ポインターの値として、n3の先頭アドレスを代入
  • ブロックn3の「prev」ポインターの値として、n1の先頭アドレスを代入
図3 ブロックn2からn1とn3の先頭アドレスを取得
図4 ブロックn1とn3の管理情報を更新

 この操作をCのコードで表すと次のようになります。

typedef struct node_t {
    size_t size;
    struct node_t *next;
    struct node_t *prev;
    char space[BLOCKSIZE];
} *node;
// e をメモリブロックのリストから外す
void unlink(node e) {
  node n3 = e->next;
  node n1 = e->prev;
  n3->prev = n1;
  n1->next = n3;
}

 一方、free()でメモリブロックが解放された場合にリストに取り込む操作は次のようになるでしょう(図5、図6)。

  • 解放されたブロックnをリストのどの位置に入れるかを決める(ここではブロックn1とブロックn3の間に入れるとする)
  • ブロックn1の「next」ポインターに、解放されたブロックnの先頭アドレスを代入
  • ブロックn3の「prev」ポインターに、解放されたブロックnの先頭アドレスを代入
  • 解放されたブロックnの「next」ポインターに、ブロックn3の先頭アドレス(==ブロックn1の「next」ポインターに入っていた値)を代入
  • 解放されたブロックnの「prev」ポインターに、ブロックn1の先頭アドレス(==ブロックn3の「prev」ポインターに入っていた値)を代入
図5 ブロックn1とn3の管理情報を更新
図6 ブロックnの管理情報を更新

 こちらの操作もCのコードで表すと次のようになります。

//eをn1の後ろに入れる
void insert(node e, node n1){
  node n3 = n1->next;
  n1->next = e;
  n3->prev = e;
  e->next = n3;
  e->prev = n1;
}

 実際のメモリ管理では、このようなリストに対する基本操作に加えて、解放されたブロックが複数隣接している場合に1個のブロックに統合して管理するとか、ブロックの大きさごとに複数のリストを作って管理するなどの工夫がありますが、話を簡単にするために省略します。

 アプリケーションプログラムからmalloc()やfree()が呼び出されると、その内側ではこのようなヒープメモリ管理のための作業(リストに対する操作)が行われているのです。

 さて、これでunlinkテクニックを説明する準備ができました。

       1|2 次のページへ

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。