関数呼び出しなど、コードをどのように変更しますか?
回答:
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
実行可能な最小限の例:実行可能ファイルを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_space
ASLRがオンになっていることを確認します(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
そしてそれは組み立てられ、うまく動作します:
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_PC32
PCの相対的な再配置ですld
。
この実験は、リンカー自体がプログラムがPIEである可能性があることを確認し、そのようにマークすることを教えてくれました。
次に、GCCでコンパイルするときに、-pie
位置に依存しないアセンブリを生成するようにGCCに指示します。
ただし、アセンブリを自分で作成する場合は、位置の独立性を手動で確認する必要があります。
ARMv8 aarch64では、位置に依存しないhelloworldをADR命令で実現できます。
ELFが位置に依存しないかどうかを判断する方法は?
GDBを介して実行するだけでなく、いくつかの静的メソッドについて次のように説明しています。
Ubuntu18.10でテスト済み。