延迟绑定机制详解
本文根据这篇文章跟踪调试,对延迟绑定的过程又有了新的感悟,写个文记录一下,建议看原文 https://www.anquanke.com/post/id/184099
lazy binding过程跟踪
针对动态链接会减速程序运行速度的现状,操作系统实现了延迟绑定(Lazy Binding)的技术:函数第一次被用到时才对函数进行绑定。
通过延迟绑定大大加快了程序的启动速度。而ELF 则使用了PLT(Procedure Linkage Table,过程链接表)的技术来实现延迟绑定。
在lazy binding中,got表项存储的东西
got: plt+6
got + 4: link_map
got + 8: _dl_runtime_resolve
_dl_runtime_resolve的源码:https://code.woboq.org/userspace/glibc/sysdeps/i386/dl-trampoline.S.html
可以看到_dl_runtime_resolve的核心在于call _dl_fixup
动态链接中最重要的结构应该是dynamic段,这个段里面保存了动态链接器所需要的基本信息。
比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。
strze不是,截图截手残了
是一个结构体数组,结构体的定义为:
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
extern Elf32_Dyn_DYNAMIC[];
Elf32_Dyn结构由一个类型值加上一个附加的数值或指针,对于不同的类型,后面附加的数值或者指针有着不同的含义。
下面给出和延迟绑定相关的类型值的定义。
d_tag类型 d_un的定义
#define DT_STRTAB 5 动态链接字符串表的地址,d_ptr表示.dynstr的地址 (Address of string table)
#define DT_SYMTAB 6 动态链接符号表的地址,d_ptr表示.dynsym的地址 (Address of symbol table)
#define DT_JMPREL 23 动态链接重定位表的地址,d_ptr表示.rel.plt的地址 (Address of PLT relocs)
#define DT_RELENT 19 单个重定位表项的大小,d_val表示单个重定位表项大小 (Size of one Rel reloc )
#define DT_SYMENT 11 单个符号表项的大小,d_val表示单个符号表项大小 (Size of one symbol table entry )
如上图所示,可以看到字符串表.dynstr的地址为0x804822c,符号表.dynsym地址为0x80481cc,其单个符号表项的大小为16,重定位表.rel.plt的地址为 0x80482d8,其单个重定位表项的大小为8
.rel.plt重定位表中包含了需要重定位的函数的信息,其也是一个结构体数组,结构体Elf32_Rel定义如下,其中r_offset表示got表地址,即动态解析函数后真正的函数地址需要填入的地方。
r_info由两部分构成,r_info>>8表示该函数对应在符号表.dynsym中的下标,r_info&0xff则表示重定位类型。
typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
我们通过readelf观察重定位表
┌─────(root)─────(/ctf/work/ctf-wiki/ret2dl/32)
└> $ readelf -r demo
Relocation section '.rel.plt' at offset 0x2d8 contains 3 entries:
Offset Info Type Sym.Value Sym. Name
0804a00c 00000107 R_386_JUMP_SLOT 00000000 read@GLIBC_2.0
0804a010 00000207 R_386_JUMP_SLOT 00000000 __stack_chk_fail@GLIBC_2.4
0804a014 00000407 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
可以看到重定位表.rel.plt为一个Elf32_Rel数组,demo程序中该数组包含三个元素,第一个是read的重定位表项Elf32_Rel结构体,第二个是__stack_chk_fail,第三个是__libc_start_main。
read的重定位表r_offset为0x0804a00c,为read的got地址,即在动态解析函数完成后,将read的函数地址填入到r_offset为0x0804a00c中。
r_info为0x00000107表示read函数的符号表为.dynsym数组中的0x00000107>>8(即0x1)个元素,它的类型为0x00000107&0xff(即0x7)对应为R_386_JUMP_SLOT类型。
接着我们去看符号表.dynsym节,它也是一个结构体Elf32_Sym数组,其结构体的定义如下:
typedef struct
{
Elf32_Word st_name; //符号名,是相对.dynstr起始的偏移
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info; //对于导入函数符号而言,它是0x12
unsigned char st_other;
Elf32_Section st_shndx;
}Elf32_Sym; //对于导入函数符号而言,其他字段都是0
其中st_name指向的是函数名称在.dynstr表中的偏移。在dynamic段中我们知道了符号表.dynsym地址为0x80481cc,查看它的值:
pwndbg> x/20wx 0x80481cc
0x80481cc: 0x00000000 0x00000000 0x00000000 0x00000000
0x80481dc: 0x0000002b 0x00000000 0x00000000 0x00000012
0x80481ec: 0x0000001a 0x00000000 0x00000000 0x00000012
0x80481fc: 0x00000042 0x00000000 0x00000000 0x00000020
0x804820c: 0x00000030 0x00000000 0x00000000 0x00000012
以及使用readelf -s查看符号表的内容:
从重定位表.rel.plt中,我们知道了read的r_info>>8为0x1,即read的符号表项对应的是.dynsym第二个元素,果然可以看到.dynsym第一个元素为read函数的Elf32_Sym结构体
可以看到它的st_name对应的是0x0000002b,即read字符串应该在.dynstr表偏移为0x2b的地方,由dynamic我们知道了.dynstr表的地址为地址为0x804822c
那么果然如此,0x804822c+0x2b的地方存储着”read“字符串
到这里似乎对read函数的解析过程有了一个简单的了解:
1,可以先通过dynamic段获取各个表的地址,包括有字符串表.dynstr的地址为0x804822c,
符号表.dynsym地址为0x80481cc,其单个符号表项的大小为16,
重定位表.rel.plt的地址为 0x80482d8,其单个重定位表项的大小为8。
2,read函数为.rel.plt表中的第一个元素,定位它的重定位表项,知道了read函数的r_offset为0x0804a00c,
以及它在符号表中的下标为0x000001,它的类型为0x7,R_386_JUMP_SLOT。
3,由0x000001知道了read函数的符号表是.dynsym第二个元素,获取到该结构体,
得到了它对应的st_name对应的是0x0000002b,即获取了read字符串应该在.dynstr表偏移为0x2b的地方。
4,最后调用函数解析匹配read字符串所对应的函数地址,将其填至r_offset为0x0804a00c,即read的got地址中。
回过头继续看_dl_runtime_resolve函数,其中的_dl_fixup的源码在下面这个文件里
延迟绑定技术总结
第一次call func@plt时
// plt表项内容
jump *(func@got) ---> got表项的值为plt+6的地址,即push n的地址
push n // 将需要延迟绑定的符号在重定位表中的index压栈
jmp PLT0
上面n为.rel.plt表中的偏移
PLT0的指令:
// PLT0指令
push *(got + 4) // 存储的是link_map的地址
jmp *(got + 8) // 存储的是_dl_runtime_resolve函数地址
_dl_runtime_resolve会调用_dl_fixup函数,其功能:
1. 从link_map获取字符串表.dynstr 符号表.dynsym 重定位表.rel.plt的地址
2.通过(第二个参数)偏移reloc_arg(push n)+.rel.plt的地址获取函数对应的重定位结构的位置,继而获取r_offset和r_info
3.根据.dynsym以及r_info>>8获取符号结构体,得到st_name (.dynstr + offset),即函数名相对于.dynstr的偏移量
4.拿到函数名后取libc中匹配函数名,找到相应函数并将地址填回got表,绑定完成