版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处连接和本声明。
本文连接:https://blog.csdn.net/qq_18661257/article/details/54694748
GOTGOT表和PLTPLT表在程序中的做用很是巨大,接下来的讲解但愿你们能够仔细看看
咱们用一个很是简单的例子来说解,代码以下:
图1
而后咱们编译
咱们直接gdb./a.outgdb./a.out来进行反编译处理,而后经过disasmaindisasmain查看mainmain函数中的反编译代码以下:
图3
咱们能够观察到gets@pltgets@plt和puts@pltputs@plt这两个函数,为何后面加了个@plt@plt,由于这个为PLTPLT表中的数据的地址。那为何反编译中的代码地址为PLTPLT表中的地址呢。
缘由
为了更好的用户体验和内存CPUCPU的利用率,程序编译时会采用两种表进行辅助,一个为PLTPLT表,一个为GOTGOT表,PLTPLT表能够称为内部函数表,GOTGOT表为全局函数表(也能够说是动态函数表这是我的自称),这两个表是相对应的,什么叫作相对应呢,PLTPLT表中的数据就是GOTGOT表中的一个地址,能够理解为必定是一一对应的,以下图:
图44
PLTPLT表中的每一项的数据内容都是对应的GOTGOT表中一项的地址这个是固定不变的,到这里你们也知道了PLTPLT表中的数据根本不是函数的真实地址,而是GOTGOT表项的地址,好坑啊。
其实在你们进入带有@plt@plt标志的函数时,这个函数其实就是个过渡做用,由于GOTGOT表项中的数据才是函数最终的地址,而PLTPLT表中的数据又是GOTGOT表项的地址,咱们就能够经过PLTPLT表跳转到GOTGOT表来获得函数真正的地址。
那问题来了,这个@plt@plt函数时怎么来的,这个函数是编译系统本身加的,你们能够经过disas gets看看里面的代码,以下图:
图55
你们能够发现,这个函数只有三行代码,第一行跳转,第二行压栈,第三行又是跳转,解释:
第一行跳转,它的做用是经过PLTPLT表跳转到GOTGOT表,而在第一次运行某一个函数以前,这个函数PLTPLT表对应的GOTGOT表中的数据为@plt@plt函数中第二行指令的地址,针对图中来讲步骤以下:
jmpjmp指令跳转到GOTGOT表
GOTGOT表中的数据为0x4004860x400486
跳转到指令地址为0x4004860x400486
执行push 0x3#这个为在GOTGOT中的下标序号
在执行jmp 0x400440
而0x4004400x400440为PLT[0]PLT[0]的地址
PLT[0]PLT[0]的指令会进入动态连接器的入口
执行一个函数将真正的函数地址覆盖到GOTGOT表中
这里咱们要提几个问题:
1. PLT[0]PLT[0]处到底作了什么,按照咱们以前的思路它不是应该跳转到GOT[0]GOT[0]吗?
2. 为何中间要进行pushpush压栈操做?
3. 压入的序号为何为0x30x3,不是最开始应该为0x00x0吗?
解决问题
问题1
看下图:
图66
咱们尝试着查看0x4004400x400440地址的数据内容发现一个问题,从0x400440−0x4004500x400440−0x400450之间的数据彻底不知道是什么,而真正的PLT[x]PLT[x]中的数据是从0x4004500x400450开始的,从这里才有了@plt@plt为后缀的地址,可是咱们disas gets看代码的时候是从0x4004400x400440开始的,咱们能够经过x /5i 0x400440查看0x4004400x400440处的代码,以下:
图77
咱们看到了后面的#以后又一个1616进制数,一看即可以知道是GOTGOT表的地址,为何这么确定呢,由于咱们能够经过objdump -R ./a.out查看一个程序的GOTGOT函数的地址,以下图:
图88
这里都是些GOTGOT地址,咱们发现都是0x601...0x601...这些,因此能够判定图77中的也是GOTGOT地址,那么咱们能够猜测出,在正式存储一个函数的GOTGOT地址前,咱们的PLTPLT表前面有一项进行一些处理,咱们暂且不具体深刻剖析这些代码有什么用,可是咱们能够确定puts@pltputs@plt前面那1616个字节也算是PLTPLT表中的内容,这其实就是咱们的PLT[0]PLT[0],正如咱们以前问题提到的那样,咱们的PLT[0]PLT[0]根本没有跳转到GOT[0]GOT[0],它不像咱们的PLT[1]PLT[1]这些存储的是GOTGOT表项的地址,它是一些代码指令,换句话说,PLT[0]PLT[0]是一个函数,这个函数的做用是经过GOT[1]GOT[1]和GOT[2]GOT[2]来正确绑定一个函数的正式地址到GOTGOT表中来。
咦,这里问题好像又产生了,原本按照最开始的思路PLT[1]PLT[1]也是跳转到GOT[1]GOT[1]的,GOT[2]GOT[2]同理,可是这两个数据好像被PLT[0]PLT[0]利用了,同时GOT[0]GOT[0]好像消失了,这里GOT[0]GOT[0]暂且不说它的做用是什么,针对GOT[1]GOT[1]和GOT[2]GOT[2]被PLT[0]PLT[0]利用,因此咱们程序中真实状况实际上是从PLT[1]PLT[1]到GOT[3]GOT[3],PLT[2]PLT[2]到GOT[4]GOT[4],因此咱们推翻了咱们的图44,创建一张新的处理表
图99
而plt[0]plt[0]代码作的事情则是:因为GOT[2]GOT[2]中存储的是动态连接器的入口地址,因此经过GOT[1]GOT[1]中的数据做为参数,跳转到GOT[2]GOT[2]所对应的函数入口地址,这个动态连接器会将一个函数的真正地址绑定到相应的GOT[x]GOT[x]中。
这就是PLTPLT表和GOTGOT表,总而言之,咱们调用一个函数的时候有两种方法,一个是经过PLTPLT表调用,一个则是经过GOTGOT表调用,由于PLTPLT表最终也是跳转GOTGOT表,GOTGOT表中则是一个函数真正的地址,这里须要注意的是,在一个函数没有运行一次以前,GOTGOT表中的数据为@plt@plt函数中下一条指令的地址,图55有说。
问题2
中间进行的压栈是为了肯定PLTPLT对应的GOTGOT表项,便是PLT[1]−>GOT[3]PLT[1]−>GOT[3],0x30x3就是GOTGOT的下标33,也就是说压栈后咱们跳转到PLT[0]PLT[0],接着PLT[0]PLT[0]中的指令会经过此次压栈的序号来肯定操做的GOTGOT表项为多少
问题3
好像都在第一个问题都已经解决了,这里压入0x30x3的缘由是由于,咱们的GOT[0]GOT[0],GOT[1]GOT[1],GOT[2]GOT[2]都有额外用处。要从GOT[3]GOT[3]开始
————————————————
版权声明:本文为CSDN博主「77458」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处连接及本声明。
原文连接:https://blog.csdn.net/qq_18661257/article/details/54694748函数