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

保護機構をやさしく理解するシリーズ。PIEとは何か? プログラム本体の位置まで動くと何が変わるのか(第5回/全10回)

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

前回は、ASLRによってメモリ上の配置が毎回変わり、スタックやライブラリなどの場所を予測しにくくする仕組みを学びました。
 
すると、ここで次の疑問が出てきます。
・スタックやライブラリの場所が変わるのはわかった。
・では、プログラム本体の場所はどうなの?
・"main()"や"win()"のような関数の位置も毎回変わるの?
・もし本体まで動くなら、何が難しくなるの?
 
この疑問に深く関わるのが、今回の「PIE」です。

白猫先生

PIEをとてもやさしく言うと、「プログラム本体も、固定された場所ではなく、動かしやすくする仕組み」です。

前回の、ASLRと似ていますが、PIEは特に「プログラム本体そのもの」に注目するところが大切です。つまり、これまで「関数はいつもこの辺にあるのでは?」と考えていたものが、そのままでは通用しにくくなるのです。
 
今回は、
・PIEとは何か。
・なぜ必要なのか。
・ASLRとどう関係するのか。
・プログラム本体の位置が動くと何が変わるのか。
を、やさしく順番に整理していきます。
 ※イメージです。

PIEとは何か

 PIEとは、簡単に言えば「プログラム本体を、固定されたアドレスではなく、動かしやすい形で配置できるようにする仕組み」です。
 
PIE はよく「Position Independent Executable」の略として説明されます。
英語は長いですが、意味をやさしく言い換えると、
「どの位置にも置きやすい実行ファイル」です。
 
ここで思い出してほしいのは、前回のASLRです。ASLRは、スタックやライブラリなどの位置を毎回変えやすくする仕組みでした。
しかし、プログラム本体そのものが「最初からこの位置に置かれる前提」で作られていると、本体まではうまく動かしにくいことがあります。
 
そこで「PIE」を使うと、プログラム本体も「どこに置かれても動けるような形」になりやすくなります。

白猫先生

簡単にたとえるなら、PIEは「どの教室に移動しても授業できるセット」のようなものです。普通の授業セットが「この教室専用」だとすると、PIE は「どの教室でもすぐ使える授業セット」です。だから、毎回教室が変わっても対応しやすくなります。

  

なぜPIEが必要なのか

 では、なぜそんな仕組みが必要なのでしょうか。
理由はとてもシンプルで、「プログラム本体の位置が固定だと、関数や命令の場所を予測しやすくなるから」です。
 
これまでの学習では、"main()"や"vuln()"、"win()"のような関数を考えてきました。
もしプログラム本体の位置が毎回同じなら、それらの関数の場所も固定されやすくなります。
 
たとえば、学習用の単純な問題で「"win()"の位置がここ」と一度わかってしまえば、次回も同じ場所にあると期待しやすくなります。
しかし、PIEが有効なら、プログラム本体そのものが毎回違う場所に置かれやすくなります。
すると、"win()"や "main()"の位置も、前回とまったく同じとは限らなくなります。

白猫先生

簡単にたとえるなら、これは「教室の中にある先生の机や黒板の位置まで毎回変わる」ようなものです。昨日「黒板の右に先生の机があった」としても、今日は教室そのものの配置が変わっていたら、その情報はそのまま使えません。

つまり、PIEは、「プログラム本体の中の“目印”まで固定で考えにくくする守り」なのです。
 
 

ASLRとPIEはどう関係しているのか

 ここで、ASLRとPIEの関係を整理しましょう。
この2つはとても似た話に見えますが、役割の見方を分けると理解しやすいです。
 
1.ASLR
ASLR は、メモリ上の配置を毎回変えやすくする仕組みです。
たとえば、
・スタック
・ヒープ
・ライブラリ
などの位置が変わりやすくなります。
 
2.PIE
PIEは、プログラム本体も動かしやすくするための準備です。
つまり、実行ファイルそのものが「どこに置かれても動きやすい作り」になっている、ということです。
この2つを組み合わせて考えると、こんなイメージになります。
・ASLRは「毎回ちがう場所に置こうとする仕組み」。
・PIEは「その移動に対応できるようにしておく仕組み」。

白猫先生

簡単にたとえるなら、「ASLRは「毎日ちがう教室を使うルール」。PIEは「どの教室でも授業できるように教材を持ち運びやすくしておく工夫」です。つまり、PIEがあると、ASLRが「プログラム本体にも効きやすくなる」という関係で見るとわかりやすいです。

 

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

 ここで、学習用のシンプルなコードを見てみます。
-----
#include <stdio.h>
#include <string.h>
 
void win(void) {
    printf("Reached win!\n");
}
 
void vuln(char *input) {
    char buf[16];
    strcpy(buf, input);
}
 
int main(int argc, char *argv[]) {
    if (argc > 1) {
        vuln(argv[1]);
    }
    return 0;
}
-----
このコードには、"win()"という関数があります。
Buffer Overflowの基礎シリーズでは、この"win()"を「特別な到達先」として学びました。
ここで考えたいのは、「この"win()"の位置が毎回同じとは限らない」ということです。
 
PIE がない世界では、プログラム本体の位置が固定されやすく、"win()"のアドレスも比較的一定で考えやすいことがあります。
しかし PIE が有効だと、プログラム本体そのものの位置が変わりやすくなるため、"win()" の場所も毎回ずれる可能性があります。
 
このコードから学ぶべきことは、「関数名が同じでも、メモリ上の住所まで同じとは限らない」という点です。
つまり、
・"win()"は存在する。
・でも、その位置を固定で考えにくい。
・だから「前回と同じアドレス」とは思えない。
ということです。
ここが、PIE の大事なポイントです。
 
 

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

 初心者の段階では、PIEの効果を「プログラム本体の住所も固定で考えにくくなる」と理解すれば十分です。
これによって何が変わるのでしょうか。
 
PIE がない世界では、
・"main()"はいつもこの辺。
・"win()"もいつもこの辺。
・プログラム本体の中の位置関係を、固定の地図として考えやすい。
という状態になりやすいです。
しかし、PIEがあると、
・プログラム本体そのものの位置がずれる。
・その中の関数の位置も、実際のアドレスとしてはずれる。
・だから前回の実行結果をそのまま当てにしにくい。
という状態になります。

白猫先生

簡単にたとえるなら、これは「学校の建物ごと、毎回ちがう場所に移動している」ようなものです。

教室の中で「先生の机は黒板の右」という関係があっても、建物ごと別の場所へ移動していたら、外から見た住所は毎回変わります。

PIEもそれに似ていて、「中の並び方がある程度同じでも、全体の置かれる場所が動く」ため、固定住所としては考えにくくなるのです。
 
つまりPIEは、「プログラム本体を固定された目標にしにくくする守り」として理解するとよいです。
 

保護機構をやさしく理解するシリーズ。PIEとは何か? プログラム本体の位置まで動くと何が変わるのかのまとめ

 今回学んだ大事なことは、PIEがプログラム本体を固定された位置ではなく、動かしやすい形にする仕組みであり、その結果として"main()"や"win()"のような関数の実際のアドレスも毎回同じとは限らなくなるという点です。
 
ASLRが「毎回ちがう場所に置こうとする仕組み」だとすれば、PIEは「プログラム本体もその移動に対応できるようにする仕組み」と考えると理解しやすくなります。つまり、PIEがあることで、スタックやライブラリだけでなく、プログラム本体そのものも固定の地図として見にくくなります。
 
大切なのは、関数名が同じでも、メモリ上の住所まで同じとは限らないという感覚を持つことです。PIEは、プログラム本体を「いつも同じ場所にある目印」として当てにしにくくする、大事な守りのひとつです。