PIE(position-independent executable)なオブジェクトの中身を見てみる
巷ではSpectre/Meltdownが話題になっている.
有効な対策として(K)ASLRが挙げられており,これはプロセス実行時のメモリ空間の配置をランダマイズすることで,例えば攻撃者が配置したコードのアドレスへのJMP等を防ぐことができる.
詳しくはシェルコードでググろう(解説できるほど詳しくない).
共有ライブラリは原則的にPIC(position-independent code)としてビルドされており,これらはASLRの恩恵に与ることができる.
しかし,通常のアプリケーションは絶対アドレスを用いて記述されているため,ASLRによるランダマイズが適用できない.
そこで,セキュリティ向上のために相対アドレスを用いたPIE(position-independent executable)と呼ばれる形式が通常のアプリケーションにおいても使用されている.
昔はセキュリティ上重要なファイルだけがPIEでビルドされていたらしいが,最近ではGentoo Linuxのprofile 17.0からPIEでのビルドがデフォルトオプションになったりしている.(これ全パッケージのリビルドが必要なので,まだ更新できてないです.同時にpython3.4がstableに降ってきたりして酷いことになりました…)
https://www.gentoo.org/support/news-items/2017-11-30-new-17-profiles.html
しかし,PIEでビルドした場合はパフォーマンスの低下が懸念されている.論文ではベンチマークであるSPEC CPU2006を用いて評価実験を行い,幾何平均で9.4%のパフォーマンス低下という結果を得たと述べている.
だが,実際にPIEを用いることで何がボトルネックになっているのか分からないので,今回はPIEなオブジェクトの中身を見てみる.
中身が見たくて見てるので,パフォーマンス低下の考察なら論文でしてると思うよ.
検証には前回と同じファイルを用いる.また,実行環境はGNU/Linux Fedora 25を用い,コンパイラはgcc 6.4.1を用いた.
#include <cstdio> class Base { public: Base() { printf("Base::Base() has called\n"); } virtual void hoge() { printf("Base::hoge() has called\n"); } virtual void fuga() { printf("Base::fuga() has called\n"); } }; class Derived : public Base { public: Derived() { printf("Derived::Derived() has called\n"); } void fuga() { printf("Derived::fuga() has called\n"); } }; void func(Base &x) { x.hoge(); x.fuga(); } int main() { Derived obj; func(obj); return 0; }
PIEとしてビルドする場合,-fPIEもしくは-fpieオプションをつけてコンパイルし,リンク時に-pieオプションをつける.
$ g++ -c -fPIE vtable.cpp $ g++ -o vtable_pie -pie vtable.o $ g++ -o vtable_pie -pie -fPIE vtable.cpp # 一発でやる場合 $ ./vtable_pie Base::Base() has called Derived::Derived() has called Base::hoge() has called Derived::fuga() has called
比較用に普通にビルドしたファイルも用意しておく.
$ g++ -o vtable_normal vtable.cpp $ ./vtable_normal Base::Base() has called Derived::Derived() has called Base::hoge() has called Derived::fuga() has called
ELFヘッダを見てみる.まずは普通にビルドした方から.
$ readelf -h vtable_normal ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x4005b0 Start of program headers: 64 (bytes into file) Start of section headers: 7288 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 9 Size of section headers: 64 (bytes) Number of section headers: 30 Section header string table index: 27
次にPIEな方.
$ readelf -h vtable_pie ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x7f0 Start of program headers: 64 (bytes into file) Start of section headers: 11504 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 9 Size of section headers: 64 (bytes) Number of section headers: 32 Section header string table index: 29
タイプが「EXEC (Executable file)」から「DYN (Shared object file)」になっており,またEntry point addressが小さくなっている.あとセクションヘッダの数が異なっており,そのせいかストリングテーブルのインデックスも異なっている.
セクションヘッダを見てみる.普通の方.
readelf -S vtable_normal There are 30 section headers, starting at offset 0x1c78: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000400238 00000238 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.ABI-tag NOTE 0000000000400254 00000254 0000000000000020 0000000000000000 A 0 0 4 [ 3] .note.gnu.build-i NOTE 0000000000400274 00000274 0000000000000024 0000000000000000 A 0 0 4 [ 4] .gnu.hash GNU_HASH 0000000000400298 00000298 0000000000000028 0000000000000000 A 5 0 8 [ 5] .dynsym DYNSYM 00000000004002c0 000002c0 00000000000000d8 0000000000000018 A 6 1 8 [ 6] .dynstr STRTAB 0000000000400398 00000398 0000000000000108 0000000000000000 A 0 0 1 [ 7] .gnu.version VERSYM 00000000004004a0 000004a0 0000000000000012 0000000000000002 A 5 0 2 [ 8] .gnu.version_r VERNEED 00000000004004b8 000004b8 0000000000000040 0000000000000000 A 6 2 8 [ 9] .rela.dyn RELA 00000000004004f8 000004f8 0000000000000060 0000000000000018 A 5 0 8 [10] .rela.plt RELA 0000000000400558 00000558 0000000000000018 0000000000000018 AI 5 23 8 [11] .init PROGBITS 0000000000400570 00000570 0000000000000017 0000000000000000 AX 0 0 4 [12] .plt PROGBITS 0000000000400590 00000590 0000000000000020 0000000000000010 AX 0 0 16 [13] .text PROGBITS 00000000004005b0 000005b0 0000000000000272 0000000000000000 AX 0 0 16 [14] .fini PROGBITS 0000000000400824 00000824 0000000000000009 0000000000000000 AX 0 0 4 [15] .rodata PROGBITS 0000000000400830 00000830 0000000000000116 0000000000000000 A 0 0 8 [16] .eh_frame_hdr PROGBITS 0000000000400948 00000948 0000000000000064 0000000000000000 A 0 0 4 [17] .eh_frame PROGBITS 00000000004009b0 000009b0 00000000000001b4 0000000000000000 A 0 0 8 [18] .init_array INIT_ARRAY 0000000000600dd8 00000dd8 0000000000000008 0000000000000000 WA 0 0 8 [19] .fini_array FINI_ARRAY 0000000000600de0 00000de0 0000000000000008 0000000000000000 WA 0 0 8 [20] .jcr PROGBITS 0000000000600de8 00000de8 0000000000000008 0000000000000000 WA 0 0 8 [21] .dynamic DYNAMIC 0000000000600df0 00000df0 0000000000000200 0000000000000010 WA 6 0 8 [22] .got PROGBITS 0000000000600ff0 00000ff0 0000000000000010 0000000000000008 WA 0 0 8 [23] .got.plt PROGBITS 0000000000601000 00001000 0000000000000020 0000000000000008 WA 0 0 8 [24] .data PROGBITS 0000000000601020 00001020 0000000000000004 0000000000000000 WA 0 0 1 [25] .bss NOBITS 0000000000601028 00001024 00000000000000b8 0000000000000000 WA 0 0 8 [26] .comment PROGBITS 0000000000000000 00001024 0000000000000058 0000000000000001 MS 0 0 1 [27] .shstrtab STRTAB 0000000000000000 00001b6e 0000000000000108 0000000000000000 0 0 1 [28] .symtab SYMTAB 0000000000000000 00001080 00000000000007b0 0000000000000018 29 46 8 [29] .strtab STRTAB 0000000000000000 00001830 000000000000033e 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
PIEな方.
$ readelf -S vtable_pie There are 32 section headers, starting at offset 0x2cf0: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000000238 00000238 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.ABI-tag NOTE 0000000000000254 00000254 0000000000000020 0000000000000000 A 0 0 4 [ 3] .note.gnu.build-i NOTE 0000000000000274 00000274 0000000000000024 0000000000000000 A 0 0 4 [ 4] .gnu.hash GNU_HASH 0000000000000298 00000298 0000000000000030 0000000000000000 A 5 0 8 [ 5] .dynsym DYNSYM 00000000000002c8 000002c8 0000000000000150 0000000000000018 A 6 2 8 [ 6] .dynstr STRTAB 0000000000000418 00000418 000000000000012f 0000000000000000 A 0 0 1 [ 7] .gnu.version VERSYM 0000000000000548 00000548 000000000000001c 0000000000000002 A 5 0 2 [ 8] .gnu.version_r VERNEED 0000000000000568 00000568 0000000000000040 0000000000000000 A 6 2 8 [ 9] .rela.dyn RELA 00000000000005a8 000005a8 00000000000001e0 0000000000000018 A 5 0 8 [10] .rela.plt RELA 0000000000000788 00000788 0000000000000018 0000000000000018 AI 5 25 8 [11] .init PROGBITS 00000000000007a0 000007a0 0000000000000017 0000000000000000 AX 0 0 4 [12] .plt PROGBITS 00000000000007c0 000007c0 0000000000000020 0000000000000010 AX 0 0 16 [13] .plt.got PROGBITS 00000000000007e0 000007e0 0000000000000008 0000000000000000 AX 0 0 8 [14] .text PROGBITS 00000000000007f0 000007f0 00000000000002c2 0000000000000000 AX 0 0 16 [15] .fini PROGBITS 0000000000000ab4 00000ab4 0000000000000009 0000000000000000 AX 0 0 4 [16] .rodata PROGBITS 0000000000000ac0 00000ac0 0000000000000097 0000000000000000 A 0 0 8 [17] .eh_frame_hdr PROGBITS 0000000000000b58 00000b58 0000000000000064 0000000000000000 A 0 0 4 [18] .eh_frame PROGBITS 0000000000000bc0 00000bc0 00000000000001b4 0000000000000000 A 0 0 8 [19] .init_array INIT_ARRAY 0000000000201d38 00001d38 0000000000000008 0000000000000000 WA 0 0 8 [20] .fini_array FINI_ARRAY 0000000000201d40 00001d40 0000000000000008 0000000000000000 WA 0 0 8 [21] .jcr PROGBITS 0000000000201d48 00001d48 0000000000000008 0000000000000000 WA 0 0 8 [22] .data.rel.ro PROGBITS 0000000000201d50 00001d50 0000000000000070 0000000000000000 WA 0 0 8 [23] .dynamic DYNAMIC 0000000000201dc0 00001dc0 0000000000000210 0000000000000010 WA 6 0 8 [24] .got PROGBITS 0000000000201fd0 00001fd0 0000000000000030 0000000000000008 WA 0 0 8 [25] .got.plt PROGBITS 0000000000202000 00002000 0000000000000020 0000000000000008 WA 0 0 8 [26] .data PROGBITS 0000000000202020 00002020 0000000000000004 0000000000000000 WA 0 0 1 [27] .bss NOBITS 0000000000202024 00002024 0000000000000004 0000000000000000 WA 0 0 1 [28] .comment PROGBITS 0000000000000000 00002024 0000000000000058 0000000000000001 MS 0 0 1 [29] .shstrtab STRTAB 0000000000000000 00002bd2 0000000000000119 0000000000000000 0 0 1 [30] .symtab SYMTAB 0000000000000000 00002080 00000000000007f8 0000000000000018 31 48 8 [31] .strtab STRTAB 0000000000000000 00002878 000000000000035a 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
.plt.gotと.data.rel.roというセクションが増えているようだ.ググったらRELRO (RELocation Read-Only)という別?の技術が出てきたが何も分からんので宿題にしたい.
逆アセンブルしてみる.
objdump -d vtable_pie |c++filt vtable_pie: file format elf64-x86-64 Disassembly of section .init: 00000000000007a0 <_init>: 7a0: 48 83 ec 08 sub $0x8,%rsp 7a4: 48 8b 05 25 18 20 00 mov 0x201825(%rip),%rax # 201fd0 <_DYNAMIC+0x210> 7ab: 48 85 c0 test %rax,%rax 7ae: 74 02 je 7b2 <_init+0x12> 7b0: ff d0 callq *%rax 7b2: 48 83 c4 08 add $0x8,%rsp 7b6: c3 retq Disassembly of section .plt: 00000000000007c0 <puts@plt-0x10>: 7c0: ff 35 42 18 20 00 pushq 0x201842(%rip) # 202008 <_GLOBAL_OFFSET_TABLE_+0x8> 7c6: ff 25 44 18 20 00 jmpq *0x201844(%rip) # 202010 <_GLOBAL_OFFSET_TABLE_+0x10> 7cc: 0f 1f 40 00 nopl 0x0(%rax) 00000000000007d0 <puts@plt>: 7d0: ff 25 42 18 20 00 jmpq *0x201842(%rip) # 202018 <_GLOBAL_OFFSET_TABLE_+0x18> 7d6: 68 00 00 00 00 pushq $0x0 7db: e9 e0 ff ff ff jmpq 7c0 <_init+0x20> Disassembly of section .plt.got: 00000000000007e0 <.plt.got>: 7e0: ff 25 12 18 20 00 jmpq *0x201812(%rip) # 201ff8 <_DYNAMIC+0x238> 7e6: 66 90 xchg %ax,%ax Disassembly of section .text: 00000000000007f0 <_start>: 7f0: 31 ed xor %ebp,%ebp 7f2: 49 89 d1 mov %rdx,%r9 7f5: 5e pop %rsi 7f6: 48 89 e2 mov %rsp,%rdx 7f9: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 7fd: 50 push %rax 7fe: 54 push %rsp 7ff: 4c 8d 05 aa 02 00 00 lea 0x2aa(%rip),%r8 # ab0 <__libc_csu_fini> 806: 48 8d 0d 33 02 00 00 lea 0x233(%rip),%rcx # a40 <__libc_csu_init> 80d: 48 8d 3d 45 01 00 00 lea 0x145(%rip),%rdi # 959 <main> 814: ff 15 c6 17 20 00 callq *0x2017c6(%rip) # 201fe0 <_DYNAMIC+0x220> 81a: f4 hlt 81b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 0000000000000820 <deregister_tm_clones>: 820: 48 8d 3d 01 18 20 00 lea 0x201801(%rip),%rdi # 202028 <__TMC_END__> 827: 48 8d 05 01 18 20 00 lea 0x201801(%rip),%rax # 20202f <__TMC_END__+0x7> 82e: 55 push %rbp 82f: 48 29 f8 sub %rdi,%rax 832: 48 89 e5 mov %rsp,%rbp 835: 48 83 f8 0e cmp $0xe,%rax 839: 76 15 jbe 850 <deregister_tm_clones+0x30> 83b: 48 8b 05 a6 17 20 00 mov 0x2017a6(%rip),%rax # 201fe8 <_DYNAMIC+0x228> 842: 48 85 c0 test %rax,%rax 845: 74 09 je 850 <deregister_tm_clones+0x30> 847: 5d pop %rbp 848: ff e0 jmpq *%rax 84a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 850: 5d pop %rbp 851: c3 retq 852: 0f 1f 40 00 nopl 0x0(%rax) 856: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 85d: 00 00 00 0000000000000860 <register_tm_clones>: 860: 48 8d 3d c1 17 20 00 lea 0x2017c1(%rip),%rdi # 202028 <__TMC_END__> 867: 48 8d 35 ba 17 20 00 lea 0x2017ba(%rip),%rsi # 202028 <__TMC_END__> 86e: 55 push %rbp 86f: 48 29 fe sub %rdi,%rsi 872: 48 89 e5 mov %rsp,%rbp 875: 48 c1 fe 03 sar $0x3,%rsi 879: 48 89 f0 mov %rsi,%rax 87c: 48 c1 e8 3f shr $0x3f,%rax 880: 48 01 c6 add %rax,%rsi 883: 48 d1 fe sar %rsi 886: 74 18 je 8a0 <register_tm_clones+0x40> 888: 48 8b 05 61 17 20 00 mov 0x201761(%rip),%rax # 201ff0 <_DYNAMIC+0x230> 88f: 48 85 c0 test %rax,%rax 892: 74 0c je 8a0 <register_tm_clones+0x40> 894: 5d pop %rbp 895: ff e0 jmpq *%rax 897: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1) 89e: 00 00 8a0: 5d pop %rbp 8a1: c3 retq 8a2: 0f 1f 40 00 nopl 0x0(%rax) 8a6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 8ad: 00 00 00 00000000000008b0 <__do_global_dtors_aux>: 8b0: 80 3d 6d 17 20 00 00 cmpb $0x0,0x20176d(%rip) # 202024 <_edata> 8b7: 75 27 jne 8e0 <__do_global_dtors_aux+0x30> 8b9: 48 83 3d 37 17 20 00 cmpq $0x0,0x201737(%rip) # 201ff8 <_DYNAMIC+0x238> 8c0: 00 8c1: 55 push %rbp 8c2: 48 89 e5 mov %rsp,%rbp 8c5: 74 0c je 8d3 <__do_global_dtors_aux+0x23> 8c7: 48 8d 3d 82 14 20 00 lea 0x201482(%rip),%rdi # 201d50 <__dso_handle> 8ce: e8 0d ff ff ff callq 7e0 <puts@plt+0x10> 8d3: e8 48 ff ff ff callq 820 <deregister_tm_clones> 8d8: 5d pop %rbp 8d9: c6 05 44 17 20 00 01 movb $0x1,0x201744(%rip) # 202024 <_edata> 8e0: f3 c3 repz retq 8e2: 0f 1f 40 00 nopl 0x0(%rax) 8e6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 8ed: 00 00 00 00000000000008f0 <frame_dummy>: 8f0: 48 8d 3d 51 14 20 00 lea 0x201451(%rip),%rdi # 201d48 <__JCR_END__> 8f7: 48 83 3f 00 cmpq $0x0,(%rdi) 8fb: 75 0b jne 908 <frame_dummy+0x18> 8fd: e9 5e ff ff ff jmpq 860 <register_tm_clones> 902: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 908: 48 8b 05 c9 16 20 00 mov 0x2016c9(%rip),%rax # 201fd8 <_DYNAMIC+0x218> 90f: 48 85 c0 test %rax,%rax 912: 74 e9 je 8fd <frame_dummy+0xd> 914: 55 push %rbp 915: 48 89 e5 mov %rsp,%rbp 918: ff d0 callq *%rax 91a: 5d pop %rbp 91b: e9 40 ff ff ff jmpq 860 <register_tm_clones> 0000000000000920 <func(Base&)>: 920: 55 push %rbp 921: 48 89 e5 mov %rsp,%rbp 924: 48 83 ec 10 sub $0x10,%rsp 928: 48 89 7d f8 mov %rdi,-0x8(%rbp) 92c: 48 8b 45 f8 mov -0x8(%rbp),%rax 930: 48 8b 00 mov (%rax),%rax 933: 48 8b 00 mov (%rax),%rax 936: 48 8b 55 f8 mov -0x8(%rbp),%rdx 93a: 48 89 d7 mov %rdx,%rdi 93d: ff d0 callq *%rax 93f: 48 8b 45 f8 mov -0x8(%rbp),%rax 943: 48 8b 00 mov (%rax),%rax 946: 48 83 c0 08 add $0x8,%rax 94a: 48 8b 00 mov (%rax),%rax 94d: 48 8b 55 f8 mov -0x8(%rbp),%rdx 951: 48 89 d7 mov %rdx,%rdi 954: ff d0 callq *%rax 956: 90 nop 957: c9 leaveq 958: c3 retq 0000000000000959 <main>: 959: 55 push %rbp 95a: 48 89 e5 mov %rsp,%rbp 95d: 48 83 ec 10 sub $0x10,%rsp 961: 48 8d 45 f8 lea -0x8(%rbp),%rax 965: 48 89 c7 mov %rax,%rdi 968: e8 75 00 00 00 callq 9e2 <Derived::Derived()> 96d: 48 8d 45 f8 lea -0x8(%rbp),%rax 971: 48 89 c7 mov %rax,%rdi 974: e8 a7 ff ff ff callq 920 <func(Base&)> 979: b8 00 00 00 00 mov $0x0,%eax 97e: c9 leaveq 97f: c3 retq 0000000000000980 <Base::Base()>: 980: 55 push %rbp 981: 48 89 e5 mov %rsp,%rbp 984: 48 83 ec 10 sub $0x10,%rsp 988: 48 89 7d f8 mov %rdi,-0x8(%rbp) 98c: 48 8d 15 f5 13 20 00 lea 0x2013f5(%rip),%rdx # 201d88 <vtable for Base+0x10> 993: 48 8b 45 f8 mov -0x8(%rbp),%rax 997: 48 89 10 mov %rdx,(%rax) 99a: 48 8d 3d 23 01 00 00 lea 0x123(%rip),%rdi # ac4 <_IO_stdin_used+0x4> 9a1: e8 2a fe ff ff callq 7d0 <puts@plt> 9a6: 90 nop 9a7: c9 leaveq 9a8: c3 retq 9a9: 90 nop 00000000000009aa <Base::hoge()>: 9aa: 55 push %rbp 9ab: 48 89 e5 mov %rsp,%rbp 9ae: 48 83 ec 10 sub $0x10,%rsp 9b2: 48 89 7d f8 mov %rdi,-0x8(%rbp) 9b6: 48 8d 3d 1f 01 00 00 lea 0x11f(%rip),%rdi # adc <_IO_stdin_used+0x1c> 9bd: e8 0e fe ff ff callq 7d0 <puts@plt> 9c2: 90 nop 9c3: c9 leaveq 9c4: c3 retq 9c5: 90 nop 00000000000009c6 <Base::fuga()>: 9c6: 55 push %rbp 9c7: 48 89 e5 mov %rsp,%rbp 9ca: 48 83 ec 10 sub $0x10,%rsp 9ce: 48 89 7d f8 mov %rdi,-0x8(%rbp) 9d2: 48 8d 3d 1b 01 00 00 lea 0x11b(%rip),%rdi # af4 <_IO_stdin_used+0x34> 9d9: e8 f2 fd ff ff callq 7d0 <puts@plt> 9de: 90 nop 9df: c9 leaveq 9e0: c3 retq 9e1: 90 nop 00000000000009e2 <Derived::Derived()>: 9e2: 55 push %rbp 9e3: 48 89 e5 mov %rsp,%rbp 9e6: 48 83 ec 10 sub $0x10,%rsp 9ea: 48 89 7d f8 mov %rdi,-0x8(%rbp) 9ee: 48 8b 45 f8 mov -0x8(%rbp),%rax 9f2: 48 89 c7 mov %rax,%rdi 9f5: e8 86 ff ff ff callq 980 <Base::Base()> 9fa: 48 8d 15 67 13 20 00 lea 0x201367(%rip),%rdx # 201d68 <vtable for Derived+0x10> a01: 48 8b 45 f8 mov -0x8(%rbp),%rax a05: 48 89 10 mov %rdx,(%rax) a08: 48 8d 3d fd 00 00 00 lea 0xfd(%rip),%rdi # b0c <_IO_stdin_used+0x4c> a0f: e8 bc fd ff ff callq 7d0 <puts@plt> a14: 90 nop a15: c9 leaveq a16: c3 retq a17: 90 nop 0000000000000a18 <Derived::fuga()>: a18: 55 push %rbp a19: 48 89 e5 mov %rsp,%rbp a1c: 48 83 ec 10 sub $0x10,%rsp a20: 48 89 7d f8 mov %rdi,-0x8(%rbp) a24: 48 8d 3d ff 00 00 00 lea 0xff(%rip),%rdi # b2a <_IO_stdin_used+0x6a> a2b: e8 a0 fd ff ff callq 7d0 <puts@plt> a30: 90 nop a31: c9 leaveq a32: c3 retq a33: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) a3a: 00 00 00 a3d: 0f 1f 00 nopl (%rax) 0000000000000a40 <__libc_csu_init>: a40: 41 57 push %r15 a42: 41 56 push %r14 a44: 41 89 ff mov %edi,%r15d a47: 41 55 push %r13 a49: 41 54 push %r12 a4b: 4c 8d 25 e6 12 20 00 lea 0x2012e6(%rip),%r12 # 201d38 <__frame_dummy_init_array_entry> a52: 55 push %rbp a53: 48 8d 2d e6 12 20 00 lea 0x2012e6(%rip),%rbp # 201d40 <__init_array_end> a5a: 53 push %rbx a5b: 49 89 f6 mov %rsi,%r14 a5e: 49 89 d5 mov %rdx,%r13 a61: 4c 29 e5 sub %r12,%rbp a64: 48 83 ec 08 sub $0x8,%rsp a68: 48 c1 fd 03 sar $0x3,%rbp a6c: e8 2f fd ff ff callq 7a0 <_init> a71: 48 85 ed test %rbp,%rbp a74: 74 20 je a96 <__libc_csu_init+0x56> a76: 31 db xor %ebx,%ebx a78: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1) a7f: 00 a80: 4c 89 ea mov %r13,%rdx a83: 4c 89 f6 mov %r14,%rsi a86: 44 89 ff mov %r15d,%edi a89: 41 ff 14 dc callq *(%r12,%rbx,8) a8d: 48 83 c3 01 add $0x1,%rbx a91: 48 39 dd cmp %rbx,%rbp a94: 75 ea jne a80 <__libc_csu_init+0x40> a96: 48 83 c4 08 add $0x8,%rsp a9a: 5b pop %rbx a9b: 5d pop %rbp a9c: 41 5c pop %r12 a9e: 41 5d pop %r13 aa0: 41 5e pop %r14 aa2: 41 5f pop %r15 aa4: c3 retq aa5: 90 nop aa6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) aad: 00 00 00 0000000000000ab0 <__libc_csu_fini>: ab0: f3 c3 repz retq Disassembly of section .fini: 0000000000000ab4 <_fini>: ab4: 48 83 ec 08 sub $0x8,%rsp ab8: 48 83 c4 08 add $0x8,%rsp abc: c3 retq
先程宿題にした.plt.gotが逆アセンブルされている.xchg %ax,%axはNOPなので,*(%rip + 0x201812)にJMPするだけのコードのようだ.
以下,普通にビルドしたプログラムの逆アセンブル結果と比較してみる.量が多いのでDerived::Derived()だけ抜き出す.
普通にビルドした方.
0000000000400760 <Derived::Derived()>: 400760: 55 push %rbp 400761: 48 89 e5 mov %rsp,%rbp 400764: 48 83 ec 10 sub $0x10,%rsp 400768: 48 89 7d f8 mov %rdi,-0x8(%rbp) 40076c: 48 8b 45 f8 mov -0x8(%rbp),%rax 400770: 48 89 c7 mov %rax,%rdi 400773: e8 8e ff ff ff callq 400706 <Base::Base()> 400778: ba d8 08 40 00 mov $0x4008d8,%edx 40077d: 48 8b 45 f8 mov -0x8(%rbp),%rax 400781: 48 89 10 mov %rdx,(%rax) 400784: bf 88 08 40 00 mov $0x400888,%edi 400789: e8 12 fe ff ff callq 4005a0 <puts@plt> 40078e: 90 nop 40078f: c9 leaveq 400790: c3 retq 400791: 90 nop
PIEの方.
00000000000009e2 <Derived::Derived()>: 9e2: 55 push %rbp 9e3: 48 89 e5 mov %rsp,%rbp 9e6: 48 83 ec 10 sub $0x10,%rsp 9ea: 48 89 7d f8 mov %rdi,-0x8(%rbp) 9ee: 48 8b 45 f8 mov -0x8(%rbp),%rax 9f2: 48 89 c7 mov %rax,%rdi 9f5: e8 86 ff ff ff callq 980 <Base::Base()> 9fa: 48 8d 15 67 13 20 00 lea 0x201367(%rip),%rdx # 201d68 <vtable for Derived+0x10> a01: 48 8b 45 f8 mov -0x8(%rbp),%rax a05: 48 89 10 mov %rdx,(%rax) a08: 48 8d 3d fd 00 00 00 lea 0xfd(%rip),%rdi # b0c <_IO_stdin_used+0x4c> a0f: e8 bc fd ff ff callq 7d0 <puts@plt> a14: 90 nop a15: c9 leaveq a16: c3 retq a17: 90 nop
見比べてみると,call命令はそのままアドレスが格納されているが,vtableのアドレスやputs()の引数(ここでputsが呼ばれているのはprintfが最適化されているため)は%ripから計算していることが分かる.普通にビルドした方はともかく,PIEの方もcall先のアドレスが固定(e.g. Base::Base() -> 0x980)になっているように見えるが,これはASLRが有効であれば実行時にランダムな値が足し合わさるようだ.
gdbで実行時のアドレスを確認してみる.gdbではASLRが無効化されるので,disable-randomizationオプションをoffにすることで有効化する.
$ gdb -ex 'set print asm-demangle on' -ex 'set disable-randomization off' vtable_pie (gdb) break Derived::Derived() Breakpoint 1 at 0x9e6 (gdb) run Starting program: /home/vagrant/vtable_pie Breakpoint 1, 0x000055e54d3719e6 in Derived::Derived() () (gdb) disassemble Dump of assembler code for function _ZN7DerivedC2Ev: 0x000055e54d3719e2 <+0>: push %rbp 0x000055e54d3719e3 <+1>: mov %rsp,%rbp => 0x000055e54d3719e6 <+4>: sub $0x10,%rsp 0x000055e54d3719ea <+8>: mov %rdi,-0x8(%rbp) 0x000055e54d3719ee <+12>: mov -0x8(%rbp),%rax 0x000055e54d3719f2 <+16>: mov %rax,%rdi 0x000055e54d3719f5 <+19>: callq 0x55e54d371980 <Base::Base()> 0x000055e54d3719fa <+24>: lea 0x201367(%rip),%rdx # 0x55e54d572d68 <vtable for Derived+16> 0x000055e54d371a01 <+31>: mov -0x8(%rbp),%rax 0x000055e54d371a05 <+35>: mov %rdx,(%rax) 0x000055e54d371a08 <+38>: lea 0xfd(%rip),%rdi # 0x55e54d371b0c 0x000055e54d371a0f <+45>: callq 0x55e54d3717d0 <puts@plt> 0x000055e54d371a14 <+50>: nop 0x000055e54d371a15 <+51>: leaveq 0x000055e54d371a16 <+52>: retq End of assembler dump. (gdb)
再度起動し,アドレス空間が変わっていることを確認する.
(gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/vagrant/vtable_pie Breakpoint 1, 0x000055dda61629e6 in Derived::Derived() () (gdb) disassemble Dump of assembler code for function _ZN7DerivedC2Ev: 0x000055dda61629e2 <+0>: push %rbp 0x000055dda61629e3 <+1>: mov %rsp,%rbp => 0x000055dda61629e6 <+4>: sub $0x10,%rsp 0x000055dda61629ea <+8>: mov %rdi,-0x8(%rbp) 0x000055dda61629ee <+12>: mov -0x8(%rbp),%rax 0x000055dda61629f2 <+16>: mov %rax,%rdi 0x000055dda61629f5 <+19>: callq 0x55dda6162980 <Base::Base()> 0x000055dda61629fa <+24>: lea 0x201367(%rip),%rdx # 0x55dda6363d68 <vtable for Derived+16> 0x000055dda6162a01 <+31>: mov -0x8(%rbp),%rax 0x000055dda6162a05 <+35>: mov %rdx,(%rax) 0x000055dda6162a08 <+38>: lea 0xfd(%rip),%rdi # 0x55dda6162b0c 0x000055dda6162a0f <+45>: callq 0x55dda61627d0 <puts@plt> 0x000055dda6162a14 <+50>: nop 0x000055dda6162a15 <+51>: leaveq 0x000055dda6162a16 <+52>: retq End of assembler dump. (gdb)
呼び出す関数(Base::Base(),puts@plt)のアドレスが変わっていることが分かる.
実行時にアドレスが足し合わされるといった説明を見たが,call命令のオペランドが実行時に機械語のレベルで書き換えられるのだろうか.それなら確かにパフォーマンス低下もうなずける.というかそれ無茶では…?
実際にこの時の機械語をチェックしてみよう.gdbのdisassemble命令に/rというオプションを付けるとマシン語の対応が分かる.
(gdb) disassemble /r Dump of assembler code for function _ZN7DerivedC2Ev: 0x000056083c3fc9e2 <+0>: 55 push %rbp 0x000056083c3fc9e3 <+1>: 48 89 e5 mov %rsp,%rbp => 0x000056083c3fc9e6 <+4>: 48 83 ec 10 sub $0x10,%rsp 0x000056083c3fc9ea <+8>: 48 89 7d f8 mov %rdi,-0x8(%rbp) 0x000056083c3fc9ee <+12>: 48 8b 45 f8 mov -0x8(%rbp),%rax 0x000056083c3fc9f2 <+16>: 48 89 c7 mov %rax,%rdi 0x000056083c3fc9f5 <+19>: e8 86 ff ff ff callq 0x56083c3fc980 <Base::Base()> 0x000056083c3fc9fa <+24>: 48 8d 15 67 13 20 00 lea 0x201367(%rip),%rdx # 0x56083c5fdd68 <vtable for Derived+16> 0x000056083c3fca01 <+31>: 48 8b 45 f8 mov -0x8(%rbp),%rax 0x000056083c3fca05 <+35>: 48 89 10 mov %rdx,(%rax) 0x000056083c3fca08 <+38>: 48 8d 3d fd 00 00 00 lea 0xfd(%rip),%rdi # 0x56083c3fcb0c 0x000056083c3fca0f <+45>: e8 bc fd ff ff callq 0x56083c3fc7d0 <puts@plt> 0x000056083c3fca14 <+50>: 90 nop 0x000056083c3fca15 <+51>: c9 leaveq 0x000056083c3fca16 <+52>: c3 retq End of assembler dump.
call命令だけ抜き出して,objdumpの逆アセンブル結果と見比べてみる.
gdb
0x000056083c3fc9f5 <+19>: e8 86 ff ff ff callq 0x56083c3fc980 <Base::Base()>
objdump
9f5: e8 86 ff ff ff callq 980 <Base::Base()>
変わってない…?と思ったが,callqのOpcodeを見ると「Call near, relative, displacement relative to next instruction」とのことなので,%ripからの相対位置で呼び出しているようだ.
Liberation: x86 Instruction Set Reference
普通にビルドした方も見てみる.
400773: e8 8e ff ff ff callq 400706 <Base::Base()>
同じくOpcodeがE8なので,おそらくここでパフォーマンス低下は招いていないだろう.
これらの結果より,PIEでない場合は即値で扱っていたアドレスがlea命令に置き換わったことで,ある程度のパフォーマンス低下が起きていると考えられる.また,ローダの処理が追加されていることもパフォーマンス低下を招いていると予想できる.これ以上はちゃんとサーベイしないと分からないので,もっと論文読んで出直します.
今回の内容マサカリビリティに溢れているので怖いっすね
参考資料:
OSセキュリティチュートリアル
PIE (位置独立実行形式) を作成する - bkブログ
Linux の共有ライブラリを作るとき PIC でコンパイルするのはなぜか - bkブログ
革命の日々! PIE(位置独立実行ファイル)で遊んでみる