| ## @file | |
| # | |
| # Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR> | |
| # | |
| # SPDX-License-Identifier: BSD-2-Clause-Patent | |
| # | |
| from __future__ import print_function | |
| import array | |
| import uuid | |
| import re | |
| import os | |
| import logging | |
| import core.pe as pe | |
| def GetLogger(): | |
| return logging.getLogger('EFI Binary File') | |
| class EFIBinaryError(Exception): | |
| def __init__(self, message): | |
| Exception.__init__(self) | |
| self._message = message | |
| def GetMessage(self): | |
| return self._message | |
| class EfiFd(object): | |
| EFI_FV_HEADER_SIZE = 0x48 | |
| def __init__(self): | |
| self._fvs = [] | |
| def Load(self, fd, size): | |
| index = fd.tell() | |
| while (index + self.EFI_FV_HEADER_SIZE < size): | |
| fv = EfiFv(self) | |
| fv.Load(fd) | |
| self._fvs.append(fv) | |
| index += fv.GetHeader().GetFvLength() | |
| index = align(index, 8) | |
| fd.seek(index) | |
| def GetFvs(self): | |
| return self._fvs | |
| class EfiFv(object): | |
| FILE_SYSTEM_GUID = uuid.UUID('{8c8ce578-8a3d-4f1c-9935-896185c32dd3}') | |
| def __init__(self, parent=None): | |
| self._size = 0 | |
| self._filename = None | |
| self._fvheader = None | |
| self._blockentries = [] | |
| self._ffs = [] | |
| # following field is for FV in FD | |
| self._parent = parent | |
| self._offset = 0 | |
| self._raw = array.array('B') | |
| def Load(self, fd): | |
| self._offset = fd.tell() | |
| self._filename = fd.name | |
| # get file header | |
| self._fvheader = EfiFirmwareVolumeHeader.Read(fd) | |
| #self._fvheader.Dump() | |
| self._size = self._fvheader.GetFvLength() | |
| if self._fvheader.GetFileSystemGuid() != self.FILE_SYSTEM_GUID: | |
| fd.seek(self._offset) | |
| self._raw.fromfile(fd, self.GetHeader().GetFvLength()) | |
| return | |
| # read block map | |
| blockentry = BlockMapEntry.Read(fd) | |
| self._blockentries.append(blockentry) | |
| while (blockentry.GetNumberBlocks() != 0 and blockentry.GetLength() != 0): | |
| self._blockentries.append(blockentry) | |
| blockentry = BlockMapEntry.Read(fd) | |
| if self._fvheader.GetSize() + (len(self._blockentries)) * 8 != \ | |
| self._fvheader.GetHeaderLength(): | |
| raise EFIBinaryError("Volume Header length not consistent with block map!") | |
| index = align(fd.tell(), 8) | |
| count = 0 | |
| while ((index + EfiFfs.FFS_HEADER_SIZE) < self._size): | |
| ffs = EfiFfs.Read(fd, self) | |
| if not isValidGuid(ffs.GetNameGuid()): | |
| break | |
| self._ffs.append(ffs) | |
| count += 1 | |
| index = align(fd.tell(), 8) | |
| fd.seek(self._offset) | |
| self._raw.fromfile(fd, self.GetHeader().GetFvLength()) | |
| def GetFfs(self): | |
| return self._ffs | |
| def GetHeader(self): | |
| return self._fvheader | |
| def GetBlockEntries(self): | |
| return self._blockentries | |
| def GetHeaderRawData(self): | |
| ret = [] | |
| ret += self._fvheader.GetRawData() | |
| for block in self._blockentries: | |
| ret += block.GetRawData() | |
| return ret | |
| def GetOffset(self): | |
| return 0 | |
| def GetRawData(self): | |
| return self._raw.tolist() | |
| class BinaryItem(object): | |
| def __init__(self, parent=None): | |
| self._size = 0 | |
| self._arr = array.array('B') | |
| self._parent = parent | |
| @classmethod | |
| def Read(cls, fd, parent=None): | |
| item = cls(parent) | |
| item.fromfile(fd) | |
| return item | |
| def Load(self, fd): | |
| self.fromfile(fd) | |
| def GetSize(self): | |
| """should be implemented by inherited class""" | |
| def fromfile(self, fd): | |
| self._arr.fromfile(fd, self.GetSize()) | |
| def GetParent(self): | |
| return self._parent | |
| class EfiFirmwareVolumeHeader(BinaryItem): | |
| def GetSize(self): | |
| return 56 | |
| def GetSigunature(self): | |
| list = self._arr.tolist() | |
| sig = '' | |
| for x in list[40:44]: | |
| sig += chr(x) | |
| return sig | |
| def GetAttribute(self): | |
| return list2int(self._arr.tolist()[44:48]) | |
| def GetErasePolarity(self): | |
| list = self.GetAttrStrings() | |
| if 'EFI_FVB2_ERASE_POLARITY' in list: | |
| return True | |
| return False | |
| def GetAttrStrings(self): | |
| list = [] | |
| value = self.GetAttribute() | |
| if (value & 0x01) != 0: | |
| list.append('EFI_FVB2_READ_DISABLED_CAP') | |
| if (value & 0x02) != 0: | |
| list.append('EFI_FVB2_READ_ENABLED_CAP') | |
| if (value & 0x04) != 0: | |
| list.append('EFI_FVB2_READ_STATUS') | |
| if (value & 0x08) != 0: | |
| list.append('EFI_FVB2_WRITE_DISABLED_CAP') | |
| if (value & 0x10) != 0: | |
| list.append('EFI_FVB2_WRITE_ENABLED_CAP') | |
| if (value & 0x20) != 0: | |
| list.append('EFI_FVB2_WRITE_STATUS') | |
| if (value & 0x40) != 0: | |
| list.append('EFI_FVB2_LOCK_CAP') | |
| if (value & 0x80) != 0: | |
| list.append('EFI_FVB2_LOCK_STATUS') | |
| if (value & 0x200) != 0: | |
| list.append('EFI_FVB2_STICKY_WRITE') | |
| if (value & 0x400) != 0: | |
| list.append('EFI_FVB2_MEMORY_MAPPED') | |
| if (value & 0x800) != 0: | |
| list.append('EFI_FVB2_ERASE_POLARITY') | |
| if (value & 0x1000) != 0: | |
| list.append('EFI_FVB2_READ_LOCK_CAP') | |
| if (value & 0x00002000) != 0: | |
| list.append('EFI_FVB2_READ_LOCK_STATUS') | |
| if (value & 0x00004000) != 0: | |
| list.append('EFI_FVB2_WRITE_LOCK_CAP') | |
| if (value & 0x00008000) != 0: | |
| list.append('EFI_FVB2_WRITE_LOCK_STATUS') | |
| if (value == 0): | |
| list.append('EFI_FVB2_ALIGNMENT_1') | |
| if (value & 0x001F0000) == 0x00010000: | |
| list.append('EFI_FVB2_ALIGNMENT_2') | |
| if (value & 0x001F0000) == 0x00020000: | |
| list.append('EFI_FVB2_ALIGNMENT_4') | |
| if (value & 0x001F0000) == 0x00030000: | |
| list.append('EFI_FVB2_ALIGNMENT_8') | |
| if (value & 0x001F0000) == 0x00040000: | |
| list.append('EFI_FVB2_ALIGNMENT_16') | |
| if (value & 0x001F0000) == 0x00050000: | |
| list.append('EFI_FVB2_ALIGNMENT_32') | |
| if (value & 0x001F0000) == 0x00060000: | |
| list.append('EFI_FVB2_ALIGNMENT_64') | |
| if (value & 0x001F0000) == 0x00070000: | |
| list.append('EFI_FVB2_ALIGNMENT_128') | |
| if (value & 0x001F0000) == 0x00080000: | |
| list.append('EFI_FVB2_ALIGNMENT_256') | |
| if (value & 0x001F0000) == 0x00090000: | |
| list.append('EFI_FVB2_ALIGNMENT_512') | |
| if (value & 0x001F0000) == 0x000A0000: | |
| list.append('EFI_FVB2_ALIGNMENT_1K') | |
| if (value & 0x001F0000) == 0x000B0000: | |
| list.append('EFI_FVB2_ALIGNMENT_2K') | |
| if (value & 0x001F0000) == 0x000C0000: | |
| list.append('EFI_FVB2_ALIGNMENT_4K') | |
| if (value & 0x001F0000) == 0x000D0000: | |
| list.append('EFI_FVB2_ALIGNMENT_8K') | |
| if (value & 0x001F0000) == 0x000E0000: | |
| list.append('EFI_FVB2_ALIGNMENT_16K') | |
| if (value & 0x001F0000) == 0x000F0000: | |
| list.append('EFI_FVB2_ALIGNMENT_32K') | |
| if (value & 0x001F0000) == 0x00100000: | |
| list.append('EFI_FVB2_ALIGNMENT_64K') | |
| if (value & 0x001F0000) == 0x00110000: | |
| list.append('EFI_FVB2_ALIGNMENT_128K') | |
| if (value & 0x001F0000) == 0x00120000: | |
| list.append('EFI_FVB2_ALIGNMENT_256K') | |
| if (value & 0x001F0000) == 0x00130000: | |
| list.append('EFI_FVB2_ALIGNMENT_512K') | |
| return list | |
| def GetHeaderLength(self): | |
| return list2int(self._arr.tolist()[48:50]) | |
| def Dump(self): | |
| print('Signature: %s' % self.GetSigunature()) | |
| print('Attribute: 0x%X' % self.GetAttribute()) | |
| print('Header Length: 0x%X' % self.GetHeaderLength()) | |
| print('File system Guid: ', self.GetFileSystemGuid()) | |
| print('Revision: 0x%X' % self.GetRevision()) | |
| print('FvLength: 0x%X' % self.GetFvLength()) | |
| def GetFileSystemGuid(self): | |
| list = self._arr.tolist() | |
| return list2guid(list[16:32]) | |
| def GetRevision(self): | |
| list = self._arr.tolist() | |
| return int(list[55]) | |
| def GetFvLength(self): | |
| list = self._arr.tolist() | |
| return list2int(list[32:40]) | |
| def GetRawData(self): | |
| return self._arr.tolist() | |
| class BlockMapEntry(BinaryItem): | |
| def GetSize(self): | |
| return 8 | |
| def GetNumberBlocks(self): | |
| list = self._arr.tolist() | |
| return list2int(list[0:4]) | |
| def GetLength(self): | |
| list = self._arr.tolist() | |
| return list2int(list[4:8]) | |
| def GetRawData(self): | |
| return self._arr.tolist() | |
| def __str__(self): | |
| return '[BlockEntry] Number = 0x%X, length=0x%X' % (self.GetNumberBlocks(), self.GetLength()) | |
| class EfiFfs(object): | |
| FFS_HEADER_SIZE = 24 | |
| def __init__(self, parent=None): | |
| self._header = None | |
| # following field is for FFS in FV file. | |
| self._parent = parent | |
| self._offset = 0 | |
| self._sections = [] | |
| def Load(self, fd): | |
| self._offset = align(fd.tell(), 8) | |
| self._header = EfiFfsHeader.Read(fd, self) | |
| if not isValidGuid(self.GetNameGuid()): | |
| return | |
| index = self._offset | |
| fileend = self._offset + self.GetSize() | |
| while (index + EfiSection.EFI_SECTION_HEADER_SIZE < fileend): | |
| section = EfiSection(self) | |
| section.Load(fd) | |
| if section.GetSize() == 0 and section.GetHeader().GetType() == 0: | |
| break | |
| self._sections.append(section) | |
| index = fd.tell() | |
| # rebase file pointer to next ffs file | |
| index = self._offset + self._header.GetFfsSize() | |
| index = align(index, 8) | |
| fd.seek(index) | |
| def GetOffset(self): | |
| return self._offset | |
| def GetSize(self): | |
| return self._header.GetFfsSize() | |
| @classmethod | |
| def Read(cls, fd, parent=None): | |
| item = cls(parent) | |
| item.Load(fd) | |
| return item | |
| def GetNameGuid(self): | |
| return self._header.GetNameGuid() | |
| def DumpContent(self): | |
| list = self._content.tolist() | |
| line = [] | |
| count = 0 | |
| for item in list: | |
| if count < 32: | |
| line.append('0x%X' % int(item)) | |
| count += 1 | |
| else: | |
| print(' '.join(line)) | |
| count = 0 | |
| line = [] | |
| line.append('0x%X' % int(item)) | |
| count += 1 | |
| def GetHeader(self): | |
| return self._header | |
| def GetParent(self): | |
| return self._parent | |
| def GetSections(self): | |
| return self._sections | |
| class EfiFfsHeader(BinaryItem): | |
| ffs_state_map = {0x01:'EFI_FILE_HEADER_CONSTRUCTION', | |
| 0x02:'EFI_FILE_HEADER_VALID', | |
| 0x04:'EFI_FILE_DATA_VALID', | |
| 0x08:'EFI_FILE_MARKED_FOR_UPDATE', | |
| 0x10:'EFI_FILE_DELETED', | |
| 0x20:'EFI_FILE_HEADER_INVALID'} | |
| def GetSize(self): | |
| return 24 | |
| def GetNameGuid(self): | |
| list = self._arr.tolist() | |
| return list2guid(list[0:16]) | |
| def GetType(self): | |
| list = self._arr.tolist() | |
| return int(list[18]) | |
| def GetTypeString(self): | |
| value = self.GetType() | |
| if value == 0x01: | |
| return 'EFI_FV_FILETYPE_RAW' | |
| if value == 0x02: | |
| return 'EFI_FV_FILETYPE_FREEFORM' | |
| if value == 0x03: | |
| return 'EFI_FV_FILETYPE_SECURITY_CORE' | |
| if value == 0x04: | |
| return 'EFI_FV_FILETYPE_PEI_CORE' | |
| if value == 0x05: | |
| return 'EFI_FV_FILETYPE_DXE_CORE' | |
| if value == 0x06: | |
| return 'EFI_FV_FILETYPE_PEIM' | |
| if value == 0x07: | |
| return 'EFI_FV_FILETYPE_DRIVER' | |
| if value == 0x08: | |
| return 'EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER' | |
| if value == 0x09: | |
| return 'EFI_FV_FILETYPE_APPLICATION' | |
| if value == 0x0B: | |
| return 'EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE' | |
| if value == 0xc0: | |
| return 'EFI_FV_FILETYPE_OEM_MIN' | |
| if value == 0xdf: | |
| return 'EFI_FV_FILETYPE_OEM_MAX' | |
| if value == 0xe0: | |
| return 'EFI_FV_FILETYPE_DEBUG_MIN' | |
| if value == 0xef: | |
| return 'EFI_FV_FILETYPE_DEBUG_MAX' | |
| if value == 0xf0: | |
| return 'EFI_FV_FILETYPE_FFS_PAD' | |
| if value == 0xff: | |
| return 'EFI_FV_FILETYPE_FFS_MAX' | |
| return 'Unknown FFS Type' | |
| def GetAttributes(self): | |
| list = self._arr.tolist() | |
| return int(list[19]) | |
| def GetFfsSize(self): | |
| list = self._arr.tolist() | |
| return list2int(list[20:23]) | |
| def GetState(self): | |
| list = self._arr.tolist() | |
| state = int(list[23]) | |
| polarity = self.GetParent().GetParent().GetHeader().GetErasePolarity() | |
| if polarity: | |
| state = (~state) & 0xFF | |
| HighestBit = 0x80 | |
| while (HighestBit != 0) and (HighestBit & state) == 0: | |
| HighestBit = HighestBit >> 1 | |
| return HighestBit | |
| def GetStateString(self): | |
| state = self.GetState() | |
| if state in self.ffs_state_map.keys(): | |
| return self.ffs_state_map[state] | |
| return 'Unknown Ffs State' | |
| def Dump(self): | |
| print("FFS name: ", self.GetNameGuid()) | |
| print("FFS type: ", self.GetType()) | |
| print("FFS attr: 0x%X" % self.GetAttributes()) | |
| print("FFS size: 0x%X" % self.GetFfsSize()) | |
| print("FFS state: 0x%X" % self.GetState()) | |
| def GetRawData(self): | |
| return self._arr.tolist() | |
| class EfiSection(object): | |
| EFI_SECTION_HEADER_SIZE = 4 | |
| def __init__(self, parent=None): | |
| self._size = 0 | |
| self._parent = parent | |
| self._offset = 0 | |
| self._contents = array.array('B') | |
| def Load(self, fd): | |
| self._offset = align(fd.tell(), 4) | |
| self._header = EfiSectionHeader.Read(fd, self) | |
| if self._header.GetTypeString() == "EFI_SECTION_PE32": | |
| pefile = pe.PEFile(self) | |
| pefile.Load(fd, self.GetContentSize()) | |
| fd.seek(self._offset) | |
| self._contents.fromfile(fd, self.GetContentSize()) | |
| # rebase file pointer to next section | |
| index = self._offset + self.GetSize() | |
| index = align(index, 4) | |
| fd.seek(index) | |
| def GetContentSize(self): | |
| return self.GetSize() - self.EFI_SECTION_HEADER_SIZE | |
| def GetContent(self): | |
| return self._contents.tolist() | |
| def GetSize(self): | |
| return self._header.GetSectionSize() | |
| def GetHeader(self): | |
| return self._header | |
| def GetSectionOffset(self): | |
| return self._offset + self.EFI_SECTION_HEADER_SIZE | |
| class EfiSectionHeader(BinaryItem): | |
| section_type_map = {0x01: 'EFI_SECTION_COMPRESSION', | |
| 0x02: 'EFI_SECTION_GUID_DEFINED', | |
| 0x10: 'EFI_SECTION_PE32', | |
| 0x11: 'EFI_SECTION_PIC', | |
| 0x12: 'EFI_SECTION_TE', | |
| 0x13: 'EFI_SECTION_DXE_DEPEX', | |
| 0x14: 'EFI_SECTION_VERSION', | |
| 0x15: 'EFI_SECTION_USER_INTERFACE', | |
| 0x16: 'EFI_SECTION_COMPATIBILITY16', | |
| 0x17: 'EFI_SECTION_FIRMWARE_VOLUME_IMAGE', | |
| 0x18: 'EFI_SECTION_FREEFORM_SUBTYPE_GUID', | |
| 0x19: 'EFI_SECTION_RAW', | |
| 0x1B: 'EFI_SECTION_PEI_DEPEX'} | |
| def GetSize(self): | |
| return 4 | |
| def GetSectionSize(self): | |
| list = self._arr.tolist() | |
| return list2int(list[0:3]) | |
| def GetType(self): | |
| list = self._arr.tolist() | |
| return int(list[3]) | |
| def GetTypeString(self): | |
| type = self.GetType() | |
| if type not in self.section_type_map.keys(): | |
| return 'Unknown Section Type' | |
| return self.section_type_map[type] | |
| def Dump(self): | |
| print('size = 0x%X' % self.GetSectionSize()) | |
| print('type = 0x%X' % self.GetType()) | |
| rMapEntry = re.compile('^(\w+)[ \(\w\)]* \(BaseAddress=([0-9a-fA-F]+), EntryPoint=([0-9a-fA-F]+), GUID=([0-9a-fA-F\-]+)') | |
| class EfiFvMapFile(object): | |
| def __init__(self): | |
| self._mapentries = {} | |
| def Load(self, path): | |
| if not os.path.exists(path): | |
| return False | |
| try: | |
| file = open(path, 'r') | |
| lines = file.readlines() | |
| file.close() | |
| except: | |
| return False | |
| for line in lines: | |
| if line[0] != ' ': | |
| # new entry | |
| ret = rMapEntry.match(line) | |
| if ret is not None: | |
| name = ret.groups()[0] | |
| baseaddr = int(ret.groups()[1], 16) | |
| entry = int(ret.groups()[2], 16) | |
| guidstr = '{' + ret.groups()[3] + '}' | |
| guid = uuid.UUID(guidstr) | |
| self._mapentries[guid] = EfiFvMapFileEntry(name, baseaddr, entry, guid) | |
| return True | |
| def GetEntry(self, guid): | |
| if guid in self._mapentries.keys(): | |
| return self._mapentries[guid] | |
| return None | |
| class EfiFvMapFileEntry(object): | |
| def __init__(self, name, baseaddr, entry, guid): | |
| self._name = name | |
| self._baseaddr = baseaddr | |
| self._entry = entry | |
| self._guid = guid | |
| def GetName(self): | |
| return self._name | |
| def GetBaseAddress(self): | |
| return self._baseaddr | |
| def GetEntryPoint(self): | |
| return self._entry | |
| def list2guid(list): | |
| val1 = list2int(list[0:4]) | |
| val2 = list2int(list[4:6]) | |
| val3 = list2int(list[6:8]) | |
| val4 = 0 | |
| for item in list[8:16]: | |
| val4 = (val4 << 8) | int(item) | |
| val = val1 << 12 * 8 | val2 << 10 * 8 | val3 << 8 * 8 | val4 | |
| guid = uuid.UUID(int=val) | |
| return guid | |
| def list2int(list): | |
| val = 0 | |
| for index in range(len(list) - 1, -1, -1): | |
| val = (val << 8) | int(list[index]) | |
| return val | |
| def align(value, alignment): | |
| return (value + ((alignment - value) & (alignment - 1))) | |
| gInvalidGuid = uuid.UUID(int=0xffffffffffffffffffffffffffffffff) | |
| def isValidGuid(guid): | |
| if guid == gInvalidGuid: | |
| return False | |
| return True |