这几天学习了 return-to-dl-resolve 的知识 在这里做一些总结。
看了两个师傅的博客
首先我们要了解到的是在ELF文件中 存在着一种延时绑定机制 叫做 : lazy binding。这种方式会在第一次调用一个函数时启动。
关于 lazy binding
- 在第一次调用程序的一个函数时。会去call 一个函数。
- 然而这个call 函数指向的地方(0x80483d0)的代码为
这个时候发现 在 write@plt 的第一个代码是一个 jmp 跳到 (0x804a01c)保存的指针去。
但是发现这个 地址并不是我们想要去的 wirte 函数的地址 而是指向了 write@plt 的下一行代
- 接下来 程序进行了 一个 push 和 一个 jmp指令。跳转到 (0x8048380)而这个地址可以用 readelf -S 文件名来找到 发现这个 地址是.plt的起始地址也就是PLT[0]
- 在jmp到这个 地址后发现有 push 了一个值然后跳转到了 _dl_runtime_resolve 函数
然后 调用这个 函数 实现 延迟绑定。 这个函数的 原型为
_dl_runtime_resolve(*link_map, rel_offset)
rel_offset 是 2.中push的值,link_map为 3.中push的值。
从而执行 找到write函数的真实地址 从而在下一次调用的时候直接跳转到 write函数去。
结合 IDA 反编译
- 第一次call 函数会跳转到 .plt
- 然后跳转到 0x804a01c的地址保存的地方 这个地方应该为
- 但是第一次 还没有保存write 函数真实的 地址所以会跳转到 .plt 的下一行代码0
- 从而去 push 参数 实现 将 write函数的真实地址保存在 .got.plt(保存函数引用位置) 上方便下次调用这个函数时能直接找到 write函数的地址
上面的就是延迟绑定的基本原理为了更深入了解return-to-dl-resolve 的实现我们还要学习一下 elf 文件。
ELF 可执行文件 由 ELF头部,程序头部表和对应的段,节头部表和其对应的节组成。
- 在程序头部表中会有 一个 PT_DYNAMIC 的段 ,这个段包含 .dynamic 节区,这个类型的段的 结构为(这个 .dynamic 节区也被称为 “重定位节区” 可以用到 readelf -d 查看):
readelf -d
- 在节区中包含着 目标文件的 所有的信息用一个结构保存着。( 其中Type为REL的节区包含重定位表项。可以用 readelf -S 查看)
readelf -S
- 记录重定位信息的节区中 。.rel.plt 用于函数定位 .rel.dyn 用于变量定位。而 REL 类型的节 的结构体为 (可以用 readelf -r 查看着两个节区)注意在 REL 类型的节中 .rel.plt 中的 info 应该如0x607 最后一位为7 而这个7 是为了在 之后找到 对应的 R_386_JUMP_SLOT。
readelf -r
其中的 .rel.plt 就是 IDA 反编译过后 .got.plt的值
- 查看资料发现 .got节 保存着全局变量偏移表,.got.plt 节保存全局函数偏移表。也就是 REL 结构体中的
在我们实现 延迟绑定时 还会调用到一个类型为 SYM的节叫做 .dynsym,这个节的 结构为(可以用到readelf -s 查看)主要用于找到REL对应的函数符号表信息。用到
ELF32_R_SYM(Elf32_Rel->r_info)
readelf -s
然后上一个 .dynsym 我们得到了 num 然后通过这个 num 在 .dynstr中找到对应的函数的字符串。
Elf32_Sym[num]->st_name=0x4c(.dynsym + Elf32_Sym_size(0x10) * num)
在.dynsym的地址基础上加上 num*0x10 这个地址保存的值, 这个值是我们需要的 函数字符串保存在 .synstr节上的偏移。比如 这个write 函数的 .rel结构体中 info = 0x607 所以这个num = 6 <-- (0x607>>8)
通过readelf -S
得到这两个节的地址
通过偏移找到write函数的字符串在 .dynstr中的偏移
用.dynstr基地址+偏移得到函数字符串
总结return-to-dl-resolve调用步骤:
- 首先找到函数 的reloc entry (reloc 入口)rel.plt结构体offset 和 info。push REL_offset
Elf32_Rel * reloc = JMPREL + reloc_offset;
- 然后计算 .dynsym 中对应的节入口。
Elf32_Sym * sym = &SYMTAB[ ELF32_R_SYM (reloc->r_info) ];
- 接着验证.rel.plt中 info 的最后一字为 7
assert (ELF32_R_TYPE(reloc->r_info) == R_386_JMP_SLOT);
- 在通过 info 中的
num = (reloc->r_info)>>8;
找到.dynsym 中 存的对应字符串的偏移Elf32_Sym[num]->st_name=0x4c(.dynsym + Elf32_Sym_size(0x10) * num)
,然后在 .dynstr 表中找到对应的 字符串的位置,然后通过字符串搜索到 函数的真实地址 。 - 函数地址写入对应got表 ,然后执行函数。以上就是关于 延迟绑定 和 ELF动态链接文件的学习。
// param link_map:链接标识符 // param reloc_offset:就是我们在调用函数的时候传进去的那个参数 _dl_runtime_resolve(struct link_map *l, ElfW(Word) reloc_offset) { // 首先找到plt中的相对位置,也即是.rel.plt中的对象ELF_rel const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); // 然后找到.dynsym中的Elf_Sym对象 const ElfW(Sym) *sym = &symtab[ELF32_R_SYM(reloc->r_info)]; // 检查reloc->r_info的最低位R_386_JUMP_SLOT=7 assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); // 通过加上strtab加上st_name,得到当前函数对应的字符串名,最后返回result result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL); // 通过查找对应字符串,找到此时的read的函数地址 value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0); // 最后把地址填充到之前那个位置上 return elf_machine_fixup_plt (l, result, reloc, rel_addr, value); }
这知识程序的运行方法 想要利用这个 漏洞要 充分理解明白这个 机制的运行。
1.程序找到对应的重定位函数。
2.通过 num 找到对应函数的 字符串 从而找到函数的真实地址。
攻击手段。
修改.dynstr节中的字符串为我们需要的字符串这样就能在绑定是找到我们想利用的函数。
修改REL 类型节的 reloc_offset 也就是偏移实现 绑定是 由偏移定位到我们伪造的一个表 从而定位到我们需要的函数。