PWN入门(四)——got表 / plt表 / 延迟绑定机制

got表
GOT表的定义

定义:在编译链接过程中,当程序需要调用外部函数或者访问外部变量时,GOT 表(Global Offset Table)就起到了重要的作用。它是一个数据结构,用于存储外部符号(函数或者变量)的绝对地址。在可执行文件和共享库中,GOT 表是实现动态链接的关键部分。

存储内容:GOT 表中存储了程序所引用的外部函数和变量的地址。例如,在一个程序中调用了标准库中的printf函数,在动态链接的情况下,printf函数的实际地址会被存储在 GOT 表的相应条目中。这些地址是在程序运行时,由动态链接器(如 Linux 系统中的ld - linux.so)进行填充和更新的。

GOT 表在动态链接中的工作原理

延迟绑定机制与 GOT 表:为了提高程序的启动速度,动态链接采用了延迟绑定的技术。当程序开始运行时,对于外部函数的调用,最初 GOT 表中的相应条目并不包含实际的函数地址,而是包含一个指向动态链接器中特定代码(如plt[0]模块)的跳转指令。当程序第一次调用某个外部函数时,会通过这个跳转指令进入动态链接器的代码,动态链接器会解析该函数的实际地址,并将其填充到 GOT 表的相应条目中。之后的函数调用就可以直接通过 GOT 表中的实际地址进行跳转,而不需要再次进入动态链接器进行解析,这种机制大大提高了程序后续调用外部函数的效率。

与 PLT表的配合:在动态链接过程中,GOT 表和 PLT 表紧密配合。PLT 表主要用于实现函数调用的跳转逻辑。当程序调用一个外部函数时,首先会跳转到 PLT 表中的相应条目,PLT 表中的代码会检查 GOT 表中该函数地址是否已经解析。如果已经解析,就直接跳转到 GOT 表中的实际函数地址;如果尚未解析,就会触发动态链接器进行解析操作,然后更新 GOT 表并完成函数调用。例如,对于一个简单的 hello_world 程序调用 printf 函数的情况,PLT 表中有一个条目用于 printf 函数的调用跳转,它会先查看 GOT 表中 printf 函数的地址状态,然后根据情况进行处理。

GOT 表在安全领域的应用和相关攻击方式

GOT 表劫持攻击:这是一种恶意攻击手段。攻击者通过缓冲区溢出等漏洞,修改 GOT 表中的函数地址。例如,在一个存在缓冲区溢出漏洞的程序中,攻击者可以将 GOT 表中 strcpy 函数的地址修改为恶意函数的地址。当程序正常调用 strcpy 函数时,实际上会跳转到恶意函数,从而执行攻击者的恶意代码,如获取系统权限、窃取数据等。

保护机制和防范措施:为了防止 GOT 表被劫持,现代操作系统和编译器采取了多种安全措施。一种常见的方法是采用地址空间布局随机化(ASLR)技术,使 GOT 表在内存中的位置随机化,增加攻击者定位和修改 GOT 表的难度。另外,一些编译器还可以在编译时进行代码加固,如添加栈保护(如金丝雀值)等机制,减少缓冲区溢出等漏洞出现的可能性,从而间接保护 GOT 表的安全。

plt表
PLT表的定义与功能

定义:PLT 表(Procedure Linkage Table)是在动态链接过程中使用的一个重要数据结构。它主要用于实现函数调用的跳转逻辑,特别是针对外部函数(即位于共享库中的函数)的调用。在程序编译时,每个调用外部函数的地方都会在 PLT 中有一个对应的条目。

功能:当程序调用一个外部函数时,首先会跳转到 PLT 表中的相应条目。PLT 表中的代码会负责检查 GOT 表中该函数地址是否已经被解析。如果已经解析,就直接跳转到 GOT 表中的实际函数地址;如果尚未解析,就会触发动态链接器进行解析操作,然后更新 GOT 表并完成函数调用。这种机制使得程序能够在运行时正确地链接和调用外部函数。

PLT 表的结构和工作原理

结构示例(以 32 位 ELF 文件为例):

在 32 位 ELF(Executable and Linkable Format)文件格式下,PLT 表中的每个条目通常由一系列指令组成。以一个简单的调用 printf 函数为例,PLT 表中对应的 printf 条目可能包含以下类型的指令:

首先是一条跳转指令,它会跳转到下一条指令所在地址加上一个偏移量。这个偏移量指向的是一个 “桩(stub)” 代码部分,这个部分主要用于处理 GOT 表中函数地址的检查和解析。

在 “桩” 代码部分,会有指令去检查 GOT 表中对应函数(如printf)的地址。如果地址为 0(表示尚未解析),就会调用动态链接器的某个函数(如_dl_runtime_resolve)来解析函数地址,并将解析后的地址存储到 GOT 表中相应位置。

最后,“桩” 代码会跳转到 GOT 表中存储的函数地址,完成函数调用。

与 GOT 表的交互工作原理:

程序启动时,GOT 表中外部函数的地址可能尚未被解析。当程序第一次调用某个外部函数(如 system 函数)时,控制流首先转到 PLT 表中对应的 system 条目。PLT 表中的代码会发现 GOT 表中 system 函数的地址尚未被解析(可能是 0 或者一个特殊的标记值),于是它会调用动态链接器来查找 system 函数在共享库中的实际地址,将这个实际地址填充到 GOT 表中对应的 system 条目中。之后,再次调用 system 函数时,PLT 表中的代码检查 GOT 表发现地址已经解析,就直接跳转到 GOT 表中的 system 函数实际地址进行调用。

PLT 表在动态链接过程中的重要性

实现延迟绑定:PLT 表是实现延迟绑定机制的关键部分。延迟绑定允许程序在运行时才解析外部函数的地址,而不是在程序启动时就全部解析。这大大提高了程序的启动速度,因为很多外部函数可能在程序运行过程中根本不会被调用。通过 PLT 表和 GOT 表的协同工作,只有当一个外部函数第一次被调用时,才会花费时间去解析它的实际地址,后续调用就可以快速地通过已经解析好的 GOT 表地址进行。

支持动态库的更新和替换:在软件的维护和更新过程中,可能会更新或替换共享库。PLT 表和 GOT 表的设计使得程序能够适应这种变化。只要共享库中的函数接口(如函数名、参数类型和返回值类型等)保持不变,即使共享库的内部实现或者内存位置发生了变化,程序通过 PLT 和 GOT 表仍然能够正确地调用这些函数。因为动态链接器可以根据新的共享库情况重新解析函数地址并更新 GOT 表,而 PLT 表的跳转逻辑可以确保程序能够正确地使用这些更新后的地址。

延迟绑定机制
延迟绑定的定义和基本原理

定义:延迟绑定(Lazy Binding)是一种在程序运行过程中动态链接共享库函数的技术。它的核心思想是推迟对外部函数(位于共享库中的函数)的地址解析,直到程序首次调用该函数时才进行解析。这样做的主要目的是为了提高程序的启动速度,因为如果在程序启动时就对所有可能用到的外部函数进行地址解析,会花费大量时间,而且很多函数可能在程序运行过程中根本不会被调用。

基本原理:

在支持延迟绑定的系统中,当程序被编译和链接时,对于外部函数的调用,会涉及到两个重要的数据结构:PLT 表和 GOT 表。

PLT 表的作用:程序中的每个外部函数调用在 PLT 表中都有一个对应的条目。当程序调用一个外部函数时,首先会跳转到 PLT 表中的相应条目。PLT 表中的代码主要负责检查 GOT 表中该函数的地址是否已经被解析。

GOT 表的作用:GOT 表用于存储外部函数的实际地址。在程序开始运行时,GOT 表中外部函数对应的条目可能并没有填充实际的函数地址,而是包含一个指向动态链接器中特定代码(如 PLT [0] 模块)的跳转指令。

首次调用的解析过程:当程序第一次调用某个外部函数时,PLT 表中的代码发现 GOT 表中的函数地址尚未被解析,就会触发动态链接器进行解析操作。动态链接器会在共享库中查找该函数的实际地址,然后将其填充到 GOT 表的相应条目中。之后,PLT 表中的代码就可以直接跳转到 GOT 表中已经解析好的函数地址进行调用。

延迟绑定的优势

性能优化方面:

启动速度提升:如前所述,程序启动时不需要解析所有可能用到的外部函数地址,减少了程序启动阶段的时间开销。对于大型软件系统,尤其是那些依赖大量共享库的应用程序,这种启动速度的提升非常显著。例如,一个复杂的图形处理软件可能会依赖多个图形库和数学库,如果在启动时就对这些库中的所有函数进行地址解析,可能会使启动时间变得很长。而采用延迟绑定,只有在真正需要调用这些库中的函数时才进行解析,大大缩短了启动时间。

内存资源利用更高效:在程序运行过程中,如果某些外部函数始终没有被调用,那么就不需要为它们分配内存来存储解析后的地址。这有助于节省内存资源,特别是对于内存有限的系统或者长时间运行的服务程序来说,这种内存资源的有效利用非常重要。

软件维护和更新方面:

便于共享库更新:延迟绑定机制使得程序对共享库的更新更加灵活。当共享库中的函数实现或者内存布局发生变化时,只要函数接口(如函数名、参数类型和返回值类型等)保持不变,程序在运行时通过动态链接器可以重新解析函数地址并更新 GOT 表。这样,软件开发者可以更容易地更新共享库,而不需要重新编译整个程序。

延迟绑定的潜在问题和应对措施

潜在问题:

首次调用延迟开销:虽然延迟绑定可以提高程序的启动速度,但第一次调用某个外部函数时,由于需要进行地址解析操作,会产生一定的延迟。对于一些对实时性要求极高的应用场景,如实时控制系统,这种首次调用的延迟可能会影响系统性能。

安全性问题:在某些情况下,延迟绑定可能会被攻击者利用。例如,攻击者可能通过缓冲区溢出等漏洞,在动态链接器解析函数地址并填充 GOT 表的过程中进行攻击,篡改函数地址,从而导致程序执行恶意代码。

应对措施:

针对首次调用延迟开销:可以根据应用程序的具体需求,对一些关键的、经常在启动后不久就会被调用的外部函数,采用预解析的方式。即在程序启动后的空闲时间或者初始化阶段,提前解析这些函数的地址,避免在关键时刻出现首次调用延迟。

针对安全性问题:采用多种安全机制来防范攻击。如地址空间布局随机化(ASLR),使程序的内存布局(包括 GOT 表的位置)随机化,增加攻击者预测和篡改函数地址的难度。还可以使用栈保护技术,如金丝雀(Canary)值,防止缓冲区溢出等漏洞的出现,间接保护延迟绑定过程的安全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值