0%

ELF文件动态链接过程

5f9bb3398d07e.jpg

ret2_dl_runtime_resolve学习第二步

ELF文件动态链接过程

这里我们以print函数为例,分析他的链接过程,首先在第一次调用print函数时,程序会跳到print_plt表,在plt表中我们可以看到他包含3条指令,其实每一个plt表都只有这三条指令。只有PLT[0]是不一样的。

1
2
3
0x8048430 <printf@plt>:	jmp    DWORD PTR ds:0x804a00c
0x8048436 <printf@plt+6>: push 0x0
0x804843b <printf@plt+11>: jmp 0x8048420

第一次调用的时候,程序会跳到相应的got表处,got表中存放的是0x8048436 <printf@plt+6>: push 0x0的地址。

image-20201030184302058.png

这里0x8048436 <printf@plt+6>: push 0x0这里的0x0代表的是printf在.rel.plt节的偏移量(reloc_arg),类似的gets函数是0x1.而offest这个域表示的是printf函数在GOT表项中的位置,0804a00c,从printf@plt的第一条指令可以看出这一点。

向堆栈中压入这个偏移量的目的一是找到printf函数的符号名,二是找到printf函数地址在GOT表项中的位置,以便后面定位到printf的相对虚地址时写入到这个GOT表项。

1
2
3
4
5
6
7
8
9
10
11
12
重定位节 '.rel.plt' 位于偏移量 0x3ac 含有 10 个条目:
偏移量 信息 类型 符号值 符号名称
0804a00c 00000107 R_386_JUMP_SLOT 00000000 printf@GLIBC_2.0
0804a010 00000207 R_386_JUMP_SLOT 00000000 gets@GLIBC_2.0
0804a014 00000307 R_386_JUMP_SLOT 00000000 time@GLIBC_2.0
0804a018 00000407 R_386_JUMP_SLOT 00000000 puts@GLIBC_2.0
0804a01c 00000507 R_386_JUMP_SLOT 00000000 __gmon_start__
0804a020 00000607 R_386_JUMP_SLOT 00000000 srand@GLIBC_2.0
0804a024 00000707 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
0804a028 00000807 R_386_JUMP_SLOT 00000000 setvbuf@GLIBC_2.0
0804a02c 00000907 R_386_JUMP_SLOT 00000000 rand@GLIBC_2.0
0804a030 00000a07 R_386_JUMP_SLOT 00000000 __isoc99_scanf@GLIBC_2.7

printf@plt的第三条指令,是跳到了PLT[0],PLT[0]中只有俩条指令,第一条指令是将0x804a004压入栈里,这个地址就是GOT[1],也就是共享库链接的地址(link_map)。然后第二条指令会跳到GOT[2]中所保存的地址,即_dl_runtime_resolve函数的入口。

1
2
► 0x8048420                              push   dword ptr [_GLOBAL_OFFSET_TABLE_+4] <0x804a004>
0x8048426 jmp dword ptr [0x804a008] <0xf7fee000>

_dl_runtime_resolve的定义如下:

1
2
3
4
5
6
7
8
9
10
11
0xf7fee000 <+0>:	push   eax
0xf7fee001 <+1>: push ecx
0xf7fee002 <+2>: push edx
0xf7fee003 <+3>: mov edx,DWORD PTR [esp+0x10]
0xf7fee007 <+7>: mov eax,DWORD PTR [esp+0xc]
0xf7fee00b <+11>: call 0xf7fe77e0 <_dl_fixup>
0xf7fee010 <+16>: pop edx
0xf7fee011 <+17>: mov ecx,DWORD PTR [esp]
0xf7fee014 <+20>: mov DWORD PTR [esp],eax
0xf7fee017 <+23>: mov eax,DWORD PTR [esp+0x4]
0xf7fee01b <+27>: ret 0xc

从printf函数到现在,一共有俩次压栈操作,一次是压入printf函数的重定位信息的偏移量(0x0),一次是GOT[1],然后进入_dl_runtime_resolve函数后,首先是3次push操作,保存寄存器值,然后俩次mov将之前俩次压入的数放到edx,eax中,之后调用_dl_fixup函数找到printf函数的实际加载地址,将它写到GOT中,并把这个地址压入eax作为_dl_runtime_resolve函数的返回值,之后将之前edx和ecx的值从栈中恢复到edx,ecx里,mov DWORD PTR [esp],eax eax_`将printf函数的实际加载地址写入此时esp指向的地址。之后的mov将eax的值恢复,之后跳到printf函数,并将栈平衡,使栈顶指向printf函数的返回地址(main函数中callq的下一条指令),这样当puts函数返回时程序就走到正确的位置执行。

当第二次调用puts函数时,由于其地址已经存在GOT对应表项中,直接根据地址跳转就可以。

函数_dl_fixup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
DL_FIXUP_VALUE_TYPE
attribute_hidden __attribute ((noinline)) 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]);

//通过参数reloc_arg计算重定位入口,这里的DT_JMPREL即.rel.plt, reloc_offset即reloc_arg
const PLTREL *const reloc
= (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);

//根据函数重定位表中的动态链接符号表索引,即r_info字段,获取函数在动态链接符号表中对应的条目。
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
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
//根据strtab+sym->st_name在字符串表中找到函数名,然后进行符号查找获取libc基地址result
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. */
//将要解析的函数的偏移地址加上libc基址,就可以获取函数的实际地址
value = DL_FIXUP_MAKE_VALUE (result,
sym ? (LOOKUP_VALUE_ADDRESS (result)
+ sym->st_value) : 0);
}
else
{
/* We already found the symbol. The module (and therefore its load
address) is also known. */
value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
result = l;
}

/* And now perhaps the relocation addend. */
value = elf_machine_plt_value (l, reloc, value);

//将已经解析完的函数地址写入相应的GOT表中
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, reloc, rel_addr, value);
}

主要的操作是:通过参数reloc_arg确定重定位表中该函数的重定位表项;再通过该重定位表项的r_info字段,在动态链接符号表中确定该函数的符号表项,以及类型,并进行一些检查。再由动态链接符号表项的st_name在动态链接字符串表中确定函数名称。

参考链接

https://www.cnblogs.com/gm-201705/p/9901553.html

https://www.cnblogs.com/gm-201705/p/9901555.html