#!/usr/bin/env python3 -B # coding=utf-8 """ Portwell EFI Extract Portwell EFI Update Extractor Copyright (C) 2021-2024 Plato Mavropoulos """ import logging import os from re import Match from typing import Final from pefile import PE from biosutilities.common.compression import efi_decompress, is_efi_compressed from biosutilities.common.paths import delete_file, make_dirs, rename_file, safe_name from biosutilities.common.executables import ms_pe from biosutilities.common.patterns import PAT_MICROSOFT_MZ, PAT_PORTWELL_EFI from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility class PortwellEfiExtract(BIOSUtility): """ Portwell EFI Update Extractor """ TITLE: str = 'Portwell EFI Update Extractor' FILE_NAMES: Final[dict[int, str]] = { 0: 'Flash.efi', 1: 'Fparts.txt', 2: 'Update.nsh', 3: 'Temp.bin', 4: 'SaveDmiData.efi' } def check_format(self) -> bool: """ Check if input is Portwell EFI executable """ try: pe_buffer: bytes = self._get_portwell_pe(in_buffer=self.input_buffer)[1] except Exception as error: # pylint: disable=broad-except logging.debug('Error: Could not check if input is Portwell EFI executable: %s', error) return False # EFI images start with PE Header MZ if PAT_MICROSOFT_MZ.search(self.input_buffer[:0x2]): # Portwell EFI files start with if PAT_PORTWELL_EFI.search(pe_buffer[:0x4]): return True return False def parse_format(self) -> bool: """ Parse & Extract Portwell UEFI Unpacker """ # Initialize EFI Payload file chunks efi_files: list[bytes] = [] make_dirs(in_path=self.extract_path) pe_file, pe_data = self._get_portwell_pe(in_buffer=self.input_buffer) efi_title: str = self._get_unpacker_tag(input_buffer=self.input_buffer, pe_file=pe_file) printer(message=efi_title, padding=self.padding) # Split EFI Payload into file chunks efi_list: list[Match[bytes]] = list(PAT_PORTWELL_EFI.finditer(pe_data)) for idx, val in enumerate(efi_list): efi_bgn: int = val.end() efi_end: int = len(pe_data) if idx == len(efi_list) - 1 else efi_list[idx + 1].start() efi_files.append(pe_data[efi_bgn:efi_end]) self._parse_efi_files(extract_path=self.extract_path, efi_files=efi_files, padding=self.padding) return True @staticmethod def _get_portwell_pe(in_buffer: bytes) -> tuple: """ Get PE of Portwell EFI executable """ # Analyze EFI Portable Executable (PE) pe_file: PE | None = ms_pe(in_buffer, silent=True) # Skip EFI executable # pylint: disable=no-member pe_data: bytes = in_buffer[pe_file.OPTIONAL_HEADER.SizeOfImage:] # type: ignore return pe_file, pe_data @staticmethod def _get_unpacker_tag(input_buffer: bytes | bytearray, pe_file: PE) -> str: """ Get Portwell UEFI Unpacker tag """ unpacker_tag_txt: str = 'UEFI Unpacker' for pe_section in pe_file.sections: # Unpacker Tag, Version, Strings etc. are found in .data PE section if pe_section.Name.startswith(b'.data'): pe_data_bgn: int = pe_section.PointerToRawData pe_data_end: int = pe_data_bgn + pe_section.SizeOfRawData # Decode any valid UTF-16 .data PE section info to a parsable text buffer pe_data_txt: str = input_buffer[pe_data_bgn:pe_data_end].decode('utf-16', 'ignore') # Search .data for UEFI Unpacker tag unpacker_tag_bgn: int = pe_data_txt.find(unpacker_tag_txt) if unpacker_tag_bgn != -1: unpacker_tag_len: int = pe_data_txt[unpacker_tag_bgn:].find('=') if unpacker_tag_len != -1: unpacker_tag_end: int = unpacker_tag_bgn + unpacker_tag_len unpacker_tag_raw: str = pe_data_txt[unpacker_tag_bgn:unpacker_tag_end] # Found full UEFI Unpacker tag, store and slightly beautify the resulting text unpacker_tag_txt = unpacker_tag_raw.strip().replace(' ', ' ').replace('<', ' <') break # Found PE .data section, skip the rest return unpacker_tag_txt def _parse_efi_files(self, extract_path: str, efi_files: list[bytes], padding: int) -> None: """ Process Portwell UEFI Unpacker payload files """ for file_index, file_data in enumerate(efi_files): if file_data in (b'', b'NULL'): continue # Skip empty/unused files # Assign Name to EFI file file_name: str = self.FILE_NAMES.get(file_index, f'Unknown_{file_index}.bin') # Print EFI file name, indicate progress printer(message=f'[{file_index}] {file_name}', padding=padding + 4) if file_name.startswith('Unknown_'): printer(message=f'Note: Detected new Portwell EFI file ID {file_index}!', padding=padding + 8) # Store EFI file output path file_path: str = os.path.join(extract_path, safe_name(in_name=file_name)) # Store EFI file data to drive with open(file_path, 'wb') as out_file: out_file.write(file_data) # Attempt to detect EFI compression & decompress when applicable if is_efi_compressed(in_object=file_data): # Store temporary compressed file name file_path_temp: str = f'{file_path}.temp' # Rename initial/compressed file rename_file(in_path=file_path, in_dest=file_path_temp) # Successful decompression, delete compressed file if efi_decompress(in_path=file_path_temp, out_path=file_path, padding=padding + 8): delete_file(in_path=file_path_temp)