第1章 総論
バッファオーバーフローを理解するための基礎知識

スタック

スタックは、それまでの作業を「棚上げ」し、のちにそれを再開できるようにする仕組みである。スタックはメモリの中の特定の領域を占め、「スタックポインタ」というレジスタを通じてアクセスされる。スタックに保存されるデータは、領域の中の大きなアドレスから小さなアドレスへ向かって順に置かれてゆく。スタックポインタは、スタックに置かれたデータの先頭のアドレスを保持している。

PUSHとPOP

スタックを操作する機械命令に、PUSHとPOPがある。

PUSH命令は、スタックに値を積む命令である。PUSH命令が実行されると、保存される値の大きさのぶんだけスタックポインタが減らされたのち、スタックポインタが指す箇所のメモリに値が保存される。

図1-11: スタックへ積む

POP命令は、スタックから値を回収する命令である。POP命令が実行されると、スタック内容先頭の値がどれかのレジスタへ転送されるとともに、その値の大きさのぶんだけスタックポインタが増やされる。

図1-12: スタックから回収

関数呼び出しとスタック

関数呼び出しも「ジャンプ」の一種であるが、単純なジャンプとは異なるところがある。すなわち、呼び出された関数における処理が済んだら、呼び出しの直前に実行していた命令の流れの続きに戻る必要がある。プロセッサには関数呼び出しのための命令と復帰のための命令が用意されているが、これらは大抵「スタック」という仕組みと共に動作するよう作られている。

(注: コンパイラによっては、スタックを用いずに関数呼び出しを実現しているものもある。)

関数呼び出しの命令(CALL命令)は、それまでの命令カウンタの値をスタックに積んでから(PUSHと同様の動作)、目的の関数の先頭を指すよう命令カウンタを書き換える。

図1-13: CALL動作

関数から呼び出し元に復帰する命令(RET命令)は、スタックからアドレス値を回収し(POPと同様の動作)、そのアドレス値を命令カウンタへ転送する。

図1-14: RET動作

ローカル変数とスタック

スタックは、関数の呼び出しと復帰の際の命令実行順序をコントロールするのみならず、関数への引数受け渡しや関数内のローカル変数のための領域としても用いられる。関数呼び出しの際の引数受け渡しは、呼び出し側で値(単数もしくは複数)をスタックへ積む(PUSH)ことで実現される。呼び出された関数のローカル変数領域もスタックの上に確保される。

図1-15: ローカル変数はスタックに置かれる