You've already forked edk2-upstream
mirror of
https://github.com/Dasharo/edk2-upstream.git
synced 2026-03-06 15:03:57 -08:00
1.Add FFS file type check: Only allow rebase operation for EFI_FV_FILETYPE_SECURITY_CORE, EFI_FV_FILETYPE_PEI_CORE, EFI_FV_FILETYPE_DXE_CORE, EFI_FV_FILETYPE_PEIM, EFI_FV_FILETYPE_DRIVER, EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER,EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE types, improving safety and compliance. 2.Automatically detect and complete the PE/COFF or TE image relocation table (reloc section) to ensure integrity and compatibility of the rebase operation. 3.After rebase, automatically update FFS checksum and FV header information to ensure correct data structure. 4.Support recursive processing for nested FVs, ensuring all related FFS files' PE/TE images are properly rebased and reloc tables are completed. 5.Use table-driven architecture for relocation types, making it easier to extend to more platforms. 6.Improve error handling and logging for better robustness and maintainability. Please attention, only IA32 and X64 PE/COFF image are supported now. For other Arch, will support it after testing. Signed-off-by: Yuwei Chen <yuwei.chen@intel.com>
572 lines
29 KiB
Python
572 lines
29 KiB
Python
## @file
|
|
# This file is used to define the BIOS Tree Node.
|
|
#
|
|
# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
|
|
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
##
|
|
from FirmwareStorageFormat.UPLHeader import *
|
|
from FirmwareStorageFormat.FvHeader import *
|
|
from FirmwareStorageFormat.FfsFileHeader import *
|
|
from FirmwareStorageFormat.SectionHeader import *
|
|
from FirmwareStorageFormat.PECOFFHeader import *
|
|
from FirmwareStorageFormat.Common import *
|
|
from utils.FmmtLogger import FmmtLogger as logger
|
|
import uuid
|
|
|
|
SectionHeaderType = {
|
|
0x01:'EFI_COMPRESSION_SECTION',
|
|
0x02:'EFI_GUID_DEFINED_SECTION',
|
|
0x03:'EFI_SECTION_DISPOSABLE',
|
|
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_FREEFORM_SUBTYPE_GUID_SECTION',
|
|
0x19:'EFI_SECTION_RAW',
|
|
0x1B:'EFI_SECTION_PEI_DEPEX',
|
|
0x1C:'EFI_SECTION_MM_DEPEX'
|
|
}
|
|
HeaderType = [0x01, 0x02, 0x14, 0x15, 0x18]
|
|
|
|
class BinaryNode:
|
|
def __init__(self, name: str) -> None:
|
|
self.Size = 0
|
|
self.Name = "BINARY" + str(name)
|
|
self.HOffset = 0
|
|
self.Data = b''
|
|
|
|
class ElfNode:
|
|
def __init__(self, buffer: bytes) -> None:
|
|
self.Header = ELF_HEADER32.from_buffer_copy(buffer)
|
|
if self.Header.ELF_Identification[0:4] != b'\x7fELF':
|
|
logger.error('Invalid Elf Header! Elf Identification {} is not ".ELF".'.format(self.Header.ELF_Identification))
|
|
raise Exception("Process Failed: Invalid ELF Header Identification!")
|
|
self.Class = self.Header.ELF_Identification[4]
|
|
if self.Class == 0x02:
|
|
self.Header = ELF_HEADER64.from_buffer_copy(buffer)
|
|
elif self.Class != 0x01:
|
|
logger.error('Invalid Elf Class! Elf Class {} is not 0x01 or 0x02.'.format(self.Class))
|
|
raise Exception("Process Failed: Invalid ELF Class!")
|
|
|
|
self.ProList = []
|
|
self.SecList = []
|
|
self.UpldInfoSection = None
|
|
self.UpldInfo = None
|
|
self.UpldBuffer = b''
|
|
self.Name = "ELF"
|
|
self.HeaderLength = len(struct2stream(self.Header))
|
|
self.HOffset = 0
|
|
self.DOffset = 0
|
|
self.ROffset = 0
|
|
self.Data = b''
|
|
self.PadData = b''
|
|
self.Upld_Info_Align = False
|
|
|
|
def GetProgramList(self, buffer: bytes) -> None:
|
|
for i in range(self.Header.ELF_PHNum):
|
|
if self.Class == 0x01:
|
|
ElfProgramHeader = ELF_PROGRAM_HEADER32.from_buffer_copy(buffer[i*self.Header.ELF_PHEntSize:])
|
|
elif self.Class == 0x02:
|
|
ElfProgramHeader = ELF_PROGRAM_HEADER64.from_buffer_copy(buffer[i*self.Header.ELF_PHEntSize:])
|
|
self.ProList.append(ElfProgramHeader)
|
|
|
|
def GetSectionList(self, buffer: bytes) -> None:
|
|
for i in range(self.Header.ELF_SHNum):
|
|
if self.Class == 0x01:
|
|
ElfSectionHeader = ELF_SECTION_HEADER32.from_buffer_copy(buffer[i*self.Header.ELF_SHEntSize:])
|
|
elif self.Class == 0x02:
|
|
ElfSectionHeader = ELF_SECTION_HEADER64.from_buffer_copy(buffer[i*self.Header.ELF_SHEntSize:])
|
|
self.SecList.append(ElfSectionHeader)
|
|
|
|
def FindUPLDSection(self, buffer: bytes) -> None:
|
|
for item in self.SecList:
|
|
if buffer[item.SH_Offset:item.SH_Offset+4] == b'PLDH' or buffer[item.SH_Offset:item.SH_Offset+4] == b'UPLD':
|
|
self.UpldInfoSection = item
|
|
self.UpldInfo = UNIVERSAL_PAYLOAD_INFO.from_buffer_copy(buffer[item.SH_Offset:item.SH_Offset+item.SH_Size])
|
|
self.UpldBuffer = struct2stream(self.UpldInfo)
|
|
if (self.UpldInfoSection.SH_Offset) % 4 == 0:
|
|
# if (self.UpldInfoSection.SH_Offset - self.Header.ELF_Entry) % 4 == 0:
|
|
self.Upld_Info_Align = True
|
|
|
|
class FvNode:
|
|
def __init__(self, name, buffer: bytes) -> None:
|
|
self.Header = EFI_FIRMWARE_VOLUME_HEADER.from_buffer_copy(buffer)
|
|
Map_num = (self.Header.HeaderLength - 56)//8
|
|
self.Header = Refine_FV_Header(Map_num).from_buffer_copy(buffer)
|
|
self.FvId = "FV" + str(name)
|
|
self.Name = "FV" + str(name)
|
|
if self.Header.ExtHeaderOffset:
|
|
self.ExtHeader = EFI_FIRMWARE_VOLUME_EXT_HEADER.from_buffer_copy(buffer[self.Header.ExtHeaderOffset:])
|
|
self.Name = uuid.UUID(bytes_le=struct2stream(self.ExtHeader.FvName))
|
|
self.ExtEntryOffset = self.Header.ExtHeaderOffset + 20
|
|
if self.ExtHeader.ExtHeaderSize != 20:
|
|
self.ExtEntryExist = 1
|
|
self.ExtEntry = EFI_FIRMWARE_VOLUME_EXT_ENTRY.from_buffer_copy(buffer[self.ExtEntryOffset:])
|
|
self.ExtTypeExist = 1
|
|
if self.ExtEntry.ExtEntryType == 0x01:
|
|
nums = (self.ExtEntry.ExtEntrySize - 8) // 16
|
|
self.ExtEntry = Refine_FV_EXT_ENTRY_OEM_TYPE_Header(nums).from_buffer_copy(buffer[self.ExtEntryOffset:])
|
|
elif self.ExtEntry.ExtEntryType == 0x02:
|
|
nums = self.ExtEntry.ExtEntrySize - 20
|
|
self.ExtEntry = Refine_FV_EXT_ENTRY_GUID_TYPE_Header(nums).from_buffer_copy(buffer[self.ExtEntryOffset:])
|
|
elif self.ExtEntry.ExtEntryType == 0x03:
|
|
self.ExtEntry = EFI_FIRMWARE_VOLUME_EXT_ENTRY_USED_SIZE_TYPE.from_buffer_copy(buffer[self.ExtEntryOffset:])
|
|
else:
|
|
self.ExtTypeExist = 0
|
|
else:
|
|
self.ExtEntryExist = 0
|
|
self.Size = self.Header.FvLength
|
|
self.HeaderLength = self.Header.HeaderLength
|
|
self.HOffset = 0
|
|
self.DOffset = 0
|
|
self.ROffset = 0
|
|
self.Data = b''
|
|
if self.Header.Signature != 1213613663:
|
|
logger.error('Invalid Fv Header! Fv {} signature {} is not "_FVH".'.format(struct2stream(self.Header), self.Header.Signature))
|
|
raise Exception("Process Failed: Fv Header Signature!")
|
|
self.PadData = b''
|
|
self.Free_Space = 0
|
|
self.ModCheckSum()
|
|
|
|
def ModCheckSum(self) -> None:
|
|
# Fv Header Sums to 0.
|
|
Header = struct2stream(self.Header)[::-1]
|
|
Size = self.HeaderLength // 2
|
|
Sum = 0
|
|
for i in range(Size):
|
|
Sum += int(Header[i*2: i*2 + 2].hex(), 16)
|
|
if Sum & 0xffff:
|
|
self.Header.Checksum = 0x10000 - (Sum - self.Header.Checksum) % 0x10000
|
|
|
|
def ModFvExt(self) -> None:
|
|
# If used space changes and self.ExtEntry.UsedSize exists, self.ExtEntry.UsedSize need to be changed.
|
|
if self.Header.ExtHeaderOffset and self.ExtEntryExist and self.ExtTypeExist and self.ExtEntry.Hdr.ExtEntryType == 0x03:
|
|
self.ExtEntry.UsedSize = self.Header.FvLength - self.Free_Space
|
|
|
|
def ModFvSize(self) -> None:
|
|
# If Fv Size changed, self.Header.FvLength and self.Header.BlockMap[i].NumBlocks need to be changed.
|
|
BlockMapNum = len(self.Header.BlockMap)
|
|
for i in range(BlockMapNum):
|
|
if self.Header.BlockMap[i].Length:
|
|
self.Header.BlockMap[i].NumBlocks = self.Header.FvLength // self.Header.BlockMap[i].Length
|
|
|
|
def ModExtHeaderData(self) -> None:
|
|
if self.Header.ExtHeaderOffset:
|
|
ExtHeaderData = struct2stream(self.ExtHeader)
|
|
ExtHeaderDataOffset = self.Header.ExtHeaderOffset - self.HeaderLength
|
|
self.Data = self.Data[:ExtHeaderDataOffset] + ExtHeaderData + self.Data[ExtHeaderDataOffset+20:]
|
|
if self.Header.ExtHeaderOffset and self.ExtEntryExist:
|
|
ExtHeaderEntryData = struct2stream(self.ExtEntry)
|
|
ExtHeaderEntryDataOffset = self.Header.ExtHeaderOffset + 20 - self.HeaderLength
|
|
self.Data = self.Data[:ExtHeaderEntryDataOffset] + ExtHeaderEntryData + self.Data[ExtHeaderEntryDataOffset+len(ExtHeaderEntryData):]
|
|
|
|
class FfsNode:
|
|
def __init__(self, buffer: bytes) -> None:
|
|
self.Header = EFI_FFS_FILE_HEADER.from_buffer_copy(buffer)
|
|
# self.Attributes = unpack("<B", buffer[21:22])[0]
|
|
if self.Header.FFS_FILE_SIZE != 0 and self.Header.Attributes != 0xff and self.Header.Attributes & 0x01 == 1:
|
|
logger.error('Error Ffs Header! Ffs {} Header Size and Attributes is not matched!'.format(uuid.UUID(bytes_le=struct2stream(self.Header.Name))))
|
|
raise Exception("Process Failed: Error Ffs Header!")
|
|
if self.Header.FFS_FILE_SIZE == 0 and self.Header.Attributes & 0x01 == 1:
|
|
self.Header = EFI_FFS_FILE_HEADER2.from_buffer_copy(buffer)
|
|
self.Name = uuid.UUID(bytes_le=struct2stream(self.Header.Name))
|
|
self.UiName = b''
|
|
self.Version = b''
|
|
self.Size = self.Header.FFS_FILE_SIZE
|
|
self.HeaderLength = self.Header.HeaderLength
|
|
self.HOffset = 0
|
|
self.DOffset = 0
|
|
self.ROffset = 0
|
|
self.Data = b''
|
|
self.PadData = b''
|
|
self.SectionMaxAlignment = SECTION_COMMON_ALIGNMENT # 4-align
|
|
self.PeCoffSecIndex = None
|
|
self.IsFsp = False
|
|
self.IsVtf = False
|
|
self.IfFsp()
|
|
self.IfVtf()
|
|
|
|
def ModCheckSum(self) -> None:
|
|
HeaderData = struct2stream(self.Header)
|
|
HeaderSum = 0
|
|
for item in HeaderData:
|
|
HeaderSum += item
|
|
HeaderSum -= self.Header.State
|
|
HeaderSum -= self.Header.IntegrityCheck.Checksum.File
|
|
if HeaderSum & 0xff:
|
|
Header = self.Header.IntegrityCheck.Checksum.Header + 0x100 - HeaderSum % 0x100
|
|
self.Header.IntegrityCheck.Checksum.Header = Header % 0x100
|
|
|
|
def IfFsp(self) -> None:
|
|
if self.Name == EFI_FSP_GUID:
|
|
self.IsFsp = True
|
|
|
|
def IfVtf(self) -> None:
|
|
if self.Name == EFI_FFS_VOLUME_TOP_FILE_GUID:
|
|
self.IsVtf = True
|
|
|
|
class SectionNode:
|
|
def __init__(self, buffer: bytes) -> None:
|
|
if buffer[0:3] != b'\xff\xff\xff':
|
|
self.Header = EFI_COMMON_SECTION_HEADER.from_buffer_copy(buffer)
|
|
else:
|
|
self.Header = EFI_COMMON_SECTION_HEADER2.from_buffer_copy(buffer)
|
|
if self.Header.Type in SectionHeaderType:
|
|
self.Name = SectionHeaderType[self.Header.Type]
|
|
elif self.Header.Type == 0:
|
|
self.Name = "EFI_SECTION_ALL"
|
|
else:
|
|
self.Name = "SECTION"
|
|
self.IsPadSection = False
|
|
self.IsUiSection = False
|
|
self.IsVerSection = False
|
|
if self.Header.Type in HeaderType:
|
|
self.ExtHeader = self.GetExtHeader(self.Header.Type, buffer[self.Header.Common_Header_Size():], (self.Header.SECTION_SIZE-self.Header.Common_Header_Size()))
|
|
self.HeaderLength = self.Header.Common_Header_Size() + self.ExtHeader.ExtHeaderSize()
|
|
else:
|
|
self.ExtHeader = None
|
|
self.HeaderLength = self.Header.Common_Header_Size()
|
|
self.Size = self.Header.SECTION_SIZE
|
|
self.Type = self.Header.Type
|
|
self.HOffset = 0
|
|
self.DOffset = 0
|
|
self.ROffset = 0
|
|
self.Data = b''
|
|
self.OriData = b''
|
|
self.OriHeader = b''
|
|
self.PadData = b''
|
|
self.IsPadSection = False
|
|
self.SectionMaxAlignment = SECTION_COMMON_ALIGNMENT # 4-align
|
|
|
|
def GetExtHeader(self, Type: int, buffer: bytes, nums: int=0) -> None:
|
|
if Type == 0x01:
|
|
return EFI_COMPRESSION_SECTION.from_buffer_copy(buffer)
|
|
elif Type == 0x02:
|
|
return EFI_GUID_DEFINED_SECTION.from_buffer_copy(buffer)
|
|
elif Type == 0x14:
|
|
self.IsVerSection = True
|
|
return Get_VERSION_Header((nums - 2)//2).from_buffer_copy(buffer)
|
|
elif Type == 0x15:
|
|
self.IsUiSection = True
|
|
return Get_USER_INTERFACE_Header(nums//2).from_buffer_copy(buffer)
|
|
elif Type == 0x18:
|
|
return EFI_FREEFORM_SUBTYPE_GUID_SECTION.from_buffer_copy(buffer)
|
|
|
|
class PeCoffNode:
|
|
def __init__(self, buffer: bytes, offset: int, size: int = 0) -> None:
|
|
self.Name = 'PeCoff'
|
|
self.offset = offset
|
|
self.Size = size
|
|
self.OriData = buffer
|
|
self.Data = buffer
|
|
self.IsTeImage = True
|
|
self.RelocationsStripped = True
|
|
self.PeCoffHeaderOffset = 0
|
|
self.ImageAddress = 0
|
|
self.DestinationAddress = 0
|
|
self.DebugDirectoryEntryVirtualAddress = 0
|
|
self.PeHeader = None
|
|
self.TeHeader = None
|
|
self.Machine = None
|
|
self.ImageType = None
|
|
self.OptionalHeader = None
|
|
self.BlkHeaderOffset = 0
|
|
self.BlkHeader = None
|
|
self.RelocationsFieldSize = 0
|
|
self.RelocationsData = None
|
|
self.RelocList = []
|
|
self.SizeOfImage = 0
|
|
self.HOffset = self.offset
|
|
self.DOffset = 0
|
|
self.IfRebase = True
|
|
|
|
self.TeHeader = EFI_TE_IMAGE_HEADER.from_buffer_copy(self.Data)
|
|
if self.TeHeader.Signature == EFI_IMAGE_DOS_SIGNATURE:
|
|
self.IsTeImage = False
|
|
self.TeHeader = None
|
|
self.DOffset = 0
|
|
self.DosHeader = EFI_IMAGE_DOS_HEADER.from_buffer_copy(self.Data)
|
|
if self.DosHeader.e_magic == EFI_IMAGE_DOS_SIGNATURE:
|
|
self.PeCoffHeaderOffset = self.DosHeader.e_lfanew #0xC0
|
|
self.PeHeader = EFI_IMAGE_OPTIONAL_HEADER_UNION.from_buffer_copy(self.Data[self.PeCoffHeaderOffset:])
|
|
if self.PeHeader.Pe32.Signature != EFI_IMAGE_NT_SIGNATURE:
|
|
self.TeHeader = self.PeHeader.Te
|
|
if self.TeHeader.Signature != EFI_TE_IMAGE_HEADER_SIGNATURE:
|
|
logger.error('Invalid Te Header! Te signature {} is not "VZ".'.format(self.TeHeader.Signature))
|
|
raise Exception('Not support TeHeader which signature is not "VZ"!')
|
|
self.IsTeImage = True
|
|
self.PeCoffLoaderCheckImageType()
|
|
self.PeCoffRebaseFlag()
|
|
if self.IfRebase:
|
|
self.PeCoffParseReloc()
|
|
|
|
def HasRelocTable(self) -> bool:
|
|
"""
|
|
Check if the PE/COFF image has a valid relocation table.
|
|
Returns True if the relocation table exists and is non-empty, False otherwise.
|
|
"""
|
|
# For TE image
|
|
if self.IsTeImage:
|
|
# DataDirectory[0] is Base Relocation Table
|
|
if hasattr(self.TeHeader, 'DataDirectory') and self.TeHeader.DataDirectory[0].Size > 0:
|
|
return True
|
|
return False
|
|
# For PE image
|
|
if self.PeHeader:
|
|
if hasattr(self.PeHeader.Pe32, 'OptionalHeader'):
|
|
dir_entry = self.PeHeader.Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]
|
|
if dir_entry.Size > 0:
|
|
return True
|
|
if hasattr(self.PeHeader, 'Pe32Plus') and hasattr(self.PeHeader.Pe32Plus, 'OptionalHeader'):
|
|
dir_entry = self.PeHeader.Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]
|
|
if dir_entry.Size > 0:
|
|
return True
|
|
return False
|
|
|
|
def FillPeReloc(self) -> bool:
|
|
"""
|
|
Supplement the PE/COFF relocation table if missing or incomplete.
|
|
Returns True if the table was supplemented, False otherwise.
|
|
"""
|
|
from ctypes import sizeof, c_uint16, c_uint32
|
|
from FirmwareStorageFormat.PECOFFHeader import EFI_IMAGE_BASE_RELOCATION, EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC, EFI_IMAGE_SIZEOF_BASE_RELOCATION
|
|
|
|
# If already has a reloc table, do nothing
|
|
if self.HasRelocTable():
|
|
return False
|
|
|
|
# For TE image
|
|
if self.IsTeImage and self.TeHeader:
|
|
if hasattr(self.TeHeader, 'DataDirectory') and self.TeHeader.DataDirectory[0].Size == 0:
|
|
# Check if Data is valid
|
|
if not isinstance(self.Data, (bytes, bytearray)):
|
|
logger.error('TE image Data is not bytes/bytearray')
|
|
return False
|
|
# Construct the minimal valid reloc block
|
|
reloc = EFI_IMAGE_BASE_RELOCATION()
|
|
reloc.VirtualAddress = 0
|
|
reloc.SizeOfBlock = EFI_IMAGE_SIZEOF_BASE_RELOCATION + 2 # header+1个WORD
|
|
reloc_block = bytes(reloc) + (c_uint16(0).value).to_bytes(2, 'little')
|
|
# Boundary check: DataDirectory[0].VirtualAddress must not overflow
|
|
va = len(self.Data)
|
|
if va > 0xFFFFFFFF:
|
|
logger.error('Reloc VA overflow for TE image')
|
|
return False
|
|
self.TeHeader.DataDirectory[0].VirtualAddress = va
|
|
self.TeHeader.DataDirectory[0].Size = len(reloc_block)
|
|
self.Data += reloc_block
|
|
return True
|
|
return False
|
|
|
|
# For PE image
|
|
if self.PeHeader:
|
|
# 32-bit PE
|
|
if hasattr(self.PeHeader.Pe32, 'OptionalHeader'):
|
|
opt = self.PeHeader.Pe32.OptionalHeader
|
|
# Check NumberOfRvaAndSizes
|
|
if hasattr(opt, 'NumberOfRvaAndSizes') and opt.NumberOfRvaAndSizes > EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC:
|
|
dir_entry = opt.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]
|
|
if dir_entry.Size == 0:
|
|
# Check if Data is valid
|
|
if not isinstance(self.Data, (bytes, bytearray)):
|
|
logger.error('PE image Data is not bytes/bytearray')
|
|
return False
|
|
reloc = EFI_IMAGE_BASE_RELOCATION()
|
|
reloc.VirtualAddress = 0
|
|
reloc.SizeOfBlock = EFI_IMAGE_SIZEOF_BASE_RELOCATION + 2
|
|
reloc_block = bytes(reloc) + (c_uint16(0).value).to_bytes(2, 'little')
|
|
va = len(self.Data)
|
|
if va > 0xFFFFFFFF:
|
|
logger.error('Reloc VA overflow for PE32')
|
|
return False
|
|
dir_entry.VirtualAddress = va
|
|
dir_entry.Size = len(reloc_block)
|
|
self.Data += reloc_block
|
|
return True
|
|
# 64-bit PE
|
|
if hasattr(self.PeHeader, 'Pe32Plus') and hasattr(self.PeHeader.Pe32Plus, 'OptionalHeader'):
|
|
opt = self.PeHeader.Pe32Plus.OptionalHeader
|
|
if hasattr(opt, 'NumberOfRvaAndSizes') and opt.NumberOfRvaAndSizes > EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC:
|
|
dir_entry = opt.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]
|
|
if dir_entry.Size == 0:
|
|
if not isinstance(self.Data, (bytes, bytearray)):
|
|
logger.error('PE+ image Data is not bytes/bytearray')
|
|
return False
|
|
reloc = EFI_IMAGE_BASE_RELOCATION()
|
|
reloc.VirtualAddress = 0
|
|
reloc.SizeOfBlock = EFI_IMAGE_SIZEOF_BASE_RELOCATION + 2
|
|
reloc_block = bytes(reloc) + (c_uint16(0).value).to_bytes(2, 'little')
|
|
va = len(self.Data)
|
|
if va > 0xFFFFFFFF:
|
|
logger.error('Reloc VA overflow for PE32+')
|
|
return False
|
|
dir_entry.VirtualAddress = va
|
|
dir_entry.Size = len(reloc_block)
|
|
self.Data += reloc_block
|
|
return True
|
|
return False
|
|
|
|
def PeCoffLoaderCheckImageType(self) -> None:
|
|
MachineTypeList = [EFI_IMAGE_FILE_MACHINE_I386, EFI_IMAGE_FILE_MACHINE_EBC, EFI_IMAGE_FILE_MACHINE_X64, EFI_IMAGE_FILE_MACHINE_ARMT, EFI_IMAGE_FILE_MACHINE_ARM64, EFI_IMAGE_FILE_MACHINE_RISCV64]
|
|
ImageTypeList = [EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION, EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER, EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER, EFI_IMAGE_SUBSYSTEM_SAL_RUNTIME_DRIVER]
|
|
# Check Machine Type
|
|
if self.IsTeImage:
|
|
self.Machine = self.TeHeader.Machine
|
|
else:
|
|
self.Machine = self.PeHeader.Pe32.FileHeader.Machine
|
|
if self.Machine not in MachineTypeList:
|
|
if self.Machine == EFI_IMAGE_FILE_MACHINE_ARM:
|
|
self.Machine = EFI_IMAGE_FILE_MACHINE_ARMT
|
|
if self.IsTeImage:
|
|
self.TeHeader.Machine = self.Machine
|
|
else:
|
|
self.PeHeader.Pe32.FileHeader.Machine = self.Machine
|
|
else:
|
|
logger.error('The Machine Type {} is not supported!'.format(self.Machine))
|
|
raise Exception('The Machine Type {} is not supported!'.format(self.Machine))
|
|
|
|
# Check Image Type
|
|
if self.IsTeImage:
|
|
self.ImageType = self.TeHeader.Subsystem
|
|
else:
|
|
self.ImageType = self.PeHeader.Pe32.OptionalHeader.Subsystem
|
|
if self.ImageType not in ImageTypeList:
|
|
logger.error('The Image Type {} is not supported!'.format(self.ImageType))
|
|
raise Exception('The Image Type {} is not supported!'.format(self.ImageType))
|
|
|
|
def PeCoffRebaseFlag(self):
|
|
if self.IsTeImage:
|
|
pass
|
|
else:
|
|
if self.PeHeader.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC:
|
|
self.SizeOfImage = self.PeHeader.Pe32.OptionalHeader.SizeOfImage
|
|
else:
|
|
self.SizeOfImage = self.PeHeader.Pe32Plus.OptionalHeader.SizeOfImage
|
|
if self.SizeOfImage != len(self.Data):
|
|
self.IfRebase = False
|
|
|
|
def PeCoffParseReloc(self) -> None:
|
|
if self.IsTeImage:
|
|
self.ImageAddress = self.TeHeader.ImageBase + self.TeHeader.StrippedSize - EFI_TE_IMAGE_HEADER_SIZE
|
|
self.BlkHeaderOffset = self.offset + EFI_TE_IMAGE_HEADER_SIZE - self.TeHeader.StrippedSize +self.TeHeader.DataDirectory[0].VirtualAddress
|
|
self.BlkSize = self.TeHeader.DataDirectory[0].Size
|
|
else:
|
|
if self.PeHeader.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC:
|
|
self.ImageAddress = self.PeHeader.Pe32.OptionalHeader.ImageBase
|
|
self.BlkHeaderOffset = self.offset + self.PeHeader.Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress
|
|
self.BlkSize = self.PeHeader.Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].Size
|
|
else:
|
|
self.ImageAddress = self.PeHeader.Pe32Plus.OptionalHeader.ImageBase
|
|
self.BlkHeaderOffset = self.offset + self.PeHeader.Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress
|
|
self.BlkSize = self.PeHeader.Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].Size
|
|
CurOff = self.BlkHeaderOffset
|
|
while CurOff < self.BlkHeaderOffset + self.BlkSize:
|
|
if CurOff % 4:
|
|
CurOff += 4 - CurOff % 4
|
|
self.BlkHeader = EFI_BLK_HEADER.from_buffer_copy(self.Data[CurOff-self.offset:])
|
|
self.RelocationsFieldSize = self.BlkHeader.BlockSize - EFI_BLK_HEADER_SIZE
|
|
self.RelocationsData = (c_uint16 * int(self.RelocationsFieldSize/2)).from_buffer_copy(self.Data[CurOff - self.offset + EFI_BLK_HEADER_SIZE:CurOff - self.offset + EFI_BLK_HEADER_SIZE + self.RelocationsFieldSize])
|
|
for EachDataField in self.RelocationsData:
|
|
# Rtype [15:12] Roffset [11:0]
|
|
EachRType = EachDataField >> 12
|
|
EachROff = EachDataField & 0xfff
|
|
if EachRType == 0: # IMAGE_REL_BASED_ABSOLUTE
|
|
continue
|
|
if ((EachRType != 3) and (EachRType != 10)): # IMAGE_REL_BASED_HIGHLOW and IMAGE_REL_BASED_DIR64
|
|
logger.error("ERROR: Unsupported relocation type %d!" % EachRType)
|
|
raise Exception("ERROR: Unsupported relocation type %d!" % EachRType)
|
|
if self.TeHeader:
|
|
TarROff = self.offset + self.BlkHeader.PageRVA + EachROff + EFI_TE_IMAGE_HEADER_SIZE - self.TeHeader.StrippedSize
|
|
else:
|
|
TarROff = self.offset + self.BlkHeader.PageRVA + EachROff
|
|
self.RelocList.append((EachRType, TarROff))
|
|
CurOff += self.BlkHeader.BlockSize
|
|
|
|
def PeCoffRebase(self, DeltaSize=0, CalcuFlag=0, base_address=None, xip_offset=0) -> None:
|
|
"""
|
|
Support absolute base address (base_address) and XIP offset (xip_offset) parameters, compatible with original DeltaSize/CalcuFlag logic.
|
|
base_address has higher priority than DeltaSize/CalcuFlag.
|
|
"""
|
|
# If base_address is explicitly specified, force absolute relocation
|
|
# XIP offset can be used for future extension (if needed)
|
|
## Rebase function
|
|
# Architecture relocation type table driven
|
|
SUPPORTED_RELOC_TYPES = {
|
|
3: 4, # IMAGE_REL_BASED_HIGHLOW (x86/x64 32bit)
|
|
10: 8, # IMAGE_REL_BASED_DIR64 (x64 64bit)
|
|
# Extendable: e.g. ARM/ARM64 etc.
|
|
}
|
|
# If base_address is explicitly specified, force absolute relocation
|
|
if base_address is not None:
|
|
CalcuFlag = 1
|
|
DeltaSize = base_address
|
|
# XIP offset can be used for future extension (if needed)
|
|
if self.TeHeader:
|
|
ImageBase = self.TeHeader.ImageBase
|
|
CurOff = self.offset + EFI_TE_IMAGE_HEADER.ImageBase.offset
|
|
ImageBaseSize = EFI_TE_IMAGE_HEADER.ImageBase.size
|
|
else:
|
|
CurOff = self.offset + self.PeCoffHeaderOffset
|
|
CurOff += EFI_IMAGE_NT_HEADERS32.OptionalHeader.offset
|
|
if self.PeHeader.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC: # PE32+ image
|
|
ImageBase = EFI_IMAGE_OPTIONAL_HEADER64.ImageBase
|
|
CurOff += EFI_IMAGE_OPTIONAL_HEADER64.ImageBase.offset
|
|
ImageBaseSize = EFI_IMAGE_OPTIONAL_HEADER64.ImageBase.size
|
|
else:
|
|
ImageBase = EFI_IMAGE_OPTIONAL_HEADER32.ImageBase
|
|
CurOff += EFI_IMAGE_OPTIONAL_HEADER32.ImageBase.offset
|
|
ImageBaseSize = EFI_IMAGE_OPTIONAL_HEADER32.ImageBase.size
|
|
if CalcuFlag:
|
|
NewImageBase = DeltaSize
|
|
if self.TeHeader:
|
|
DeltaSize = DeltaSize - self.TeHeader.ImageBase
|
|
self.TeHeader.ImageBase = NewImageBase
|
|
self.ImageAddress = self.TeHeader.ImageBase + self.TeHeader.StrippedSize - EFI_TE_IMAGE_HEADER_SIZE
|
|
else:
|
|
DeltaSize = DeltaSize - self.ImageAddress
|
|
if self.PeHeader.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC:
|
|
self.PeHeader.Pe32.OptionalHeader.ImageBase = NewImageBase
|
|
self.ImageAddress = self.PeHeader.Pe32.OptionalHeader.ImageBase
|
|
else:
|
|
self.PeHeader.Pe32Plus.OptionalHeader.ImageBase = NewImageBase
|
|
self.ImageAddress = self.PeHeader.Pe32Plus.OptionalHeader.ImageBase
|
|
CurValue = NewImageBase
|
|
else:
|
|
if self.TeHeader:
|
|
self.TeHeader.ImageBase += DeltaSize
|
|
self.ImageAddress = self.TeHeader.ImageBase + self.TeHeader.StrippedSize - EFI_TE_IMAGE_HEADER_SIZE
|
|
CurValue = self.TeHeader.ImageBase
|
|
else:
|
|
if self.PeHeader.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC:
|
|
self.PeHeader.Pe32.OptionalHeader.ImageBase += DeltaSize
|
|
self.ImageAddress = self.PeHeader.Pe32.OptionalHeader.ImageBase
|
|
else:
|
|
self.PeHeader.Pe32Plus.OptionalHeader.ImageBase += DeltaSize
|
|
self.ImageAddress = self.PeHeader.Pe32Plus.OptionalHeader.ImageBase
|
|
CurValue = self.ImageAddress
|
|
self.Data = self.Data[:CurOff-self.offset] + CurValue.to_bytes(ImageBaseSize, byteorder='little',signed=False) + self.Data[CurOff-self.offset+ImageBaseSize:]
|
|
|
|
for (EachRType, TarROff) in self.RelocList:
|
|
if EachRType in SUPPORTED_RELOC_TYPES:
|
|
size = SUPPORTED_RELOC_TYPES[EachRType]
|
|
CurValue = Bytes2Val(self.Data[TarROff-self.offset:TarROff+size-self.offset])
|
|
CurValue += DeltaSize
|
|
self.Data = self.Data[:TarROff-self.offset] + CurValue.to_bytes(size, byteorder='little',signed=False) + self.Data[TarROff+size-self.offset:]
|
|
else:
|
|
logger.error(f"ERROR: Unsupported relocation type {EachRType}! (please extend SUPPORTED_RELOC_TYPES)")
|
|
raise Exception(f"ERROR: Unsupported relocation type {EachRType}! (please extend SUPPORTED_RELOC_TYPES)")
|
|
|
|
class FreeSpaceNode:
|
|
def __init__(self, buffer: bytes) -> None:
|
|
self.Name = 'Free_Space'
|
|
self.Data = buffer
|
|
self.Size = len(buffer)
|
|
self.HOffset = 0
|
|
self.DOffset = 0
|
|
self.ROffset = 0
|
|
self.PadData = b''
|