mirror of
https://github.com/chipsec/chipsec
synced 2026-06-08 13:31:00 +00:00
c45686c7b1
Original commit for chipsec v1: 6b265feffa
Signed-off-by: Nathaniel Mitchell <nathaniel.p.mitchell@intel.com>
562 lines
22 KiB
Python
562 lines
22 KiB
Python
# CHIPSEC: Platform Security Assessment Framework
|
|
# Copyright (c) 2020-2021, Intel Corporation
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; Version 2.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# Contact information:
|
|
# chipsec@intel.com
|
|
#
|
|
|
|
"""
|
|
UEFI Firmware Volume Parsing/Modification Functionality
|
|
"""
|
|
|
|
import hashlib
|
|
import struct
|
|
from typing import Optional, Tuple
|
|
from uuid import UUID
|
|
from chipsec.library.defines import bytestostring
|
|
from chipsec.library.uefi.common import get_3b_size, bit_set, align
|
|
from chipsec.library.logger import logger
|
|
|
|
################################################################################################
|
|
#
|
|
# EFI Firmware Volume Defines
|
|
#
|
|
################################################################################################
|
|
|
|
FFS_ATTRIB_LARGE_FILE = 0x01
|
|
FFS_ATTRIB_FIXED = 0x04
|
|
FFS_ATTRIB_DATA_ALIGNMENT = 0x38
|
|
FFS_ATTRIB_CHECKSUM = 0x40
|
|
|
|
EFI_FILE_HEADER_CONSTRUCTION = 0x01
|
|
EFI_FILE_HEADER_VALID = 0x02
|
|
EFI_FILE_DATA_VALID = 0x04
|
|
EFI_FILE_MARKED_FOR_UPDATE = 0x08
|
|
EFI_FILE_DELETED = 0x10
|
|
EFI_FILE_HEADER_INVALID = 0x20
|
|
|
|
FFS_FIXED_CHECKSUM = 0xAA
|
|
|
|
EFI_FVB2_ERASE_POLARITY = 0x00000800
|
|
|
|
EFI_FV_FILETYPE_ALL = 0x00
|
|
EFI_FV_FILETYPE_RAW = 0x01
|
|
EFI_FV_FILETYPE_FREEFORM = 0x02
|
|
EFI_FV_FILETYPE_SECURITY_CORE = 0x03
|
|
EFI_FV_FILETYPE_PEI_CORE = 0x04
|
|
EFI_FV_FILETYPE_DXE_CORE = 0x05
|
|
EFI_FV_FILETYPE_PEIM = 0x06
|
|
EFI_FV_FILETYPE_DRIVER = 0x07
|
|
EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER = 0x08
|
|
EFI_FV_FILETYPE_APPLICATION = 0x09
|
|
EFI_FV_FILETYPE_MM = 0x0a
|
|
EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE = 0x0b
|
|
EFI_FV_FILETYPE_COMBINED_MM_DXE = 0x0c
|
|
EFI_FV_FILETYPE_MM_CORE = 0x0d
|
|
EFI_FV_FILETYPE_MM_STANDALONE = 0x0e
|
|
EFI_FV_FILETYPE_MM_CORE_STANDALONE = 0x0f
|
|
EFI_FV_FILETYPE_FFS_PAD = 0xf0
|
|
|
|
FILE_TYPE_NAMES = {
|
|
0x00: 'FV_ALL',
|
|
0x01: 'FV_RAW',
|
|
0x02: 'FV_FREEFORM',
|
|
0x03: 'FV_SECURITY_CORE',
|
|
0x04: 'FV_PEI_CORE',
|
|
0x05: 'FV_DXE_CORE',
|
|
0x06: 'FV_PEIM',
|
|
0x07: 'FV_DRIVER',
|
|
0x08: 'FV_COMBINED_PEIM_DRIVER',
|
|
0x09: 'FV_APPLICATION',
|
|
0x0A: 'FV_MM',
|
|
0x0B: 'FV_FVIMAGE',
|
|
0x0C: 'FV_COMBINED_MM_DXE',
|
|
0x0D: 'FV_MM_CORE',
|
|
0x0E: 'FV_MM_STANDALONE',
|
|
0x0F: 'FV_MM_CORE_STANDALONE',
|
|
0xF0: 'FV_FFS_PAD'
|
|
}
|
|
|
|
EFI_SECTION_ALL = 0x00
|
|
EFI_SECTION_COMPRESSION = 0x01
|
|
EFI_SECTION_GUID_DEFINED = 0x02
|
|
EFI_SECTION_PE32 = 0x10
|
|
EFI_SECTION_PIC = 0x11
|
|
EFI_SECTION_TE = 0x12
|
|
EFI_SECTION_DXE_DEPEX = 0x13
|
|
EFI_SECTION_VERSION = 0x14
|
|
EFI_SECTION_USER_INTERFACE = 0x15
|
|
EFI_SECTION_COMPATIBILITY16 = 0x16
|
|
EFI_SECTION_FIRMWARE_VOLUME_IMAGE = 0x17
|
|
EFI_SECTION_FREEFORM_SUBTYPE_GUID = 0x18
|
|
EFI_SECTION_RAW = 0x19
|
|
EFI_SECTION_PEI_DEPEX = 0x1B
|
|
EFI_SECTION_MM_DEPEX = 0x1C
|
|
|
|
SECTION_NAMES = {
|
|
0x00: 'S_ALL',
|
|
0x01: 'S_COMPRESSION',
|
|
0x02: 'S_GUID_DEFINED',
|
|
0x10: 'S_PE32',
|
|
0x11: 'S_PIC',
|
|
0x12: 'S_TE',
|
|
0x13: 'S_DXE_DEPEX',
|
|
0x14: 'S_VERSION',
|
|
0x15: 'S_USER_INTERFACE',
|
|
0x16: 'S_COMPATIBILITY16',
|
|
0x17: 'S_FV_IMAGE',
|
|
0x18: 'S_FREEFORM_SUBTYPE_GUID',
|
|
0x19: 'S_RAW',
|
|
0x1B: 'S_PEI_DEPEX',
|
|
0x1C: 'S_MM_DEPEX'
|
|
}
|
|
|
|
EFI_SECTIONS_EXE = [EFI_SECTION_PE32, EFI_SECTION_TE, EFI_SECTION_PIC, EFI_SECTION_COMPATIBILITY16]
|
|
|
|
EFI_FIRMWARE_VOLUME_HEADER = "<16s16sQIIHHHBB"
|
|
EFI_FIRMWARE_VOLUME_HEADER_size = struct.calcsize(EFI_FIRMWARE_VOLUME_HEADER)
|
|
EFI_FV_BLOCK_MAP_ENTRY = "<II"
|
|
EFI_FFS_FILE_HEADER = "<16sHBB3sB"
|
|
EFI_FFS_FILE_HEADER2 = "<16sHBB3sBQ"
|
|
EFI_COMMON_SECTION_HEADER = "<3sB"
|
|
EFI_COMPRESSION_SECTION = "<IB"
|
|
EFI_COMPRESSION_SECTION_size = struct.calcsize(EFI_COMPRESSION_SECTION)
|
|
EFI_GUID_DEFINED_SECTION = "<16sHH"
|
|
EFI_GUID_DEFINED_SECTION_size = struct.calcsize(EFI_GUID_DEFINED_SECTION)
|
|
EFI_SECTION_BROTLI_PARAM_LGWIN = 20
|
|
EFI_SECTION_BROTLI_HEADER_FORMAT = '<QQ'
|
|
EFI_SECTION_BROTLI_HEADER_LENGTH = struct.calcsize(EFI_SECTION_BROTLI_HEADER_FORMAT)
|
|
EFI_SECTION_ZLIB_AMD_HEADER_FORMAT = '<20xI232x'
|
|
EFI_SECTION_ZLIB_AMD_HEADER_LENGTH = struct.calcsize(EFI_SECTION_ZLIB_AMD_HEADER_FORMAT)
|
|
|
|
WIN_CERTIFICATE = "<IHH16s"
|
|
WIN_CERTIFICATE_size = struct.calcsize(WIN_CERTIFICATE)
|
|
|
|
WIN_CERT_TYPE_PKCS_SIGNED_DATA = 0x0002
|
|
WIN_CERT_TYPE_EFI_PKCS115 = 0x0EF0
|
|
WIN_CERT_TYPE_EFI_GUID = 0x0EF1
|
|
|
|
EFI_CRC32_GUIDED_SECTION_EXTRACTION_PROTOCOL_GUID = UUID("FC1BCDB0-7D31-49AA-936A-A4600D9DD083")
|
|
EFI_CERT_TYPE_RSA_2048_SHA256_GUID = UUID("A7717414-C616-4977-9420-844712A735BF")
|
|
EFI_CERT_TYPE_RSA_2048_SHA256_GUID_size = struct.calcsize("16s256s256s")
|
|
EFI_FIRMWARE_CONTENTS_SIGNED_GUID = UUID("0F9D89E8-9259-4F76-A5AF-0C89E34023DF")
|
|
EFI_FIRMWARE_FILE_SYSTEM_GUID = UUID("7A9354D9-0468-444A-81CE-0BF617D890DF")
|
|
EFI_FIRMWARE_FILE_SYSTEM2_GUID = UUID("8C8CE578-8A3D-4F1C-9935-896185C32DD3")
|
|
EFI_FIRMWARE_FILE_SYSTEM3_GUID = UUID("5473C07A-3DCB-4DCA-BD6F-1E9689E7349A")
|
|
|
|
EFI_FS_GUIDS = [EFI_FIRMWARE_FILE_SYSTEM3_GUID, EFI_FIRMWARE_FILE_SYSTEM2_GUID, EFI_FIRMWARE_FILE_SYSTEM_GUID]
|
|
|
|
EFI_GUIDED_SECTION_BROTLI = UUID('3D532050-5CDA-4FD0-879E-0F7F630D5AFB')
|
|
EFI_GUIDED_SECTION_GZIP = UUID('1D301FE9-BE79-4353-91C2-D23BC959AE0C')
|
|
EFI_GUIDED_SECTION_LZMA = UUID('EE4E5898-3914-4259-9D6E-DC7BD79403CF')
|
|
EFI_GUIDED_SECTION_LZMAF86 = UUID('D42AE6BD-1352-4BFB-909A-CA72A6EAE889')
|
|
EFI_GUIDED_SECTION_LZMA_HP = UUID('0ED85E23-F253-413F-A03C-901987B04397')
|
|
EFI_GUIDED_SECTION_LZMA_MS = UUID('BD9921EA-ED91-404A-8B2F-B4D724747C8C')
|
|
EFI_GUIDED_SECTION_TIANO = UUID('A31280AD-481E-41B6-95E8-127F4C984779')
|
|
EFI_GUIDED_SECTION_ZLIB_AMD1 = UUID('CE3233F5-2CD6-4D87-9152-4A238BB6D1C4')
|
|
EFI_GUIDED_SECTION_ZLIB_AMD2 = UUID('991EFAC0-E260-416B-A4B8-3B153072B804')
|
|
|
|
FIRMWARE_VOLUME_GUID = UUID("24400798-3807-4A42-B413-A1ECEE205DD8")
|
|
VOLUME_SECTION_GUID = UUID("367AE684-335D-4671-A16D-899DBFEA6B88")
|
|
EFI_FFS_VOLUME_TOP_FILE_GUID = UUID("1BA0062E-C779-4582-8566-336AE8F78F09")
|
|
|
|
DEF_INDENT = " "
|
|
|
|
|
|
class EFI_MODULE:
|
|
def __init__(self, Offset: int, Guid: Optional[UUID], HeaderSize: int, Attributes: int, Image: bytes):
|
|
self.Offset = Offset
|
|
self.Guid = Guid
|
|
self.HeaderSize = HeaderSize
|
|
self.Attributes = Attributes
|
|
self.Image = Image
|
|
self.ui_string = ''
|
|
self.isNVRAM = False
|
|
self.NVRAMType = ''
|
|
|
|
self.indent = ''
|
|
|
|
self.MD5 = None
|
|
self.SHA1 = None
|
|
self.SHA256 = None
|
|
|
|
# a list of children EFI_MODULE nodes to build the EFI_MODULE object model
|
|
self.children = []
|
|
|
|
def name(self) -> str:
|
|
_name = type(self).__name__.encode('ascii', 'ignore')
|
|
_guid = str(self.Guid).upper()
|
|
_ui_str = self.ui_string.encode('ascii', 'ignore') if self.ui_string else ''
|
|
return f'{_name} {{{_guid}}} {_ui_str}'
|
|
|
|
def __str__(self) -> str:
|
|
_ind = self.indent + DEF_INDENT
|
|
_s = ''
|
|
if self.MD5:
|
|
_s = f'\n{_ind}MD5 : {self.MD5}'
|
|
if self.SHA1:
|
|
_s += f'\n{_ind}SHA1 : {self.SHA1}'
|
|
if self.SHA256:
|
|
_s += f'\n{_ind}SHA256: {self.SHA256}'
|
|
return bytestostring(_s)
|
|
|
|
def calc_hashes(self, off: int = 0) -> None:
|
|
if self.Image is None:
|
|
return
|
|
hmd5 = hashlib.md5()
|
|
hmd5.update(self.Image[off:])
|
|
self.MD5 = hmd5.hexdigest()
|
|
hsha1 = hashlib.sha1()
|
|
hsha1.update(self.Image[off:])
|
|
self.SHA1 = hsha1.hexdigest()
|
|
hsha256 = hashlib.sha256()
|
|
hsha256.update(self.Image[off:])
|
|
self.SHA256 = hsha256.hexdigest()
|
|
|
|
|
|
class EFI_FV(EFI_MODULE):
|
|
def __init__(self, Offset: int, Guid: UUID, Size: int, Attributes: int, HeaderSize: int, Checksum: int, ExtHeaderOffset: int, Image: bytes, CalcSum: int):
|
|
super(EFI_FV, self).__init__(Offset, Guid, HeaderSize, Attributes, Image)
|
|
self.Size = Size
|
|
self.Checksum = Checksum
|
|
self.ExtHeaderOffset = ExtHeaderOffset
|
|
self.CalcSum = CalcSum
|
|
|
|
def __str__(self) -> str:
|
|
schecksum = f'{self.Checksum:04X}h ({self.CalcSum:04X}h) *** checksum mismatch ***' if self.CalcSum != self.Checksum else f'{self.Checksum:04X}h'
|
|
_s = f'\n{self.indent}{type(self).__name__} +{self.Offset:08X}h {{{self.Guid}}}: '
|
|
_s += f"Size {self.Size:08X}h, Attr {self.Attributes:08X}h, HdrSize {self.HeaderSize:04X}h, ExtHdrOffset {self.ExtHeaderOffset:08X}h, Checksum {schecksum}"
|
|
_s += super(EFI_FV, self).__str__()
|
|
return bytestostring(_s)
|
|
|
|
|
|
class EFI_FILE(EFI_MODULE):
|
|
def __init__(self, Offset: int, Guid: UUID, Type: int, Attributes: int, State: int, Checksum: int, Size: int, Image: bytes, HeaderSize: int, UD: bool, CalcSum: int):
|
|
super(EFI_FILE, self).__init__(Offset, Guid, HeaderSize, Attributes, Image)
|
|
self.Name = Guid
|
|
self.Type = Type
|
|
self.State = State
|
|
self.Size = Size
|
|
self.Checksum = Checksum
|
|
self.UD = UD
|
|
self.CalcSum = CalcSum
|
|
|
|
def __str__(self) -> str:
|
|
schecksum = f'{self.Checksum:04X}h ({self.CalcSum:04X}h) *** checksum mismatch ***' if self.CalcSum != self.Checksum else f'{self.Checksum:04X}h'
|
|
_s = f'\n{self.indent}+{self.Offset:08X}h {self.name()}\n{self.indent}Type {self.Type:02X}h, Attr {self.Attributes:08X}h, State {self.State:02X}h, Size {self.Size:06X}h, Checksum {schecksum}'
|
|
_s += (super(EFI_FILE, self).__str__() + '\n')
|
|
return bytestostring(_s)
|
|
|
|
|
|
class EFI_SECTION(EFI_MODULE):
|
|
def __init__(self, Offset: int, Name: str, Type: int, Image: bytes, HeaderSize: int, Size: int):
|
|
super(EFI_SECTION, self).__init__(Offset, None, HeaderSize, 0, Image)
|
|
self.Name = Name
|
|
self.Type = Type
|
|
self.DataOffset = 0
|
|
self.Comments = ''
|
|
self.Size = Size
|
|
|
|
# parent GUID used in search, export to JSON/log
|
|
self.parentGuid = None
|
|
|
|
def name(self) -> str:
|
|
_name = self.Name.encode('ascii', 'ignore')
|
|
_guid = self.parentGuid
|
|
_ui_str = self.ui_string.encode('ascii', 'ignore') if self.ui_string else ''
|
|
return f'{_name} section of binary {{{_guid}}} {_ui_str}'
|
|
|
|
def __str__(self) -> str:
|
|
_s = f'{self.indent}+{self.Offset:08X}h {self.name()}: Type {self.Type:02X}h'
|
|
if self.Guid:
|
|
_s += f' GUID {{{self.Guid}}}'
|
|
if self.Attributes:
|
|
_s += f' Attr {self.Attributes:04X}h'
|
|
if self.DataOffset:
|
|
_s += f' DataOffset {self.DataOffset:04X}h'
|
|
if self.Comments:
|
|
_s += f' Comments {self.Comments}'
|
|
_s += super(EFI_SECTION, self).__str__()
|
|
return bytestostring(_s)
|
|
|
|
|
|
def FvSum8(buffer: bytes) -> int:
|
|
sum8 = 0
|
|
for b in bytestostring(buffer):
|
|
sum8 = (sum8 + ord(b)) & 0xff
|
|
return sum8
|
|
|
|
|
|
def FvChecksum8(buffer: bytes) -> int:
|
|
return ((0x100 - FvSum8(buffer)) & 0xff)
|
|
|
|
|
|
def FvSum16(buffer: bytes) -> int:
|
|
sum16 = 0
|
|
buffer_str = bytestostring(buffer)
|
|
blen = len(buffer) // 2
|
|
i = 0
|
|
while i < blen:
|
|
el16 = ord(buffer_str[2 * i]) | (ord(buffer_str[2 * i + 1]) << 8)
|
|
sum16 = (sum16 + el16) & 0xffff
|
|
i = i + 1
|
|
return sum16
|
|
|
|
|
|
def FvChecksum16(buffer: bytes) -> int:
|
|
return ((0x10000 - FvSum16(buffer)) & 0xffff)
|
|
|
|
|
|
def ValidateFwVolumeHeader(FsGuid: UUID, FvLength: int, HeaderLength: int, ExtHeaderOffset: int, Reserved: int,
|
|
size: int, Calcsum: int, Checksum: int) -> bool:
|
|
fv_rsvd = Reserved == 0
|
|
if not fv_rsvd:
|
|
logger().log_hal(f'WARNING: Firmware Volume {{{FsGuid}}} header has non-zero reserved values 0b{Reserved:b}')
|
|
|
|
fv_len = FvLength <= size
|
|
if not fv_len:
|
|
logger().log_hal(f'WARNING: Firmware Volume {{{FsGuid}}} data length 0x{FvLength:X} '
|
|
f'exceeds total image length 0x{size:X}')
|
|
|
|
fv_header_len = (ExtHeaderOffset < FvLength) and (HeaderLength < FvLength)
|
|
if not fv_header_len:
|
|
logger().log_hal(f'WARNING: Firmware Volume {{{FsGuid}}} header length 0x{HeaderLength:X} '
|
|
f'exceeds or matches volume data length 0x{FvLength:X}')
|
|
|
|
if Checksum != Calcsum:
|
|
logger().log_hal(f'WARNING: Firmware Volume {{{FsGuid}}} checksum 0x{Checksum:X} '
|
|
f'does not match calculated checksum 0x{Calcsum:X}')
|
|
|
|
return fv_rsvd and fv_len and fv_header_len
|
|
|
|
|
|
def NextFwVolume(buffer: bytes, off: int = 0, last_fv_size: int = 0) -> Optional[EFI_FV]:
|
|
fof = off if last_fv_size == 0 else off + max(last_fv_size, EFI_FIRMWARE_VOLUME_HEADER_size)
|
|
size = len(buffer)
|
|
while (fof + EFI_FIRMWARE_VOLUME_HEADER_size) < size:
|
|
fof = bytestostring(buffer).find("_FVH", fof)
|
|
if fof == -1 or size - fof < EFI_FIRMWARE_VOLUME_HEADER_size:
|
|
break
|
|
elif fof < 0x28:
|
|
# continue searching for signature if header is not valid
|
|
fof += 0x4
|
|
continue
|
|
fof = fof - 0x28
|
|
ZeroVector, FileSystemGuid0, \
|
|
FvLength, Signature, Attributes, HeaderLength, Checksum, ExtHeaderOffset, \
|
|
Reserved, Revision = struct.unpack(EFI_FIRMWARE_VOLUME_HEADER, buffer[fof:fof + EFI_FIRMWARE_VOLUME_HEADER_size])
|
|
fvh = struct.pack(EFI_FIRMWARE_VOLUME_HEADER, ZeroVector,
|
|
FileSystemGuid0,
|
|
FvLength, Signature, Attributes, HeaderLength, 0, ExtHeaderOffset,
|
|
Reserved, Revision)
|
|
if (len(fvh) < HeaderLength):
|
|
tail = buffer[fof + len(fvh):fof + HeaderLength]
|
|
fvh = fvh + tail
|
|
CalcSum = FvChecksum16(fvh)
|
|
FsGuid = UUID(bytes_le=FileSystemGuid0)
|
|
FvImage = buffer[fof:fof + FvLength]
|
|
if ValidateFwVolumeHeader(FsGuid, FvLength, HeaderLength, ExtHeaderOffset, Reserved, len(FvImage), CalcSum, Checksum):
|
|
return EFI_FV(fof, FsGuid, FvLength, Attributes, HeaderLength, Checksum, ExtHeaderOffset, FvImage, CalcSum)
|
|
else:
|
|
fof += 0x2C
|
|
return None
|
|
|
|
|
|
def GetFvHeader(buffer: bytes, off: int = 0) -> Tuple[int, int, int]:
|
|
EFI_FV_BLOCK_MAP_ENTRY_SZ = struct.calcsize(EFI_FV_BLOCK_MAP_ENTRY)
|
|
header_size = EFI_FIRMWARE_VOLUME_HEADER_size + struct.calcsize(EFI_FV_BLOCK_MAP_ENTRY)
|
|
if (len(buffer) < header_size):
|
|
return (0, 0, 0)
|
|
size = 0
|
|
fof = off + EFI_FIRMWARE_VOLUME_HEADER_size
|
|
ZeroVector, FileSystemGuid0, \
|
|
FvLength, _, Attributes, HeaderLength, Checksum, ExtHeaderOffset, \
|
|
Reserved, Revision = struct.unpack(EFI_FIRMWARE_VOLUME_HEADER, buffer[off:off + EFI_FIRMWARE_VOLUME_HEADER_size])
|
|
numblocks, lenblock = struct.unpack(EFI_FV_BLOCK_MAP_ENTRY, buffer[fof:fof + struct.calcsize(EFI_FV_BLOCK_MAP_ENTRY)])
|
|
fv_header_str = f'''
|
|
\nFV volume offset: 0x{fof:08X}
|
|
\tFvLength: 0x{FvLength:08X}
|
|
\tAttributes: 0x{Attributes:08X}
|
|
\tHeaderLength: 0x{HeaderLength:04X}
|
|
\tChecksum: 0x{Checksum:04X}
|
|
\tRevision: 0x{Revision:02X}
|
|
\tExtHeaderOffset: 0x{ExtHeaderOffset:02X}
|
|
\tReserved: 0x{Reserved:02X}
|
|
FFS Guid: {UUID(bytes_le=FileSystemGuid0)}
|
|
'''
|
|
logger().log_hal(fv_header_str)
|
|
|
|
while not (numblocks == 0 and lenblock == 0):
|
|
fof += EFI_FV_BLOCK_MAP_ENTRY_SZ
|
|
if (fof + EFI_FV_BLOCK_MAP_ENTRY_SZ) >= len(buffer):
|
|
return (0, 0, 0)
|
|
if numblocks != 0:
|
|
logger().log_hal(f'Num blocks: 0x{numblocks:08X}\n')
|
|
logger().log_hal(f'block Len: 0x{lenblock:08X}\n')
|
|
size = size + (numblocks * lenblock)
|
|
numblocks, lenblock = struct.unpack(EFI_FV_BLOCK_MAP_ENTRY, buffer[fof:fof + EFI_FV_BLOCK_MAP_ENTRY_SZ])
|
|
if FvLength != size:
|
|
logger().log_hal("ERROR: Volume Size not consistent with Block Maps")
|
|
return (0, 0, 0)
|
|
if size >= 0x40000000 or size == 0:
|
|
logger().log_hal("ERROR: Volume is corrupted")
|
|
return (0, 0, 0)
|
|
return (size, HeaderLength, Attributes)
|
|
|
|
|
|
def NextFwFile(FvImage: bytes, FvLength: int, fof: int, polarity: bool) -> Optional[EFI_FILE]:
|
|
file_header_size = struct.calcsize(EFI_FFS_FILE_HEADER)
|
|
fof = align(fof, 8)
|
|
cur_offset = fof
|
|
res = None
|
|
update_or_deleted = False
|
|
|
|
while cur_offset + file_header_size < min(FvLength, len(FvImage)):
|
|
fsize = 0
|
|
# if (fof + file_header_size) <= min(FvLength, len(FvImage)):
|
|
# Check for a blank header
|
|
if polarity:
|
|
blank = b"\xff" * file_header_size
|
|
else:
|
|
blank = b"\x00" * file_header_size
|
|
|
|
if (blank == FvImage[cur_offset:cur_offset + file_header_size]):
|
|
# next_offset = fof + 8
|
|
cur_offset += 8
|
|
continue
|
|
Name0, IntegrityCheck, Type, Attributes, Size, State = struct.unpack(EFI_FFS_FILE_HEADER, FvImage[cur_offset:cur_offset + file_header_size])
|
|
# Get File Header Size
|
|
if Attributes & FFS_ATTRIB_LARGE_FILE:
|
|
header_size = struct.calcsize(EFI_FFS_FILE_HEADER2)
|
|
else:
|
|
header_size = struct.calcsize(EFI_FFS_FILE_HEADER)
|
|
|
|
# Get File size
|
|
if Attributes & FFS_ATTRIB_LARGE_FILE and len(FvImage) > fof + struct.calcsize(EFI_FFS_FILE_HEADER2):
|
|
fsize = struct.unpack("Q", FvImage[fof + file_header_size:fof + file_header_size + struct.calcsize("Q")])[0]
|
|
fsize &= 0xFFFFFFFF
|
|
if fsize == 0 or fsize > FvLength - cur_offset:
|
|
fsize = get_3b_size(Size)
|
|
|
|
# Validate fsize is a legal value
|
|
if fsize == 0 or fsize > FvLength - cur_offset:
|
|
logger().log_hal("WARNING: Unable to get correct file size for NextFwFile corrupt header information")
|
|
break
|
|
# Get next_offset
|
|
update_or_deleted = (bit_set(State, EFI_FILE_MARKED_FOR_UPDATE, polarity)) or (bit_set(State, EFI_FILE_DELETED, polarity))
|
|
if not ((bit_set(State, EFI_FILE_DATA_VALID, polarity)) or update_or_deleted):
|
|
# else:
|
|
cur_offset = align(cur_offset + 1, 8)
|
|
continue
|
|
Name = UUID(bytes_le=Name0)
|
|
# TODO need to fix up checksum?
|
|
fheader = struct.pack(EFI_FFS_FILE_HEADER, Name0, 0, Type, Attributes, Size, 0)
|
|
hsum = FvChecksum8(fheader)
|
|
if (Attributes & FFS_ATTRIB_CHECKSUM):
|
|
fsum = FvChecksum8(FvImage[cur_offset + file_header_size:cur_offset + fsize])
|
|
else:
|
|
fsum = FFS_FIXED_CHECKSUM
|
|
CalcSum = (hsum | (fsum << 8))
|
|
_image = FvImage[cur_offset:cur_offset + fsize]
|
|
res = EFI_FILE(cur_offset, Name, Type, Attributes, State, IntegrityCheck, fsize, _image, header_size, update_or_deleted, CalcSum)
|
|
break
|
|
return res
|
|
|
|
|
|
def NextFwFileSection(sections: bytes, ssize: int, sof: int, polarity: bool) -> Optional[EFI_SECTION]:
|
|
EFI_COMMON_SECTION_HEADER_size = struct.calcsize(EFI_COMMON_SECTION_HEADER)
|
|
res = None
|
|
curr_offset = sof
|
|
ssize = min(ssize, len(sections))
|
|
while curr_offset + EFI_COMMON_SECTION_HEADER_size < ssize:
|
|
Size, Type = struct.unpack(EFI_COMMON_SECTION_HEADER, sections[curr_offset:curr_offset + EFI_COMMON_SECTION_HEADER_size])
|
|
Size = get_3b_size(Size)
|
|
Header_Size = EFI_COMMON_SECTION_HEADER_size
|
|
if Size == 0xFFFFFF and (curr_offset + EFI_COMMON_SECTION_HEADER_size + struct.calcsize("I")) < ssize:
|
|
_start = curr_offset + EFI_COMMON_SECTION_HEADER_size
|
|
_finish = _start + struct.calcsize("I")
|
|
Size = struct.unpack("I", sections[_start:_finish])[0]
|
|
Header_Size = EFI_COMMON_SECTION_HEADER_size + struct.calcsize("I")
|
|
if Type in SECTION_NAMES.keys():
|
|
sec_name = SECTION_NAMES[Type]
|
|
else:
|
|
sec_name = f'S_UNKNOWN_{Type:02X}'
|
|
if (Size == 0xffffff and Type == 0xff) or (Size == 0):
|
|
curr_offset = align(curr_offset + 4, 4)
|
|
continue
|
|
sec_body = sections[curr_offset:curr_offset + Size]
|
|
res = EFI_SECTION(curr_offset, sec_name, Type, sec_body, Header_Size, align(Size, 4))
|
|
break
|
|
return res
|
|
|
|
# #################################################################################################
|
|
#
|
|
# UEFI Firmware Volume Parsing/Modification Functionality
|
|
#
|
|
# #################################################################################################
|
|
|
|
|
|
def align_image(image: bytes, size: int = 8, fill: bytes = b'\x00') -> bytes:
|
|
return image.ljust(((len(image) + size - 1) // size) * size, fill)
|
|
|
|
|
|
def get_guid_bin(guid: UUID) -> bytes:
|
|
values = str(guid).split('-')
|
|
if [len(x) for x in values] == [8, 4, 4, 4, 12]:
|
|
values = values[0:3] + [values[3][0:2], values[3][2:4]] + [values[4][x:x + 2] for x in range(0, 12, 2)]
|
|
values = [int(x, 16) for x in values]
|
|
return struct.pack('<LHHBBBBBBBB', *tuple(values))
|
|
return b''
|
|
|
|
|
|
def assemble_uefi_file(guid: UUID, image: bytes) -> bytes:
|
|
EFI_FFS_FILE_HEADER = "<16sHBBL"
|
|
FileHeaderSize = struct.calcsize(EFI_FFS_FILE_HEADER)
|
|
|
|
Type = EFI_FV_FILETYPE_FREEFORM
|
|
CheckSum = 0x0000
|
|
Attributes = 0x40
|
|
Size = FileHeaderSize + len(image)
|
|
State = 0xF8
|
|
|
|
SizeState = (Size & 0x00FFFFFF) | (State << 24)
|
|
FileHeader = struct.pack(EFI_FFS_FILE_HEADER, get_guid_bin(guid), CheckSum, Type, Attributes, (Size & 0x00FFFFFF))
|
|
|
|
hsum = FvChecksum8(FileHeader)
|
|
if (Attributes & FFS_ATTRIB_CHECKSUM):
|
|
fsum = FvChecksum8(image)
|
|
else:
|
|
fsum = FFS_FIXED_CHECKSUM
|
|
CheckSum = (hsum | (fsum << 8))
|
|
|
|
return struct.pack(EFI_FFS_FILE_HEADER, get_guid_bin(guid), CheckSum, Type, Attributes, SizeState) + image
|
|
|
|
|
|
def assemble_uefi_section(image: bytes, uncomressed_size: int, compression_type: int) -> bytes:
|
|
EFI_COMPRESSION_SECTION_HEADER = "<LLB"
|
|
SectionType = EFI_SECTION_COMPRESSION
|
|
SectionSize = struct.calcsize(EFI_COMPRESSION_SECTION_HEADER) + len(image)
|
|
SectionHeader = struct.pack(EFI_COMPRESSION_SECTION_HEADER, (SectionSize & 0x00FFFFFF) | (SectionType << 24), uncomressed_size, compression_type)
|
|
return SectionHeader + image
|
|
|
|
|
|
def assemble_uefi_raw(image: bytes) -> bytes:
|
|
return align_image(struct.pack('<L', ((len(image) + 4) & 0x00FFFFFF) + (EFI_SECTION_RAW << 24)) + image)
|
|
|
|
|
|
def DecodeSection(SecType, SecBody, SecHeaderSize) -> None:
|
|
pass
|