原生程序文件格式
- Android 原生程序符合 ARM 的 EABI 规范并用 ELF 作为可执行程序文件格式
- ARM ELF File Format 文档描述了 ARM 平台上 ELF 文件必须实现和遵循的规范和细节,Android 在实现该规范时扩展了它
- 不同处理器架构的 ELF 文件,仅在平台架构细节方面存在差异,ELF 文件的整体结构和使用的数据类型在定义上是一致的
- 除了要符合 EABI 规范,Android 平台上的 ELF 文件的生成还要依靠链接器执行链操作时的链接脚本。链接脚本定义了生成的可执行程序中各节区的名称、地址空间布局及一些文件格式的链接,不同的链接脚本生成的原生可执行文件的格式可能不同。Android 原生程序的编译工具链用的编译器是 gcc 和 Clang,Clang 用的链接器是 gcc 的后端,因此所有原生程序的链接工作都由编译工具链中的 ld 命令和相应的链接脚本完成
- 可用之前章节提到的 010 Editor 的 ELF.bt 模板学习 ELF 文件格式
原生程序的文件类型
- 软件开发中,经常能看到原生程序类型有扩展名为“.a”的静态链接库、“.so”的动态链接库及没有扩展名的原生可执行文件。若关注编译细节,还能看到“.o”的目标文件(Object File)。虽然这些文件都属于原生程序,但只有原生可执行文件才可直接执行,其他的都要通过间接的方式加载执行
- ELF 格式文档将原生程序分为三类描述:
- Relocatable File:可重定位文件,以“.o”结尾的目标文件
- Executable File:原生可执行文件
- Shared Object File:共享的目标文件
- 可执行如下命令生成这三种类型文件:
- 上图一是用的 Clang,加不加
-fPIE -pie
编译出的都是共享的目标文件。上图二用的是 gcc,不加上述参数编译出的是原生可执行文件,加了编译出的是共享的目标文件。编译时,若只编译不链接,生成的“.o”文件就是可重定位文件
- 上图中用
file
命令查看 app 文件时,输出中有“LSB”,说明程序是小端字节序(Little-Endian)的,若输出是“MSB”,说明是大端字节序的。Android 系统默认使用小端字节序
AArch64 ELF 文件格式
- 原生可执行文件与共享的目标文件很相似,下面的文件格式学习中,若不特意指明,则对二者皆有效,二者的区别会在合适时机学习
- 一个 ELF 文件可按内容分为三部分:
- ELF Header:文件头
- Program Header Table:程序头表,包含多个 Program Header 和 Segment(段)数据
- Section Header Table:节区头表,包含多个 Section Header 和 Section(节区)数据
- ELF Header 描述了 ELF 的基本文件信息,其中就有 Program Header Table 和 Section Header Table 在 ELF 中的偏移
- Program Header Table 定义的是 Segment 信息。Segment 的概念和 macOS 中的 Mach-O、Windows 中的 PE 类似,描述了一个地址段数据的读、写、执行属性及是否会加载到内存等信息。ELF 中的 Program Header Table 可有多个 Program Header,每个 Program Header 对应一个 Segment,Segment 的个数由 ELF Header 给出
- Section Header Table 定义的是 Section 信息。其中的 Section Header 在 ELF 中可有多个,每个 Section Header 对应一个 Section 的数据,Section 的个数在 ELF Header 中给出
- Section 和 Segment 的关系:一个 Segment 可包含多个 Section;每个 Section 只属于一个 Segment(特殊情况下可同时属于多个 Segment,且 Segment 间的数据可重叠)
- ELF 根据链接和执行阶段所需的文件信息,提供了链接视图(Linking View)和执行视图(Execution View):
- 可看到,Section 包含了链接时需要的信息,而 Segment 包含了运行时需要的信息。ELF 在链接时,链接器通过 Section Header Table 寻找节区信息;在运行时,加载器通过 Program Header Table 寻找 Segment 并加载
- “.o”文件作为“中间结果”,既不能加载,也不能执行。可重定位的“.o”文件不包含 Program Header Table。低版本的 Android 系统中,由于执行时不依赖 Section Header Table 的信息,对加载到内存的 ELF,Section Header Table 没有用,很多 so 的软件壳通过这个特性修改与 ELF 文件头的 Section Header Table 相关的文件结构信息,达到反逆向分析。Android 7.0 及后来的版本,这种保护方式无效,动态链接库在加载 ELF 时加强了对文件格式的验证,包括在加载时校验 ELF 的 Section Header Table 信息,因此软件壳要调整保护策略
- ELF 文件的格式定义可在 Android NDK 的头文件
elf.h
中找到,也可在 Android 源码文件 art/runtime/elf.h
中找到。Android 平台上 ELF 文件的 EABI 还没稳定下来,每次 Android 系统升级和 NDK 升级都可能使文件格式变化
elf.h
重新定义了程序使用的基本数据类型:
typedef __u32 Elf32_Addr;
typedef __u16 Elf32_Half;
typedef __u32 Elf32_Off;
typedef __s32 Elf32_Sword;
typedef __u32 Elf32_Word;
typedef __u64 Elf64_Addr;
typedef __u16 Elf64_Half;
typedef __s16 Elf64_SHalf;
typedef __u64 Elf64_Off;
typedef __s32 Elf64_Sword;
typedef __u32 Elf64_Word;
typedef __u64 Elf64_Xword;
typedef __s64 Elf64_Sxword;
- Elf32 表示该数据类型是 32 位的 ELF,Elf64表示该数据类型是 64 位的 ELF,“Addr”结尾的数据表示地址指针,“Off”结尾的数据表示文件偏移,其他数据类型表示不同的字段占用的字节数
- 不同的 ELF 架构,其 ELF Header 也不同。32 位的 ELF 文件头用结构体 Elf32_Ehdr 表示,64 位的用 Elf64_Ehdr 表示:
#define EI_NIDENT 16
typedef struct elf32_hdr {
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;
typedef struct elf64_hdr {
unsigned char e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
e_ident
- Elf32_Ehdr 和 Elf64_Ehdr 的字段基本一样,只数据类型的位数不同,判断一个 ELF 是 32 位还是 64 位,依据是第一个
e_ident
字段,EI_NIDENT
的值为 16,在 e_ident
字段的 16 字节中,按每个字节的序号定义了它们的用途
#define EI_MAG0 0
#define EI_MAG1 1
#define EI_MAG2 2
#define EI_MAG3 3
#define EI_CLASS 4
#define EI_DATA 5
#define EI_VERSION 6
#define EI_OSABI 7
#define EI_PAD 8
#define ELFMAG0 0x7f
#define ELFMAG1 'E'
#define ELFMAG2 'L'
#define ELFMAG3 'F'
#define ELFMAG "\177ELF"
#