ハッカーくろちゃんが学ぶ、スタックフレームとRBP(episode-07)

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

これまでの話で、私たちは「スタック」というメモリ領域について学んできました。関数が呼び出されるとスタックに積まれ、処理が終わると元の場所に戻る。この一連の流れは、プログラムが正しく動くための大前提です。
 
しかし、ここで一つ疑問が生まれます。
関数はどうやって、「自分の変数はどこか」「どこまでが自分の領域か」を判断しているのでしょうか。
その答えが、今回のテーマであるスタックフレームとRBP(ベースポインタ)です。
 
この2つは、GDBでスタックを読む、バッファオーバーフローを理解する、ROPを組み立てるといったすべての基礎になります。
[fuki-r]スタックの中にも“区切り”があるってこと?[/fuki-r][fuki-l]ええ。その区切りを理解するのが、今日のゴールよ[/fuki-l]
 ※イメージです。

スタックフレームとは何か

スタックフレームの正体

 スタックフレームとは、
ある関数が実行されている間だけ使う、スタック上の専用領域」です。
関数が呼ばれるたびに、
“引数"、"ローカル変数"、"保存されたレジスタ"、"リターンアドレス"といった情報が、ひとまとめで確保されます。
この「ひとまとめ」が、スタックフレームです。
 

なぜフレームが必要なのか

 もしスタックフレームが存在しなければ、「複数の関数が、同じスタックを無秩序に使う」ことになり、どの変数がどの関数のものなのか分からなくなります。
つまり、スタックフレームは関数ごとの作業スペースを明確に分けるための仕組みなのです。
[fuki-r]関数ごとに“自分の机”がある感じですね[/fuki-r][fuki-l]その通り。机があるから混乱しないのよ[/fuki-l]
 
 

スタックフレームの内部構造 

スタックフレームの中身

 一般的な関数のスタックフレームは、次のような要素で構成されます。
 
・上位アドレス側
   ・引数
・中央
   ・リターンアドレス
   ・保存されたRBP
・下位アドレス側
   ・ローカル変数
 
スタックは「上から下へ伸びる」ため、ローカル変数はアドレスが小さい方向に配置されるのが特徴です。
 

フレームは関数ごとに積み重なる 

関数Aが関数Bを呼び出すと、
1.Aのスタックフレームが存在
2.その上にBのスタックフレームが積まれる
3.Bが終わると、Bのフレームだけが破棄される
 
この積み重なりが、
コールスタックと呼ばれる状態です。
[fuki-r]箱の上に、さらに箱が積まれていく感じですね[/fuki-r][fuki-l]ええ。だから“スタック(積み重ねる)”と呼ばれるの[/fuki-l]
 
 

RBPとは何か 

RBPの役割

 RBP(Base Pointer)は、「今の関数のスタックフレームの基準点」を指すレジスタです。
RBPがあることで、プログラムは、ローカル変数の位置や、保存された情報の位置を、安定して参照できます。
 

なぜRSPだけでは足りないのか 

スタックポインタであるRSPは、
・push
・pop
 関数呼び出し
などで、常に値が変化します。
 
もしRSPだけを頼りにすると、ローカル変数の位置が毎回ずれてしまいます。
そこで、
・RSPは「今のスタック先頭」
・RBPは「この関数の基準点」
という役割分担が生まれました。
[fuki-r]RBPは“原点”みたいなもの?[/fuki-r][fuki-l]ええ。そこから距離で位置を測るの[/fuki-l]
 
 

関数プロローグとRBPの設定 

関数が始まるときに起きていること

 多くの関数は、冒頭で次のような処理を行います。
1.以前のRBPをスタックに保存
2.RBP ← 現在のRSP
3.ローカル変数分の領域を確保
これを 関数プロローグ と呼びます。
 

これが意味すること

 この処理により、「この関数のRBP」が固定され、ローカル変数は、"RBP – オフセット"という形で参照されます。
GDBで"info frame"や、"x/gx $rbp"を確認すると、この構造が見えてきます。
[fuki-r]最初に“基準点”を決めてから作業するんですね[/fuki-r][fuki-l]だから後で迷子にならないのよ[/fuki-l]
 
 

エクスプロイト開発との関係

 なぜ攻撃者はRBPを理解するのか

 バッファオーバーフローでは、"ローカル変数"、"保存されたRBP"、"リターンアドレス"が、同じスタックフレーム内に並んで存在します。
つまり、「どこまで書き込むと何が上書きされるのか」を理解するには、スタックフレーム構造の理解が不可欠です。
 

RBPを知ると見える世界

 RBPを理解すると、
・オフセット計算
・ペイロード長の決定
・スタックレイアウトの把握
が、論理的に説明できるようになります。
 
これは「勘」ではなく、構造に基づくエクスプロイトへの第一歩です。
[fuki-r]攻撃も、仕組みを知るところからなんですね[/fuki-r][fuki-l]ええ。理解が深いほど、再現性が生まれるの[/fuki-l]
 
 

ハッカーくろちゃんが学ぶ、スタックフレームとRBPのまとめ

 スタックフレームは、関数ごとの専用作業領域。RBPは、そのフレームの基準点。
RBPがあるから、ローカル変数を安定して参照できます。
エクスプロイト開発では、フレーム構造の理解が成功率を左右する
 
スタックフレームとRBPを理解したあなたは、「スタックを眺める側」から「スタックを読み解く側」へ進みました。