动态链接

使用动态链接时,程序按模块拆分为多个独立部分,在程序运行时才链接在一起。linux中ELF动态链接文件称为动态共享对象(Dynamic shared objects),以.so为拓展名。常用的C语言运行库glibc保存为libc.so。

链接的操作由动态链接器ld完成。运行时动态链接器与普通共享对象一同被映射到进程的地址空间,程序在运行时首先运行ld,完成所有动态链接工作后再转交主程序。

如果程序中存在跨模块的数据访问,由于目标变量的地址要在装载时才能确定,需要使得程序中的代码与地址无关。于是把跟地址相关的代码放在数据段里,即全局偏移表(Global Offset Table)。至于模块间的调用、跳转,也用GOT表实现,但是出于效率考虑,需要引入延迟绑定机制。

延迟绑定(Lazy Binding)的思想是在程序第一次用到时才绑定(符号查找,重定位),使用PLT表(Procedure Linkage Table)实现,实现伪代码如下:

PLT0:
    push *(GOT + 4)
    jump *(GOT + 8)
...

bar@plt:
    jmp *(bar@GOT)
label:
    push n
    jump PLT0
...

bar@GOT:
    jmp label

其中bar@GOT中初始保存bar@plt的下一条指令地址(label),随后push的n是bar在重定位表项中的序号。GOT + 4 中保存一个名为link_map的结构体的地址,它保存了本模块动态链接的相关信息,GOT + 8中保存_dl_runtime_resolve()的地址,该函数的作用便是解析link_map,计算出bar函数的真正地址,并将其填入bar@GOT中。

理解延迟绑定相关实现,与后面利用密切相关。

相关结构

.interp段

一个字符串,动态连接器的路径

.dynamic段

readelf -d Lib.so查看
保存了动态连接器所需的基本信息,结构如下:

typedef struct
{
   Elf32_Sword   d_tag;          /* Dynamic entry type */
   union
     {
       Elf32_Word d_val;         /* Integer value */
       Elf32_Addr d_ptr;         /* Address value */
     } d_un;
 } Elf32_Dyn;
 
typedef struct
{
	Elf64_Sxword d_tag; /* Dynamic entry type */
	union
	  {
		Elf64_Xword d_val; /* Integer value */
		Elf64_Addr d_ptr; /* Address value */
	  } d_un;
} Elf64_Dyn;

在elf.h中定义了不同d_tag的值与对应类型,后面比较常用的有:

  • DT_REL 动态链接重定位表地址
  • DT_SYMTAB 动态链接符号表地址
  • DT_STRTAB 动态链接字符串表地址
  • DT_INIT 初始化代码地址
  • DT_FINI 结束代码地址

IDA查看如下:

LOAD:0000000000005090                 Elf64_Dyn <5, 51F0h>    ; DT_STRTAB
LOAD:00000000000050A0                 Elf64_Dyn <6, 5368h>    ; DT_SYMTAB
LOAD:00000000000050B0                 Elf64_Dyn <0Ah, 171h>   ; DT_STRSZ
LOAD:00000000000050C0                 Elf64_Dyn <0Bh, 18h>    ; DT_SYMENT
LOAD:00000000000050D0                 Elf64_Dyn <15h, 0>      ; DT_DEBUG
LOAD:00000000000050E0                 Elf64_Dyn <3, 4000h>    ; DT_PLTGOT
LOAD:00000000000050F0                 Elf64_Dyn <2, 198h>     ; DT_PLTRELSZ
LOAD:0000000000005100                 Elf64_Dyn <14h, 7>      ; DT_PLTREL
LOAD:0000000000005110                 Elf64_Dyn <17h, 948h>   ; DT_JMPREL
LOAD:0000000000005120                 Elf64_Dyn <7, 828h>     ; DT_RELA

DT_REL动态链接重定位表

readelf -r Lib.so查看

共享对象的重定位在装载时完成,重定位表分为.rel.dyn和.rel.plt。前者修正数据引用,即.got和.data;后者修正.got.plt

typedef struct
{
	Elf32_Addr r_offset; /* Address */
	Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;

typedef struct
{
	Elf64_Addr r_offset; /* Address */
	Elf64_Xword r_info; /* Relocation type and symbol index */
} Elf64_Rel;

/* How to extract and insert information held in the r_info field. */

#define ELF32_R_SYM(val) ((val) >> 8)
#define ELF32_R_TYPE(val) ((val) & 0xff)
#define ELF32_R_INFO(sym, type) (((sym) << 8) + ((type) & 0xff))

#define ELF64_R_SYM(i) ((i) >> 32)
#define ELF64_R_TYPE(i) ((i) & 0xffffffff)
#define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) << 32) + (type))

r_offset表示需要修正的地址,r_info高位表示该符号在符号表中的序号,低位表示符号类型

IDA中如下:

LOAD:0000000000000828 ; ELF RELA Relocation Table
LOAD:0000000000000828                 Elf64_Rela <3DE0h, 8, 1340h> ; R_X86_64_RELATIVE +1340h
LOAD:0000000000000840                 Elf64_Rela <3DE8h, 8, 15B5h> ; R_X86_64_RELATIVE +15B5h
LOAD:0000000000000858                 Elf64_Rela <3DF0h, 8, 1300h> ; R_X86_64_RELATIVE +1300h
LOAD:0000000000000870                 Elf64_Rela <40A8h, 8, 40A8h> ; R_X86_64_RELATIVE +40A8h
LOAD:0000000000000888                 Elf64_Rela <3FD8h, 200000006h, 0> ; R_X86_64_GLOB_DAT __libc_start_main
LOAD:00000000000008A0                 Elf64_Rela <3FE0h, 400000006h, 0> ; R_X86_64_GLOB_DAT _ITM_deregisterTMCloneTable
LOAD:00000000000008B8                 Elf64_Rela <3FE8h, 1000000006h, 0> ; R_X86_64_GLOB_DAT __gmon_start__
LOAD:00000000000008D0                 Elf64_Rela <3FF0h, 1500000006h, 0> ; R_X86_64_GLOB_DAT _ITM_registerTMCloneTable
LOAD:00000000000008E8                 Elf64_Rela <3FF8h, 1700000006h, 0> ; R_X86_64_GLOB_DAT __cxa_finalize
LOAD:0000000000000900                 Elf64_Rela <4100h, 1600000005h, 0> ; R_X86_64_COPY stdout
LOAD:0000000000000918                 Elf64_Rela <4110h, 1800000005h, 0> ; R_X86_64_COPY stdin
LOAD:0000000000000930                 Elf64_Rela <4120h, 1A00000005h, 0> ; R_X86_64_COPY stderr
LOAD:0000000000000948 ; ELF JMPREL Relocation Table
LOAD:0000000000000948                 Elf64_Rela <4018h, 100000007h, 0> ; R_X86_64_JUMP_SLOT free
LOAD:0000000000000960                 Elf64_Rela <4020h, 300000007h, 0> ; R_X86_64_JUMP_SLOT strncpy
LOAD:0000000000000978                 Elf64_Rela <4028h, 500000007h, 0> ; R_X86_64_JUMP_SLOT _exit
LOAD:0000000000000990                 Elf64_Rela <4030h, 600000007h, 0> ; R_X86_64_JUMP_SLOT puts
LOAD:00000000000009A8                 Elf64_Rela <4038h, 700000007h, 0> ; R_X86_64_JUMP_SLOT write
LOAD:00000000000009C0                 Elf64_Rela <4040h, 800000007h, 0> ; R_X86_64_JUMP_SLOT strlen
LOAD:00000000000009D8                 Elf64_Rela <4048h, 900000007h, 0> ; R_X86_64_JUMP_SLOT __stack_chk_fail
LOAD:00000000000009F0                 Elf64_Rela <4050h, 0A00000007h, 0> ; R_X86_64_JUMP_SLOT mmap
LOAD:0000000000000A08                 Elf64_Rela <4058h, 0B00000007h, 0> ; R_X86_64_JUMP_SLOT printf
LOAD:0000000000000A20                 Elf64_Rela <4060h, 0C00000007h, 0> ; R_X86_64_JUMP_SLOT memset
LOAD:0000000000000A38                 Elf64_Rela <4068h, 0D00000007h, 0> ; R_X86_64_JUMP_SLOT alarm
LOAD:0000000000000A50                 Elf64_Rela <4070h, 0E00000007h, 0> ; R_X86_64_JUMP_SLOT read
LOAD:0000000000000A68                 Elf64_Rela <4078h, 0F00000007h, 0> ; R_X86_64_JUMP_SLOT memcmp
LOAD:0000000000000A80                 Elf64_Rela <4080h, 1100000007h, 0> ; R_X86_64_JUMP_SLOT malloc
LOAD:0000000000000A98                 Elf64_Rela <4088h, 1200000007h, 0> ; R_X86_64_JUMP_SLOT setvbuf
LOAD:0000000000000AB0                 Elf64_Rela <4090h, 1300000007h, 0> ; R_X86_64_JUMP_SLOT __isoc99_scanf
LOAD:0000000000000AC8                 Elf64_Rela <4098h, 1400000007h, 0> ; R_X86_64_JUMP_SLOT exit

DT_SYMTAB 动态链接符号表

readelf -sD Lib.so查看
通常保存在段.dynsym

typedef struct
{
	Elf32_Word st_name; /* Symbol name (string tbl index) */
	Elf32_Addr st_value; /* Symbol value */
	Elf32_Word st_size; /* Symbol size */
	unsigned char st_info; /* Symbol type and binding */
	unsigned char st_other; /* Symbol visibility */
	Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;

typedef struct
{
	Elf64_Word st_name; /* Symbol name (string tbl index) */
	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 */
	Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;

/* How to extract and insert information held in the st_info field. */

#define ELF32_ST_BIND(val) (((unsigned char) (val)) >> 4)
#define ELF32_ST_TYPE(val) ((val) & 0xf)
#define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf))

/* Both Elf32_Sym and Elf64_Sym use the same one-byte st_info field. */
#define ELF64_ST_BIND(val) ELF32_ST_BIND (val)
#define ELF64_ST_TYPE(val) ELF32_ST_TYPE (val)
#define ELF64_ST_INFO(bind, type) ELF32_ST_INFO ((bind), (type))

DT_STRTAB 动态链接字符串表

.dynstr中

一个字符串表,索引[0]永远为0,获取的时候是取相对[0]处的地址作为偏移来取字符串的。

三个表关系

三个表有这样的查找关系:

  1. 先根据.rel.plt重定位表的r_info字段r_info>>8确定在.dynsym表中的偏移。
  2. 根据在.dynsym中的偏移得到在中.dynstr的字符串地址。
  3. 之后由动态链接库解析字符串拿到地址。

一个结构体,保存动态链接相关信息

struct link_map
  {
    /* These first few members are part of the protocol with the debugger.
       This is the same format used in SVR4.  */
    ElfW(Addr) l_addr;		/* Difference between the address in the ELF
				   file and the addresses in memory.  */
    char *l_name;		/* Absolute file name object was found in.  */
    ElfW(Dyn) *l_ld;		/* Dynamic section of the shared object.  */
    struct link_map *l_next, *l_prev; /* Chain of loaded objects.  */
    /* All following members are internal to the dynamic linker.
       They may change without notice.  */
    /* This is an element which is only ever different from a pointer to
       the very same copy of this type for ld.so when it is used in more
       than one namespace.  */
    struct link_map *l_real;
    /* Number of the namespace this link map belongs to.  */
    Lmid_t l_ns;
    struct libname_list *l_libname;
    /* Indexed pointers to dynamic section.
       [0,DT_NUM) are indexed by the processor-independent tags.
       [DT_NUM,DT_NUM+DT_THISPROCNUM) are indexed by the tag minus DT_LOPROC.
       [DT_NUM+DT_THISPROCNUM,DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM) are
       indexed by DT_VERSIONTAGIDX(tagvalue).
       [DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM,
	DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM) are indexed by
       DT_EXTRATAGIDX(tagvalue).
       [DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM,
	DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM+DT_VALNUM) are
       indexed by DT_VALTAGIDX(tagvalue) and
       [DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM+DT_VALNUM,
	DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM+DT_VALNUM+DT_ADDRNUM)
       are indexed by DT_ADDRTAGIDX(tagvalue), see <elf.h>.  */
    ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM
		      + DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM];
    const ElfW(Phdr) *l_phdr;	/* Pointer to program header table in core.  */
    ElfW(Addr) l_entry;		/* Entry point location.  */
    ElfW(Half) l_phnum;		/* Number of program header entries.  */
    ElfW(Half) l_ldnum;		/* Number of dynamic segment entries.  */
    ...
  }

有两个比较重要的部分:

  • l_addr 程序的基址
  • l_name 程序的名称
  • l_info[]一个数组,保存了多个重定位项的地址

总结

调用_dl_runtime_resolve时,传参link_map和rel_offset,查找流程如下

_dl_runtime_resolve(link_map, rel_offset)
                                       +
          +-----------+                |
          | Elf32_Rel | <--------------+
          +-----------+
     +--+ | r_offset  |        +-----------+
     |    |  r_info   | +----> | Elf32_Sym |
     |    +-----------+        +-----------+      +----------+
     |      .rel.plt           |  st_name  | +--> | system\0 |
     |                         |           |      +----------+
     v                         +-----------+        .dynstr
+----+-----+                      .dynsym
| <system> |
+----------+
  .got.plt

ret2dlresolve

原理

_dl_runtime_resolve实际调用的是_dl_fixup(link_map, reloc_arg),代码如下:

/* This function is called through a special trampoline from the PLT the
   first time each PLT entry is called.  We must perform the relocation
   specified in the PLT of the given shared object, and return the resolved
   function address to the trampoline, which will restart the original call
   to that address.  Future calls will bounce directly from the PLT to the
   function.  */
DL_FIXUP_VALUE_TYPE
attribute_hidden __attribute ((noinline)) DL_ARCH_FIXUP_ATTRIBUTE
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
	   ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
	   struct link_map *l, ElfW(Word) reloc_arg)
{
  const ElfW(Sym) *const symtab
    = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
  const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
  const uintptr_t pltgot = (uintptr_t) D_PTR (l, l_info[DT_PLTGOT]);
  const PLTREL *const reloc           // 通过重定位表+偏移reloc_arg获取要解析函数的重定位信息
    = (const void *) (D_PTR (l, l_info[DT_JMPREL])
		      + reloc_offset (pltgot, reloc_arg));
  const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
  const ElfW(Sym) *refsym = sym;
  void *const rel_addr = (void *)(l->l_addr + reloc->r_offset); //rel_addr为要解析函数的重定位地址,即对应GOT表项地址
  lookup_t result;
  DL_FIXUP_VALUE_TYPE value;
  /* Sanity check that we're really looking at a PLT relocation.  */
  assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
   /* Look up the target symbol.  If the normal lookup rules are not
      used don't look in the global scope.  */
  if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
    {
      const struct r_found_version *version = NULL;
      if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
	{
	  const ElfW(Half) *vernum =
	    (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
	  ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
	  version = &l->l_versions[ndx];
	  if (version->hash == 0)
	    version = NULL;
	}
      /* We need to keep the scope around so do some locking.  This is
	 not necessary for objects which cannot be unloaded or when
	 we are not using any threads (yet).  */
      int flags = DL_LOOKUP_ADD_DEPENDENCY;
      if (!RTLD_SINGLE_THREAD_P)
	{
	  THREAD_GSCOPE_SET_FLAG ();
	  flags |= DL_LOOKUP_GSCOPE_LOCK;
	}
#ifdef RTLD_ENABLE_FOREIGN_CALL
      RTLD_ENABLE_FOREIGN_CALL;
#endif
      result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, //_dl_lookup_symbol_x 通过函数名解析得到对应函数符号表信息sym,返回对应的linkmap
				    version, ELF_RTYPE_CLASS_PLT, flags, NULL);
      /* We are done with the global scope.  */
      if (!RTLD_SINGLE_THREAD_P)
	THREAD_GSCOPE_RESET_FLAG ();
#ifdef RTLD_FINALIZE_FOREIGN_CALL
      RTLD_FINALIZE_FOREIGN_CALL;
#endif
      /* Currently result contains the base load address (or link map)
	 of the object that defines sym.  Now add in the symbol
	 offset.  */
      value = DL_FIXUP_MAKE_VALUE (result,    // 通过result和sym确定函数真实加载地址
				   SYMBOL_ADDRESS (result, sym, false));
    }
  else
    {
      /* We already found the symbol.  The module (and therefore its load
	 address) is also known.  */
      value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true));
      result = l;
    }
  /* And now perhaps the relocation addend.  */
  value = elf_machine_plt_value (l, reloc, value);
  if (sym != NULL
      && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))
    value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));
#ifdef SHARED
  /* Auditing checkpoint: we have a new binding.  Provide the auditing
     libraries the possibility to change the value and tell us whether further
     auditing is wanted.
     The l_reloc_result is only allocated if there is an audit module which
     provides a la_symbind.  */
  if (l->l_reloc_result != NULL)
    {
      /* This is the address in the array where we store the result of previous
	 relocations.  */
      struct reloc_result *reloc_result
	= &l->l_reloc_result[reloc_index (pltgot, reloc_arg, sizeof (PLTREL))];
      unsigned int init = atomic_load_acquire (&reloc_result->init);
      if (init == 0)
	{
	  _dl_audit_symbind (l, reloc_result, sym, &value, result);
	  /* Store the result for later runs.  */
	  if (__glibc_likely (! GLRO(dl_bind_not)))
	    {
	      reloc_result->addr = value;
	      /* Guarantee all previous writes complete before init is
		 updated.  See CONCURRENCY NOTES below.  */
	      atomic_store_release (&reloc_result->init, 1);
	    }
	}
      else
	value = reloc_result->addr;
    }
#endif
  /* Finally, fix up the plt itself.  */
  if (__glibc_unlikely (GLRO(dl_bind_not)))
    return value;
  return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
}

/* Return true if dynamic section in the shared library L should be
   relocated.  */
static inline bool
dl_relocate_ld (const struct link_map *l)
{
  /* Don't relocate dynamic section if it is readonly  */
  return !(l->l_ld_readonly || DL_RO_DYN_SECTION);
}
/* All references to the value of l_info[DT_PLTGOT],
  l_info[DT_STRTAB], l_info[DT_SYMTAB], l_info[DT_RELA],
  l_info[DT_REL], l_info[DT_JMPREL], and l_info[VERSYMIDX (DT_VERSYM)]
  have to be accessed via the D_PTR macro.  The macro is needed since for
  most architectures the entry is already relocated - but for some not
  and we need to relocate at access time.  */
#define D_PTR(map, i) \
  ((map)->i->d_un.d_ptr + (dl_relocate_ld (map) ? 0 : (map)->l_addr))

该函数查找函数对应的重定位表项通过:

const PLTREL *const reloc
 = (const void *) (D_PTR (l, l_info[DT_JMPREL])
		+ reloc_offset (pltgot, reloc_arg));

即通过link_map中的l_info数组中的DT_JMPREL项找到.rel.plt地址,加上先前传入参数reloc_arg的偏移,确定要解析的函数(如alarm)的重定位表项信息reloc;

然后result = _dl_lookup_symbol_x 通过函数名解析得到对应函数(alarm)符号表信息sym,返回对应的linkmap结构地址;gdb调试界面如下:

 ► 0x7ffff7fd5f6c <_dl_fixup+252>    call   _dl_lookup_symbol_x                <_dl_lookup_symbol_x>
        rdi: 0x555555559205 ◂— 0x616d006d72616c61 /* 'alarm' */
        rsi: 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f
        rdx: 0x7fffffffd5c0 —▸ 0x555555559490 ◂— 0x1200000015
        rcx: 0x7ffff7ffe650 —▸ 0x7ffff7ffe5a0 —▸ 0x7ffff7fbb7f0 —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— ...
        r8: 0x7ffff7fbb840 —▸ 0x5555555592ce ◂— 'GLIBC_2.2.5'
        r9: 0x1
        arg[6]: 0x1
        arg[7]: 0x0

最后value = DL_FIXUP_MAKE_VALUE 通过result->l_addr + (sym)->st_value 确定函数真实加载地址

函数结束会将 elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value)
会将解析的真实地址value写入到rel_addr,即对应GOT表项。

以上就是_dl_runtime_resolve解析函数地址的流程。

32/64区别

存在以下区别:

  • 在32位中,reloc_arg作为偏移量,而在64位中作为.rel.plt的数组下标
  • 结构体均升级为64位版本
  • version问题
    • version问题如下
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
{
	const ElfW(Half) *vernum =
	  (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
	ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
	version = &l->l_versions[ndx];
	if (version->hash == 0)
		version = NULL;
}

此处把(reloc->r_info)>>32作为下标取值vernum,由于我们伪造的(reloc->r_info)>>32很大,导致容易取到不可读区域。

解决方法之一是避免进入该循环,即使得l->l_info[VERSYMIDX (DT_VERSYM)]为0,地址为link_map+0x1c8。然而这需要泄露ld地址,都有地址了还打ret2dl就不礼貌了。

另一种解决方案是选择不进入if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)的大循环,而是走else分支

else
  {
	/* We already found the symbol. The module (and therefore its load
address) is also known. */
	value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true));
	result = l;
  }
#define LOOKUP_VALUE_ADDRESS(map, set) ((set) || (map) ? (map)->l_addr : 0)
/* Calculate the address of symbol REF using the base address from map MAP,
   if non-NULL.  Don't check for NULL map if MAP_SET is TRUE.  */
#define SYMBOL_ADDRESS(map, ref, map_set)				\
  ((ref) == NULL ? 0							\
   : (__glibc_unlikely ((ref)->st_shndx == SHN_ABS) ? 0			\
      : LOOKUP_VALUE_ADDRESS (map, map_set)) + (ref)->st_value)

DL_FIXUP_MAKE_VALUE用于计算函数真实值。我们只需将sym->st_value指向某个已解析函数的got表,l->addr指向目标函数和已解析函数的偏移。

在不泄露ld的情况下伪造l->addr,我们需要伪造link_map,一般需要满足:

  1. link_map中的DT_STRTAB、DT_SYMTAB、DT_JMPREL可读
  2. DT_SYMTAB结构体中的d_ptr即sym,*(sym+5) & 0x03 != 0
  3. (reloc->r_info)&0xff == 7
  4. rel_addr = l->addr + reloc->r_offset即原先需要修改的got表地址有可写权限
  5. l->l_addr + sym->st_value 为system的地址

利用

通过以上原理解析,我们知道了动态库解析一个函数地址的流程,那么我们根据该流程有以下几个攻击思路:

  1. 利用传入错误的reloc_arg使其查找到我们伪造好的.rel.plt项,实现对重定位项的劫持,解析为我们想要的函数,由延迟绑定的机制可知,我们需要先压栈我们需要的reloc_arg,随后调用plt0。并在对应地址上伪造好.rel.plt,dynsym,dynstr。在dynstr中写入想要解析的任意函数即可。
  2. 控制linkmap的l_addr(需要泄露地址),使其错误的解析到libc中其他的函数地址

具体利用参考Adcanced_ROP利用手法。

参考

dl相关攻击汇总