假的 ret2dl_resolve

发布时间 2023-08-20 14:51:00作者: giacomo捏

glibc2.36

引入 (lazy binding)

下文的分析将以如下代码为例

# include<stdio.h>

int main(){
    char c;
    c = getchar();
    printf("c: %c\n", c);
	return 0;
}

//gcc -g dl.c -no-pie -o dl

在 gdb 中下断点 b 6 使函数停 printf 执行前

image-20230721150114090

一步步执行可以看见跳转到了 printf@plt 所在的地方,这里是一段跳转的代码,将要跳转到 got.plt 中存放的地址。不过此时 got 还未初始化,存放的不是真实的地址

而是将 printf 的偏移(0)入栈,再跳转到 0x401020 (这是什么固定的地址吗?),将 link_map 入栈,最后跳转到 _dl_runtime_resolve_xsavec

image-20230721150706486

通过以上的调试过程可以知道

  • plt

    是过程链接表,存放跳转时候的代码

  • got

    完成初始化之后存放真实地址,未初始化时是 plt 附近的地址

后续 _dl_runtime_resolve_xsavec 将会调用 _dl_fixup 查找真正的函数地址并写入 got 表,具体的过程结合源码分析。

结构

节表

可以用 readelf -d dl 命令查看所有节表

image-20230721171524209

其中几个比较关键

  • STRTAB

    字符串表

  • SYMTAB

    符号表,可以用 readelf -s dl 查看

  • PLTGOT

    就是常说的 got 表

  • JUMPREL

    函数重定位表,可以用 readelf -r dl 查看

    Relocation section '.rela.plt' at offset 0x508 contains 2 entries:
      Offset          Info           Type           Sym. Value    Sym. Name + Addend
    000000404018  000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
    000000404020  000300000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0
    

结构体

这是链接器(linker)为每一共享库创建一个 link_map 结构体,所有 link_map 组织成链表。定义在 inlude/link.h 中,太长了所以只摘取了部分。

struct link_map
  {
    //libc 基址
    ElfW(Addr) l_addr;
    ...
	//dynamic section
    ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM
		      + DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM];
   ...
    //version
    struct r_found_version *l_versions;

可以在 gdb 中查看这个链结构,具体做法是把 dl-runtime.c 文件放在同一个目录下后(如果编译时没有保留 -g 选项要导入一下调试信息)就可以在 gdb 看见源码

image-20230721180231981

当执行到结构体附近的代码时可以打印结构体信息,这里有一个小技巧,set print pretty on 能打印详细的结构体信息。

image-20230721180556828

也可以用 -> 访问结构体成员,比如下一个 link_map 的名字是

image-20230721180814583

Elf64_Rel

每一个需要重定位的函数都有一个 Elf64_Rela 结构体,存储了重定位所需要的信息,这些 Elf64_Rela 组成了 JUMPREL 。

  • r_offset

    它包含解析符号的地址将存储的位置(在GOT中)。

  • r_info

    指示重新定位类型并充当符号表索引。它将用于在 DYNSYM 部分中查找相应的Elf64_Sym结构。

image-20230721181014860

printf 对应的 reloc 为,我们可以在前文 JUMPREL 中看见同样的结构。

image-20230721181153309

Elf64_Dyn

记录 dynamic table 信息

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;

Elf64_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;

_dl_fixup

完整代码

_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
    = (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);
  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,
				    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,
				   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));

  /* 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);
}

可以看到,这个函数接收两个参数,分别是我们在引入的小节中 push 的参数,link_map l 存放 libc 的信息,reloc_arg 是在 JUPREL 用于查找的序号。

symtab&strtab&pltgot&reloc

函数首先从 link_map 中找到以上节表的地址,我们先来看第一条解析 symtab 的语句。

const ElfW(Sym) *const symtab
    = (const void *) D_PTR (l, l_info[DT_SYMTAB]);

这里赋值了一个指向常量 ElfW(Sym) 类型的常量指针 symtab。根据宏,类型ELFw(Sym) 可以解析成 Elf64_sym

#define ElfW(type)	_ElfW (Elf, __ELF_NATIVE_CLASS, type)
#define _ElfW(e,w,t)	_ElfW_1 (e, w, _##t) // 64 位 w = 64
#define _ElfW_1(e,w,t)	e##w##t  // ## 用于连接成符号

至于 symtab 是如何得到的呢?在 elf/elf.h 中定义了DT_SYMTAB 为常量 6,即 l_info[6] 表示了第六个 ”Dynamic section entry“ 不会翻译(

#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 */

而 D_PTR 的宏如下

# define D_PTR(map, i) (map)->i->d_un.d_ptr

因此 D_PTR (l, l_info[DT_SYMTAB]) 表示的是

D_PTR (l, l_info[DT_SYMTAB])
    = l-> l_info[5]->d_un.d_ptr

pwndbg> p/x *l->l_info[6]
$57 = {
  d_tag = 0x6,
  d_un = {
    d_val = 0x4003d0,
    d_ptr = 0x4003d0
  }
}
pwndbg> p/x l->l_info[6]->d_un->d_ptr
$58 = 0x4003d0

其值正好是 SYMTAB。获得 strtab&pltgot&reloc 变量的值的过程于此相似,不再赘述。

随后,根据 reloc (Elf64_Rel 结构体)函数重定位的信息 r_offset,r_offset 得到 got 表真实地址和符号信息

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);

image-20230721181153309

根据宏 #define ELF64_R_SYM(i) ((i) >> 32) 可以得知,这是取 r_info 前四字节,因此 sym 指向 symtab 的序号 2 的结构体。

l->l_addr 表示程序的加载基址,这里关了 pie 所以是 0,得到了 got 表地址 rel_addr。

两个检查

第一个检查是检查 r_info 的后一字节为 0x7

assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);

#define ELF32_R_TYPE(val)   ((val) & 0xff)
#define ELF_MACHINE_JMP_SLOT	R_X86_64_JUMP_SLOT
#define R_X86_64_JUMP_SLOT	7	/* Create PLT entry */

//等价于
assert ((reloc->r_info & 0xff) == 0x7);

第二个检查是符号的 visibility 如果不满足 check 的话就表示 normal lookup rules are not used

if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)

#define ELF64_ST_VISIBILITY(o)	ELF32_ST_VISIBILITY (o)
#define ELF32_ST_VISIBILITY(o)	((o) & 0x03)

其次计算 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;
}

寻找地址、填入 got

在 libc 中定位函数地址

result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
  version, ELF_RTYPE_CLASS_PLT, flags, NULL);

加上 libc 的基址

value = DL_FIXUP_MAKE_VALUE (result,
 SYMBOL_ADDRESS (result, sym, false));

写入 got 表

return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);

ret2dl_resolve

再说吧

后话

clion 怎么跳转到定义

https://syst3mfailure.io/ret2dl_resolve/

https://xz.aliyun.com/t/5722

clion不支持代码跳转 不同文件夹之间clion无法代码跳转? - 知乎用户的回答 - 知乎 https://www.zhihu.com/question/50223053/answer/752977021