VariableTool
这是一个基于pycharm开发的变量编辑工具,旨在对指定的fd文件中的variable值进行修改。
导论
Variable的存储结构
下图为Variable在fdf文件中的描述,可以看出在fdf文件中Variable的存储结构层次为EFI_FIRMWARE_VOLUME_HEADER --> VARIABLE_STORE_HEADER -->
其中EFI_FIRMWARE_VOLUME_HEADER为存储Variable的固件卷头,存储着固件卷的各种属性,VARIABLE_STORE_HEADER声明了Variable的存储区域属性。
上述两部分基本为固定值,在VARIABLE_STORE_HEADER之后,便是存储变量的存储区了,存储区中的某一个Variable的存储结构为:
VARIABLE_HEADER (GUID在里面)--> VARIABLE_NAME --> VALUE
其中VARIABLE_HEADER中包含了VARIABLE的各种属性,如State、Attributes、Datasize等。而多个变量的存储结构为:
VARIABLE_HEADER --> VARIABLE_NAME --> VALUE
--> VARIABLE_HEADER --> VARIABLE_NAME --> VALUE
--> VARIABLE_HEADER --> VARIABLE_NAME --> VALUE
.........
基于此,想要修改fd文件中的Variable值,那么首先需要找到这块VARIABLE_STORE_HEADER,而GUID与Vairable_Name唯一标识了一个Variable,因此,从头部向后检索GUID与Vairable_Name便可以找到需要修改的Vairable。
python中的二进制文本格式
fd文件是一个二进制文件,这个二进制文件内容如下所示,可以看到都是以1个字节为单位显示,但是,在python中,利用python的open函数,以rb+形式打开一个fd文件,最终读到的fd文件的内容格式为b'\x**\x**\x**\x**\x**\x**........',即下图第二行的78E58C8C,在python中是b'\x78\xE5x8C\x8C'。
基于此,想要寻找fd文件中的指定内容,需要先将输入内容转换为固定的b'\x**\x**\x**\x**\x**\x**........'格式,然后再到fd文件中遍历。
对齐问题
在Variable的存储中会涉及到对齐问题,关于Variable的对齐问题,一样通过研究源码来看,可以看到关于对齐主要有两个宏定义,一个是ALIGNMENT、另一个是HEADER_ALIGNMENT。
两个宏的主要使用集中在下图,通过注释可以看到该部分主要操作是计算Variable的总大小,将代码倒着看,从TotalNeededSize开始看,可以看到Variable的总大小为VariableSize的值,而VariableSize的值调用了HEADER_ALIGN宏,该宏的作用是将输入参数对齐到HEADER_ALIGNMENT的倍数,此处HEADER_ALIGNMENT为4,因此就是将输入参数对齐到4的倍数,输入参数有三个Variable_Header_Size,VarNameSize,VarDataSize,根据前文导论,可以看出,是相呼应的,表明了Variable的存储结构。
再往上看VarNameSize与VarDataSize都经过了一个GET_PAD_SIZE处理,此处看到上图由于ALIGNMENT = 1,因此GET_PAD_SIZE为0,可以推测如果ALIGNMENT为其他值,此处其实也是对Name与Data进行对齐处理,对齐到ALIGNMENT的倍数。
程序流程图
函数介绍
整个工具的函数由三部分组成,test(),custom_string_rearrange(),string_to_hex()
custom_string_rearrange
custom_string_rearrange主要用于GUID转换顺序,GUID由8-4-4-16格式组成,但在EmulatorPkg.fd文件中的gEfiVariableGuid排布形式可以看出格式中的8是以每两位倒序排布的,接着是第一个4,同样是以每两位倒序排布,第二个4同理,而最后的16却是以顺序排布。
但是从用户端读取GUID的习惯自然是全顺序输入,例如ABCDEFGH,但程序却只能处理GHEFCDAB,因此custom_string_rearrange便是做了这件事,通过切片操作,将GUID分段处理,最终得到程序能够读取的GUID顺序。
string_to_hex
string_to_hex用于将变量名进行硬编码,将输入的变量名转换为\x**类型,根据导论提到的python中二进制文本格式,输入的字符串,必须最后变成b'\x**\x**\x**\x**\x**\x**........'格式,才能被识别出,而string_to_hex正是将变量名进行硬编码,先转换为\x**\x**\x**\x**\x**\x**........,随后利用ast模块中的literal_eval将\x**\x**\x**\x**\x**\x**........转化为b'\x**\x**\x**\x**\x**\x**........',如此,变量名便可被识别。
在程序实现中,还涉及到一个问题,即在fd文件中,变量名以2个字节表示1个字符,且结束符也算在变量名中,例如setup,16进制为7365747570a,存储形式为0x73,0x00,0x65,0x00,0x74,0x00,0x75,0x00,0x0a,0x00,0x00,0x00,因此该函数还需要在每个字符后添加\x00,以确保2个字节表示一个字符,而在结尾加上2个\x00确保结束符也被转化。
此处提一嘴输入的GUID、变量值同样需要转化为b'\x**\x**\x**\x**\x**\x**........'格式,但是由于其在fd中存储并非以2个字节为1单位,因此无需如此繁琐,只需要先硬编码,在ast即可。
test
test函数为整个工具的主题部分,所做的主要工作有读取文件、读取GUID、判断GUID是否存在、读取变量名、读取变量值,修改变量值。
读取文件:
此处以rb+的形式打开fd_file_name,即以二进制读写的形式打开fd文件,将文件的数据读到binary_data。
读取GUID:
此处调用了custom_string_rearrange函数,该函数的主要作用是将用户输入的GUID值转换为固定格式。前文已经提过,随后便是硬编码,ast,最终转为b'\x**\x**\x**\x**\x**\x**........'格式,这种数据就是binary_data中的数据存放格式,接下来,便可在binary_data进行检索GUID。
判断GUID是否存在:
GUID存在:
判断变量名是否存在,此处调用string_to_hex,将输入的变量转换为16进制格式。即输入Phytium,得到b'\x50\x00\x68\x00\x79\x00\x74\x00\x69\x00\x75\x00\x6d\x00\x00\x00',此处的string_to_hex主要做了两个工作,首先在每个字节后插入\x00,其次在末尾加入结束符, 随后利用ast.literal_eval将其转换为b'\x******'格式的数据。将上述转换后的变量名输入binary_data检索。
若变量名存在:将输入的变量值进行同样的操作,转换为16进制形式,此处补\X00是由用户输入的变量值的大小与原变量值的存储大小决定的,原变量值的存储大小,即DataSize,在Variable_Header中已经声明,因此,利用DataSize - 输入的变量值长度,即可得到需要补充的\x00的数量,即假设原始DataSize为6,用户输入变量值1234,转16进制并补\X00后为b'\x31\x32\x33\x34\x00\x00‘,随后将整体直接覆写之前的空间。
GUID不存在:
执行创建新变量操作,将新输入的GUID、变量名、变量值都转换为16进制格式。首先检索到Variable_Store_Header的最后一段\x5A\xFE\x00\x00\x00\x00\x00\x00,接下来向后检索,当检索到连续4个FF,意味着此处为存储空闲区,因此在此处写入Variable_Header (含GUID)+ Variable_name + Value。
运行
修改已有变量
文件为FV_RECOVERY_Test.fd,提前向变量存储区添加变量Phytium,值 “3D
变量Phytium值已修改为Test123
添加变量
同样向FV_RECOVERY_Test.fd中添加变量AddVar,值为Add123
添加功能
添加读取有效变量个数功能
思路
先查找到Variable_Store_Header的末尾,在这之后存储的便是各个变量,根据Variable_Header中的startId是固定的0xAA、0x55,以及state属性为VarADDED,即0x3F位有效变量,那么只要搜寻存储区头部后有多少个\xAA\x55\x3F\x00即可找到有效的变量个数,直到找到连续4个FF,意味着已到存储区末,退出查找。
程序流程图
运行
三个有效变量Phytium、AddVar、Test2
1个有效变量Phytium
改进
之前的运行其实并没有考虑对齐问题,而在导论中提到的对齐问题已经诠释了edk2源码中是如何解决Variable的存储对齐问题,而在利用UefiBiosEditor读取fd文件后,最终得到了Variable的真实对齐方式。
下图为一个名为a,而值为12的变量,可以观察到
00000000地址存储的是a的Variable_Header,其中AA55、3F分别Header中的startId和state,07000000为a的属性,即NV、RT、BS,04000000为a的NameSize,此处为4的原因是a为char16类型,因此转化为16进制为6100,而结束符也需要计算在内,即0000,因此a的NameSize为4字节,而第二个04000000为a的DataSize,即a含有一个4字节大小的值。
00000010存储的是a的GUID。
00000020存储的是a的Variable_name与Variable_name,其中61000000为Variable_name,即a,而31320000为Variable_value,即12。
从这可以得出Vairable的对齐只需要判断Variable_name+Variable_value为4字节的最小公倍数即可,此处为8字节,其中Variable_name4字节,Variable_value4字节,抛开对齐规则,其实Variable_value只有2字节,但是为了对齐,补上了2字节的00。
思路
代码实现
首先依旧是输入新的GUID,并调序,转换16进制形式
输入变量名,并转换为16进制形式
输入变量属性,转换为16进制形式
输入变量值,并进行对齐操作
1、计算Vairable_name的长度,Variable_value的长度
2、得到两者之和除4的余数
3、利用4减余数即可得到需要补0的个数
获取输入的Vairable_name的大小与Variable_value的大小,填充到Variable_Header中
最终运行
添加新变量
向1.fd文件中添加多个变量
利用UefiBiosEditor读取1.fd,可以查找到所有变量,在未加入对齐之前,是无法检测到多个变量的,而加入对其之后,已可以检测出全部变量。
而在模拟器中也是可以找到这些变量的,此处有些变量显示不全,为模拟器的bug问题
修改已有变量
ab变量的初始值为123abc
修改ab的值为test
修改成功
总结
此VariableTool并非真正完美实现了对Variable的编辑,仅作学习Variable使用,在针对已有变量的修改功能中,从edk2源码中的SetVariable函数可以看到,实际上并非直接修改已有变量的值,而是先在空闲区重新建立一个同名变量,将修改值放在该同名变量下,并将原来的变量的头部中的state置为delete,同名变量的头部的state置为VAR_ADDED,而在创建新变量时,也需要考虑创建的变量为Non-Volatile类型还是Volatile类型以及是否调用Reclaim操作等。