ELF文件格式解析

ELF文件格式解析ELF 文件格式说明

1. ELF文件简介

首先,你需要知道的是所谓对象文件(Object files)有三个种类:

  1. 可重定位的对象文件(Relocatable file)
    这是由汇编器汇编生成的 .o 文件。后面的链接器(link editor)拿一个或一些 Relocatable object files 作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件(Shared object file)。我们可以使用 ar 工具将众多的 .o Relocatable object files 归档(archive)成 .a 静态库文件。如何产生 Relocatable file,你应该很熟悉了,请参见我们相关的基本概念文章和JulWiki。另外,可以预先告诉大家的是我们的内核可加载模块 .ko 文件也是 Relocatable object file。

  2. 可执行的对象文件(Executable file)
    这我们见的多了。文本编辑器vi、调式用的工具gdb、播放mp3歌曲的软件mplayer等等都是Executable object file。你应该已经知道,在我们的 Linux 系统里面,存在两种可执行的东西。除了这里说的 Executable object file,另外一种就是可执行的脚本(如shell脚本)。注意这些脚本不是 Executable object file,它们只是文本文件,但是执行这些脚本所用的解释器就是 Executable object file,比如 bash shell 程序。

  3. 可被共享的对象文件(Shared object file)
    这些就是所谓的动态库文件,也即 .so 文件。如果拿前面的静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空 间;另外如果拿它们放到Linux系统上一起运行,也会浪费掉宝贵的物理内存。如果将静态库换成动态库,那么这些问题都不会出现。动态库在发挥作用的过程 中,必须经过两个步骤:

这里我们主要是以Shared Object File(.so)为重点分析对象,因为我们在逆向APK中会遇到的绝大部分都是此类文件。

2. ELF文件格式

首先,ELF文件格式提供了两种视图,分别是链接视图和执行视图。

这里写图片描述

链接视图是以节(section)为单位,执行视图是以段(segment)为单位。链接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图。上图左侧的视角是从链接来看的,右侧的视角是执行来看的。总个文件可以分为四个部分:

- ELF header: 描述整个文件的组织。 - Program Header Table: 描述文件中的各种segments,用来告诉系统如何创建进程映像的。 - sections 或者 segments:segments是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件,也就是说,在链接阶段,我们可以忽略program header table来处理此文件,在运行阶段可以忽略section header table来处理此程序(所以很多加固手段删除了section header table)。从图中我们也可以看出,segments与sections是包含的关系,一个segment包含若干个section。 - Section Header Table: 包含了文件各个segction的属性信息,我们都将结合例子来解释。 

这里写图片描述

程序头部表(Program Header Table),如果存在的话,告诉系统如何创建进程映像。
节区头部表(Section Header Table)包含了描述文件节区的信息,比如大小、偏移等。

这验证了第一张图中所述,segment是section的一个集合,sections按照一定规则映射到segment。那么为什么需要区分两种不同视图?

当ELF文件被加载到内存中后,系统会将多个具有相同权限(flg值)section合并一个segment。操作系统往往以页为基本单位来管理内存分配,一般页的大小为4096B,即4KB的大小。同时,内存的权限管理的粒度也是以页为单位,页内的内存是具有同样的权限等属性,并且操作系统对内存的管理往往追求高效和高利用率这样的目标。ELF文件在被映射时,是以系统的页长度为单位的,那么每个section在映射时的长度都是系统页长度的整数倍,如果section的长度不是其整数倍,则导致多余部分也将占用一个页。而我们从上面的例子中知道,一个ELF文件具有很多的section,那么会导致内存浪费严重。这样可以减少页面内部的碎片,节省了空间,显著提高内存利用率。

3. ELF Header

#define EI_NIDENT 16 typedef struct { unsigned char e_ident[EI_NIDENT]; ELF32_Half e_type; ELF32_Half e_machine; ELF32_Word e_version; ELF32__Addr e_entry; ELF32_Off e_phoff; ELF32_Off e_shoff; ELF32_Word e_flags; ELF32_Half e_ehsize; ELF32_Half e_phentsize; ELF32_Half e_phnum; ELF32_Half e_shentsize; ELF32_Half e_shnum; ELF32_Half e_shstrndx; }Elf32_Ehdr;

接着运行readelf -h android_server命令,可以看到ELF Header结构的内容。

这里写图片描述

这里写图片描述
Executable File(可执行文件android_server)

这里写图片描述
Relocatable File(.o文件)

在ELF Header中我们需要重点关注以下几个字段:

  1. e_entry:程序入口地址
    这 个sum.o的进入点是0x0(e_entry),这表面Relocatable objects不会有程序进入点。所谓程序进入点是指当程序真正执行起来的时候,其第一条要运行的指令的运行时地址。因为Relocatable objects file只是供再链接而已,所以它不存在进入点。而可执行文件test和动态库.so都存在所谓的进入点,且可执行文件的e_entry指向C库中的_start,而动态库.so中的进入点指向 call_gmon_start。
    如上图中e_entry = 0xD8B0,我们用ida打开该文件看到确实是_start()函数的地址。
    这里写图片描述






  2. e_ehsize:ELF Header结构大小
  3. e_phoff、e_phentsize、e_phnum:描述Program Header Table的偏移、大小、结构。
  4. e_shoff、e_shentsize、e_shnum:描述Section Header Table的偏移、大小、结构。
  5. e_shstrndx:这一项描述的是字符串表在Section Header Table中的索引,值25表示的是Section Header Table中第25项是字符串表(String Table)。

4. Section Header Table

5. Section

下面我们分析一些so文件中重要的Section,包括符号表、重定位表、GOT表等。

-符号表(.dynsym)

符号表包含用来定位、重定位程序中符号定义和引用的信息,简单的理解就是符号表记录了该文件中的所有符号,所谓的符号就是经过修饰了的函数名或者变量名,不同的编译器有不同的修饰规则。例如符号_ZL15global_static_a,就是由global_static_a变量名经过修饰而来。

符号表项的格式如下:

typedef struct { Elf32_Word st_name; //符号表项名称。如果该值非0,则表示符号名的字 //符串表索引(offset),否则符号表项没有名称。 Elf32_Addr st_value; //符号的取值。依赖于具体的上下文,可能是一个绝对值、一个地址等等。 Elf32_Word st_size; //符号的尺寸大小。例如一个数据对象的大小是对象中包含的字节数。 unsigned char st_info; //符号的类型和绑定属性。 unsigned char st_other; //未定义。 Elf32_Half st_shndx; //每个符号表项都以和其他节区的关系的方式给出定义。              //此成员给出相关的节区头部表索引。 } Elf32_sym; 

-字符串表(.dynstr)

-重定位表

重定位表在ELF文件中扮演很重要的角色,首先我们得理解重定位的概念,程序从代码到可执行文件这个过程中,要经历编译器,汇编器和链接器对代码的处理。然而编译器和汇编器通常为每个文件创建程序地址从0开始的目标代码,但是几乎没有计算机会允许从地址0加载你的程序。如果一个程序是由多个子程序组成的,那么所有的子程序必需要加载到互不重叠的地址上。重定位就是为程序不同部分分配加载地址,调整程序中的数据和代码以反映所分配地址的过程。简单的言之,则是将程序中的各个部分映射到合理的地址上来。
换句话来说,重定位是将符号引用与符号定义进行连接的过程。例如,当程序调用了一个函数时,相关的调用指令必须把控制传输到适当的目标执行地址。
具体来说,就是把符号的value进行重新定位。




可重定位文件必须包含如何修改其节区内容的信息,从而允许可执行文件和共享目标文件保存进程的程序映象的正确信息。这就是重定位表项做的工作。重定位表项的格式如下:

typedef struct { Elf32_Addr r_offset; //重定位动作所适用的位置(受影响的存储单位的第一个字节的偏移或者虚拟地址) Elf32_Word r_info; //要进行重定位的符号表索引,以及将实施的重定位类型(哪些位需要修改,以及如何计算它们的取值) //其中 .rel.dyn 重定位类型一般为R_386_GLOB_DAT和R_386_COPY;.rel.plt为R_386_JUMP_SLOT } Elf32_Rel; ``` ``` typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; Elf32_Word r_addend; } Elf32_Rela; 

对 r_info 成员使用 ELF32_R_TYPE 宏运算可得到重定位类型,使用 ELF32_R_SYM 宏运算可得到符号在符号表里的索引值。 三种宏的具体定义如下:

#define ELF32_R_SYM(i) ((i)>>8)  #define ELF32_R_TYPE(i) ((unsigned char)(i))  #define ELF32_R_INFO(s, t) (((s)

再看一下重定位表中的内容。

这里写图片描述

我们可以看到,每8个字节(s_entsize)一个表项。第一个表项中的r_offset值为0xc7660,r_info为0xa16。其中r_offset指向下图中GOT表中第一项__imp_clock_gettime外部函数地址。那么我们如何利用r_offset值来找到其对应的符号呢?如上所述,进行 ELF32_R_SYM宏运算实际上就是将r_info右移8位,0xa16右移8位得到0xa,因此这就是其在符号表中的索引。

这里写图片描述

从下图中可以看见符号表的s_entsize值为10h,即16个字节每条目。因此我们可以找到其索引为0xa的条目的st_name值为0x9ea。那么怎么证明我们确实找到的是clock_gettime函数的符号呢?我们再来看一下st_name值是不是正确的。

这里写图片描述

  • .rel.text:重定位的地方在.text段内,以offset指定具体要定位位置。在链接时候由链接器完成。.rel.text属于普通重定位辅助段 ,他由编译器编译产生,存在于obj文件内。连接器连接时,他用于最终可执行文件或者动态库的重定位。通过它修改原obj文件的.text段后,合并到最终可执行文件或者动态文件的.text段。其类型一般为R_386_32和R_386_PC32。
  • .rel.dyn:重定位的地方在.got段内。主要是针对外部数据变量符号。例如全局数据。重定位在程序运行时定位,一般是在.init段内。定位过程:获得符号对应value后,根据rel.dyn表中对应的offset,修改.got表对应位置的value。另外,.rel.dyn 含义是指和dyn有关,一般是指在程序运行时候,动态加载。区别于rel.plt,rel.plt是指和plt相关,具体是指在某个函数被调用时候加载。我个人理解这个Section的作用是,在重定位过程中,动态链接器根据r_offset找到.got对应表项,来完成对.got表项值的修改。

.rel.dyn和.rel.plt是动态定位辅助段。由连接器产生,存在于可执行文件或者动态库文件内。借助这两个辅助段可以动态修改对应.got和.got.plt段,从而实现运行时重定位。

  • .rel.plt:重定位的地方在.got.plt段内(注意也是.got内,具体区分而已)。 主要是针对外部函数符号。一般是函数首次被调用时候重定位。首次调用时会重定位函数地址,把最终函数地址放到.got内,以后读取该.got就直接得到最终函数地址。我个人理解这个Section的作用是,在重定位过程中,动态链接器根据r_offset找到.got对应表项,来完成对.got表项值的修改。
  • .plt段(过程链接表):所有外部函数调用都是经过一个对应桩函数,这些桩函数都在.plt段内。具体调用外部函数过程是:
    调用对应桩函数—>桩函数取出.got表表内地址—>然后跳转到这个地址.如果是第一次,这个跳转地址默认是桩函数本身跳转处地址的下一个指令地址(目的是通过桩函数统一集中取地址和加载地址),后续接着把对应函数的真实地址加载进来放到.got表对应处,同时跳转执行该地址指令.以后桩函数从.got取得地址都是真实函数地址了。
    下图是.plt某表项,它包含了取.got表地址和跳转执行两条指令。
    这里写图片描述






  • .got(全局偏移表)

6. Program Header Table

程序头部的数据结构如下:

typedef struct { Elf32_Word p_type; //此数组元素描述的段的类型,或者如何解释此数组元素的信息。  Elf32_Off p_offset; //此成员给出从文件头到该段第一个字节的偏移 Elf32_Addr p_vaddr; //此成员给出段的第一个字节将被放到内存中的虚拟地址 Elf32_Addr p_paddr; //此成员仅用于与物理地址相关的系统中。System V忽略所有应用程序的物理地址信息。 Elf32_Word p_filesz; //此成员给出段在文件映像中所占的字节数。可以为0。 Elf32_Word p_memsz; //此成员给出段在内存映像中占用的字节数。可以为0。 Elf32_Word p_flags; //此成员给出与段相关的标志。 Elf32_Word p_align; //此成员给出段在文件中和内存中如何对齐。 } Elf32_phdr;

我们看到,以下两个工具确实是照此格式解析的。

这里写图片描述

这里写图片描述


7. 参考链接

1.http://www.choudan.net/2013/11/16/Linux%E8%BF%9B%E7%A8%8B%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4%E5%86%8D%E5%AD%A6%E4%B9%A0.html

2.http://www.choudan.net/2013/10/25/Linux%E8%BF%9B%E7%A8%8B%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4%E5%AD%A6%E4%B9%A0%28%E4%BA%8C%29.html Linux进程地址空间学习

3.http://blog.chinaunix.net/uid-52437-id-3029374.html linux下elf重定位理解

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/202320.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月20日 上午7:24
下一篇 2026年3月20日 上午7:24


相关推荐

  • Win10 pycharm anaconda 配置多环境走一波!

    Win10 pycharm anaconda 配置多环境走一波!没想到 第一篇博客是关于 win10pycharm 如何配置 conda 多环境的 Win10pycharm 配置多环境走一波 首先在进行 pycharm 配置前 确保你有打开 pycharm 的管理员权限 如果你是在一台服务器上和大家共同使用 这里就必须要管理员权限 右键管理员身份运行 pycharm 如果是自己的电脑就算了 如果你在创建过程中遇到了 Permissi

    2026年3月27日
    3
  • 安装 arm nginx aarch64[通俗易懂]

    安装 arm nginx aarch64[通俗易懂]网上搜了一大堆通过编译方式安装nginx的方法其实nginx提供了aarch64版本的nginxhttp://nginx.org/en/linux_packages.html2中选择,使用yum安装软件的选择centos的方法,apt安装软件的选择debain或者ubuntu的方法添加软件源地址,然后就可以快乐的玩耍了不管是yum还是apt安装后nginx都不会自己启动需要systemctlstartnginx手动启动nginx服务

    2022年10月16日
    3
  • 推荐系统(Recommendation system )介绍[通俗易懂]

    推荐系统(Recommendation system )介绍[通俗易懂]前言随着电子商务的发展,网络购物成为一种趋势,当你打开某个购物网站比如淘宝、京东的时候,会看到很多给你推荐的产品,你是否觉得这些推荐的产品都是你似曾相识或者正好需要的呢。这个就是现在电子商务里面的推

    2022年8月4日
    7
  • pycharm下载插件_SiteD 插件中心

    pycharm下载插件_SiteD 插件中心我使用的PyCharm软件的版本:2016.1.4参考网站:https://www.jetbrains.com/help/pycharm/2016.1/installing-updating-and-uninstalling-repository-plugins.html给PyCharm软件添加plugins的图文操作(这里以添加Markdown插件)Step1.启动PyC

    2022年8月26日
    6
  • idea怎么集成git(idea中git的使用)

    一、IDEA集成git方法   首先idea集成git我们需要先下载一个小软件,gitbash 地址:https://git-scm.com/downloads 。下载好了之后直接下一步下一步傻瓜试安装。安装好后回在你指定的文件夹下有个git文件夹,文件结构如下:当然如果你对git命令比较熟悉,用这个软件就可以实现所有的git操作了。下面我们来集成进IDEA开发工具。打开I…

    2022年4月18日
    226
  • jar包反编译,查看源代码

    jar包反编译,查看源代码介绍一个反编译工具 jd gui 下载地址 http jd benow ca 根据自己的电脑系统下载对应版本 下载完之后 点击运行点击 file openfile 选择想查看的 jar 就能看到结果了 不过源码中注释的部分反编译后无法查看

    2026年3月20日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号