リターンアドレスと関数の戻り道(episode-06)

黒ネコと学ぶ・論理的エクスプロイト開発への道

プログラムは、関数を呼び出して処理を進め、終わると必ず「元の場所」に戻ってきます。
私たちは普段、この動きを当たり前のように使っていますが、実はこの「戻り道」こそが、エクスプロイト開発の重要な分岐点になります。
 
episode-05では、GDBを使ってスタックを観察しました。
今回はそのスタックの中にひっそりと存在する 「リターンアドレス」 に注目します。
 [fuki-r]関数って…終わったら、どうやって元の場所に戻るの?[/fuki-r]
[fuki-l]いいところに気づいたわね。その「戻る先の住所」を管理しているのが、リターンアドレスなの。[/fuki-l]
この仕組みを理解すると、なぜスタックオーバーフローが危険なのか、なぜ制御が奪われるのかが、論理的に見えてきます。
※イメージです。

関数呼び出しの「見えない約束」

[fuki-r]mainからhelloを呼んだら、helloが終わったあと、mainに戻るよね?[/fuki-r]
[fuki-l]ええ。そのとき、CPUはこう考えているの。[/fuki-l]
「hello が終わったら、どこに戻ればいい?」
その答えが リターンアドレス です。
 
 

リターンアドレスとは何か?

リターンアドレスとは、関数が終了したあとに、次に実行する命令の場所(アドレス)のことです。
 
・関数が呼び出されるとき、CPUは次の処理を行います。
1.「戻ってくる場所(アドレス)」を決める。
2.そのアドレスを スタックに積む。
3.関数の中へジャンプする。
[fuki-r]えっ、戻り先もスタックに積まれてるの?[/fuki-r]
[fuki-l]そう。だからスタックは「データ」だけじゃなく、「制御情報」も持っているの。[/fuki-l]
 

スタックの中身をもう一度整理しよう

関数"hello()"の中にいるとき、スタックには例えばこんなものがあります。
・引数
・ローカル変数 x
・リターンアドレス ← ここが重要!
・前のフレーム情報
[fuki-r]リターンアドレスって、結構大事な場所にあるね…[/fuki-r]
[fuki-l]ええ。ここを書き換えられたら、戻る先そのものが変わるわ。[/fuki-l]
 
 

GDBで「戻り道」を意識してみる

GDBで関数の中にいるとき、こんな流れを思い出してください。
【gdbの画面】
(gdb) break hello
(gdb) run
(gdb) bt
 
“bt"(backtrace)で表示されるのは、どの関数から、どの関数へ来たか。
つまり、戻り道の履歴です。
[fuki-r]backtrace って、地図みたいだね![/fuki-r]
[fuki-l]その通り。エクスプロイト開発では、この地図を「逆に使う」の。[/fuki-l]
 
 

なぜエクスプロイトと関係するのか?

ここがとても重要です。
もし攻撃者が、スタック上のデータを書き換えられる。そして リターンアドレスに届いてしまったらどうなるでしょう?
[fuki-r]……戻る先を、攻撃者が決められる?[/fuki-r]
[fuki-l]正解。それが、制御フローの乗っ取りよ。[/fuki-l]
プログラムは疑いもせず、「ここに戻れ」と書かれたアドレスへジャンプします。
それが本来のコードでなくてもです。
  
大切なのは、今はまだ攻撃しないこと。episode-06のゴールは、
・リターンアドレスは何か。
・なぜ重要なのか。
・どこに置かれているのか。
を、頭の中で図として描けるようになることです。
[fuki-r]なんだか…仕組みが見えてきた気がする。[/fuki-r]
[fuki-l]その“見えた感覚”が、論理的エクスプロイトの入口よ。[/fuki-l]
 
 

リターンアドレスと関数の戻り道のまとめ

今回は、リターンアドレスと関数の戻り道について学びました。
プログラムは関数を呼び出すたびに、「どこへ戻るか」という情報をスタックに保存しています。
これがリターンアドレスです。
 
スタックは単なるデータ置き場ではなく、プログラムの流れそのものを支配する場所でもあります。
だからこそ、ここが壊れると挙動全体が変わってしまいます。
重要なのは、「怖いテクニック」を覚えることではありません。
なぜ危険なのかを、構造として理解することです。
 
次回は、スタックフレームとRBP(ベースポインタ) に注目し、「どうやってこの構造が保たれているのか」を見ていきます。お楽しみにね。