ELF 文件大致分为3个主要部分
1、ELF HEAD --ELF文件头部分
2、 Program Header Table --程序头表
3、Section Header Table --节头表
这个部分称为“头”,里面大致描述在这个文件里面的组织。如:文件魔术、目标架构体系(如ARM、X86...)、版本信息、各个部分的大小、各个部分的偏移起始地址等等。
下面描述的位置都是固定的,且位置都是紧接着下一部分的位置。(有误欢迎指出)
这里我用的是IDA的android_server文件做演示,来简单看一部分内容。
文件的标识信息(e_ident):前16字节 (包括魔术部分:前4字节 如.ELF)
文件类型(e_type ):2字节
目标架构(e_machine ):2字节
版本(e_version):4字节
程序入口虚拟地址(e_entry ):4字节
程序头部表偏移地址(e_phoff ):4字节
节区头部表偏移地址(e_shoff ):4字节
保存与文件相关的,特定于处理器的标志(e_flags ):4字节
ELF头的大小(e_ehsize ):2字节
每个程序头部表的大小(e_phentsize ):2字节
程序头部表的数量(e_phnum ):2字节
每个节区头部表的大小(e_shentsize):2字节
节区头部表的数量(e_shnum ):2字节
节区字符串表位置(e_shstrndx):2字节
......
程序头表描述的是程序里面各个段的信息。
这里来举例看一下
比如程序头,第一部分, 这个部分描述程序头的信息,比如类型、大小、偏移等等;这个部分描述的就是程序头的信息。
一个程序中到底有多少节信息,取决于这一部分,节头表。
比较经典的,就是这里的导出函数信息。
姓名:罗学元 学号:21181214375 学院:广州研究院
【嵌牛导读】什么是ELF文件
【嵌牛鼻子】什么是ELF文件
【嵌牛提问】什么是ELF文件,它有哪些部分组成、每部分包含哪些信息
ELF文件分为四个部分:elf header,program header table,section header table,dynamic symbol table。其中节头表(section header table) 和 段头表(program header table) 中用到的数据相同,只是组织方式不同。
一、ELF header
每个ELF文件都必须存在一个ELF_Header,这里存放了很多重要的信息用来描述整个文件的组织,如: 版本信息,入口信息,偏移信息等,程序执行也必须依靠其提供的信息:
数据结构如下:
e_xxx 和上面对应表如下图:
其中数据类型如下:
二、Program header table 程序头表
存储so文件运行时所需要的信息,这部分信息会直接被linker使用,用于加载so文件,告诉系统如何在内存中创建映像,在图中也可以看出来,有程序头部表才有段,有段就必须有程序头部表,其中存放各个段的基本信息(包括地址指针)
节到段的映射:
链接视图是以节(section)为单位,执行视图是以段(segment)为单位。链接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图。上图左侧的视角是从链接来看的,右侧的视角是执行来看的。
段(Segment): 就是将文件分成一段一段映射到内存中,段中通常包括一个或多个节区。
那么为什么需要节和段两种视图? 当ELF文件被加载到内存中后,系统会将多个具有相同权限(flg值)section合并一个segment。 *** 作系统往往以页为基本单位来管理内存分配,一般页的大小为4096B,即4KB的大小。同时,内存的权限管理的粒度也是以页为单位,页内的内存是具有同样的权限等属性,并且 *** 作系统对内存的管理往往追求高效和高利用率这样的目标。ELF文件在被映射时,是以系统的页长度为单位的,那么每个section在映射时的长度都是系统页长度的整数倍,如果section的长度不是其整数倍,则导致多余部分也将占用一个页。而我们从上面的例子中知道,一个ELF文件具有很多的section,那么会导致内存浪费严重。这样可以减少页面内部的碎片,节省了空间,显著提高内存利用率。
readelf -S xxx # 用来查看可执行文件中有哪些section,如下图:
readelf --segments xxx # 可以查看该文件的执行视图,下图红框部分为上图的节信息在段中的显示:
最后加载进内存的只有program header table 程序头表里的load段,其他都只是描述信息,加载过程中用到,但是最后加载进去内存的只有load段。
三、Section header table 节头部表
类似与程序头部表,但与其相对应的是节区(Section);节区(Section): 将文件分成一个个节区,每个节区都有其对应的功能,如符号表,哈 希 表 等。
.relname和.relaname: 010Editor打开so,展现形式为下图,.rel.dyn 和 .rel.plt ,是用来重定向dyn和plt的,也就是静态情况下,存放偏移值,如果进行动态调试的时候,就会加上基址变成绝对地址(重定向)。
下面第二张图中,左边红框就是偏移值,右边红框只要把基址加进来,就是绝对地址,把基址加进来的过程就是重定向的过程:
.plt 程序链接表,用于做映射关系,拿到依赖so的绝对地址,做重定向的:
四、Dynamic symbol table
这里是符号表,也就是会用到的所有函数名称表,包括自己写的函数和依赖的系统so中的函数,到时候.plt会对这部分重定向 。
为了增加反编译难度,保护应用安全,现在越来越多的应用把一些重要的逻辑,加密,重要信息用so库存储。如果想反编译so,那么我们就需要了解so文件格式。这样有助于帮助我分析so文件内容和加固so,防止自己的so被别人轻易破解。
so文件使用的是elf格式来存在信息,所以,我们需要了解elf文件格式。这样就可以读取到我们需要的数据。
这篇文章不会把基本的东西讲太多,网上关于so文件格式也有讲解,本文会把自己分析过程遇到的问题,网上查不到,或者你们在学习过程可能会产生的疑问一一列举出来。
对于基础的东西请自己百度或者看下面给出的ELF文件格式文档。
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且它们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。
在Linux用该命令可以直接查看so相关信息,mac可以百度,查看替代方案。
so文件二进制数据图
接下来就以ELF头来做分析
看一下ELF头在elf.h中的定义,如果没有源码,可以参考上面的ELF文件格式文档。
e_ident[EI_NIDENT]这16个字节代表数据如下:
EI_NIDENT :e_ident数组的大小。
剩余字节,可对照数据结构查看,我们用命令看一下ELF头信息
从ELF信息里面可以拿到:
通过e_phoff和e_phnum,e_phentsize可以找到程序头部表的在文件中的起始地址和个数以及单个程序头部表的大小。
同理,可以找到节点表的起始地址和个数以及单个表的大小。
010editor工具左边显示了二进制文件的地址,很方便查看。
通过命令看看具体数据
第一列就是Section表索引,还记得上面的e_shstrndx字段吧,它表示节区字符串表在节区头部表格中索引。由上面的数据图可以看出e_shstrndx是0x25,即37,对于图中的.shstrtab这个正是节区字符串表的名字,具体这些名字代表什么,请查看文档。
从二进制数据看到只是一个数字,并不是像.shstrtab的字符串,那么又是怎么回事呢?
在Section表数据结构中的一个字段是:sh_name 表示节区名称,表示在节区字符串表中的索引,即信息很可能存储在.shstrtab这个字符串表中。根据上面图中的Off得到起始地址。
Section表数据结构索引为1的sh_name值为3A,即58。而节区字符串表的起始地址是E9F4a,即图中为红色00的位置,然后从这个位置往后数58个字节,刚好是图中蓝色部分的数据,是不是Section节区表格中的第二行的名字(图中少了nt字符,我怀疑是长度过长,没有显示出来,经验证短一点的,完全一只)。
知道了sh_name起始地址,那怎么知道到哪里结束呢,sh_name必须以空字符结束,从起始地址一直找到为空字符的地方即可。
更多请参考文档: 链接: https://pan.baidu.com/s/1GgUqFF1dJ9sbCoSU3XzElw
密码:zmac参考: https://www.cnblogs.com/1024Planet/p/6272620.html
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)