保護機構をやさしく理解するシリーズ。ASLRとは何か? アドレスが毎回変わると何が起きるのか(第4回/全10回)

黒ネコと学ぶ、保護機構をやさしく理解する

前回は、Stack Canaryがreturn addressの前にいる見張り役として働き、バッファオーバーフローが大事な戻り情報に近づいたことを見つけやすくする仕組みであることを学びました。
 
すると次に、こんな疑問が出てきます。
・見張りをすり抜けたら、次はどうなるの?
・メモリの中の「場所」は毎回同じなの?
・もし大事なアドレスがいつも同じなら、予測されやすいのでは?
 
この疑問に深く関わるのが、今回の「ASLR」です。[fuki-r]ASLR をとてもやさしく言うと、「メモリ上の場所を毎回少しずつ変えて、予測しにくくする仕組み」です。[/fuki-r]これまでの学習では、スタックやreturn address、関数の位置などを「場所」として見てきました。
その「場所」が毎回同じだと、もし誰かがその位置を知っていた場合、そこを狙いやすくなります。
そこで ASLR は、その配置を固定しないことで、予測を難しくします。
 
今回は、
・ASLRとは何か。
・なぜ必要なのか。
・何が毎回変わるのか。
・予測が難しくなると何が起きるのか。
を、やさしく順番に整理していきます。
 ※イメージです。

ASLRとは何か

 ASLR とは、簡単に言えば「メモリ上のアドレス配置を毎回ランダムに近い形で変える仕組み」です。ASLR はよく、Address Space Layout Randomizationの略として説明されます。
英語は長いですが、意味をやさしく言い換えると、「アドレスの並び方を毎回ずらすこと」です。
 
ここでいうアドレスとは、メモリ上の住所のようなものです。
プログラムの中では、
・スタック
・ヒープ
・ライブラリ
・プログラム本体
などが、メモリ上のどこかに置かれます。
 
もしそれらの位置が毎回まったく同じなら、「この関数はいつもここ」、「このスタック領域はいつもこの辺」と予測しやすくなります。
 
しかし、ASLRがあると、プログラムを実行するたびに、その位置が少しずつ変わります。
その結果、前回の実行で見えた住所が、次回もそのまま使えるとは限らなくなります。 [fuki-l]簡単にたとえるなら、ASLRは「毎回ちがう席順にする仕組み」のようなものです。昨日は前から2番目にいた人が、今日は後ろのほうにいるかもしれない。そうすると、場所を覚えて狙うのが難しくなります。
[/fuki-l]
 

なぜASLRが必要なのか

 では、なぜそんな仕組みが必要なのでしょうか。
理由はとてもシンプルで、場所が固定されていると、狙われやすいからです。
 
これまでのBuffer Overflowシリーズでは、return addressや、関数の位置、スタック上のデータなどを「どこにあるか」という視点で考えてきました。もしそれらが毎回同じ住所にあるなら、場所を知っている人にとってはとても都合がよくなります。
 
たとえば、学校で毎日まったく同じ席順なら、「この人はいつもここにいる」と簡単にわかります。
でも毎日席替えがあると、昨日の情報がそのまま使えなくなります。
コンピュータでも同じで、メモリ上の配置が毎回変われば、前に見たアドレスをそのまま当てにしにくくなるのです。
ここで大切なのは、ASLRは「バグそのものをなくしているわけではない」ということです。
バグがある可能性は残るかもしれません。
しかし、そのバグを利用して次の段階へ進もうとするとき、「どこに何があるか」がわかりにくいことが大きな壁になります。
 
つまり、ASLRは「場所の予測を難しくすることで、問題がそのまま広がりにくくする守り」なのです。
 
 

何の場所が毎回変わるのか

 ここで、何のアドレスが変わるのかをざっくり整理しましょう。
ASLRの影響を考えるとき、初心者向けには次のようなものを意識するとわかりやすいです。
1.スタック
関数のローカル変数や一時的な情報が置かれる場所です。
これまで学んできた"buf"や、return addressに関係が深い領域です。
 
2.ヒープ
動的に確保されるメモリの領域です。
“malloc"などで使われる場所です。
 
3.共有ライブラリ
たとえば、libcのようなライブラリも、メモリ上のどこかに読み込まれます。
この位置も固定ではなくなることがあります。
 
4.プログラム本体
環境や設定によっては、プログラム本体の位置も変わりやすくなります。
これは後で学ぶ「PIE」と深く関わります。
 
初心者のうちは、「スタックやライブラリの場所が毎回同じとは限らない」とまず押さえれば十分です。 [fuki-l]簡単にたとえるなら、教室だけでなく、
・先生の机
・ロッカー
・本棚
・黒板の位置
まで少しずつ毎回変わるようなものです。そうなると、昨日の教室の地図をそのまま使うのは難しくなります。
[/fuki-l] 
 

サンプルコードでやさしく考える

 ここで、学習用のシンプルなコードを見てみます。
—–
#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;
}
—–
このコードには"char buf[16];"というローカルバッファがあります。
“buf"はスタック上に置かれることが多いです。
ここで考えたいのは、「その"buf"が毎回まったく同じ住所にあるとは限らない」ということです。
 
同じプログラムを実行しても、ASLRが有効なら、スタックの始まりや周辺の位置関係が少し変わることがあります。
すると、
・前回見たアドレス。
・以前の実行で確認した場所。
・さっきの実行で出てきた配置。
が、そのまま次回も通用するとは限りません。
 
このコード自体は単純ですが、ここから学ぶべきことはとても大切です。
それは、「メモリの地図は毎回固定とは限らない」ということです。
 
Buffer Overflowの基礎だけ見ていると、つい「どこに何があるかはいつも同じ」と考えたくなります。
しかし、ASLRがあると、その前提が崩れます。
 
つまり ASLR は、「場所を手がかりに進もうとする考え方を、そのままでは使いにくくする守り」なのです。
 
 

ASLRがあると何が変わるのか

 初心者の段階では、ASLRの効果を「場所を覚えても、次に同じとは限らない」と理解すれば十分です。
これによって何が変わるのでしょうか。
ASLR がない世界では、
・スタックはいつも同じあたり。
・ライブラリはいつも同じあたり。
・大事な関数も毎回同じ場所。
というふうに、地図が固定されやすくなります。
しかし ASLR があると、「今回はここにある。」「でも次回は少し違う」「だから前の実行結果をそのまま使いにくい」という状態になります。 [fuki-l]簡単にたとえるなら、これは「宝探しの地図が毎回少しずつ書き換わる」ようなものです。
昨日「木の右に3歩」の場所に宝があったとしても、今日は木の場所そのものが変わっているかもしれません。
[/fuki-l]そうなると、前の地図はそのまま使えません。
ここで大切なのは、ASLR は「完全に何もわからなくする魔法ではない」ということではなく、「固定アドレスを当てにしにくくする仕組み」だという点です。
 
つまり ASLR は、「予測」を難しくする守りとして理解するとよいです。
 
 

保護機構をやさしく理解するシリーズ。ASLRとは何か? アドレスが毎回変わると何が起きるのかのまとめ

 今回学んだ大事なことは、ASLR がメモリ上のアドレス配置を毎回変えることで、場所の予測を難しくする保護機構だという点です。スタック、ヒープ、共有ライブラリ、場合によってはプログラム本体などの位置が毎回少しずつ変わるため、前回の実行で見えた住所をそのまま次回も使えるとは限りません。
つまり ASLR は、バグそのものをなくすのではなく、「どこに何があるか」を固定の前提で考えにくくすることで、問題がそのまま広がるのを防ぎやすくしています。大切なのは、ASLR を「毎回ちがう席順にする仕組み」や「地図を少しずつ変える仕組み」としてイメージすることです。
 
これによって、固定された住所を当てにする考え方が通用しにくくなる、という感覚を持てるようになります。