C言語でリターンアドレスを書き換える

提供: セキュリティ
移動: 案内検索
スポンサーリンク

C言語プログラムで関数のリターンアドレスを書き換え、意図的に他の関数に処理を移すサンプルコードを紹介します。

概要

C言語のプログラムは、スタックリターンアドレスが積まれています。

関数を呼び出すときに、スタックに関数の引数、リターンアドレスベースポインタなどが積まれます。 関数の処理を終えたとき、その関数の呼び出し元に制御を移します。呼び出し元のプログラムを再開するアドレスは、リターンアドレスとしてスタックに保存されています。

今回、C言語プログラムで意図的に、リターンアドレスを書き換え、任意のコードを実行するサンプルプログラムを紹介します。 このドキュメントは、バッファオーバーランの理解するための1つの参考情報です。

やること

下記のコードを用意します。

void foo() { ...; }
void bar() { ...; }
int main () { bar(); }

main()がbar()を呼び出すだけのプログラムです。 bar()でリターンアドレスを書き換え、foo()に制御を移す、というのを実験的にやってみます。

ソースコード

rewrite_return_address1.c

/*
 * stack.c
 * Copyright (C) 2014 kaoru <kaoru@bsd>
 */
#include <stdio.h>
 
void
foo()
{
        (void) puts("I'm foo");
}
 
void
bar()
{
        void *p;
        *((&p) + 2)= &foo;
}
 
int
main(int argc, char const* argv[])
{
        bar();
        return 0;
}

コンパイル

cc -ggdb rewrite_return_address1.c

実行例

意図通り、foo()が実行され、Segmentation faultが引き起こされました。 main()に戻らずに、強引に foo()を実行したからです。

% ./a.out
I'm foo
Segmentation fault

解説

リターンアドレスの書き換えは、bar()のコードで行っています。 自動変数 ポインタp から2つ上がリターンアドレスです。

void
bar()
{
        void *p;
        *((&p) + 2)= &foo;
}

&p でpのアドレスを表し、pのアドレスを+2にしたアドレスにfooのアドレスを書き込むため、 *((&p) +2) という書式になります。

リターンアドレスは、スタック上の自動変数 p のアドレスから2つ先にあります。

barのスタックフレーム
スタック
自動変数 p
ベースポインタ
リターンアドレス

gdbで確認するとfoo()のアドレスは、 0x80484a0 です。

(gdb) p foo
$3 = {void ()} 0x80484a0 <foo>

gdbでbar()を逆アセンブルすると以下の通りです。

(gdb) disassemble  bar
Dump of assembler code for function bar:
0x080484c0 <bar+0>:     push   %ebp
0x080484c1 <bar+1>:     mov    %esp,%ebp
0x080484c3 <bar+3>:     push   %eax
0x080484c4 <bar+4>:     lea    0x80484a0,%eax
0x080484ca <bar+10>:    mov    %eax,0x4(%ebp)
0x080484cd <bar+13>:    add    $0x4,%esp
0x080484d0 <bar+16>:    pop    %ebp
0x080484d1 <bar+17>:    ret
End of assembler dump.

ここで fooのアドレスをレジスタ eax に代入し、eaxの内容をベースポインタ(ebp)の4byteずらしたリターンアドレスの領域に書き込みます(mov)。

0x080484c4 <bar+4>:     lea    0x80484a0,%eax
0x080484ca <bar+10>:    mov    %eax,0x4(%ebp)

自動変数を利用して、リターンアドレスの書き換えができました。

関連項目




スポンサーリンク