連載
» 2014年01月23日 18時00分 公開

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

[戸田洋三(JPCERT/CC),@IT]
前のページへ 1|2|3|4       

単純なヒープバッファオーバーフロー

 動的メモリ管理に関連する脆弱性として、まずはヒープ領域上で単純なメモリ上書きが発生するケースについて見てみましょう。ヒープ領域を使ったサンプルプログラムを以下に示します。せっかくなので、関数ポインターを使ったコードにしてみました。

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
void yes(){ printf("YES!\n"); }
void no(){ printf("NO!\n"); }
struct ftable {
  char resp[4];
  void (*Y)();
  void (*N)();
};
int main(int argc, char *argv[]){
  // init
  struct ftable *c = malloc(sizeof (struct ftable));
  if (!c){ exit(10); }
  c->Y = &yes;
  c->N = &no;
  scanf("%s", c->resp);
  // printf("%s\n", c->resp);
  // printf("yes(%x) at (c->Y)(%x)\n", (c->Y), &(c->Y));
  // printf("no(%x) at (c->N)(%x)\n", (c->N), &(c->N));
  if(0 == strncmp("yes", c->resp, 3)){
    (c->Y)();
  }
  else {
    (c->N)();
    // free(c);
  }
  free(c);
}
ヒープバッファオーバーフローを起こすサンプルコード heap_of0.c

 ユーザーの入力を読み取り、それが「yes」だったらyes()関数を実行して「YES!」を出力し、それ以外であればno()関数を実行して「NO!」を出力します。

 このサンプルプログラムの問題は、scanf()関数で入力を読み取っている部分にあります。入力をコピーする先はc->respで4バイトしか用意していません。ところが、scanf()の書式指定文字列は「%s」なので、入力が続く限り読み込んでしまいます。構造体ftableには文字配列respの後ろに関数ポインターYとNが配置されます。つまり、scanf()でバッファオーバーフローが起こると、YやNが上書きされることになるのです。(図4)

図4 プログラム実行時のメモリの様子

 サンプルコードでコメントアウトしてあるprintf()が幾つかありますが、これらの出力を有効にして4バイト以上の長い入力を幾つか処理させてみると、ポインターの値がどのように影響されるかが分かります。ここでは32ビット(i386)Linux環境で試しています。

$ echo "yes" | ./heap_of0
yes(8048454) at (c->Y)(804a00c)
no(8048468) at (c->N)(804a010)
YES!
$ echo "no" | ./heap_of0
yes(8048454) at (c->Y)(804a00c)
no(8048468) at (c->N)(804a010)
NO!
$ 

 ちゃんと入力に応じて「YES!」や「NO!」が出力されているようですね。この出力例からは、

  • 関数ポインターYのアドレス: 0x804a00c
  • 関数ポインターNのアドレス: 0x804a010
  • yes()関数のエントリポイント: 0x8048454
  • no()関数のエントリポイント: 0x8048468

といったことが分かります。

 4文字以上の入力ではどうでしょう。

$ echo "yes0" | ./heap_of0       ← 4文字入力
yes(8048400) at (c->Y)(804a00c)
no(8048468) at (c->N)(804a010)
$ echo "yes00" | ./heap_of0      ← 5文字入力
yes(8040030) at (c->Y)(804a00c)
no(8048468) at (c->N)(804a010)
Segmentation fault
$ echo "yes000" | ./heap_of0     ← 6文字入力
yes(8003030) at (c->Y)(804a00c)
no(8048468) at (c->N)(804a010)
Segmentation fault
$ 

 入力する文字列が4文字、5文字と増えていくと、Yの値が侵食されていくのが分かります。5文字以上の入力ではプログラムが異常終了しています。

 ではYの部分を丸ごと上書きしたらどうでしょうか。細工した入力を作って処理させてみます。

$ perl -e 'print "yesY", chr(0x68), chr(0x84), chr(0x04), chr(0x08);'
|  ./heap_of0
yes(8048468) at (c->Y)(804a00c)
no(8048400) at (c->N)(804a010)
NO!
$

 入力データの先頭は「yes」であり、関数ポインターYが指している関数を呼び出しているのですが、出力結果は「NO!」、つまりno()が実行されています。これは、scanf()による上書きで関数ポインターYの値が無理やりno()のエントリポイントに書き換わってしまっているからです。


 ヒープ領域においても、隣接するデータがバッファオーバーフローによって書き換えられることがある、これはスタックバッファオーバーフローの場合と同じです。

 今回のサンプルコードでは、関数ポインターを上書きすることでコードの実行フローを改変できる例をご紹介しました。次回はヒープ領域の管理の仕組みを悪用した攻撃について説明したいと思います。

[参考情報]

OpenBSD Journal

Is Your Stack Protector Working?

http://undeadly.org/cgi?action=article&sid=20131202075805

VERACODE BLOG

A Tale of Two Compilers

http://www.veracode.com/blog/2013/11/a-tale-of-two-compilers/

AMD64 Architecture Programmer's Manual

Volume 1: Application Programming

http://support.amd.com/TechDocs/24592.pdf

The Art of Computer Programming

Volume 1 Fundamental Algorithms Third Edition日本語版

http://ascii.asciimw.jp/books/books/detail/4-7561-4411-X.shtml

ruBSD 2013

Exploit Mitigation Techniques: An Update after 10 years

http://www.openbsd.org/papers/ru13-deraadt/


前のページへ 1|2|3|4       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

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

メールマガジン登録

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