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

Buffer Overflowとは何か?64bit環境でBuffer Overflowを見るときの注意点(第8回/全10回)

黒ネコと学ぶ、Buffer Overflow 超基礎シリーズ

ここまでのシリーズでは、Buffer Overflow の基本から始まり、
・バッファとは何か
・配列とメモリの関係
・境界チェック不足の危険性
・スタックとローカル変数
・saved RBP と return address
・クラッシュの見方
・オフセットの考え方
までを学んできました。
 
ここで次に大切になるのが、32bitと64bitの違いです。
Buffer Overflow の解説記事や古い資料を読むと、"EIP"、"EBP"、"ESP"といった言葉が出てくることがあります。
一方で、今のLinux環境やCTFの学習では、"RIP"、"RBP"、"RSP"、といった言葉をよく見ます。
 
この違いに戸惑う方はとても多いです。
ですが、ここで大事なのは「全部を一気に暗記すること」ではありません。
まずは、
「今の学習環境では64bitがよく使われる」
「そのため、アドレスの大きさやレジスタ名の見方が少し変わる」
と理解することが大切です。
 
今回は、
・32bitと64bitは何が違うのか。
・Buffer Overflow の見え方はどう変わるのか。
・GDBで何に注意すればよいのか。
・なぜ古い解説と今の環境で差があるのか。
をやさしく整理していきます。
 ※イメージです。

32bitと64bitは何が違うのか

 まず、32bitと64bitの違いをとても簡単に言うと、コンピュータが一度に扱うデータの大きさや、アドレスの広さの考え方が違うということです。
 
ここで特に大事なのは、アドレスの大きさです。
アドレスとは、メモリ上の場所を示す番号のようなものです。
これまでのシリーズで言えば、ポインタが持つ「住所」に近いものです。
 
32bit環境では、このアドレスを表す大きさが基本的に4バイトです。
一方、64bit環境では、8バイトになります。

白猫先生

簡単ににたとえるなら、32bitは「短めの住所」で、64bitは「もっと長く細かい住所」というイメージです。

この違いは、Buffer Overflow でもとても大事です。
なぜなら、関数が戻るための"return address"も、環境によって大きさの考え方が変わるからです。
 
つまり64bit環境では、アドレスを8バイトで考える感覚が必要になります。
 
 

レジスタ名はどう変わるのか

 次に、GDBでよく見るレジスタ名の違いを整理しましょう。
古い32bitの解説では、よく次のような名前が出てきます。
・EIP
・EBP
・ESP
 
一方、64bitでは次のようになります。
・RIP
・RBP
・RSP
 
上記の意味をざっくり対応させると、
・EIP → RIP:今実行しようとしている場所。
・EBP → RBP:スタックフレームの基準。
・ESP → RSP:スタックの先頭あたりを示す情報。
と考えるとわかりやすいです。
 
初心者のうちは、この名前の違いだけで混乱しやすいです。
ですが、大切なのは「役割」が大きく変わるわけではない、という点です。
 
つまり、
・32bitでは EIP / EBP / ESP
・64bitでは RIP / RBP / RSP
というように、
"今の場所”、"基準”、"スタック先頭”を見るための名前が少し変わると理解すれば十分です。

白猫先生

簡単に言うなら、クラス替えで名札の色が変わったようなものです。呼び方は変わっても、役割そのものは大きく変わりません。

 
 

64bitでは何に注意すればよいのか

 では、64bit環境で、Buffer Overflowを見るとき、何に注意すればよいのでしょうか。
 
特に大事なのは次の3つです。
1.return address を8バイトで考える。
64bit環境では、アドレスが8バイトです。
そのため、return address も 8バイト単位で意識する必要があります。
つまり、オフセットを考えるときも、「最後に届く大事な値は8バイト」という感覚が大切です。
 
2.GDBで見るレジスタ名が違う。
クラッシュしたとき、今どこを実行しようとしていたかを見るなら、64bitではRIPを見ます。
スタックフレームの基準ならRBP、スタック先頭の動きならRSPを意識します。
つまり、前に読んだ32bitの解説をそのまま使おうとすると、「EIPがない」、「ESPではなくRSP?」と混乱しやすいです。
 
3.古い解説は32bit前提のものが多い。
ネットや古い教材には、32bit前提のBuffer Overflow解説がとても多くあります。
そのため、そのまま今の環境に当てはめると、レジスタ名や、アドレス幅の違いでわかりにくくなることがあります。
ここで大切なのは、「昔の解説が間違っている」のではなく、「前提環境が違う」と理解することです。
 
 

サンプルコードでやさしく理解する

 ここで、これまでにも近い形で見てきたシンプルなコードを使って考えてみます。
-----
#include <stdio.h>
#include <string.h>
 
void vuln(char *input) {
    char buf[16];
    strcpy(buf, input);
    printf("%s\n", buf);
}
 
int main(int argc, char *argv[]) {
    if (argc > 1) {
        vuln(argv[1]);
    }
    return 0;
}
-----
このコード自体は、32bitでも64bitでも見た目はほとんど変わりません。
ですが、中で起きていることの見え方には違いがあります。
 
たとえば、
char buf[16];
は16バイトぶんのローカルバッファです。
ここに長すぎる入力を与えると、スタック上のその先へ書き込みが進む可能性があります。
 
32bitでも64bitでも、「バッファの外へはみ出すと危険」という本質は変わりません。
ですが、64bitではその先にあるreturn addressが8バイトであることや、GDBで見るときにRIP/RBP/RSPを使うことが違ってきます。
 
つまりこのコードから学ぶべきことは、「ソースコードが同じでも、実行環境が違うと観察するポイントの名前や大きさが変わる」ということです。

白猫先生

簡単にたとえるなら、同じゲームをしていても、遊んでいるハードが違うとボタン配置が少し違うようなものです。ゲームの目的は同じでも、操作の感覚には違いがあります。

 
 

なぜ古い解説と今の環境で差があるのか

 Buffer Overflow を学んでいると、「記事によって説明が少し違う」と感じることがあります。
その大きな理由のひとつが、32bit前提か64bit前提かの違いです。
 
昔は32bit環境がよく使われていたため、多くの入門記事や教材が32bitベースで書かれていました。
そのため、そこでは"EIP"、"EBP"、"ESP"は4バイトのアドレスを前提に話が進んでいます。
 
一方、今の環境では64bitが主流なので"RIP"、"RBP"、"RSP"は8バイトのアドレスを前提に見ることが多くなります。
 
ここで大事なのは、違いに戸惑ってもあわてないことです。
本質は大きく変わっていません。
 
本質はいつも同じです。
・バッファがある。
・長すぎる入力であふれる。
・スタック上の大事な情報に届くかもしれない。
・その結果、クラッシュや制御の変化が起きる。
この流れは32bitでも64bitでも共通です。
 
つまり、違うのは「見ている地図の縮尺や表記」であって、「起きている考え方そのもの」ではありません。
 
 

Buffer Overflowとは何か?64bit環境でBuffer Overflowを見るときの注意点のまとめ

 今回学んだ大事なことは、32bitと64bitでは特にアドレスの大きさとレジスタ名の見え方が異なり、Buffer Overflow を観察するときのポイントも少し変わるという点です。
32bitでは、EIP・EBP・ESPがよく使われますが、64bitでは、RIP・RBP・RSPを見ることが多くなります。また、64bitではアドレスやreturn addressを8バイトで考える必要があります。そのため、古い32bit前提の解説を今の環境で読むと、名前やサイズの違いで混乱しやすくなります。
ただし、本質は変わりません。バッファがあり、長すぎる入力であふれ、その結果としてスタック上の大事な情報に影響が及ぶかもしれない、という流れは同じです。つまり大切なのは、32bitと64bitの違いを丸暗記することではなく、今の環境では何をどの名前で見ればよいかを整理しながら、本質を見失わないことです。