mirror of https://github.com/acidanthera/audk.git
2186 lines
71 KiB
Python
Executable File
2186 lines
71 KiB
Python
Executable File
#!/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()
|