「C言語のgets関数でリターンアドレスを書き換える」の版間の差分

提供: セキュリティ
移動: 案内検索
(ページの作成:「C言語のgets()関数は、バッファオーバーランを引き起こすため、使用するなと言われています。このページは、実際に[[...」)
 
(相違点なし)

2014年2月20日 (木) 12:38時点における最新版

C言語のgets()関数は、バッファオーバーランを引き起こすため、使用するなと言われています。このページは、実際にバッファオーバーランを引き起こすためのサンプルです。

読み方

gets
げっつえす、げっつ

概要

このページのサンプルでは、gets()関数を使用するプログラムのリターンアドレスを書き換え、プログラムに含まれている別の関数を呼び出します。環境によって、バイトオーダー、アドレス、プログラム改ざんのデータのサイズが変わるのでご注意ください。

gets.c は、以下の動作をします。

  1. main()がbar()を呼び出す
  2. bar()は、gets()を呼び出す
  3. stdin から入力を得る
  4. gets()関数が返る
  5. bar()は終わり、main()に返る
  6. main()が終了し、プログラムも終了する


この実験では、gets()の入力データで、bar()のスタックフレームにあるリターンアドレスを書き換えます。結果、以下の振る舞いとなります。

  1. main()がbar()を呼び出す
  2. bar()は、gets()を呼び出す
  3. stdin から入力を得る
  4. gets()関数が返る
    1. gets()の読み込みで、bar()のリターンアドレスがfoo()に書き換わる
  5. bar()は終わり、foo()が呼び出される
  6. fooは、puts()でstdoutに文字列を表示し、プログラムを終了(exit)する

実験環境

  • FreeBSD 64bit環境
    • ただし、バイナリは、32bit ELFで作成する
  • gcc コンパイラ 4.2.1

ソース

gets.c

/*
 * gets.c
 * Copyright (C) 2014 kaoru <kaoru@bsd>
 */
#include <stdio.h>
#include <stdlib.h>
void
foo()
{
        (void) puts("OK");
        exit(EXIT_SUCCESS);
}
void
bar()
{
        char buf[1];
        gets(buf);
}
int
main(int argc, char const* argv[])
{
        bar();
        return 0;
}

コンパイル

32bit環境の場合

cc gets.c

64bit環境の場合

cc -m32 gets.c

以下の警告が出ても、今回の実験では無視します。

% cc -m32 gets.c
/var/tmp//ccAIkjrl.o: In function `bar':
gets.c:(.text+0x2d): warning: warning: this program uses gets(), which is unsafe.

使い方

バッファオーバーランを確認する

最初にデータの書き換えを行う領域を特定するため、バッファをどの程度書き込みをするとバッファオーバーランを引き起こせるか確認します。

% echo -n 'a' | ./a.out
warning: this program uses gets(), which is unsafe.
% echo -n 'aa' | ./a.out
warning: this program uses gets(), which is unsafe.
% echo -n 'aaa' | ./a.out
warning: this program uses gets(), which is unsafe.
% echo -n 'aaaa' | ./a.out
warning: this program uses gets(), which is unsafe.
% echo -n 'aaaaa' | ./a.out
warning: this program uses gets(), which is unsafe.
Segmentation fault
Exit 139

a が 5 文字になったときに Segmentation fault を引き起こしました。

fooのアドレスを調べる

リターンアドレスを書き換え、意図したプログラムを実行するために、実行したいプログラムのアドレスを調べます。 foo()関数のアドレスを調べます。 どのようなやり方でも構いません。objdumpgdbなどいくつも方法はあります。

% objdump -d a.out | fgrep foo
08048490 <foo>:

foo()関数のアドレスは、 08048490 とわかりました。

リターンアドレスを書き換える

アドレスを書き換えるために、プログラムのstdinに入力するデータを作成します。 インテルCPUのバイトオーダーは、リトルエンディアンであるため、アドレス 08048490を並び替えます。 プログラム改ざんのデータは、以下のようになります。

aaaaa\x90\x84\x04\x08

それでは、プログラムに上のコードを入力します。foo()が呼び出され、OKが表示されることを確認できました。

% perl -e 'print "aaaaa\x90\x84\x04\x08"' | ./a.out
warning: this program uses gets(), which is unsafe.
OK

以上のことからgets()関数を使うべきではないということです。

関連項目