相关结构
ELF可执行文件由ELF头部,程序头部表和其对应的段,节头部表和其对应的节组成。如果一个可执行文件参与动态链接,它的程序头部表将包含类型为PT_DYNAMIC
的段,它包含.dynamic
节。结构如下:
1
|
typedef struct {
|
其中Tag对应着每个节。比如JMPREL
对应着.rel.plt
节中包含目标文件的所有信息。节的结构如下:
1
|
typedef struct {
|
如下图,列出了该文件的31个节区。其中类型为REL的节区包含重定位表项。
(1).rel.plt
节是用于函数重定位,.rel.dyn
节是用于变量重定位
1
|
typedef struct {
|
如图,在.rel.plt中列出了链接的C库函数,以下均以write函数为例,write函数的r_offset=0x0804a01c,r_info=0x607
(2).got
节保存全局变量偏移表,.got.plt
节保存全局函数偏移表。.got.plt
对应着Elf32_Rel
结构中r_offset
的值。
(3).dynsym
节包含了动态链接符号表。Elf32_Sym[num]中的num对应着ELF32_R_SYM(Elf32_Rel->r_info)
。根据定义,
1
|
ELF32_R_SYM(Elf32_Rel->r_info) = (Elf32_Rel->r_info) >> 8
|
1
|
typedef struct
|
write的索引值为ELF32_R_SYM(0x607) = 0x607 >> 8 = 6。而Elf32_Sym[6]即保存着write的符号表信息。并且ELF32_R_TYPE(0x607) = 7,对应R_386_JUMP_SLOT
。
(4).dynstr
节包含了动态链接的字符串。这个节以x00
作为开始和结尾,中间每个字符串也以x00
间隔。
Elf32_Sym[6]->st_name=0x4c(.dynsym + Elf32_Sym_size * num),所以.dynstr
加上0x4c的偏移量,就是字符串write。
(5).plt
节是过程链接表。过程链接表把位置独立的函数调用重定向到绝对位置。
当程序执行call write@plt时,实际会跳到0x0804a01c去执行。
延迟绑定
程序在执行的过程中,可能引入的有些C库函数到结束时都不会执行。所以ELF采用延迟绑定的技术,在第一次调用C库函数是时才会去寻找真正的位置进行绑定。
具体来说,在前一部分我们已经知道,当程序执行call write@plt时,实际会跳到0x0804a01c去执行。而0x0804a01c处的汇编代码仅仅三行。我们来看一下这三行代码做了什么。
第一行:前面提到过0x0804a01c是write的GOT表位置,当我们第一次调用write时,其对应的GOT表里并没有存放write的真实地址,而是write@plt的下一条指令地址。
第二、三行:把reloc_arg=0x20作为参数推入栈中,跳到0x08048380(PLT[0])继续执行。
0x08048380(PLT[0])再把link_map=*(GOT+4)
(即GOT[1],链接器的标识信息)作为参数推入栈中,而*(GOT+8)
(即GOT[2],动态链接器中的入口点)中保存的是_dl_runtime_resolve
函数的地址。因此以上指令相当于执行了_dl_runtime_resolve(link_map, reloc_arg)
,该函数会完成符号的解析,即将真实的write函数地址写入其GOT条目中,随后把控制权交给write函数。
_dl_runtime_resolve是在glibc-2.23/sysdeps/i386/dl-trampoline.S中用汇编实现的。0xf7fededb处即调用_dl_fixup
,并且通过寄存器传参。
_dl_fixup是在glibc-2.23/elf/dl-runtime.c实现的,我们只关注一些主要函数。
1
|
_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg)
|
漏洞利用方式
1.控制eip
为PLT[0]的地址,只需传递一个index_arg
参数
index_arg = 伪造的目标地址-.rel.plt段基地址
2.控制index_arg
的大小,使reloc
的位置落在可控地址内
3.伪造reloc
的内容,使sym
落在可控地址内
r_info=[(欲伪造的地址-.dynsym基地址)/0x10]<<8+0x07
4.伪造sym
的内容,使name
落在可控地址内
st_name = 伪造地址 - 基地址
5.伪造name
为任意库函数,如system