#!/usr/bin/python3 ''' Copyright (c) Apple Inc. 2021 SPDX-License-Identifier: BSD-2-Clause-Patent Class that abstracts PE/COFF debug info parsing via a Python file like object. You can port this code into an arbitrary debugger by invoking the classes and passing in a file like object that abstracts the debugger reading memory. If you run this file directly it will parse the passed in PE/COFF files for debug info: python3 ./efi_pefcoff.py DxeCore.efi IA32`/DxeCore.dll load = 0x00000000 EntryPoint = 0x000030d2 TextAddress = 0x00000240 DataAddress = 0x000042c0 .text 0x00000240 (0x04080) flags:0x60000020 .data 0x000042C0 (0x001C0) flags:0xC0000040 .reloc 0x00004480 (0x00240) flags:0x42000040 Note: PeCoffClass uses virtual addresses and not file offsets. It needs to work when images are loaded into memory. as long as virtual address map to file addresses this code can process binary files. Note: This file can also contain generic worker functions (like GuidNames) that abstract debugger agnostic services to the debugger. This file should never import debugger specific modules. ''' import sys import os import uuid import struct import re from ctypes import c_char, c_uint8, c_uint16, c_uint32, c_uint64, c_void_p from ctypes import ARRAY, sizeof from ctypes import Structure, LittleEndianStructure # # The empty LittleEndianStructure must have _fields_ assigned prior to use or # sizeof(). Anything that is size UINTN may need to get adjusted. # # The issue is ctypes matches our local machine, not the machine we are # trying to debug. Call patch_ctypes() passing in the byte width from the # debugger python to make sure you are in sync. # # Splitting out the _field_ from the Structure (LittleEndianStructure) class # allows it to be patched. # class EFI_LOADED_IMAGE_PROTOCOL(LittleEndianStructure): pass EFI_LOADED_IMAGE_PROTOCOL_fields_ = [ ('Revision', c_uint32), ('ParentHandle', c_void_p), ('SystemTable', c_void_p), ('DeviceHandle', c_void_p), ('FilePath', c_void_p), ('Reserved', c_void_p), ('LoadOptionsSize', c_uint32), ('LoadOptions', c_void_p), ('ImageBase', c_void_p), ('ImageSize', c_uint64), ('ImageCodeType', c_uint32), ('ImageDataType', c_uint32), ('Unload', c_void_p), ] class EFI_GUID(LittleEndianStructure): _fields_ = [ ('Data1', c_uint32), ('Data2', c_uint16), ('Data3', c_uint16), ('Data4', ARRAY(c_uint8, 8)) ] class EFI_SYSTEM_TABLE_POINTER(LittleEndianStructure): _fields_ = [ ('Signature', c_uint64), ('EfiSystemTableBase', c_uint64), ('Crc32', c_uint32) ] class EFI_DEBUG_IMAGE_INFO_NORMAL(LittleEndianStructure): pass EFI_DEBUG_IMAGE_INFO_NORMAL_fields_ = [ ('ImageInfoType', c_uint32), ('LoadedImageProtocolInstance', c_void_p), ('ImageHandle', c_void_p) ] class EFI_DEBUG_IMAGE_INFO(LittleEndianStructure): pass EFI_DEBUG_IMAGE_INFO_fields_ = [ ('NormalImage', c_void_p), ] class EFI_DEBUG_IMAGE_INFO_TABLE_HEADER(LittleEndianStructure): pass EFI_DEBUG_IMAGE_INFO_TABLE_HEADER_fields_ = [ ('UpdateStatus', c_uint32), ('TableSize', c_uint32), ('EfiDebugImageInfoTable', c_void_p), ] class EFI_TABLE_HEADER(LittleEndianStructure): _fields_ = [ ('Signature', c_uint64), ('Revision', c_uint32), ('HeaderSize', c_uint32), ('CRC32', c_uint32), ('Reserved', c_uint32), ] class EFI_CONFIGURATION_TABLE(LittleEndianStructure): pass EFI_CONFIGURATION_TABLE_fields_ = [ ('VendorGuid', EFI_GUID), ('VendorTable', c_void_p) ] class EFI_SYSTEM_TABLE(LittleEndianStructure): pass EFI_SYSTEM_TABLE_fields_ = [ ('Hdr', EFI_TABLE_HEADER), ('FirmwareVendor', c_void_p), ('FirmwareRevision', c_uint32), ('ConsoleInHandle', c_void_p), ('ConIn', c_void_p), ('ConsoleOutHandle', c_void_p), ('ConOut', c_void_p), ('StandardErrHandle', c_void_p), ('StdErr', c_void_p), ('RuntimeService', c_void_p), ('BootService', c_void_p), ('NumberOfTableEntries', c_void_p), ('ConfigurationTable', c_void_p), ] class EFI_IMAGE_DATA_DIRECTORY(LittleEndianStructure): _fields_ = [ ('VirtualAddress', c_uint32), ('Size', c_uint32) ] class EFI_TE_IMAGE_HEADER(LittleEndianStructure): _fields_ = [ ('Signature', ARRAY(c_char, 2)), ('Machine', c_uint16), ('NumberOfSections', c_uint8), ('Subsystem', c_uint8), ('StrippedSize', c_uint16), ('AddressOfEntryPoint', c_uint32), ('BaseOfCode', c_uint32), ('ImageBase', c_uint64), ('DataDirectoryBaseReloc', EFI_IMAGE_DATA_DIRECTORY), ('DataDirectoryDebug', EFI_IMAGE_DATA_DIRECTORY) ] class EFI_IMAGE_DOS_HEADER(LittleEndianStructure): _fields_ = [ ('e_magic', c_uint16), ('e_cblp', c_uint16), ('e_cp', c_uint16), ('e_crlc', c_uint16), ('e_cparhdr', c_uint16), ('e_minalloc', c_uint16), ('e_maxalloc', c_uint16), ('e_ss', c_uint16), ('e_sp', c_uint16), ('e_csum', c_uint16), ('e_ip', c_uint16), ('e_cs', c_uint16), ('e_lfarlc', c_uint16), ('e_ovno', c_uint16), ('e_res', ARRAY(c_uint16, 4)), ('e_oemid', c_uint16), ('e_oeminfo', c_uint16), ('e_res2', ARRAY(c_uint16, 10)), ('e_lfanew', c_uint16) ] class EFI_IMAGE_FILE_HEADER(LittleEndianStructure): _fields_ = [ ('Machine', c_uint16), ('NumberOfSections', c_uint16), ('TimeDateStamp', c_uint32), ('PointerToSymbolTable', c_uint32), ('NumberOfSymbols', c_uint32), ('SizeOfOptionalHeader', c_uint16), ('Characteristics', c_uint16) ] class EFI_IMAGE_OPTIONAL_HEADER32(LittleEndianStructure): _fields_ = [ ('Magic', c_uint16), ('MajorLinkerVersion', c_uint8), ('MinorLinkerVersion', c_uint8), ('SizeOfCode', c_uint32), ('SizeOfInitializedData', c_uint32), ('SizeOfUninitializedData', c_uint32), ('AddressOfEntryPoint', c_uint32), ('BaseOfCode', c_uint32), ('BaseOfData', c_uint32), ('ImageBase', c_uint32), ('SectionAlignment', c_uint32), ('FileAlignment', c_uint32), ('MajorOperatingSystemVersion', c_uint16), ('MinorOperatingSystemVersion', c_uint16), ('MajorImageVersion', c_uint16), ('MinorImageVersion', c_uint16), ('MajorSubsystemVersion', c_uint16), ('MinorSubsystemVersion', c_uint16), ('Win32VersionValue', c_uint32), ('SizeOfImage', c_uint32), ('SizeOfHeaders', c_uint32), ('CheckSum', c_uint32), ('Subsystem', c_uint16), ('DllCharacteristics', c_uint16), ('SizeOfStackReserve', c_uint32), ('SizeOfStackCommit', c_uint32), ('SizeOfHeapReserve', c_uint32), ('SizeOfHeapCommit', c_uint32), ('LoaderFlags', c_uint32), ('NumberOfRvaAndSizes', c_uint32), ('DataDirectory', ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16)) ] class EFI_IMAGE_NT_HEADERS32(LittleEndianStructure): _fields_ = [ ('Signature', c_uint32), ('FileHeader', EFI_IMAGE_FILE_HEADER), ('OptionalHeader', EFI_IMAGE_OPTIONAL_HEADER32) ] class EFI_IMAGE_OPTIONAL_HEADER64(LittleEndianStructure): _fields_ = [ ('Magic', c_uint16), ('MajorLinkerVersion', c_uint8), ('MinorLinkerVersion', c_uint8), ('SizeOfCode', c_uint32), ('SizeOfInitializedData', c_uint32), ('SizeOfUninitializedData', c_uint32), ('AddressOfEntryPoint', c_uint32), ('BaseOfCode', c_uint32), ('BaseOfData', c_uint32), ('ImageBase', c_uint32), ('SectionAlignment', c_uint32), ('FileAlignment', c_uint32), ('MajorOperatingSystemVersion', c_uint16), ('MinorOperatingSystemVersion', c_uint16), ('MajorImageVersion', c_uint16), ('MinorImageVersion', c_uint16), ('MajorSubsystemVersion', c_uint16), ('MinorSubsystemVersion', c_uint16), ('Win32VersionValue', c_uint32), ('SizeOfImage', c_uint32), ('SizeOfHeaders', c_uint32), ('CheckSum', c_uint32), ('Subsystem', c_uint16), ('DllCharacteristics', c_uint16), ('SizeOfStackReserve', c_uint64), ('SizeOfStackCommit', c_uint64), ('SizeOfHeapReserve', c_uint64), ('SizeOfHeapCommit', c_uint64), ('LoaderFlags', c_uint32), ('NumberOfRvaAndSizes', c_uint32), ('DataDirectory', ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16)) ] class EFI_IMAGE_NT_HEADERS64(LittleEndianStructure): _fields_ = [ ('Signature', c_uint32), ('FileHeader', EFI_IMAGE_FILE_HEADER), ('OptionalHeader', EFI_IMAGE_OPTIONAL_HEADER64) ] class EFI_IMAGE_DEBUG_DIRECTORY_ENTRY(LittleEndianStructure): _fields_ = [ ('Characteristics', c_uint32), ('TimeDateStamp', c_uint32), ('MajorVersion', c_uint16), ('MinorVersion', c_uint16), ('Type', c_uint32), ('SizeOfData', c_uint32), ('RVA', c_uint32), ('FileOffset', c_uint32), ] class EFI_IMAGE_SECTION_HEADER(LittleEndianStructure): _fields_ = [ ('Name', ARRAY(c_char, 8)), ('VirtualSize', c_uint32), ('VirtualAddress', c_uint32), ('SizeOfRawData', c_uint32), ('PointerToRawData', c_uint32), ('PointerToRelocations', c_uint32), ('PointerToLinenumbers', c_uint32), ('NumberOfRelocations', c_uint16), ('NumberOfLinenumbers', c_uint16), ('Characteristics', c_uint32), ] EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b DIRECTORY_DEBUG = 6 image_machine_dict = { 0x014c: "IA32", 0x0200: "IPF", 0x0EBC: "EBC", 0x8664: "X64", 0x01c2: "ARM", 0xAA64: "AArch64", 0x5032: "RISC32", 0x5064: "RISC64", 0x5128: "RISCV128", } def patch_void_p_to_ctype(patch_type, to_patch): '''Optionally patch c_void_p in the Structure._fields_''' if patch_type is None: return to_patch result = [] for name, c_type in to_patch: if type(c_type) == type(c_void_p): result.append((name, c_uint32)) else: result.append((name, c_type)) return result def patch_ctypes(pointer_width=8): ''' Pass in the pointer width of the system being debugged. If it is not the same as c_void_p then patch the _fields_ with the correct type. For any ctypes Structure that has a c_void_p this function needs to be called prior to use or sizeof() to initialize _fields_. ''' if sizeof(c_void_p) == pointer_width: patch_type = None elif pointer_width == 16: assert False elif pointer_width == 8: patch_type = c_uint64 elif pointer_width == 4: patch_type = c_uint32 else: raise Exception(f'ERROR: Unkown pointer_width = {pointer_width}') # If you add a ctypes Structure class with a c_void_p you need to add # it to this list. Note: you should use c_void_p for UINTN values. EFI_LOADED_IMAGE_PROTOCOL._fields_ = patch_void_p_to_ctype( patch_type, EFI_LOADED_IMAGE_PROTOCOL_fields_) EFI_DEBUG_IMAGE_INFO_NORMAL._fields_ = patch_void_p_to_ctype( patch_type, EFI_DEBUG_IMAGE_INFO_NORMAL_fields_) EFI_DEBUG_IMAGE_INFO._fields_ = patch_void_p_to_ctype( patch_type, EFI_DEBUG_IMAGE_INFO_fields_) EFI_DEBUG_IMAGE_INFO_TABLE_HEADER._fields_ = patch_void_p_to_ctype( patch_type, EFI_DEBUG_IMAGE_INFO_TABLE_HEADER_fields_) EFI_CONFIGURATION_TABLE._fields_ = patch_void_p_to_ctype( patch_type, EFI_CONFIGURATION_TABLE_fields_) EFI_SYSTEM_TABLE._fields_ = patch_void_p_to_ctype( patch_type, EFI_SYSTEM_TABLE_fields_) # patch up anything else that needs to know pointer_width EfiStatusClass(pointer_width) def ctype_to_str(ctype, indent='', hide_list=[]): ''' Given a ctype object print out as a string by walking the _fields_ in the cstring Class ''' result = '' for field in ctype._fields_: attr = getattr(ctype, field[0]) tname = type(attr).__name__ if field[0] in hide_list: continue result += indent + f'{field[0]} = ' if tname == 'EFI_GUID': result += GuidNames.to_name(GuidNames.to_uuid(attr)) + '\n' elif issubclass(type(attr), Structure): result += f'{tname}\n' + \ ctype_to_str(attr, indent + ' ', hide_list) elif isinstance(attr, int): result += f'0x{attr:x}\n' else: result += f'{attr}\n' return result def hexline(addr, data): hexstr = '' printable = '' for i in range(0, len(data)): hexstr += f'{data[i]:02x} ' printable += chr(data[i]) if data[i] > 0x20 and data[i] < 0x7f else '.' return f'{addr:04x} {hexstr:48s} |{printable:s}|' def hexdump(data, indent=''): if not isinstance(data, bytearray): data = bytearray(data) result = '' for i in range(0, len(data), 16): result += indent + hexline(i, data[i:i+16]) + '\n' return result class EfiTpl: ''' Return string for EFI_TPL''' def __init__(self, tpl): self.tpl = tpl def __str__(self): if self.tpl < 4: result = f'{self.tpl:d}' elif self.tpl < 8: result = "TPL_APPLICATION" if self.tpl - 4 > 0: result += f' + {self.tpl - 4:d}' elif self.tpl < 16: result = "TPL_CALLBACK" if self.tpl - 8 > 0: result += f' + {self.tpl - 8:d}' elif self.tpl < 31: result = "TPL_NOTIFY" if self.tpl - 16 > 0: result += f' + {self.tpl - 16:d}' elif self.tpl == 31: result = "TPL_HIGH_LEVEL" else: result = f'Invalid TPL = {self.tpl:d}' return result class EfiBootMode: ''' Class to return human readable string for EFI_BOOT_MODE Methods ----------- to_str(boot_mode, default) return string for boot_mode, and return default if there is not a match. ''' EFI_BOOT_MODE_dict = { 0x00: "BOOT_WITH_FULL_CONFIGURATION", 0x01: "BOOT_WITH_MINIMAL_CONFIGURATION", 0x02: "BOOT_ASSUMING_NO_CONFIGURATION_CHANGES", 0x03: "BOOT_WITH_FULL_CONFIGURATION_PLUS_DIAGNOSTICS", 0x04: "BOOT_WITH_DEFAULT_SETTINGS", 0x05: "BOOT_ON_S4_RESUME", 0x06: "BOOT_ON_S5_RESUME", 0x07: "BOOT_WITH_MFG_MODE_SETTINGS", 0x10: "BOOT_ON_S2_RESUME", 0x11: "BOOT_ON_S3_RESUME", 0x12: "BOOT_ON_FLASH_UPDATE", 0x20: "BOOT_IN_RECOVERY_MODE", } def __init__(self, boot_mode): self._boot_mode = boot_mode def __str__(self): return self.to_str(self._boot_mode) @classmethod def to_str(cls, boot_mode, default=''): return cls.EFI_BOOT_MODE_dict.get(boot_mode, default) class EfiStatusClass: ''' Class to decode EFI_STATUS to a human readable string. You need to pass in pointer_width to get the corret value since the EFI_STATUS code values are different based on the sizeof UINTN. The default is sizeof(UINTN) == 8. Attributes ?????? _dict_ : dictionary dictionary of EFI_STATUS that has beed updated to match pointer_width. Methods ----------- patch_dictionary(pointer_width) to_str(status, default) ''' _dict_ = {} _EFI_STATUS_UINT32_dict = { 0: "Success", 1: "Warning Unknown Glyph", 2: "Warning Delete Failure", 3: "Warning Write Failure", 4: "Warning Buffer Too Small", 5: "Warning Stale Data", 6: "Warngin File System", (0x20000000 | 0): "Warning interrupt source pending", (0x20000000 | 1): "Warning interrupt source quiesced", (0x80000000 | 1): "Load Error", (0x80000000 | 2): "Invalid Parameter", (0x80000000 | 3): "Unsupported", (0x80000000 | 4): "Bad Buffer Size", (0x80000000 | 5): "Buffer Too Small", (0x80000000 | 6): "Not Ready", (0x80000000 | 7): "Device Error", (0x80000000 | 8): "Write Protected", (0x80000000 | 9): "Out of Resources", (0x80000000 | 10): "Volume Corrupt", (0x80000000 | 11): "Volume Full", (0x80000000 | 12): "No Media", (0x80000000 | 13): "Media changed", (0x80000000 | 14): "Not Found", (0x80000000 | 15): "Access Denied", (0x80000000 | 16): "No Response", (0x80000000 | 17): "No mapping", (0x80000000 | 18): "Time out", (0x80000000 | 19): "Not started", (0x80000000 | 20): "Already started", (0x80000000 | 21): "Aborted", (0x80000000 | 22): "ICMP Error", (0x80000000 | 23): "TFTP Error", (0x80000000 | 24): "Protocol Error", (0x80000000 | 25): "Incompatible Version", (0x80000000 | 26): "Security Violation", (0x80000000 | 27): "CRC Error", (0x80000000 | 28): "End of Media", (0x80000000 | 31): "End of File", (0x80000000 | 32): "Invalid Language", (0x80000000 | 33): "Compromised Data", (0x80000000 | 35): "HTTP Error", (0xA0000000 | 0): "Interrupt Pending", } def __init__(self, status=None, pointer_width=8): self.status = status # this will convert to 64-bit version if needed self.patch_dictionary(pointer_width) def __str__(self): return self.to_str(self.status) @classmethod def to_str(cls, status, default=''): return cls._dict_.get(status, default) @classmethod def patch_dictionary(cls, pointer_width): '''Patch UINTN upper bits like values ''' if cls._dict_: # only patch the class variable once return False if pointer_width == 4: cls._dict = cls._EFI_STATUS_UINT32_dict elif pointer_width == 8: for key, value in cls._EFI_STATUS_UINT32_dict.items(): mask = (key & 0xE0000000) << 32 new_key = (key & 0x1FFFFFFF) | mask cls._dict_[new_key] = value return True else: return False class GuidNames: ''' Class to expose the C names of EFI_GUID's. The _dict_ starts with common EFI System Table entry EFI_GUID's. _dict_ can get updated with the build generated Guid.xref file if a path to a module is passed into add_build_guid_file(). If symbols are loaded for any module in the build the path the build product should imply the relative location of that builds Guid.xref file. Attributes ??????---- _dict_ : dictionary dictionary of EFI_GUID (uuid) strings to C global names Methods ------- to_uuid(uuid) convert a hex UUID string or bytearray to a uuid.UUID to_name(uuid) convert a UUID string to a C global constant name. to_guid(guid_name) convert a C global constant EFI_GUID name to uuid hex string. is_guid_str(name) name is a hex UUID string. Example: 49152E77-1ADA-4764-B7A2-7AFEFED95E8B to_c_guid(value) convert a uuid.UUID or UUID string to a c_guid string (see is_c_guid()) from_c_guid(value) covert a C guid string to a hex UUID string. is_c_guid(name) name is the C initialization value for an EFI_GUID. Example: { 0x414e6bdd, 0xe47b, 0x47cc, { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c, 0xf5, 0x16 }} add_build_guid_file(module_path, custom_file): assume module_path is an edk2 build product and load the Guid.xref file from that build to fill in _dict_. If you know the path and file name of a custom Guid.xref you can pass it in as custom_file. ''' _dict_ = { # Common EFI System Table values '05AD34BA-6F02-4214-952E-4DA0398E2BB9': 'gEfiDxeServicesTableGuid', '7739F24C-93D7-11D4-9A3A-0090273FC14D': 'gEfiHobListGuid', '4C19049F-4137-4DD3-9C10-8B97A83FFDFA': 'gEfiMemoryTypeInformationGuid', '49152E77-1ADA-4764-B7A2-7AFEFED95E8B': 'gEfiDebugImageInfoTableGuid', '060CC026-4C0D-4DDA-8F41-595FEF00A502': 'gMemoryStatusCodeRecordGuid', 'EB9D2D31-2D88-11D3-9A16-0090273FC14D': 'gEfiSmbiosTableGuid', 'EB9D2D30-2D88-11D3-9A16-0090273FC14D': 'gEfiAcpi10TableGuid', '8868E871-E4F1-11D3-BC22-0080C73C8881': 'gEfiAcpi20TableGuid', } guid_files = [] def __init__(self, uuid=None, pointer_width=8): self.uuid = None if uuid is None else self.to_uuid(uuid) def __str__(self): if self.uuid is None: result = '' for key, value in GuidNames._dict_.items(): result += f'{key}: {value}\n' else: result = self.to_name(self.uuid) return result @classmethod def to_uuid(cls, obj): try: return uuid.UUID(bytes_le=bytes(obj)) except (ValueError, TypeError): try: return uuid.UUID(bytes_le=obj) except (ValueError, TypeError): return uuid.UUID(obj) @classmethod def to_name(cls, uuid): if not isinstance(uuid, str): uuid = str(uuid) if cls.is_c_guid(uuid): uuid = cls.from_c_guid(uuid) return cls._dict_.get(uuid.upper(), uuid.upper()) @classmethod def to_guid(cls, guid_name): for key, value in cls._dict_.items(): if guid_name == value: return key.upper() else: raise KeyError(key) @classmethod def is_guid_str(cls, name): if not isinstance(name, str): return False return name.count('-') >= 4 @classmethod def to_c_guid(cls, value): if isinstance(value, uuid.UUID): guid = value else: guid = uuid.UUID(value) (data1, data2, data3, data4_0, data4_1, data4_2, data4_3, data4_4, data4_5, data4_6, data4_7) = struct.unpack( '/Build/OvmfX64/DEBUG_XCODE5/X64/../DxeCore.dll # Walk backwards looking for a toolchain like name. # Then look for GUID database: # Build/OvmfX64//DEBUG_XCODE5/FV/Guid.xref for i in reversed(module_path.split(os.sep)): if (i.startswith('DEBUG_') or i.startswith('RELEASE_') or i.startswith('NOOPT_')): build_root = os.path.join( module_path.rsplit(i, 1)[0], i) break xref = os.path.join(build_root, 'FV', 'Guid.xref') if xref in cls.guid_files: # only processes the file one time return True with open(xref) as f: content = f.readlines() cls.guid_files.append(xref) for lines in content: try: if cls.is_guid_str(lines): # a regex would be more pedantic words = lines.split() cls._dict_[words[0].upper()] = words[1].strip('\n') except ValueError: pass return True return False class EFI_HOB_GENERIC_HEADER(LittleEndianStructure): _fields_ = [ ('HobType', c_uint16), ('HobLength', c_uint16), ('Reserved', c_uint32) ] class EFI_HOB_HANDOFF_INFO_TABLE(LittleEndianStructure): _fields_ = [ ('Header', EFI_HOB_GENERIC_HEADER), ('Version', c_uint32), ('BootMode', c_uint32), ('EfiMemoryTop', c_uint64), ('EfiMemoryBottom', c_uint64), ('EfiFreeMemoryTop', c_uint64), ('EfiFreeMemoryBottom', c_uint64), ('EfiEndOfHobList', c_uint64), ] class EFI_HOB_MEMORY_ALLOCATION(LittleEndianStructure): _fields_ = [ ('Header', EFI_HOB_GENERIC_HEADER), ('Name', EFI_GUID), ('MemoryBaseAddress', c_uint64), ('MemoryLength', c_uint64), ('MemoryType', c_uint32), ('Reserved', c_uint32), ] class EFI_HOB_RESOURCE_DESCRIPTOR(LittleEndianStructure): _fields_ = [ ('Header', EFI_HOB_GENERIC_HEADER), ('Owner', EFI_GUID), ('ResourceType', c_uint32), ('ResourceAttribute', c_uint32), ('PhysicalStart', c_uint64), ('ResourceLength', c_uint64), ] class EFI_HOB_GUID_TYPE(LittleEndianStructure): _fields_ = [ ('Header', EFI_HOB_GENERIC_HEADER), ('Name', EFI_GUID), ] class EFI_HOB_FIRMWARE_VOLUME(LittleEndianStructure): _fields_ = [ ('Header', EFI_HOB_GENERIC_HEADER), ('BaseAddress', c_uint64), ('Length', c_uint64), ] class EFI_HOB_CPU(LittleEndianStructure): _fields_ = [ ('Header', EFI_HOB_GENERIC_HEADER), ('SizeOfMemorySpace', c_uint8), ('SizeOfIoSpace', c_uint8), ('Reserved', ARRAY(c_uint8, 6)), ] class EFI_HOB_MEMORY_POOL(LittleEndianStructure): _fields_ = [ ('Header', EFI_HOB_GENERIC_HEADER), ] class EFI_HOB_FIRMWARE_VOLUME2(LittleEndianStructure): _fields_ = [ ('Header', EFI_HOB_GENERIC_HEADER), ('BaseAddress', c_uint64), ('Length', c_uint64), ('FvName', EFI_GUID), ('FileName', EFI_GUID) ] class EFI_HOB_FIRMWARE_VOLUME3(LittleEndianStructure): _fields_ = [ ('HobType', c_uint16), ('HobLength', c_uint16), ('Reserved', c_uint32), ('BaseAddress', c_uint64), ('Length', c_uint64), ('AuthenticationStatus', c_uint32), ('ExtractedFv', c_uint8), ('FvName', EFI_GUID), ('FileName', EFI_GUID), ] class EFI_HOB_UEFI_CAPSULE(LittleEndianStructure): _fields_ = [ ('HobType', c_uint16), ('HobLength', c_uint16), ('Reserved', c_uint32), ('BaseAddress', c_uint64), ('Length', c_uint64), ] class EfiHob: ''' Parse EFI Device Paths based on the edk2 C Structures defined above. In the context of this class verbose means hexdump extra data. Attributes ?????? Hob : list List of HOBs. Each entry contains the name, HOB type, HOB length, the ctype struct for the HOB, and any extra data. Methods ----------- get_hob_by_type(hob_type) return string that decodes the HOBs of hob_type. If hob_type is None then return all HOBs. ''' Hob = [] verbose = False hob_dict = { 1: EFI_HOB_HANDOFF_INFO_TABLE, 2: EFI_HOB_MEMORY_ALLOCATION, 3: EFI_HOB_RESOURCE_DESCRIPTOR, 4: EFI_HOB_GUID_TYPE, 5: EFI_HOB_FIRMWARE_VOLUME, 6: EFI_HOB_CPU, 7: EFI_HOB_MEMORY_POOL, 9: EFI_HOB_FIRMWARE_VOLUME2, 0xb: EFI_HOB_UEFI_CAPSULE, 0xc: EFI_HOB_FIRMWARE_VOLUME3, 0xffff: EFI_HOB_GENERIC_HEADER, } def __init__(self, file, address=None, verbose=False, count=1000): self._file = file EfiHob.verbose = verbose if len(EfiHob.Hob) != 0 and address is None: return if address is not None: hob_ptr = address else: hob_ptr = EfiConfigurationTable(file).GetConfigTable( '7739F24C-93D7-11D4-9A3A-0090273FC14D') self.read_hobs(hob_ptr) @ classmethod def __str__(cls): return cls.get_hob_by_type(None) @ classmethod def get_hob_by_type(cls, hob_type): result = "" for (Name, HobType, HobLen, chob, extra) in cls.Hob: if hob_type is not None: if hob_type != HobType: continue result += f'Type: {Name:s} (0x{HobType:01x}) Len: 0x{HobLen:03x}\n' result += ctype_to_str(chob, ' ', ['Reserved']) if cls.verbose: if extra is not None: result += hexdump(extra, ' ') return result def read_hobs(self, hob_ptr, count=1000): if hob_ptr is None: return try: for _ in range(count): # while True hdr, _ = self._ctype_read_ex(EFI_HOB_GENERIC_HEADER, hob_ptr) if hdr.HobType == 0xffff: break type_str = self.hob_dict.get( hdr.HobType, EFI_HOB_GENERIC_HEADER) hob, extra = self._ctype_read_ex( type_str, hob_ptr, hdr.HobLength) EfiHob.Hob.append( (type(hob).__name__, hdr.HobType, hdr.HobLength, hob, extra)) hob_ptr += hdr.HobLength except ValueError: pass def _ctype_read_ex(self, ctype_struct, offset=0, rsize=None): if offset != 0: self._file.seek(offset) type_size = sizeof(ctype_struct) size = rsize if rsize else type_size data = self._file.read(size) cdata = ctype_struct.from_buffer(bytearray(data)) if size > type_size: return cdata, data[type_size:] else: return cdata, None class EFI_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Type', c_uint8), ('SubType', c_uint8), # UINT8 Length[2] # Cheat and use c_uint16 since we don't care about alignment ('Length', c_uint16) ] class PCI_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('Function', c_uint8), ('Device', c_uint8) ] class PCCARD_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('FunctionNumber', c_uint8), ] class MEMMAP_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('StartingAddress', c_uint64), ('EndingAddress', c_uint64), ] class VENDOR_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('Guid', EFI_GUID), ] class CONTROLLER_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('ControllerNumber', c_uint32), ] class BMC_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('InterfaceType', c_uint8), ('BaseAddress', ARRAY(c_uint8, 8)), ] class BBS_BBS_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('DeviceType', c_uint16), ('StatusFlag', c_uint16) ] class ACPI_HID_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('HID', c_uint32), ('UID', c_uint32) ] class ACPI_EXTENDED_HID_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('HID', c_uint32), ('UID', c_uint32), ('CID', c_uint32) ] class ACPI_ADR_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('ARD', c_uint32) ] class ACPI_NVDIMM_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('NFITDeviceHandle', c_uint32) ] class ATAPI_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("PrimarySecondary", c_uint8), ("SlaveMaster", c_uint8), ("Lun", c_uint16) ] class SCSI_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("Pun", c_uint16), ("Lun", c_uint16) ] class FIBRECHANNEL_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("Reserved", c_uint32), ("WWN", c_uint64), ("Lun", c_uint64) ] class F1394_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("Reserved", c_uint32), ("Guid", c_uint64) ] class USB_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("ParentPortNumber", c_uint8), ("InterfaceNumber", c_uint8), ] class I2O_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("Tid", c_uint32) ] class INFINIBAND_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("ResourceFlags", c_uint32), ("PortGid", ARRAY(c_uint8, 16)), ("ServiceId", c_uint64), ("TargetPortId", c_uint64), ("DeviceId", c_uint64) ] class UART_FLOW_CONTROL_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("Guid", EFI_GUID), ("FlowControlMap", c_uint32) ] class SAS_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("Guid", EFI_GUID), ("Reserved", c_uint32), ("SasAddress", c_uint64), ("Lun", c_uint64), ("DeviceTopology", c_uint16), ("RelativeTargetPort", c_uint16) ] class EFI_MAC_ADDRESS(LittleEndianStructure): _pack_ = 1 _fields_ = [ ("Addr", ARRAY(c_uint8, 32)), ] class MAC_ADDR_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('MacAddress', EFI_MAC_ADDRESS), ('IfType', c_uint8) ] class IPv4_ADDRESS(LittleEndianStructure): _fields_ = [ ("Addr", ARRAY(c_uint8, 4)), ] class IPv4_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('LocalIpAddress', IPv4_ADDRESS), ('RemoteIpAddress', IPv4_ADDRESS), ('LocalPort', c_uint16), ('RemotePort', c_uint16), ('Protocol', c_uint16), ('StaticIpAddress', c_uint8), ('GatewayIpAddress', IPv4_ADDRESS), ('SubnetMask', IPv4_ADDRESS) ] class IPv6_ADDRESS(LittleEndianStructure): _fields_ = [ ("Addr", ARRAY(c_uint8, 16)), ] class IPv6_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('LocalIpAddress', IPv6_ADDRESS), ('RemoteIpAddress', IPv6_ADDRESS), ('LocalPort', c_uint16), ('RemotePort', c_uint16), ('Protocol', c_uint16), ('IpAddressOrigin', c_uint8), ('PrefixLength', c_uint8), ('GatewayIpAddress', IPv6_ADDRESS) ] class UART_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('Reserved', c_uint32), ('BaudRate', c_uint64), ('DataBits', c_uint8), ('Parity', c_uint8), ('StopBits', c_uint8) ] class USB_CLASS_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('VendorId', c_uint16), ('ProductId', c_uint16), ('DeviceClass', c_uint8), ('DeviceCSjblass', c_uint8), ('DeviceProtocol', c_uint8), ] class USB_WWID_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('InterfaceNumber', c_uint16), ('VendorId', c_uint16), ('ProductId', c_uint16), ] class DEVICE_LOGICAL_UNIT_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('Lun', c_uint8) ] class SATA_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('HBAPortNumber', c_uint16), ('PortMultiplierPortNumber', c_uint16), ('Lun', c_uint16), ] class ISCSI_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('NetworkProtocol', c_uint16), ('LoginOption', c_uint16), ('Lun', c_uint64), ('TargetPortalGroupTag', c_uint16), ] class VLAN_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("VlandId", c_uint16) ] class FIBRECHANNELEX_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("Reserved", c_uint16), ("WWN", ARRAY(c_uint8, 8)), ("Lun", ARRAY(c_uint8, 8)), ] class SASEX_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("SasAddress", ARRAY(c_uint8, 8)), ("Lun", ARRAY(c_uint8, 8)), ("DeviceTopology", c_uint16), ("RelativeTargetPort", c_uint16) ] class NVME_NAMESPACE_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("NamespaceId", c_uint32), ("NamespaceUuid", c_uint64) ] class DNS_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("IsIPv6", c_uint8), ("DnsServerIp", IPv6_ADDRESS) ] class UFS_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("Pun", c_uint8), ("Lun", c_uint8), ] class SD_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("SlotNumber", c_uint8) ] class BLUETOOTH_ADDRESS(LittleEndianStructure): _pack_ = 1 _fields_ = [ ("Address", ARRAY(c_uint8, 6)) ] class BLUETOOTH_LE_ADDRESS(LittleEndianStructure): _pack_ = 1 _fields_ = [ ("Format", c_uint8), ("Class", c_uint16) ] class BLUETOOTH_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("BD_ADDR", BLUETOOTH_ADDRESS) ] class WIFI_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("SSId", ARRAY(c_uint8, 32)) ] class EMMC_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("SlotNumber", c_uint8) ] class BLUETOOTH_LE_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("BD_ADDR", BLUETOOTH_LE_ADDRESS) ] class NVDIMM_NAMESPACE_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("Uuid", EFI_GUID) ] class REST_SERVICE_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("RESTService", c_uint8), ("AccessMode", c_uint8) ] class REST_VENDOR_SERVICE_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ("RESTService", c_uint8), ("AccessMode", c_uint8), ("Guid", EFI_GUID), ] class HARDDRIVE_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('PartitionNumber', c_uint32), ('PartitionStart', c_uint64), ('PartitionSize', c_uint64), ('Signature', ARRAY(c_uint8, 16)), ('MBRType', c_uint8), ('SignatureType', c_uint8) ] class CDROM_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('BootEntry', c_uint32), ('PartitionStart', c_uint64), ('PartitionSize', c_uint64) ] class MEDIA_PROTOCOL_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('Protocol', EFI_GUID) ] class MEDIA_FW_VOL_FILEPATH_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('FvFileName', EFI_GUID) ] class MEDIA_FW_VOL_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('FvName', EFI_GUID) ] class MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('Reserved', c_uint32), ('StartingOffset', c_uint64), ('EndingOffset', c_uint64) ] class MEDIA_RAM_DISK_DEVICE_PATH(LittleEndianStructure): _pack_ = 1 _fields_ = [ ('Header', EFI_DEVICE_PATH), ('StartingAddr', c_uint64), ('EndingAddr', c_uint64), ('TypeGuid', EFI_GUID), ('Instance', c_uint16) ] class EfiDevicePath: ''' Parse EFI Device Paths based on the edk2 C Structures defined above. In the context of this class verbose means hexdump extra data. Attributes ?????? DevicePath : list List of devixe path instances. Each instance is a list of nodes for the given Device Path instance. Methods ----------- device_path_node(address) return the Device Path ctype hdr, ctype, and any extra data in the Device Path node. This is just a single Device Path node, not the entire Device Path. device_path_node_str(address) return the device path node (not the entire Device Path) as a string ''' DevicePath = [] device_path_dict = { # ( Type, SubType ) : Device Path C typedef # HARDWARE_DEVICE_PATH (1, 1): PCI_DEVICE_PATH, (1, 2): PCCARD_DEVICE_PATH, (1, 3): MEMMAP_DEVICE_PATH, (1, 4): VENDOR_DEVICE_PATH, (1, 5): CONTROLLER_DEVICE_PATH, (1, 6): BMC_DEVICE_PATH, # ACPI_DEVICE_PATH (2, 1): ACPI_HID_DEVICE_PATH, (2, 2): ACPI_EXTENDED_HID_DEVICE_PATH, (2, 3): ACPI_ADR_DEVICE_PATH, (2, 4): ACPI_NVDIMM_DEVICE_PATH, # MESSAGING_DEVICE_PATH (3, 1): ATAPI_DEVICE_PATH, (3, 2): SCSI_DEVICE_PATH, (3, 3): FIBRECHANNEL_DEVICE_PATH, (3, 4): F1394_DEVICE_PATH, (3, 5): USB_DEVICE_PATH, (3, 6): I2O_DEVICE_PATH, (3, 9): INFINIBAND_DEVICE_PATH, (3, 10): VENDOR_DEVICE_PATH, (3, 11): MAC_ADDR_DEVICE_PATH, (3, 12): IPv4_DEVICE_PATH, (3, 13): IPv6_DEVICE_PATH, (3, 14): UART_DEVICE_PATH, (3, 15): USB_CLASS_DEVICE_PATH, (3, 16): USB_WWID_DEVICE_PATH, (3, 17): DEVICE_LOGICAL_UNIT_DEVICE_PATH, (3, 18): SATA_DEVICE_PATH, (3, 19): ISCSI_DEVICE_PATH, (3, 20): VLAN_DEVICE_PATH, (3, 21): FIBRECHANNELEX_DEVICE_PATH, (3, 22): SASEX_DEVICE_PATH, (3, 23): NVME_NAMESPACE_DEVICE_PATH, (3, 24): DNS_DEVICE_PATH, (3, 25): UFS_DEVICE_PATH, (3, 26): SD_DEVICE_PATH, (3, 27): BLUETOOTH_DEVICE_PATH, (3, 28): WIFI_DEVICE_PATH, (3, 29): EMMC_DEVICE_PATH, (3, 30): BLUETOOTH_LE_DEVICE_PATH, (3, 31): DNS_DEVICE_PATH, (3, 32): NVDIMM_NAMESPACE_DEVICE_PATH, (3, 33): REST_SERVICE_DEVICE_PATH, (3, 34): REST_VENDOR_SERVICE_DEVICE_PATH, # MEDIA_DEVICE_PATH (4, 1): HARDDRIVE_DEVICE_PATH, (4, 2): CDROM_DEVICE_PATH, (4, 3): VENDOR_DEVICE_PATH, (4, 4): EFI_DEVICE_PATH, (4, 5): MEDIA_PROTOCOL_DEVICE_PATH, (4, 6): MEDIA_FW_VOL_FILEPATH_DEVICE_PATH, (4, 7): MEDIA_FW_VOL_DEVICE_PATH, (4, 8): MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH, (4, 9): MEDIA_RAM_DISK_DEVICE_PATH, # BBS_DEVICE_PATH (5, 1): BBS_BBS_DEVICE_PATH, } guid_override_dict = { uuid.UUID('37499A9D-542F-4C89-A026-35DA142094E4'): UART_FLOW_CONTROL_DEVICE_PATH, uuid.UUID('D487DDB4-008B-11D9-AFDC-001083FFCA4D'): SAS_DEVICE_PATH, } def __init__(self, file, ptr=None, verbose=False, count=64): ''' Convert ptr into a list of Device Path nodes. If verbose also hexdump extra data. ''' self._file = file self._verbose = verbose if ptr is None: return try: instance = [] for _ in range(count): # while True hdr, _ = self._ctype_read_ex(EFI_DEVICE_PATH, ptr) if hdr.Length < sizeof(EFI_DEVICE_PATH): # Not a valid device path break if hdr.Type == 0x7F: # END_DEVICE_PATH_TYPE self.DevicePath.append(instance) if hdr.SubType == 0xFF: # END_ENTIRE_DEVICE_PATH_SUBTYPE break if hdr.SubType == 0x01: # END_INSTANCE_DEVICE_PATH_SUBTYPE # start new device path instance instance = [] type_str = self.device_path_dict.get( (hdr.Type, hdr.SubType), EFI_DEVICE_PATH) node, extra = self._ctype_read_ex(type_str, ptr, hdr.Length) if 'VENDOR_DEVICE_PATH' in type(node).__name__: guid_type = self.guid_override_dict.get( GuidNames.to_uuid(node.Guid), None) if guid_type: # use the ctype associated with the GUID node, extra = self._ctype_read_ex( guid_type, ptr, hdr.Length) instance.append((type(node).__name__, hdr.Type, hdr.SubType, hdr.Length, node, extra)) ptr += hdr.Length except ValueError: pass def __str__(self): ''' ''' if not self.valid(): return '' result = "" for instance in self.DevicePath: for (Name, Type, SubType, Length, cnode, extra) in instance: result += f'{Name:s} {Type:2d}:{SubType:2d} Len: {Length:3d}\n' result += ctype_to_str(cnode, ' ', ['Reserved']) if self._verbose: if extra is not None: result += hexdump(extra, ' ') result += '\n' return result def valid(self): return True if self.DevicePath else False def device_path_node(self, address): try: hdr, _ = self._ctype_read_ex(EFI_DEVICE_PATH, address) if hdr.Length < sizeof(EFI_DEVICE_PATH): return None, None, None type_str = self.device_path_dict.get( (hdr.Type, hdr.SubType), EFI_DEVICE_PATH) cnode, extra = self._ctype_read_ex(type_str, address, hdr.Length) return hdr, cnode, extra except ValueError: return None, None, None def device_path_node_str(self, address, verbose=False): hdr, cnode, extra = self.device_path_node(address) if hdr is None: return '' cname = type(cnode).__name__ result = f'{cname:s} {hdr.Type:2d}:{hdr.SubType:2d} ' result += f'Len: 0x{hdr.Length:03x}\n' result += ctype_to_str(cnode, ' ', ['Reserved']) if verbose: if extra is not None: result += hexdump(extra, ' ') return result def _ctype_read_ex(self, ctype_struct, offset=0, rsize=None): if offset != 0: self._file.seek(offset) type_size = sizeof(ctype_struct) size = rsize if rsize else type_size data = self._file.read(size) if data is None: return None, None cdata = ctype_struct.from_buffer(bytearray(data)) if size > type_size: return cdata, data[type_size:] else: return cdata, None class EfiConfigurationTable: ''' A class to abstract EFI Configuration Tables from gST->ConfigurationTable and gST->NumberOfTableEntries. Pass in the gST pointer from EFI, likely you need to look up this address after you have loaded symbols Attributes ?????? ConfigurationTableDict : dictionary dictionary of EFI Configuration Table entries Methods ----------- GetConfigTable(uuid) pass in VendorGuid and return VendorTable from EFI System Table DebugImageInfo(table) return tuple of load address and size of PE/COFF images ''' ConfigurationTableDict = {} def __init__(self, file, gST_addr=None): self._file = file if gST_addr is None: # ToDo add code to search for gST via EFI_SYSTEM_TABLE_POINTER return gST = self._ctype_read(EFI_SYSTEM_TABLE, gST_addr) self.read_efi_config_table(gST.NumberOfTableEntries, gST.ConfigurationTable, self._ctype_read) @ classmethod def __str__(cls): '''return EFI_CONFIGURATION_TABLE entries as a string''' result = "" for key, value in cls.ConfigurationTableDict.items(): result += f'{GuidNames().to_name(key):>37s}: ' result += f'VendorTable = 0x{value:08x}\n' return result def _ctype_read(self, ctype_struct, offset=0): '''ctype worker function to read data''' if offset != 0: self._file.seek(offset) data = self._file.read(sizeof(ctype_struct)) return ctype_struct.from_buffer(bytearray(data)) @ classmethod def read_efi_config_table(cls, table_cnt, table_ptr, ctype_read): '''Create a dictionary of EFI Configuration table entries''' EmptryTables = EFI_CONFIGURATION_TABLE * table_cnt Tables = ctype_read(EmptryTables, table_ptr) for i in range(table_cnt): cls.ConfigurationTableDict[str(GuidNames.to_uuid( Tables[i].VendorGuid)).upper()] = Tables[i].VendorTable return cls.ConfigurationTableDict def GetConfigTable(self, uuid): ''' Return VendorTable for VendorGuid (uuid.UUID) or None''' return self.ConfigurationTableDict.get(uuid.upper()) def DebugImageInfo(self, table=None): ''' Walk the debug image info table to find the LoadedImage protocols for all the loaded PE/COFF images and return a list of load address and image size. ''' ImageLoad = [] if table is None: table = self.GetConfigTable('49152e77-1ada-4764-b7a2-7afefed95e8b') DbgInfoHdr = self._ctype_read(EFI_DEBUG_IMAGE_INFO_TABLE_HEADER, table) NormalImageArray = EFI_DEBUG_IMAGE_INFO * DbgInfoHdr.TableSize NormalImageArray = self._ctype_read( NormalImageArray, DbgInfoHdr.EfiDebugImageInfoTable) for i in range(DbgInfoHdr.TableSize): ImageInfo = self._ctype_read( EFI_DEBUG_IMAGE_INFO_NORMAL, NormalImageArray[i].NormalImage) LoadedImage = self._ctype_read( EFI_LOADED_IMAGE_PROTOCOL, ImageInfo.LoadedImageProtocolInstance) ImageLoad.append((LoadedImage.ImageBase, LoadedImage.ImageSize)) return ImageLoad class PeTeImage: ''' A class to abstract PE/COFF or TE image processing via passing in a Python file like object. If you pass in an address the PE/COFF is parsed, if you pass in NULL for an address then you get a class instance you can use to search memory for a PE/COFF hader given a pc value. Attributes ?????? LoadAddress : int Load address of the PE/COFF image AddressOfEntryPoint : int Address of the Entry point of the PE/COFF image TextAddress : int Start of the PE/COFF text section DataAddress : int Start of the PE/COFF data section CodeViewPdb : str File name of the symbols file CodeViewUuid : uuid:UUID GUID for "RSDS" Debug Directory entry, or Mach-O UUID for "MTOC" Methods ----------- pcToPeCoff(address, step, max_range, rom_range) Given an address(pc) find the PE/COFF image it is in sections_to_str() return a string giving info for all the PE/COFF sections ''' def __init__(self, file, address=0): self._file = file # book keeping, but public self.PeHdr = None self.TeHdr = None self.Machine = None self.Subsystem = None self.CodeViewSig = None self.e_lfanew = 0 self.NumberOfSections = 0 self.Sections = None # Things debuggers may want to know self.LoadAddress = 0 if address is None else address self.EndLoadAddress = 0 self.AddressOfEntryPoint = 0 self.TextAddress = 0 self.DataAddress = 0 self.CodeViewPdb = None self.CodeViewUuid = None self.TeAdjust = 0 self.dir_name = { 0: 'Export Table', 1: 'Import Table', 2: 'Resource Table', 3: 'Exception Table', 4: 'Certificate Table', 5: 'Relocation Table', 6: 'Debug', 7: 'Architecture', 8: 'Global Ptr', 9: 'TLS Table', 10: 'Load Config Table', 11: 'Bound Import', 12: 'IAT', 13: 'Delay Import Descriptor', 14: 'CLR Runtime Header', 15: 'Reserved', } if address is not None: if self.maybe(): self.parse() def __str__(self): if self.PeHdr is None and self.TeHdr is None: # no PE/COFF header found return "" if self.CodeViewPdb: pdb = f'{self.Machine}`{self.CodeViewPdb}' else: pdb = 'No Debug Info:' if self.CodeViewUuid: guid = f'{self.CodeViewUuid}:' else: guid = '' slide = f'slide = {self.TeAdjust:d} ' if self.TeAdjust != 0 else ' ' res = guid + f'{pdb} load = 0x{self.LoadAddress:08x} ' + slide return res def _seek(self, offset): """ seek() relative to start of PE/COFF (TE) image """ self._file.seek(self.LoadAddress + offset) def _read_offset(self, size, offset=None): """ read() relative to start of PE/COFF (TE) image if offset is not None then seek() before the read """ if offset is not None: self._seek(offset) return self._file.read(size) def _read_ctype(self, ctype_struct, offset=None): data = self._read_offset(sizeof(ctype_struct), offset) return ctype_struct.from_buffer(bytearray(data), 0) def _unsigned(self, i): """return a 32-bit unsigned int (UINT32) """ return int.from_bytes(i, byteorder='little', signed=False) def pcToPeCoff(self, address, step=None, max_range=None, rom_range=[0xFE800000, 0xFFFFFFFF]): """ Given an address search backwards for PE/COFF (TE) header For DXE 4K is probably OK For PEI you might have to search every 4 bytes. """ if step is None: step = 0x1000 if max_range is None: max_range = 0x200000 if address in range(*rom_range): # The XIP code in the ROM ends up 4 byte aligned. step = 4 max_range = min(max_range, 0x100000) # Align address to page boundary for memory image search. address = address & ~(step-1) # Search every step backward offset_range = list(range(0, min(max_range, address), step)) for offset in offset_range: if self.maybe(address - offset): if self.parse(): return True return False def maybe(self, offset=None): """Probe to see if this offset is likely a PE/COFF or TE file """ self.LoadAddress = 0 e_magic = self._read_offset(2, offset) header_ok = e_magic == b'MZ' or e_magic == b'VZ' if offset is not None and header_ok: self.LoadAddress = offset return header_ok def parse(self): """Parse PE/COFF (TE) debug directory entry """ DosHdr = self._read_ctype(EFI_IMAGE_DOS_HEADER, 0) if DosHdr.e_magic == self._unsigned(b'VZ'): # TE image self.TeHdr = self._read_ctype(EFI_TE_IMAGE_HEADER, 0) self.TeAdjust = sizeof(self.TeHdr) - self.TeHdr.StrippedSize self.Machine = image_machine_dict.get(self.TeHdr.Machine, None) self.Subsystem = self.TeHdr.Subsystem self.AddressOfEntryPoint = self.TeHdr.AddressOfEntryPoint debug_dir_size = self.TeHdr.DataDirectoryDebug.Size debug_dir_offset = (self.TeAdjust + self.TeHdr.DataDirectoryDebug.VirtualAddress) else: if DosHdr.e_magic == self._unsigned(b'MZ'): self.e_lfanew = DosHdr.e_lfanew else: self.e_lfanew = 0 self.PeHdr = self._read_ctype( EFI_IMAGE_NT_HEADERS64, self.e_lfanew) if self.PeHdr.Signature != self._unsigned(b'PE\0\0'): return False if self.PeHdr.OptionalHeader.Magic == \ EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC: self.PeHdr = self._read_ctype( EFI_IMAGE_NT_HEADERS32, self.e_lfanew) if self.PeHdr.OptionalHeader.NumberOfRvaAndSizes <= \ DIRECTORY_DEBUG: return False self.Machine = image_machine_dict.get( self.PeHdr.FileHeader.Machine, None) self.Subsystem = self.PeHdr.OptionalHeader.Subsystem self.AddressOfEntryPoint = \ self.PeHdr.OptionalHeader.AddressOfEntryPoint self.TeAdjust = 0 debug_dir_size = self.PeHdr.OptionalHeader.DataDirectory[ DIRECTORY_DEBUG].Size debug_dir_offset = self.PeHdr.OptionalHeader.DataDirectory[ DIRECTORY_DEBUG].VirtualAddress if self.Machine is None or self.Subsystem not in [0, 10, 11, 12]: return False self.AddressOfEntryPoint += self.LoadAddress self.sections() return self.processDebugDirEntry(debug_dir_offset, debug_dir_size) def sections(self): '''Parse the PE/COFF (TE) section table''' if self.Sections is not None: return elif self.TeHdr is not None: self.NumberOfSections = self.TeHdr.NumberOfSections offset = sizeof(EFI_TE_IMAGE_HEADER) elif self.PeHdr is not None: self.NumberOfSections = self.PeHdr.FileHeader.NumberOfSections offset = sizeof(c_uint32) + \ sizeof(EFI_IMAGE_FILE_HEADER) offset += self.PeHdr.FileHeader.SizeOfOptionalHeader offset += self.e_lfanew else: return self.Sections = EFI_IMAGE_SECTION_HEADER * self.NumberOfSections self.Sections = self._read_ctype(self.Sections, offset) for i in range(self.NumberOfSections): name = str(self.Sections[i].Name, 'ascii', 'ignore') addr = self.Sections[i].VirtualAddress addr += self.LoadAddress + self.TeAdjust if name == '.text': self.TextAddress = addr elif name == '.data': self.DataAddress = addr end_addr = addr + self.Sections[i].VirtualSize - 1 if end_addr > self.EndLoadAddress: self.EndLoadAddress = end_addr def sections_to_str(self): # return text summary of sections # name virt addr (virt size) flags:Characteristics result = '' for i in range(self.NumberOfSections): name = str(self.Sections[i].Name, 'ascii', 'ignore') result += f'{name:8s} ' result += f'0x{self.Sections[i].VirtualAddress:08X} ' result += f'(0x{self.Sections[i].VirtualSize:05X}) ' result += f'flags:0x{self.Sections[i].Characteristics:08X}\n' return result def directory_to_str(self): result = '' if self.TeHdr: debug_size = self.TeHdr.DataDirectoryDebug.Size if debug_size > 0: debug_offset = (self.TeAdjust + self.TeHdr.DataDirectoryDebug.VirtualAddress) result += f"Debug 0x{debug_offset:08X} 0x{debug_size}\n" relocation_size = self.TeHdr.DataDirectoryBaseReloc.Size if relocation_size > 0: relocation_offset = ( self.TeAdjust + self.TeHdr.DataDirectoryBaseReloc.VirtualAddress) result += f'Relocation 0x{relocation_offset:08X} ' result += f' 0x{relocation_size}\n' elif self.PeHdr: for i in range(self.PeHdr.OptionalHeader.NumberOfRvaAndSizes): size = self.PeHdr.OptionalHeader.DataDirectory[i].Size if size == 0: continue virt_addr = self.PeHdr.OptionalHeader.DataDirectory[ i].VirtualAddress name = self.dir_name.get(i, '?') result += f'{name:s} 0x{virt_addr:08X} 0x{size:X}\n' return result def processDebugDirEntry(self, virt_address, virt_size): """Process PE/COFF Debug Directory Entry""" if (virt_address == 0 or virt_size < sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)): return False data = bytearray(self._read_offset(virt_size, virt_address)) for offset in range(0, virt_size, sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)): DirectoryEntry = EFI_IMAGE_DEBUG_DIRECTORY_ENTRY.from_buffer( data[offset:]) if DirectoryEntry.Type != 2: continue entry = self._read_offset( DirectoryEntry.SizeOfData, DirectoryEntry.RVA + self.TeAdjust) self.CodeViewSig = entry[:4] if self.CodeViewSig == b'MTOC': self.CodeViewUuid = uuid.UUID(bytes_le=entry[4:4+16]) PdbOffset = 20 elif self.CodeViewSig == b'RSDS': self.CodeViewUuid = uuid.UUID(bytes_le=entry[4:4+16]) PdbOffset = 24 elif self.CodeViewSig == b'NB10': PdbOffset = 16 else: continue # can't find documentation about Pdb string encoding? # guessing utf-8 since that will match file systems in macOS # and Linux Windows is UTF-16, or ANSI adjusted for local. # We might need a different value for Windows here? self.CodeViewPdb = entry[PdbOffset:].split(b'\x00')[ 0].decode('utf-8') return True return False def main(): '''Process arguments as PE/COFF files''' for fname in sys.argv[1:]: with open(fname, 'rb') as f: image = PeTeImage(f) print(image) res = f'EntryPoint = 0x{image.AddressOfEntryPoint:08x} ' res += f'TextAddress = 0x{image.TextAddress:08x} ' res += f'DataAddress = 0x{image.DataAddress:08x}' print(res) print(image.sections_to_str()) print('Data Directories:') print(image.directory_to_str()) if __name__ == "__main__": main()