gccおよびldの位置に依存しない実行可能ファイルの-fPIEオプションとは何ですか?


回答:


100

PIEは、実行可能ファイルでアドレス空間配置のランダム化(ASLR)をサポートするためのものです。

PIEモードが作成される前は、プログラムの実行可能ファイルをメモリ内のランダムアドレスに配置することはできず、位置独立コード(PIC)ダイナミックライブラリのみをランダムオフセットに再配置できました。PICがダイナミックライブラリに対して行うのと非常によく似ていますが、違いは、プロシージャリンケージテーブル(PLT)が作成されず、代わりにPC相対再配置が使用されることです。

gcc /リンカーでPIEサポートを有効にした後、プログラムの本体がコンパイルされ、位置に依存しないコードとしてリンクされます。ダイナミックリンカは、ダイナミックライブラリと同様に、プログラムモジュールで完全な再配置処理を実行します。グローバルデータの使用はすべて、グローバルオフセットテーブル(GOT)を介したアクセスに変換され、GOTの再配置が追加されます。

PIEは、このOpenBSDPIEプレゼンテーションで詳しく説明されています。

機能の変更は、このスライドに示されています(PIEとPIC)。

x86 pic vs pie

ローカルグローバル変数と関数はパイで最適化されます

外部グローバル変数と関数はpicと同じです

そして、で、このスライド(古いスタイルのリンク対PIE)

x86パイとフラグなし(修正済み)

ローカルグローバル変数と関数はfixedに似ています

外部グローバル変数と関数はpicと同じです

PIEはと互換性がない可能性があることに注意してください -static


3
また、ウィキペディアで:en.wikipedia.org/wiki/...
osgx

5
-pieと-staticがARMで互換性があり、x86では互換性がないのはなぜですか?私のSOの質問:stackoverflow.com/questions/27082959/...
4ntoine

56

実行可能な最小限の例:実行可能ファイルを2回GDB

アクションを確認したい場合は、ASLRがPIE実行可能ファイルで機能し、実行間でアドレスを変更することを確認しましょう。

main.c

#include <stdio.h>

int main(void) {
    puts("hello");
}

main.sh

#!/usr/bin/env bash
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
for pie in no-pie pie; do
  exe="${pie}.out"
  gcc -O0 -std=c99 "-${pie}" "-f${pie}" -ggdb3 -o "$exe" main.c
  gdb -batch -nh \
    -ex 'set disable-randomization off' \
    -ex 'break main' \
    -ex 'run' \
    -ex 'printf "pc = 0x%llx\n", (long  long unsigned)$pc' \
    -ex 'run' \
    -ex 'printf "pc = 0x%llx\n", (long  long unsigned)$pc' \
    "./$exe" \
  ;
  echo
  echo
done

のある人にとって-no-pie、すべてが退屈です:

Breakpoint 1 at 0x401126: file main.c, line 4.

Breakpoint 1, main () at main.c:4
4           puts("hello");
pc = 0x401126

Breakpoint 1, main () at main.c:4
4           puts("hello");
pc = 0x401126

実行を開始する前にbreak main、ブレークポイントをに設定します0x401126

次に、両方の実行中にrun、アドレスで停止します0x401126

1-pieしかしがはるかに興味深いです。

Breakpoint 1 at 0x1139: file main.c, line 4.

Breakpoint 1, main () at main.c:4
4           puts("hello");
pc = 0x5630df2d6139

Breakpoint 1, main () at main.c:4
4           puts("hello");
pc = 0x55763ab2e139

実行を開始する前に、GDBは実行可能ファイルに存在する「ダミー」アドレスを取得します0x1139

ただし、GDBは起動後、ダイナミックローダーがプログラムを別の場所に配置したことをインテリジェントに認識し、最初のブレークはで停止しました0x5630df2d6139

次に、2回目の実行でも、実行可能ファイルが再び移動したことにインテリジェントに気づき、で壊れてしまいました0x55763ab2e139

echo 2 | sudo tee /proc/sys/kernel/randomize_va_spaceASLRがオンになっていることを確認します(Ubuntu 17.10のデフォルト):ASLR(アドレス空間配置のランダム化)を一時的に無効にするにはどうすればよいですか?| Ubuntuに聞いてください

set disable-randomization offそれ以外の場合は、GDBが必要です。名前が示すように、GDBはデフォルトでプロセスのASLRをオフにして、実行全体で固定アドレスを提供し、デバッグエクスペリエンスを向上させます。gdbアドレスと「実際の」アドレスの違いは?| スタックオーバーフロー

readelf 分析

さらに、次のことも確認できます。

readelf -s ./no-pie.out | grep main

実際のランタイムロードアドレスを示します(pcは4バイト後に次の命令を指します):

64: 0000000000401122    21 FUNC    GLOBAL DEFAULT   13 main

一方:

readelf -s ./pie.out | grep main

オフセットを与えるだけです:

65: 0000000000001135    23 FUNC    GLOBAL DEFAULT   14 main

ASLRを(randomize_va_spaceまたはのいずれかでset disable-randomization off)オフにすることにより、GDBは常にmainアドレスを与えます:0x5555555547a9、したがって、-pieアドレスは以下から構成されていると推測します:

0x555555554000 + random offset + symbol offset (79a)

TODO Linuxカーネル/ glibcローダー/どこにハードコードされている0x555555554000はどこにありますか?LinuxでPIE実行可能ファイルのテキストセクションのアドレスはどのように決定されますか?

最小限の組み立て例

私たちができるもう1つのすばらしいことは、PIEの意味をより具体的に理解するために、いくつかのアセンブリコードをいじることです。

Linuxx86_64自立型アセンブリhelloworldでそれを行うことができます。

main.S

.text
.global _start
_start:
asm_main_after_prologue:
    /* write */
    mov $1, %rax   /* syscall number */
    mov $1, %rdi   /* stdout */
    mov $msg, %rsi  /* buffer */
    mov $len, %rdx /* len */
    syscall

    /* exit */
    mov $60, %rax   /* syscall number */
    mov $0, %rdi    /* exit status */
    syscall
msg:
    .ascii "hello\n"
len = . - msg

GitHubアップストリーム

そしてそれは組み立てられ、うまく動作します:

as -o main.o main.S
ld -o main.out main.o
./main.out

ただし、PIEとしてリンクしようとすると(Linuxで静的にリンクされた位置に依存しない実行可能ELFを作成する方法で--no-dynamic-linker説明されているように必要です):

ld --no-dynamic-linker -pie -o main.out main.o

その後、リンクは次のように失敗します。

ld: main.o: relocation R_X86_64_32S against `.text' can not be used when making a PIE object; recompile with -fPIC
ld: final link failed: nonrepresentable section on output

行のため:

mov $msg, %rsi  /* buffer */

movオペランドにメッセージアドレスをハードコードするため、位置に依存しません。

代わりに、位置に依存しない方法でそれを書く場合:

lea msg(%rip), %rsi

その後、PIEリンクは正常に機能し、GDBは、実行可能ファイルが毎回メモリ内の異なる場所にロードされることを示しています。

ここでの違いは、構文が原因で現在のPCleaアドレスをmsg基準にしてのアドレスをエンコードしたことです。64ビットアセンブリプログラムでRIP相対アドレスを使用する方法ripも参照してください

両方のバージョンを次のように分解することで、それを理解することもできます。

objdump -S main.o

それぞれ与える:

e:   48 c7 c6 00 00 00 00    mov    $0x0,%rsi
e:   48 8d 35 19 00 00 00    lea    0x19(%rip),%rsi        # 2e <msg>

000000000000002e <msg>:
  2e:   68 65 6c 6c 6f          pushq  $0x6f6c6c65

したがって、現在のアドレス+ 0x19としてエンコードされleaた完全に正しいアドレスがすでにあることがはっきりとmsgわかります。

movただし、バージョンではアドレスがに設定されています00 00 00 00。これは、そこで再配置が実行されることを意味します。リンカーは何をしますか?エラーメッセージの不可解なものR_X86_64_32Sは、ld必要とされた実際のタイプの再配置であり、PIE実行可能ファイルでは発生しません。

私たちができるもう1つの楽しいことmsg.text、次の代わりにデータセクションにを配置することです。

.data
msg:
    .ascii "hello\n"
len = . - msg

これで、次のように.oアセンブルされます。

e:   48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # 15 <_start+0x15>

したがって、RIPオフセットは0になり、アセンブラによって再配置が要求されたと推測されます。私たちはそれを次のように確認します:

readelf -r main.o

これは:

Relocation section '.rela.text' at offset 0x160 contains 1 entry:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000011  000200000002 R_X86_64_PC32     0000000000000000 .data - 4

明らかに、PIE実行可能ファイルを処理できるR_X86_64_PC32PCの相対的な再配置ですld

この実験は、リンカー自体がプログラムがPIEである可能性があることを確認し、そのようにマークすることを教えてくれました。

次に、GCCでコンパイルするときに、-pie位置に依存しないアセンブリを生成するようにGCCに指示します。

ただし、アセンブリを自分で作成する場合は、位置の独立性を手動で確認する必要があります。

ARMv8 aarch64では、位置に依存しないhelloworldをADR命令で実現できます。

ELFが位置に依存しないかどうかを判断する方法は?

GDBを介して実行するだけでなく、いくつかの静的メソッドについて次のように説明しています。

Ubuntu18.10でテスト済み。


1
こんにちはチロ!ASLRオフパイオン開始アドレスについて個別の質問を作成し、ここにリンクできますか?
osgx

1
@osgx完了。あなたはすでにそれを知っていますか、それともその場でそれを掘り起こすつもりですか?:-)あなたがそれにいる間、Linuxカーネル/ dynローダーが何かがPIEを持っているかどうかをどのように決定するかを説明するのはクールでしょう:unix.stackexchange.com/questions/89211
CiroSantilli郝海东冠状病六四事件法轮功

まだわかりませんが、glibcのrtldから掘り下げる必要があることはわかっています-glibc / elf github.com/lattera/glibc/tree/master/elf(インタープリターがまだld-linux.soの場合)。3年前、Basileは0x55555555についてもstackoverflow.com/questions/29856044について確信がありませんでしたが、その質問はld.so自体の開始アドレスに関するものだったので、カーネルのfs /binfmt_elf.cまたはreadelf / objdumpとリンカースクリプトを調べてください
osgx 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.