0.重要前提
在Linux
下,函数采用延迟绑定技术,是用到哪个函数才对哪个函数进行重定位.
1.基础知识
一个ELF文件是有很多个section
构成的
section
的结构
typedef struct {
Elf32_Word sh_name; // section头部字符串表节区的索引
Elf32_Word sh_type; // section类型
Elf32_Word sh_flags; // section标志,用于描述属性
Elf32_Addr sh_addr; // section的内存映像
Elf32_Off sh_offset; // section的文件偏移
Elf32_Word sh_size; // section的长度
Elf32_Word sh_link; // section头部表索引链接
Elf32_Word sh_info; // 附加信息
Elf32_Word sh_addralign; // section对齐约束
Elf32_Word sh_entsize; // 固定大小的section表项的长度
} Elf32_Shdr;
其中包括有.dynsym
, .dynstr
, .rel.dyn
, rel.plt
, .plt
, .plt.got
, .got
,.got.plt
等
.dynsym --> 动态链接符号表, _dl_fixup会用到
(dynamic linking symbol table)
.dynstr --> 动态链接字符串表, _dl_fixup会用到
.rel.dyn --> 变量重定位
.rel.plt --> 函数重定位
.plt --> 跳转表, 俗称PLT[0]
.got --> 全局变量偏移
.got.plt --> 保存全局函数偏移表
下面以read
函数作为例讲解整个延迟绑定过程:
第一步
从上面整个执行流程来看: 压入了0x0(reloc_arg) 和 ds:0x804a004(link_map)
, 作为 _dl_runtime_resolve
的参数.
接下来解释参数:
reloc_arg
使用命令分析ELF
文件:readelf -r ./pwn01
0x80482b0: 0x0804a00c 0x00000107 0x0804a010 0x00000207
0x80482c0: 0x0804a014 0x00000407 0x08ec8353 0x00009fe8
(与上面图中的数值一致)
link_map
链接器的表示信息, 链接的时候就已经写入了.
结论: 以上过程想当于执行了_dl_runtime_resolve(link_map, reloc_arg)
, 该函数会完成符号解析, 将真正的地址写入到read@got
中.
第二步
0xf7fee001<_dl_runtime_resolve+1> : push ecx
0xf7fee002<_dl_runtime_resolve+2> : push edx
0xf7fee003<_dl_runtime_resolve+3> : mov edx,DWORD PTR [esp+0x10]
0xf7fee007<_dl_runtime_resolve+7> : mov eax,DWORD || PTR [esp+0xc]
0xf7fee00b<_dl_runtime_resolve+11> : call 0xf7fe77e0 <_dl_fixup>
......
可以看到_dl_runtime_resolve
中又调用了_dl_fixup
//部分_dl_fixup源码
_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg){
//首先通过参数reloca_arg计算入口地址,DT_JMPREL即.rel.plt,reloc_offset就是reloc_arg
const PLTREL *const reloc=(const void*)(D_PTR(l,l_info[DT_JMPREL])+reloc_offset);
// 通过reloc->r_info(0x107)找到.dynsym中对应的条目
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
// 检查reloc->r_info的最低位是否为0x7, 不是则退出
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
//接着到strtab + sym->st_name中找到符号字符串, result为libc的基地址
result = _dl_lookup_symbol_x (
strtab+sym->st_name,l,&sym,l->l_scope,version,ELF_RTYPE_CLASS_PLT,flags,NULL);
// value 就是目标函数相对与libc基地址的偏移地址
value = DL_FIXUP_MAKE_VALUE(
result,sym?(LOOKUP_VALUE_ADDRESS(result)+sym->st_value):0);
// 写入指定的.got表
return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
}
有童鞋可能有疑问:
_dl_fix
前面有push 指令
, 为什么_dl_fixup
和_dl_runtime_resolve
的参数一样都是reloc_arg, link_map
呢?
小结论: _dl_fixup
最终会通过函数名来查找函数对应的地址, 因此函数名是不可能重复的.
第三步
接下来实际调试~
.rel.plt = 0x080482b4, reloc_arg = 0
typedef struct {
Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址
Elf32_Word r_info; // 符号表索引
} Elf32_Rel;
reloc-> info = 0x107; reloc-> r_offset = 0x0804a00c
接着
const ElfW(Sym) *sym = (Elf32_Rel->r_info) >> 8 = 0x1
第四步_dl_runtime_resolve
将控制权交给目标函数
2.攻击利用
_dl_runtime_resolve
有两个参数, 我们就是要控制这两个参数, 让系统最终来查找我们自己构造的函数名, 最终实现执行system("/bin/sh")