ELF的GOT和PLT以及PIC

ELF的GOT和PLT以及PIC

Link:http://zhiwei.li/text/2009/04/elf%E7%9A%84got%E5%92%8Cplt%E4%BB%A5%E5%8F%8Apic/



全局偏移表(GOT)和过程链接表(PLT)

ELF 格式的共享库使用 PIC技术使代码和数据的引用与地址无关,程序可以被加载到地址空间的任意位置。PIC在代码中的跳转和分支指令不使用绝对地址。PICELF可执行映像的数据段中建立一个存放所有全局变量指针的全局偏移量表GOT

对于模块外部引用的全局变量和全局函数,用GOT 表的表项内容作为地址来间接寻址;对于本模块内的静态变量和静态函数,用GOT 表的首地址作为一个基准,用相对于该基准的偏移量来引用,因为不论程序被加载到何种地址空间,模块内的静态变量和静态函数与GOT 的距离是固定的,并且在链接阶段就可知晓其距离的大小。这样,PIC使用 GOT来引用变量和函数的绝对地址,把位置独立的引用重定向到绝对位置。

对于 PIC代码,代码段内不存在重定位项,实际的重定位项只是在数据段的GOT 表内。共享目标文件中的重定位类型有R_386_RELATIVER_386_GLOB_DATR_386_JMP_SLOT,用于在动态链接器加载映射共享库或者模块运行的时候对指针类型的静态数据、全局变量符号地址和全局函数符号地址进行重定位。

1.2 PLT
过程链接表用于把位置独立的函数调用重定向到绝对位置。通过PLT 动态链接的程序支持惰性绑定模式。每个动态链接的程序和共享库都有一个PLTPLT表的每一项都是一小段代码,对应于本运行模块要引用的一个全局函数。程序对某个函数的访问都被调整为对PLT 入口的访问。

每个 PLT 入口项对应一个GOT 项,执行函数实际上就是跳转到相应GOT 项存储的地址,该 GOT项初始值为 PLTn项中的push 指令地址(jmp 的下一条指令,所以第 1次跳转没有任何作用),待符号解析完成后存放符号的真正地址。动态链接器在装载映射共享库时在GOT 里设置 2 个特殊值:在GOT+4( GOT[1])设置动态库映射信息数据结构link_map地址;在 GOT+8(GOT[2])设置动态链接器符号解
析函数的地址_dl_runtime_resolve

PLT 的第 1 个入口PLT0 是一段访问动态链接器的特殊代码。程序对PLT 入口的第 1 次访问都转到了PLT0,最后跳入GOT[2]存储的地址执行符号解析函数。待完成符号解析后,将符号的实际地址存入相应的GOT 项,这样以后调用函数时可直接跳到实际的函数地址,不必再执行符号解析函数

操作系统运行程序时,首先将解释器程序即动态链接器ld.so映射到一个合适的地址,然后启动ld.sold.so先完成自己的初始化工作,再从可执行文件的动态库依赖表中指定的路径名查找所需要的库,将其加载映射到内存。

Linux用一个全局的库映射信息结构structlink_map链表来管理和控制所有动态库的加载,动态库的加载过程实际上是映射库文件到内存中,并填充库映射信息结构添加到链表中的过程。结构struct link_map描述共享目标文件的加载映射信息,是动态链接器在运行时内部使用的一个结构,通过它保持对已装载的库和库中符号的跟踪。
link_map使用双向链接中间件“l_next”和“l_prev”链接进程中所有加载的共享库。当动态链接器需要去查找符号的时候,可以向前或向后遍历这个链表,通过访问链表上的每一个库去搜索需要查找的符号。Link_map链表的入口由每个可执行映像的全局偏移表的第2 个入口(GOT[1])指向,查找符号时先从GOT[1]读取 link_map结点地址,然后沿着link-map结点进行搜索。

动态库的加载映射过程主要分 3步:
(1) 动态链接器调用__mmap 函数对动态库的所有PT_LOAD可加载段进行整体映射:

l_map_start=(ElfW(Addr))__mmap ((void *)0, maplength,prot,
MAP_COPY | MAP_FILE, fd, mapoff);

返回值 l_map_start是实际映射的虚拟地址,和段结构成员p_vaddr 指定的虚拟地址不一定相同,这对于位置无关代码不会产生影响。但是对于数据段和link_map 结构中其它相关的位置描述信息还要进行修正。共享库映射的内存位置关系如图1l_addr是实际映射地址和原来指定的映射地址的差值,用于其它位置信息的修正,即简单地将原来指定的虚拟地址加上l_addr 就可以得到实际加载的虚拟地址

(2)共享文件映射完毕,动态链接器处理共享库的PT_DYNAMIC动态段,将各项动态链接信息主要是哈希表、符号表、字符串表、重定位表、PLT重定位项表等地址填写到 link_mapl_info 数组结构中。l_infolink_map最重要的字段之一,几乎所有与动态链接管理相关的内容都与l_info数组有关。动态链接器还要加载处理当前共享库的所有依赖库。

(3)由于实际的映射地址和指定的虚拟地址有可能不同,因此还要对动态库及其依赖库进行重定位。设置动态库的第1个和第 2 GOT 表项:

Elf32_Addr *got =
(Elf32_Addr *)lmap->l_info[DT_PLTGOT].d_un.d_ptr;
got[1]=lmap;
got[2]=&_dl_runtime_resolve;

对动态库的所有重定位项进行重定位,在重定位项指定的偏移地址处加上修正值l_addr。动态项 DT_REL给出了重定位表的地址,DT_RELSZ给出重定位表项的数目。
映射完毕后,动态链接器调用共享库(包括所有相关的依赖库)自备的初始化函数进行初始化。

程序连接表(Procedure LinkageTable)可以使被感染的文件调用外部的函数。这要比修改LD_PRELOAD环境变量实现调用的重定向优越的多,首先不牵扯到环境变量的修改

程序连接表(PLT)

  在ELF文件中,全局偏移表(GlobalOffsetTable,GOT)能够把位置无关的地址定位到绝对地址,程序连接表也有类似的作用,它能够把位置无关的函数调用定向到绝对地址。连接编辑器(linkeditor)不能解决程序从一个可执行文件或者共享库目标到另外一个的执行转移。结果,连接编辑器只能把包含程序转移控制的一些入口安排到程序连接表(PLT)中。在systemV体系中,程序连接表位于共享正文中,但是它们使用私有全局偏移表(privateglobal offsettable)中的地址。动态连接器(例如:ld-2.2.2.so)会决定目标的绝对地址并且修改全局偏移表在内存中的影象。因而,动态连接器能够重定向这些入口,而勿需破坏程序正文的位置无关性和共享特性。可执行文件和共享目标文件有各自的程序连接表。

elf的动态连接库是内存位置无关的,就是说你可以把这个库加载到内存的任何位置都没有影响。这就叫做positionindependent。在编译内存位置无关的动态连接库时,要给编译器加上-fpic选项,让编译器产生的目标文件是内存位置无关的还会尽量减少对变量引用时使用绝对地址。把库编译成内存位置无关会带来一些花费,编译器会保留一个寄存器来指向全局偏移量表(global offset table(or GOT forshort)),这就会导致编译器在优化代码时少了一个寄存器可以使用,但是在最坏的情况下这种性能的减少只有3%,在其他情况下是大大小于3%的。

tags: ELF, GOT
postedin C/C++, 逆向工程,Linux by zhiwei



你可能感兴趣的:(ELF的GOT和PLT以及PIC)