【注意】このサイトに記載されていることを他人に試すことは「不正アクセス禁止法」に該当する場合があります。詳しくはこちらから

保存されたRBPが壊れると何が起きるか(episode-09)

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

― スタック崩壊の「本当の始まり」を理解する ―
 
「リターンアドレスの前に壊れるもの」
前回(episode-08)では、バッファオーバーフローが「メモリ境界をチェックしない設計」から生まれ、スタック上では下から順に壊れていく、という流れを説明しました。
 
ローカル変数
保存されたRBP
リターンアドレス
 
多くの解説では、「リターンアドレスが書き換わると制御が奪われる」という結論だけが強調されがちです。
 
しかし、その直前に必ず通過する重要な地点があります。
それが、保存されたRBP(Saved RBP)です。
 
ここでは、
・保存されたRBPとは何か
・それが壊れると何が起きるのか
・なぜ攻撃者にとって重要なのか
を、GDBで観察できる現象として整理していきます。
※イメージです。

 保存されたRBPとは何か(再確認)

 関数が呼び出されると、スタックフレームが作られます。
x86-64 の一般的な関数プロローグは次の形です。
(アセンブラ言語)
push rbp
mov rbp, rsp
sub rsp, 0x??
 
ここで行われていることは、
1.呼び出し元のRBPをスタックに保存。
2.RBPを「この関数の基準点」に設定。
3.ローカル変数用の領域を確保。
 
つまり、保存されたRBP = 呼び出し元のスタックフレームへ戻るための道標です。

くろちゃん

RBPって、ただのレジスタじゃないの?


白猫先生

今どこにいるか”と“どこに戻るか”を同時に覚えている、と考えるといいわ

  

RBPが壊れると、何が起きるのか

 関数の終了時、CPUは次の処理を行います。
(アセンブラ言語)
leave   ; mov rsp, rbp → pop rbp
ret
 
この "leave" 命令は、
1.RSPをRBPの位置に戻す。
2.保存されていたRBPを取り出す。
という2段階の動きをします。
 
ここで重要なのは、保存されたRBPが壊れていると、RSPの復元先そのものが狂うという点です。
 
 初心者がよく疑問に思うのが、
「RBPが壊れたら、すぐ落ちるのでは?」という点です。
 
しかし実際には、
・プログラムがしばらく動き続ける。
・その後、意味不明な場所でクラッシュする。
という挙動を示すことがよくあります。
 
理由は単純です。
・RBPは、次のスタックフレームの基準。
・すぐには使われない場合がある。
・しかし「次の関数」、「次の leave」で致命傷になる。
 
つまり、保存されたRBPの破壊は遅延して効いてくるタイプの破壊なのです。

くろちゃん

壊れたのに、すぐ倒れないのが逆に怖いね…


白猫先生

ええ。“気づかない崩壊”こそが一番危険なの

  

スタックフレーム全体がずれる現象

 保存されたRBPが壊れると、次に起きるのは、
・ローカル変数の参照が狂う。
・本来のスタック境界を無視してアクセスする。
・`leave` 後の `ret` が想定外の位置を読む。
 
結果として、
・Segmentation Fault。
・不正なアドレス参照。
・予測不能な挙動。
が発生します。
 
これは、スタック構造そのものが壊れた状態です。
 
 

攻撃者視点 なぜRBPは重要なのか

 攻撃者は、いきなりリターンアドレスを書き換えません。
理由は、
・ASLR でアドレスが読めない。
・直接書くと即クラッシュする。
 
そこで注目されるのが保存されたRBPです。
・RBPを書き換える。
・次の "leave"で RSPを誘導。
・その先で、制御可能な位置にretを導く。
 
これが、Stack Pivot(スタックピボット)と呼ばれる技術の入口です。

くろちゃん

RBPって、“踏み台”にされるんだ…ん


白猫先生

そう。直接飛ばず、まず足場を動かすの

  

GDBで見ると、すべては「納得できる」

 GDBで観察すると、
・"info registers rbp rsp"
・"x/xx $rbp"
・"bt"
などを通して、RBPがどこを指しているか、どの時点で壊れたか、なぜ次の関数で落ちたかが、すべて論理的に説明できるようになります。
 
ここで重要なのは、バグは「魔法」ではなく構造が壊れた結果として起きているという事実です。
 
 

保存されたRBPが壊れると何が起きるかのまとめ

 保存されたRBPは、単なる一時データではなく、スタックフレーム同士を正しくつなぐための重要な基準点です。
バッファオーバーフローによってこのRBPが破壊されると、関数終了時に行われるスタック復元処理そのものが狂い、RSPやリターンアドレスの読み取り位置がずれてしまいます。その結果、プログラムはすぐにクラッシュしない場合でも、後続の処理で予測不能な挙動や異常終了を引き起こします。
攻撃者はこの性質を利用し、保存されたRBPを書き換えることでスタックの基準を移動させ、制御フローを段階的に誘導します。
GDBで観察すれば、これらの現象はすべて論理的に理解でき、スタック破壊は再現可能な構造的問題であることが見えてきます。