C言語で関数の戻り先アドレスを書き換えてみる

バッファオーバーフローを利用して関数の戻り先アドレス書き換えるサンプルとか見かけるけど、自分でやったことがなかったので、やってみた。

ソースコード (test.c)

#include <stdio.h>

void hack()
{
  printf("Hacked!\n");
}

void func()
{
  int a[1];
  a[2] = &hack;
}

int main()
{
  func();
  return 0;
}

実行

Ubuntu13.04の32ビット版上のgcc 4.7.3でコンパイルし、実行。

$ gcc test.c
$ ./a.out
Hacked!
Segmentation fault (コアダンプ)

関数hackはどこからも呼ばれないはずですが、実行され、その後セグメンテーションエラーでプログラムが終了しました。

解説

C言語のプログラムでは、関数が呼び出されると、スタック領域に関数フレームが作られます。
f:id:kuro_m88:20130801022245p:plain
関数フレームの一番上には、ローカル変数が配置され、その下にその関数を呼び出した関数の関数フレームのアドレス(ベースポインタ, BP)が書かれていて、その下にはその関数の実行が終了した後の戻り先アドレスが書かれています。
配列aは要素数1として宣言したので、関数フレームにはint型の数値一個分の変数を格納するスペースしかありません。
ここで、あえてa[2]というアドレスにアクセスしてみます。すると、そこは関数の戻り先アドレスが書いてある場所ですので、ここに関数hackのアドレス(&hack)を代入してあげれば、funcの実行終了後にmainに戻ることなくhackが実行されます。
また、最後にセグメンテーションエラーが起きたのは、関数hackが終了した後の戻り先アドレスはどこにも書いておらず、不定であるためにたまたまそこに適当なアドレスの命令を実行しようとしたためです。