i386 Linux下 ELF 动态链接分析 (一)

导读:
Aug 29
动态链接分析 (一)” href=”http://www.cs.virginia.edu/~wh5a/blog/2007/08/29/143_i386-linux%e4%b8%8b-elf-%e5%8a%a8%e6%80%81%e9%93%be%e6%8e%a5%e5%88%86%e6%9e%90-%ef%bc%88%e4%b8%80%ef%bc%89.html” rel=bookmark>i386 Linux下 ELF 动态链接分析 (一)
work Add comments

Ian Lance Taylor正在写连载文章介绍linkers。一直想了解dynamic linking的过程,于是正好就学习了一下。program loader、program linker和dynamic linker的具体工作过程暂不在讨论范围。

首先,随便写个小程序然后用objdump看一下:

$ objdump -d main.o00000000

:0: 8d 4c 24 04 lea 0×4(%esp),%ecx4: 83 e4 f0 and $0xfffffff0,%esp7: ff 71 fc pushl 0xfffffffc(%ecx)a: 55 push %ebpb: 89 e5 mov %esp,%ebpd: 51 push %ecxe: 83 ec 14 sub $0×14,%esp11: e8 fc ff ff ff call 12
16: 89 04 24 mov %eax,(%esp)19: e8 fc ff ff ff call 1a
1e: e8 fc ff ff ff call 1f
23: 89 44 24 04 mov %eax,0×4(%esp)27: c7 04 24 00 00 00 00 movl $0×0,(%esp)2e: e8 fc ff ff ff call 2f
33: 83 c4 14 add $0×14,%esp36: 59 pop %ecx37: 5d pop %ebp38: 8d 61 fc lea 0xfffffffc(%ecx),%esp3b: c3 ret

我们看到main call了一些函数,但地址都是12,1a这样的数字。这些数字表示的是本.text section中的offset,需要被linker patch。这些relocations信息可以用readelf -r查看:

$ readelf -r main.o

Relocation section ‘.rel.text’ at offset 0×388 contains 5 entries:Offset Info Type Sym.Value Sym. Name00000012 00000902 R_386_PC32 00000000 foo0000001a 00000a02 R_386_PC32 00000000 printf0000001f 00000b02 R_386_PC32 00000000 bar0000002a 00000501 R_386_32 00000000 .rodata0000002f 00000a02 R_386_PC32 00000000 printf

这些函数地址在link时会被patch成真正的地址(静态联入),或者在plt中的地址(动态联入)。我们readelf -r a.out还能看到打印出如下信息,这些信息我们在后面还会看到有所呼应:

$ readelf -r a.out

Relocation section ‘.rel.dyn’ at offset 0×34c contains 1 entries:Offset Info Type Sym.Value Sym. Name08049744 00000206 R_386_GLOB_DAT 00000000 __gmon_start__

Relocation section ‘.rel.plt’ at offset 0×354 contains 5 entries:Offset Info Type Sym.Value Sym. Name08049754 00000107 R_386_JUMP_SLOT 00000000 bar08049758 00000207 R_386_JUMP_SLOT 00000000 __gmon_start__0804975c 00000407 R_386_JUMP_SLOT 00000000 __libc_start_main08049760 00000507 R_386_JUMP_SLOT 00000000 foo08049764 00000607 R_386_JUMP_SLOT 00000000 printf

以动态连接方式产生的可执行文件会在.interp这个section中写入dynamic loader的路径。例如:

wh5a@power3 /tmp/dyn $ readelf -S a.out|grep interp[ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1wh5a@power3 /tmp/dyn $ gdb -q a.out(gdb) x/s 0×80481340×8048134: “/lib/ld-linux.so.2″

这样的话在程序被载入时,dynamic loader将会被调用以便装入程序依赖的动态库。

下面来看foo函数是如何被调用的:0×080484e5

: call 0×80483d4 我们前面提到从main中call的函数地址都需要被patch,这个过程被static linker做了第一步,使得call指令指向plt中。第二步的patch缺省时将会在动态时按需进行,也就是lazy symbol binding。这一行为可以通过环境变量LD_BIND_NOW=1进行修改,这样在debugging时会有些帮助。plt的基地址可以使用readelf -S看出来,got也是很重要的信息,在此一并列出:

[11] .plt PROGBITS 08048394 000394 000060 04 AX 0 0 4[21] .got PROGBITS 08049744 000744 000004 04 WA 0 0 4[22] .got.plt PROGBITS 08049748 000748 000020 04 WA 0 0 4

plt每个entry是16 bytes,got每个entry是4 bytes。

PLT0的信息比较特殊(这里把具体的地址列出以便理解):

push GOT[1] ; 0×804974cjmp GOT[2] ; *0×80497500×00000000 ; padding

这之后的plt表项对应于每个动态函数。它们的顺序与readelf -r列出的顺序相同。我们知道foo是第4个函数,也就是PLT[4],通过计算地址知道是0×80483d4,确实就是main函数call它的地址。其中内容为:

PLT4:080483d4 :80483d4: ff 25 60 97 04 08 jmp *0×8049760 ; GOT[6]80483da: 68 18 00 00 00 push $0×18 ; foo’s relocation offset80483df: e9 b0 ff ff ff jmp 8048394 ; PLT0

PLT entry的第一条指令跳转到GOT中,也就是说GOT起到了又一层indirection的作用。GOT的每个表项被初始化为指向到PLT entry的第二条指令。这里PLT4对应于GOT6(还记得readelf -r显示的信息么?找到0×8049760了吗?)是因为GOT[0..2]都有特殊的作用(GOT[0]似乎指向.dynamic section,存放的是给dynamic loader有用的一些信息 [5])。那么这个GOT的基址又是怎么来的呢?它对应于.got.plt这个section。.got section存放的应该是global variables,还有待继续研究。

初始状态下,foo尚未resolve,所以GOT尚未被dynamic linker patch。这样一来,GOT6使得0×80483da这条指令被执行。这条指令的作用是将foo所对应的offset压栈。接下来跳到PLT0继续执行。PLT0首先将GOT1中的内容(指向一个link_map结构)压栈,然后跳到GOT2继续执行。GOT[2]指向_dl_runtime_resolve函数。这个函数是由dynamic linker提供的,通过查看proc文件系统的maps文件也可以看出GOT[2]确实指向的是/lib/ld-2.6.so的地址空间。这个函数是很简单的一段汇编,用来建立必要的堆栈环境以便让_dl_fixup(源码在glibc/elf/dl-runtime.c)来完成真正的工作。前面我们压入了两个参数,一个是GOT[1]的内容,也就是一个link_map的地址,另一个是待解决symbol的offset,在这里是0×18。这之后的详细工作过程参见源码及[2]。这个函数最终将会patch GOT[6],使得下次PLT4(也就是foo)再次被调用时可以直接取到foo的真正地址。接下来,_dl_fixup返回foo的真正地址给_dl_runtime_resolve,它将返回值放到栈顶,xchg %eax,(%esp),然后直接一个ret就跳到了foo了。

最后再总结一下.got.plt的作用:

.got.plt (0×8049748)0×0804966c GOT[0], .dynamic0×00ba3650 GOT[1], the link map0×00b9a2b0 GOT[2], always jump here to resolve symbols. /lib/ld-linux.so.2 is loaded here.…0×080483da GOT[6], not resolved yet, so points right back to the instruction after the jmp

References:[1] Linkers part 4[2] ELF动态解析符号过程(修订版)[3] How to hijack the Global Offset Table with pointers for root shells[4] The ELF Object File Format: Introduction[5] The ELF Object File Format by Dissection[6] Before main() 分析

本文转自 http://www.cs.virginia.edu/~wh5a/blog/2007/08/29/143_i386-linux%E4%B8%8B-elf-%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5%E5%88%86%E6%9E%90-%EF%BC%88%E4%B8%80%EF%BC%89.html
莫找借口失败,只找理由成功。(不为失败找理由,要为成功找方法)

i386 Linux下 ELF 动态链接分析 (一)

相关文章:

你感兴趣的文章:

标签云: