首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
7
6
GOT与PLT
发表于: 2026-2-5 16:40
2961
GOT与PLT
x0rrrrr
1
2026-2-5 16:40
2961
# GOT与PLT ## 一、前言 这两天在研究ELF文件结构,遇到这个got和plt的核心部分,故记录下来分享。 **GOT(`Global Offset Table`)全局偏移表**,**PLT(`Procedure Linkage Table`)过程链接表**。是实现**地址无关代码**与**延迟绑定**的灵魂。其核心在于"解耦":代码层通过**PLT**这一固定跳板发起调用,而运行时的真实函数地址则是由**GOT**这个表实时维护。 ## 二、GOT表 在动态链接的过程中,共享对象(`.so`等)**最终装载地址在编译时期是不确定的,**而是在装载时,装载器根据当前地址空间情况分配。而**共享对象**当中可能会有很多**绝对地址的引用**,如果共享对象被装载在了其他位置,这些绝对地址也需要更改,解决这个问题的方法就是:把静态链接中的重定位推迟到**模块装载完成之后**,这个时候目标i地址确定,就可以对所有绝对地址引用进行重定位了。 这个过程也叫做**装载时重定位**,和`windows`下的**基址重置**(`Rebasing`)相似。 ### 地址无关代码 **装载时重定位**时解决动态模块中有绝对地址引用的办法之一,但是有一个很大的缺点,就是指令部分无法在多个进程之间共享。这时候需要有一种更好的方法解决**共享对象指令中**对绝对地址的重定位问题。 这里的目的很简单,希望程序模块中共享的指令部分在装载时不需要因为装载地址的改变而改变。实现的基本想法就是将指令中那些**需要被修改的部分分离出来**,根据数据部分放在一起,这样指令部分就能够保持不变,而数据部分可以在每个进程中拥有一个副本,这种方法就是**地址无关代码**技术。 > 这段话读起来可能有点拗口,这里解析一下: > > 首先**共享库**,系统中可能有100个进程都在使用它。比如`libc.so`在物理内存中保留一份libc的指令代码,所有进程都映射这同一块物理内存,这样就能极大地节省内存空间。 > > 但是在**装载时重定位**破坏了这个机制: > > + 假设现在代码中有一条指令是:`mov eax, [0x1000]`,`0x1000`是一个绝对地址 > + **进程A:**将该模块加载到`0x0000`,那么这条指令就是对的 > + **进程B:**将该模块加载到`0x2000`,那么就需要修改为`mov eax, [0x3000]` > > **后果**就是,一旦进程B修改了这段代码指令,这段物理内存就变了,就无法跟进程A共享。操作系统不得不为进程B复制一份全新的代码页,这就是**指令部分无法在多个进程共享** 这里先来了解一下模块中的各种类型的**地址引用方式** #### 模块中地址引用方式 这里将共享对象模块中的地址引用按照是否为**跨模块**分为两类:**模块内部引用**和**模块外部引用**。而按照不同引用方式可分为:**指令引用**和**数据访问**。这样就有4种情况: 1. 模块内部的函数调用、跳转等 2. 模块内部的数据访问,如模块中定义的全局变量、静态变量 3. 模块外部的数据访问,比如定义在其他模块中的全局变量 4. 模块外部的函数调用、跳转 **使用示例:** ~~~cpp static int a; extern int b; extern void ext(); void bar() { a = 1; // --> 类型2,访问模块内部数据 b = 2; // --> 类型3, 访问模块外部数据 } void foo() { bar(); // --> 类型1,调用模块内部函数 ext(); // --> 类型4,调用模块外部函数 } ~~~ ##### 类型一 模块内部调用或跳转 这种类型是最简单的。被调用的函数与调用这都处于一个模块,他们**相对位置**是固定的。而`call`,`jmp`这些指令都是相对地址调用的,对于这种指令来说不需要重定位。例如下面这一段汇编:  这是在`foo`函数中调用`bar`函数,通过硬编码可以看到这个`call 0x8048344`的字节码是:`e8 e8 ff ff ff`,而后面这个`0xffffffe8`其实就是下一条指令到指定地址的偏移: ~~~bash 0xffffffe8(-24) = 0x8048344 - 0x804835C ~~~ 那么其实只要这个两个函数的相对位置不变,这条指令就是**地址无关的**。 这里还存在一个**全局符号介入**的问题,这里先不管 ##### 类型二 模块内部数据访问 这种类型很明显也不能够直接包含数据的绝对地址,那么唯一的方法就是**相对寻址**。一般来说,一个模块是若干个页的代码后面紧跟若干个页的数据。这些也的相对位置是固定的,那么只需要相对于**当前指令地址加上固定偏移量**就能访问模块内部数据了。`elf`当中使用了一个很巧妙的方式来获取当前指令位置: ~~~asm call get_pc_thunk.cx add ecx, xxxx mov [ecx], xxxx <get_pc_thunk.cx>: mov ecx, [esp] ret ~~~ 通过这么一个`get_pc_thunk`就能够获取到`add ecx, xxxx`的指令地址了。原理就是call会将下一条指令地址入栈,此时栈顶就是下一条指令地址,然后再将该值赋值给寄存器即可。 ##### 类型三 模块间数据访问 模块间的数据访问就比较麻烦了,因为模块间的数据访问目标地址需要等到装载时才能决定。例如上边例子的变量b,他被定义在其他模块中,并且加载时才能确定。要使得代码这个变量b**代码地址无关**,基本思想就是将**地址相关的部分**放到数据段中。`ELF`的做法就是在数据段当中建立一个指向这些变量的**指针数组**,也被称为**全局偏移表(`Global Offset Table`, GOT)**,当需要引用这些全局变量的时候,可以通过`GOT`表中对应的项**间接引用**  需要访问变量b的时候,程序先访问`got`表,拿到b地址之后,再访问目标地址。 在装载的过程中,链接器会查找每个变量所在的地址,然后填充`GOT`表的各个项,以确保每个指针指向正确的位置。而GOT表本身就是放在数据段的,每个进程都有独立的副本,所以就是**地址无关的。** ##### 类型四 模块间调用、跳转 对于模块间的调用和跳转也可以使用上面类型三的方法来解决。不同的是,got表项中存放的是目标函数地址。  ### 小结 通过对地址无关代码的了解从而引申出来了**GOT**表,这个表是为了解决动态链接过程中**模块外部的数据访问和跳转调用**问题的。 ## 三、延迟绑定(PLT) ### 动态链接问题 动态链接比静态链接灵活得多,但它是以牺牲一部分性能为代价的。而动态链接比静态链接慢的主要原因是动态链接下对与全局和静态的数据访问都要进行复杂GOT定位,然后间接寻址。对于模块的调用也要先定位GOT表,然后再间接跳转,这样一来程序运行速度就会降低。 另外一个减慢运行速度的原因是动态链接的连接工作,在程序开始时会进行一次链接工作,此时动态链接器会寻找并装载所需要的共享对象,然后进行地址重定位等工作。这些工作会减慢程序启动的时间。 这是影响动态链接性能的两个主要问题。接下来就来介绍一下优化动态链接性能的方式 ### 延迟绑定 在动态链接下,程序模块之间包含了大量的函数引用。但是在**程序的一次运行当中**,可能很多的函数都用不上,此时在启动时进行链接就变成了一种浪费。所以**ELF**采用了一种**延迟绑定**(`Lazy Binding`)的做法,基本思想就是当**函数第一次被使用到的时候才进行绑定**。这种做法可以大大加快程序的启动速度。 **ELF**使用**PLT(Procedure Linkage Table)**的方法来实现**延迟绑定**。 #### 绑定函数猜测 在开始详细介绍`PLT`之前,先从动态链接器的角度设想一下:当第一次调用某个**动态库**的`xxx`函数的时候,需要调用这么一个绑定函数来完成绑定工作,这里假设这个函数叫做**`lookup`**。那么`lookup`函数,至少需要知道这个绑定发生在**哪个模块**,**哪个函数**?那么就可以假设这个函数原型是:`lookup(module, function)`。在`Glibc`当中,这个函数真正的名字叫做:`_dl_runtime_resolve()` #### 具体实现 当我们调用某个外部函数的时候,通常是通过`GOT`相应的项进行简介跳转。**PLT**为了实现延迟绑定,在这个过程中又增加了一层**间接跳转**,通过一个叫做**PLT**项的结构来进行跳转。每个**外部函数**在plt中都有一个对应的项,这里随便举一个例:`test()`函数在`plt`中的项叫做`bar@plt`,理论上来说,每一个`plt`项的实现差不多都是这样的: ~~~asm dtest@plt: jmp [test@GOT] push n push moduleID jmp _dl_runtime_resolve ~~~ `test@plt`第一条指令就是通过`GOT`间接跳转的指令。为了实现延迟绑定,链接器在初始化阶段将`push n`的地址填入到`test@GOT`中,这个过程不需要链接符号,所以速度很快。 第二条指令`push n`,这个指令当中的`n`是`test`这个符号引用在`.rela.plt`或者`.rel.plt`中的下标。 第三条指令是`push moduleID`,这个`moduleID`是一个指向`link_map`结构的指针,`_dl_runtime_resolve`通过这个`link_map`可以获取到其他模块的基址。 最后就是调用`_dl_runtime_resolve`,`_dl_runtime_resolve`经过一系列操作之后将`test`真正的地址填入到`test@GOT`当中 一旦`test()`这个函数被解析完毕,再次调用`test@plt`时,第一条jmp指令就能跳转到真正的`test()`函数当中。 #### PLT实际结构 上面描述的时**PLT**的基本原理,PLT真实的实现比这个结构复杂一些,`ELF`将GOT拆分成两个表 `.got`和`.got.plt`。其中`.got`用来保存全局变量的引用地址,`.got.plt`用来保存函数引用的地址,也就是说,所有对于外部函数的引用全部被分离出来放到了`.got.plt`。 其中`.got.plt`前三项是有特殊含义的,如下: + **第一项:**`.dynamic`段的地址,这个段描述了本模块动态链接的相关信息 + **第二项:**本模块的`moduleID` + **第三项:**`_dl_runtime_resolve()` 其中第二项和第三项有动态链接器在装载共享模块的时候将他们初始化。在IDA中就是这样的:  接着就是`plt`项的结构,与上面的也不太一样。实际上,`push moduleID`和`jmp _dl_runtime_resolve`都是可以复用的,`elf`中将这块部分放到了`PLT[0]`当中,而这两个指令的位置就换成了`jmp PLT[0]`的位置: ~~~asm PLT0: push [GOT+4] jmp [GOT+8] ... test@plt: jmp [test@GOT] push n jmp PLT0 ~~~ ida反汇编如下:  ### 小结 通过上面的分析,我们可以画出绑定前和绑定后的两个结构图 **绑定前:**  **绑定后:**  ## 参考 <a href="elink@c11K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6B7M7o6x3I4x3g2)9J5k6h3N6A6N6r3S2#2j5W2)9J5k6h3W2G2i4K6u0r3j5g2)9J5k6s2m8J5L8$3N6J5j5h3#2E0k6i4u0Q4x3X3c8K6i4K6u0V1N6$3!0J5K9#2)9J5c8X3y4Z5j5i4m8@1k6i4u0Q4y4h3j5%4i4K6u0W2K9s2c8E0L8l9`.`.">第7章 动态链接 - 程序员的自我修养</a>
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于
2026-2-6 10:00 被x0rrrrr编辑 ,原因:
#基础理论
收藏
・
7
点赞
・
6
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
xianyuuuan
非常支持你的观点!
2026-4-1 17:32
我的小拇指啊
谢谢你的细致分析,受益匪浅!
2026-3-13 17:45
A0tem
这个讨论对我很有帮助,谢谢!
2026-2-10 09:52
lanoche
谢谢你的细致分析,受益匪浅!
2026-2-10 09:43
yeyeshun
+3
非常支持你的观点!
2026-2-6 08:46
git_47499test-look
你的分享对大家帮助很大,非常感谢!
2026-2-5 18:48
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
3
)
yeyeshun
雪 币:
1762
活跃值:
(4834)
能力值:
( LV7,RANK:140 )
在线值:
发帖
12
回帖
415
粉丝
18
关注
私信
yeyeshun
2
2
楼
简历 -> 建立
简介 -> 间接
盆里 -> 分离
2026-2-6 08:47
0
x0rrrrr
雪 币:
1964
活跃值:
(1471)
能力值:
( LV5,RANK:70 )
在线值:
发帖
7
回帖
14
粉丝
32
关注
私信
x0rrrrr
1
3
楼
yeyeshun
简历 -> 建立 简介 -> 间接 盆里 -> 分离
2026-2-6 10:00
0
taoying
雪 币:
200
活跃值:
(677)
能力值:
( LV2,RANK:10 )
在线值:
发帖
2
回帖
21
粉丝
3
关注
私信
taoying
4
楼
6
2026-3-9 09:33
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
x0rrrrr
1
7
发帖
14
回帖
70
RANK
关注
私信
他的文章
[原创]APP整体加固原理
42422
[原创]记一次银狐样本分析
16080
[原创]libmsaoaidsec.so Frida检测、反调与绕过实战
9488
[原创]某软件ollvm混淆登录参数分析
51958
整体加固Demo及加壳工具的编写
2510
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部