保護機構をやさしく理解するシリーズ。なぜ今のバイナリは簡単に壊せないのか?保護機構の全体像(第1回/全10回)
今回は、保護機構シリーズの入口として、「なぜ今のバイナリは簡単に壊せないのか? 保護機構の全体像」をテーマにシリーズ化します。
Buffer Overflow の基礎を学ぶと、次のような疑問が出てきます。
・バッファがあふれたなら、すぐに好きなように動かせるの?
・return address まで届いたら、すぐ成功なの?
・昔の解説ではうまくいきそうなのに、今の環境ではなぜ難しいの?
この疑問に答えるために必要なのが「保護機構」の理解です。
昔の入門記事や簡単な学習用コードでは、バッファオーバーフローが起きると、そのまま大きな影響が出る例を見かけることがあります。
ですが、現代のLinuxや、一般的な実行環境では、プログラムは何重もの守りで保護されていることが多く、そう簡単には思い通りに動きません。
つまり、今のバイナリは「壊れない」わけではないけれど、「壊れても簡単には悪い方向へ進まないように工夫されている」のです。
このシリーズでは、そうした守りの仕組みをやさしく学んでいきます。
第1回の今回は、細かい仕組みに入る前に、まず
・保護機構とは何か。
・なぜ必要なのか。
・どんな種類があるのか。
・どういう発想で見ればよいのか。
を、全体図としてつかむことを目標にします。
※イメージです。
保護機構とは何か
保護機構とは、簡単に言えば「プログラムが壊れたり、悪用されたりしにくくするための守りの仕組み」です。
たとえば、家を守ることを考えてみてください。
家には、玄関の鍵だけでなく、窓の鍵、防犯カメラ、オートロック、警報装置など、いくつもの守りがあります。
もし1つの守りが突破されても、次の守りが被害を小さくしてくれるかもしれません。
コンピュータのプログラムでも、これと似た考え方が使われています。
たとえば、バッファオーバーフローが起きたとしても、
・スタック上のコードを実行しにくくする。
・アドレスを毎回変える。
・大事な場所が壊れたら止まる。
・書き換えられやすい表を守る。
といった工夫によって、被害を抑えたり、悪用を難しくしたりします。
ここで大切なのは、保護機構は「バグをゼロにする魔法」ではないということです。
保護機構は、バグそのものをなくすものではなく、バグがあっても被害を広げにくくするための壁です。
この考え方を持つだけでも、今後の理解がかなり楽になります。
なぜ現代のバイナリには保護機構が必要なのか
では、なぜここまで守りが必要なのでしょうか。
理由はとてもシンプルです。
プログラムには、人間が作る以上、どうしてもミスや想定外の動きが入りこむ可能性があるからです。
たとえば、
・長さ確認が不足していた。
・ポインタの扱いを間違えた。
・メモリ解放後の使い方に問題があった。
・入力チェックが足りなかった
といったことが起こると、脆弱性につながることがあります。
もし守りが何もなければ、1つのミスがそのまま大きな問題になるかもしれません。
しかし、保護機構があれば、
・すぐクラッシュして止まる。
・予測しにくくなる。
・その場で実行できなくなる。
・次の段階へ進みにくくなる。
といった形で、被害を小さくできる可能性があります。 [fuki-l]簡単にたとえるなら、保護機構は「転んでも大けがしにくくするためのヘルメットやひざ当て」のようなものです。転ばないことが一番ですが、現実には転ぶこともあります。だからこそ、転んだときに大事故にならないよう備えておくのです。[/fuki-l]プログラムの世界でも、「ミスは起きうる。だから守りも必要」という考え方がとても大切です。
よく出てくる保護機構の全体像
ここで、保護機構の名前をざっくり整理しておきましょう。
この回では、まだ細かい暗記は不要です。
まずは「こんな守りがあるんだな」と全体像をつかめれば十分です。
1.NX
NXは、ざっくり言うと「この場所は実行用ではありません」とする守りです。
たとえばスタックにデータを書けても、その場所をそのまま命令として実行しにくくします。
2.Stack Canary
Canaryは、スタック上の大事な戻り情報の前に置かれる「見張り役の値」です。
バッファのあふれがその付近まで届いたとき、見張りが壊れていたら異常として止めます。
3.ASLR
ASLR は、メモリ上の配置を毎回変える仕組みです。
つまり、「前回と同じ住所にあるとは限らない」ようにして、予測を難しくします。
4.PIE
PIEは、プログラム本体の位置も動かしやすくする仕組みです。
ASLRと関係が深く、「本体アドレスの固定化を避ける」イメージで考えるとよいです。
5.RELRO
RELROは、書き換えられると困る表の一部を守る仕組みです。
細かいところは後で学べば大丈夫ですが、「動的リンクまわりの安全性を高める守り」として出てきます。
この5つは、現代のバイナリを学ぶときによく見る基本メンバーです。
サンプルコードと「守りがない世界」を想像してみる
ここで、学習用にとても単純なコードを見てみます。
—–
#include <stdio.h>
#include <string.h>
void vuln(char *input) {
char buf[16];
strcpy(buf, input);
printf(“buf: %s\n", buf);
}
int main(int argc, char *argv[]) {
if (argc > 1) {
vuln(argv[1]);
}
return 0;
}
—–
このコードでは、"vuln()"の中に"char buf[16];"という小さなバッファがあります。
そして、"strcpy(buf, input);"によって、長さ確認なしで入力がコピーされます。
Buffer Overflowの基礎だけを見ると、「長い入力なら buf の外へあふれるかもしれない」と考えられます。
ここで想像してほしいのは、「守りがまったくない世界」です。
もし守りがなければ、
・スタック上の重要な情報に届きやすい。
・書いた場所をそのまま実行しやすい。
・アドレスが予測しやすい。
・書き換えたい場所が守られていない。
といった条件が重なって、問題がそのまま広がる可能性があります。
しかし現代では、多くの場合ここに保護機構が入ります。
その結果、たとえバグがあっても、
・途中で止まる。
・実行できない。
・場所が毎回変わる。
・狙った書き換えが難しい。
といった形で、簡単には進まなくなります。
つまり、このコードから本当に学ぶべきことは、「脆弱性があるかもしれない」ことと、「それを簡単に悪用できる」ことは別問題だという点です。
ここが、保護機構シリーズの入口としてとても重要です。
保護機構を見るときの考え方
初心者のうちは、NX、Canary、ASLR、PIE、RELROという名前がたくさん出てくると、全部をばらばらに覚えようとしてしまいがちです。
ですが、最初はそうしなくて大丈夫です。
大切なのは、「この守りは、何を難しくしているのか?」という視点です。
たとえば、
・NXは「その場で実行すること」を難しくする。
・Canaryは「戻り情報まで届いたこと」を見つけやすくする。
・ASLR/PIEは「場所を当てること」を難しくする。
・RELROは「特定の表の書き換え」を難しくする。
というふうに考えると、少し整理しやすくなります。 [fuki-l]簡単にたとえるなら、保護機構はそれぞれ、鍵、見張り、地図を毎回変える仕組み、触れないようにする保護カバーのような役割を持っています。[/fuki-l]つまり、名前の暗記より先に、「何を守っていて、何を難しくしているか」をつかむことが大切なのです。
この見方ができるようになると、次回からの個別テーマもかなり理解しやすくなります。
なぜ今のバイナリは簡単に壊せないのか?保護機構の全体像のまとめ
今回学んだ大事なことは、現代のバイナリが簡単に壊せないのは、バグがまったく存在しないからではなく、NX、Stack Canary、ASLR、PIE、RELRO などの保護機構によって、被害の拡大や悪用が難しくされているからだという点です。
保護機構は、プログラムのミスそのものを消す魔法ではなく、ミスがあっても大事故になりにくくするための壁のようなものです。たとえば、実行しにくくする、場所を予測しにくくする、重要な情報の破壊を見つける、書き換えにくくする、といった形で働きます。大切なのは、名前を丸暗記することではなく、「この守りは何を難しくしているのか」という視点で見ることです。
この全体像をつかむことで、次回以降のNXや、Canaryなどの個別テーマが、ばらばらの知識ではなく、つながった守りとして理解しやすくなります。