本文分析ELF文件在運行時動態重定向的實現。
以下面的兩個程序為例:
$ cat mlib.c
int boo()
{
return 0;
}
int foo()
{
void *fn = boo;
boo();
return 0;
}
$ cat ./main.c
int boo();
int main()
{
void *fn = boo;$ cat ./main.c
int boo();
int main()
{
void *fn = boo;
boo();
}
boo();
}
$ gcc -o libmlib.so -shared -fPIC ./mlib.c
$ gcc -o main -lmlib -L. ./main.c
main函數中引用的函數boo定義在shared library中。
loader如何實現對shared library中的函數調用進行重定向 初始時,main函數中對boo的調用實際call的是boo@plt,地址為0x400560,位置處於section .plt中(參見表一)。
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000400660 : push %rbp
0x0000000000400661 : mov %rsp,%rbp
=> 0x0000000000400664 : sub $0x10,%rsp
0x0000000000400668 : movq $0x400560,-0x8(%rbp)
0x0000000000400670 : mov $0x0,%eax
0x0000000000400675 : callq 0x400560
0x000000000040067a : leaveq
0x000000000040067b : retq
End of assembler dump.
對boo@plt進行反匯編,可以看出第一條jmp語句跳到了下一行0x400566。0x2被push到了堆棧。0x2應該代表.rela.plt中的第三項(0為第一項),該信息將被傳遞給loader。
(gdb) disassemble 0x400560
Dump of assembler code for function boo@plt:
0x0000000000400560 : jmpq *0x2004f2(%rip) # 0x600a58
0x0000000000400566 : pushq $0x2
0x000000000040056b : jmpq 0x400530
(gdb) x /xg 0x600a58
0x600a58 : 0x0000000000400566
push操作后,jmp到了0x400530,接着又把0x600a38中的數據push到了堆棧。注意0x600a38是.got.plt 中的第二個entry(每個entry的size是8)。最后又jmp到了0x00007ffff7def2d0(地址0x600a40中的數據)。
(gdb) x /5i 0x400530
0x400530: pushq 0x200502(%rip) # 0x600a38
0x400536: jmpq *0x200504(%rip) # 0x600a40
0x40053c: nopl 0x0(%rax)
0x400540 <__libc_start_main>: jmpq *0x200502(%rip) # 0x600a48 <__libc_start_main>
0x400546 <__libc_start_main>: pushq $0x0
(gdb) x /xg 0x600a38
0x600a38: 0x00007ffff7ffe190
(gdb) x /xg 0x600a40
0x600a40: 0x00007ffff7def2d0
從shared library map中可以看出,0x00007ffff7def2d0對應到/lib64/ld-linux-x86-64.so.2中。loader將利用之前push的兩個參數0x2和0x00007ffff7ffe190對boo@got.plt(即0x600a58)中的數據進行更新,修改成函數boo的實際地址。
(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0x00007ffff7ddbba0 0x00007ffff7df4baa Yes (*) /lib64/ld-linux-x86-64.so.2
No linux-vdso.so.1
0x00007ffff7bda5b0 0x00007ffff7bda6c8 Yes (*) ./libmlib.so
0x00007ffff784c470 0x00007ffff7978aa0 Yes (*) /usr/lib/libc.so.6
(*): Shared library is missing debugging information.
將程序運行到斷點0x000000000040067a,檢查0x600a58的數值,可以看出已經變成了函數boo的地址。
(gdb) x /xg 0x600a58
0x600a58 : 0x00007ffff7bda698
(gdb) p boo
$1 = {} 0x7ffff7bda698
loader如何實現對shared library中變量的重定向
在main函數中對boo的地址進行了引用。可以看出boo的地址就是0x400560,即,而非boo的真正地址。后面可以看出,即使在libmlib.so中對boo的引用同樣也指向0x400560。××這樣做的目的應當是確保在main和library當中對同一函數的地址引用是相等的××。
0x0000000000400668 : movq $0x400560,-0x8(%rbp)
在shared library中的foo函數中對boo也做了引用,可以看出boo的地址被用0x200978中的數據間接代替了,而且0x200978中的數值是0,顯然不對。
gdb ./libmlib.so
(gdb) disassemble foo
Dump of assembler code for function foo:
0x00000000000006a3 : push %rbp
0x00000000000006a4 : mov %rsp,%rbp
0x00000000000006a7 : sub $0x10,%rsp
0x00000000000006ab : mov 0x2002c6(%rip),%rax # 0x200978
0x00000000000006b2 : mov %rax,-0x8(%rbp)
0x00000000000006b6 : mov $0x0,%eax
0x00000000000006bb : callq 0x590
0x00000000000006c0 : mov $0x0,%eax
0x00000000000006c5 : leaveq
0x00000000000006c6 : retq
(gdb) x /xg 0x200978
0x200978: 0x0000000000000000
這是因為在.rela.dyn中有一個boo的重定向項。當libmlib.so被load進main中時,loader將會根據相應symbol的地址修正所有R_X86_64_GLOB_DAT重定向項的值。這些重定向項所指向的需要修改的地址都位於section .got中(參見表三)。
$ readelf -r ./libmlib.so
Relocation section '.rela.dyn' at offset 0x430 contains 9 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000200780 000000000008 R_X86_64_RELATIVE 660
000000200788 000000000008 R_X86_64_RELATIVE 620
0000002009b8 000000000008 R_X86_64_RELATIVE 2009b8
000000200958 000200000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMClone + 0
000000200960 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000200968 000400000006 R_X86_64_GLOB_DAT 0000000000000000 _Jv_RegisterClasses + 0
000000200970 000500000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTa + 0
000000200978 000a00000006 R_X86_64_GLOB_DAT 0000000000000698 boo + 0
000000200980 000600000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize + 0
Relocation section '.rela.plt' at offset 0x508 contains 3 entries:
Offset Info Type Sym. Value Sym. Name + Addend
0000002009a0 000300000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0
0000002009a8 000a00000007 R_X86_64_JUMP_SLO 0000000000000698 boo + 0
0000002009b0 000600000007 R_X86_64_JUMP_SLO 0000000000000000 __cxa_finalize + 0
下面可以看出,在load之后foo中所引用的boo的地址更新成了0x400560,即。
gdb ./main
(gdb) disassemble foo
Dump of assembler code for function foo:
0x00007ffff7bda6a3 : push %rbp
0x00007ffff7bda6a4 : mov %rsp,%rbp
0x00007ffff7bda6a7 : sub $0x10,%rsp
0x00007ffff7bda6ab : mov 0x2002c6(%rip),%rax # 0x7ffff7dda978
0x00007ffff7bda6b2 : mov %rax,-0x8(%rbp)
0x00007ffff7bda6b6 : mov $0x0,%eax
0x00007ffff7bda6bb : callq 0x7ffff7bda590
0x00007ffff7bda6c0 : mov $0x0,%eax
0x00007ffff7bda6c5 : leaveq
0x00007ffff7bda6c6 : retq
End of assembler dump.
(gdb) x /xg 0x7ffff7dda978
0x7ffff7dda978: 0x0000000000400560
與重定向相關的section .plt
重定向的入口代碼。對於每一個需要重定向的函數,都會類似下面的代碼。其中push的數值應該代表.rela.plt中的對應entry。
(gdb) disassemble 0x400560
Dump of assembler code for function boo@plt:
0x0000000000400560 : jmpq *0x2004f2(%rip) # 0x600a58
0x0000000000400566 : pushq $0x2
0x000000000040056b : jmpq 0x400530
.got
R_X86_64_GLOB_DAT重定向項所指向的需要修改的地址。用於重定向對於變量的引用。
.got.plt
每一項占8個字節。
第一項的數值是0x0000000000600848,即dynamic section的起始地址。
第二項的數值是0x00007ffff7ffe190,據說代表link_map的起始地址?loader用它進行relocation.
第三項的數值是0x00007ffff7def2d0,是loader的_dl_runtime_resolve函數起始地址。
(gdb) disassemble 0x00007ffff7def2d0
Dump of assembler code for function _dl_runtime_resolve:
之后的每一項對應一個函數的重定向地址。初始時,如boo@got.plt所示,該值指向下一條匯編指令。重定向完成后,該值指向真正的函數地址。
(gdb) x /8xg 0x600a30
0x600a30: 0x0000000000600848 0x00007ffff7ffe190
0x600a40: 0x00007ffff7def2d0 0x00007ffff784e920
附表 表一,main的section
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 0000000000400200 00000200
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 000000000040021c 0000021c
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 000000000040023c 0000023c
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400260 00000260
000000000000003c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002a0 000002a0
0000000000000120 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 00000000004003c0 000003c0
00000000000000b5 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400476 00000476
0000000000000018 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400490 00000490
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 00000000004004b0 000004b0
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 00000000004004c8 000004c8
0000000000000048 0000000000000018 A 5 12 8
[11] .init PROGBITS 0000000000400510 00000510
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000400530 00000530
0000000000000040 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000400570 00000570
0000000000000184 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 00000000004006f4 000006f4
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 0000000000400700 00000700
0000000000000004 0000000000000004 AM 0 0 4
[16] .eh_frame_hdr PROGBITS 0000000000400704 00000704
0000000000000034 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 0000000000400738 00000738
00000000000000f4 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000600830 00000830
0000000000000008 0000000000000000 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000600838 00000838
0000000000000008 0000000000000000 WA 0 0 8
[20] .jcr PROGBITS 0000000000600840 00000840
0000000000000008 0000000000000000 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000600848 00000848
00000000000001e0 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000600a28 00000a28
0000000000000008 0000000000000008 WA 0 0 8
[23] .got.plt PROGBITS 0000000000600a30 00000a30
0000000000000030 0000000000000008 WA 0 0 8
[24] .data PROGBITS 0000000000600a60 00000a60
0000000000000010 0000000000000000 WA 0 0 8
[25] .bss NOBITS 0000000000600a70 00000a70
0000000000000008 0000000000000000 WA 0 0 4
[26] .comment PROGBITS 0000000000000000 00000a70
000000000000004e 0000000000000001 MS 0 0 1
[27] .shstrtab STRTAB 0000000000000000 00000abe
0000000000000108 0000000000000000 0 0 1
[28] .symtab SYMTAB 0000000000000000 00001348
0000000000000648 0000000000000018 29 47 8
[29] .strtab STRTAB 0000000000000000 00001990
000000000000022f 0000000000000000 0 0 1
表二,main的重定向信息
注意.rela.plt中的第三行boo的重定向信息,其中offset是000000600a58,剛好在section .got.plt中。
$ readelf -r ./main
Relocation section '.rela.dyn' at offset 0x4b0 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000600a28 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
Relocation section '.rela.plt' at offset 0x4c8 contains 3 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000600a48 000200000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0
000000600a50 000300000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0
000000600a58 000800000007 R_X86_64_JUMP_SLO 0000000000400560 boo + 0
表三,libmlib.so的section(部分)
$ readelf -S ./libmlib.so
There are 27 section headers, starting at offset 0xad8:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.gnu.build-i NOTE 0000000000000190 00000190
0000000000000024 0000000000000000 A 0 0 4
[ 2] .gnu.hash GNU_HASH 00000000000001b8 000001b8
0000000000000040 0000000000000000 A 3 0 8
..........
[19] .got PROGBITS 0000000000200958 00000958
0000000000000030 0000000000000008 WA 0 0 8