透过深入分析延迟绑定了解 ret_2_dl_resolve

2023-10-11 11:59

本文主要是介绍透过深入分析延迟绑定了解 ret_2_dl_resolve,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0x00 前言

  本文主要分析了在延迟绑定中,调用某函数之后如何找到正确的地址。文章中深入的分析了这个过程,并且分析完之后针对该链接介绍了一些攻击手法和程序所作的一些保护。

0x01 基础知识

一、动态链接

  在动态链接方式实现以前,普遍采用静态链接的方式来生成可执行文件。 如果一个程序使用了外部的库函数,那么整个库都会被直接编译到可执行文件中。ELF 支持动态链接,这在处理共享库的时候就会非常高效。 当一个程序被加载进内存时,动态链接器会把需要的共享库加载并绑定到该进程的地址空间中。随后在调用某个函数时,对该函数地址进行解析,以达到对该函数调用的目的。

二、两个表

1.PLT表(Procedure Linkage Table)

(1)简介: 过程链接表,在程序中以 .plt 节表示,该表处于代码段,每一个表项表示了一个与要重定位的函数相关的若干条指令,每个表项长度为 16 个字节,存储的是用于做延迟绑定的代码。
(2)结构简介:

PLT[0]  --> 与每个函数第一次链接相关指令
例:
0x4004c0:
0x4004c0:  ff 35 42 0b 20 00    	push   QWORD PTR [rip+0x200b42]      // push [GOT[1]] 
0x4004c6:  ff 25 44 0b 20 00    	jmp    QWORD PTR [rip+0x200b44]      // jmp  [GOT[2]] 
0x4004cc:  0f 1f 40 00          	nop    DWORD PTR [rax+0x0]
即:   第一条指令为 push 一个值,该值为 GOT[1] 处存放的地址,第二条指令为 jmp 到一个地址执行,该值为 GOT[2] 处存放的地址PLT[1]  --> 某个函数链接时所需要的指令,与 got 表一一对应
例:
0x4004d0 <__stack_chk_fail@plt>:
0x4004d0:  ff 25 42 0b 20 00    	jmp    QWORD PTR [rip+0x200b42]  // jmp GOT[3]   
0x4004d6:  68 00 00 00 00       	push   0x0                       // push reloc_arg
0x4004db:  e9 e0 ff ff ff       	jmp    0x4004c0 <_init+0x20>     // jmp PLT[0]
即:第一条指令为: jmp 到一个地址执行,该地址为对应 GOT 表项处存放的地址,在下文中会具体讨论这种结构第二条指令为: push 一个值,该值作用在下文提到第三个指令为: jmp 一个地址执行,其实该地址就是上边提到的 PLT[0] 的地址,也就是说接下来要执行 PLT[0] 中保存的两条指令...
2.GOT表(Global Offset Table)

(1)简介: 全局偏移表,在程序中以 .got.plt 表示,该表处于数据段,每一个表项存储的都是一个地址,每个表项长度是当前程序的对应需要寻址长度(32位程序:4字节,64位程序:8字节)。d_tag = DT_PLTGOT
(2)结构简介:

GOT[0]  --> 此处存放的是 .dynamic 的地址;该节()的作用会在下文讨论
GOT[1]  --> 此处存放的是 link_map 的地址;该结构也会在下文讨论 
GOT[2]  --> 此处存放的是 dl_runtime_resolve 函数的地址
GOT[3]  --> 与 PLT[1] 对应,存放的是与该表项 (PLT[1]) 要解析的函数相关地址,由于延迟绑定的原因,开始未调用对应函数时该项存的是 PLT[1] 中第二条指令的地址,当进行完一次延迟绑定之后存放的才是所要解析的函数的真实地址
GOT[4]  --> 与 PLT[2] 对应,所以存放的是与 PLT[2] 所解析的函数相关的地址...
3.两个表之间的关系
GOT[0]: .dynamic 地址                    PLT[0]: 与每个函数第一次链接相关指令
GOT[1]: link_map 地址
GOT[2]: dl_runtime_resolve 函数地址       
GOT[3]  -->  PLT[1]    // 一一对应
GOT[4]  -->  PLT[2]    // 相互协同,作用于一个函数
GOT[5]  -->  PLT[3]    // 一个保存的是该函数所需要的延迟绑定的指令
GOT[6]  -->  PLT[4]    // 一个是保存个该函数链接所需要的地址.             ..             ..             .

三、一个段(节)三个节

  在下面只对一些接下来要用到的结构体成员做一些中文解释。

1. .dynmic

(1)介绍:
  因为在加载过程中,.dynmic 节整个以一个段的形式加载进内存,所以说在程序中的 .dynmic 节也就是运行后的 .dynmic 段。该段主要与动态链接的整个过程有关,所以保存的是与动态链接相关信息,此处主要用于寻找与动态链接相关的其他节( .dynsym .dynstr .rela.plt 等节)。该段保存了许多 Elf64_Dyn 结构,该数据结构保存了一些其他节的信息。下面展示该段所保存的数据结构。p_type = PT_DYNAMIC(值为 0x2)的段。

(2)结构:

// 该结构都有 64 位程序和 32 位程序的区别,不过大致结构相似,此处只讨论 64 位程序中的
// /usr/include/elf.htypedef struct{Elf64_Sxword  d_tag;                  /* Dynamic entry type */// d_tag 识别该结构体表示的哪一个节,通过以此字段不同来寻找不同的节union{Elf64_Xword d_val;                /* Integer value */// 对应节的地址,用于存储该结构体表示下的节所在的地址Elf64_Addr d_ptr;                 /* Address value */// 一般于上一个字段表示的值相同,所以笔者现在并不了解他们的区别} d_un;} Elf64_Dyn;
2. .dynsym

(1)介绍:
  动态符号表,存储着在动态链接中所需要的每个函数所对应的符号信息,每个结构体分别对应一个符号 (函数) 。结构体数组。 d_tag = DT_SYMTAB(值为 0x6) 的节。
(2)结构:

 typedef struct{Elf64_Word    st_name;          /* Symbol name (string tbl index) */// 保存着该函数函数名在 .dynstr 中的偏移,可以结合 .dynstr 找到准确函数名。unsigned char st_info;          /* Symbol type and binding */unsigned char st_other;         /* Symbol visibility */Elf64_Section st_shndx;         /* Section index */Elf64_Addr    st_value;         /* Symbol value */// 如果这个符号被导出,则存有这个导出函数的虚拟地址,否则为NULL.  Elf64_Xword   st_size;          /* Symbol size */} Elf64_Sym;
3. .dynstr

(1)介绍:
  动态字符串表,表中存放了一系列字符串,这些字符串代表了符号的名称,在此处可以看成函数名,以空字符作为终止符。 该结构是一个字符串数组。d_tag = DT_STRTAB(值为 0x5) 的节。
(2)结构:
  一个字符串数组

4. .rel.plt (.rela.plt)

(1)介绍:
  重定位节,保存了重定位相关的信息,这些信息描述了如何在链接或者运行时,对 ELF 目标文件的某部分内容或者进程镜像进行补充或修改。每个结构体也与某一个重定位的函数相关。结构体数组。d_tag = DT_REL(值为 0x11) / d_tag = DT_RELA(值为 0x7) 的节。
(2)结构:

typedef struct
{Elf64_Addr    r_offset;            /* Address */// 此处表示的是解析完的函数真实地址存放的位置,// 即对应解析函数的 GOT 表项地址Elf64_Xword   r_info;              /* Relocation type and symbol index */// 该结构主要用到高某位,表示索引,低位表示类型// 例如:0x10000007 此处 1 表示索引,7 代表类型,主要用到 1 值,还记得上边在 PLT 中的指令嘛?//每一个表项的第二条指令, PUSH 了一个索引,所 PUSH 的索引与此相关,//也就是通过 PLT 中 PUSH 的索引找到当时解析的函数对应的此结构体的 
} Elf64_Rel;//与上一结构体类似,只是不同编译环境下产生的不同结构,作用相同,就不再次讨论typedef struct{Elf32_Addr    r_offset;            /* Address */Elf32_Word    r_info;              /* Relocation type and symbol index */Elf32_Sword   r_addend;            /* Addend */} Elf32_Rela;

四、扩充结构体(在 Full RELRO 用到)

  本节讨论的是在 Full RELRO 攻击中用到的结构,所以如果不打算研究该攻击手法可以跳过该节。d_tag = DT_DEBUG。

struct r_debug{  //由于并没有找到该结构体的定义,所以没有声明类型r_versionr_map        //指向 link_mapr_brkr_stater_ldbase
}

五、link_map结构

1.简介:

  保存着 Binary 里面所有信息的一个结构体,该结构体很大,内容丰富。

2.主要字段:
l_next:链接着该程序所有用到的 libary 上边提到的 GOT[1] 中保存的地址是第一层 link_map 中所表示的 libary,此时是指向的程序本身,不过可以用 l_next 结构寻找下一层表示的 libary,以此来遍历程序中所用到的 libary,并利用下边所提到的字段找到该层 libary 的名字、基地址、以及所有的 section 等信息。 
l_name:表示 libary 的名字
l_addr:表示 libary 的基地址
l_info[x]:指向该 libary 下的 .dynamic。 l_info[1] 指向 d_tag = 1 时所表示的 section ,所以可以改变 x 的值找到每个相关 section 的地址。在链接过程中 binary 中的 section 地址,以及 libary 中的地址都是通过此方法确定的。

0x02 链接过程

一、概括描述

  完成延迟绑定的函数主要是 dl_runtime__resolve(link_map_obj, reloc_arg) ,该函数的第一个参数是一个 link_map 结构,第二个参数是一个重定位参数,即运行 PLT 中的代码时 PUSH 进栈中的参数。该函数主要是调用一个 dl_fixup(link_map_obj, reloc_arg) 完成了主要功能。参数一的主要作用是:获得重定位函数所在了 libary 的基地址,以及获取在 libary 中寻找需要定位函数时所需要的 Section (.dynstr .dynsym 等)。第二个函数主要是确定需要解析的函数名,以及解析完之后写回的地址。
  该过程可以先大概理解为,dl_fixup 函数通过 reloc_arg 参数确定当前正在解析的函数名。之后,拿着这个函数名,再利用 link_map 结构找到 libary 中的 .dynsym .dynstr 。利用 .dynsym .dynstr 进行匹配。若匹配成功,则从 .dynsym 中获取该函数的函数地址。

// 上边的详细过程
reloc_arg   -->   函数名 A利用 link_map -->  l_info[x] 通过改变 x 的值,确定 .dynsym .dynstr 
再用 .dynsym 与 .dynstr  对整个动态符号表 .dynstym 进行遍历,去匹配函数名 A 
若 某一个 Elf64_Sym(符号) 的 st_name + .dynstr ==  A
则 该 Elf64_Sym 表示的符号即为函数 A//  整个过程可以这样理解,不过真实情况使用的 Hash 方法去寻找的这个 Elf64_Sym(符号)

二、具体过程

1.调用某个函数后进入该函数的 PLT[x] ,在 PLT[x] 中 push 一个参数 reloc_arg 。

通过参数确定作用函数
【问题 1】通过这个 reloc_arg 可以干什么?
【答案 1】拿到这个 reloc_arg 后,链接器会通过该值找到对应函数的 Elf_Rel 结构,通过该结构的 r_info 变量中的偏移量找到对应函数的 Elf_Sym 结构,然后再通过 Elf_Sym 结构的 st_name 结合之前已经确定的 .dynstr 地址,通过 st_name + .dynstr 获得对应函数的函数名。这就是拿到 reloc_arg 参数后链接器获得的信息,即知道了本次链接中的函数的函数名。(注:此处用到的 binary 中的 Elf_Rel Elf_Sym .dynstr 等地址都是通过 link_map->l_info[x] 的方式寻找的。)

2.在链接过程中 PLT[0] 会 push dl_runtime_resolve 函数的第二个参数 link_map。

【问题 2】通过 link_map 我们能获得什么?
【答案 2】拿到这个变量后链接器会获得所要解析的函数的函数库(通过 link_map 的 l_next 字段),然后拿到这个外部库之后 link_map 的 l_addr 字段会记录该库的基地址,然后链接器通过 new_hash 函数求出要链接函数的 hash(new_hash(st_name + .dynstr)),然后通过该 hash 和之前的保存值进行匹配,如果匹配上就获得了该函数在外部库的 Elf64_Sym 结构,然后通过该结构的 st_value 获取该函数在外部库里面的偏移,最后通过 st_value + l_addr 获取该函数的真实地址,最后通过 Elf64_Rel 的 r_offset 定位该函数在 GOT 中对应的地址,然后将最后结果写入该地址中。(其中有通过这两个参数共同获得的东西,不过为了便于理解就不再分开讨论。)

0x03 攻击

一、保护手段 (RELRO)

  RELRO:重定位只读手段

1.无保护

  在这种模式下关于重定位并不进行任何保护。

2.部分保护

  在这种模式下,一些段 (包括.dynamic) 在初始化后将会被标识为只读。

3.完全保护

  在这种模式下,除了会开启部分保护外。惰性解析会被禁用(所有的导入符号将在开始时被解析,.got.plt 段会被完全初始化为目标函数的终地址,并被标记为只读)。此外,既然惰性解析被禁用,GOT[1] 与 GOT[2] 条目将不会被初始化,存值为0。

二、对应攻击方法

  动态装载器认为它接收到的参数都是值得信任的,因为它假设这些都是直接由 ELF 文件提供的或者是它自己在开始时初始化的。然而,当一个攻击者能够修改这些数据时,这个假设就不成立 了。一些动态装载器(FreeBSD)会验证自己接收到的输入。然而,他们还是完全地信任控制结构 ,但这些也会可以轻易地破坏。

1.无保护

原理:
  动态装载器从 .rel.plt 中的 Elf_Rel 结构开始工作,顺着其中的下标找到 .dynsym 段中对应 Elf_Sym 结构的位置,并终使用它确定待解析符号的名称(在 .dynstr 段中的一段字符串)。简单的调用任意函数的办法就是使用希望的函数的名称覆盖字符串表中的条目 ,然后再调用动态装载器,但这是不可能的,因为保存着动态符号字符串表的段,即.dynstr,是不可写的。
过程:
  动态装载器是从 .dynamic 段的 DT_STRTAB 条目中获得 .dynstr 段的地址的,而且 DT_STRTAB 条目的位置是已知的,默认情况下也可写。我们可以将这个条目的 d_val 域覆盖为 .bss 段。 这块内存区域上将会包含一段字符串,比如 system。到了这一步,攻击者需要选择一个已经存在的符号,它的偏移在伪造的字符串表中正好指向 system 的位置,接着调用其对应的符号解析重定位过程。可以通过将其重定位项的偏移压栈并跳转到 PLT0 实现。
限制:
   这种方式非常简单,但仅当二进制程序的 .dynamic 段可写时有效。对于使用部分或完全 RELRO 编译的二进制程序,需要使用更复杂的攻击。

2.部分保护

原理:
  _dl_runtime_resolve 函数的第二个参数是 Elf_Rel 条目在 .rel.plt 段中对应当前请求函数的偏移。动态装载器将这个值加上 .rel.plt 的基地址来得到目标 Elf_Rel 结构的绝对地址。然而多数动态装载器实现不去检查重定位表的边界。这就表明如果一个大于 .rel.plt 的值传到 _dl_runtime_resolve 中,装载器将会认为特定的地址上的数据是一个 Elf_Rel 结构并使用它,即使那里已经超出了.rel .plt段的范围。
过程:
  计算一个新的 reloc_arg 参数,将 _dl_runtime_resolve 解析的位置劫持到一个可控内存。然后在那里构造一个 Elf_Rel 结构,并填写 r_offset 的值为一个可写的内存地址,将最后解析出的函数地址写在那里。同时,r_info 也要修改成一个可控区域处。并在该区域伪造一个 Elf_Sym 结构,其中的 st_name 域,指向另一个可控区域,并在该处填写要伪造成的函数名(例:system)。
  简而言之,该过程伪造了函数链接中所需要的所有结构(Elf_Sym Elf_Rel .dynstr),通过控制 reloc_arg 指向到伪造的 Elf_Rel ,再通过Elf_Rel 中的 r_info 找到伪造的 Elf_Sym 最后通过 Elf_Sym 的 st_name 找到最终伪造后需要解析的函数(例:system),解析完后通过 Elf_Rel 的 r_offset 写回到正确位置,达到劫持函数解析的目的,最终执行自己想要执行的函数。
限制:
  首先,Elf_Rel 的下标需要是正数,因为 r_info 域在 ELF 标准中规定是一个无符号整数。这就意味着在实际中这块可写的内存空间(例如.bss段)必须是位于 .dynsym 段之后。这种情况总是满足的。另一个限制是 ELF 会使用的符号版本系统。在这种情况下,Elf_Rel 的 r_info 域不仅用作动态符号表中的下标,也用作符号版本表(.gnu.version段)中的下标。
扩充方法:
  可以通过修改指向程序那一层的 link_map,具体做法是把该层 link_map->l_info[DT_STRTAB]->st_value 的值劫持到一个我们可控的区域,然后在该区域填充伪造函数,其实该方法也是通过修改 .dynstr 的方式实现攻击的手法。不过该方法必须有能够改写 st_value 值所需要的 gadget。

3.完全保护

原理:
  DT_DEBUG 条目的值是动态装载器在加载时设置好的,它指向一个 r_debug 类型的数据结构。这个数据结构保存着调试 器用来标识动态装载器的基地址并拦截相应事件需要的信息。此外,这个结构的 r_map 域保存着一个指向 link_map 链表头部的指针。
过程:
  攻击者使用 DT_DEBUG 这个动态条目来获取 r_debug 结构。接着,解引用 r_map 域从而得到主程序的 link_map 结构。然后像上边扩充方法那样破坏 l_info[DT_STRTAB]。接着攻击者同样需要恢复 _dl_runtime_resolve 函数的指针,通过 link_map->l_next 获取其他链接库中用到的该函数。具体获取手法是攻击者通过 l_info[DT_PLTGOT] 来获取对应的符号,然后通过 st_value 获取 .plt.got 节所在的地址,前面讨论过,该节的第三个偏移所存放的内容即为 _dl_runtime_resolve 函数地址。不过在这一切都做好之后,还有一个问题值得关注,_dl_runtime_resolve 不仅仅会调用目标函数,还会尝试将它的地址写到 GOT 项中。因为完全 RELRO 保护下 GOT 是不可写的,所以程序就会崩溃。不过我们可以通过伪造 link_map 中的 DT_JMPREL 动态条目来绕过这个问题。(具体操作方式和扩充方法中修改 .dynstr 类似)原本 DT_JMPREL 指向 .rel.dyn 段,我们将其改为一块可控区域,并那里写有一个 Elf_Rel 结构,并将其 r_offset 域指向一块可写的内存区域,其 r_info 指向我们的目标符号。至此,我们就完成了整个攻击过程。

0x04参考

😃 paper:sec15-paper-di-frederico
😃 bilibili 视频

这篇关于透过深入分析延迟绑定了解 ret_2_dl_resolve的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/187884

相关文章

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

SpringMVC入参绑定特别注意

1.直接在controller中定义一个变量,但是此种传输方式有一个限制就是参数名和请求中的参数名必须保持一致,否则失效。 @RequestMapping("test2")@ResponseBodypublic DBHackResponse<UserInfoVo> test2(String id , String name){UserInfoVo userInfoVo = new UserInf

速了解MySQL 数据库不同存储引擎

快速了解MySQL 数据库不同存储引擎 MySQL 提供了多种存储引擎,每种存储引擎都有其特定的特性和适用场景。了解这些存储引擎的特性,有助于在设计数据库时做出合理的选择。以下是 MySQL 中几种常用存储引擎的详细介绍。 1. InnoDB 特点: 事务支持:InnoDB 是一个支持 ACID(原子性、一致性、隔离性、持久性)事务的存储引擎。行级锁:使用行级锁来提高并发性,减少锁竞争

消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法

消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法   消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法 [转载]原地址:http://blog.csdn.net/x605940745/article/details/17911115 消除SDK更新时的“

Maven(插件配置和生命周期的绑定)

1.这篇文章很好,介绍的maven插件的。 2.maven的source插件为例,可以把源代码打成包。 Goals Overview就可以查看该插件下面所有的目标。 这里我们要使用的是source:jar-no-fork。 3.查看source插件的example,然后配置到riil-collect.xml中。  <build>   <plugins>    <pl

ActiveMQ—消息特性(延迟和定时消息投递)

ActiveMQ消息特性:延迟和定时消息投递(Delay and Schedule Message Delivery) 转自:http://blog.csdn.net/kimmking/article/details/8443872 有时候我们不希望消息马上被broker投递出去,而是想要消息60秒以后发给消费者,或者我们想让消息没隔一定时间投递一次,一共投递指定的次数。。。 类似

PHP: 深入了解一致性哈希

前言 随着memcache、redis以及其它一些内存K/V数据库的流行,一致性哈希也越来越被开发者所了解。因为这些内存K/V数据库大多不提供分布式支持(本文以redis为例),所以如果要提供多台redis server来提供服务的话,就需要解决如何将数据分散到redis server,并且在增减redis server时如何最大化的不令数据重新分布,这将是本文讨论的范畴。 取模算法 取模运

MySQL主从同步延迟原理及解决方案

概述 MySQL的主从同步是一个很成熟的架构,优点为: ①在从服务器可以执行查询工作(即我们常说的读功能),降低主服务器压力; ②在从主服务器进行备份,避免备份期间影响主服务器服务; ③当主服务器出现问题时,可以切换到从服务器。 相信大家对于这些好处已经非常了解了,在项目的部署中也采用这种方案。但是MySQL的主从同步一直有从库延迟的问题,那么为什么会有这种问题。这种问题如何解决呢? MyS

Weex入门教程之1,了解Weex

【资料合集】Weex Conf回顾集锦:讲义PDF+活动视频! PDF分享:链接:http://pan.baidu.com/s/1hr8RniG 密码:fa3j 官方教程:https://weex-project.io/cn/v-0.10/guide/index.html 用意 主要是介绍Weex,并未涉及开发方面,好让我们开始开发之前充分地了解Weex到底是个什么。 以下描述主要摘取于

react笔记 8-19 事件对象、获取dom元素、双向绑定

1、事件对象event 通过事件的event对象获取它的dom元素 run=(event)=>{event.target.style="background:yellowgreen" //event的父级为他本身event.target.getAttribute("aid") //这样便获取到了它的自定义属性aid}render() {return (<div><h2>{