Linux-03

PE文件的组成

  • 头部

    • DOS头(兼容老版本系统而保留的)

    • NT头

      • 文件头

      • 扩展头

        • 数据目录表

          • 导出表 : 用于保存dll中的导出符号

          • 导入表 : 用于保存本模块所使用的其它模块的符号

            • 在程序中调用导出函数的时候,形成汇编句式:

              call [IAT]

          • 重定位表 : 支持随机加载基址

      • 区段头

        • .text : 用于保存可执行的代码

        • .data : 用于全局变量/对象的初始数据

        • .rdata : 用于保存常量数据

  • 区段数据

ELF文件组成

  • ELF头

    • 文件头 : 用于记录一个ELF文件的信息(多少位? 能够运行的CPU平台是什么?,程序入口点在哪里?等等)

    • 程序头表 : 程序运行起来的时候, 需要用到哪些区段的数据? 例如,程序的可执行代码存在哪个区段? 常量数据存在哪个区段? ELF文件运行起来的时候, 必须存在程序头,否则ELF文件无法加载并运行.

    • 区段头表 : 用于记录ELF文件的主要的数据.

      • .text 记录代码

      • .data 记录数据

      • .rdata 记录常量数据

      • .symtab 记录符号表(相当于PE文件的导出表)的数据

      • .rel.plt 记录某个区段的重定位内容(相当于PE文件的导入表)

命令readelf -h 文件名可以文件头信息

命令readelf -l 文件名可以查看程序头信息

命令readelf -s 文件名可以段信息

1567598446876

区段头

  1. 区段名

    1. 区段名是一个基于区段头字符串表数段首地址的偏移

    2. 区段头字符串表在区段表中的下标记录在elf文件头中. 字段:e_shstrtabndx

    3. 找名字的过程是:

      1. 通过elf头的e_shoff找到区段头表的首地址.

      2. 通过elf头的区段头字符串表下标

      3. 通过下标索引区段表, 就得到区段头字符串表的区段头

      4. 通过字符串表的区段头的:sh_offset就可以得到字符串表在文件中的首地址.

      5. 最后通过区段名偏移 + 字符串表的偏移 ,就能定为到字符串.

        1567598507725

      1567598522141

  2. sh_link字段

    1. 如本区段的数据是一张表, 表中的数据要引用其它区段的数据时, link字段就会保存被引用的区段的区段头下标.

  3. sh_info字段

    1. 每个不同类型的区段, info的意义是不一样的

    2. 例如,区段是符号表时, info的值是一个符号在符号表中的下标. 这个符号符号表中第一个非LOCAL类型的符号.

ELF的原理

  1. 怎么支持随机加载基址

    1. 什么样的汇编代码会产生需要重定位的代码

      printf("hello world\n");
      ; 对应的汇编:
      push 0x43000 ; "hello world\n"字符串首地址
      call printf
      add esp,4

      MessageBox(0,0,0,0);
      ; 对应的汇编:
      push 0
      push 0
      push 0
      push 0
      call [0x403008]; MessageBox所在的IAT的地址
    2. 在PE文件中, 使用重定位表来保存需要重定位的代码的位置, 在加载的时候, 如果发生随机基址, 那么就遍历重定位表来修改每个地址.

    3. 在ELF文件中, 没有这样的重定位表. 但是ELF文件是支持随机加载基址的. 在ELF文件中,使用的是getpc + 偏移的技术来支持随机加载基址.

      #include <stdio.h>
      int g_nNum = 0x12345678;
      int main()
      {
      printf("hello %d",g_nNum);
      }

      汇编:

      lea     ecx, [esp+4]
      and     esp, 0FFFFFFF0h
      push    dword ptr [ecx-4]
      push    ebp
      mov     ebp, esp
      push    ebx
      push    ecx
      call    __x86_get_pc_thunk_ax
      add     eax, 1AA7h

      ; 根据全局变量的偏移来访问全局变量的值.
      ; 这样一来是不会产生重定位的.
      mov     edx, (g_nNum - 1FD8h)[eax] ; mov edx,[eax + g_nNum - 0x1FD8]                       ; MOV EDX,DWORD PTR DS:[EAX+0x30]
      sub     esp, 8
      push    edx
      lea     edx, (aHelloD - 1FD8h)[eax] ; "hello %d"
      push    edx             ; format
      mov     ebx, eax
      call    _printf
      add     esp, 10h
      mov     eax, 0
      lea     esp, [ebp-8]
      pop     ecx
      pop     ebx
      pop     ebp
      lea     esp, [ecx-4]
      retn
    4. getpc之后再加上一个偏移值, 得出的值是一个.got段的首地址

      一般,只读数据段.rodata.got的上面, 数据段.data.got的下面.

  2. 怎么实现导出表的功能

    1. 在PE文件中,导出一个函数需要使用关键字声明或使用def文件定义.才能导出.

    2. PE文件使用导出表来记录导出函数的信息:

      1. 导出函数的名字 (导出名称表)

      2. 导出函数的序号 (导出表的序号基数+导出地址表的下标)

      3. 导出函数在文件中的rva (导出地址表)

    3. 在ELF文件中, 导出一个函数不需要任何的额外手续, 在文件中定义的函数都被默认导出. 只有被加了static的函数或全局变量才不会被导出.

    4. 使用符号表来记录导出函数的信息

      1. 符号表由符号结构体来组成

      2. 符号结构体记录的是:

        1. 符号的名字

        2. 符号的类型

        3. 符号的绑定类型(决定了符号是否被导出)

        4. 符号数据在文件中的位置(如果符号是一个函数,那么这个位置就是导出函数的地址.)

        5. 符号数据在文件中的大小(如果符号是一个函数, 就是函数机器码占的字节数)

      3. ELF文件的符号表保存在区段表中. 只要区段类型是SHT_SYMTAB的时候, 说明该区段保存的就是符号表.

      4. 符号表使用的结构体:

        typedef struct
        {
          Elf64_Word st_name;//符号名
          unsigned char st_info;//符号类型和绑定类型
          unsigned char st_other;// 符号的可见性
          Elf64_Section st_shndx;//符号所在的区段的下标
          Elf64_Addr st_value;//符号数据在文件的偏移
          Elf64_Xword st_size;//符号数据所占的字节数
        } Elf64_Sym;
  3. 怎么实现导入表的功能

    1. 在PE文件,使用导入表的时候, 是通过call [IAT]来调用导入函数

    2. 在PE文件中, 使用了导入结构体数组来记录导出的dll和函数

      1. DllName的字段记录要导入的dll的名字

      2. 有导入名称表记录了从dll中导入进来的函数名或函数的序号

    3. 在加载器加载PE文件的时候, 会根据导入名称表修复导入地址表(IAT表)

    4. 在ELF文件中, 使用导入表的时候, 所有的导入函数的地址都保存在.got段中, 保存的时候,是以一个4字节的数组来保存的, 这些导入函数的地址在运行前是没有的, 在运行的时候,加载器会找到所有导入函数的地址,并依次填充进去.

    5. 在调用导入函数的时候, 步骤有两步

      ; 1. 先调用.plt段中.
      call .plt + 偏移  ; 通过偏移调用到.plt段

      ; 2. 在plt段中有一个句代码.直接通过.got的首地址
      ;   索引到一个导入函数的地址, 最终完成调用.
      .plt jmp [ ebx + 偏移 ] ;ebx保存的是.got的首地址

      .got 导入函数地址1
      .got 导入函数地址2
      .got 导入函数地址3
    6. 导入函数的名字以及导入函数应该填充到.got表的哪个位置?

      1. 通过动态符号表(SHT_DYNSYM)(可称为导入名称表)和重定位表来完成的.

      2. 重定位表只用于保存符号名和.got的对应关系

        typedef struct
        {
          Elf64_Addr r_offset;// 重定位的位置
          Elf64_Xword r_info;//符号的类型和符号的下标
        } Elf64_Rel;
      3. 总结

        1. 重定位表记录的内容是: 要重定位的位置, 以及要重定位的符号(符号实际就是导入的符号)

      4. 导入的符号在哪个SO文件中?

        1. 区段头中, 当区段类型是SHT_DYNAMIC时, 这个段就记录着动态链接的信息, 其中就有记录需要加载哪些so文件.

          1567598533021

ELF文件的畸形化

转载于:https://www.cnblogs.com/ltyandy/p/11462940.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值