アセンブリでの関数呼び出しの備忘録
CTFのPwnについて勉強している時に、関数呼び出時の処理をよく忘れてしまうので一度まとめてみます。
関数呼び出しの手順
関数を呼び出すときは以下の処理が行われます
- 呼び出し元のアドレスを保存し、関数のアドレスへ移動(call)
- 関数の処理
- スタック上のアドレスの開放(leave)
- 呼び出し元へ帰る(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はスタックの開放を行う命令で、次の処理が行われます。
leaveは次のようにあらわすこともできます
mov rsp, rbp pop rbp
ret
ret命令はスタックの呼び出し元アドレスをpopし、そのアドレスへ戻ります。
retはアセンブリで次のようにあらわすことができます
pop rip