got/plt之_dl_runtime_resolve

前言

  在上篇文章中,已经简单介绍了plt/got表的基本知识,但没有深入下去,这篇文章将继续理解动态链接的实现过程,同时也是ret2dlresolve基础知识。

_dl_runtime_resolve

  首先回顾下上篇的内容(主要是延迟绑定的概念)可以用下面这个GIF动图来概括:


延迟绑定

  但这里还有很多没有说清楚的地方,就是_dl_runtime_resolve是怎么找到目标函数地址的,这也是这篇文章主要叙述的内容。

  开始之前希望先入一些概念,以免在后面的名词上有疑惑。

  • .rel.plt:保存了重定位表信息,使用Elf32_Rel结构,包括了函数在.got.plt表中的偏移(在开了pie的情况下)或地址。
  • .got.plt:保存了重定位地址。
  • .dynsym:保存了符号的Elf32_Sym结构(包括符号名称的字符串),符号可代指函数或变量。

  .rel.plt.got.plt有什么区别呢,可以看下图:

  .rel.plt是对函数引用的修正,它修正的位置位于.got.plt,也是函数重定位的入口,包含了更多的信息,如寻址方式,sym符号的地址(通过解析,后面会介绍到)等。

  下面通过一个例子来说明。

  首先看一下程序中包含的sections:readelf -S demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1
[ 5] .dynsym DYNSYM 080481cc 0001cc 000060 10 A 6 1 4
[ 6] .dynstr STRTAB 0804822c 00022c 000050 00 A 0 0 1
[ 9] .rel.dyn REL 080482a8 0002a8 000008 08 A 5 0 4
[10] .rel.plt REL 080482b0 0002b0 000018 08 AI 5 24 4
[11] .init PROGBITS 080482c8 0002c8 000023 00 AX 0 0 4
[12] .plt PROGBITS 080482f0 0002f0 000040 04 AX 0 0 16
[13] .plt.got PROGBITS 08048330 000330 000008 00 AX 0 0 8
[14] .text PROGBITS 08048340 000340 0001b2 00 AX 0 0 16
[22] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4
[23] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[24] .got.plt PROGBITS 0804a000 001000 000018 04 WA 0 0 4
[25] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4
[26] .bss NOBITS 0804a020 001020 000004 00 WA 0 0 1

  这里只截取了比较有价值的段。再看看重定位段:

1
2
3
4
5
6
7
8
9
10
11
$ readelf -r demo

Relocation section '.rel.dyn' at offset 0x2a8 contains 1 entries:
Offset Info Type Sym.Value Sym. Name
08049ffc 00000306 R_386_GLOB_DAT 00000000 __gmon_start__

Relocation section '.rel.plt' at offset 0x2b0 contains 3 entries:
Offset Info Type Sym.Value Sym. Name
0804a00c 00000107 R_386_JUMP_SLOT 00000000 read@GLIBC_2.0
0804a010 00000207 R_386_JUMP_SLOT 00000000 alarm@GLIBC_2.0
0804a014 00000407 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0

  在sections中看到.got.plt的开始地址是0x0804a000,而在.rel.plt表的第一项是0x0804a00c,这是因为got.plt表的前三项是:

  • got[0]: 本ELF动态段(.dynamic段)的装载地址
  • got[1]:本ELF的link_map数据结构描述符地址
  • got[2]:_dl_runtime_resolve函数的地址

  所以.rel.plt的第一项就是0x0x0804a000 + 4*3 = 0x0804a00c

动态调试

  这里我们跟踪read函数。

  IDA里:

  这里为什么是push 0x0

  这是因为read是在.rel.plt表中第一个字段,所以在索引时就是.rel.plt->[0](逻辑上),这样就可以取出read的Elf32_Rel结构体。

  怎么验证呢?使用readelf -d demo

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
Dynamic section at offset 0xf14 contains 24 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000c (INIT) 0x80482c8
0x0000000d (FINI) 0x80484f4
0x00000019 (INIT_ARRAY) 0x8049f08
0x0000001b (INIT_ARRAYSZ) 4 (bytes)
0x0000001a (FINI_ARRAY) 0x8049f0c
0x0000001c (FINI_ARRAYSZ) 4 (bytes)
0x6ffffef5 (GNU_HASH) 0x80481ac
0x00000005 (STRTAB) 0x804822c
0x00000006 (SYMTAB) 0x80481cc
0x0000000a (STRSZ) 80 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x804a000
0x00000002 (PLTRELSZ) 24 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x80482b0
0x00000011 (REL) 0x80482a8
0x00000012 (RELSZ) 8 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x8048288
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x804827c
0x00000000 (NULL) 0x0

  可以看到有个JMPREL段,地址为0x80482b0跟前面看到的[10] .rel.plt一样,这个是.rel.plt的地址。查看偏移为0的地方:

1
2
3
gdb-peda$ x /6xw 0x80482b0+0
0x80482b0: 0x0804a00c 0x00000107 0x0804a010 0x00000207
0x80482c0: 0x0804a014 0x00000407

  关于Elf32_Rel的结构如下:

1
2
3
4
5
6
7
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;
#define ELF32_R_SYM(val) ((val) >> 8)
#define ELF32_R_TYPE(val) ((val) & 0xff)

  0x0804a00c就是read@got.plt的地址,同时也是r_offset, 0x00000107就是r_info

  再继续跟下去,就会得到如下:

  就相当于调用了 _dl_runtime_resolve((link_map *)m, 0),其中link_map提供了运行时的必要信息, 而0则是read函数在.rel.plt中的偏移。

  link_map的数据结构是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 
Structure describing a loaded shared object.
The `l_next' and `l_prev' members form a chain of all the shared objects loaded at startup.
These data structures exist in space used by the run-time dynamic linker; modifying them may have disastrous results.
*/

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

  进入到_dl_runtime_resolve的处理过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
_dl_runtime_resolve:
cfi_adjust_cfa_offset (8)
pushl %eax # Preserve registers otherwise clobbered.
cfi_adjust_cfa_offset (4)
pushl %ecx
cfi_adjust_cfa_offset (4)
pushl %edx
cfi_adjust_cfa_offset (4)
movl 16(%esp), %edx # Copy args pushed by PLT in register. Note
movl 12(%esp), %eax # that `fixup' takes its parameters in regs.
call _dl_fixup # Call resolver.
popl %edx # Get register content back.
cfi_adjust_cfa_offset (-4)
movl (%esp), %ecx
movl %eax, (%esp) # Store the function address.
movl 4(%esp), %eax
ret $12 # Jump to function address.

  此时栈的状态如下:

  在函数内部接着调用了_dl_fixup。其实现是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 函数声明,reloc_arg其实是我们 push 进来的 0x0
_dl_fixup (struct link_map *l, ElfW(Word) reloc_arg)

// 得到 symtab 符号表
const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
// 取出 offset 在.rel.plt表中的Elf32_Rel数据结构
// DT_JMPREL 就是 JMPREL .rel.plt 的起始地址
// 以 read 为例,那么 reloc->r_offset = 0x0804a00c, reloc->r_info = 0x107
const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_arg);

const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];

// ELF_MACHINE_JMP_SLOT == R_386_JUMP_SLOT这是一种寻址方式,表示被修正的位置只需要填入符号的地址即可。
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
// 下面单独解释
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,version, ELF_RTYPE_CLASS_PLT, flags, NULL);
// 下面单独解释
value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);

//将已经解析完的函数地址写入相应的GOT表中
return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);

  讲解一:

1
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];

  ElfW(Sym)的数据结构是:

1
2
3
4
5
6
7
8
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_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;

  找到.dynsym中对应的结构体。这里注意类型强制转换,定义在上面,以read为例:

1
2
ELFW(R_SYM) (reloc->r_info) = 0x107 >> 8 = 1  // 代表目标在.dynsym中的index
ELFW(R_TYPE)(reloc->r_info) = 0x107 && 0xff = 7 // 寻址方式

  查看.dynsym,read确实是第1个:


注意看第一行

  相关参数如下:

1
2
3
4
0x00000005 (STRTAB)                     0x804822c    // 字符串表
0x00000006 (SYMTAB) 0x80481cc // 符号表
0x0000000a (STRSZ) 80 (bytes)
0x0000000b (SYMENT) 16 (bytes) // 符号长度

  gdb中如下(*16是因为SYMENT)
ElfW(Sym)

  _dl_lookup_symbol_x根据strtab+sym->st_name在字符串表中找到函数名,然后进行符号查找获取libc基地址result。

1
2
3
/* Search loaded objects' symbol tables for a definition of the symbol
UNDEF_NAME, perhaps with a requested version for the symbol.*/
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,version, ELF_RTYPE_CLASS_PLT, flags, NULL);

  strtab + sym->st_name在gdb中如下:

  这里读者可以按上述的数据结构翻译一下。

  讲解二:

1
2
// 将要解析的函数的偏移地址加上libc基址,就可以获取函数的实际地址
value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);

  所以上面两条语句就类似于pwntools里查找/bin/sh一样(next(libc.search('/bin/sh')))。

  以上就是_dl_runtime_resolve/_dl_fixup处理的细节。

Overview

参考链接

# CTF, pwn

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×