• 转载!


    相关结构

    ELF可执行文件由ELF头部,程序头部表和其对应的段,节头部表和其对应的节组成。如果一个可执行文件参与动态链接,它的程序头部表将包含类型为PT_DYNAMIC的段,它包含.dynamic节。结构如下:

    1
    2
    3
    4
    5
    6
    7
    typedef struct {
    Elf32_Sword d_tag;
    union {
    Elf32_Word d_val;
    Elf32_Addr d_ptr;
    } d_un;
    } Elf32_Dyn;

    其中Tag对应着每个节。比如JMPREL对应着.rel.plt

    节中包含目标文件的所有信息。节的结构如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    typedef struct {
    Elf32_Word sh_name; // 节头部字符串表节区的索引
    Elf32_Word sh_type; // 节类型
    Elf32_Word sh_flags; // 节标志,用于描述属性
    Elf32_Addr sh_addr; // 节的内存映像
    Elf32_Off sh_offset; // 节的文件偏移
    Elf32_Word sh_size; // 节的长度
    Elf32_Word sh_link; // 节头部表索引链接
    Elf32_Word sh_info; // 附加信息
    Elf32_Word sh_addralign; // 节对齐约束
    Elf32_Word sh_entsize; // 固定大小的节表项的长度
    } Elf32_Shdr;

    如下图,列出了该文件的31个节区。其中类型为REL的节区包含重定位表项。

    (1).rel.plt节是用于函数重定位,.rel.dyn节是用于变量重定位

    1
    2
    3
    4
    5
    6
    7
    8
    typedef struct {
    Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址
    Elf32_Word r_info; // 符号表索引
    } Elf32_Rel;

    #define ELF32_R_SYM(info) ((info)>>8)
    #define ELF32_R_TYPE(info) ((unsigned char)(info))
    #define ELF32_R_INFO(sym, type) (((sym)<<8)+(unsigned char)(type))


    如图,在.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
    2
    3
    4
    5
    6
    7
    8
    9
    typedef struct
    {
    Elf32_Word st_name; // Symbol name(string tbl index)
    Elf32_Addr st_value; // Symbol value
    Elf32_Word st_size; // Symbol size
    unsigned char st_info; // Symbol type and binding
    unsigned char st_other; // Symbol visibility under glibc>=2.2
    Elf32_Section st_shndx; // Section index
    } Elf32_Sym;

    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    _dl_fixup(struct link_map *l, ElfW(Word) reloc_arg)
    {
    // 首先通过参数reloc_arg计算重定位入口,这里的JMPREL即.rel.plt,reloc_offset即reloc_arg
    const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
    // 然后通过reloc->r_info找到.dynsym中对应的条目
    const ElfW(Sym) *sym = &symtab[ELFW(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+sym->st_name找到符号表字符串,result为libc基地址
    result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
    // value为libc基址加上要解析函数的偏移地址,也即实际地址
    value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);
    // 最后把value写入相应的GOT表条目中
    return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
    }

    漏洞利用方式

    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

  • 相关阅读:
    沟通,你都做了什么
    Markdown,你只需要掌握这几个
    一周心态-情景联想
    一周心态-漫谈计划之死
    我的Git之旅(1)---git安装、github注册以及一些基本命令
    我的20132014
    Python 笔记——4 条件控制
    Python 笔记——3 数据类型
    Python 笔记——1语法分析
    纯CSS实现兼容ie6以上的圆角头像
  • 原文地址:https://www.cnblogs.com/0xHack/p/11656801.html
Copyright © 2020-2023  润新知