android study 4

Author Avatar
Xzhah 5月 27, 2022
  • 在其它设备中阅读本文章

1.文件结构

1.1 ELF文件结构

1.1.1 ELF文件结构概述

​ ELF文件由三部分组成,ELF文件头(位置固定,大小固定),段头表,节头表。

​ ELF整体结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ELF Header -> ELF 文件头的位置是固定的
Program Header Table -> ELF程序头描述的段的相关信息,用来告诉系统如何生成这个进程,如果是重定位文件(.o文件)可以没有这个表
.shstrtab 该section包含了节的名称
.bss 该section包含了在内存中程序的未初始化的数据,当程序运行开始时,系统将用0来初始化该区域。该section不占用文件空间,该section type = SHT_NOBITS。(bss存在的意义就是缩小二进制文件空间)
.init
.text 可执行区域(代码)
.rodata 只读数据
.data 和.data1 该section 包含了在内存中程序的初始化数据
.debug 该section包含了符号调试信息
.dynamic 该section包含了动态链接信息,该section属性将包含SHF_ALLOC比特位,而SHF_WRITE比特位是否为1取决于处理器
.dynsym 该section包含了动态链接符号表
.fini 该section 包含了用于终止进程可执行指令代码
.symtab
.hash 包含了符号hash表
.line
.strtab 字符串表
.got 包含了全局偏移表
.plt 程序链接表
Section Header Table --> ELF 节头表描述的是节区的信息 动态用段,静态是节区。对于可加载文件是可选的
程序加载的时候只会加载LOAD 段

​ Program Header Table中存储了segment的描述信息,segment是站在系统的角度对section的一种组织方式,对于操作系统而言,数据属性通常是读,写,执行,符合同一数据属性的 section 理论上就可以放到一起以节省加载时的内存,所以一个segment 中可以包含连续的多个 section。

​ 而 section header 和 program header 都是对于 elf 文件中 sections 的描述,只是采用了不同的方式,elf 文件中的 sections 按照 segment 的组成形式进行排列,这样就不需要为每个 segment 重新生成一份针对 segment 的 sections 的组合.

1.2.2 链接视图与执行视图

​ 静态链接器会以链接视图解析ELF文件,编译时生成的.o文件以及链接后的.so文件均可通过链接视图进行解析,链接视图是没有段表的,如.o文件不会有段表。执行视图则是在程序被装载运行时用到的视图。链接视图以section为单位,执行视图以segment为单位。

1.2.3 ELF文件头

​ ELF文件头记录了ELF文件类型,结构,版本,程序入口地址等信息,并提供了节头表,程序头表在文件中的偏移位置。ELF文件头具体结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct {
unsigned char e_ident[EI_NIDENT];
uint16_t e_type; /* ELF文件类型 */
uint16_t e_machine; /* 硬件平台 */
uint32_t e_version; /* ELF头部版本 */
ElfN_Addr e_entry; /* 程序执行的入口地址 */
ElfN_Off e_phoff; /* 程序执行的入口地址 */
ElfN_Off e_shoff; /* 节头表偏移量 */
uint32_t e_flags; /* 处理器特定标志 */
uint16_t e_ehsize; /* ELF头部长度 */
uint16_t e_phentsize; /* 程序头表中一个条目的长度 */
uint16_t e_phnum; /* 程序头表条目数目 */
uint16_t e_shentsize; /* 节头表中一个条目的长度 */
uint16_t e_shnum; /* 节头表条目个数 */
uint16_t e_shstrndx; /* 节头表字符索引 */
} ElfN_Ehdr;

1.2.4 程序头表

​ 程序头表会记录ELF文件中所有段的信息,包括段的类型,段的偏移地址,段的虚拟地址,物理地址等信息。段类型则可以分为PT_LOAD(描述可装载的段,如代码段,数据段,这些可被装载进内存中),PT_DYNAMIC(动态链接文件特有,包含了动态链接文件所需要的信息) ,PT_NOTE(保存了与特定供应商或者系统相关的附加信息),PT_INTERP,PT_PHDR(程序头表本身位置和大小)

1.2.5 节头部表

​ 节头表用于描述ELF文件各节区的位置和大小,主要用于链接与调试,节头对于程序的运行来说不是必须的。

1.2 Dex文件结构

1.2.1 Dex文件头

​ dex文件头一般固定为0x70个字节大小,包含标志、版本号、校验码、sha-1 签名以及其他一些方法、类的数量和偏移地址等信息。如下图所示。

1.2.2 字符串解析

​ 在文件头0x38和0x3c存储了字符串信息,分别是字符串数量以及字符串索引区的偏移地址。在字符串索引区,四字节一组表示一个字符串在数据区的偏移地址。偏移地址里采用了MUTF-8编码,第一个字节表示字符串长度,最后一个字节00表示字符串结束

1.2.3 类的类型解析

​ 类的偏移地址里存储了该类名在字符串数组中的索引值,4字节一组。

1.2.4 方法原型的解析

​ Dex中的方法原型定义了一个方法的返回值类型和参数类型,例如一个方法返回值为void,参数类型为int,则方法原型表示为V(I)。一共12个字节,前四个字节是用到的字符串索引,后八个字节是返回值,参数用到的类的索引。

1.2.5 字段解析

​ 在偏移地址处有8个字节,前两个字节是之前解析出类的索引,表示这个字段在该类中定义。3-4字节也是类的索引,表示该字段类型。最后四个字节是字符串列表的索引,用来表示字段名。

1.2.6 方法定义解析

​ 在偏移地址处有8个字节,前两个字节是类解析的索引,用来表示该方法属于哪个类。3-4字节是方法原型的索引,最后四个字节是字符串索引用来表示方法名。

1.2.7 类的解析

​ class_def_item有32个字节,1-4字节是之前类类型解析的索引,也就是类名。5-8字节是access_flags,是该类的访问标志,表明这个类是public还是private。9-12字节是父类类型解析的索引,也就是父类名。13-16字节是接口信息的偏移地址。17-20字节是字符串索引,表明该类所在的java文件名。21-24字节是注释信息的偏移地址,25-28字节指向class_data_off,是类数据第二层结构的偏移地址,该结构中记录了该类的方法和字段。29-32字节在没有相关信息情况下为0,如果有信息会指向一个结构的偏移地址。

类的第二次层结构:class_data_item

​ class_data_item中第一个uleb128编码,指明了该类中静态字段的数量。第二个uleb128编码,指明了该类中实例字段的数量。第三个uleb128编码指明了该类中直接方法的个数。第四个uleb128编码指明了该类中虚方法的个数。然后如果各字段/方法数量>0,又会有指明相应字段/方法详细信息的结构。这里具体以方法信息为例,里面有3个uleb128编码,第一个是方法原型的索引,第二个是访问标志,第三个则是方法具体字节码的偏移地址。对应结构体是code_item,里面记录了smali代码。

类的第三层结构: code_item

​ code_item中会记录该方法使用的寄存器数量,参数个数,try-catch数量,调试信息结构体偏移,指令列表等信息。指令列表里面就是一些opcode。

到这里,其实就可以根据以上信息对dex进行一个反编译为smali指令的工作了。

References

https://cloud.tencent.com/developer/article/1663852