| #!/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`<path...>/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( | |
| '<IHH8B', guid.bytes_le) | |
| return (f'{{ 0x{data1:08X}, 0x{data2:04X}, 0x{data3:04X}, ' | |
| f'{{ 0x{data4_0:02X}, 0x{data4_1:02X}, 0x{data4_2:02X}, ' | |
| f'0x{data4_3:02X}, 0x{data4_4:02X}, 0x{data4_5:02X}, ' | |
| f'0x{data4_6:02X}, 0x{data4_7:02X} }} }}') | |
| @ classmethod | |
| def from_c_guid(cls, value): | |
| try: | |
| hex = [int(x, 16) for x in re.findall(r"[\w']+", value)] | |
| return (f'{hex[0]:08X}-{hex[1]:04X}-{hex[2]:04X}' | |
| + f'-{hex[3]:02X}{hex[4]:02X}-{hex[5]:02X}{hex[6]:02X}' | |
| + f'{hex[7]:02X}{hex[8]:02X}{hex[9]:02X}{hex[10]:02X}') | |
| except ValueError: | |
| return value | |
| @ classmethod | |
| def is_c_guid(cls, name): | |
| if not isinstance(name, str): | |
| return False | |
| return name.count('{') == 2 and name.count('}') == 2 | |
| @ classmethod | |
| def add_build_guid_file(cls, module_path, custom_file=None): | |
| if custom_file is not None: | |
| xref = custom_file | |
| else: | |
| # module_path will look like: | |
| # <repo>/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 '<class: EfiDevicePath>' | |
| 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 "<class: PeTeImage>" | |
| 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() |