动态延迟绑定
执行过程:
-
call库函数首先会跳转到plt条目的第一条指令处
-
跳转got表:
-
- 如果是非第一次调用该函数,则got表中存储的是函数的实际地址,然后直接去执行函数了,就无下面的操作
- 如果是第一次调用,则存储的是函数的plt条目的第二条指令地址,然后就会执行以下步骤
-
将got表中偏移压入栈中,并跳转到解析地址的代码片段
-
解析完成后跳转去执行库函数并把实际地址写入got表
IDA 分析过程
plt表结构如上,
以puts函数为例,在ida中该函数指向的是plt表第一个值
jmp cs:off_601018
plt表第二个表项push 0
代表获取got表中偏移量为0的库函数,也就是第一个函数
plt表第三个表项为jmp sub_4003F0
跳转过去可以发现执行了两个指令
第一条push cs:qword_601008
将got表中ink_map
结构的地址压入栈中
ink_map中记载了动态链接库加载的一些信息
然后执行jmp cs:qword_601010
跳转到got表的第三个表项数据动态连接器地址
跳转过去的时候已经给了动态连接器两个参数:
- puts函数偏移
- ink_map地址
然后动态连接器会进行解析获取puts函数在内存中的地址,解析完成后跳转去执行库函数并把实际地址写入got表
原理:
函数在动态链接库中偏移量是固定的,但是动态链接库的基址不是固定的,函数地址=动态链接库基址+偏移
在内存中多个进程间共享动态链接库。
好处:用不上的函数是不会做解析的,提高效率,缩减程序运行时间
使用gdb调试
gdb test1
b main
r
got
此时指向plt+6
也就是push 0
的地址
n
n
got
当我们执行过一次call指令后
此时解析成了puts函数在整个内存中的实际地址
看一下puts函数地址
disass puts
此时地址是能够对应上的
通过plt调用和call调用栈结构的不同
直接覆盖函数返回地址为system函数的plt表的话,栈结构如下
相比较与call指令来说,我们可以更容易控制system执行完后的程序流,当执行到run函数的ret指令后,整个栈结构为
call指令自动将下面一个单元地址
call system
等于push $eip+1;jmp system@plt