人麻了,我写的不见了,就看看下面那个师傅的吧,回头再补坑
Ret2dlresolve攻击解析 - chan’s blog

Q1:什么是动态链接?

  • 把链接这个过程推迟到了运行时再进行,在可执行文件装载时或运行时,由操作系统的装载程序加载库

Q2:为什么需要动态链接?

  • 解决静态链接的空间浪费和更新困难问题

Q3:动态链接的实现?

  • Linux:.so;Windows:.dll

  • 共享的是代码,私有的是数据

  • 共享对象的最终装载地址在编译时是不确定的,而是在装载时,装载器根据当前地址空间的空闲情况,动态分配一块足够大小的虚拟地址空间给相应的共享对象

Q4:动态链接对象共享难题?

  • 指令部分有两个地方需要在装载时确定

    • 一是指令访问其他模块的数据。如果其他模块的数据的地址需要再装载时才能确定,这就必须修改指令中的地址
    • 二是指令中调用其他模块的函数
  • 解决方法:全局偏移表 GOT(global offset table)

Q5:GOT?

  • 数据段中的一个指针数组:
    • 存储:存放跨模块的数据/函数的地址
    • 动态修改:在装载时动态填入
    • 访问:通过GOT的指针间接访问
  • 说白了GOT是所有需要动态装载模块的一个wrapper,是一个存放具体跳转地址的指针
  • 在ELF中,.got用来保存全局变量引用的地址,.got.plt用来保存函数引用的地址

Q6:x86 PLT过程?

PLT基本流程:

1
2
3
4
5
6
7
8
9
10
<br>/** csapp中GOT指的是(.got.plt + got)**/ PLT[0]: # 被所有的.plt实体所共享 push *(GOT+4) # 4.将Module ID(GOT[1])压入栈 jmp *(GOT+8) # 5.调用_dl_runtime_resolve()(GOT[2]),根据id+n完成符号解析与重定位,并将解析地址填入到bar@GOT中(也就是下次就不需要解析了) ... ... bar@plt( PLT[n] ): jmp *(bar@GOT) # 1.如果符号已绑定,则跳转到符号位置;如果未绑定,则跳转到下面 push n # 2.将符号在重定位表中的下标压入栈 jmp PLT0 # 3.跳转到PLT0处<br>

一句话描述:PLT首先会查找.got.plt节(GOT)。如果GOT找不到符号地址(主要由于延迟绑定 lazy-binding),那么PLT将会调用一个动态加载器中的函数_dl_runtime_resolve()

  • 在主ELF镜像的.dynstr节中找到NULL为终结符的字符串puts\0
  • 然后在所有加载的共享目标中找到puts的地址(如libc.so.6

e.g.

在执行完_dl_runtime_resolve()函数之后read@GOT的数据就被修改为装载到堆上的地址,即

Q7:.got.plt

  • .got.plt前面三项:
1
2
3
4
5
6
7
8
9
10
11
12
13
<br>------------------------.dynamic段地址------------------------本模块ID------------------------_dl_runtime_resolve()地址------------------------导入函数1------------------------导入函数2------------------------...------------------------<br>
  • 在GDB中,我们可以看到.got.plt第一个导入函数的地址为0x80498c4

根据前面.got.plt的结构,我们可以知晓Module ID地址为0x80498bc_dl_runtime_resolve()地址为0x80498c0

Q7:_dl_runtime_resolve()

_dl_runtime_resolve()函数有两个参数,一个是link_map,在本例中为0xf7ffd940,而另一个参数是reloc_index,在本例中为0x8。请注意,这两个参数都是存放在栈中的,由于当前环境为x86,参数是存放在栈上的;但当环境为x64时,这两个参数仍然是存放在栈中的,原因也很简单,因为rdi, rsi, rdx(rcx, r8, r9...)现在存放的是被调函数的参数,为了减少额外的保护现场汇编码,这里直接将参数入栈处理。

_dl_runtime_resolve(link_map, reloc_index)做了如下的事:

  • 找到目标函数名以NULL截断的字符串(如”read”);
  • 在所有已经载入的库(共享目标)中寻找该地址;
  • 将该地址写回GOT(即将找到的地址填入read@got.plt中);
  • 跳转到read()并执行。

这里存在一个问题,就是目标函数名是如何找到的?我们将在下一问中进行解答。

Q8:寻找的函数名是如何确定的?

总体寻址流程[3]:


细节:

.dynamic段:

  • 存放.dynsym.dynstr地址

  • 存放Elf_Dyn实体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <br>typedef struct { Elf32_Sword d_tag; union { Elf32_Word d_val; Elf32_Addr d_ptr; } d_un;} Elf32_Dyn;// x64typedef 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;<br>

link_map.l_info

  • 动态加载器存储这些实体的结构体(link_map结构体成员很多[4],我们仅需关注l_info
  • 定义:
1
2
3
4
5
6
7
8
9
10
11
12
<br>struct link_map {... /* 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];...<br>

d_tag (dynamic entry type) 定义在elf/elf.h[5]中:

1
2
3
4
5
6
<br>/* Legal values for d_tag (dynamic entry type). */...#define DT_STRTAB 5 /* Address of string table (.dynstr) */#define DT_SYMTAB 6 /* Address of symbol table (.dynsym) */...#define DT_JMPREL 23 /* Address of PLT relocs (.plt.dyn) */<br>

在Q6中,PLT[0]是有将Module ID的值压入堆栈,而该值就是link_map:

那link_map到l_info的偏移值是多少呢?A:+8 * 位长(x86:4;x64:8)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<br>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; ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM + DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM];<br>

即l_info的地址为link_map + 8 * (4/8),那么有:

Member Address
link_map->l_info[DT_STRTAB] link_map_base + (8 + 5 = 13) * (4/8)
link_map->l_info[DT_SYMTAB] link_map_base + (8 + 6 = 14) * (4/8)
link_map->l_info[DT_JMPREL] link_map_base + (8 + 23 = 31) * (4/8)

然后我们可以查看.dynstr存放的数据,如下所示:


至此,我们得到了.rel.plt.dynstr.dynsym的地址。接下来,我们根据上述的寻址流程一步一步找到对应的函数名:

如前所述,reloc_index在此例中为0x8。那么read函数Elf_Rel结构体的地址为0x08048304 + 8 = 0x804830c,即

这里显然0x080498c8r_offset,而0x207r_info

1
2
3
<br>// glibc/elf/elf.h#define ELF32_R_SYM(val) ((val) >> 8)#define ELF32_R_TYPE(val) ((val) & 0xff)<br>

因此,read对应的Elf_SYM结构体在.dynsym的偏移为ELF32_R_SYM(r_info)= 0x207 >> 8 = 2

1
2
3
4
5
6
7
8
9
<br>typedef struct{ Elf32_Word st_name; /* Symbol name (string tbl index) (16 bits)*/ Elf32_Addr st_value; /* Symbol value (16 bits)*/ Elf32_Word st_size; /* Symbol size (16 bits)*/ unsigned char st_info; /* Symbol type and binding (8 bits)*/ unsigned char st_other; /* Symbol visibility (8 bits) */ Elf32_Section st_shndx; /* Section index (16 bits)*/} Elf32_Sym;<br>

sizeof(struct Elf32_Sym)=4*4=0x10(注意这里结构体内存对齐),那么对应Elf_SYM结构体的起始位置为0x080481ac + 0x10 * 0x2 = 0x80481cc,则有

🤔 哎嘿,这里居然不是地址吗?那这里0x5c是个啥玩意??我们不妨猜测一下,该地址是搜索字符串在.dynstr的偏移值?我们来验证一下:

这样一来,我们就得到了函数名,然后链接器将在所有加载的共享代码中找到包含该字符串的函数地址,并将该地址填入GOT表中。