ret2dlresolve分析

admin 2026-08-15 05:32:36 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细分析ret2dlresolve攻击技术,重点阐述其在无法泄露libc时调用库函数的原理。通过解析ELF文件结构(如.dynamic、.rela.plt、.dynsym节)及相关数据结构(Elf64Dyn、Elf64Rela、Elf64_Sym),揭示该技术本质是通过可控地址写入特定值实现函数解析劫持。文档深入探讨重定位类型、符号绑定机制等底层细节,为二进制安全研究提供重要技术参考。 综合评分: 87 文章分类: 二进制安全,漏洞分析,红队,渗透测试,CTF


cover_image

ret2dlresolve分析

奶粉批发 奶粉批发

看雪学苑

2026年6月23日 18:05 上海

在小说阅读器读本章

去阅读

该方法最大的作用在无法泄露libc时可以调用libc中的函数,其本质是向一个地址,写入一个值,而这两个部分都可以在一定程度上被控制的。

分析源码前,先看一些相关的结构体与宏。

结构体

.dynamic

该节存放了许多Elf64_Dyn结构体,在IDA中位于got表的上面,保存了动态链接器所需要的基本信息,比如存放了ELF文件其他节的标识和起始地址。

结构体定义如下所示:

typedef struct {
    Elf64_Sxword d_tag;//动态段标识号

    Elf64_Sxword d_un //动态段起始地址
} Elf64_Dyn;

其中关键字d_tag定义如下:

/* Legal values for d_tag (dynamic entry type).  */
#define DT_NULL                0                /* Marks end of dynamic section */
#define DT_NEEDED              1                /* Name of needed library */
#define DT_PLTRELSZ            2                /* Size in bytes of PLT relocs */
#define DT_PLTGOT              3                /* Processor defined value */
#define DT_HASH                4                /* Address of symbol hash table */
#define DT_STRTAB              5                /* Address of string table */
#define DT_SYMTAB              6                /* Address of symbol table */
#define DT_RELA                7                /* Address of Rela relocs */
#define DT_RELASZ              8                /* Total size of Rela relocs */
#define DT_RELAENT             9                /* Size of one Rela reloc */
#define DT_STRSZ              10                /* Size of string table */
#define DT_SYMENT             11                /* Size of one symbol table entry */
#define DT_INIT               12                /* Address of init function */
#define DT_FINI               13                /* Address of termination function */
#define DT_SONAME             14                /* Name of shared object */
#define DT_RPATH              15                /* Library search path (deprecated) */
#define DT_SYMBOLIC           16                /* Start symbol search here */
#define DT_REL                17                /* Address of Rel relocs */
#define DT_RELSZ              18                /* Total size of Rel relocs */
#define DT_RELENT             19                /* Size of one Rel reloc */
#define DT_PLTREL             20                /* Type of reloc in PLT */
#define DT_DEBUG              21                /* For debugging; unspecified */
#define DT_TEXTREL            22                /* Reloc might modify .text */
#define DT_JMPREL             23                /* Address of PLT relocs */
#define DT_BIND_NOW           24                /* Process relocations of object */
#define DT_INIT_ARRAY         25                /* Array with addresses of init fct */
#define DT_FINI_ARRAY         26                /* Array with addresses of fini fct */
#define DT_INIT_ARRAYSZ       27                /* Size in bytes of DT_INIT_ARRAY */
#define DT_FINI_ARRAYSZ       28                /* Size in bytes of DT_FINI_ARRAY */
#define DT_RUNPATH            29                /* Library search path */
#define DT_FLAGS              30                /* Flags for the object being loaded */
#define DT_ENCODING           32                /* Start of encoded range */
#define DT_PREINIT_ARRAY      32                /* Array with addresses of preinit fct*/
#define DT_PREINIT_ARRAYSZ    33                /* size in bytes of DT_PREINIT_ARRAY */
#define DT_SYMTAB_SHNDX       34                /* Address of SYMTAB_SHNDX section */
#define DT_NUM                35                /* Number used */

.rela.plt

该节存放了许多Elf64_Rela结构体,是对函数引用的修正,修正的位置在got.plt表,每个libc库函数都有自己的Elf64_Rela结构体。在程序入口附近,位于LOAD段。

typedef struct {
    Elf64_Addr r_offset;  /* 表示重定位所作用的虚拟地址或相对基地址的偏移,got表地址 */
    Elf64_Xword r_info;   /* 这是一个复合值,重定位类型和符号表下标 */
    Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;

r_info是一个复合值,其高32位表示该重定位项在动态链接符号表.dynsym中对应项的下标低32位表示该重定位项的重定向类型

重定位类型(Relocation Types)

#define R_386_NONE     0  /* No reloc */
#define R_386_32       1  /* Direct 32 bit  */
#define R_386_PC32     2  /* PC relative 32 bit */
#define R_386_GOT32    3  /* 32 bit GOT entry */
#define R_386_PLT32    4  /* 32 bit PLT address */
#define R_386_COPY     5  /* Copy symbol at runtime */
#define R_386_GLOB_DAT 6  /* Create GOT entry */
#define R_386_JMP_SLOT 7  /* Create PLT entry */
#define R_386_RELATIVE 8  /* Adjust by program base */
......

/* AMD x86-64 relocations.  */
#define R_X86_64_NONE      0  /* No reloc */
#define R_X86_64_64        1  /* Direct 64 bit  */
#define R_X86_64_PC32      2  /* PC relative 32 bit signed */
#define R_X86_64_GOT32     3  /* 32 bit GOT entry */
#define R_X86_64_PLT32     4  /* 32 bit PLT address */
#define R_X86_64_COPY      5  /* Copy symbol at runtime */
#define R_X86_64_GLOB_DAT  6  /* Create GOT entry */
#define R_X86_64_JUMP_SLOT 7  /* Create PLT entry */
#define R_X86_64_RELATIVE  8  /* Adjust by program base */
#define R_X86_64_GOTPCREL  9  /* 32 bit signed PC relative offset to GOT */
......

32位ELF一般用来函数重定位的重定位类型就是R_386_JMP_SLOT类型,64位ELF函数重定位的重定位类型就是R_X86_64_JUMP_SLOT类型,源码对其的注释是Create PLT entry。这种类型的函数重定位都会在ELF中创建一个PLT入口

.rela.dyn

该节,用于普通动态重定位,程序加载时处理,常见定位项:

 __libc_start_main
 __gmon_start__
 stdout

其中结构体也为Elf64_Rela

.dynsym

该节存放了许多Elf64_Sym结构体,同样地,每个libc函数都有自己的Elf64_Sym。在程序入口附近,位于LOAD段。

typedef struct {
  Elf64_Word    st_name;  /* 符号名在关联字符串表中的偏移 */
  unsignedchar st_info;  /* 符号绑定属性和符号类型 */
  unsignedchar st_other; /* 符号可见性,低2位有效,0表示 STV_DEFAULT ,即是否在共享器内已知,已知则寻找对应的共享器,没有则在传入的共享器中寻找偏移*/
  Elf64_Section st_shndx; /* 符号所在节在节头表中的索引 */
  Elf64_Addr    st_value; /* 符号值:含义取决于 ELF 文件类型 */
  Elf64_Xword   st_size;  /* 符号大小,0表示大小未知或无大小 */
} Elf64_Sym;

st_info大小为 1 Btyes,高4位表示符号的绑定特征,低4位表示符号类型。

绑定特征(高四位)

#define STB_LOCAL   0       /* Local symbol  局部符号(本文件可见) */
#define STB_GLOBAL  1       /* Global symbol 全局符号(多文件可见) */
#define STB_WEAK    2       /* Weak symbol 弱符号,即遇到同名的符号优先弃用该符号的声明*/
#define STB_NUM     3       /* Number of defined types.  */
#define STB_LOOS    10      /* Start of OS-specific */
#define STB_GNU_UNIQUE  10      /* Unique symbol.  */
#define STB_HIOS    12      /* End of OS-specific */
#define STB_LOPROC  13      /* Start of processor-specific */
#define STB_HIPROC  15      /* End of processor-specific */

绑定特征0,1,2均可取。

符号类型(低四位)

#define STT_NOTYPE      0   /* 符号类型未指定 */
#define STT_OBJECT      1   /* 符号是一个数据对象 */
#define STT_FUNC        2   /* 符号是一个代码对象 / 函数 */
#define STT_SECTION     3   /* 符号与某个节 section 相关联 */
#define STT_FILE        4   /* 符号名是文件名 */
#define STT_COMMON      5   /* 符号是一个 common 数据对象 */
#define STT_TLS         6   /* 符号是线程局部存储 TLS 数据对象 */
#define STT_NUM         7   /* 已定义类型的数量 */
#define STT_LOOS        10  /* 操作系统特定类型的起始值 */
#define STT_GNU_IFUNC   10  /* 符号是间接代码对象 GNU indirect function */
#define STT_HIOS        12  /* 操作系统特定类型的结束值 */
#define STT_LOPROC      13  /* 处理器特定类型的起始值 */
#define STT_HIPROC      15  /* 处理器特定类型的结束值 */

变量、函数分别取1, 2 即可。

那么稍微总结一下得到st_info取值的一般规律如下:

绑定函数:        st_info = 0x12 例如: read,printf,__libc_start_main
绑定全局变量:    st_info = 0x11 例如: stdin,stdout,_IO_stdin_used
绑定弱变量:      st_info = 0x20 例如: __gmon_start__

节头表

| 节头表索引 | 节名 | | — | — | | 0 | SHN_UNDEF / 空节 | | 1 | .text | | 2 | .data | | 3 | .bss | | 4 | .rodata |

.dynstr

即字符串表(STRTAB),该节存放的是libc函数的符号名,就是一个一个的字符串,诸如此类’exit’、’read’。在程序入口附近,位于LOAD段。

link_map 是 glibc 动态链接器 ld-linux 用来描述“一个已加载 ELF 对象”的核心结构体

一个进程里每加载一个 ELF 对象,就会有一个对应的 struct link_map

主程序        -> 一个 link_maplibc.so.6     -> 一个 link_mapld-linux      -> 一个 link_maplibpthread    -> 一个 link_map其他 so       -> 各自一个 link_map

它们通过链表串起来:

main link_map -> libc link_map -> ld-linux link_map -> ...

该结构体在 ld 中。

/* 描述一个已加载共享对象的结构体。`l_next' 和 `l_prev'
   成员构成了一条链,链上包含所有在启动时加载的共享对象。

   这些数据结构位于运行时动态链接器使用的空间中;
   修改它们可能会造成灾难性后果。

   如果有必要,这个数据结构将来可能会发生变化。
   用户级程序必须避免定义这种类型的对象。 */

struct link_map
  {
    /* 前几个成员是与调试器通信协议的一部分。
       这与 SVR4 使用的格式相同。 */

    ElfW(Addr) l_addr;          /* ELF 文件中的地址与内存中地址之间的差值。 */
char *l_name;               /* 找到该对象时使用的绝对文件名。 */
    ElfW(Dyn) *l_ld;            /* 该共享对象的动态段。 */
    struct link_map *l_next, *l_prev; /* 已加载对象链表。 */

    /* 后续所有成员都是动态链接器内部使用的。
       它们可能会在没有通知的情况下发生变化。 */

    /* 这个元素通常只会指向同一个类型对象本身;
       只有当 ld.so 被用于多个命名空间时,它才可能不同。 */
    struct link_map *l_real;

    /* 此 link_map 所属命名空间的编号。 */
    Lmid_t l_ns;

    struct libname_list *l_libname;

    /* 指向动态段的索引指针数组。
       [0,DT_NUM) 由处理器无关的 tag 进行索引。
       [DT_NUM,DT_NUM+DT_THISPROCNUM) 由 tag 减去 DT_LOPROC 后进行索引。
       [DT_NUM+DT_THISPROCNUM,DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM)
       由 DT_VERSIONTAGIDX(tagvalue) 进行索引。
       [DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM,
        DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM)
       由 DT_EXTRATAGIDX(tagvalue) 进行索引。
       [DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM,
        DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM+DT_VALNUM)
       由 DT_VALTAGIDX(tagvalue) 进行索引;
       [DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM+DT_VALNUM,
        DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM+DT_VALNUM+DT_ADDRNUM)
&nbsp; &nbsp; &nbsp; &nbsp;由 DT_ADDRTAGIDX(tagvalue) 进行索引,参见 <elf.h>。 */

&nbsp; &nbsp;&nbsp;ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; + DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM];

const&nbsp;ElfW(Phdr)&nbsp;*l_phdr; &nbsp;&nbsp;/* 指向内存中程序头表的指针。 */
&nbsp; &nbsp;&nbsp;ElfW(Addr) l_entry; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;/* 程序入口点位置。 */
&nbsp; &nbsp;&nbsp;ElfW(Half) l_phnum; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;/* 程序头表项数量。 */
&nbsp; &nbsp;&nbsp;ElfW(Half) l_ldnum; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;/* 动态段表项数量。 */

&nbsp; &nbsp;&nbsp;/* DT_NEEDED 依赖项以及它们的依赖项数组,
&nbsp; &nbsp; &nbsp; &nbsp;按符号查找所需的依赖顺序排列,包括带重复项和不带重复项的情况。
&nbsp; &nbsp; &nbsp; &nbsp;在依赖项被加载之前,这里没有表项。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;r_scope_elem&nbsp;l_searchlist;

&nbsp; &nbsp;&nbsp;/* 我们需要一个特殊的搜索列表来处理带有 DT_SYMBOLIC 标记的对象。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;r_scope_elem&nbsp;l_symbolic_searchlist;

&nbsp; &nbsp;&nbsp;/* 最先导致该对象被加载的依赖对象。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;link_map&nbsp;*l_loader;

&nbsp; &nbsp;&nbsp;/* 版本名称数组。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;r_found_version&nbsp;*l_versions;
unsignedint&nbsp;l_nversions;

&nbsp; &nbsp;&nbsp;/* 符号哈希表。 */
&nbsp; &nbsp; Elf_Symndx l_nbuckets;
&nbsp; &nbsp; Elf32_Word l_gnu_bitmask_idxbits;
&nbsp; &nbsp; Elf32_Word l_gnu_shift;
const&nbsp;ElfW(Addr)&nbsp;*l_gnu_bitmask;

&nbsp; &nbsp;&nbsp;union
&nbsp; &nbsp; {
const&nbsp;Elf32_Word *l_gnu_buckets;
const&nbsp;Elf_Symndx *l_chain;
&nbsp; &nbsp; };

&nbsp; &nbsp;&nbsp;union
&nbsp; &nbsp; {
const&nbsp;Elf32_Word *l_gnu_chain_zero;
const&nbsp;Elf_Symndx *l_buckets;
&nbsp; &nbsp; };

unsignedint&nbsp;l_direct_opencount;&nbsp;/* dlopen/dlclose 使用的引用计数。 */

&nbsp; &nbsp;&nbsp;enum&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;/* 该对象的来源。 */
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; lt_executable, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/* 主可执行程序。 */
&nbsp; &nbsp; &nbsp; &nbsp; lt_library, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;/* 主可执行程序需要的库。 */
&nbsp; &nbsp; &nbsp; &nbsp; lt_loaded &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;/* 运行时额外加载的共享对象。 */
&nbsp; &nbsp; &nbsp; } l_type:2;

unsignedint&nbsp;l_dt_relr_ref:1;&nbsp;/* 如果引用了 GLIBC_ABI_DT_RELR,则非零。 */
unsignedint&nbsp;l_relocated:1; &nbsp;&nbsp;/* 如果该对象的重定位已完成,则非零。 */
unsignedint&nbsp;l_init_called:1;&nbsp;/* 如果 DT_INIT 函数已被调用,则非零。 */
unsignedint&nbsp;l_global:1; &nbsp; &nbsp; &nbsp;/* 如果该对象位于 _dl_global_scope 中,则非零。 */
unsignedint&nbsp;l_reserved:2; &nbsp; &nbsp;/* 保留给内部使用。 */
unsignedint&nbsp;l_main_map:1; &nbsp; &nbsp;/* 如果这是主程序的 map,则非零。 */
unsignedint&nbsp;l_visited:1; &nbsp; &nbsp;&nbsp;/* 内部用于 map 依赖图遍历。 */
unsignedint&nbsp;l_map_used:1; &nbsp; &nbsp;/* 这两个 bit 用于 _dl_close_worker 中 */
unsignedint&nbsp;l_map_done:1; &nbsp; &nbsp;/* 遍历 map。 */

unsignedint&nbsp;l_phdr_allocated:1;&nbsp;/* 如果 `l_phdr' 指向的数据结构是动态分配的,则非零。 */
unsignedint&nbsp;l_soname_added:1; &nbsp;&nbsp;/* 如果 SONAME 确定已经在 l_libname 链表中,则非零。 */
unsignedint&nbsp;l_faked:1; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/* 如果这是一个没有关联文件的伪造描述符,则非零。 */

unsignedint&nbsp;l_need_tls_init:1; &nbsp;/* 如果重定位完成时应在该 link_map 上调用
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; GL(dl_init_static_tls),则非零。 */

unsignedint&nbsp;l_auditing:1; &nbsp; &nbsp; &nbsp;&nbsp;/* 如果该 DSO 用于 auditing,则非零。 */
unsignedint&nbsp;l_audit_any_plt:1; &nbsp;/* 如果至少有一个 audit 模块对 PLT 拦截感兴趣,则非零。 */

unsignedint&nbsp;l_removed:1; &nbsp; &nbsp; &nbsp; &nbsp;/* 如果该对象已被移除,不能再使用,则非零。 */

unsignedint&nbsp;l_contiguous:1; &nbsp; &nbsp;&nbsp;/* 如果段之间的空洞已被 mprotect 处理,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 或者根本不存在段间空洞,则非零。 */

unsignedint&nbsp;l_free_initfini:1; &nbsp;/* 如果 l_initfini 可以被释放,则非零;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 也就是说,它不是由 ld.so 中的 dummy malloc 分配的。 */

unsignedint&nbsp;l_ld_readonly:1; &nbsp; &nbsp;/* 如果动态段是只读的,则非零。 */

unsignedint&nbsp;l_find_object_processed:1;&nbsp;/* 如果为零,说明 _dl_find_object_update
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;还需要处理这个 lt_library map。 */

&nbsp; &nbsp;&nbsp;/* 该 map 的 NODELETE 状态。
&nbsp; &nbsp; &nbsp; &nbsp;只对类型为 lt_loaded 的 map 有效。

&nbsp; &nbsp; &nbsp; &nbsp;lazy binding 会直接设置 l_nodelete_active,
&nbsp; &nbsp; &nbsp; &nbsp;这可能发生在信号处理函数中。

&nbsp; &nbsp; &nbsp; &nbsp;初始加载 DF_1_NODELETE 对象时会设置 l_nodelete_pending。
&nbsp; &nbsp; &nbsp; &nbsp;重定位过程也可能设置 l_nodelete_pending。

&nbsp; &nbsp; &nbsp; &nbsp;l_nodelete_pending 类型的 map 会在 dlopen 的最后阶段,
&nbsp; &nbsp; &nbsp; &nbsp;即调用 ELF 构造函数之前,被提升为 l_nodelete_active 状态。

&nbsp; &nbsp; &nbsp; &nbsp;dlclose 只会拒绝卸载 l_nodelete_active 类型的 map;
&nbsp; &nbsp; &nbsp; &nbsp;pending 状态会被忽略。 */
bool&nbsp;l_nodelete_active;
bool&nbsp;l_nodelete_pending;

#include&nbsp;<link_map.h>

&nbsp; &nbsp;&nbsp;/* 收集到的该对象自身 RPATH 目录信息。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;r_search_path_struct&nbsp;l_rpath_dirs;

&nbsp; &nbsp;&nbsp;/* profiling 时收集到的重定位结果。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;reloc_result
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; DL_FIXUP_VALUE_TYPE addr;
&nbsp; &nbsp; &nbsp;&nbsp;struct&nbsp;link_map&nbsp;*bound;
unsignedint&nbsp;boundndx;
uint32_t&nbsp;enterexit;
unsignedint&nbsp;flags;

&nbsp; &nbsp; &nbsp;&nbsp;/* 并发说明:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;该字段用于保护多线程环境下 relocation result 的并发初始化。
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;更详细的说明见 elf/dl-runtime.c。 */
unsignedint&nbsp;init;
&nbsp; &nbsp; } *l_reloc_result;

&nbsp; &nbsp;&nbsp;/* 如果可用,指向版本信息的指针。 */
&nbsp; &nbsp;&nbsp;ElfW(Versym) *l_versyms;

&nbsp; &nbsp;&nbsp;/* 表示该对象被找到时所在路径的字符串。 */
constchar&nbsp;*l_origin;

&nbsp; &nbsp;&nbsp;/* 该对象内存映射的起始和结束地址。
&nbsp; &nbsp; &nbsp; &nbsp;l_map_start 不一定等于 l_addr。 */
&nbsp; &nbsp;&nbsp;ElfW(Addr) l_map_start, l_map_end;

&nbsp; &nbsp;&nbsp;/* `l_scope' 的默认数组。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;r_scope_elem&nbsp;*l_scope_mem[4];

&nbsp; &nbsp;&nbsp;/* 为 `l_scope' 分配的数组大小。 */
size_t&nbsp;l_scope_max;

&nbsp; &nbsp;&nbsp;/* 该数组定义此 link_map 的符号查找作用域。
&nbsp; &nbsp; &nbsp; &nbsp;初始时最多有三个不同的作用域列表。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;r_scope_elem&nbsp;**l_scope;

&nbsp; &nbsp;&nbsp;/* 类似的数组,但这里只包含局部作用域。
&nbsp; &nbsp; &nbsp; &nbsp;偶尔会用到。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;r_scope_elem&nbsp;*l_local_scope[2];

&nbsp; &nbsp;&nbsp;/* 该信息用于准确判断一个共享对象是否与已经加载的某个对象相同。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;r_file_id&nbsp;l_file_id;

&nbsp; &nbsp;&nbsp;/* 收集到的该对象自身 RUNPATH 目录信息。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;r_search_path_struct&nbsp;l_runpath_dirs;

&nbsp; &nbsp;&nbsp;/* 按 init 和 fini 调用顺序排列的对象列表。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;link_map&nbsp;**l_initfini;

&nbsp; &nbsp;&nbsp;/* 通过符号绑定引入的依赖项列表。 */
&nbsp; &nbsp;&nbsp;struct&nbsp;link_map_reldeps
&nbsp; &nbsp; &nbsp; {
unsignedint&nbsp;act;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;struct&nbsp;link_map&nbsp;*list[];
&nbsp; &nbsp; &nbsp; } *l_reldeps;

unsignedint&nbsp;l_reldepsmax;

&nbsp; &nbsp;&nbsp;/* 如果该 DSO 正在被使用,则非零。 */
unsignedint&nbsp;l_used;

&nbsp; &nbsp;&nbsp;/* 各种标志字。 */
&nbsp; &nbsp;&nbsp;ElfW(Word) l_feature_1;
&nbsp; &nbsp;&nbsp;ElfW(Word) l_flags_1;
&nbsp; &nbsp;&nbsp;ElfW(Word) l_flags;

&nbsp; &nbsp;&nbsp;/* 在 `dl_close' 中临时使用。 */
int&nbsp;l_idx;

&nbsp; &nbsp;&nbsp;struct&nbsp;link_map_machine&nbsp;l_mach;

&nbsp; &nbsp;&nbsp;struct
&nbsp; &nbsp; {
const&nbsp;ElfW(Sym)&nbsp;*sym;
int&nbsp;type_class;
&nbsp; &nbsp; &nbsp;&nbsp;struct&nbsp;link_map&nbsp;*value;
const&nbsp;ElfW(Sym)&nbsp;*ret;
&nbsp; &nbsp; } l_lookup_cache;

&nbsp; &nbsp;&nbsp;/* 线程局部存储 TLS 相关信息。 */

&nbsp; &nbsp;&nbsp;/* 初始化镜像的起始地址。 */
void&nbsp;*l_tls_initimage;

&nbsp; &nbsp;&nbsp;/* 初始化镜像大小。 */
size_t&nbsp;l_tls_initimage_size;

&nbsp; &nbsp;&nbsp;/* TLS 块大小。 */
size_t&nbsp;l_tls_blocksize;

&nbsp; &nbsp;&nbsp;/* TLS 块的对齐要求。 */
size_t&nbsp;l_tls_align;

&nbsp; &nbsp;&nbsp;/* 模块对齐后的第一个字节偏移。 */
size_t&nbsp;l_tls_firstbyte_offset;

#ifndef&nbsp;NO_TLS_OFFSET
#&nbsp;define&nbsp;NO_TLS_OFFSET &nbsp;0
#endif

#ifndef&nbsp;FORCED_DYNAMIC_TLS_OFFSET
#&nbsp;if&nbsp;NO_TLS_OFFSET == 0
# &nbsp;define&nbsp;FORCED_DYNAMIC_TLS_OFFSET -1
#&nbsp;elif&nbsp;NO_TLS_OFFSET == -1
# &nbsp;define&nbsp;FORCED_DYNAMIC_TLS_OFFSET -2
#&nbsp;else
# &nbsp;error&nbsp;"FORCED_DYNAMIC_TLS_OFFSET is not defined"
#&nbsp;endif
#endif

&nbsp; &nbsp;&nbsp;/* 对于启动时已经存在的对象:其在静态 TLS 块中的偏移。 */
ptrdiff_t&nbsp;l_tls_offset;

&nbsp; &nbsp;&nbsp;/* 模块在 dtv 数组中的索引。 */
size_t&nbsp;l_tls_modid;

&nbsp; &nbsp;&nbsp;/* 该 DSO 构造出的 thread_local 对象数量。
&nbsp; &nbsp; &nbsp; &nbsp;该字段会被原子访问和修改,并不总是由 load lock 保护。
&nbsp; &nbsp; &nbsp; &nbsp;另见 cxa_thread_atexit_impl.c 中的并发说明。 */
size_t&nbsp;l_tls_dtor_count;

&nbsp; &nbsp;&nbsp;/* 重定位完成后用于修改权限的信息。 */
&nbsp; &nbsp;&nbsp;ElfW(Addr) l_relro_addr;
size_t&nbsp;l_relro_size;

unsignedlonglongint&nbsp;l_serial;
&nbsp; };

无pie示例:

{
&nbsp; l_addr = 0,
&nbsp; l_name = 0x7ffff7ffe8b8&nbsp;"",
&nbsp; l_ld = 0x403148,
&nbsp; l_next = 0x7ffff7ffe8c0,
&nbsp; l_prev = 0x0,
&nbsp; l_real = 0x7ffff7ffe2e0,
&nbsp; l_ns = 0,
&nbsp; l_libname = 0x7ffff7ffe8a0,
&nbsp; l_info = {0x0, 0x403148, 0x403228, 0x403218, 0x0, 0x4031c8, 0x4031d8, 0x403258, 0x403268, 0x403278, 0x4031e8, 0x4031f8,
&nbsp; &nbsp; 0x403158, 0x403168, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x403238, 0x403208, 0x0, 0x403248, 0x0, 0x403178, 0x403198, 0x403188,
&nbsp; &nbsp; 0x4031a8, 0x0 <repeats 13&nbsp;times>, 0x403298, 0x403288, 0x0 <repeats 13&nbsp;times>, 0x4032a8, 0x0 <repeats 25&nbsp;times>, 0x4031b8},
&nbsp; l_phdr = 0x400040,
&nbsp; l_entry = 4198512,
&nbsp; l_phnum = 12,
&nbsp; l_ldnum = 0,
&nbsp; l_searchlist = {
&nbsp; &nbsp; r_list = 0x7ffff7fbb6c0,
&nbsp; &nbsp; r_nlist = 3
&nbsp; },
&nbsp; l_symbolic_searchlist = {
&nbsp; &nbsp; r_list = 0x7ffff7ffe898,
&nbsp; &nbsp; r_nlist = 0
&nbsp; },
......
}

开pie实例:

{
&nbsp; l_addr = 93824992231424,
&nbsp; l_name = 0x7ffff7ffe8b8&nbsp;"",
&nbsp; l_ld = 0x555555557150,
&nbsp; l_next = 0x7ffff7ffe8c0,
&nbsp; l_prev = 0x0,
&nbsp; l_real = 0x7ffff7ffe2e0,
&nbsp; l_ns = 0,
&nbsp; l_libname = 0x7ffff7ffe8a0,
&nbsp; l_info = {0x0, 0x555555557150, 0x555555557230, 0x555555557220, 0x0, 0x5555555571d0, 0x5555555571e0, 0x555555557260,
&nbsp; &nbsp; 0x555555557270, 0x555555557280, 0x5555555571f0, 0x555555557200, 0x555555557160, 0x555555557170, 0x0, 0x0, 0x0, 0x0, 0x0,
&nbsp; &nbsp; 0x0, 0x555555557240, 0x555555557210, 0x0, 0x555555557250, 0x0, 0x555555557180, 0x5555555571a0, 0x555555557190,
&nbsp; &nbsp; 0x5555555571b0, 0x0 <repeats 13&nbsp;times>, 0x5555555572b0, 0x5555555572a0, 0x0, 0x0, 0x555555557290, 0x0, 0x5555555572d0, 0x0,
&nbsp; &nbsp; 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5555555572c0, 0x0 <repeats 25&nbsp;times>, 0x5555555571c0},
&nbsp; l_phdr = 0x555555554040,
&nbsp; l_entry = 93824992235648,
&nbsp; l_phnum = 12,
&nbsp; l_ldnum = 0,
&nbsp; l_searchlist = {
&nbsp; &nbsp; r_list = 0x7ffff7fbb6c0,
&nbsp; &nbsp; r_nlist = 3
&nbsp; },
&nbsp; l_symbolic_searchlist = {
&nbsp; &nbsp; r_list = 0x7ffff7ffe898,
&nbsp; &nbsp; r_nlist = 0
&nbsp; },
......
}
pwndbg> p /x 93824992231424
$5&nbsp;= 0x555555554000

  • ELFW( 是 glibc / ELF 代码里常见的宏,用来根据当前平台自动选择 32 位 ELF 类型/宏 或 64 位 ELF 类型/宏
  • 找 .dynamic 中的 tab 的地址
/*找 .dynamic 中的 tab 的地址*/
#define&nbsp;D_PTR(map, i) \
&nbsp; ((map)->i->d_un.d_ptr + (dl_relocate_ld (map) ? 0 : (map)->l_addr))

/* Return true if dynamic section in the shared library L should be
&nbsp; &nbsp;relocated. &nbsp;*/
&nbsp; &nbsp;/*判断是否开启pie*/
static&nbsp;inline&nbsp;bool
dl_relocate_ld&nbsp;(const&nbsp;struct&nbsp;link_map *l){
&nbsp;&nbsp;/* Don't relocate dynamic section if it is readonly &nbsp;*/
&nbsp;&nbsp;return&nbsp;!(l->l_ld_readonly || DL_RO_DYN_SECTION);
}

/*·.dynamic` 段里的 `Elf64_Dyn` 结构体*/
&nbsp;&nbsp;typedef&nbsp;struct&nbsp;{
&nbsp; &nbsp; Elf64_Sxword d_tag;
&nbsp; &nbsp;&nbsp;union&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; Elf64_Xword d_val;
&nbsp; &nbsp; &nbsp; &nbsp; Elf64_Addr &nbsp;d_ptr;
&nbsp; &nbsp; } d_un;
} Elf64_Dyn;
  • 类型定义,用于定义需要绑定的函数所在的link_map
typedef&nbsp;struct&nbsp;link_map&nbsp;*lookup_t;
  • 处理偏移参数(参数从0开始,.rela.plt.rela.dyn的参数分开计算)
/*把 PLT 传给动态链接器的参数 `pltn` 转换成 `.rel.plt` / `.rela.plt` 重定位表里的 字节偏移*/
/*这里直接返回偏移,常规架构所传参数就是其在 tab 中的字节偏移*/
static&nbsp;inline&nbsp;uintptr_t
reloc_offset&nbsp;(uintptr_t&nbsp;plt0,&nbsp;uintptr_t&nbsp;pltn){
&nbsp;&nbsp;return&nbsp;pltn;
}

函数

SYMBOL_ADDRESS(map, ref, map_set)

  • 计算符号 ref 的运行时地址。

  • 如果 ref == NULL,返回 0;

  • 如果 ref 是 SHN_ABS 绝对符号(st_shndx == SHN_ABS),直接返回st_value

  • 否则使用 map->l_addr 作为模块基址,加上 ref->st_value(st_value + l_addr);

  • map_set 为 true 时,表示调用者保证 map 有效,不额外检查 map 是否为 NULL。

elf_machine_fixup_plt

static&nbsp;inline&nbsp;ElfW(Addr)
elf_machine_fixup_plt&nbsp;(struct&nbsp;link_map *map,&nbsp;lookup_t&nbsp;t,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;const&nbsp;ElfW(Sym) *refsym,&nbsp;const&nbsp;ElfW(Sym) *sym,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;const&nbsp;ElfW(Rela) *reloc,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ElfW(Addr) *reloc_addr, ElfW(Addr) value){
&nbsp;&nbsp;return&nbsp;*reloc_addr = value;
}

_dl_fixup()

_dl_fixup (
#&nbsp;ifdef&nbsp;ELF_MACHINE_RUNTIME_FIXUP_ARGS
     &nbsp; ELF_MACHINE_RUNTIME_FIXUP_ARGS,
#&nbsp;endif
     &nbsp;&nbsp;struct&nbsp;link_map *l,&nbsp;ElW(Word) reloc_arg)// &nbsp; &nbsp;(link_map,offset)
{

&nbsp;&nbsp;/*#define D_PTR(map, i) \ &nbsp; &nbsp; &nbsp; D_PTR是一个宏定义,用于通过linkmap寻址 glibc/sysdeps/generic/ldsodefs.h
&nbsp; ((map)->i->d_un.d_ptr + (dl_relocate_ld (map) ? 0 : (map)->l_addr))*/
&nbsp;&nbsp;//ELFW宏用来拼接字符串,在这里实际上是为了自动兼容32和64位,Elf32_Sym或Elf64_Sym
const&nbsp;ElfW(Sym)&nbsp;*const&nbsp;symtab
&nbsp; &nbsp;&nbsp;= (constvoid&nbsp;*)&nbsp;D_PTR&nbsp;(l, l_info[DT_SYMTAB]);
constchar&nbsp;*strtab = (constvoid&nbsp;*)&nbsp;D_PTR&nbsp;(l, l_info[DT_STRTAB]);

constuintptr_t&nbsp;pltgot = (uintptr_t)&nbsp;D_PTR&nbsp;(l, l_info[DT_PLTGOT]);

&nbsp;&nbsp;//通过link_map结构获取重定位表.rel.plt中所求函数的重定位项的地址
&nbsp; &nbsp;&nbsp;//reloc_offset为所解析函数的重定位项在重定位表.rel.plt中的偏移
const&nbsp;PLTREL *const&nbsp;reloc
&nbsp; &nbsp; = (constvoid&nbsp;*) (D_PTR&nbsp;(l, l_info[DT_JMPREL])
         &nbsp; &nbsp; &nbsp;+&nbsp;reloc_offset&nbsp;(pltgot, reloc_arg));

&nbsp;&nbsp;//求出所求函数在动态链接符号表.dynsym中对应符号项的地址
const&nbsp;ElfW(Sym)&nbsp;*sym&nbsp;= &symtab[ELFW(R_SYM) (reloc->r_info)];
const&nbsp;ElfW(Sym)&nbsp;*refsym&nbsp;= sym;

&nbsp;&nbsp;//l_addr是共享库或可执行文件加载基址,rel_addr是重定位需要修改内容的地址,也就是.got.plt中所求函数对应项
&nbsp; &nbsp;&nbsp;//r_offset为相对虚拟地址,rel_addr为虚拟地址
void&nbsp;*const&nbsp;rel_addr = (void&nbsp;*)(l->l_addr + reloc->r_offset);

lookup_t&nbsp;result;&nbsp;//查找函数的结果,其为定义函数的共享对象的加载基地址,为 link_map 类型
&nbsp; DL_FIXUP_VALUE_TYPE value; &nbsp;//DL_FIXUP_VALUE_TYPE是fixup/profile_fixup返回值的类型。用于保存函数的真实地址。

&nbsp;&nbsp;/* Sanity check that we're really looking at a PLT relocation. &nbsp;*/
&nbsp; &nbsp;/* 安全性检查,我们需要确定它是一个PLT的重定位项 */&nbsp;&nbsp;//r_info的低8位为重定位类型设置为7意味着它是一个PLT的重定位项
&nbsp;&nbsp;assert&nbsp;(ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);

&nbsp; &nbsp;/* Look up the target symbol. &nbsp;If the normal lookup rules are not
&nbsp; &nbsp; &nbsp; used don't look in the global scope. &nbsp;*/
&nbsp; &nbsp; &nbsp;&nbsp;/* 查找目标符号。如果未使用常规查找规则,则不要在全局范围内查找。 */
&nbsp;&nbsp;/*判断是否是受保护符号*/
&nbsp;&nbsp;if&nbsp;(__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other),&nbsp;0) ==&nbsp;0) &nbsp; &nbsp;//if(sym->st_other==0)
&nbsp; &nbsp; {
const&nbsp;struct&nbsp;r_found_version&nbsp;*version =&nbsp;NULL;
    &nbsp;/*确认elf 的 link_map 中存有版本编号*/
     &nbsp;if&nbsp;(l->l_info[VERSYMIDX&nbsp;(DT_VERSYM)] !=&nbsp;NULL)
        {
/*处理偏移成对应版本的偏移*/
         &nbsp;const&nbsp;ElfW(Half)&nbsp;*vernum&nbsp;= (constvoid&nbsp;*)&nbsp;D_PTR&nbsp;(l, l_info[VERSYMIDX&nbsp;(DT_VERSYM)]);
         &nbsp;ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] &&nbsp;0x7fff;
         &nbsp;version = &l->l_versions[ndx];
         &nbsp;if&nbsp;(version->hash ==&nbsp;0)
         &nbsp; &nbsp;version =&nbsp;NULL;
        }

&nbsp; &nbsp; &nbsp;&nbsp;/* We need to keep the scope around so do some locking. &nbsp;This is
     not necessary for objects which cannot be unloaded or when
     we are not using any threads (yet). &nbsp;*/
&nbsp; &nbsp;/* 我们需要保持范围不变,因此需要进行一些锁定。 对于无法卸载的对象或单线程的对象,这不是必需的。 &nbsp;*/
int&nbsp;flags = DL_LOOKUP_ADD_DEPENDENCY;
&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!RTLD_SINGLE_THREAD_P)
        {
         &nbsp;THREAD_GSCOPE_SET_FLAG&nbsp;();
         &nbsp;flags |= DL_LOOKUP_GSCOPE_LOCK;
        }

#ifdef&nbsp;RTLD_ENABLE_FOREIGN_CALL
&nbsp; &nbsp; &nbsp; RTLD_ENABLE_FOREIGN_CALL;
#endif
//strtab + sym->st_name为所解析函数的符号在字符串表中的地址,result为定义函数的共享对象的加载基地址即libc基地址
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;//_dl_lookup_symbol_x的功能是在加载的共享对象的符号表中搜索符号的定义,其参数也许带有该符号的版本。
&nbsp; &nbsp; &nbsp; result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,version, ELF_RTYPE_CLASS_PLT, flags,&nbsp;NULL);

&nbsp; &nbsp; &nbsp;&nbsp;/* We are done with the global scope. &nbsp;*/
&nbsp; &nbsp; &nbsp;&nbsp;/* 我们已经完成了全局范围的工作。 */
&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!RTLD_SINGLE_THREAD_P)
THREAD_GSCOPE_RESET_FLAG&nbsp;();

#ifdef&nbsp;RTLD_FINALIZE_FOREIGN_CALL
     &nbsp; &nbsp; &nbsp;RTLD_FINALIZE_FOREIGN_CALL;
#endif
/* 当前result包含定义sym的共享对象的加载基地址(或link map)。 现在添加符号偏移量。 */
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;//value为所求函数的真实内存地址
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;//SYMBOL_ADDRESS(map, ref, map_set):如果ref不是NULL,则使用映射MAP中的基地址来计算符号引用的地址。
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;//map_set 为 true 时,表示调用者保证 map 有效,不额外检查 map 是否为 NULL。

&nbsp; &nbsp; &nbsp;&nbsp;/* Currently result contains the base load address (or link map)
     of the object that defines sym. &nbsp;Now add in the symbol
     offset. &nbsp;*/
     &nbsp; value =&nbsp;DL_FIXUP_MAKE_VALUE&nbsp;(result,&nbsp;SYMBOL_ADDRESS&nbsp;(result, sym,&nbsp;false));
&nbsp; &nbsp; }
&nbsp;&nbsp;else//未使用常规查找规则
&nbsp; &nbsp; {/*我们已经找到符号了。模块(以及它的负载)
地址)也是已知的。* /
&nbsp; &nbsp; &nbsp; /* We already found the symbol. &nbsp;The module (and therefore its load
     address) is also known. &nbsp;*/
&nbsp; &nbsp; &nbsp; value =&nbsp;DL_FIXUP_MAKE_VALUE&nbsp;(l,&nbsp;SYMBOL_ADDRESS&nbsp;(l, sym,&nbsp;true));
&nbsp; &nbsp; &nbsp; result = l;
&nbsp; &nbsp; }

&nbsp;&nbsp;/* And now perhaps the relocation addend. &nbsp;*/
&nbsp;&nbsp;//现在也许是重新定位的加法。对不同架构返回不同的处理函数。
&nbsp;&nbsp;//elf_machine_plt_value返回PLT重定位的最终值。在x86-64上JUMP_SLOT重定位忽略addend。
&nbsp; value =&nbsp;elf_machine_plt_value&nbsp;(l, reloc, value);

&nbsp;&nbsp;if&nbsp;(sym !=&nbsp;NULL
&nbsp; &nbsp; &nbsp; && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC,&nbsp;0))
&nbsp; &nbsp; value =&nbsp;elf_ifunc_invoke&nbsp;(DL_FIXUP_VALUE_ADDR&nbsp;(value));

##ifdef&nbsp;SHARED
&nbsp;&nbsp;/* 审计检查点:当前产生了一个新的符号绑定。
&nbsp; &nbsp; &nbsp;给审计库一个机会,让它可以修改绑定得到的地址 value,
&nbsp; &nbsp; &nbsp;并告知动态链接器后续是否还需要继续审计。

&nbsp; &nbsp; &nbsp;只有存在提供 la_symbind 回调函数的审计模块时,
&nbsp; &nbsp; &nbsp;l_reloc_result 才会被分配。 */
&nbsp;&nbsp;if&nbsp;(l->l_reloc_result !=&nbsp;NULL)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp;&nbsp;/* 这是数组中的一个地址,用于保存之前重定位得到的结果。 */
&nbsp; &nbsp; &nbsp;&nbsp;struct&nbsp;reloc_result&nbsp;*reloc_result
&nbsp; &nbsp; &nbsp; &nbsp; = &l->l_reloc_result[reloc_index&nbsp;(pltgot, reloc_arg,&nbsp;sizeof&nbsp;(PLTREL))];

unsignedint&nbsp;init =&nbsp;atomic_load_acquire&nbsp;(&reloc_result->init);

&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(init ==&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _dl_audit_symbind (l, reloc_result, sym, &value, result);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;/* 保存本次结果,供后续再次执行时使用。 */
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(__glibc_likely (!&nbsp;GLRO(dl_bind_not)))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reloc_result->addr = value;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;/* 保证前面的所有写操作都完成后,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;再更新 init。详见下面的并发说明。 */
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;atomic_store_release&nbsp;(&reloc_result->init,&nbsp;1);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp;&nbsp;else
&nbsp; &nbsp; &nbsp; &nbsp; value = reloc_result->addr;
&nbsp; &nbsp; }
#endif

&nbsp;&nbsp;/* Finally, fix up the plt itself. &nbsp;*/
&nbsp;&nbsp;/* 最后,修复PLT本身。 */
&nbsp;&nbsp;if&nbsp;(__glibc_unlikely (GLRO(dl_bind_not)))
&nbsp; &nbsp;&nbsp;return&nbsp;value;
//向所查找函数对应的GOT表中填写找到的函数的真实地址。
&nbsp;&nbsp;return&nbsp;elf_machine_fixup_plt&nbsp;(l, result, refsym, sym, reloc, rel_addr, value);
}

执行流

elf中的link_map与_dl_runtime_resolve_xsavec()指针在.bss段中.got段开头

下图省略所有被调用函数,调试模块,部分结构体,绿色为常规执行(判断返回为 ture),红色为可控执行流,蓝色为相加。

利用思路

该方法最大的作用在无法泄露libc时可以调用libc中的函数,其本质是向一个地址,写入一个值,而这两个部分都可以在一定程度上被控制:

*(l_addr_para + r_offset) = l_addr_choi + st_value

该利用并不只限制于覆盖got表段,可以覆盖任意位置可被覆盖(可写)的能被调用的函数指针,然后调用 libc 任意位置的代码片段(内核未开启ibt保护时)。

触发

汇编层面,延迟绑定第一次执行函数执行流如下:

call func@plt

.plt
jmp func@got

.got
push&nbsp;rela_off
jmp text_near

.text
push&nbsp;qword ptr [link_map_addr.got]
jmp qword ptr [_dl_runtime_resolve_xsavec.got]

所以,有如下两种触发方式:

  • 用任意写覆盖部分指针、偏移,或进行伪造,利用未加载的函数触发
  • 通过栈溢出,劫持执行流,在栈上布置两个指针(可以为真实指针或伪造的结构体指针)、返回地址,模拟执行

触发前栈布局:

&nbsp; &nbsp;<- rsp
...
saved rbp &nbsp; <- rbp
_dl_runtime_resolve_xsavec()
rela_off
link_map_ptr
ret_addr

出发后栈布局:

rela_off &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<-rsp
link_map_ptr
ret_addr

NO RELRO

该模式下,上如所有表段中的结构体可写。有任意写可以根据以上执行流,覆盖任意偏移进行利用;

只有溢出,需泄露pie或无pie,泄露 ld 地址,伪造link_map

这种情况下思路比较广泛,只需知道调用过程、各结构体位置,就可以根据具体情况利用。

Partial RELRO

该模式下,所有表段不可被写,但是.bss段的link_map_dl_runtime_resolve_xsavec()的指针可以被写。

有任意写,可以覆盖.bss段指针,伪造link_map; 只有溢出,需泄露pie或无pie,需泄露 ld 地址,伪造link_map

伪造link_map

根据上面的执行流,可以有如下的利用思路:

通过将sym结构体伪造在got表附近,使st_valuest_other命中合适位置

*(l_addr_para + r_offset) = l_addr_choi + st_value
  • 使st_other非 0 ,将执行流导向非常规流程(红色)
  • 使vt_value为已经被写过的 libc 函数入口地址,此时link_map为通过参数传入的link_map,通过l_addr调整偏移,到目标代码段地址
  • 由于调整了传入的link_mapl_addr,所以在写rela结构体时,需将r_offset设置为target - l_addr

以上思路,只是需要一个共享器内的地址,以及其版本,就能通过偏移计算调用共享器中的函数,调用 ld 的思路相同。

FULL_RELERO

当开启FULL_RELERO时,整个GOT表将标记为read-only,并且所有的外部引用变量/函数都将在程序装载时由动态链接器解析完成。

此时.got.plt表中的第二项 表项GOT[1]装载的link_map地址 以及第二项 表项GOT[2]装载的dl_runtime_resolve函数地址将是0。

所以此时,利用方式与上面完全一样,仍然可以在.got表进行伪造,只是很难泄露 ld 的地址。

下面介绍一个结构体:

/*
&nbsp; &nbsp;运行时动态链接器使用的传统 rendezvous 结构,
&nbsp; &nbsp;用于向调试器传递共享对象加载情况的详细信息。
&nbsp;*/
struct&nbsp;r_debug
{
&nbsp;&nbsp;/* 该协议的版本号。它应该大于 0。 */
int&nbsp;r_version;

&nbsp;&nbsp;struct&nbsp;link_map&nbsp;*r_map; &nbsp;/* 已加载对象链表的头指针。 */

&nbsp;&nbsp;/*
&nbsp; &nbsp; &nbsp;这是运行时链接器内部某个函数的地址。

&nbsp; &nbsp; &nbsp;当链接器开始映射一个库、取消映射一个库时,
&nbsp; &nbsp; &nbsp;以及当这种映射变化完成时,
&nbsp; &nbsp; &nbsp;这个函数总是会被调用。

&nbsp; &nbsp; &nbsp;如果调试器想要感知共享对象映射关系的变化,
&nbsp; &nbsp; &nbsp;可以在这个地址处设置断点。
&nbsp; &nbsp;*/
&nbsp;&nbsp;ElfW(Addr) r_brk;

&nbsp;&nbsp;enum
&nbsp; {
&nbsp; &nbsp;&nbsp;/*
&nbsp; &nbsp; &nbsp; &nbsp;当调用 `r_brk` 地址处的函数时,
&nbsp; &nbsp; &nbsp; &nbsp;这个状态值描述当前正在发生的映射变化。
&nbsp; &nbsp; &nbsp;*/
&nbsp; &nbsp; RT_CONSISTENT, &nbsp;/* 映射变化已经完成。 */
&nbsp; &nbsp; RT_ADD, &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;/* 开始添加一个新对象。 */
&nbsp; &nbsp; RT_DELETE &nbsp; &nbsp; &nbsp;&nbsp;/* 开始移除一个对象映射。 */
&nbsp; } r_state;

&nbsp;&nbsp;ElfW(Addr) r_ldbase; &nbsp;/* 链接器自身被加载到的基地址。 */
};

是一个用于向调试器传递信息的结构体,也被定义在.synamic中:

#define&nbsp;DT_DEBUG 21&nbsp;/* For debugging; unspecified */

该结构体中有link_map的地址,此时,通过多次访问该link_map的双链表,找到另一非FULL_RELERO的程序,读取其got表中的地址就能找到dl_runtime_resolve的地址。

注,link_map在 ld 的堆区中,其与 ld 中的函数相对偏移并不固定。

#

看雪ID:奶粉批发

https://bbs.kanxue.com/user-home-1062075.htm

*本文为看雪论坛优秀文章,由 奶粉批发 原创,转载请注明来自看雪社区

第十届安全开发者峰会【议题征集】-欢迎投稿

往期推荐

我们绕过了 GarudaDefender 整套 Frida 检测,但这已经不是重点了

一次 Flutter App 实战:还原 encData 参数解密流程

单机DMA劫持HyperV!调试+取证两种思路解决2026腾讯游戏安全技术竞赛决赛

Android风险环境检测——签名校验

和爱豆更近一步——爱豆聊天App反调试绕过

球分享

球点赞

球在看

点击阅读原文查看更多


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:看雪学苑 奶粉批发 奶粉批发《ret2dlresolve分析》

ret2dlresolve分析 网络安全文章

ret2dlresolve分析

文章总结: 本文详细分析ret2dlresolve攻击技术,重点阐述其在无法泄露libc时调用库函数的原理。通过解析ELF文件结构(如.dynamic、.rel
评论:0   参与:  0