第五話:スタックとLIFO、だから何?(怒)

第四話:CALL命令で起こること」で端折ったスタックの話です。

「スタック」という単語を(例えば情報処理技術者試験などの勉強をして)知っている人の知識としては、

1. 一時的に値を退避しておく場所である
2. Last In Fast Out(LIFO:最後に入れたものを最初に取り出す)である

この2つが挙げられると思います。

1.は読んで字のごとくだからいいとして、2.については実際に見てみた方が早いでしょう。

ここで、新しい実行ファイルの登場です。ここからダウンロードしてください。ソースは以下の通り。

 #include  <stdio.h>
int main() {
__asm mov ecx, 0x0A
__asm push 0x10
__asm push 0x20
__asm push ecx
__asm pop eax
__asm pop ebx
__asm push 0x30
__asm pop ecx
__asm pop edx
return 0;
}

これはCのソースですが、「__asm」というのを付けることでインラインでアセンブラを書くことができます。特定の命令について知りたい時はこういうのを書くといいかもしれません。コンパイル方法は「第零話:まずは動かしてみる 〜ブレイクポイントとステップ実行〜」を参照のこと。

ではこの実行ファイルをOllyDbgで開いて、最初の「mov ecx, 0x0A」という命令のところにブレイクポイントを設置して実行してみます。

[F7]を一度押してステップ実行します。ECXに0x0Aが入ります。

レジスタ「ESP」とPUSH/POP命令

さて、ここからいよいよPUSH/POPでスタックをいじります。

スタック領域に関係する重要なレジスタとして、「ESP」と「EBP」があります。EBPは次回に持ち越すとして、今回はESPに注目しましょう。

ESPはExtended Stack Pointerの略で、今どこのスタック領域を使っているかを指し示しています。

この場合、ESPは「0012FF3C」を指していますね。右下のスタック領域のアドレスを見てみると、(自分で動かしていなければ)「0012FF3C」が一番上にきていると思います。

では[F7]押下で「PUSH 10」を実行してみましょう。

スタック領域が下がって「10」が挿入されましたね。ESPは0012FF3Cの4バイト上の0012FF38を指しています。

もう一度[F7]押下でスタック領域がまた下がって「20」が挿入され、ESPの値も変わります。

次は「PUSH ECX」で、値でなくレジスタをPUSHしてみます。ECXには「0A」という値が入っています。[F7]押下でステップ実行すると・・。

はい、ECXの値0x0Aがスタック領域に入りましたね。こんな感じで、PUSHするとスタック領域に値が積まれていきます。

次はPOP命令でスタック領域から値を取り出してみましょう。ESPが指しているスタック領域の値とレジスタ「EAX」に気をつけながら[F7]押下。

ESPが指していた場所の値0x0AがEAXに入りましたね。そしてスタック領域が上がってESPの値も+4されています。

もう一度[F7]押下。次は「POP EBX」なのでEBXにスタック領域の値が入ります。

スタック領域が上がってESP=ESP+4になると。

ここで「PUSH 30」。

スタック領域が下がって0x30が入りました。ESP=ESP-4。

次は「POP ECX」。上図のESPが指している0012FF34の値0x30がレジスタECXに入ります。そしてESP=ESP+4

さらに「POP EDX」。上図のESPが指している0012FF38の値0x10がレジスタEDXに入ります。そしてESP=ESP+4

LIFO

やってみるとよく分かると思いますが、PUSH/POPによるスタック領域の値の挿入・取り出しはLIFOになっています。今回のサンプルだと、以下のように最初に3回連続でPUSHしていますよね。

PUSH 10
PUSH 20
PUSH ECX(値は0A)

で、上記3つの命令を実行後のスタックの状態はこう。

で、この直後の「POP EAX」では一番上にある0Aを取り出しています。
ESPの値は、PUSHで-4、POPで+4されます。

このLIFOという仕組みは、積み上げたお皿に例えられることが多いです。ビュッフェとかにあるような、一番下にバネ付きの板があって、皿を取り出すとちょっと上がり、皿を戻すとちょっと下がるというあれです。

あれだと、最初に積んだものは下に、最後に積んだものが上になるわけですが、取り出す時は上からです。だからLast In Fast Out。どうよ!分かりやすい例えでしょ?

と、初めてこの説明を聞いたときにnekotricolorは思ったわけです。

でっていう(怒

なぜこんなしち面倒なことをしなければならないのか。いったいこうすることにどんなメリットがあるのか。

スタック領域の真骨頂は、サブルーチンの呼び出しとそこからの戻り方にあります(と、私は思っています)。「第四話:CALL命令で起こること」でも説明した、CALLとRETですね。この辺を掘り下げると、スタックという仕組みの良さがわかります。同時に、CALLとRETが古き良きバッファオーバーフローを利用した攻撃の重要なポイントになる理由も。

次回はその辺の話を、今回説明しなかったレジスタ「EBP」の話に絡めてお送りします。

(本稿はここで終わりです。ためになったという方、他のも読みたい!という方、投げ銭していただけると嬉しいです。)

ここから先は

0字

¥ 100

この記事が気に入ったらサポートをしてみませんか?