首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

ELF文件格式(二)

继续讲ELF文件的详细格式,还是看下面的图:

ELF文件由以下几个部分组成:

(1) ELF header

(2) Section Header Table/Program Header Table

(3) Sections/Segments

ELF Header

ELF Header在文件最开始处,长度固定为52个字节。详细定义如下图:

其中e_ident是一个16字节的数组,内容固定为7F 45 4C 46(即字符串ELF),这部分的作用就是表示这是一个ELF格式的文件。e_type表示文件的类型,值为1时表示可重定位文件,2表示可执行我文件,3表示共享目标文件。Android系统中的so文件,e_type=3。e_machine和e_version表示文件的CPU体系结构和版本。e_entry表示程序的入口地址,so文件没有入口地址,此处为0。e_phoff表示Program Header Table在文件中的偏移量。e_shoff表示Section Header Table在文件中的偏移量。e_phentsizze表示 Program Header Table的大小。e_phnum表示Program Header的数量。同理,e_shentsize和e_shnum表示Section Header Table的大小和Section Header的数量。e_shstrndx表示Section名称字符串表的索引,这个估计不好理解,没关系,后文会举例说明。

上段介绍的内容,看一眼就算了,具体的格式细节并不重要,理解其含义才是有必要的。组成ELF文件(so文件)的真正内容,是各个Section,每个Section有一个Header,ELF格式把所有的Header排列在一起存放,这就是Section Header Table。同时,几个Section可以组成一个Segment,每个Segment也都有一个Header,这些Header也是排列在一起存放,就是Program Header Table。ELF的header,除了标记是ELF格式外,就是记录这两个Table在什么地方,有多少个Segment/Sectioin,上面的一切详细定义,都是为了这个服务的。

以Android 7.0中的libmedia.so为例,通过readelf命令查看其ELF Header:

如图,Program header table偏移位置为52,Section Header Table偏移位置为848376。

Section Header Table

Section Header Table是把多个Section Header按顺序排列到一起,在文件中的位置和数量由ELF header指定,通过ELF Header可快速定位Section Header。

详细定义如图:

每个Section Header占40个字节,sh_name表示名称,此处只有4个字节,存储不下字符串,实际存放的是在字符串Section中的索引。sh_type表示类型,可以有很多种类型,甚至可以自定义类型,每个类型的Section都有其自己的格式。后续的内容,指定了Section在文件中的偏移位置、大小等信息。

同样,可以用readelf -S 命令,查看Section Header。

如图,libmedia.so共有29个Section Header,类型有STRTAB、DYNSYM、PROGBITS等。

Section

每个类型的Section都有其独有的格式,这里介绍几种。

STRTAB

字符串Section。

这种类型的格式最简单,其实就是把字符串紧密的排列到一起,以0分隔字符串。其中第一个字符串为NULL,第二个为.shstrtab等待。

在上面介绍Section Header时,图中最后一个Section Header的sh_name值为1,这个1就是在名为shstrtab的字符串section中的索引,即shstrtab。

同样,第5个Section,名为.dynstr的Section,类型也为STRTAB。其文件地址为0xCF70,实际内容为:

可见,这里面保存的是程序中用到的各个字符串,包括函数名称、字符串常量等。这个.dynstr很重要,后续还会用到。

符号表(Symbol Table)Section

我们最常用的就是符号表了,如上面介绍Section Header的图,其中.dynsym Section,类型DYNSYM,就是符号表。符号表,也是有一些符号紧密排列组成,符号的格式如下:

每个符号固定占用16个字节,因此,根据符号表的大小,除以16就是其中符号的数量。

其中st_name表示名称,当然这也是在字符串Section(.dynstr)中的索引。

st_info比较复杂,其高4位表示类型,类型有STT_OBJECT、STT_FUNC、STT_SECTION等几种,其中STT_FUNC才表示这是一个函数符号,低4位表示类型的绑定信息,取值有局部符号、全局符号、弱符号几种。

当为函数符号时,st_shndx表示函数的定义在那个Section中,st_value表示在文件中的具体偏移地址。

Program Header Table

与上面讲的Section Header Table一样,Program Header Table也是将一些Segment Header顺序 排放到一起,详细定义如下:

不具体解释每个字段了,其作用也是存放各个Segment的信息,并指向各自的Segment。

用readelf -l 命令查看libmedia.so:

Program Header,是用于执行的。其中需要注意,名为LOAD的Header,其虚拟地址VirtAddr可能不为0,在将so映射到内存时,地址会加上VirtAddr的值。

实践

ELF文件详细格式基本都介绍完了,看了这些有什么用么?

用处是可以帮你更好的理解Hook过程,并定位其中出现的问题。

一般来讲,native hook大致过程如下:

dlopen("libmedia.so") 通过dlopen打开一个so文件

dlsym("fun_name") 获得一个函数的地址。

有了地址以后,就可以替换入口,实现hook了。

其中,重要的是dlsym函数,即通过函数名找到其对应的地址。dlsym功能就是在符号表中查找函数。

明白了ELF格式,就能知道dlsym内部究竟做了什么。虽然有现成的系统函数,但是了解其机制和原理,对深入理解问题是很有帮助的,另外,在Android 7.0上,dlopen和dlsym也不能用于native hook,具体原因不在这里分析。下面通过hook系统libmedia.so中的_ZN7android11AudioRecord4readEPvjb函数为例,说明具体的过程。

打开libmedia.so文件,读取文件头部的ELF Header。

从ELF Header中定位到Program Header Table,查找其中类型为LOAD的Header,看其VirtAddr内容,记录最小的VirtAddr内容。本例中,VirtAddr=0x1d000。

从ELF Header中定位到Section Header Table,并通过ELF Header查看.shstrtab在Section Header Table中的索引,这个.shstrtab保存了各个Section Header的名称。

在Section Header Table中,找到符号表(.dynsym)和字符串(.dynstr)两个Section。

遍历符号表中的每个符号,符号中的st_name为在.dynstr中的索引,跟进这个可以找到符号名称。同时,需要跟进st_info过滤掉类型不是函数的符号。

找到与指定函数名称匹配的符号。st_value为函数的偏移地址,本例中,偏移为0x81DF1,这个偏移是自动加上VirtAddr的结果,这样方便加载程序,但我们还需要减去VirtAddr才能找到函数的定义。结果为:0x64DF1。

验证

使用objdump命令,反编译libmedia.so,找到_ZN7android11AudioRecord4readEPvjb函数:

以二进制打开libmedia.so文件,定位到0x64DF0位置:

如图,验证0x64DF0处的内容就是函数_ZN7android11AudioRecord4readEPvjb的定义(指令集)。

有了函数地址,再Hook就非常简单了。

参考:

1 官方文档《ELF_Format》

2 《ELF文件系统格式》

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180615G1JDWK00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券