第八章 Android 原生程序开发与逆向分析(三)(原生程序文件格式)

本文详细介绍了Android原生程序的文件格式,重点讲解了AArch64 ELF文件结构,包括文件头、程序头表和节区头表等关键部分。通过对ELF Header的e_ident、e_type、e_machine等字段的分析,揭示了程序的类型、字节序和架构信息。此外,还探讨了Program Header Table和Section Header Table的作用,以及动态链接库的加载和执行视图。
摘要由CSDN通过智能技术生成

原生程序文件格式

  • 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"
#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值