• 延迟绑定机制详解


    本文根据这篇文章跟踪调试,对延迟绑定的过程又有了新的感悟,写个文记录一下,建议看原文 https://www.anquanke.com/post/id/184099

    lazy binding过程跟踪

    针对动态链接会减速程序运行速度的现状,操作系统实现了延迟绑定(Lazy Binding)的技术:函数第一次被用到时才对函数进行绑定。
    通过延迟绑定大大加快了程序的启动速度。而ELF 则使用了PLT(Procedure Linkage Table,过程链接表)的技术来实现延迟绑定。

    在lazy binding中,got表项存储的东西

    got: plt+6
    got + 4: link_map
    got + 8: _dl_runtime_resolve

    _dl_runtime_resolve的源码:https://code.woboq.org/userspace/glibc/sysdeps/i386/dl-trampoline.S.html
    可以看到_dl_runtime_resolve的核心在于call _dl_fixup

    动态链接中最重要的结构应该是dynamic段,这个段里面保存了动态链接器所需要的基本信息。
    比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。
    strze不是,截图截手残了

    是一个结构体数组,结构体的定义为:

    typedef struct {
        Elf32_Sword     d_tag;
        union {
            Elf32_Word  d_val;
            Elf32_Addr  d_ptr;
        } d_un;
    } Elf32_Dyn;
    extern Elf32_Dyn_DYNAMIC[];
    

    Elf32_Dyn结构由一个类型值加上一个附加的数值或指针,对于不同的类型,后面附加的数值或者指针有着不同的含义。
    下面给出和延迟绑定相关的类型值的定义。

    d_tag类型 	                              d_un的定义
    #define DT_STRTAB 5 	动态链接字符串表的地址,d_ptr表示.dynstr的地址 (Address of string table)
    #define DT_SYMTAB 6 	动态链接符号表的地址,d_ptr表示.dynsym的地址 (Address of symbol table)
    #define DT_JMPREL 23 	动态链接重定位表的地址,d_ptr表示.rel.plt的地址 (Address of PLT relocs)
    #define DT_RELENT 19 	单个重定位表项的大小,d_val表示单个重定位表项大小 (Size of one Rel reloc )
    #define DT_SYMENT 11 	单个符号表项的大小,d_val表示单个符号表项大小 (Size of one symbol table entry )
    

    如上图所示,可以看到字符串表.dynstr的地址为0x804822c,符号表.dynsym地址为0x80481cc,其单个符号表项的大小为16,重定位表.rel.plt的地址为 0x80482d8,其单个重定位表项的大小为8

    .rel.plt重定位表中包含了需要重定位的函数的信息,其也是一个结构体数组,结构体Elf32_Rel定义如下,其中r_offset表示got表地址,即动态解析函数后真正的函数地址需要填入的地方。
    r_info由两部分构成,r_info>>8表示该函数对应在符号表.dynsym中的下标,r_info&0xff则表示重定位类型。

    typedef struct {
        Elf32_Addr        r_offset;
        Elf32_Word       r_info;
    } Elf32_Rel;
    

    我们通过readelf观察重定位表

     ┌─────(root)─────(/ctf/work/ctf-wiki/ret2dl/32) 
     └> $ readelf -r demo
    Relocation section '.rel.plt' at offset 0x2d8 contains 3 entries:
     Offset     Info    Type            Sym.Value  Sym. Name
    0804a00c  00000107 R_386_JUMP_SLOT   00000000   read@GLIBC_2.0
    0804a010  00000207 R_386_JUMP_SLOT   00000000   __stack_chk_fail@GLIBC_2.4
    0804a014  00000407 R_386_JUMP_SLOT   00000000   __libc_start_main@GLIBC_2.0
    

    可以看到重定位表.rel.plt为一个Elf32_Rel数组,demo程序中该数组包含三个元素,第一个是read的重定位表项Elf32_Rel结构体,第二个是__stack_chk_fail,第三个是__libc_start_main。
    read的重定位表r_offset为0x0804a00c,为read的got地址,即在动态解析函数完成后,将read的函数地址填入到r_offset为0x0804a00c中。
    r_info为0x00000107表示read函数的符号表为.dynsym数组中的0x00000107>>8(即0x1)个元素,它的类型为0x00000107&0xff(即0x7)对应为R_386_JUMP_SLOT类型。

    接着我们去看符号表.dynsym节,它也是一个结构体Elf32_Sym数组,其结构体的定义如下:

    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
    

    其中st_name指向的是函数名称在.dynstr表中的偏移。在dynamic段中我们知道了符号表.dynsym地址为0x80481cc,查看它的值:

    pwndbg> x/20wx 0x80481cc
    0x80481cc:      0x00000000      0x00000000      0x00000000      0x00000000
    0x80481dc:      0x0000002b      0x00000000      0x00000000      0x00000012
    0x80481ec:      0x0000001a      0x00000000      0x00000000      0x00000012
    0x80481fc:      0x00000042      0x00000000      0x00000000      0x00000020
    0x804820c:      0x00000030      0x00000000      0x00000000      0x00000012
    

    以及使用readelf -s查看符号表的内容:

    从重定位表.rel.plt中,我们知道了read的r_info>>8为0x1,即read的符号表项对应的是.dynsym第二个元素,果然可以看到.dynsym第一个元素为read函数的Elf32_Sym结构体
    可以看到它的st_name对应的是0x0000002b,即read字符串应该在.dynstr表偏移为0x2b的地方,由dynamic我们知道了.dynstr表的地址为地址为0x804822c
    那么果然如此,0x804822c+0x2b的地方存储着”read“字符串

    到这里似乎对read函数的解析过程有了一个简单的了解:

    1,可以先通过dynamic段获取各个表的地址,包括有字符串表.dynstr的地址为0x804822c,
    符号表.dynsym地址为0x80481cc,其单个符号表项的大小为16,
    重定位表.rel.plt的地址为 0x80482d8,其单个重定位表项的大小为8。
    2,read函数为.rel.plt表中的第一个元素,定位它的重定位表项,知道了read函数的r_offset为0x0804a00c,
    以及它在符号表中的下标为0x000001,它的类型为0x7,R_386_JUMP_SLOT。
    3,由0x000001知道了read函数的符号表是.dynsym第二个元素,获取到该结构体,
    得到了它对应的st_name对应的是0x0000002b,即获取了read字符串应该在.dynstr表偏移为0x2b的地方。
    4,最后调用函数解析匹配read字符串所对应的函数地址,将其填至r_offset为0x0804a00c,即read的got地址中。
    

    回过头继续看_dl_runtime_resolve函数,其中的_dl_fixup的源码在下面这个文件里

    延迟绑定技术总结

    第一次call func@plt时

    // plt表项内容
    jump *(func@got)   --->    got表项的值为plt+6的地址,即push n的地址
    push n     // 将需要延迟绑定的符号在重定位表中的index压栈
    jmp PLT0
    

    上面n为.rel.plt表中的偏移
    PLT0的指令:

    // PLT0指令
    push *(got + 4)      // 存储的是link_map的地址
    jmp *(got + 8)       // 存储的是_dl_runtime_resolve函数地址
    

    _dl_runtime_resolve会调用_dl_fixup函数,其功能:

    1. 从link_map获取字符串表.dynstr      符号表.dynsym      重定位表.rel.plt的地址
    2.通过(第二个参数)偏移reloc_arg(push n)+.rel.plt的地址获取函数对应的重定位结构的位置,继而获取r_offset和r_info
    3.根据.dynsym以及r_info>>8获取符号结构体,得到st_name (.dynstr + offset),即函数名相对于.dynstr的偏移量
    4.拿到函数名后取libc中匹配函数名,找到相应函数并将地址填回got表,绑定完成
    

    流程图

    一个实例

  • 相关阅读:
    缓存Cache
    RDD的行动操作
    redis数据库的配置
    requests的封装(user-agent,proxies)
    phantjs
    python多线程
    etree-xpath
    Flask
    Flask
    Flask
  • 原文地址:https://www.cnblogs.com/lemon629/p/13940300.html
Copyright © 2020-2023  润新知