diff --git a/HISTORY.rst b/HISTORY.rst index c79701b..4386e7a 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,11 +2,17 @@ History ======= +2.0.5 (2024-03-21) +------------------ +- Feature Improvements: + - Base implementation for parse EFI variable data + + 2.0.4 (2024-02-28) ------------------ - Feature Improvements: - - JSON output capability has been introduced to enhance the online mode operation output. - + - JSON output capability has been introduced to enhance the online mode operation output. + - Bug Fixes: - Addressed an issue with CSV generation in the generate_csv() function, ensuring smoother functionality. diff --git a/parse_bin.py b/parse_bin.py new file mode 100644 index 0000000..58ff603 --- /dev/null +++ b/parse_bin.py @@ -0,0 +1,23 @@ +import os + +from xmlcli import XmlCli as cli +from xmlcli.common import bios_fw_parser + +workspace_dir = r"D:\PublicShare\gahan\_debug\NVL_bios_data_bin" +bin_file = os.path.join(workspace_dir, "BIOS_NVL_S_Internal_0462.00_Dispatch_VS__PreProd.rom") +# bin_file = os.path.join(workspace_dir, "MTL_FSPWRAPPER_3184_01_R.rom") +output_xml = f"{bin_file}.xml" +output_json = f"{bin_file}.json" + +uefi_parser = bios_fw_parser.UefiParser(bin_file=bin_file, + parsing_level=0, + base_address=0x0, + guid_to_store=[cli.fwp.gBiosKnobsDataBinGuid] + ) +# parse binary +output_dict = uefi_parser.parse_binary() +output_dict = uefi_parser.sort_output_fv(output_dict) +# write content to json file +uefi_parser.write_result_to_file(output_json, output_dict=output_dict) + +cli.savexml(output_xml, bin_file) diff --git a/pyproject.toml b/pyproject.toml index b10e97f..293b201 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "xmlcli" -version = "2.0.2" +version = "2.0.5" description = "UFFAF - UEFI Firmware Foundational Automation Framework (formerly Xml-Cli)" authors = ["Gahan Saraya "] maintainers = ["Intel "] diff --git a/src/xmlcli/common/bios_fw_parser.py b/src/xmlcli/common/bios_fw_parser.py index d804801..ef6211e 100644 --- a/src/xmlcli/common/bios_fw_parser.py +++ b/src/xmlcli/common/bios_fw_parser.py @@ -61,6 +61,8 @@ FirmwareVolumeGuid = namedtuple("FirmwareVolumeGuid", ["name", "guid", "method", "description"]) +RAW_SECTION_EFI_INITIAL_OFFSET = 0x26 + class UefiParser(object): """ @@ -80,7 +82,11 @@ def __init__(self, bin_file, parsing_level=0, **kwargs): self.parsing_level = utils.PARSING_LEVEL_MAP.get(parsing_level, utils.PARSING_LEVEL_MAP.get(0)) self.user_specified_base_address = kwargs.get("base_address", None) self.guid_to_store = kwargs.get("guid_to_store", []) # if passed guid_to_store, list of guid then store the respective binary by guid + if self.guid_to_store: + self.guid_to_store = [utils.guid_formatter(guid) for guid in self.guid_to_store] log.info(f"GUID bins to store: {self.guid_to_store}") + self.parse_efi_variable = kwargs.get("parse_efi_variable", True) + self.efi_variables = {} # this data would be populated if parse_efi_variable set to `True`. # reformat guids to specific format self.stored_guids = {utils.guid_formatter(guid): [] for guid in self.guid_to_store} log.debug(self.stored_guids) @@ -465,6 +471,10 @@ def parse_ffs_section(self, buffer, buffer_pointer, end_point, ffs_data, _type, end = align_buffer + section.get_section_size() # calculate end of the section buffer # create name for section directory to store data/code parsed within it section_dir = os.path.join(bin_dir, f"SECTION_0x{align_buffer:x}_to_0x{end:x}") + if ffs_guid in self.guid_to_store and section_tuple.name == "EFI_SECTION_RAW": + self.parse_efi_variable_data(buffer, buffer_pointer=buffer_pointer + RAW_SECTION_EFI_INITIAL_OFFSET, + end_point=end) + print(self.efi_variables) if section_tuple.is_encapsulated: # encapsulation sections # value can be 0x1 - EFI_SECTION_COMPRESSION, 0x2 -EFI_SECTION_GUID_DEFINED or 0x3 - EFI_SECTION_DISPOSABLE nesting_level += 1 @@ -648,6 +658,29 @@ def read_guid_defined_section(self, buffer, guid, buffer_pointer, section, nesti ) return result + def parse_efi_variable_data(self, buffer, buffer_pointer=0x0, end_point=0x0): + if not self.parse_efi_variable: + log.info("EFI Variable parsing Skipped") + return False + log.debug(f"=======> offset: 0x{buffer_pointer:X} end: 0x{end_point:X}") + efi_var_struct = structure.efi_variable_structure() + if efi_var_struct.cls_size + buffer_pointer <= end_point: + buffer.seek(buffer_pointer) + efi_var_data = efi_var_struct.read_from(buffer) + if efi_var_data.guid.guid != '00000000-0000-0000-0000000000000000' and efi_var_data.name_length and efi_var_data.data_length: + efi_var_struct = structure.efi_variable_structure(name_length=efi_var_data.name_length, + data_length=efi_var_data.data_length) + if efi_var_struct.cls_size + buffer_pointer <= end_point: + buffer.seek(buffer_pointer) + efi_var_data = efi_var_struct.read_from(buffer) + log.debug(efi_var_data) + _key = f"{efi_var_data.get_name}_{efi_var_data.guid.guid}" + self.efi_variables[_key] = efi_var_data.dump_dict() + return self.parse_efi_variable_data(buffer, buffer_pointer=buffer_pointer + efi_var_data.cls_size, end_point=end_point) + else: + print("No data available to parse") + return False + def sort_output_fv(self, input_dict=None): from collections import OrderedDict input_dict = input_dict if input_dict else self.output diff --git a/src/xmlcli/common/structure.py b/src/xmlcli/common/structure.py index f5c1f41..481b34a 100644 --- a/src/xmlcli/common/structure.py +++ b/src/xmlcli/common/structure.py @@ -705,6 +705,47 @@ class FitEntry(utils.StructureHelper): ('Checksum', ctypes.c_uint8), # 1 byte ] + +############################################################################### +# EFI Variable Structure +############################################################################### +def efi_variable_structure(name_length=0, data_length=0): + """Structure for EFI Variable stored in NVRAM region + + REF: edk2/BaseTools/Source/C/Include/Protocol/HiiFramework.h + + :param name_length: Length data bytes for efi variable name + :param data_length: data size of the given variable + :return: + """ + class EfiVariableStructure(utils.StructureHelper): + _pack_ = 1 + _fields_ = [ + # read bits + ("unknown", ctypes.ARRAY(ctypes.c_uint8, 0x21)), # Unknown size between two structures + ## Offset -- 0x47 + ("State", ctypes.c_uint8), #--> 00 + ("Attribute", ctypes.c_uint32), #--> 00 00 00 00 + ("name_length", ctypes.c_uint32), #--> 00 00 00 0C + ("data_length", ctypes.c_uint32), #--> 00 00 10 56 + ("guid", utils.Guid), # 16 Bytes + ("name", ctypes.ARRAY(ctypes.c_uint8, name_length)), # 12 Bytes + ("data", ctypes.ARRAY(ctypes.c_uint8, data_length)), # 4182 Byes + ] + + @property + def get_name(self): + return ''.join([chr(i) for i in self.name]).replace("\x00", "") + + def get_value(self, name): + if name == "name": + return self.get_name + else: + return super().get_value(name) + + return EfiVariableStructure() + + # END:STRUCTURES ####################################################################################################### diff --git a/src/xmlcli/common/utils.py b/src/xmlcli/common/utils.py index 9e86846..e6767db 100644 --- a/src/xmlcli/common/utils.py +++ b/src/xmlcli/common/utils.py @@ -637,6 +637,8 @@ def array_to_int(val): val = [f"{_val:0>2x}" if is_integer(_val) else _val.dump_dict() for _idx, _val in enumerate(list(val))] if flag: val = hex(int("".join(val[::-1]), 16)) + else: + return "" return val def get_value(self, name):