最早是2015年在一篇论文中提出的攻击技术.
How the ELF Ruined Christmas
因为通常漏洞利用包含两个阶段
- 信息泄露, 得知内存布局
- 通过已知内存布局, 利用漏洞
但由于不是常常能够得知内存信息, 所以ret2dl-resolve是一种仅仅利用ELF文件格式和动态装载器弱点进行漏洞利用的方法.
符号解析图示
.rel.plt段由Elf_Rel结构体构成
Elf_Rel结构体
struct Elf32_Rel {
Elf32_Addr r_offset; // Location (file byte offset, or program virtual addr)
Elf32_Word r_info; // Symbol table index and type of relocation to apply
// These accessors and mutators correspond to the ELF32_R_SYM, ELF32_R_TYPE,
// and ELF32_R_INFO macros defined in the ELF specification:
Elf32_Word getSymbol() const { return (r_info >> 8); }
unsigned char getType() const { return (unsigned char)(r_info & 0x0ff); }
void setSymbol(Elf32_Word s) { setSymbolAndType(s, getType()); }
void setType(unsigned char t) { setSymbolAndType(getSymbol(), t); }
void setSymbolAndType(Elf32_Word s, unsigned char t) {
r_info = (s << 8) + t;
}
};
.dynsym段由Elf_Sym结构体构成
Elf_Sym结构体(32位, 源码见ELF.h
// Symbol table entries for ELF32.
struct Elf32_Sym {
Elf32_Word st_name; // Symbol name (index into string table)
Elf32_Addr st_value; // Value or address associated with the symbol
Elf32_Word st_size; // Size of the symbol
unsigned char st_info; // Symbol's type and binding attributes
unsigned char st_other; // Must be zero; reserved
Elf32_Half st_shndx; // Which section (header table index) it's defined in
// These accessors and mutators correspond to the ELF32_ST_BIND,
// ELF32_ST_TYPE, and ELF32_ST_INFO macros defined in the ELF specification:
unsigned char getBinding() const { return st_info >> 4; }
unsigned char getType() const { return st_info & 0x0f; }
void setBinding(unsigned char b) { setBindingAndType(b, getType()); }
void setType(unsigned char t) { setBindingAndType(getBinding(), t); }
void setBindingAndType(unsigned char b, unsigned char t) {
st_info = (b << 4) + (t & 0x0f);
}
};
最后 .dynstr段由函数名称字符串构成
作用解析:
其中st_name是.dynstr的偏移, 索引到符号名字符串, st_value存放符号虚拟地址, 符号没导出时为null.
导入符号解析时会进行重定位, 重定位项是一个Elf_Rel结构体的实例, 组成.rel.plt段用于导入函数, 和.rel.dyn段用于导入全局变量. Elf_Rel结构体有两个域, 分别是r_offset, r_info, r_offset保存符号写入内存的绝对地址, r_info前三位作为.dynsym段的无符号下标索引Elf_Sym结构体.
程序导入函数的流程可能不是很简单明了, 但总结起来是4个步骤:
- 动态链接器在.dynstr段添加函数名称字符串
- 在.dynsym段添加一个指向函数名称字符串的结构体Elf_Sym
- 在.rel.plt段添加指向Elf_Sym的Elf_Rel结构体.
- Elf_Rel结构体的r_offset构成GOT表, 保存在.got.plt段中.
ret2dl-resolve的核心就是围绕导入函数的延迟绑定机制做文章, 只有调用到函数时才会用PLT表更新GOT表, 索引到函数地址. 根据开启的RELRP保护机制, ret2dl-resolve攻击技术有三种变化.
关闭 RELRO 保护
Partial RELRO 保护: .dynamic 段等会被标识为只读
Full RELRO 保护: 禁用延迟绑定, .got.plt一开始就被完全初始化, 且标为只读.
现在讨论前两种方法
关闭 RELRO 保护
.dynamic段可写, 动态链接器从其中的DT_STRTAB条目获取.dynstr段地址, 攻击者通过修改相应DT_STRTAB条目, 使动态链接器访问.bss段上伪造的字符串表, 最终解析到execve()函数并执行.
Partial RELRO 保护
.dynamic段不可写, _dl_runtime_resolve()函数第二个参数reloc_index是Elf_Rel在.rel.plt的偏移, 如果控制reloc_index很大, 超出.rel.plt的地址然后落在.bss段, 就可以访问伪造的Elf_Rel, 然后化归到关闭 RELRO 保护的情况.