バッファオーバラン

2年以上放置してからの新しい記事。

gets()やscanf()がオーバーフローを起こすだのと世間で噂のバッファオーバラン。具体的にどうコードが実行されるのか謎だという方は結構いるんじゃないでしょうか。
いきなりですが、バッファオーバランの実験をしてみました。最適化しないでコンパイルしたらたぶん動きます。
バッファオーバーランが起こるとfoo()が呼び出され終了コード1で終了します。起こらなければ"The program is going to exit normally."と表示され終了コード0で終了します。

#include <stdio.h>
#include <stdlib.h>

int i;  // overrun()内で宣言するとa[]のオーバーフローで上書きされる可能性がある

// foo()はどこからも呼び出されていない
void foo()
{
    printf("foo() called. (buffer overrun!)\n");
    exit(1);
}

void overrun()
{
    int a[1];

    for(i = 0; i < 5; i++)
        a[i] = (int)foo;    // foo()のアドレスを代入
}

int main(void)
{
    overrun();

    puts("The program is going to exit normally.\n");
    return 0;
}

Visual Studio 2012 x86 Nativeのプロンプトでコンパイル。

C:\Users\User\Desktop\overflow\overflow>cl -W4 overflow.c
Microsoft(R) C/C++ Optimizing Compiler Version 17.00.60315.1 for x86
Copyright (C) Microsoft Corporation. All rights reserved.

overflow.c
Microsoft (R) Incremental Linker Version 11.00.60315.1
Copyright (C) Microsoft Corporation. All rights reserved.

/out:overflow.exe
overflow.obj

C:\Users\User\Desktop\overflow\overflow>overflow
foo() called. (buffer overrun!)

プロジェクトを作って逆アセンブルも見てみました


0x8b1310を見ればわかるように、fooのアドレスは0x8b10b9です。
見事にリターンアドレスが書き換えられ、この後foo()が呼び出されました。