## @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 |