本文为看雪论坛优秀文章
看雪论坛作者ID:安和桥南
本篇文章将会尝试解释重定位,PLT,GOT以及延迟绑定,并且通过一个简单的例子动态调试追踪延迟绑定的过程。
Relocation is the process of connecting symbolic references with symbolic definitions. Relocatable files must have information that describes how to modify their section contents, thus allowing executable and shared object files to hold the right information for a process's program image. Relocation entries are these data.
即:
// 隐式加法重定位,重定位处的汇编一般会是 e8 fc ff ff ff
typedef struct {
Elf32_Addr r_offset;
uint32_t r_info;
} Elf32_Rel;
// 显式加法重定位,重定位时需要计算的加数存储在 r_addend
typedef struct {
Elf32_Addr r_offset;
uint32_t r_info;
int32_t r_addend;
} Elf32_Rela;
r_information存储了需要重定位符号表的索引和重定位类型。
r_addend 用于计算存储在可定位字段中的值。
在一段只输出hello world的程序中,采用静态链接,需要将整个glibc链接到程序中,如果500个程序都需要使用glibc中的函数,那么glibc就会被封装进500个程序中。
可不可以将多个程序都会使用的库单独剥离出来,同时在源程序和库之间建立某种联系,确保源程序在执行的时候可以调用到库函数?
① 源程序中call [email protected]
② PLT 中对应表项存储的内容是jmp [email protected],因此会跳进到`[email protected]。
③ 在绑定之前,[email protected]位置存储还是[email protected]表项的第二指令的地址,因此会跳到PLT 执行第二条指令push n,这里的n指的是puts函数在GOT中的位置,这里的入栈操作是为了找到puts函数的符号名,以及puts函数在GOT表项中所占的位置,便于后续将函数的实际地址写入GOT表。
④ 执行PLT表项第三条指令jmp PLT[0],PLT[0]首先将GOT[1]入栈,然后jmp GOT [2].
⑤ GOT[2]保存的是_dl_runtime_resolve()函数的入口地址,因此这里实际上就是调用_dl_runtime_resolve()函数,完成符号解析和重定位工作,并将puts()函数的真实地址写入[email protected]。然后将控制权交给puts()函数。
在后续调用,就会直接到GOT表项取得puts函数的地址。
PLT[0]: push GOT[1]
jmp GOT[2]
PLT[1]: __libc_start_main()
PLT[2]: jmp GOT[4]
push 0
jmp PLT[0]
...
GOT[0]: .dynmic 地址
GOT[1]: relor
GOT[2]: _dl_runtime_resolve()
GOT[3]: sys startup # 针对不同环境此处内容可能不同
GOT[4]: PLT[2]第二条指令地址
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Linux null 5.15.0-67-generic #74-Ubuntu SMP Wed Feb 22 14:14:39 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
#include <stdio.h>
int print_hello() {
printf("hello PLT and GOT\n");
}
int main() {
print_hello();
return 0;
}
gcc main.c -o test -save-temps -m32 -g -Wl,-z,lazy
-save-temps 会保存所有的中间输出结果。
-m32 表示输出的是32程序。
-Wl,-z,lazy强制开启延迟绑定。
-g 方便调试。
objdump -M intel -d main.o
00000000 <print_hello>:
0: 53 push ebx
1: 83 ec 14 sub esp,0x14
4: e8 fc ff ff ff call 5 <print_hello+0x5>
9: 81 c3 02 00 00 00 add ebx,0x2
f: 8d 83 00 00 00 00 lea eax,[ebx+0x0]
15: 50 push eax
16: e8 fc ff ff ff call 17 <print_hello+0x17>
1b: 83 c4 18 add esp,0x18
1e: 5b pop ebx
1f: c3 ret
00000024 <main>:
20: 55 push ebp
21: 89 e5 mov ebp,esp
23: 83 e4 f0 and esp,0xfffffff0
26: e8 fc ff ff ff call 27 <main+0x7>
2b: b8 00 00 00 00 mov eax,0x0
30: c9 leave
31: c3 ret
objdump -M intel -d test
0000119d <print_hello>:
119d: 53 push ebx
119e: 83 ec 14 sub esp,0x14
11a1: e8 fa fe ff ff call 10a0 <__x86.get_pc_thunk.bx>
11a6: 81 c3 32 2e 00 00 add ebx,0x2e32
11ac: 8d 83 30 e0 ff ff lea eax,[ebx-0x1fd0]
11b2: 50 push eax
11b3: e8 98 fe ff ff call 1050 <[email protected]>
11b8: 83 c4 18 add esp,0x18
11bb: 5b pop ebx
11bc: c3 ret
000011bd <main>:
11bd: 55 push ebp
11be: 89 e5 mov ebp,esp
11c0: 83 e4 f0 and esp,0xfffffff0
11c3: e8 d5 ff ff ff call 119d <print_hello>
11c8: b8 00 00 00 00 mov eax,0x0
11cd: c9 leave
11ce: c3 ret
4: e8 fc ff ff ff call 5 <print_hello+0x5>
16: e8 fc ff ff ff call 17 <print_hello+0x17>
11a1: e8 fa fe ff ff call 10a0 <__x86.get_pc_thunk.bx>
11b3: e8 98 fe ff ff call 1050 <puts@plt>
readelf -s test
20: 000010a0 4 FUNC GLOBAL HIDDEN 14 __x86.get_pc_thunk.bx
27: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.0
S 是符号的值
A 是重定位条目中的加数,在显示加数中,会使用ElfN_REL中的值,而隐式的则是在call指令后面。
P 是要进行重定位的存储单位的地址。
即:__x86.get_pc_thunk.bx: $0x10a0 + 0xffff fffc -0x11a2 = 0xffff fefa$
```c
ElfW(Addr) _dl_runtime_resolve (ElfW(Addr) *ref)
{
/* ... */
}
```
struct link_map *l;
ElfW(Addr) result;
l = ((struct link_map *) D_PTR (ref[1], l_info[DT_PLTGOT]))->l_prev;
ElfW(Addr) reloc_arg = D_PTR (ref[1], l_info[DT_RELA]->d_un.d_ptr)
+ sizeof (ElfW(Rela));
result = _dl_fixup (l, ref[0], reloc_arg, 0);
return result;
看雪ID:安和桥南
https://bbs.kanxue.com/user-home-882195.htm
# 往期推荐
3、无路远征——GLIBC2.37后时代的IO攻击之道 house_of_秦月汉关
4、Wibu Codemeter 7.3学习笔记——Codemeter服务端
5、Windows应用层实现VmWare穿透读写-实现无签名驱动加载
6、wibu软授权
球分享
球点赞
球在看
点击“阅读原文”,了解更多!