2006年3月 1日

PIE (位置独立実行形式) を作成する

通常、PIC (位置独立コード) は共有ライブラリに用いられますが、Linux 上で最近の GCC, Glibc および GNU Binutils を使うと、実行ファイルも位置独立にすることができます。本記事では PIE (位置独立実行形式) を作成する方法と特徴を紹介します。

 

PIE の基本

それでは例を見てみましょう。次のようなファイル foo.c があるとします。

#include <stdio.h>
void foo() {
    printf("hello\n");
}

int main() {
    foo();
    return 0;
}

このファイルを -fPIE というオプションをつけてコンパイルし、 -pie というオプションをつけてリンクすれば PIE を作成できます。出来上がったファイルは普通に実行できます。

% gcc -c -fPIE foo.c
% gcc -o foo -pie foo.o
% ./foo
hello

objdump -d で foo を逆アセンブルしてみると、アドレスが非常に小さい数字となっていることがわかります。これは、アドレス空間上のどの位置にマップしても動くよう、共有ライブラリと同様に、PIE内はすべて相対アドレスになっているためです。

% objdump -d foo |grep main -A3
00000862 <main>:
 862:   55                      push   %ebp
 863:   89 e5                   mov    %esp,%ebp
 865:   83 ec 08                sub    $0x8,%esp

一方、 gcc にオプションを何も渡さずに作成した実行ファイルの場合、次のようになります。

% gcc foo.c
% objdump -d a.out |grep main -A3
08048378 <main>:
 8048378:       55                      push   %ebp
 8048379:       89 e5                   mov    %esp,%ebp
 804837b:       83 ec 08                sub    $0x8,%esp

動的リンカ ld.so は PIE の実行時に、共有ライブラリに対して行うのと同様に、相対アドレスをアドレス空間上にマップする処理を行います。このとき、一部の Linux ディストリビューションではセキュリティ強化のためにマップするアドレスのランダム化を行っています。これには特定のアドレスを突くような攻撃を防ぐ効果があります。

実行も動的リンクもできるバイナリ

ここまで見てきたように PIE は共有ライブラリと非常に似た性質を持っています。実際、 リンク時に -rdynamic オプションを加えると実行もできるし動的リンクもできるというバイナリを作成できます。 -rdynamic は実行ファイルの中にも動的リンク用のシンボルを残すオプションです。

次の例はファイル foo を動的リンクして関数 foo() を呼び出すプログラム call-foo.c を作成・実行しています。

% cat call-foo.c
void foo();
int main() {
    foo();
    return 0;
}

% gcc -c -fPIE foo.c; gcc -rdynamic -o foo -pie foo.o
% gcc -o call-foo call-foo.c ./foo
% ./call-foo
hello

実行も動的リンクもできるというバイナリがうれしい場面はあまりないと思いますが、非常に特殊な場面では役に立つかもしれません。下の例は自分自身を動的リンクしつつ階乗を計算するプログラムです。

% cat factorial.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <assert.h>

int factorial(int n) {
    if (n == 0) {
        return 1;
    } else {
        void *handle = dlopen ("./factorial", RTLD_LAZY);
        assert(handle != NULL);
        int (*factorial)(int) = dlsym(handle, "factorial");
        n = n *  factorial(n - 1);
        dlclose(handle);
        return n;
    }
}

int main() {
    const int n = 5;
    printf("factorial(%d) = %d\n", n, factorial(n));
    return 0;
}

% ./factorial
factorial(5) = 120

本物の共有ライブラリとの違い

共有ライブラリと動的リンク可能な PIC は似ていますが、non-static な関数の呼び出しに違いがあります。前者は共有ライブラリ内の非 static な関数を PLT 経由で呼び出しますが、PIE は PIE 内の非 static な関数は PLT を経由しないで直接呼びます。

このため、PIE を動的リンクして用いると、 LD_PRELOAD によるシンボルの置き換えの動作が共有ライブラリのときと変わってきます。LD_PRELOAD は PLT を経由しない関数呼び出しに対しては効力がないためです。

まとめ

本記事では PIE (位置独立実行形式) を作成する方法と特徴を紹介しました。 PIE はそれほど広く用いられている技術ではありませんが、セキュリティの強化にも使えるため、今後、利用される場面が増えていくのではないかと思います。

参考文献