アセンブリでの関数呼び出しの備忘録

CTFのPwnについて勉強している時に、関数呼び出時の処理をよく忘れてしまうので一度まとめてみます。

関数呼び出しの手順

関数を呼び出すときは以下の処理が行われます

  1. 呼び出し元のアドレスを保存し、関数のアドレスへ移動(call)
  2. 関数の処理
  3. スタック上のアドレスの開放(leave)
  4. 呼び出し元へ帰る(ret)

call

call命令は関数を呼び出すための命令で、次の処理が行われます。

  • call命令の次の命令のアドレスをスタックにpushする
  • 関数のアドレスへジャンプする

call命令を実行するとスタックは次のようになります。

low     +--------------------+
address |                    |
        +--------------------+
        |                    |
        +--------------------+
high    | callの次のアドレス   |
address +--------------------+

関数の最初の処理

呼び出された関数では最初に次の処理が行われます。

  • rbpをスタックにpush
  • rbpにrspをコピー

これは、関数の処理が全て終わったときにスタックの状態を復元できるようにするために行われます。

この時点でのスタックの様子は次のようになります。

low     +--------------------+
address |                    |
        +--------------------+
        |        rbp         |
        +--------------------+
high    |  callの次のアドレス  |
address +--------------------+

この処理は次のように表されます。

push rbp
mov rbp, rsp

関数の処理

rspとrbpの退避が終わってから関数の処理が始まります。

関数のローカル変数はrbpを基準として、rbpより低いアドレスが使われます。

この時点でのスタックの様子は次のようになります。

low     +--------------------+
address |     ローカル変数     |
        +--------------------+
        |        rbp         |
        +--------------------+
high    |  callの次のアドレス  |
address +--------------------+

leave

leaveはスタックの開放を行う命令で、次の処理が行われます。

  • rspにrbpの値をコピーしrspを復元
  • スタックに保存したrbpのアドレスからrbpを復元

leaveは次のようにあらわすこともできます

mov rsp, rbp
pop rbp

ret

ret命令はスタックの呼び出し元アドレスをpopし、そのアドレスへ戻ります。

retはアセンブリで次のようにあらわすことができます

pop rip

参考リンク