之前做的一个验证实验,直观了解下延迟绑定重定位技术。
.plt/.got.plt
.plt存储的是过程连接表 (Procedure Linkege Table,PLT) 。包含了共享库函数地址解析的代码。每个 PLT entry 对应一个function的解析。
.got.plt存储着全局偏移表。每个 entry 存储着一个函数的实际地址,其中前3个 entry 是特殊的。
- GOT[0]:存放了指向可执行文件动态段的地址
- GOT[1]:存放link_map结构的地址
- GOT[2]:存放了指向动态链接器_dl_runtime_resolve()函数的地址
示例分析
.plt和.got.plt共同构成了共享库重定位延迟绑定技术,实现了对共享库的动态解析。这里以调用write()函数为例1
2
3
4
5
6
7...
write(STDOUT_FILENO,"Hello,World\n",13);
write(STDOUT_FILENO,"Second run\n",12);
...
#compiler
gcc -z norelro -no-pie -fno-stack-protector -g -o test_plt_got test_plt_got.c
这个程序会连续两次调用write()函数。
查看 .plt
1 | $objdump -d -j .plt ./test_plt_got |
查看 .got.plt
*0x80497b8存储的就是write()的.got.plt entry 地址。1
2
3
4
5
6
7
8
9
10$objdump -R ./test_plt_got
./test_plt_got: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
080497a0 R_386_GLOB_DAT __gmon_start__
080497b0 R_386_JUMP_SLOT read@GLIBC_2.0
080497b4 R_386_JUMP_SLOT __libc_start_main@GLIBC_2.0
080497b8 R_386_JUMP_SLOT write@GLIBC_2.0
运行过程解析
peda-gdb 调试,b write@plt 下断点,r运行,解析过程如下:
1.程序运行,第一次调用write()函数,调用write@plt() \ref{fig:plt1_PNG}
2.PLT 代码做一次到 GOT 中地址的间接跳转:jmp *0x80497b8 , 即 write@got 地址为0x80497b8
x 0x80497b8,可以发现存储的数据为0x8048306,也就是write@plt的第二行指令
3.这样就又回到了 PLT 代码,执行push 0x10
4.执行 PLT 第三行代码:jmp 0x80482d0,跳转到 PLT[0]
查看 PLT[0] 前两行涉及到的指针,查看其存储的数据分别为:0xb7fff920;0xb7fee2f0
5.PLT[0] 第一行指令 push GOT[1] 的地址,该地址中存放了指向link_map的偏移地址
6.PLT[0] 第二行指令跳转到 GOT[2] 存放的地址,该地址指向了动态链接器的 _dl_runtime_resolve() 函数
_dl_runtime_resolve() 会将write()函数真正的内存地址加到 GOT entry 中
7.第二次调用write()函数时,可以发现 write@plt 将直接跳转到 glibc 中的 write() 函数地址0xb7ead760
参考
- Learning Linux Binary Analysis.Ryan O’Neill