実はASLRという言葉を初めて知りました(苦笑)が、何の事かは大体見当がつきましたし、yohhoyさんが教えてくださった "-fPIE -pie" オプション付でコンパイルすれば,関数アドレス(main, func等)もランダム化される事を、私はUbuntu 16.04(32bit, 64bit両方)で確認できました。解説される方がいらっしゃらないようなので、でしゃばってみます。
ASLRは論理アドレスをランダム化していますが、
これは見せかけ上のもので、コンソール上の数字がランダムに変わっているだけですか?
それとも論理アドレスが実際に変わっているのですか?
ユーザプログラムが扱えるメモリアドレスは論理アドレスだけです。
実際に、論理アドレスが変わったから、異なるアドレスが表示されたのです。
論理アドレスが変わるということは「実行するたびに、
異なる物理アドレスに展開されている」という理解で正しいですか?
論理アドレスが変わるということは「実行するたびに異なる論理アドレスに展開されている」という事でしかありません。
逆に、論理アドレスが同じなら、物理アドレスも同じ…とはなりません。論理アドレスが同じだろうが違っていようが、おそらく毎回全く異なる物理アドレスが割り当てられるのだろう、と想像していればよいと思います。
ユーザプログラムの動作とは、即ちCPUの動作です。繰返しますが、CPUが扱う(=アクセスできる)アドレスは論理アドレスだけです。その論理アドレスに対応する物理アドレスがどこかなんて、OSか物理デバイスドライバをデバッグするのでもなければ私達に関わりの無い事、そう割りきって構わないと思います。
念の為:論理アドレスを物理アドレスに変換する装置がメモリ管理ユニット(MMU)です。CPU、論理アドレス(virtual address)、物理アドレス(physical address)の関係は、MMUの動作の模式図が単純でわかりやすい(少なくとも私にはw)。論理アドレスと物理アドレスの対応付けをするのがページテーブルであり、ページテーブルを設定するのはOSの仕事です。
MMUを持たないコンピュータもあります。その場合、論理アドレスはそのまま物理アドレスとなってメモリをアクセスします。
1回目はランダム化できています。2回目はできていません。
1回目のソースと2回目のソースコードでなぜ結果が異なるのですか?
データとプログラムでは、メモリ領域を変更することの困難さが違うから、でしょう。
スタック領域は、プロセス起動時にスタックポインタを正しくセットしさえすれば、何番地でも支障なくプログラムは動作します。malloc()が返すメモリアドレスは、元々何番地が割り当てられるか実行するまでわからないのだから、実行の都度アドレスが変化しても動作に支障などあるはずが無い。つまり、これらのデータ領域はランダム化が容易です。
ところが、プログラム自体(機械語コード)は、一般にロードするアドレスを変えると動作できなくなります。位置独立コードをご覧下さい。「PIE」(Position Independent Executable: 位置独立実行形式)とは、ロードするアドレスが変わっても動作できる機械語コードの事です。特別な呼び名が有るくらい普通じゃない、と言えるでしょう。"-fPIE -pie"コンパイルオプションはPIEコードにコンパイルする指定ですね。
即ち、機械語コード(プログラム自体)のランダム化は、PIEコードにコンパイルする必要がある程度に、容易ではないのです。
おまけ:テストプログラムは一本化できます。
- グローバル変数(含static変数)のアドレスも表示してみる
- 直接スタックポインタを表示する代りに、スタックに割り当てられるauto変数や関数の引数のアドレスを表示しても良い
C
1#include <stdio.h>
2#include <stdlib.h>
3
4int v_global;
5
6void * get_sp(void)
7{
8 asm("movl %esp, %eax");
9}
10
11void func(const char* str)
12{
13 printf ("%s\n", str);
14}
15
16int main(int argc, char **argv)
17{
18 func("* print addresses *");
19 printf("address of main() : %p\n", main);
20 printf("address of func() : %p\n", func);
21 printf("address of v_global : %p\n", &v_global);
22 printf("malloc() returns : %p\n", malloc(16));
23 printf("stack pointer : %p\n", get_sp());
24 printf("address of argc : %p\n", &argc); // in stack area
25 return 0;
26}