【栈溢出】动态链接和延迟绑定
About GOT and PLT
PIC
PIC(Position Independent Code)是为了重定位动态链接库的symbols(库函数)。
现代操作系统不允许修改代码段,只能修改数据段,而使用了动态链接库后,函数的地址只有在执行时才能确定,所以程序内调用的库中的函数在编译时不知道。所以,编译时将函数调用返回.data段,而包含PIC的程序在运行时需要更改.data段的GOT和PLT来重定位全局变量。
GOT
GOT(Global Offset Table),也就是GOT表为每个全局变量保存了入口地址,在调用全局变量时,会直接调用对应GOT表条目中保存的地址,而不调用绝对地址
PLT
PLT(Procedural Linkage Table)过程链接表,为每个全局变量保存了一段代码。第一次调用一个函数会调用形如 function@PLT 的函数,这就是调到了函数对应的PLT表的开头执行
Something about .dynamic
ELF的.dynamic section 里包含了和重定位有关的很多信息
![img](lazy binding in detail/clipboard-1564454952382.png)
GOT表分为两部分,**.got和 .got.plt,前一个保存全局变量引用位置(模块外部引用的全局变量也和got表有关),后一个保存函数引用位置**
我们通常说的GOT指的是.got.plt
GOT表的起始地址如下查询:
1 | readelf -d test | grep GOT |
GOT表的前三项有特殊意义
![img](lazy binding in detail/clipboard-1564454998202.png)
第一项保存 .dynamic 段的地址
第二项 link_map的地址
第三个是_dl_runtime_resolve函数的地址
![img](lazy binding in detail/clipboard-1564455010461.png)
![img](lazy binding in detail/clipboard-1564455025599.png)
第四项开始,就是函数的GOT表了。
第四项是read函数。
![img](lazy binding in detail/clipboard-1564455035519.png)
readelf -r显示如下
![img](lazy binding in detail/clipboard-1564455041635.png)
包含Sym.Value和Sym.Name两个字段,这些信息是通过 symbol index字段找到的(RLF32_R_SYM(r_info)=1)
具体地,.dynamic section中的 SYMTAB,即.dynsym section,保存的是相关的符号信息。每一条符号信息的大小在SYMENT中体现,是16Byte,通过 readelf -s来查看。
![img](lazy binding in detail/clipboard-1564455049303.png)
PLTRELSZ指定了.rel.plt的大小,RELENT指定每一项的大小,PLTREL指定条目类型为REL,JMPREL对应.rel.plt地址,保存了重定位表,保存的是结构体信息。(**)
![img](lazy binding in detail/clipboard-1564455063083.png)
REL的数据结构为:
1 | typedef uint32_t Elf32_Addr; |
r_offset就是对应函数的GOT表地址,我们来看.rel.plt的第一项和第二项
r_info,指明重定位所作用的符号表索引和重定位的类型,这两部分信息存在同一个4字节中,使用下面的两个宏定义来转换得到符号表索引,和重定位类型。
![img](lazy binding in detail/clipboard-1564455107384.png)
(目前为止知道:结构体REL里存储的是函数GOT表地址,和类型信息。结构体REL是.rel.plt中每一项的存储单位)
再看:
![img](lazy binding in detail/clipboard-1564455113559.png)
上面两条和read,alarm对应。
0x804a00c和0x804a010分别是read和alarm函数的got表地址。
How ELF Relocation Works
在第一次 call 0x804a00c <read@plt> 时会调到PLT段中,第一句话会跳到GOT条目指向的地址。
![img](lazy binding in detail/clipboard-1564455149619.png)
首先,push 0
,(push reloc_offset
,我们调用的是read,而read在.rel.plt的偏移是0,故push0.如果我们要调用的是alarm,就会push 0x8
)
然后,push link_map
,(0x804a004,GOT表的第二项)
最后,jmp [0x804a008]
(GOT表第三项,保存_dl_runtime_resolve函数的地址)
_dl_runtime_resolve函数的工作如下:
根据reloc_offset找到.rel.plt段中的结构体
1
Elf32_Rel * p = JMPREL + rel_offset;
![img](lazy binding in detail/clipboard-1564455216366.png)
根据ELF32_R_SYM(r_info)找到.dynsym中对应的结构体
1
Elf32_Sym *sym = SYMTAB[ELF32_R_SYM(p->r_info)]
SYMTAB保存数组的地址,SYNENT标记每个结构体的大小。
结构体为:
1
2
3
4
5
6
7
8typedef struct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;
- 根据sym->stname = 0x29(这其实是一个偏移量),在.dynstr中也就是STRTAB找到函数对应的字符串,找到函数对应的地址,再填入GOT表对应的位置,跳到函数起始地址执行。