与ELF文件动态链接过程相关,linux中,程序使用dl_runtime_reslove(link_map_obj,reloc_offset)来对应链接的函数进行重定位。如果我们可以控制相关的参数及其对应地址的内容岂不是可以控制解析到哪个函数?
基本原理
当第一次call或者jmp一个动态链接库中函数时,如图(puts为例):
- 并不会直接跳转到libc的puts函数的地址,会先跳转到got表中push一个0和一个
qword ptr [rip + 0x2fe2]
,然后跳转至dl_runtime_resolves
函数执行
- 实际上
_dl_runtime_resolve_xsavec
函数的位置存储在got[1]
处
- 在准备执行
_dl_runtime_resolve_xsavec
函数前,程序一直在plt节中执行,取用的数据都来自got表
- 如果我们更详细地看一下plt和got表是怎样区分不同函数的,就会发现:通过push不同的值(0, 1, …)到栈中。
_dlruntime_resolve_xsavec_
函数的作用就是根据push的两个参数获取目标函数(puts)的地址,然后将该地址放到got表中并调用。
dlruntime_resolve工作流程
大致工作流程:
- 首先访问
.dynamic
节找到.dynstr
,.dynsym
,.rela.plt
, - 然后根据
reloc_offset
参数找到目标函数在.dynsym
节中对应的Elf32_Sym
结构体, - 然后由结构体内容得到函数在
.strtab
节中偏移, - 最终找到函数名对应字符串,
- 然后在动态链接库中根据
link_map
查找函数地址并覆写到got表中再执行。
详细看源码,参glibc/elf/dl-runtime.c
相关节介绍
.dynamic
动态节,其中包含很多动态链接所需的关键信息。现在我们只关心STRTAB
,SYMTAB
,JMPREL
这三项,他们分别包含了指向.dynstr
,.dynsym
,.rela.plt
节的指针。
当动调的时候可能发现,实际上对应位置加载的值略有不同,不必惊慌,以实际地址为准
.dynstr
一个字符串表,第一个字节是‘\0’,随后是以‘\0’结尾的字符串,用于动态链接。使用时用相对于第一个字节的偏移做指针。
.dynsym
一个符号表(结构体数组),里面记录了各种符号信息。每个结构体对应一个符号。下面给出该结构体定义:
typedef struct
{
Elf32_Word st_name; //符号名,是相对.dynstr起始的偏移,这种引用字符串的方式在前面说过了
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info; //对于导入函数符号而言,它是0x12
unsigned char st_other;
Elf32_Section st_shndx;
}Elf32_Sym; //对于导入函数符号而言,其他字段都是0
.rela.plt
一个重定位表(结构体数组),里面记录了部分重定位信息。每个结构体对应一个导入符号。下面给出该结构体定义:
typedef struct
{
Elf32_Addr r_offset; // 指向got的指针
Elf32_Word r_info; // 关于导入符号的信息,(r_info>>8)对应在dynsym中下标
} Elf32_Rel; // r_info低字节处会自带一个0x07,我们忽略它
利用手法
说手法先说思路,具体实现下次再讲
思路
思路有三:
- 一是直接劫持
.dynamic
中的STRTAB
,适用于.dynamic
可写的情况(No RELRO) - 二是直接劫持
reloc_offset
参数,适用于.dynamic
不可写但可劫持程序流的情况(Partial RELRO) - 三是改
link_map
结构体,适用于.dynamic
不可写且难以劫持程序流的情况(Partial RELRO)