菜单

kaoqi
kaoqi
发布于 2026-07-17 / 66 阅读
1
0

AARCH64--EFI文件解析

本文以grub2-GNU项目的代码为依托展开

GRUB2-GNU下载链接:https://ftp.gnu.org/gnu/grub

在下载到本地后,可以首先执行bootstrap命令生成m4相关的.in文件,然后执行configure命令,根据各个.in文件生成makefile文件,最后执行make命令,就可以得到一个属于自己的efi文件啦。

这里以生成的比较通用型的bootaa64.efi文件举例,其文件头部各个地方遵循着于/include/grub/pe32.h头文件中所描述的内容。下面结合实际文件进行分析。

hexdump、objdump两个命令是比较常用的可以用于二进制文件解析的工具,其中hexdump比较直白易懂,就是把二进制以16进制的形式展现出来。

下面是efi文件的一个二进制图像:

首先,可以注意到这里的二进制文件遵循着一定的由msdos头-->PE头-->COFF头-->等等其他的规则。这一点可以在后面的struct文件解析中分析出来。注意,这个EFI文件一般由UEFI固件启动,也就是类BIOS的底层硬件东东。

UEFI固件协议:https://uefi.org/specs/UEFI/2.11/

(PS:UEFI固件协议关注ACPI协议更多一些,efi文件本身在此文中主要还是解决一下启动链初始化的问题,ACPI的参考:https://uefi.org/specs/ACPI/6.6/)

一般UEFI固件协议会丢一个handler和一个system table给efi文件的主函数,这里后文结合pe32.h文件细细讲一下怎么丢过去的。

PE32主要头部

1、DOS头

最先映入眼帘的是msdos头部,这里可以看到grub2代码中的定义如下:

可以看到,从grub头文件的角度看,我们几乎不关心除了magic和offset之外的数据。这里pe32.h文件还贴心的提供了一个参考范本给我们。可以看到,efi文件最开始就是这样的一个dos头部,头的第一个部分是5A4D的一个魔术字头,然后就主要依靠最后的00000080

2、PE头

由DOS头的最后可以指向PE头,PE头的结构体分为PE头整体和COFF头细分两个领域。具体可以参考:

这里的结构中,PE头首部首先是有一个结构体,然后进入COFF头,如下:

这里可以看到,efi的hexdump中,根据DOS头最后偏移地址00000080跳转到PE头后,下面做基于上述struct的一个解析:

首先是char形式45(E)50(P)的一个PE头魔术字头,为4代表这个魔术字头占4个位置,也即为45500000;

然后是maine号,这个好理解,aa64就代表aarch64的CPU指令机架构嘛;

再然后是段数目num_section,这个通过objdump命令可以看出对应关系来,确实是4个段;

然后是时间戳time,这里的8E0054A4通过小端字节序转变为54A48E00之后,换算为10进制,再带入到1970的时间计算公式中可以算出时间为2015年1月1日早上8点的意思;

symtab_offset这个32位的值为0,其实说明没有symtab,即不含COFF的符号表(侧面说明这不是一个x86)

num_symbols这个参数对应COFF符号表的数目,当然也为0

optional_header_size这个值为00f0,代表着后续可选头的大小

characteristics这个值为020e代表着文件属性标志位,例如是否可执行、是否为 DLL、是否去除了重定位信息等。

注意,这里描述了optional header 的大小为00f0,此时COFF头结束位置在0098位置,所以optional头结束位置在0188位置。

3、PE可执行头Optional Header

前面的DOS头和PE头并没有给定具体的信息,比如代码的整体长度、入口函数位置等信息。这些信息实际上是在PE头的optional_header中定义的。

optional头参考pe32.h文件中的grub_pe64_optional_header结构体。

这是一个非常的长而且非常的大的结构体,它紧跟着上文提及的COFF头存在,可以看到,首部魔术字为020e时代表这个optional header是一个pe64格式的,如果首部魔术字为010e的话,则代表为pe32格式的。下面可以根据hexdump的图和struct做一个对比分析:

uint16的magic字段为020e,代表这里启用的是pe64 optional header;

unit8的major linker version,0b,代表做这个东西的链接器的版本最大值

unit8的minor linker version,02,代表做这个东西的链接器的版本最小值

uint32的code size,代表的是代码的大小长度为0000e000

uint32的data size,代表的是数据大小长度为0000d000

uint32的bss size,代表的是未初始化全局变量的大小为00090000

这里,三个东西加起来的大小为000ab000的大小,实际的代码块大小为000ad000的大小。

uint32的entry addr,代表的是入口函数的地址为00001000

uint32的code base,代表的是代码节起始的RVA相对虚拟地址为00001000

uint64的image base,代表的是首选加载基地址,在64位的EFI文件中一般为0

uint32的section alignment,代表的是代码或数据节加载到内存中的对齐颗粒度,此处为00001000代表颗粒度为4KB,需要确保起始地址为该数值的整数倍

uint32的file alignment,代表的是文件中原始节数据的对齐因子,此处为00001000代表颗粒度为4KB

uint16的major os version,操作系统的最高版本,这里为0000

uint16的minor os version,操作系统的最低版本,这里为0000

uint16的major image version,镜像文件的版本号,这里为0000

uint16的minor image version,最低版本号,这里为0000

uint16的major subsystem version,子系统版本,这里为0000

uint16的minor subsystem version,子系统最低版本,这里为0000

上述这些版本好共有6个uint16,都是0000

uint32 reserved,保留字段,为00000000

uint32的image size,image大小,为0000d000

uint32的header size,header的大小,为000a1000

uint32的checksum,校验和,为00000000

uint16的subsystem,子系统,为0000

uint16的all characteristics,DLL或可执行文件的动态特性标志位,000a

uint32的stack reserve size,初始保留栈空间大小,01000000

uint32的stact commit size,初始实际提交的栈空间大小,00010000

uint32的heap reserve size,初始保留的堆空间大小,00000000

uint32的heap commit size,初始提交的堆空间大小,00010000

uint32的loader flags,保留字段,历史上用于调试,目前已废弃,00000000

uint32的num data directories,数据目录表,00000000

另外,PE头以下的这些数据导航的内容,也全为0

/* Data directories. */

struct grub_pe32_data_directory export_table;00000000

struct grub_pe32_data_directory import_table;00000000

struct grub_pe32_data_directory resource_table;00000000

struct grub_pe32_data_directory exception_table;00000010

struct grub_pe32_data_directory certificate_table;00000000

struct grub_pe32_data_directory base_relocation_table;00000000

struct grub_pe32_data_directory debug;00000000

struct grub_pe32_data_directory architecture;00000000

struct grub_pe32_data_directory global_ptr;00000000

struct grub_pe32_data_directory tls_table;00000000

struct grub_pe32_data_directory load_config_table;00000000

struct grub_pe32_data_directory bound_import;00000000

struct grub_pe32_data_directory iat;00000000

struct grub_pe32_data_directory delay_import_descriptor;00000000

struct grub_pe32_data_directory com_runtime_header;0000c000

struct grub_pe32_data_directory reserved_entry;000a1000

最后总结一下这个optional header,其中最重要的东西起始就是给定的主要的entry addr的偏移地址了,有这个偏移地址,接下来就可以去做ELF格式的段啦~

注意,在optional header之后,起始还跟了一些描述段头的东西:

段从0188位置开始,遵从如下结构:(0188位置开始是因为optional_header_size大小为00f0,而且COFF头是在0098位置结束的)

段名为2e74657874000000,即char[8]对应的.text段名

uint32的virtual size为0000e000,段大小

uint32的virtual address为00001000,段地址

uint32的raw data size为0000e000,原始数据大小

uint32的raw data offset为00001000,原始数据偏移

uint32的relocations offset为00000000,重定位偏移

uint32的line numbers offset为00000000,行偏移数目为0

uint16的num relocations为0000,重定位数目为0

uint16的num line numbers为0000,行偏移数目为0

uint32的characteristics为60000020,文件属性描述符

这样的段结构组织起来,就构成后续的ELF结构


评论