From ec14803065515285f622ea21fe6fe06a61ae6c82 Mon Sep 17 00:00:00 2001 From: platomav Date: Mon, 27 Dec 2021 00:46:37 +0200 Subject: [PATCH 01/52] Dell PFS Update Extractor v5.0 Dell PFS Update Extractor v5.0 is a complete refactor/re-design of the previous Dell PFS BIOS Extractor. The logic of the script has been re-written to be cleaner, smarter, faster and more robust to PFS format changes. Notable new features include: 1) Support for PFS Utilities section extraction, aside from the Firmware one 2) Support and proper extraction of Intel BIOS Guard protected Firmware 3) Support for parsing some newer PFS Information and Signature entries 4) Ability to show verbose output of extraction progress and PFS structures 5) Ability to better integrate the script to other projects via new parameters 6) Extensive code re-structuring in more modular form for future expansion --- Dell PFS BIOS Extractor/Dell_PFS_Extract.py | 943 ------------- Dell PFS Update Extractor/Dell_PFS_Extract.py | 1245 +++++++++++++++++ README.md | 28 +- 3 files changed, 1267 insertions(+), 949 deletions(-) delete mode 100644 Dell PFS BIOS Extractor/Dell_PFS_Extract.py create mode 100644 Dell PFS Update Extractor/Dell_PFS_Extract.py diff --git a/Dell PFS BIOS Extractor/Dell_PFS_Extract.py b/Dell PFS BIOS Extractor/Dell_PFS_Extract.py deleted file mode 100644 index fafd4c4..0000000 --- a/Dell PFS BIOS Extractor/Dell_PFS_Extract.py +++ /dev/null @@ -1,943 +0,0 @@ -#!/usr/bin/env python3 -#coding=utf-8 - -""" -Dell PFS Extract -Dell PFS BIOS Extractor -Copyright (C) 2019-2021 Plato Mavropoulos -Inspired from https://github.com/LongSoft/PFSExtractor-RS by Nikolaj Schlej -""" - -title = 'Dell PFS BIOS Extractor v4.9' - -import os -import re -import sys -import zlib -import lzma -import shutil -import struct -import ctypes -import argparse -import traceback - -# Set ctypes Structure types -char = ctypes.c_char -uint8_t = ctypes.c_ubyte -uint16_t = ctypes.c_ushort -uint32_t = ctypes.c_uint -uint64_t = ctypes.c_uint64 - -class PFS_HDR(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('Tag', char*8), # 0x00 - ('HeaderVersion', uint32_t), # 0x08 - ('PayloadSize', uint32_t), # 0x0C - # 0x10 - ] - - def pfs_print(self) : - print('\nPFS Header:\n') - print('Tag : %s' % self.Tag.decode('utf-8')) - print('HeaderVersion : %d' % self.HeaderVersion) - print('PayloadSize : 0x%X' % self.PayloadSize) - -class PFS_FTR(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('PayloadSize', uint32_t), # 0x00 - ('Checksum', uint32_t), # 0x04 ~CRC32 w/ Vector 0 - ('Tag', char*8), # 0x08 - # 0x10 - ] - - def pfs_print(self) : - print('\nPFS Footer:\n') - print('PayloadSize : 0x%X' % self.PayloadSize) - print('Checksum : 0x%0.8X' % self.Checksum) - print('Tag : %s' % self.Tag.decode('utf-8')) - -class PFS_ENTRY(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('GUID', uint32_t*4), # 0x00 Little Endian - ('HeaderVersion', uint32_t), # 0x10 1 - ('VersionType', uint8_t*4), # 0x14 - ('Version', uint16_t*4), # 0x18 - ('Reserved', uint64_t), # 0x20 - ('DataSize', uint32_t), # 0x28 - ('DataSigSize', uint32_t), # 0x2C - ('DataMetSize', uint32_t), # 0x30 - ('DataMetSigSize', uint32_t), # 0x34 - ('Unknown', uint32_t*4), # 0x38 - # 0x48 - ] - - def pfs_print(self) : - GUID = ''.join('%0.8X' % int.from_bytes(struct.pack('= met_info_size : - # Get Nested PFS Metadata Structure values - entry_info = get_struct(entry_metadata, 0, METADATA_INFO) - - # As Nested PFS Entry Name, we'll use the actual PFS File Name - entry_name = entry_info.FileName.decode('utf-8').strip('.exe') - - # As Nested PFS Entry Version, we'll use the actual PFS File Version - entry_version = entry_info.FileVersion.decode('utf-8') - - # Store all relevant Nested PFS Metadata/Information details - info_all.append([entry_guid, entry_name, entry_version]) - - # Re-set Nested PFS Entry Version from Metadata - entries_all[index][2] = entry_version - - # Parse each PFS Entry Data for special types (zlib or Chunks) - for index in range(len(entries_all)) : - entry_data = entries_all[index][4] # Get PFS Entry Data - entry_type = entries_all[index][3] # Get PFS Entry Type - - # Very small PFS Entry Data cannot be of special type - if len(entry_data) < pfs_header_size : continue - - # Get possible PFS Header Structure values - entry_hdr = get_struct(entry_data, 0, PFS_HDR) - - # Check for possibly zlib-compressed (0x4 Compressed Size + Compressed Data) PFS Entry Data - # The 0xE sized zlib "BIOS" section pattern (0xAA type) should be found after the Compressed Size - zlib_bios_hdr_match = zlib_bios_header.search(entry_data) - - # Check if a sub PFS Header with Payload has Chunked Entries - pfs_entry_struct, pfs_entry_size = get_pfs_entry(entry_data, pfs_header_size) # Get PFS Entry Info - chunk_info_hdr_off = pfs_header_size + pfs_entry_size # Chunk Info Header starts after PFS Header & PFS Entry - if len(entry_data[chunk_info_hdr_off:chunk_info_hdr_off + chunk_info_header_size]) == chunk_info_header_size : - chunk_info_hdr = get_struct(entry_data, chunk_info_hdr_off, CHUNK_INFO_HDR) # Get Chunk Info Header - chunk_dell_tag = chunk_info_hdr.DellTag.replace(b'\x00',b'\x20').decode('utf-8','ignore').strip() # Chunk Dell Tag - chunk_flags_size = chunk_info_hdr.FlagsSize # Size of Chunk Info Flags - chunk_payload_size = chunk_info_hdr.ChunkSize # Size of Chunk Raw Data - else : - chunk_dell_tag = 'None' # Payload does not have Chunked Entries - chunk_flags_size = 0 - - if entry_hdr.Tag == b'PFS.HDR.' and chunk_dell_tag.startswith('Dell') : - # Validate that a known sub PFS Header Version was encountered - if entry_hdr.HeaderVersion not in (1,2) : - print('\n Error: Unknown sub PFS Entry Header Version %d!' % entry_hdr.HeaderVersion) - - # Get sub PFS Footer Data after sub PFS Header Payload - chunks_footer = entry_data[pfs_header_size + entry_hdr.PayloadSize:pfs_header_size + entry_hdr.PayloadSize + pfs_footer_size] - - # Get sub PFS Footer Structure values - entry_ftr = get_struct(chunks_footer, 0, PFS_FTR) - - # Validate that a sub PFS Footer was parsed - if entry_ftr.Tag != b'PFS.FTR.' : - print('\n Error: Sub PFS Entry Footer could not be found!') - - # Validate that the sub PFS Header Payload Size matches the one at the sub PFS Footer - if entry_hdr.PayloadSize != entry_ftr.PayloadSize : - print('\n Error: Sub PFS Entry Header & Footer Payload Size mismatch!') - - # Get sub PFS Entry Structure values - pfs_chunk_entry = get_struct(entry_data, pfs_header_size, pfs_entry_struct) - - # Validate that a known sub PFS Entry Header Version was encountered - if pfs_chunk_entry.HeaderVersion not in (1,2) : - print('\n Error: Unknown sub PFS Chunk Entry Header Version %d!' % pfs_chunk_entry.HeaderVersion) - - # Validate that the sub PFS Entry Reserved field is empty - if pfs_chunk_entry.Reserved != 0 : - print('\n Error: Detected non-empty sub PFS Chunk Entry Reserved field!') - - # Validate that the Chunk Extra Info Footer End Marker is proper - chunk_info_ftr_off = chunk_info_hdr_off + chunk_info_header_size + chunk_flags_size - chunk_info_ftr = get_struct(entry_data, chunk_info_ftr_off, CHUNK_INFO_FTR) - if chunk_info_ftr.EndMarker != 0xFF : - print('\n Error: Unknown sub PFS Chunk Info Footer End Marker 0x%X!' % chunk_info_ftr.EndMarker) - - # Get sub PFS Payload Data - chunks_payload = entry_data[pfs_header_size:pfs_header_size + entry_hdr.PayloadSize] - - # Calculate the sub PFS Payload Data CRC-32 w/ Vector 0 Checksum - chunks_footer_checksum = ~zlib.crc32(chunks_payload, 0) & 0xFFFFFFFF - - # Validate sub PFS Payload Data Checksum via the sub PFS Footer - if entry_ftr.Checksum != chunks_footer_checksum : - print('\n Error: Invalid sub PFS Entry Footer Payload Checksum!') - - # Parse all sub PFS Payload Entries/Chunks - chunk_data_all = [] # Storage for each sub PFS Entry/Chunk Order + Data - chunk_entry_start = 0 # Increasing sub PFS Entry/Chunk starting offset - pfs_entry_struct, pfs_entry_size = get_pfs_entry(chunks_payload, chunk_entry_start) # Get PFS Entry Info (initial) - while len(chunks_payload[chunk_entry_start:chunk_entry_start + pfs_entry_size]) == pfs_entry_size : - # Get Next PFS Entry Info, skip at the first loop iteration because we already have it - if chunk_entry_start : pfs_entry_struct, pfs_entry_size = get_pfs_entry(chunks_payload, chunk_entry_start) - - # Get sub PFS Entry Structure values - pfs_chunk_entry = get_struct(chunks_payload, chunk_entry_start, pfs_entry_struct) - - # Validate that a known sub PFS Entry Header Version was encountered - if pfs_chunk_entry.HeaderVersion not in (1,2) : - print('\n Error: Unknown sub PFS Chunk Entry Header Version %d!' % pfs_chunk_entry.HeaderVersion) - - # Validate that the sub PFS Entry Reserved field is empty - if pfs_chunk_entry.Reserved != 0 : - print('\n Error: Detected non-empty sub PFS Chunk Entry Reserved field!') - - # Each sub PFS Payload Entry/Chunk includes some Extra Chunk Data/Information at the beginning - # The Chunk Extra Info consists of a Header (0x28), variable sized Flags & End Marker Footer (0x8) - # We need the Chunk Extra Info size so that its Data can be removed from the final Chunk Raw Data - chunk_info_hdr_off = chunk_entry_start + pfs_entry_size # Chunk Info Header starts after PFS Entry - chunk_info_hdr = get_struct(chunks_payload, chunk_info_hdr_off, CHUNK_INFO_HDR) # Get Chunk Info Header - chunk_dell_tag = chunk_info_hdr.DellTag.replace(b'\x00',b'\x20').decode('utf-8','ignore').strip() # Chunk Dell Tag - chunk_flags_size = chunk_info_hdr.FlagsSize # Size of Chunk Info Flags - chunk_payload_size = chunk_info_hdr.ChunkSize # Size of Chunk Raw Data - chunk_info_size = pfs_chunk_entry.DataSize - chunk_payload_size # Size of Chunk Info - - # Validate that the Chunk Extra Info Header Dell Tag is proper - if not chunk_dell_tag.startswith('Dell') : - print('\n Error: Unknown sub PFS Chunk Info Header Dell Tag %s!' % chunk_dell_tag) - - # Validate that the Chunk Extra Info Footer End Marker is proper - chunk_info_ftr_off = chunk_info_hdr_off + chunk_info_header_size + chunk_flags_size - chunk_info_ftr = get_struct(chunks_payload, chunk_info_ftr_off, CHUNK_INFO_FTR) - if chunk_info_ftr.EndMarker != 0xFF : - print('\n Error: Unknown sub PFS Chunk Info Footer End Marker 0x%X!' % chunk_info_ftr.EndMarker) - - # The sub PFS Payload Entries/Chunks are not in proper order by default - # We can get the Chunk Order Number from Chunk Extra Info > Flags byte 0x16 - chunk_entry_number = chunks_payload[chunk_info_hdr_off + chunk_info_header_size + 0x16] # Get Chunk Order Number - - # Get sub PFS Entry Version string via "Version" and "VersionType" fields - # This is not useful as the Version of each Chunk does not matter at all - #chunk_entry_version = get_version(pfs_chunk_entry.Version, pfs_chunk_entry.VersionType) - - # Sub PFS Entry Data starts after the sub PFS Entry Structure - chunk_entry_data_start = chunk_entry_start + pfs_entry_size - chunk_entry_data_end = chunk_entry_data_start + pfs_chunk_entry.DataSize - - # Sub PFS Entry Data Signature starts after sub PFS Entry Data - chunk_entry_data_sig_start = chunk_entry_data_end - chunk_entry_data_sig_end = chunk_entry_data_sig_start + pfs_chunk_entry.DataSigSize - - # Sub PFS Entry Metadata starts after sub PFS Entry Data Signature - chunk_entry_met_start = chunk_entry_data_sig_end - chunk_entry_met_end = chunk_entry_met_start + pfs_chunk_entry.DataMetSize - - # Sub PFS Entry Metadata Signature starts after sub PFS Entry Metadata - chunk_entry_met_sig_start = chunk_entry_met_end - chunk_entry_met_sig_end = chunk_entry_met_sig_start + pfs_chunk_entry.DataMetSigSize - - chunk_entry_data = chunks_payload[chunk_entry_data_start:chunk_entry_data_end] # Store sub PFS Entry Data - #chunk_entry_data_sig = chunks_payload[chunk_entry_data_sig_start:chunk_entry_data_sig_end] # Store sub PFS Entry Data Signature - #chunk_entry_met = chunks_payload[chunk_entry_met_start:chunk_entry_met_end] # Store sub PFS Entry Metadata - #chunk_entry_met_sig = chunks_payload[chunk_entry_met_sig_start:chunk_entry_met_sig_end] # Store sub PFS Entry Metadata Signature - - # Store each sub PFS Entry/Chunk Extra Info Size, Order Number & Raw Data - chunk_data_all.append((chunk_entry_number, chunk_entry_data, chunk_info_size)) - - chunk_entry_start = chunk_entry_met_sig_end # Next sub PFS Entry/Chunk starts after sub PFS Entry Metadata Signature - - chunk_data_all.sort() # Sort all sub PFS Entries/Chunks based on their Order Number - - entry_data = b'' # Initialize new PFS Entry Data - for chunk in chunk_data_all : # Merge all sub PFS Chunks into the final new PFS Entry Data - entry_data += chunk[1][chunk[2]:] # Skip the sub PFS Chunk Extra Info when merging - - entry_type = 'CHUNKS' # Re-set PFS Entry Type from OTHER to CHUNKS, in case such info is needed afterwards - - # Check if the PFS Entry Data are zlib-compressed in a "BIOS" pattern with 0xAA type. - # A zlib-compressed PFS Entry Data contains a full PFS structure, like the main Dell PFS BIOS image. - elif zlib_bios_hdr_match : - # Store the compressed zlib stream start offset - compressed_start = zlib_bios_hdr_match.start() + 0xC - - # Store the "BIOS" section header start offset - header_start = zlib_bios_hdr_match.start() - 0x4 - - # Store the "BIOS" section header contents (16 bytes) - header_data = entry_data[header_start:compressed_start] - - # Check if the "BIOS" section header Checksum XOR 8 is valid - if chk_xor_8(header_data[:0xF], 0) != header_data[0xF] : - print('\n Error: Dell sub-PFS BIOS section data is corrupted!') - - # Store the compressed zlib stream size from the header contents - compressed_size_hdr = int.from_bytes(header_data[:0x4], 'little') - - # Store the compressed zlib stream end offset - compressed_end = compressed_start + compressed_size_hdr - - # Store the compressed zlib stream contents - compressed_data = entry_data[compressed_start:compressed_end] - - # Check if the compressed zlib stream is complete, based on header - if len(compressed_data) != compressed_size_hdr : - print('\n Error: Dell sub-PFS BIOS section data is corrupted!') - - # Store the "BIOS" section footer contents (16 bytes) - footer_data = entry_data[compressed_end:compressed_end + 0x10] - - # Check if the "BIOS" section footer Checksum XOR 8 is valid - if chk_xor_8(footer_data[:0xF], 0) != footer_data[0xF] : - print('\n Error: Dell sub-PFS BIOS section data is corrupted!') - - # Search input PFS Entry Data for zlib "BIOS" section footer - zlib_bios_ftr_match = zlib_bios_footer.search(footer_data) - - # Check if "BIOS" section footer was found in the PFS Entry Data - if not zlib_bios_ftr_match : - print('\n Error: Dell sub-PFS BIOS section data is corrupted!') - - # Store the compressed zlib stream size from the footer contents - compressed_size_ftr = int.from_bytes(footer_data[:0x4], 'little') - - # Check if the compressed zlib stream is complete, based on footer - if compressed_size_ftr != compressed_size_hdr : - print('\n Error: Dell sub-PFS BIOS section data is corrupted!') - - # Decompress "BIOS" section payload, starting from zlib header start of 0x789C - entry_data = zlib.decompress(compressed_data) - - entry_type = 'ZLIB' # Re-set PFS Entry Type from OTHER to ZLIB, in case such info is needed afterwards - - pfs_count += 1 # Increase the count/index of parsed main PFS structures by one - - # Get the Name of the zlib-compressed full PFS structure via the already stored PFS Information - # The zlib-compressed full PFS structure(s) are used to contain multiple BIOS (CombineBiosNameX) - # When zlib-compressed full PFS structure(s) exist within the main/first full PFS structure, - # its PFS Information should contain their names (CombineBiosNameX). Since the main/first - # full PFS structure has count/index 1, the rest start at 2+ and thus, their PFS Information - # names can be retrieved in order by subtracting 2 from the main/first PFS Information values - sub_pfs_name = ' %s v%s' % (info_all[pfs_count - 2][1], info_all[pfs_count - 2][2]) if info_all else ' UNKNOWN' - - # Recursively call the Dell PFS.HDR. Extractor function for each zlib-compressed full PFS structure - pfs_extract(entry_data, pfs_count, sub_pfs_name, pfs_count) # For recursive calls, pfs_index = pfs_count - - entries_all[index][4] = entry_data # Adjust PFS Entry Data after merging Chunks or zlib-decompressing - entries_all[index][3] = entry_type # Adjust PFS Entry Type from OTHER to either CHUNKS or ZLIB - - # Name & Store each PFS Entry/Component Data, Data Signature, Metadata, Metadata Signature - for entry_index in range(len(entries_all)) : - file_index = entries_all[entry_index][0] - file_guid = entries_all[entry_index][1] - file_version = entries_all[entry_index][2] - file_type = entries_all[entry_index][3] - file_data = entries_all[entry_index][4] - file_data_sig = entries_all[entry_index][5] - file_meta = entries_all[entry_index][6] - file_meta_sig = entries_all[entry_index][7] - - # Give Names to special PFS Entries, not covered by PFS Information - if file_type == 'MODEL_INFO' : - file_name = 'Model Information' - elif file_type == 'PFS_INFO' : - file_name = 'PFS Information' - if not is_advanced : continue # Don't store PFS Information in non-advanced user mode - else : - file_name = '' - - # Most PFS Entry Names & Versions are found at PFS Information via their GUID - # Version can be found at PFS_ENTRY but prefer PFS Information when possible - for info_index in range(len(info_all)) : - info_guid = info_all[info_index][0] - info_name = info_all[info_index][1] - info_version = info_all[info_index][2] - - # Give proper Name & Version info if Entry/Information GUIDs match - if info_guid == file_guid : - file_name = info_name - file_version = info_version - - info_all[info_index][0] = 'USED' # PFS with zlib-compressed full PFS (multiple BIOS) use the same GUID - break # Break at 1st Name match to not rename from next zlib-compressed full PFS with the same GUID - - data_ext = '.data.bin' if is_advanced else '.bin' # Simpler Data Extension for non-advanced users - meta_ext = '.meta.bin' if is_advanced else '.bin' # Simpler Metadata Extension for non-advanced users - full_name = '%d%s -- %d %s v%s' % (pfs_index, pfs_name, file_index, file_name, file_version) # Full Entry Name - safe_name = re.sub(r'[\\/*?:"<>|]', '_', full_name) # Replace common Windows reserved/illegal filename characters - - is_zlib = file_type == 'ZLIB' # Determine if PFS Entry Data was zlib-compressed - - # For both advanced & non-advanced users, the goal is to store final/usable files only - # so empty or intermediate files such as sub-PFS, PFS w/ Chunks or zlib-PFS are skipped - if file_data and not is_zlib : # Store Data (advanced & non-advanced users) - # Some Data may be Text or XML files with useful information for non-advanced users - is_text, final_data, file_ext, write_mode = bin_is_text(file_data, file_type, False, is_advanced) - - final_name = '%s%s' % (safe_name, data_ext[:-4] + file_ext if is_text else data_ext) - final_path = os.path.join(output_path, final_name) - - with open(final_path, write_mode) as o : o.write(final_data) # Write final Data - - if file_data_sig and is_advanced : # Store Data Signature (advanced users only) - final_name = '%s.data.sig' % safe_name - final_path = os.path.join(output_path, final_name) - - with open(final_path, 'wb') as o : o.write(file_data_sig) # Write final Data Signature - - # Main/First PFS CombineBiosNameX Metadata files must be kept for accurate Model Information - # All users should check these files in order to choose the correct CombineBiosNameX modules - if file_meta and (is_zlib or is_advanced) : # Store Metadata (advanced & maybe non-advanced users) - # Some Data may be Text or XML files with useful information for non-advanced users - is_text, final_data, file_ext, write_mode = bin_is_text(file_meta, file_type, True, is_advanced) - - final_name = '%s%s' % (safe_name, meta_ext[:-4] + file_ext if is_text else meta_ext) - final_path = os.path.join(output_path, final_name) - - with open(final_path, write_mode) as o : o.write(final_data) # Write final Data Metadata - - if file_meta_sig and is_advanced : # Store Metadata Signature (advanced users only) - final_name = '%s.meta.sig' % safe_name - final_path = os.path.join(output_path, final_name) - - with open(final_path, 'wb') as o : o.write(file_meta_sig) # Write final Data Metadata Signature - -# Check if file is Text/XML and Convert -def bin_is_text(buffer, file_type, is_metadata, is_advanced) : - is_text = False - write_mode = 'wb' - extension = '.bin' - - # Only for non-advanced users due to signature (.sig) invalidation - if not is_advanced : - if b',END' in buffer[-0x7:] : # Text Type 1 - is_text = True - write_mode = 'w' - extension = '.txt' - buffer = buffer.decode('utf-8').split(',END')[0].replace(';','\n') - elif buffer.startswith(b'VendorName=Dell') : # Text Type 2 - is_text = True - write_mode = 'w' - extension = '.txt' - buffer = buffer.split(b'\x00')[0].decode('utf-8').replace(';','\n') - elif b'= len(buffer)) or (fit_len < struct_len) : - print('\n Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name.__name__)) - - input('\nPress enter to exit') - - sys.exit(1) - - ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len) - - return structure - -# Pause after any unexpected Python exception -# https://stackoverflow.com/a/781074 by Torsten Marek -def show_exception_and_exit(exc_type, exc_value, tb) : - if exc_type is KeyboardInterrupt : - print('\n') - else : - print('\nError: %s crashed, please report the following:\n' % title) - traceback.print_exception(exc_type, exc_value, tb) - input('\nPress enter to exit') - - sys.exit(1) - -# Set pause-able Python exception handler -sys.excepthook = show_exception_and_exit - -# Show script title -print('\n' + title) - -# Set console/shell window title -user_os = sys.platform -if user_os == 'win32' : ctypes.windll.kernel32.SetConsoleTitleW(title) -elif user_os.startswith('linux') or user_os == 'darwin' or user_os.find('bsd') != -1 : sys.stdout.write('\x1b]2;' + title + '\x07') - -# Set argparse Arguments -parser = argparse.ArgumentParser() -parser.add_argument('images', type=argparse.FileType('r'), nargs='*') -parser.add_argument('-a', '--advanced', help='extract in advanced user mode', action='store_true') -args = parser.parse_args() - -# Get ctypes Structure Sizes -pfs_header_size = ctypes.sizeof(PFS_HDR) -pfs_footer_size = ctypes.sizeof(PFS_FTR) -pfs_info_size = ctypes.sizeof(PFS_INFO) -met_info_size = ctypes.sizeof(METADATA_INFO) -chunk_info_header_size = ctypes.sizeof(CHUNK_INFO_HDR) -chunk_info_footer_size = ctypes.sizeof(CHUNK_INFO_FTR) - -# The Dell ThinOS PKG BIOS images usually contain more than one section. Each section starts with -# a 0x30 sized header, which begins with pattern 72135500. The section length is found at 0x10-0x14 -# and its (optional) MD5 hash at 0x20-0x30. The section data can be raw or LZMA2 (7zXZ) compressed. -# The LZMA2 section includes the actual Dell PFS BIOS image, so it needs to be decompressed first. -# For the purposes of this utility, we are only interested in extracting the Dell PFS BIOS section. -lzma_pkg_header = re.compile(br'\x72\x13\x55\x00.{45}\x37\x7A\x58\x5A', re.DOTALL) - -# The Dell PFS BIOS images usually contain more than one section. Each section is zlib-compressed -# with header pattern ********++EEAA761BECBB20F1E651--789C where ******** is the zlib stream size, -# ++ is the section type and -- the header Checksum XOR 8. The "BIOS" section has type 0xAA and its -# files are stored in PFS format. The "Utility" section has type 0xBB and its files are stored in PFS -# BIN or 7z formats. There could be more section types but for the purposes of this utility, we are -# only interested in extracting the "BIOS" section files. Each section is followed by a footer pattern -# ********EEAAEE8F491BE8AE143790-- where ******** is the zlib stream size and ++ the footer Checksum XOR 8. -zlib_bios_header = re.compile(br'\xAA\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) -zlib_bios_footer = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') - -if len(sys.argv) >= 2 : - # Drag & Drop or CLI - pfs_exec = [] - for image in args.images : - pfs_exec.append(image.name) -else : - # Folder path - pfs_exec = [] - in_path = input('\nEnter the full folder path: ') - print('\nWorking...') - for root, _, files in os.walk(in_path): - for name in files : - pfs_exec.append(os.path.join(root, name)) - -# Process each input Dell PFS BIOS image -for input_file in pfs_exec : - input_name,input_extension = os.path.splitext(os.path.basename(input_file)) - input_dir = os.path.dirname(os.path.abspath(input_file)) - - print('\n*** %s%s' % (input_name, input_extension)) - - # Check if input file exists - if not os.path.isfile(input_file) : - print('\n Error: This input file does not exist!') - continue # Next input file - - with open(input_file, 'rb') as in_file : input_data = in_file.read() - - # Search input image for ThinOS PKG 7zXZ section header - lzma_pkg_hdr_match = lzma_pkg_header.search(input_data) - - # Decompress ThinOS PKG 7zXZ section first, if present - if lzma_pkg_hdr_match : - lzma_len_off = lzma_pkg_hdr_match.start() + 0x10 - lzma_len_int = int.from_bytes(input_data[lzma_len_off:lzma_len_off + 0x4], 'little') - lzma_bin_off = lzma_pkg_hdr_match.end() - 0x5 - lzma_bin_dat = input_data[lzma_bin_off:lzma_bin_off + lzma_len_int] - - # Check if the compressed 7zXZ stream is complete, based on header - if len(lzma_bin_dat) != lzma_len_int : - print('\n Error: This Dell ThinOS PKG BIOS image is corrupted!') - continue # Next input file - - input_data = lzma.decompress(lzma_bin_dat) - - # Search input image for zlib "BIOS" section header - zlib_bios_hdr_match = zlib_bios_header.search(input_data) - - # Check if "BIOS" section was found in the image - if not zlib_bios_hdr_match : - print('\n Error: This is not a Dell PFS BIOS image!') - continue # Next input file - - # Store the compressed zlib stream start offset - compressed_start = zlib_bios_hdr_match.start() + 0xC - - # Store the "BIOS" section header start offset - header_start = zlib_bios_hdr_match.start() - 0x4 - - # Store the "BIOS" section header contents (16 bytes) - header_data = input_data[header_start:compressed_start] - - # Check if the "BIOS" section header Checksum XOR 8 is valid - if chk_xor_8(header_data[:0xF], 0) != header_data[0xF] : - print('\n Error: This Dell PFS BIOS image is corrupted!') - continue # Next input file - - # Store the compressed zlib stream size from the header contents - compressed_size_hdr = int.from_bytes(header_data[:0x4], 'little') - - # Store the compressed zlib stream end offset - compressed_end = compressed_start + compressed_size_hdr - - # Store the compressed zlib stream contents - compressed_data = input_data[compressed_start:compressed_end] - - # Check if the compressed zlib stream is complete, based on header - if len(compressed_data) != compressed_size_hdr : - print('\n Error: This Dell PFS BIOS image is corrupted!') - continue # Next input file - - # Store the "BIOS" section footer contents (16 bytes) - footer_data = input_data[compressed_end:compressed_end + 0x10] - - # Check if the "BIOS" section footer Checksum XOR 8 is valid - if chk_xor_8(footer_data[:0xF], 0) != footer_data[0xF] : - print('\n Error: This Dell PFS BIOS image is corrupted!') - continue # Next input file - - # Search input image for zlib "BIOS" section footer - zlib_bios_ftr_match = zlib_bios_footer.search(footer_data) - - # Check if "BIOS" section footer was found in the image - if not zlib_bios_ftr_match : - print('\n Error: This Dell PFS BIOS image is corrupted!') - continue # Next input file - - # Store the compressed zlib stream size from the footer contents - compressed_size_ftr = int.from_bytes(footer_data[:0x4], 'little') - - # Check if the compressed zlib stream is complete, based on footer - if compressed_size_ftr != compressed_size_hdr : - print('\n Error: This Dell PFS BIOS image is corrupted!') - continue # Next input file - - # Decompress "BIOS" section payload, starting from zlib header start of 0x789C - input_data = zlib.decompress(compressed_data) - - output_path = os.path.join(input_dir, '%s%s' % (input_name, input_extension) + '_extracted') # Set extraction directory - - if os.path.isdir(output_path) : shutil.rmtree(output_path) # Delete any existing extraction directory - - os.mkdir(output_path) # Create extraction directory - - pfs_name = '' # N/A for Main/First/Initial full PFS, used for sub-PFS recursions - pfs_index = 1 # Main/First/Initial full PFS Index is 1 - pfs_count = 1 # Main/First/Initial full PFS Count is 1 - is_advanced = bool(args.advanced) # Set Advanced user mode optional argument - - pfs_extract(input_data, pfs_index, pfs_name, pfs_count) # Call the Dell PFS.HDR. Extractor function - - print('\n Extracted Dell PFS BIOS image!') - -input('\nDone!') - -sys.exit(0) \ No newline at end of file diff --git a/Dell PFS Update Extractor/Dell_PFS_Extract.py b/Dell PFS Update Extractor/Dell_PFS_Extract.py new file mode 100644 index 0000000..c094e8b --- /dev/null +++ b/Dell PFS Update Extractor/Dell_PFS_Extract.py @@ -0,0 +1,1245 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Dell PFS Extract +Dell PFS Update Extractor +Copyright (C) 2018-2021 Plato Mavropoulos +""" + +title = 'Dell PFS Update Extractor v5.0' + +import sys + +# Detect Python version +sys_py = sys.version_info + +# Check Python version +if sys_py < (3,7) : + sys.stdout.write('%s\n\nError: Python >= 3.7 required, not %d.%d!\n' % (title, sys_py[0], sys_py[1])) + + if '--auto-exit' not in sys.argv and '-e' not in sys.argv : + (raw_input if sys_py[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 + + sys.exit(1) + +# Detect OS platform +sys_os = sys.platform + +# Check OS platform +if sys_os == 'win32' : + sys.stdout.reconfigure(encoding='utf-8') # Fix Windows Unicode console redirection +elif sys_os.startswith('linux') or sys_os == 'darwin' or sys_os.find('bsd') != -1 : + pass # Supported/Tested +else : + print('%s\n\nError: Unsupported platform "%s"!\n' % (title, sys_os)) + + if '--auto-exit' not in sys.argv and '-e' not in sys.argv : input('Press enter to exit') + + sys.exit(1) + +# Skip __pycache__ generation +sys.dont_write_bytecode = True + +# Python imports +import os +import re +import zlib +import lzma +import shutil +import ctypes +import inspect +import pathlib +import argparse +import traceback + +# Optional imports +try : + from big_script_tool import BigScript + is_bgst = True +except : + is_bgst = False + +# Set ctypes Structure types +char = ctypes.c_char +uint8_t = ctypes.c_ubyte +uint16_t = ctypes.c_ushort +uint32_t = ctypes.c_uint +uint64_t = ctypes.c_uint64 + +# Dell PFS Header Structure +class PFS_DELL_HDR(ctypes.LittleEndianStructure) : + _pack_ = 1 + _fields_ = [ + ('Tag', char*8), # 0x00 + ('HeaderVersion', uint32_t), # 0x08 + ('PayloadSize', uint32_t), # 0x0C + # 0x10 + ] + + def pfs_print(self, padd) : + print('\n%sPFS Header:\n' % (' ' * (padd - 4))) + print('%sHeader Tag : %s' % (' ' * padd, self.Tag.decode('utf-8'))) + print('%sHeader Version : %d' % (' ' * padd, self.HeaderVersion)) + print('%sPayload Size : 0x%X' % (' ' * padd, self.PayloadSize)) + +# Dell PFS Footer Structure +class PFS_DELL_FTR(ctypes.LittleEndianStructure) : + _pack_ = 1 + _fields_ = [ + ('PayloadSize', uint32_t), # 0x00 + ('Checksum', uint32_t), # 0x04 ~CRC32 w/ Vector 0 + ('Tag', char*8), # 0x08 + # 0x10 + ] + + def pfs_print(self, padd) : + print('\n%sPFS Footer:\n' % (' ' * (padd - 4))) + print('%sPayload Size : 0x%X' % (' ' * padd, self.PayloadSize)) + print('%sPayload Checksum : 0x%0.8X' % (' ' * padd, self.Checksum)) + print('%sFooter Tag : %s' % (' ' * padd, self.Tag.decode('utf-8'))) + +# Dell PFS Entry Revision 1 Structure +class PFS_ENTRY_R1(ctypes.LittleEndianStructure) : + _pack_ = 1 + _fields_ = [ + ('GUID', uint32_t*4), # 0x00 Little Endian + ('HeaderVersion', uint32_t), # 0x10 1 + ('VersionType', uint8_t*4), # 0x14 + ('Version', uint16_t*4), # 0x18 + ('Reserved', uint64_t), # 0x20 + ('DataSize', uint32_t), # 0x28 + ('DataSigSize', uint32_t), # 0x2C + ('DataMetSize', uint32_t), # 0x30 + ('DataMetSigSize', uint32_t), # 0x34 + ('Unknown', uint32_t*4), # 0x38 + # 0x48 + ] + + def pfs_print(self, padd) : + GUID = '%0.*X' % (0x10 * 2, int.from_bytes(self.GUID, 'little')) + Unknown = '%0.*X' % (0x10 * 2, int.from_bytes(self.Unknown, 'little')) + Version = get_entry_ver(self.Version, self.VersionType, padd - 4) + + print('\n%sPFS Entry:\n' % (' ' * (padd - 4))) + print('%sEntry GUID : %s' % (' ' * padd, GUID)) + print('%sEntry Version : %d' % (' ' * padd, self.HeaderVersion)) + print('%sPayload Version : %s' % (' ' * padd, Version)) + print('%sReserved : 0x%X' % (' ' * padd, self.Reserved)) + print('%sPayload Data Size : 0x%X' % (' ' * padd, self.DataSize)) + print('%sPayload Signature Size : 0x%X' % (' ' * padd, self.DataSigSize)) + print('%sMetadata Data Size : 0x%X' % (' ' * padd, self.DataMetSize)) + print('%sMetadata Signature Size : 0x%X' % (' ' * padd, self.DataMetSigSize)) + print('%sUnknown : %s' % (' ' * padd, Unknown)) + +# Dell PFS Entry Revision 2 Structure +class PFS_ENTRY_R2(ctypes.LittleEndianStructure) : + _pack_ = 1 + _fields_ = [ + ('GUID', uint32_t*4), # 0x00 Little Endian + ('HeaderVersion', uint32_t), # 0x10 2 + ('VersionType', uint8_t*4), # 0x14 + ('Version', uint16_t*4), # 0x18 + ('Reserved', uint64_t), # 0x20 + ('DataSize', uint32_t), # 0x28 + ('DataSigSize', uint32_t), # 0x2C + ('DataMetSize', uint32_t), # 0x30 + ('DataMetSigSize', uint32_t), # 0x34 + ('Unknown', uint32_t*8), # 0x38 + # 0x58 + ] + + def pfs_print(self, padd) : + GUID = '%0.*X' % (0x10 * 2, int.from_bytes(self.GUID, 'little')) + Unknown = '%0.*X' % (0x20 * 2, int.from_bytes(self.Unknown, 'little')) + Version = get_entry_ver(self.Version, self.VersionType, padd - 4) + + print('\n%sPFS Entry:\n' % (' ' * (padd - 4))) + print('%sEntry GUID : %s' % (' ' * padd, GUID)) + print('%sEntry Version : %d' % (' ' * padd, self.HeaderVersion)) + print('%sPayload Version : %s' % (' ' * padd, Version)) + print('%sReserved : 0x%X' % (' ' * padd, self.Reserved)) + print('%sPayload Data Size : 0x%X' % (' ' * padd, self.DataSize)) + print('%sPayload Signature Size : 0x%X' % (' ' * padd, self.DataSigSize)) + print('%sMetadata Data Size : 0x%X' % (' ' * padd, self.DataMetSize)) + print('%sMetadata Signature Size : 0x%X' % (' ' * padd, self.DataMetSigSize)) + print('%sUnknown : %s' % (' ' * padd, Unknown)) + +# Dell PFS Information Header Structure +class PFS_INFO_HDR(ctypes.LittleEndianStructure) : + _pack_ = 1 + _fields_ = [ + ('HeaderVersion', uint32_t), # 0x00 + ('GUID', uint32_t*4), # 0x04 Little Endian + # 0x14 + ] + + def pfs_print(self, padd) : + GUID = '%0.*X' % (0x10 * 2, int.from_bytes(self.GUID, 'little')) + + print('\n%sPFS Information Header:\n' % (' ' * (padd - 4))) + print('%sInfo Version : %d' % (' ' * padd, self.HeaderVersion)) + print('%sEntry GUID : %s' % (' ' * padd, GUID)) + +# Dell PFS FileName Header Structure +class PFS_NAME_HDR(ctypes.LittleEndianStructure) : + _pack_ = 1 + _fields_ = [ + ('Version', uint16_t*4), # 0x00 + ('VersionType', uint8_t*4), # 0x08 + ('CharacterCount', uint16_t), # 0x0C UTF-16 2-byte Characters + # 0x0E + ] + + def pfs_print(self, padd) : + Version = get_entry_ver(self.Version, self.VersionType, padd - 4) + + print('\n%sPFS FileName Entry:\n' % (' ' * (padd - 4))) + print('%sPayload Version : %s' % (' ' * padd, Version)) + print('%sCharacter Count : %d' % (' ' * padd, self.CharacterCount)) + +# Dell PFS Metadata Header Structure +class PFS_META_HDR(ctypes.LittleEndianStructure) : + _pack_ = 1 + _fields_ = [ + ('ModelIDs', char*501), # 0x000 + ('FileName', char*100), # 0x1F5 + ('FileVersion', char*33), # 0x259 + ('Date', char*33), # 0x27A + ('Brand', char*80), # 0x29B + ('ModelFile', char*80), # 0x2EB + ('ModelName', char*100), # 0x33B + ('ModelVersion', char*33), # 0x39F + # 0x3C0 + ] + + def pfs_print(self, padd) : + print('\n%sPFS Metadata Information:\n' % (' ' * (padd - 4))) + print('%sModel IDs : %s' % (' ' * padd, self.ModelIDs.decode('utf-8').strip(',END'))) + print('%sFile Name : %s' % (' ' * padd, self.FileName.decode('utf-8'))) + print('%sFile Version : %s' % (' ' * padd, self.FileVersion.decode('utf-8'))) + print('%sDate : %s' % (' ' * padd, self.Date.decode('utf-8'))) + print('%sBrand : %s' % (' ' * padd, self.Brand.decode('utf-8'))) + print('%sModel File : %s' % (' ' * padd, self.ModelFile.decode('utf-8'))) + print('%sModel Name : %s' % (' ' * padd, self.ModelName.decode('utf-8'))) + print('%sModel Version : %s' % (' ' * padd, self.ModelVersion.decode('utf-8'))) + + def pfs_write(self) : + return '%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s' % (self.ModelIDs.decode('utf-8').strip(',END'), self.FileName.decode('utf-8'), + self.FileVersion.decode('utf-8'), self.Date.decode('utf-8'), self.Brand.decode('utf-8'), self.ModelFile.decode('utf-8'), + self.ModelName.decode('utf-8'), self.ModelVersion.decode('utf-8')) + +# Dell PFS BIOS Guard Header Structure +class PFS_PFAT_HDR(ctypes.LittleEndianStructure) : + _pack_ = 1 + _fields_ = [ + ('PFATVerMajor', uint16_t), # 0x00 + ('PFATVerMinor', uint16_t), # 0x02 + ('PlatformID', uint8_t*16), # 0x04 + ('Attributes', uint32_t), # 0x14 + ('ScriptVerMajor', uint16_t), # 0x16 + ('ScriptVerMinor', uint16_t), # 0x18 + ('ScriptSize', uint32_t), # 0x1C + ('DataSize', uint32_t), # 0x20 + ('BIOSSVN', uint32_t), # 0x24 + ('ECSVN', uint32_t), # 0x28 + ('VendorInfo', uint32_t), # 0x2C + # 0x30 + ] + + def __init__(self, count, *args, **kwargs): + super().__init__(*args, **kwargs) + self.count = count + + def get_flags(self) : + attr = PFS_PFAT_HDR_ATTRIBUTES_GET() + attr.asbytes = self.Attributes + + return attr.b.SFAM, attr.b.ProtectEC, attr.b.GFXMitDis, attr.b.FTU, attr.b.Reserved + + def pfs_print(self, padd) : + no_yes = ['No','Yes'] + f1,f2,f3,f4,f5 = self.get_flags() + + PlatformID = bytes(self.PlatformID).strip(b'\x00') + try : # STRING + PlatformID = PlatformID.decode('utf-8') + except : # GUID + PlatformID = '%0.*X' % (0x10 * 2, int.from_bytes(self.PlatformID, 'big')) + PlatformID = '{%s-%s-%s-%s-%s}' % (PlatformID[:8], PlatformID[8:12], PlatformID[12:16], PlatformID[16:20], PlatformID[20:]) + + print('\n%sPFAT Block %d Header:\n' % (' ' * (padd - 4), self.count)) + print('%sPFAT Version : %d.%d' % (' ' * padd, self.PFATVerMajor, self.PFATVerMinor)) + print('%sPlatform ID : %s' % (' ' * padd, PlatformID)) + print('%sSigned Flash Address Map : %s' % (' ' * padd, no_yes[f1])) + print('%sProtected EC OpCodes : %s' % (' ' * padd, no_yes[f2])) + print('%sGraphics Security Disable : %s' % (' ' * padd, no_yes[f3])) + print('%sFault Tolerant Update : %s' % (' ' * padd, no_yes[f4])) + print('%sAttributes Reserved : 0x%X' % (' ' * padd, f5)) + print('%sScript Version : %d.%d' % (' ' * padd, self.ScriptVerMajor, self.ScriptVerMinor)) + print('%sScript Size : 0x%X' % (' ' * padd, self.ScriptSize)) + print('%sData Size : 0x%X' % (' ' * padd, self.DataSize)) + print('%sBIOS SVN : 0x%X' % (' ' * padd, self.BIOSSVN)) + print('%sEC SVN : 0x%X' % (' ' * padd, self.ECSVN)) + print('%sVendor Info : 0x%X' % (' ' * padd, self.VendorInfo)) + +# Dell PFS BIOS Guard Attributes Flags Structure +class PFS_PFAT_HDR_ATTRIBUTES(ctypes.LittleEndianStructure): + _fields_ = [ + ('SFAM', uint32_t, 1), # Signed Flash Address Map + ('ProtectEC', uint32_t, 1), # Protected EC OpCodes + ('GFXMitDis', uint32_t, 1), # GFX Security Disable + ('FTU', uint32_t, 1), # Fault Tolerant Update + ('Reserved', uint32_t, 28) + ] + +# Dell PFS BIOS Guard Attributes Get Structure +class PFS_PFAT_HDR_ATTRIBUTES_GET(ctypes.Union): + _fields_ = [ + ('b', PFS_PFAT_HDR_ATTRIBUTES), + ('asbytes', uint32_t) + ] + +# Dell PFS BIOS Guard Signature Structure +class PFS_PFAT_SIG(ctypes.LittleEndianStructure) : + _pack_ = 1 + _fields_ = [ + ('Unknown0', uint32_t), # 0x00 + ('Unknown1', uint32_t), # 0x04 + ('PublicKey', uint32_t*64), # 0x08 + ('Exponent', uint32_t), # 0x108 + ('Signature', uint32_t*64), # 0x10C + # 0x20C + ] + + def __init__(self, count, *args, **kwargs): + super().__init__(*args, **kwargs) + self.count = count + + def pfs_print(self, padd) : + PublicKey = '%0.*X' % (0x100 * 2, int.from_bytes(self.PublicKey, 'little')) + Signature = '%0.*X' % (0x100 * 2, int.from_bytes(self.Signature, 'little')) + + print('\n%sPFAT Block %d Signature:\n' % (' ' * (padd - 4), self.count)) + print('%sUnknown 0 : 0x%X' % (' ' * padd, self.Unknown0)) + print('%sUnknown 1 : 0x%X' % (' ' * padd, self.Unknown1)) + print('%sPublic Key : %s [...]' % (' ' * padd, PublicKey[:32])) + print('%sExponent : 0x%X' % (' ' * padd, self.Exponent)) + print('%sSignature : %s [...]' % (' ' * padd, Signature[:32])) + +# Dell PFS BIOS Guard Metadata Structure +class PFS_PFAT_MET(ctypes.LittleEndianStructure) : + _pack_ = 1 + _fields_ = [ + ('OffsetTop', uint32_t), # 0x00 + ('Unknown0', uint32_t), # 0x04 + ('OffsetBase', uint32_t), # 0x08 + ('BlockSize', uint32_t), # 0x0C + ('Unknown1', uint32_t), # 0x10 + ('Unknown2', uint32_t), # 0x14 + ('Unknown3', uint8_t), # 0x18 + # 0x19 + ] + + def __init__(self, count, *args, **kwargs): + super().__init__(*args, **kwargs) + self.count = count + + def pfs_print(self, padd) : + print('\n%sPFAT Block %d Metadata:\n' % (' ' * (padd - 4), self.count)) + print('%sOffset Top : 0x%X' % (' ' * padd, self.OffsetTop)) + print('%sUnknown 0 : 0x%X' % (' ' * padd, self.Unknown0)) + print('%sOffset Base : 0x%X' % (' ' * padd, self.OffsetBase)) + print('%sBlock Size : 0x%X' % (' ' * padd, self.BlockSize)) + print('%sUnknown 1 : 0x%X' % (' ' * padd, self.Unknown1)) + print('%sUnknown 2 : 0x%X' % (' ' * padd, self.Unknown2)) + print('%sUnknown 3 : 0x%X' % (' ' * padd, self.Unknown3)) + +# Dell PFS Update Analysis +def main(exit_code, pfs_input_images) : + # Process each input Dell PFS update image + for input_file in pfs_input_images : + input_name,input_ext = os.path.splitext(os.path.basename(input_file)) + input_dir = os.path.dirname(os.path.abspath(input_file)) + + print('\n*** %s%s' % (input_name, input_ext)) + + # Check if input file exists + if not os.path.isfile(input_file) : + print('\n Error: This input file does not exist!') + continue # Next input file + + with open(input_file, 'rb') as in_file : input_data = in_file.read() + + # Search input image for ThinOS PKG 7zXZ section header + lzma_pkg_hdr_match = lzma_pkg_header.search(input_data) + + # Decompress ThinOS PKG 7zXZ section first, if present + if lzma_pkg_hdr_match : + lzma_len_off = lzma_pkg_hdr_match.start() + 0x10 + lzma_len_int = int.from_bytes(input_data[lzma_len_off:lzma_len_off + 0x4], 'little') + lzma_bin_off = lzma_pkg_hdr_match.end() - 0x5 + lzma_bin_dat = input_data[lzma_bin_off:lzma_bin_off + lzma_len_int] + + # Check if the compressed 7zXZ stream is complete, based on header + if len(lzma_bin_dat) != lzma_len_int : + print('\n Error: This Dell ThinOS PKG update image is corrupted!') + continue # Next input file + + input_data = lzma.decompress(lzma_bin_dat) + + # Search input image for PFS ZLIB Sections + pfs_zlib_offsets = get_section_offsets(input_data) + + if not pfs_zlib_offsets : + print('\n Error: This is not a Dell PFS update image!') + continue # Next input file + + # Set user extraction path + extract_path_user = get_absolute_path(args.output_dir) + + # Set main extraction path (optional user specified path taken into account) + extract_path_main = os.path.join(extract_path_user, '%s%s' % (input_name, input_ext) + '_extracted') + + # Parse each PFS ZLIB Section + for offset in pfs_zlib_offsets : + # Call the PFS ZLIB Section Parser function + pfs_section_parse(input_data, offset, extract_path_main, ' ' + input_name, 1, 1, False, 4) + + exit_code -= 1 # Adjust exit code to reflect extraction progress + + if not bool(args.auto_exit) : input('\nDone!') + + return exit_code + +# Get PFS ZLIB Section Offsets +def get_section_offsets(buffer) : + pfs_zlib_init = list(pfs_zlib_header.finditer(buffer)) + + if not pfs_zlib_init : return [] # No PFS ZLIB detected + + pfs_zlib_list = [] # Initialize PFS ZLIB offset list + + # Remove duplicate/nested PFS ZLIB offsets + for zlib_c in pfs_zlib_init : + is_duplicate = False # Initialize duplicate/nested PFS ZLIB offset + + for zlib_o in pfs_zlib_init : + zlib_o_size = int.from_bytes(buffer[zlib_o.start() - 0x5:zlib_o.start() - 0x1], 'little') + + # If current PFS ZLIB offset is within another PFS ZLIB range (start-end), set as duplicate + if zlib_o.start() < zlib_c.start() < zlib_o.start() + zlib_o_size : is_duplicate = True + + if not is_duplicate : pfs_zlib_list.append(zlib_c.start()) + + return pfs_zlib_list + +# Dell PFS ZLIB Section Parser +def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padd) : + is_zlib_error = False # Initialize PFS ZLIB-related error state + + section_type = zlib_data[zlib_start - 0x1] # Byte before PFS ZLIB Section pattern is Section Type (e.g. AA, BB) + section_name = section_dict[section_type] if section_type in section_dict else 'Unknown (%0.2X)' % section_type + + # Set PFS ZLIB Section extraction sub-directory path + section_path = os.path.join(output_path, section_name) + + # Delete existing extraction sub-directory (not in recursions) + if os.path.isdir(section_path) and not is_rec : shutil.rmtree(section_path) + + # Create extraction sub-directory + if not os.path.isdir(section_path) : os.makedirs(section_path) + + # Store the compressed zlib stream start offset + compressed_start = zlib_start + 0xB + + # Store the PFS ZLIB section header start offset + header_start = zlib_start - 0x5 + + # Store the PFS ZLIB section header contents (16 bytes) + header_data = zlib_data[header_start:compressed_start] + + # Check if the PFS ZLIB section header Checksum XOR 8 is valid + if chk_xor_8(header_data[:0xF], 0) != header_data[0xF] : + print('\n%sError: Invalid Dell PFS ZLIB section Header Checksum!' % (' ' * padd)) + is_zlib_error = True + + # Store the compressed zlib stream size from the header contents + compressed_size_hdr = int.from_bytes(header_data[:0x4], 'little') + + # Store the compressed zlib stream end offset + compressed_end = compressed_start + compressed_size_hdr + + # Store the compressed zlib stream contents + compressed_data = zlib_data[compressed_start:compressed_end] + + # Check if the compressed zlib stream is complete, based on header + if len(compressed_data) != compressed_size_hdr : + print('\n%sError: Incomplete Dell PFS ZLIB section data (Header)!' % (' ' * padd)) + is_zlib_error = True + + # Store the PFS ZLIB section footer contents (16 bytes) + footer_data = zlib_data[compressed_end:compressed_end + 0x10] + + # Search input section for PFS ZLIB section footer + pfs_zlib_footer_match = pfs_zlib_footer.search(footer_data) + + # Check if PFS ZLIB section footer was found in the section + if not pfs_zlib_footer_match : + print('\n%sError: This Dell PFS ZLIB section is corrupted!' % (' ' * padd)) + is_zlib_error = True + + # Check if the PFS ZLIB section footer Checksum XOR 8 is valid + if chk_xor_8(footer_data[:0xF], 0) != footer_data[0xF] : + print('\n%sError: Invalid Dell PFS ZLIB section Footer Checksum!' % (' ' * padd)) + is_zlib_error = True + + # Store the compressed zlib stream size from the footer contents + compressed_size_ftr = int.from_bytes(footer_data[:0x4], 'little') + + # Check if the compressed zlib stream is complete, based on footer + if compressed_size_ftr != compressed_size_hdr : + print('\n%sError: Incomplete Dell PFS ZLIB section data (Footer)!' % (' ' * padd)) + is_zlib_error = True + + # Decompress PFS ZLIB section payload + try : + assert not is_zlib_error # ZLIB errors are critical + section_data = zlib.decompress(compressed_data) # ZLIB decompression + except : + section_data = zlib_data # Fallback to raw ZLIB data upon critical error + + # Call the PFS Extract function on the decompressed PFS ZLIB Section + pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padd) + + # Show extraction complete message for each main PFS ZLIB Section + print('\n%sExtracted Dell PFS %d >%s > %s section!' % (' ' * padd, pfs_index, pfs_name, section_name)) + +# Parse & Extract Dell PFS Volume +def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd) : + if is_verbose : print('\n%sPFS Volume:' % (' ' * pfs_padd)) + + # Get PFS Header Structure values + pfs_hdr = get_struct(buffer, 0, PFS_DELL_HDR, None, pfs_padd + 4) + + # Validate that a PFS Header was parsed + if pfs_hdr.Tag != b'PFS.HDR.' : + msg_print(pfs_padd + 4, 'Error: PFS Header could not be found!') + return # Critical error, abort + + # Show PFS Header Structure info + if is_verbose : pfs_hdr.pfs_print(pfs_padd + 8) + + # Validate that a known PFS Header Version was encountered + chk_hdr_ver(pfs_hdr.HeaderVersion, 'PFS', pfs_padd + 8) + + # Get PFS Payload Data + pfs_payload = buffer[dpfs_hdr_size:dpfs_hdr_size + pfs_hdr.PayloadSize] + + # Parse all PFS Payload Entries/Components + entry_index = 1 # Index number of each PFS Entry + entry_start = 0 # Increasing PFS Entry starting offset + entries_all = [] # Storage for each PFS Entry details + filename_info = [] # Buffer for FileName Information Entry Data + signature_info = [] # Buffer for Signature Information Entry Data + pfs_entry_struct, pfs_entry_size = get_pfs_entry(pfs_payload, entry_start) # Get PFS Entry Info + while len(pfs_payload[entry_start:entry_start + pfs_entry_size]) == pfs_entry_size : + # Analyze PFS Entry Structure and get relevant info + pfs_entry,entry_version,entry_guid,entry_data,entry_data_sig,entry_met,entry_met_sig,next_entry = \ + parse_pfs_entry(pfs_payload, entry_start, pfs_entry_size, pfs_entry_struct, None, 'PFS Entry', pfs_padd) + + entry_type = 'OTHER' # Adjusted later if PFS Entry is Zlib, PFAT, PFS Info, Model Info + + # Get PFS Information from the PFS Entry with GUID E0717CE3A9BB25824B9F0DC8FD041960 or B033CB16EC9B45A14055F80E4D583FD3 + if entry_guid in ['E0717CE3A9BB25824B9F0DC8FD041960','B033CB16EC9B45A14055F80E4D583FD3'] : + filename_info = entry_data + entry_type = 'NAME_INFO' + + # Get Model Information from the PFS Entry with GUID 6F1D619A22A6CB924FD4DA68233AE3FB + elif entry_guid == '6F1D619A22A6CB924FD4DA68233AE3FB' : + entry_type = 'MODEL_INFO' + + # Get Signature Information from the PFS Entry with GUID D086AFEE3ADBAEA94D5CED583C880BB7 + elif entry_guid == 'D086AFEE3ADBAEA94D5CED583C880BB7' : + signature_info = entry_data + entry_type = 'SIG_INFO' + + # Get Nested PFS from the PFS Entry with GUID 900FAE60437F3AB14055F456AC9FDA84 + elif entry_guid == '900FAE60437F3AB14055F456AC9FDA84' : + entry_type = 'NESTED_PFS' # Nested PFS are usually zlib-compressed so it might change to 'ZLIB' later + + # Store all relevant PFS Entry details + entries_all.append([entry_index, entry_guid, entry_version, entry_type, entry_data, entry_data_sig, entry_met, entry_met_sig]) + + entry_index += 1 # Increase PFS Entry Index number for user-friendly output and name duplicates + entry_start = next_entry # Next PFS Entry starts after PFS Entry Metadata Signature + + # Parse all PFS Information Entries/Descriptors + info_start = 0 # Increasing PFS Information Entry starting offset + info_all = [] # Storage for each PFS Information Entry details + while len(filename_info[info_start:info_start + info_hdr_size]) == info_hdr_size : + # Get PFS Information Header Structure info + entry_info_hdr = get_struct(filename_info, info_start, PFS_INFO_HDR, None, pfs_padd + 8) + + # Show PFS Information Header Structure info + if is_verbose : entry_info_hdr.pfs_print(pfs_padd + 8) + + # Validate that a known PFS Information Header Version was encountered + if entry_info_hdr.HeaderVersion != 1 : + msg_print(pfs_padd + 8, 'Error: Unknown PFS Information Header Version %d!' % entry_info_hdr.HeaderVersion) + break # Skip PFS Information Entries/Descriptors in case of unknown PFS Information Header Version + + # Get PFS Information Header GUID in Big Endian format to match each Info to the equivalent stored PFS Entry details + entry_guid = '%0.*X' % (0x10 * 2, int.from_bytes(entry_info_hdr.GUID, 'little')) + + # Get PFS FileName Structure values + entry_info_mod = get_struct(filename_info, info_start + info_hdr_size, PFS_NAME_HDR, None, pfs_padd + 8) + + # Show PFS FileName Structure info + if is_verbose : entry_info_mod.pfs_print(pfs_padd + 12) + + # The PFS FileName Structure is not complete by itself. The size of the last field (Entry Name) is determined from + # CharacterCount multiplied by 2 due to usage of UTF-16 2-byte Characters. Any Entry Name leading and/or trailing + # space/null characters are stripped and common Windows reserved/illegal filename characters are replaced + name_start = info_start + info_hdr_size + name_hdr_size # PFS Entry's FileName start offset + name_size = entry_info_mod.CharacterCount * 2 # PFS Entry's FileName buffer total size + name_data = filename_info[name_start:name_start + name_size] # PFS Entry's FileName buffer + entry_name = re.sub(win_char_bad, '_', name_data.decode('utf-16').strip()) # PFS Entry's FileName value + + # Show PFS FileName Name info (padding matches the one from PFS FileName Structure info) + if is_verbose : print('%sPayload Name%s: %s' % (' ' * (pfs_padd + 12), ' ' * 4, entry_name)) + + # Get PFS FileName Version string via "Version" and "VersionType" fields + # PFS FileName Version string must be preferred over PFS Entry's Version + entry_version = get_entry_ver(entry_info_mod.Version, entry_info_mod.VersionType, pfs_padd + 12) + + # Store all relevant PFS FileName details + info_all.append([entry_guid, entry_name, entry_version]) + + # The next PFS Information Header starts after the calculated FileName size + # Two space/null characters seem to always exist after each FileName value + info_start += (info_hdr_size + name_hdr_size + name_size + 0x2) + + # Parse Nested PFS Metadata when its PFS Information Entry is missing + for index in range(len(entries_all)) : + if entries_all[index][3] == 'NESTED_PFS' and not filename_info : + entry_guid = entries_all[index][1] # Nested PFS Entry GUID in Big Endian format + entry_metadata = entries_all[index][6] # Use Metadata as PFS Information Entry + + # When PFS Information Entry exists, Nested PFS Metadata contains only Model IDs + # When it's missing, the Metadata structure is large and contains equivalent info + if len(entry_metadata) >= meta_hdr_size : + # Get Nested PFS Metadata Structure values + entry_info = get_struct(entry_metadata, 0, PFS_META_HDR, None, pfs_padd + 4) + + # Show Nested PFS Metadata Structure info + if is_verbose : entry_info.pfs_print(pfs_padd + 8) + + # As Nested PFS Entry Name, we'll use the actual PFS File Name + # Replace common Windows reserved/illegal filename characters + entry_name = re.sub(win_char_bad, '_', entry_info.FileName.decode('utf-8').strip('.exe')) + + # As Nested PFS Entry Version, we'll use the actual PFS File Version + entry_version = entry_info.FileVersion.decode('utf-8') + + # Store all relevant Nested PFS Metadata/Information details + info_all.append([entry_guid, entry_name, entry_version]) + + # Re-set Nested PFS Entry Version from Metadata + entries_all[index][2] = entry_version + + # Parse all PFS Signature Entries/Descriptors + sign_start = 0 # Increasing PFS Signature Entry starting offset + while len(signature_info[sign_start:sign_start + info_hdr_size]) == info_hdr_size : + # Get PFS Information Header Structure info + entry_info_hdr = get_struct(signature_info, sign_start, PFS_INFO_HDR, None, pfs_padd + 8) + + # Show PFS Information Header Structure info + if is_verbose : entry_info_hdr.pfs_print(pfs_padd + 8) + + # Validate that a known PFS Information Header Version was encountered + if entry_info_hdr.HeaderVersion != 1 : + msg_print(pfs_padd + 8, 'Error: Unknown PFS Information Header Version %d!' % entry_info_hdr.HeaderVersion) + break # Skip PFS Signature Entries/Descriptors in case of unknown Header Version + + # PFS Signature Entries/Descriptors have PFS_INFO_HDR + PFS_ENTRY_R* + Sign Size [0x2] + Sign Data [Sig Size] + pfs_entry_struct, pfs_entry_size = get_pfs_entry(signature_info, sign_start + info_hdr_size) # Get PFS Entry Info + + # Get PFS Entry Header Structure info + entry_hdr = get_struct(signature_info, sign_start + info_hdr_size, pfs_entry_struct, None, pfs_padd + 8) + + # Show PFS Information Header Structure info + if is_verbose : entry_hdr.pfs_print(pfs_padd + 12) + + # Show PFS Signature Size & Data (after PFS_ENTRY_R*) + sign_info_start = sign_start + info_hdr_size + pfs_entry_size + sign_size = int.from_bytes(signature_info[sign_info_start:sign_info_start + 0x2], 'little') + sign_data_raw = signature_info[sign_info_start + 0x2:sign_info_start + 0x2 + sign_size] + sign_data_txt = '%0.*X' % (sign_size * 2, int.from_bytes(sign_data_raw, 'little')) + if is_verbose : + print('\n%sSignature Information:\n' % (' ' * (pfs_padd + 8))) + print('%sSignature Size : 0x%X' % (' ' * (pfs_padd + 12), sign_size)) + print('%sSignature Data : %s [...]' % (' ' * (pfs_padd + 12), sign_data_txt[:32])) + + # The next PFS Signature Entry/Descriptor starts after the previous Signature Data + sign_start += (info_hdr_size + pfs_entry_size + 0x2 + sign_size) + + # Parse each PFS Entry Data for special types (zlib or PFAT) + for index in range(len(entries_all)) : + entry_data = entries_all[index][4] # Get PFS Entry Data + entry_type = entries_all[index][3] # Get PFS Entry Type + + # Very small PFS Entry Data cannot be of special type + if len(entry_data) < dpfs_hdr_size : continue + + # Check if PFS Entry contains zlib-compressed sub-PFS Volume + pfs_zlib_offsets = get_section_offsets(entry_data) + + # Check if PFS Entry contains sub-PFS Volume with PFAT Payload + is_pfat = False # Initial PFAT state for sub-PFS Entry + _, pfat_entry_size = get_pfs_entry(entry_data, dpfs_hdr_size) # Get possible PFS PFAT Entry Size + pfat_hdr_off = dpfs_hdr_size + pfat_entry_size # Possible PFAT Header starts after PFS Header & Entry + pfat_entry_hdr = get_struct(entry_data, 0, PFS_DELL_HDR, None, pfs_padd + 8) # Possible PFS PFAT Entry + if len(entry_data) - pfat_hdr_off >= pfat_hdr_size : + pfat_hdr = get_struct(entry_data, pfat_hdr_off, PFS_PFAT_HDR, [0], pfs_padd + 8) + is_pfat = bytes(pfat_hdr.PlatformID).startswith((b'Dell',b'DELL')) + + # Parse PFS Entry which contains sub-PFS Volume with PFAT Payload + if pfat_entry_hdr.Tag == b'PFS.HDR.' and is_pfat : + entry_type = 'PFAT' # Re-set PFS Entry Type from OTHER to PFAT, to use such info afterwards + + entry_data = parse_pfat_pfs(pfat_entry_hdr, entry_data, pfs_padd) # Parse sub-PFS PFAT Volume + + # Parse PFS Entry which contains zlib-compressed sub-PFS Volume + elif pfs_zlib_offsets : + entry_type = 'ZLIB' # Re-set PFS Entry Type from OTHER to ZLIB, to use such info afterwards + pfs_count += 1 # Increase the count/index of parsed main PFS structures by one + + # Parse each sub-PFS ZLIB Section + for offset in pfs_zlib_offsets : + # Get the Name of the zlib-compressed full PFS structure via the already stored PFS Information + # The zlib-compressed full PFS structure(s) are used to contain multiple FW (CombineBiosNameX) + # When zlib-compressed full PFS structure(s) exist within the main/first full PFS structure, + # its PFS Information should contain their names (CombineBiosNameX). Since the main/first + # full PFS structure has count/index 1, the rest start at 2+ and thus, their PFS Information + # names can be retrieved in order by subtracting 2 from the main/first PFS Information values + sub_pfs_name = ' %s v%s' % (info_all[pfs_count - 2][1], info_all[pfs_count - 2][2]) if info_all else ' UNKNOWN' + + # Set the sub-PFS output path (create sub-folders for each sub-PFS and its ZLIB sections) + sub_pfs_path = os.path.join(output_path, str(pfs_count) + sub_pfs_name) + + # Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count) + pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4) + + entries_all[index][4] = entry_data # Adjust PFS Entry Data after parsing PFAT (same ZLIB raw data, not stored afterwards) + entries_all[index][3] = entry_type # Adjust PFS Entry Type from OTHER to PFAT or ZLIB (ZLIB is ignored at file extraction) + + # Name & Store each PFS Entry/Component Data, Data Signature, Metadata, Metadata Signature + for entry_index in range(len(entries_all)) : + file_index = entries_all[entry_index][0] + file_guid = entries_all[entry_index][1] + file_version = entries_all[entry_index][2] + file_type = entries_all[entry_index][3] + file_data = entries_all[entry_index][4] + file_data_sig = entries_all[entry_index][5] + file_meta = entries_all[entry_index][6] + file_meta_sig = entries_all[entry_index][7] + + # Give Names to special PFS Entries, not covered by PFS Information + if file_type == 'MODEL_INFO' : + file_name = 'Model Information' + elif file_type == 'NAME_INFO' : + file_name = 'Filename Information' + if not is_advanced : continue # Don't store Filename Information in non-advanced user mode + elif file_type == 'SIG_INFO' : + file_name = 'Signature Information' + if not is_advanced : continue # Don't store Signature Information in non-advanced user mode + else : + file_name = '' + + # Most PFS Entry Names & Versions are found at PFS Information via their GUID + # Version can be found at PFS_ENTRY_R* but prefer PFS Information when possible + for info_index in range(len(info_all)) : + info_guid = info_all[info_index][0] + info_name = info_all[info_index][1] + info_version = info_all[info_index][2] + + # Give proper Name & Version info if Entry/Information GUIDs match + if info_guid == file_guid : + file_name = info_name + file_version = info_version + + info_all[info_index][0] = 'USED' # PFS with zlib-compressed sub-PFS use the same GUID + break # Break at 1st Name match to not rename again from next zlib-compressed sub-PFS with the same GUID + + # For both advanced & non-advanced users, the goal is to store final/usable files only + # so empty or intermediate files such as sub-PFS, PFS w/ PFAT or zlib-PFS are skipped + # Main/First PFS CombineBiosNameX Metadata files must be kept for accurate Model Information + # All users should check these files in order to choose the correct CombineBiosNameX modules + write_files = [] # Initialize list of output PFS Entry files to be written/extracted + + is_zlib = bool(file_type == 'ZLIB') # Determine if PFS Entry Data was zlib-compressed + + if file_data and not is_zlib : write_files.append([file_data, 'data']) # PFS Entry Data Payload + if file_data_sig and is_advanced : write_files.append([file_data_sig, 'sign_data']) # PFS Entry Data Signature + if file_meta and (is_zlib or is_advanced) : write_files.append([file_meta, 'meta']) # PFS Entry Metadata Payload + if file_meta_sig and is_advanced : write_files.append([file_meta_sig, 'sign_meta']) # PFS Entry Metadata Signature + + # Write/Extract PFS Entry files + for file in write_files : + pfs_file_write(file[0], file[1], file_type, output_path, pfs_padd, pfs_index, pfs_name, file_index, file_name, file_version, output_path) + + # Get PFS Footer Data after PFS Header Payload + pfs_footer = buffer[dpfs_hdr_size + pfs_hdr.PayloadSize:dpfs_hdr_size + pfs_hdr.PayloadSize + dpfs_ftr_size] + + # Analyze PFS Footer Structure + chk_pfs_ftr(pfs_footer, pfs_payload, pfs_hdr.PayloadSize, 'PFS', pfs_padd) + +# Analyze Dell PFS Entry Structure +def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, struct_args, text, padd) : + # Get PFS Entry Structure values + pfs_entry = get_struct(entry_buffer, entry_start, entry_struct, struct_args, padd + 4) + + # Show PFS Entry Structure info + if is_verbose : pfs_entry.pfs_print(padd + 8) + + # Validate that a known PFS Entry Header Version was encountered + chk_hdr_ver(pfs_entry.HeaderVersion, text, padd + 8) + + # Validate that the PFS Entry Reserved field is empty + if pfs_entry.Reserved != 0 : + msg_print(padd + 8, 'Error: Detected non-empty %s Reserved field!' % text) + + # Get PFS Entry Version string via "Version" and "VersionType" fields + entry_version = get_entry_ver(pfs_entry.Version, pfs_entry.VersionType, padd + 8) + + # Get PFS Entry GUID in Big Endian format + entry_guid = '%0.*X' % (0x10 * 2, int.from_bytes(pfs_entry.GUID, 'little')) + + # PFS Entry Data starts after the PFS Entry Structure + entry_data_start = entry_start + entry_size + entry_data_end = entry_data_start + pfs_entry.DataSize + + # PFS Entry Data Signature starts after PFS Entry Data + entry_data_sig_start = entry_data_end + entry_data_sig_end = entry_data_sig_start + pfs_entry.DataSigSize + + # PFS Entry Metadata starts after PFS Entry Data Signature + entry_met_start = entry_data_sig_end + entry_met_end = entry_met_start + pfs_entry.DataMetSize + + # PFS Entry Metadata Signature starts after PFS Entry Metadata + entry_met_sig_start = entry_met_end + entry_met_sig_end = entry_met_sig_start + pfs_entry.DataMetSigSize + + entry_data = entry_buffer[entry_data_start:entry_data_end] # Store PFS Entry Data + entry_data_sig = entry_buffer[entry_data_sig_start:entry_data_sig_end] # Store PFS Entry Data Signature + entry_met = entry_buffer[entry_met_start:entry_met_end] # Store PFS Entry Metadata + entry_met_sig = entry_buffer[entry_met_sig_start:entry_met_sig_end] # Store PFS Entry Metadata Signature + + return pfs_entry, entry_version, entry_guid, entry_data, entry_data_sig, entry_met, entry_met_sig, entry_met_sig_end + +# Parse Dell PFS Volume with PFAT Payload +def parse_pfat_pfs(entry_hdr, entry_data, padd) : + if is_verbose : print('\n%sPFS Volume:' % (' ' * (padd + 4))) + + # Show sub-PFS Header Structure Info + if is_verbose : entry_hdr.pfs_print(padd + 12) + + # Validate that a known sub-PFS Header Version was encountered + chk_hdr_ver(entry_hdr.HeaderVersion, 'sub-PFS', padd + 12) + + # Get sub-PFS Payload Data + pfat_payload = entry_data[dpfs_hdr_size:dpfs_hdr_size + entry_hdr.PayloadSize] + + # Get sub-PFS Footer Data after sub-PFS Header Payload (must be retrieved at the initial entry_data, before PFAT parsing) + pfat_footer = entry_data[dpfs_hdr_size + entry_hdr.PayloadSize:dpfs_hdr_size + entry_hdr.PayloadSize + dpfs_ftr_size] + + # Parse all sub-PFS Payload PFAT Entries + pfat_data_all = [] # Storage for all sub-PFS PFAT Entries Order/Offset & Payload/Raw Data + pfat_entry_start = 0 # Increasing sub-PFS PFAT Entry start offset + pfat_entry_index = 0 # Increasing sub-PFS PFAT Entry count index + _, pfs_entry_size = get_pfs_entry(pfat_payload, 0) # Get initial PFS PFAT Entry Size for loop + while len(pfat_payload[pfat_entry_start:pfat_entry_start + pfs_entry_size]) == pfs_entry_size : + # Get sub-PFS PFAT Entry Structure & Size info + pfat_entry_struct, pfat_entry_size = get_pfs_entry(pfat_payload, pfat_entry_start) + + # Analyze sub-PFS PFAT Entry Structure and get relevant info + pfat_entry,pfat_entry_version,pfat_entry_guid,pfat_entry_data,pfat_entry_data_sig,pfat_entry_met,pfat_entry_met_sig,pfat_next_entry = \ + parse_pfs_entry(pfat_payload, pfat_entry_start, pfat_entry_size, pfat_entry_struct, None, 'sub-PFS PFAT Entry', padd + 4) + + # Each sub-PFS PFAT Entry includes an AMI BIOS Guard (a.k.a. PFAT) block at the beginning + # We need to parse the PFAT block and remove its contents from the final Payload/Raw Data + pfat_hdr_off = pfat_entry_start + pfat_entry_size # PFAT block starts after PFS Entry + + # Get sub-PFS PFAT Header Structure values + pfat_hdr = get_struct(pfat_payload, pfat_hdr_off, PFS_PFAT_HDR, [pfat_entry_index], padd + 12) + + # Show sub-PFS PFAT Header Structure info + if is_verbose : pfat_hdr.pfs_print(padd + 16) + + # Get PFAT Header Flags (SFAM, ProtectEC, GFXMitDis, FTU, Reserved) + pfat_flag_sig,_,_,_,_ = pfat_hdr.get_flags() + + pfat_script_start = pfat_hdr_off + pfat_hdr_size # PFAT Block Script Start + pfat_script_end = pfat_script_start + pfat_hdr.ScriptSize # PFAT Block Script End + pfat_script_data = pfat_payload[pfat_script_start:pfat_script_end] # PFAT Block Script Data + pfat_payload_start = pfat_script_end # PFAT Block Payload Start (at Script end) + pfat_payload_end = pfat_script_end + pfat_hdr.DataSize # PFAT Block Data End + pfat_payload_data = pfat_payload[pfat_payload_start:pfat_payload_end] # PFAT Block Raw Data + pfat_hdr_bgs_size = pfat_hdr_size + pfat_hdr.ScriptSize # PFAT Block Header & Script Size + + # The PFAT Script End should match the total Entry Data Size w/o PFAT block + if pfat_hdr_bgs_size != pfat_entry.DataSize - pfat_hdr.DataSize : + msg_print(padd + 16, 'Error: Detected sub-PFS PFAT Entry Header & PFAT Size mismatch!') + + # Parse sub-PFS PFAT Signature, if applicable (only when PFAT Header > SFAM flag is set) + if pfat_flag_sig and pfat_payload[pfat_payload_end:pfat_payload_end + pfat_sig_size] == pfat_sig_size : + # Get sub-PFS PFAT Signature Structure values + pfat_sig = get_struct(pfat_payload, pfat_payload_end, PFS_PFAT_SIG, [pfat_entry_index], padd + 12) + + # Show sub-PFS PFAT Signature Structure info + if is_verbose : pfat_sig.pfs_print(padd + 16) + + # Show PFAT Script via BIOS Guard Script Tool + # https://github.com/allowitsme/big-tool by Dmitry Frolov + if is_verbose : + print('\n%sPFAT Block %d Script:\n' % (' ' * (padd + 12), pfat_entry_index)) + is_opcode_div = len(pfat_script_data) % 8 == 0 + is_begin_end = pfat_script_data[:8] + pfat_script_data[-8:] == b'\x01' + b'\x00' * 7 + b'\xFF' + b'\x00' * 7 + if is_opcode_div and is_begin_end and is_bgst : + pfat_script_decomp = BigScript(code_bytes=pfat_script_data) + pfat_script_lines = pfat_script_decomp.to_string().replace('\t',' ').split('\n') + for line in pfat_script_lines : + spacing = ' ' * (padd + 16) if line.endswith(('begin','end',':')) else ' ' * (padd + 24) + operands = [op for op in line.split(' ') if op != ''] + print(spacing + ('{:<12s}' + '{:<11s}' * (len(operands) - 1)).format(*operands)) + elif not is_opcode_div : + print('%sError: Script not divisible by OpCode length!' % (' ' * (padd + 16))) + elif not is_begin_end : + print('%sError: Script lacks Begin and/or End OpCodes!' % (' ' * (padd + 16))) + elif not is_bgst : + print('%sError: BIOS Guard Script Tool dependency missing!' % (' ' * (padd + 16))) + + # The payload of sub-PFS PFAT Entries is not in proper order by default + # We can get each payload's order from PFAT Script > OpCode #2 (set I0 imm) + # PFAT Script OpCode #2 > Operand #3 stores the payload Offset in final image + pfat_entry_off = int.from_bytes(pfat_script_data[0xC:0x10], 'little') + + # Parse sub-PFS PFAT Entry/Block Metadata + if len(pfat_entry_met) >= pfat_met_size : + # Get sub-PFS PFAT Metadata Structure values + pfat_met = get_struct(pfat_entry_met, 0, PFS_PFAT_MET, [pfat_entry_index], padd + 12) + + # Show sub-PFS PFAT Metadata Structure info + if is_verbose : pfat_met.pfs_print(padd + 16) + + # Another way to get each PFAT Entry payload's Order is from its Metadata at 0x8-0xC, if applicable + # Check that the PFAT Entry payload Order/Offset from PFAT Script matches the one from PFAT Metadata + if pfat_entry_off != pfat_met.OffsetBase : + msg_print(padd + 16, 'Error: Detected sub-PFS PFAT Entry Metadata & PFAT Base Offset mismatch!') + pfat_entry_off = pfat_met.OffsetBase # Prefer Offset from Metadata, in case PFAT Script differs + + # Check that the PFAT Entry payload Size from PFAT Header matches the one from PFAT Metadata + if pfat_hdr.DataSize != pfat_met.BlockSize : + msg_print(padd + 16, 'Error: Detected sub-PFS PFAT Entry Metadata & PFAT Block Size mismatch!') + + # Get sub-PFS Entry Raw Data by subtracting PFAT Header & Script from PFAT Entry Data + pfat_entry_data_raw = pfat_entry_data[pfat_hdr_bgs_size:] + + # The sub-PFS Entry Raw Data (w/o PFAT Header & Script) should match with the PFAT Block payload + if pfat_entry_data_raw != pfat_payload_data : + msg_print(padd + 16, 'Error: Detected sub-PFS PFAT Entry w/o PFAT & PFAT Block Data mismatch!') + pfat_entry_data_raw = pfat_payload_data # Prefer Data from PFAT Block, in case PFAT Entry differs + + # Store each sub-PFS PFAT Entry Order/Offset and Payload/Raw Data (w/o PFAT) + pfat_data_all.append((pfat_entry_off, pfat_entry_data_raw)) + + pfat_entry_start = pfat_next_entry # Next sub-PFS PFAT Entry starts after sub-PFS Entry Metadata Signature + + pfat_entry_index += 1 + + pfat_data_all.sort() # Sort all sub-PFS PFAT Entries payloads/data based on their Order/Offset + + entry_data = b'' # Initialize new sub-PFS Entry Data + for pfat_data in pfat_data_all : entry_data += pfat_data[1] # Merge all sub-PFS PFAT Entry Payload/Raw into the final sub-PFS Entry Data + + # Verify that the Order/Offset of the last PFAT Entry w/ its Size matches the final sub-PFS Entry Data Size + if len(entry_data) != pfat_data_all[-1][0] + len(pfat_data_all[-1][1]) : + msg_print(padd + 8, 'Error: Detected sub-PFS PFAT Entry Buffer & Last Offset Size mismatch!') + + # Analyze sub-PFS Footer Structure + chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padd + 4) + + return entry_data + +# Get Dell PFS Entry Structure & Size via its Version +def get_pfs_entry(buffer, offset) : + pfs_entry_ver = int.from_bytes(buffer[offset + 0x10:offset + 0x14], 'little') # PFS Entry Version + + if pfs_entry_ver == 1 : return PFS_ENTRY_R1, ctypes.sizeof(PFS_ENTRY_R1) + if pfs_entry_ver == 2 : return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2) + + return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2) + +# Determine Dell PFS Entry Version string +def get_entry_ver(version_fields, version_types, msg_padd) : + version = '' # Initialize Version string + + # Each Version Type (1 byte) determines the type of each Version Value (2 bytes) + # Version Type 'N' is Number, 'A' is Text and ' ' is Empty/Unused + for idx in range(len(version_fields)) : + eol = '' if idx == len(version_fields) - 1 else '.' + + if version_types[idx] == 65 : version += '%X%s' % (version_fields[idx], eol) # 0x41 = ASCII + elif version_types[idx] == 78 : version += '%d%s' % (version_fields[idx], eol) # 0x4E = Number + elif version_types[idx] in (0, 32) : version = version.strip('.') # 0x00 or 0x20 = Unused + else : + version += '%X%s' % (version_fields[idx], eol) # Unknown + msg_print(msg_padd, 'Error: Unknown PFS Entry Version Type 0x%0.2X!' % version_types[idx]) + + return version + +# Check if Dell PFS Header Version is known +def chk_hdr_ver(version, text, padd) : + if version in (1,2) : return + + msg_print(padd, 'Error: Unknown %s Header Version %d!' % (text, version)) + +# Analyze Dell PFS Footer Structure +def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padd) : + # Get PFS Footer Structure values + pfs_ftr = get_struct(footer_buffer, 0, PFS_DELL_FTR, None, padd + 8) + + # Validate that a PFS Footer was parsed + if pfs_ftr.Tag == b'PFS.FTR.' : + # Show PFS Footer Structure info + if is_verbose : pfs_ftr.pfs_print(padd + 8) + else : + msg_print(padd + 4, 'Error: %s Footer could not be found!' % text) + + # Validate that PFS Header Payload Size matches the one at PFS Footer + if data_size != pfs_ftr.PayloadSize : + msg_print(padd + 4, 'Error: %s Header & Footer Payload Size mismatch!' % text) + + # Calculate the PFS Payload Data CRC-32 w/ Vector 0 + pfs_ftr_crc = ~zlib.crc32(data_buffer, 0) & 0xFFFFFFFF + + # Validate PFS Payload Data Checksum via PFS Footer + if pfs_ftr.Checksum != pfs_ftr_crc : + msg_print(padd + 4, 'Error: Invalid %s Footer Payload Checksum!' % text) + +# Write/Extract Dell PFS Entry Files (Data, Metadata, Signature) +def pfs_file_write(bin_buff, bin_name, bin_type, out_path, padd, pfs_idx, pfs_name, file_idx, file_name, file_ver, output_path) : + full_name = '%d%s -- %d %s v%s' % (pfs_idx, pfs_name, file_idx, file_name, file_ver) # Full PFS Entry Name + safe_name = re.sub(win_char_bad, '_', full_name) # Replace common Windows reserved/illegal filename characters + + # Store Data/Metadata Signature (advanced users only) + if bin_name.startswith('sign') : + final_name = '%s.%s.sig' % (safe_name, bin_name.split('_')[1]) + final_path = os.path.join(output_path, final_name) + + with open(final_path, 'wb') as pfs_out : pfs_out.write(bin_buff) # Write final Data/Metadata Signature + + return # Skip further processing for Signatures + + # Store Data/Metadata Payload + bin_ext = '.%s.bin' % bin_name if is_advanced else '.bin' # Simpler Data/Metadata Extension for non-advanced users + + # Some Data may be Text or XML files with useful information for non-advanced users + is_text,final_data,file_ext,write_mode = bin_is_text(bin_buff, bin_type, bin_name == 'meta', is_advanced, is_verbose, padd) + + final_name = '%s%s' % (safe_name, bin_ext[:-4] + file_ext if is_text else bin_ext) + final_path = os.path.join(out_path, final_name) + + with open(final_path, write_mode) as pfs_out : pfs_out.write(final_data) # Write final Data/Metadata Payload + +# Check if Dell PFS Entry file/data is Text/XML and Convert +def bin_is_text(buffer, file_type, is_metadata, is_advanced, is_verbose, pfs_padd) : + is_text = False + write_mode = 'wb' + extension = '.bin' + buffer_in = buffer + + if b',END' in buffer[-0x8:] : # Text Type 1 + is_text = True + write_mode = 'w' + extension = '.txt' + buffer = buffer.decode('utf-8').split(',END')[0].replace(';','\n') + elif buffer.startswith(b'VendorName=Dell') : # Text Type 2 + is_text = True + write_mode = 'w' + extension = '.txt' + buffer = buffer.split(b'\x00')[0].decode('utf-8').replace(';','\n') + elif b'|]' + +# Initialize Dell PFS input file list +pfs_input_images = [] + +# Process input files +if len(sys.argv) >= 2 : + # Drag & Drop or CLI + if args.input_dir : + input_path_user = get_absolute_path(args.input_dir) + pfs_input_images = get_path_files(input_path_user) + else : + pfs_input_images = [image.name for image in args.images] +else : + # Script w/o parameters + input_path_user = get_absolute_path(input('\nEnter input directory path: ')) + pfs_input_images = get_path_files(input_path_user) + +# Initialize global variables +exit_code = len(pfs_input_images) # Initialize exit code with input file count +is_advanced = bool(args.advanced) # Set Advanced user mode optional argument +is_verbose = bool(args.verbose) # Set Verbose output mode optional argument + +# Initialize Dell PFS Update Extractor +if __name__ == '__main__': + sys.exit(main(exit_code, pfs_input_images)) \ No newline at end of file diff --git a/README.md b/README.md index 9048a7f..fb8804c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ BIOS Utilities Donation via Paypal or Debit/Credit Card -* [**Dell PFS BIOS Extractor**](#dell-pfs-bios-extractor) +* [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) * [**AMI UCP BIOS Extractor**](#ami-ucp-bios-extractor) * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) * [**Phoenix SCT BIOS Extractor**](#phoenix-sct-bios-extractor) @@ -20,13 +20,13 @@ * [**Apple EFI IM4P Splitter**](#apple-efi-im4p-splitter) * [**Apple EFI Package Extractor**](#apple-efi-package-extractor) -## **Dell PFS BIOS Extractor** +## **Dell PFS Update Extractor** -![](https://i.imgur.com/Oy1IkcW.png) +![](https://i.imgur.com/5WaGPPl.png) #### **Description** -Parses Dell PFS BIOS images and extracts their SPI/BIOS/UEFI firmware components. It supports all Dell PFS revisions and formats, including those which are originally LZMA compressed in ThinOS packages, ZLIB compressed or split in chunks. The output comprises only final firmware components which are directly usable by end users. An optional Advanced user mode is available as well, which additionally extracts firmware Signatures and more Metadata. +Parses Dell PFS Update images and extracts their Firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and Utilities (e.g. Flasher etc) component sections. It supports all Dell PFS revisions and formats, including those which are originally LZMA compressed in ThinOS packages, ZLIB compressed or Intel BIOS Guard (PFAT) protected. The output comprises only final firmware components which are directly usable by end users. An optional advanced user mode is available as well, which additionally extracts firmware Signatures and more Metadata. #### **Usage** @@ -34,6 +34,10 @@ You can either Drag & Drop or manually enter the full path of a folder containin * -h or --help : show help message and exit * -a or --advanced : extract in advanced user mode +* -v or --verbose : show PFS structure information +* -e or --auto-exit : skip press enter to exit prompts +* -o or --output-dir : extract in given output directory +* -i or --input-dir : extract from given input directory #### **Download** @@ -45,7 +49,9 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 #### **Prerequisites** -To run the utility, you do not need any 3rd party tool. +To decompile the Intel BIOS Guard Scripts via the Python script, you need to additionally have the following 3rd party Python utility at the same directory: + +* [BIOS Guard Script Tool](https://github.com/allowitsme/big-tool/tree/sdk-compat) (i.e. big_script_tool.py) #### **Build/Freeze/Compile with PyInstaller** @@ -59,7 +65,11 @@ PyInstaller can build/freeze/compile the utility at all three supported platform > pip3 install pyinstaller -3. Build/Freeze/Compile: +3. Copy BIOS Guard Script Tool dependency to build directory: + +> Dell_PFS_Extract.py, big_script_tool.py + +4. Build/Freeze/Compile: > pyinstaller --noupx --onefile Dell_PFS_Extract.py @@ -73,6 +83,12 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![](https://i.imgur.com/LCsUknA.png) +![](https://i.imgur.com/TcARQpk.png) + +![](https://i.imgur.com/UWCx75g.png) + +![](https://i.imgur.com/1rokMss.png) + ## **AMI UCP BIOS Extractor** ![](https://i.imgur.com/6YWoMGk.png) From e56aa66895dd42fea8928c5e170229fc46ffa0a7 Mon Sep 17 00:00:00 2001 From: platomav Date: Wed, 5 Jan 2022 16:44:58 +0200 Subject: [PATCH 02/52] Dell PFS Update Extractor v5.1 Fixed BIOS Guard (PFAT) PFS Entry Signature parsing bug Minor improvement in BIOS Guard (PFAT) PFS Entry detection --- Dell PFS Update Extractor/Dell_PFS_Extract.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dell PFS Update Extractor/Dell_PFS_Extract.py b/Dell PFS Update Extractor/Dell_PFS_Extract.py index c094e8b..af1ad75 100644 --- a/Dell PFS Update Extractor/Dell_PFS_Extract.py +++ b/Dell PFS Update Extractor/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Dell PFS Update Extractor Copyright (C) 2018-2021 Plato Mavropoulos """ -title = 'Dell PFS Update Extractor v5.0' +title = 'Dell PFS Update Extractor v5.1' import sys @@ -702,7 +702,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd) : pfat_entry_hdr = get_struct(entry_data, 0, PFS_DELL_HDR, None, pfs_padd + 8) # Possible PFS PFAT Entry if len(entry_data) - pfat_hdr_off >= pfat_hdr_size : pfat_hdr = get_struct(entry_data, pfat_hdr_off, PFS_PFAT_HDR, [0], pfs_padd + 8) - is_pfat = bytes(pfat_hdr.PlatformID).startswith((b'Dell',b'DELL')) + is_pfat = bytes(pfat_hdr.PlatformID).upper().startswith(b'DELL') # Parse PFS Entry which contains sub-PFS Volume with PFAT Payload if pfat_entry_hdr.Tag == b'PFS.HDR.' and is_pfat : @@ -894,7 +894,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padd) : msg_print(padd + 16, 'Error: Detected sub-PFS PFAT Entry Header & PFAT Size mismatch!') # Parse sub-PFS PFAT Signature, if applicable (only when PFAT Header > SFAM flag is set) - if pfat_flag_sig and pfat_payload[pfat_payload_end:pfat_payload_end + pfat_sig_size] == pfat_sig_size : + if pfat_flag_sig and len(pfat_payload[pfat_payload_end:pfat_payload_end + pfat_sig_size]) == pfat_sig_size : # Get sub-PFS PFAT Signature Structure values pfat_sig = get_struct(pfat_payload, pfat_payload_end, PFS_PFAT_SIG, [pfat_entry_index], padd + 12) From 0f05e9b0bea7777407c04c5bd9d995467a3c1f1a Mon Sep 17 00:00:00 2001 From: platomav Date: Mon, 14 Mar 2022 00:57:18 +0200 Subject: [PATCH 03/52] Insyde iFlash Image Extractor v1.0 Parses Insyde iFlash images and extracts their raw components (e.g. SPI/BIOS/UEFI, EC, ME, Flasher, Configuration etc) --- .../Insyde_iFlash_Extract.py | 270 ++++++++++++++++++ LICENSE | 2 +- README.md | 57 ++++ 3 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 Insyde iFlash Image Extractor/Insyde_iFlash_Extract.py diff --git a/Insyde iFlash Image Extractor/Insyde_iFlash_Extract.py b/Insyde iFlash Image Extractor/Insyde_iFlash_Extract.py new file mode 100644 index 0000000..d8ca64c --- /dev/null +++ b/Insyde iFlash Image Extractor/Insyde_iFlash_Extract.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Insyde iFlash Extract +Insyde iFlash Image Extractor +Copyright (C) 2022 Plato Mavropoulos +""" + +title = 'Insyde iFlash Image Extractor v1.0' + +import sys + +# Detect Python version +sys_py = sys.version_info + +# Check Python version +if sys_py < (3,7): + sys.stdout.write('%s\n\nError: Python >= 3.7 required, not %d.%d!\n' % (title, sys_py[0], sys_py[1])) + + if '--auto-exit' not in sys.argv and '-e' not in sys.argv: + (raw_input if sys_py[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 + + sys.exit(1) + +# Detect OS platform +sys_os = sys.platform + +# Check OS platform +if sys_os == 'win32': + sys.stdout.reconfigure(encoding='utf-8') # Fix Windows Unicode console redirection +elif sys_os.startswith('linux') or sys_os == 'darwin' or sys_os.find('bsd') != -1: + pass # Supported/Tested +else: + print('%s\n\nError: Unsupported platform "%s"!\n' % (title, sys_os)) + + if '--auto-exit' not in sys.argv and '-e' not in sys.argv: input('Press enter to exit') + + sys.exit(1) + +# Python imports +import os +import re +import ctypes +import inspect +import pathlib +import argparse +import traceback + +# Set ctypes Structure types +char = ctypes.c_char +uint32_t = ctypes.c_uint + +class IflashHeader(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Signature', char*9), # 0x00 $_IFLASH_ + ('ImageTag', char*7), # 0x08 + ('TotalSize', uint32_t), # 0x10 from header end + ('ImageSize', uint32_t), # 0x14 from header end + # 0x18 + ] + + def ifl_print(self, padd): + p = ' ' * (padd - 1) + + print(p, 'Signature : %s' % self.Signature.decode('utf-8','ignore')) + print(p, 'Image Name: %s' % self.ImageTag.decode('utf-8','ignore')) + print(p, 'Total Size: 0x%X' % self.TotalSize) + print(p, 'Image Size: 0x%X' % self.ImageSize) + +class InsydeIflash: + def __init__(self, in_data, out_path, in_padd, in_verbose): + self.fw_data = in_data + self.ex_path = out_path + self.padding = in_padd + self.verbose = in_verbose + + self.hdr_len = ctypes.sizeof(IflashHeader) + + self.mod_names = { + 'DRV_IMG':['isflash','efi'], + 'INI_IMG':['platform','ini'], + 'BIOSIMG':['BIOS','bin'], + 'ME_IMG_':['ME','bin'], + 'EC_IMG_':['EC','bin'], + 'OEM_ID_':['OEM_ID','bin'], + 'BIOSCER':['Certificate','bin'], + 'BIOSCR2':['Certificate_2','bin'], + } + + def iflash_parse(self): + all_ins_ifl = pat_ins_ifl.finditer(self.fw_data) + + if not all_ins_ifl: return 1 + + if not os.path.isdir(self.ex_path): os.mkdir(self.ex_path) + + for ins_ifl in all_ins_ifl: + ifl_off = ins_ifl.start() + + ifl_hdr = get_struct(self.fw_data, ifl_off, IflashHeader) + + if self.verbose: + print('\n%sInsyde iFlash Module @ 0x%0.8X\n' % (' ' * self.padding, ifl_off)) + + ifl_hdr.ifl_print(self.padding + 4) + + mod_bgn = ifl_off + self.hdr_len + mod_end = mod_bgn + ifl_hdr.ImageSize + mod_bin = self.fw_data[mod_bgn:mod_end] + + if not mod_bin: continue # Empty/Missing Module + + mod_tag = ifl_hdr.ImageTag.decode('utf-8','ignore') + out_tag = self.mod_names[mod_tag][0] if mod_tag in self.mod_names else mod_tag + out_ext = self.mod_names[mod_tag][1] if mod_tag in self.mod_names else 'bin' + + out_name = get_safe_name('%s [0x%0.8X-0x%0.8X].%s' % (out_tag, mod_bgn, mod_end, out_ext)) + out_path = os.path.join(self.ex_path, out_name) + + with open(out_path, 'wb') as out: out.write(mod_bin) + + print('\n%sExtracted' % (' ' * (self.padding + 8 if self.verbose else self.padding)), out_name) + + return 0 + +# Process ctypes Structure Classes +# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky +def get_struct(buffer, start_offset, class_name, param_list=None): + if param_list is None: param_list = [] + + structure = class_name(*param_list) # Unpack parameter list + struct_len = ctypes.sizeof(structure) + struct_data = buffer[start_offset:start_offset + struct_len] + fit_len = min(len(struct_data), struct_len) + + ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len) + + return structure + +# Get absolute file path (argparse object) +def get_absolute_path(argparse_path): + if not argparse_path: + absolute_path = get_script_dir() # Use input file directory if no user path is specified + else: + # Check if user specified path is absolute, otherwise convert it to input file relative + if pathlib.Path(argparse_path).is_absolute(): absolute_path = argparse_path + else: absolute_path = os.path.join(get_script_dir(), argparse_path) + + return absolute_path + +# Get list of files from absolute path +def get_path_files(abs_path): + file_list = [] # Initialize list of files + + # Traverse input absolute path + for root,_,files in os.walk(abs_path): + file_list = [os.path.join(root, name) for name in files] + + return file_list + +# Fix illegal/reserved Windows characters +def get_safe_name(file_name): + raw_name = repr(file_name).strip("'") + + return re.sub(r'[\\/*?:"<>|]', '_', raw_name) + +# Get python script working directory +# https://stackoverflow.com/a/22881871 by jfs +def get_script_dir(follow_symlinks=True): + if getattr(sys, 'frozen', False): + path = os.path.abspath(sys.executable) + else: + path = inspect.getabsfile(get_script_dir) + if follow_symlinks: + path = os.path.realpath(path) + + return os.path.dirname(path) + +# Pause after any unexpected Python exception +# https://stackoverflow.com/a/781074 by Torsten Marek +def show_exception_and_exit(exc_type, exc_value, tb): + if exc_type is KeyboardInterrupt : + print('\n') + else: + print('\nError: %s crashed, please report the following:\n' % title) + traceback.print_exception(exc_type, exc_value, tb) + if not bool(args.auto_exit): input('\nPress enter to exit') + + sys.exit(1) # Crash exceptions are critical + +# Insyde iFlash Section Signature +pat_ins_ifl = re.compile(br'\$_IFLASH_') + +if __name__ == '__main__': + # Show script title + print('\n' + title) + + # Set console/shell window title + user_os = sys.platform + if user_os == 'win32': ctypes.windll.kernel32.SetConsoleTitleW(title) + elif user_os.startswith('linux') or user_os == 'darwin' or user_os.find('bsd') != -1: sys.stdout.write('\x1b]2;' + title + '\x07') + + # Set argparse Arguments + parser = argparse.ArgumentParser() + parser.add_argument('images', type=argparse.FileType('r'), nargs='*') + parser.add_argument('-v', '--verbose', help='show iFlash structure information', action='store_true') + parser.add_argument('-e', '--auto-exit', help='skip press enter to exit prompts', action='store_true') + parser.add_argument('-o', '--output-dir', help='extract in given output directory') + parser.add_argument('-i', '--input-dir', help='extract from given input directory') + args = parser.parse_args() + + # Set pause-able Python exception handler (must be after args) + sys.excepthook = show_exception_and_exit + + # Initialize Dell PFS input file list + iflash_input_images = [] + + # Process input files + if len(sys.argv) >= 2: + # Drag & Drop or CLI + if args.input_dir: + input_path_user = get_absolute_path(args.input_dir) + iflash_input_images = get_path_files(input_path_user) + else: + iflash_input_images = [image.name for image in args.images] + + output_path_user = get_absolute_path(args.output_dir or args.input_dir) + else: + # Script w/o parameters + input_path_user = get_absolute_path(input('\nEnter input directory path: ')) + iflash_input_images = get_path_files(input_path_user) + + output_path_user = get_absolute_path(input('\nEnter output directory path: ')) + + # Initialize global variables + exit_code = len(iflash_input_images) # Initialize exit code with input file count + is_verbose = bool(args.verbose) # Set Verbose output mode optional argument + + for input_file in iflash_input_images: + input_name = os.path.basename(input_file) + input_padd = 8 + + print('\n*** %s' % input_name) + + # Check if input file exists + if not os.path.isfile(input_file): + print('\n%sError: This input file does not exist!' % (' ' * input_padd)) + continue # Next input file + + with open(input_file, 'rb') as in_file: input_data = in_file.read() + + # Search input image for Insyde iFlash Sections + is_ins_ifl = pat_ins_ifl.search(input_data) + + if not is_ins_ifl: + print('\n%sError: This is not an Insyde iFlash image!' % (' ' * input_padd)) + continue # Next input file + + # Set main extraction path (optional user specified path taken into account) + output_path = os.path.join(output_path_user, input_name + '_extracted') + + InsydeIflash(input_data, output_path, input_padd, is_verbose).iflash_parse() + + exit_code -= 1 # Adjust exit code to reflect extraction progress + + if not bool(args.auto_exit): input('\nDone!') + + sys.exit(exit_code) diff --git a/LICENSE b/LICENSE index 47e82f4..06831fb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2021 Plato Mavropoulos +Copyright (c) 2019-2022 Plato Mavropoulos Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.md b/README.md index fb8804c..0f77960 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ * [**AMI UCP BIOS Extractor**](#ami-ucp-bios-extractor) * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) * [**Phoenix SCT BIOS Extractor**](#phoenix-sct-bios-extractor) +* [**Insyde iFlash Image Extractor**](#insyde-iflash-image-extractor) * [**Portwell EFI BIOS Extractor**](#portwell-efi-bios-extractor) * [**Panasonic BIOS Update Extractor**](#panasonic-bios-update-extractor) * [**VAIO Packaging Manager Extractor**](#vaio-packaging-manager-extractor) @@ -262,6 +263,62 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![](https://i.imgur.com/Td6F5mm.png) +## **Insyde iFlash Image Extractor** + +![](https://i.imgur.com/13GJjwO.png) + +#### **Description** + +Parses Insyde iFlash images and extracts their raw components (e.g. SPI/BIOS/UEFI, EC, ME, Flasher, Configuration etc). + +#### **Usage** + +You can either Drag & Drop or manually enter the full path of a folder containing Insyde iFlash images. Optional arguments: + +* -h or --help : show help message and exit +* -v or --verbose : show iFlash structure information +* -e or --auto-exit : skip press enter to exit prompts +* -o or --output-dir : extract in given output directory +* -i or --input-dir : extract from given input directory + +#### **Download** + +An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. + +#### **Prerequisites** + +To run the utility, you do not need any 3rd party tool. + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.7.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Build/Freeze/Compile: + +> pyinstaller --noupx --onefile Insyde_iFlash_Extract.py + +At dist folder you should find the final utility executable + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![](https://i.imgur.com/Pn4JNiG.png) + ## **Portwell EFI BIOS Extractor** ![](https://i.imgur.com/ySdUSgf.png) From 2029ffc8b78b19acf61124979cd07b7d94e7a69a Mon Sep 17 00:00:00 2001 From: platomav Date: Mon, 28 Mar 2022 00:58:07 +0300 Subject: [PATCH 04/52] Apple EFI Package Grabber v2.0 Parses user-provided (DB) list of Apple Software Update CatalogURL .sucatalog links and saves all newer (since last run) EFI firmware package links into a text file. It removes any xml formatting, ignores false positives, removes duplicate links and sorts them in alphabetical order for easy comparison afterwards. --- Apple EFI Package Grabber/Apple_EFI_Grab.dat | 4 + Apple EFI Package Grabber/Apple_EFI_Grab.py | 129 ++++++++++++++++++ .../Apple_EFI_Links.py | 71 ---------- README.md | 12 +- 4 files changed, 139 insertions(+), 77 deletions(-) create mode 100644 Apple EFI Package Grabber/Apple_EFI_Grab.dat create mode 100644 Apple EFI Package Grabber/Apple_EFI_Grab.py delete mode 100644 Apple EFI Sucatalog Link Grabber/Apple_EFI_Links.py diff --git a/Apple EFI Package Grabber/Apple_EFI_Grab.dat b/Apple EFI Package Grabber/Apple_EFI_Grab.dat new file mode 100644 index 0000000..5740129 --- /dev/null +++ b/Apple EFI Package Grabber/Apple_EFI_Grab.dat @@ -0,0 +1,4 @@ +2021-01-01 00:00:00 + + + diff --git a/Apple EFI Package Grabber/Apple_EFI_Grab.py b/Apple EFI Package Grabber/Apple_EFI_Grab.py new file mode 100644 index 0000000..c4d544b --- /dev/null +++ b/Apple EFI Package Grabber/Apple_EFI_Grab.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Apple EFI Grab +Apple EFI Package Grabber +Copyright (C) 2018-2021 Plato Mavropoulos +""" + +title = 'Apple EFI Package Grabber v2.0' + +print('\n' + title) + +import sys + +sys_ver = sys.version_info +if sys_ver < (3,7): + sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1])) + (raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 + sys.exit(1) + +import traceback + +def show_exception_and_exit(exc_type, exc_value, tb): + if exc_type is KeyboardInterrupt: + print('\nNote: Keyboard Interrupt!') + else: + print('\nError: %s crashed, please report the following:\n' % title) + traceback.print_exception(exc_type, exc_value, tb) + input('\nPress enter to exit') + + sys.exit(1) + +sys.excepthook = show_exception_and_exit + +import datetime +import urllib.request +from multiprocessing.pool import ThreadPool + +def fetch_cat_info(name): + url = cat_url[:-len('others/')] + name if name in ['index.sucatalog','index-1.sucatalog'] else cat_url + name + with urllib.request.urlopen(urllib.request.Request(url, method='HEAD')) as head : mod = head.headers['last-modified'] + + return name, url, mod + +def fetch_cat_links(cat_file): + cat_links = [] + + with urllib.request.urlopen(cat_file[1]) as link: fdata = link.readlines() + + cat_lines = [l.decode('utf-8').strip('\n') for l in fdata] + + for line in cat_lines: + if ('.pkg' in line or '.tar' in line) and ('FirmwareUpd' in line or '/BridgeOSUpdateCustomer' in line or 'EFIUpd' in line) \ + and 'Bluetooth' not in line and 'DPVGA' not in line and 'Thunderbolt' not in line and 'PMG5' not in line and 'HardDrive' not in line: + down_link = line[line.find('http'):(line.find('.pkg') if '.pkg' in line else line.find('.tar')) + 4] + down_link = down_link.replace('http:','https:') + cat_links.append(down_link) + + return cat_links + +dat_db = 'Apple_EFI_Grab.dat' +cat_url = 'https://swscan.apple.com/content/catalogs/others/' +apple_cat = [] +down_links = [] +svr_date = None +thread_num = 2 + +with open(dat_db, 'r', encoding='utf-8') as dat: db_lines = dat.readlines() +db_lines = [line.strip('\n') for line in db_lines] + +db_date = datetime.datetime.strptime(db_lines[0], '%Y-%m-%d %H:%M:%S') +db_links = set([line for line in db_lines if line.startswith('https')]) +db_sucat = [line for line in db_lines if line.startswith('index')] + +print('\nGetting Catalog Listing...') + +if not db_sucat: + input('\nError: Failed to retrieve Catalogs from DB!\n\nDone!') + sys.exit(1) + +apple_mod = ThreadPool(thread_num).imap_unordered(fetch_cat_info, db_sucat) + +for name, url, mod in apple_mod: + dt = datetime.datetime.strptime(mod, '%a, %d %b %Y %H:%M:%S %Z') + if not svr_date or dt > svr_date : svr_date = dt + + apple_cat.append((name, url, dt)) + +if not svr_date: + input('\nError: Failed to retrieve Current Catalog Datetime!\n\nDone!') + sys.exit(1) + +print('\n Previous Catalog Datetime :', db_date) +print(' Current Catalog Datetime :', svr_date) + +if svr_date <= db_date: + input('\nNothing new since %s!\n\nDone!' % db_date) + sys.exit() + +print('\nGetting Catalog Links...') + +down_links = ThreadPool(thread_num).imap_unordered(fetch_cat_links, apple_cat) +down_links = [item for sublist in down_links for item in sublist] + +if not down_links: + input('\nError: Failed to retrieve Catalog Links!\n\nDone!') + sys.exit(1) + +new_links = sorted(list(dict.fromkeys([link for link in down_links if link not in db_links]))) + +if new_links: + print('\nFound %d new link(s) between %s and %s!' % (len(new_links), db_date, svr_date)) + + cur_date = datetime.datetime.utcnow().isoformat(timespec='seconds').replace('-','').replace('T','').replace(':','') # Local UTC Unix + + with open('Apple_%s.txt' % cur_date, 'w', encoding='utf-8') as lout: lout.write('\n'.join(map(str, new_links))) +else: + print('\nThere are no new links between %s and %s!' % (db_date, svr_date)) + +new_db_sucat = '\n'.join(map(str, db_sucat)) + +new_db_links = '\n'.join(map(str, sorted(list(dict.fromkeys(down_links))))) + +new_db_lines = '%s\n\n%s\n\n%s' % (svr_date, new_db_sucat, new_db_links) + +with open(dat_db, 'w', encoding='utf-8') as dbout: dbout.write(new_db_lines) + +input('\nDone!') \ No newline at end of file diff --git a/Apple EFI Sucatalog Link Grabber/Apple_EFI_Links.py b/Apple EFI Sucatalog Link Grabber/Apple_EFI_Links.py deleted file mode 100644 index 9e9c124..0000000 --- a/Apple EFI Sucatalog Link Grabber/Apple_EFI_Links.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 - -""" -Apple EFI Links -Apple EFI Sucatalog Link Grabber -Copyright (C) 2018-2019 Plato Mavropoulos -""" - -print('Apple EFI Sucatalog Link Grabber v1.2\n') - -import os -import sys -import datetime - -# Remove previous output files -if os.path.isfile('OUT.txt') : os.remove('OUT.txt') - -# Get input catalog file paths -if len(sys.argv) >= 2 : - # Drag & Drop or CLI - catalogs = sys.argv[1:] -else : - # Working directory - catalogs = [] - for root, dirs, files in os.walk(os.getcwd()) : - for name in files : - if name.endswith('.sucatalog') : - catalogs.append(os.path.join(root, name)) - -print('Working...') - -# Parse each input xml file -for input_file in catalogs : - input_name,input_extension = os.path.splitext(os.path.basename(input_file)) - - print('\n%s%s' % (input_name, input_extension)) - - with open(input_file, 'r') as in_file : - for line in in_file : - # Find EFI Firmware package links - if ('.pkg' in line or '.tar' in line) and ('FirmwareUpd' in line or '/BridgeOSUpdateCustomer' in line or 'EFIUpd' in line) \ - and 'Bluetooth' not in line and 'DPVGA' not in line and 'Thunderbolt' not in line and 'PMG5' not in line and 'HardDrive' not in line : - if '.pkg' in line : link = line[line.find('http'):line.find('.pkg') + 4] # Remove xml formatting - else : link = line[line.find('http'):line.find('.tar') + 4] - - with open('OUT.txt', 'a') as out_file : out_file.write(link + '\n') # Store links in temporary output file - -# Parse temporary output file -if os.path.isfile('OUT.txt') : - with open('OUT.txt', 'r+') as out_file : - parsed_lines = [] - final_lines = [] - - for line in out_file : - if line not in parsed_lines : # Remove duplicate links - final_lines.append(line) - parsed_lines.append(line) - - final_lines = ''.join(map(str, sorted(final_lines))) - - current_datetime = datetime.datetime.utcnow().isoformat(timespec='seconds').replace('-','').replace('T','').replace(':','') - - output_file = 'EFI %s.txt' % current_datetime - - with open(output_file, 'w') as efi_file : efi_file.write(final_lines) # Save final output file - - print('\nStored %s!' % output_file) - - os.remove('OUT.txt') # Remove temporary output file - -input('\nDone!') \ No newline at end of file diff --git a/README.md b/README.md index 0f77960..e51f5ac 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ * [**Fujitsu UPC BIOS Extractor**](#fujitsu-upc-bios-extractor) * [**Fujitsu SFX BIOS Extractor**](#fujitsu-sfx-bios-extractor) * [**Award BIOS Module Extractor**](#award-bios-module-extractor) -* [**Apple EFI Sucatalog Link Grabber**](#apple-efi-sucatalog-link-grabber) +* [**Apple EFI Package Grabber**](#apple-efi-package-grabber) * [**Apple EFI File Renamer**](#apple-efi-file-renamer) * [**Apple EFI IM4P Splitter**](#apple-efi-im4p-splitter) * [**Apple EFI Package Extractor**](#apple-efi-package-extractor) @@ -384,17 +384,17 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![](https://i.imgur.com/EhCzMLk.png) -## **Apple EFI Sucatalog Link Grabber** +## **Apple EFI Package Grabber** -![](https://i.imgur.com/zTVFs4I.png) +![](https://i.imgur.com/BaHrjGi.png) #### **Description** -Parses Apple Software Update CatalogURL .sucatalog files and saves all EFI firmware package links into a text file. It removes any xml formatting, ignores false positives, removes duplicate links and sorts them in alphabetical order for easy comparison afterwards. +Parses user-provided (DB) list of Apple Software Update CatalogURL .sucatalog links and saves all newer (since last run) EFI firmware package links into a text file. It removes any xml formatting, ignores false positives, removes duplicate links and sorts them in alphabetical order for easy comparison afterwards. #### **Usage** -You can either Drag & Drop or let it automatically parse any .sucatalog files within its working directory. +First, you need to familiarize a bit with the DB (i.e. Apple_EFI_Grab.dat file). It consists of 3 sections: Last run DateTime (YYYY-MM-DD HH:MM:SS), Sucatalog links to check and EFI Package links which have been gathered so far across all runs. Before running the utility for the fist time, you need to insert the Sucatalog links into the DB, below the 1st line (DateTime). The Sucatalog links in the DB are stored in partial form, starting from "index" string. For example: "https://swscan.apple.com/content/catalogs/others/index-12.merged-1.sucatalog" must be stored as "index-12.merged-1.sucatalog" in the DB. The Sucatalog links are not pre-included in the DB but you can find them online (e.g. https://github.com/zhangyoufu/swscan.apple.com/blob/master/url.txt). #### **Download** @@ -422,7 +422,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 3. Build/Freeze/Compile: -> pyinstaller --noupx --onefile Apple_EFI_Links.py +> pyinstaller --noupx --onefile Apple_EFI_Grab.py At dist folder you should find the final utility executable From 132457afdae01dc70c2706235992d98079d7bec0 Mon Sep 17 00:00:00 2001 From: platomav Date: Fri, 1 Apr 2022 17:43:22 +0300 Subject: [PATCH 05/52] Initial refactor commit Added AMI UCP BIOS Extractor v2.0_a1 Added AMI BIOS Guard Extractor v4.0_a1 --- AMI_PFAT_Extract.py | 352 ++++++++++++++++++++ AMI_UCP_Extract.py | 514 +++++++++++++++++++++++++++++ README.md | 759 ++----------------------------------------- common/a7z_comp.py | 45 +++ common/checksums.py | 13 + common/efi_comp.py | 51 +++ common/externals.py | 11 + common/num_ops.py | 11 + common/path_ops.py | 79 +++++ common/patterns.py | 8 + common/script_get.py | 19 ++ common/struct_ops.py | 24 ++ common/system.py | 79 +++++ common/text_ops.py | 6 + 14 files changed, 1240 insertions(+), 731 deletions(-) create mode 100644 AMI_PFAT_Extract.py create mode 100644 AMI_UCP_Extract.py create mode 100644 common/a7z_comp.py create mode 100644 common/checksums.py create mode 100644 common/efi_comp.py create mode 100644 common/externals.py create mode 100644 common/num_ops.py create mode 100644 common/path_ops.py create mode 100644 common/patterns.py create mode 100644 common/script_get.py create mode 100644 common/struct_ops.py create mode 100644 common/system.py create mode 100644 common/text_ops.py diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py new file mode 100644 index 0000000..42e217b --- /dev/null +++ b/AMI_PFAT_Extract.py @@ -0,0 +1,352 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +AMI PFAT Extract +AMI BIOS Guard Extractor +Copyright (C) 2018-2022 Plato Mavropoulos +""" + +title = 'AMI BIOS Guard Extractor v4.0_a1' + +import os +import re +import sys +import shutil +import ctypes + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.patterns import PAT_AMI_PFAT +from common.externals import get_bgs_tool +from common.num_ops import get_ordinal +from common.text_ops import padder +from common.path_ops import argparse_init, process_input_files, safe_name +from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t +from common.system import nice_exc_handler, check_sys_py, check_sys_os, show_title, print_input + +class AmiBiosGuardHeader(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Size', uint32_t), # 0x00 Header + Entries + ('Checksum', uint32_t), # 0x04 ? + ('Tag', char*8), # 0x04 _AMIPFAT + ('Flags', uint8_t), # 0x10 ? + # 0x11 + ] + + def struct_print(self, padding): + p = padder(padding) + + print(p + 'Size :', '0x%X' % self.Size) + print(p + 'Checksum:', '0x%0.4X' % self.Checksum) + print(p + 'Tag :', self.Tag.decode('utf-8')) + print(p + 'Flags :', '0x%0.2X' % self.Flags) + +class IntelBiosGuardHeader(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('BGVerMajor', uint16_t), # 0x00 + ('BGVerMinor', uint16_t), # 0x02 + ('PlatformID', uint8_t*16), # 0x04 + ('Attributes', uint32_t), # 0x14 + ('ScriptVerMajor', uint16_t), # 0x16 + ('ScriptVerMinor', uint16_t), # 0x18 + ('ScriptSize', uint32_t), # 0x1C + ('DataSize', uint32_t), # 0x20 + ('BIOSSVN', uint32_t), # 0x24 + ('ECSVN', uint32_t), # 0x28 + ('VendorInfo', uint32_t), # 0x2C + # 0x30 + ] + + def get_platform_id(self): + id_byte = bytes(self.PlatformID) + + id_text = re.sub(r'[\n\t\r\x00 ]', '', id_byte.decode('utf-8','ignore')) + + id_hexs = '%0.*X' % (0x10 * 2, int.from_bytes(id_byte, 'big')) + id_guid = '{%s-%s-%s-%s-%s}' % (id_hexs[:8], id_hexs[8:12], id_hexs[12:16], id_hexs[16:20], id_hexs[20:]) + + return '%s %s' % (id_text, id_guid) + + def get_flags(self): + attr = IntelBiosGuardHeaderGetAttributes() + attr.asbytes = self.Attributes + + return attr.b.SFAM, attr.b.ProtectEC, attr.b.GFXMitDis, attr.b.FTU, attr.b.Reserved + + def struct_print(self, padding): + p = padder(padding) + + no_yes = ['No','Yes'] + f1,f2,f3,f4,f5 = self.get_flags() + + print(p + 'BIOS Guard Version :', '%d.%d' % (self.BGVerMajor, self.BGVerMinor)) + print(p + 'Platform Identity :', self.get_platform_id()) + print(p + 'Signed Flash Address Map :', no_yes[f1]) + print(p + 'Protected EC OpCodes :', no_yes[f2]) + print(p + 'Graphics Security Disable :', no_yes[f3]) + print(p + 'Fault Tolerant Update :', no_yes[f4]) + print(p + 'Attributes Reserved :', '0x%X' % f5) + print(p + 'Script Version :', '%d.%d' % (self.ScriptVerMajor, self.ScriptVerMinor)) + print(p + 'Script Size :', '0x%X' % self.ScriptSize) + print(p + 'Data Size :', '0x%X' % self.DataSize) + print(p + 'BIOS Security Version Number:', '0x%X' % self.BIOSSVN) + print(p + 'EC Security Version Number :', '0x%X' % self.ECSVN) + print(p + 'Vendor Information :', '0x%X' % self.VendorInfo) + +class IntelBiosGuardHeaderAttributes(ctypes.LittleEndianStructure): + _fields_ = [ + ('SFAM', uint32_t, 1), # Signed Flash Address Map + ('ProtectEC', uint32_t, 1), # Protected EC OpCodes + ('GFXMitDis', uint32_t, 1), # GFX Security Disable + ('FTU', uint32_t, 1), # Fault Tolerant Update + ('Reserved', uint32_t, 28) # Reserved/Unknown + ] + +class IntelBiosGuardHeaderGetAttributes(ctypes.Union): + _fields_ = [ + ('b', IntelBiosGuardHeaderAttributes), + ('asbytes', uint32_t) + ] + +class IntelBiosGuardSignature2k(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Unknown0', uint32_t), # 0x000 + ('Unknown1', uint32_t), # 0x004 + ('Modulus', uint32_t*64), # 0x008 + ('Exponent', uint32_t), # 0x108 + ('Signature', uint32_t*64), # 0x10C + # 0x20C + ] + + def struct_print(self, padding): + p = padder(padding) + + Modulus = '%0.*X' % (0x100 * 2, int.from_bytes(self.Modulus, 'little')) + Signature = '%0.*X' % (0x100 * 2, int.from_bytes(self.Signature, 'little')) + + print(p + 'Unknown 0:', '0x%X' % self.Unknown0) + print(p + 'Unknown 1:', '0x%X' % self.Unknown1) + print(p + 'Modulus :', '%s [...]' % Modulus[:32]) + print(p + 'Exponent :', '0x%X' % self.Exponent) + print(p + 'Signature:', '%s [...]' % Signature[:32]) + +def get_ami_pfat(input_buffer): + match = PAT_AMI_PFAT.search(input_buffer) + + buffer = input_buffer[match.start() - 0x8:] if match else b'' + + return match, buffer + +def get_file_name(index, title): + return safe_name('%0.2d -- %s' % (index, title)) + +def parse_bg_script(script_data, padding): + is_opcode_div = len(script_data) % 8 == 0 + + if not is_opcode_div: + print('%sError: Script not divisible by OpCode length!' % padder(padding)) + + return 1 + + is_begin_end = script_data[:8] + script_data[-8:] == b'\x01' + b'\x00' * 7 + b'\xFF' + b'\x00' * 7 + + if not is_begin_end: + print('%sError: Script lacks Begin and/or End OpCodes!' % padder(padding)) + + return 2 + + BigScript = get_bgs_tool() + + if not BigScript: + print('%sError: BIOS Guard Script Tool dependency missing!' % padder(padding)) + + return 3 + + script = BigScript(code_bytes=script_data).to_string().replace('\t',' ').split('\n') + + for opcode in script: + if opcode.endswith(('begin','end')): spacing = padder(padding) + elif opcode.endswith(':'): spacing = padder(padding + 4) + else: spacing = padder(padding + 12) + + operands = [operand for operand in opcode.split(' ') if operand] + print(spacing + ('{:<12s}' + '{:<11s}' * (len(operands) - 1)).format(*operands)) + + return 0 + +def parse_pfat_hdr(buffer, padding): + block_all = [] + + pfat_hdr = get_struct(buffer, 0x0, AmiBiosGuardHeader) + + hdr_size = pfat_hdr.Size + hdr_data = buffer[PFAT_AMI_HDR_LEN:hdr_size] + hdr_text = hdr_data.decode('utf-8').splitlines() + + print('\n%sAMI BIOS Guard Header:\n' % padder(padding)) + + pfat_hdr.struct_print(padding + 4) + + hdr_title,*hdr_files = hdr_text + + files_count = len(hdr_files) + + hdr_tag,*hdr_indexes = hdr_title.split('II') + + print('\n%s%s\n' % (padder(padding + 4), hdr_tag)) + + bgt_indexes = [int(h, 16) for h in re.findall(r'.{1,4}', hdr_indexes[0])] if hdr_indexes else [] + + for index,entry in enumerate(hdr_files): + entry_parts = entry.split(';') + + info = entry_parts[0].split() + name = entry_parts[1] + + flags = int(info[0]) + param = info[1] + count = int(info[2]) + + order = get_ordinal((bgt_indexes[index] if bgt_indexes else index) + 1) + + desc = '%s (Index: %0.2d, Flash: %s, Parameter: %s, Flags: 0x%X, Blocks: %d)' % (name, index + 1, order, param, flags, count) + + block_all += [(desc, name, order, param, flags, index, i, count) for i in range(count)] + + _ = [print(padder(padding + 8) + block[0]) for block in block_all if block[6] == 0] + + return block_all, hdr_size, files_count + +def parse_pfat_file(buffer, output_path, padding): + file_path = '' + all_blocks_dict = {} + + extract_name = os.path.basename(output_path) + + extract_path = os.path.join(output_path + '_extracted', '') + + if os.path.isdir(extract_path): shutil.rmtree(extract_path) + + os.mkdir(extract_path) + + block_all,block_off,file_count = parse_pfat_hdr(buffer, padding) + + for block in block_all: + file_desc,file_name,_,_,_,file_index,block_index,block_count = block + + if block_index == 0: + print('\n%s%s' % (padder(padding + 4), file_desc)) + + file_path = os.path.join(extract_path, get_file_name(file_index + 1, file_name)) + + all_blocks_dict[file_index] = b'' + + block_status = '%d/%d' % (block_index + 1, block_count) + + bg_hdr = get_struct(buffer, block_off, IntelBiosGuardHeader) + + print('\n%sIntel BIOS Guard %s Header:\n' % (padder(padding + 8), block_status)) + + bg_hdr.struct_print(padding + 12) + + bg_script_bgn = block_off + PFAT_BLK_HDR_LEN + bg_script_end = bg_script_bgn + bg_hdr.ScriptSize + bg_script_bin = buffer[bg_script_bgn:bg_script_end] + + bg_data_bgn = bg_script_end + bg_data_end = bg_data_bgn + bg_hdr.DataSize + bg_data_bin = buffer[bg_data_bgn:bg_data_end] + + block_off = bg_data_end # Assume next block starts at data end + + is_sfam,_,_,_,_ = bg_hdr.get_flags() # SFAM, ProtectEC, GFXMitDis, FTU, Reserved + + if is_sfam: + bg_sig_bgn = bg_data_end + bg_sig_end = bg_sig_bgn + PFAT_BLK_S2K_LEN + bg_sig_bin = buffer[bg_sig_bgn:bg_sig_end] + + if len(bg_sig_bin) == PFAT_BLK_S2K_LEN: + bg_sig = get_struct(bg_sig_bin, 0x0, IntelBiosGuardSignature2k) + + print('\n%sIntel BIOS Guard %s Signature:\n' % (padder(padding + 8), block_status)) + + bg_sig.struct_print(padding + 12) + + block_off = bg_sig_end # Adjust next block to start at data + signature end + + print('\n%sIntel BIOS Guard %s Script:\n' % (padder(padding + 8), block_status)) + + _ = parse_bg_script(bg_script_bin, padding + 12) + + with open(file_path, 'ab') as out_dat: out_dat.write(bg_data_bin) + + all_blocks_dict[file_index] += bg_data_bin + + pfat_oob_data = buffer[block_off:] # Store out-of-bounds data after the end of PFAT files + + pfat_oob_path = os.path.join(extract_path, get_file_name(file_count + 1, extract_name + '_OOB.bin')) + + with open(pfat_oob_path, 'wb') as out_oob: out_oob.write(pfat_oob_data) + + oob_pfat_match,pfat_oob_buffer = get_ami_pfat(pfat_oob_data) + + if oob_pfat_match: parse_pfat_file(pfat_oob_buffer, pfat_oob_path, padding) + + in_all_data = b''.join([block[1] for block in sorted(all_blocks_dict.items())]) + + in_all_path = os.path.join(extract_path, get_file_name(0, extract_name + '_ALL.bin')) + + with open(in_all_path, 'wb') as out_all: out_all.write(in_all_data + pfat_oob_data) + +PFAT_AMI_HDR_LEN = ctypes.sizeof(AmiBiosGuardHeader) +PFAT_BLK_HDR_LEN = ctypes.sizeof(IntelBiosGuardHeader) +PFAT_BLK_S2K_LEN = ctypes.sizeof(IntelBiosGuardSignature2k) + +if __name__ == '__main__': + # Show script title + show_title(title) + + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Pretty Python exception handler (must be after argparse) + sys.excepthook = nice_exc_handler + + # Check Python Version (must be after argparse) + check_sys_py() + + # Check OS Platform (must be after argparse) + check_sys_os() + + # Process input files and generate output path + input_files,output_path = process_input_files(arguments, sys.argv) + + # Initial output padding count + padding = 4 + + for input_file in input_files: + input_name = os.path.basename(input_file) + + print('\n*** %s' % input_name) + + with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + + pfat_match,pfat_buffer = get_ami_pfat(input_buffer) + + if not pfat_match: + print('\n%sError: This is not an AMI BIOS Guard (PFAT) image!' % padder(padding)) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + parse_pfat_file(pfat_buffer, extract_path, padding) + + print_input('\nDone!') diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py new file mode 100644 index 0000000..c16743a --- /dev/null +++ b/AMI_UCP_Extract.py @@ -0,0 +1,514 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +AMI UCP Extract +AMI UCP BIOS Extractor +Copyright (C) 2021-2022 Plato Mavropoulos +""" + +title = 'AMI UCP BIOS Extractor v2.0_a1' + +import os +import sys +import shutil +import struct +import ctypes +import contextlib + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG +from common.checksums import checksum16 +from common.text_ops import padder +from common.a7z_comp import a7z_decompress, is_7z_supported +from common.efi_comp import efi_decompress, is_efi_compressed +from common.path_ops import argparse_init, process_input_files, safe_name +from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t +from common.system import nice_exc_handler, check_sys_py, check_sys_os, show_title, print_input +from AMI_PFAT_Extract import get_ami_pfat, parse_pfat_file + +class UafHeader(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('ModuleTag', char*4), # 0x00 + ('ModuleSize', uint32_t), # 0x04 + ('Checksum', uint16_t), # 0x08 + ('Unknown0', uint8_t), # 0x0A + ('Unknown1', uint8_t), # 0x0A + ('Reserved', uint32_t), # 0x0C + # 0x10 + ] + + def struct_print(self, padding): + p = padder(padding) + + print(p + 'Tag :', self.ModuleTag.decode('utf-8')) + print(p + 'Size :', '0x%X' % self.ModuleSize) + print(p + 'Checksum :', '0x%0.4X' % self.Checksum) + print(p + 'Unknown 0 :', '0x%0.2X' % self.Unknown0) + print(p + 'Unknown 1 :', '0x%0.2X' % self.Unknown1) + print(p + 'Reserved :', '0x%0.8X' % self.Reserved) + +class UafModule(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('CompressSize', uint32_t), # 0x00 + ('OriginalSize', uint32_t), # 0x04 + # 0x08 + ] + + def struct_print(self, padding, filename): + p = padder(padding) + + print(p + 'Compress Size:', '0x%X' % self.CompressSize) + print(p + 'Original Size:', '0x%X' % self.OriginalSize) + print(p + 'File Name :', filename) + +class UiiHeader(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('UIISize', uint16_t), # 0x00 + ('Checksum', uint16_t), # 0x02 + ('UtilityVersion', uint32_t), # 0x04 AFU|BGT (Unknown, Signed) + ('InfoSize', uint16_t), # 0x08 + ('SupportBIOS', uint8_t), # 0x0A + ('SupportOS', uint8_t), # 0x0B + ('DataBusWidth', uint8_t), # 0x0C + ('ProgramType', uint8_t), # 0x0D + ('ProgramMode', uint8_t), # 0x0E + ('SourceSafeRel', uint8_t), # 0x0F + # 0x10 + ] + + SBI = {1: 'ALL', 2: 'AMIBIOS8', 3: 'UEFI', 4: 'AMIBIOS8/UEFI'} + SOS = {1: 'DOS', 2: 'EFI', 3: 'Windows', 4: 'Linux', 5: 'FreeBSD', 6: 'MacOS', 128: 'Multi-Platform'} + DBW = {1: '16b', 2: '16/32b', 3: '32b', 4: '64b'} + PTP = {1: 'Executable', 2: 'Library', 3: 'Driver'} + PMD = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'} + + def struct_print(self, padding, description): + p = padder(padding) + + SupportBIOS = self.SBI.get(self.SupportBIOS, 'Unknown (%d)' % self.SupportBIOS) + SupportOS = self.SOS.get(self.SupportOS, 'Unknown (%d)' % self.SupportOS) + DataBusWidth = self.DBW.get(self.DataBusWidth, 'Unknown (%d)' % self.DataBusWidth) + ProgramType = self.PTP.get(self.ProgramType, 'Unknown (%d)' % self.ProgramType) + ProgramMode = self.PMD.get(self.ProgramMode, 'Unknown (%d)' % self.ProgramMode) + + print(p + 'UII Size :', '0x%X' % self.UIISize) + print(p + 'Checksum :', '0x%0.4X' % self.Checksum) + print(p + 'Tool Version :', '0x%0.8X' % self.UtilityVersion) + print(p + 'Info Size :', '0x%X' % self.InfoSize) + print(p + 'Supported BIOS:', SupportBIOS) + print(p + 'Supported OS :', SupportOS) + print(p + 'Data Bus Width:', DataBusWidth) + print(p + 'Program Type :', ProgramType) + print(p + 'Program Mode :', ProgramMode) + print(p + 'SourceSafe Tag:', '%0.2d' % self.SourceSafeRel) + print(p + 'Description :', description) + +class DisHeader(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('PasswordSize', uint16_t), # 0x00 + ('EntryCount', uint16_t), # 0x02 + ('Password', char*12), # 0x04 + # 0x10 + ] + + def struct_print(self, padding): + p = padder(padding) + + print(p + 'Password Size:', '0x%X' % self.PasswordSize) + print(p + 'Entry Count :', self.EntryCount) + print(p + 'Password :', self.Password.decode('utf-8')) + +class DisModule(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('EnabledDisabled', uint8_t), # 0x00 + ('ShownHidden', uint8_t), # 0x01 + ('Command', char*32), # 0x02 + ('Description', char*256), # 0x22 + # 0x122 + ] + + ENDIS = {0: 'Disabled', 1: 'Enabled'} + SHOWN = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'} + + def struct_print(self, padding): + p = padder(padding) + + EnabledDisabled = self.ENDIS.get(self.EnabledDisabled, 'Unknown (%d)' % self.EnabledDisabled) + ShownHidden = self.SHOWN.get(self.ShownHidden, 'Unknown (%d)' % self.ShownHidden) + + print(p + 'State :', EnabledDisabled) + print(p + 'Display :', ShownHidden) + print(p + 'Command :', self.Command.decode('utf-8').strip()) + print(p + 'Description:', self.Description.decode('utf-8').strip()) + +# Validate @UAF Module Checksum-16 +def chk16_validate(data, tag, padd=0): + if checksum16(data) != 0: + print_input('\n%sError: Invalid UCP Module %s Checksum!' % (padder(padd), tag)) + else: + print('\n%sChecksum of UCP Module %s is valid!' % (padder(padd), tag)) + +# Get all input file AMI UCP patterns +def get_ami_ucp(buffer): + uaf_len_max = 0x0 # Length of largest detected @UAF + uaf_hdr_off = 0x0 # Offset of largest detected @UAF + uaf_buf_bin = b'' # Buffer of largest detected @UAF + + for uaf in PAT_AMI_UCP.finditer(buffer): + uaf_len_cur = int.from_bytes(buffer[uaf.start() + 0x4:uaf.start() + 0x8], 'little') + + if uaf_len_cur > uaf_len_max: + uaf_len_max = uaf_len_cur + uaf_hdr_off = uaf.start() + uaf_buf_bin = buffer[uaf_hdr_off:uaf_hdr_off + uaf_len_max] + + return uaf_hdr_off, uaf_buf_bin + +# Get list of @UAF Modules +def get_uaf_mod(buffer, uaf_off=0x0): + uaf_all = [] # Initialize list of all @UAF Modules + + while buffer[uaf_off] == 0x40: # ASCII of @ is 0x40 + uaf_hdr = get_struct(buffer, uaf_off, UafHeader) # Parse @UAF Module Structure + + uaf_tag = uaf_hdr.ModuleTag.decode('utf-8') # Get unique @UAF Module Tag + + uaf_all.append([uaf_tag, uaf_off, uaf_hdr]) # Store @UAF Module Info + + uaf_off += uaf_hdr.ModuleSize # Adjust to next @UAF Module offset + + if uaf_off >= len(buffer): break # Stop parsing at EOF + + # Check if @UAF Module NAL exists and place it first + # Parsing NAL first allows naming all @UAF Modules + for mod_idx,mod_val in enumerate(uaf_all): + if mod_val[0] == '@NAL': + uaf_all.insert(1, uaf_all.pop(mod_idx)) # After UII for visual purposes + break # NAL found, skip the rest + + return uaf_all + +# Parse & Extract AMI UCP structures +def ucp_extract(buffer, output_path, padding=0, is_chk16=False): + nal_dict = {} # Initialize @NAL Dictionary per UCP + + print('\n%sUtility Configuration Program' % padder(padding)) + + extract_path = os.path.join(output_path + '_extracted', '') + + if os.path.isdir(extract_path): shutil.rmtree(extract_path) + + os.mkdir(extract_path) + + uaf_hdr = get_struct(buffer, 0, UafHeader) # Parse @UAF Header Structure + + print('\n%sUtility Auxiliary File > @UAF:\n' % padder(padding + 4)) + + uaf_hdr.struct_print(padding + 8) + + fake = struct.pack(' @UAF Module/Section +def uaf_extract(buffer, extract_path, mod_info, padding=0, is_chk16=False, nal_dict=None): + if nal_dict is None: nal_dict = {} + + uaf_tag,uaf_off,uaf_hdr = mod_info + + uaf_data_all = buffer[uaf_off:uaf_off + uaf_hdr.ModuleSize] # @UAF Module Entire Data + + uaf_data_mod = uaf_data_all[UAF_HDR_LEN:] # @UAF Module EFI Data + + uaf_data_raw = uaf_data_mod[UAF_MOD_LEN:] # @UAF Module Raw Data + + print('\n%sUtility Auxiliary File > %s:\n' % (padder(padding), uaf_tag)) + + uaf_hdr.struct_print(padding + 4) # Print @UAF Module Info + + uaf_mod = get_struct(buffer, uaf_off + UAF_HDR_LEN, UafModule) # Parse UAF Module EFI Structure + + is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect @UAF Module EFI Compression + + if uaf_tag in nal_dict: uaf_name = nal_dict[uaf_tag] # Always prefer NAL naming first + elif uaf_tag in UAF_TAG_DICT: uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming + elif uaf_tag == '@ROM': uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature) + elif uaf_tag.startswith('@R0'): uaf_name = 'BIOS_0%s.bin' % uaf_tag[3:] # BIOS/PFAT Firmware + elif uaf_tag.startswith('@S0'): uaf_name = 'BIOS_0%s.sig' % uaf_tag[3:] # BIOS/PFAT Signature + elif uaf_tag.startswith('@DR'): uaf_name = 'DROM_0%s.bin' % uaf_tag[3:] # Thunderbolt Retimer Firmware + elif uaf_tag.startswith('@DS'): uaf_name = 'DROM_0%s.sig' % uaf_tag[3:] # Thunderbolt Retimer Signature + elif uaf_tag.startswith('@EC'): uaf_name = 'EC_0%s.bin' % uaf_tag[3:] # Embedded Controller Firmware + elif uaf_tag.startswith('@ME'): uaf_name = 'ME_0%s.bin' % uaf_tag[3:] # Management Engine Firmware + else: uaf_name = uaf_tag # Could not name the @UAF Module, use Tag instead + + uaf_fext = '' if uaf_name != uaf_tag else '.bin' + + uaf_mod.struct_print(padding + 4, uaf_name + uaf_fext) # Print @UAF Module EFI Info + + # Check if unknown @UAF Module Tag is present in NAL but not in built-in dictionary + if uaf_tag in nal_dict and uaf_tag not in UAF_TAG_DICT and not uaf_tag.startswith(('@ROM','@R0','@S0','@DR','@DS')): + print_input('\n%sNote: Detected new AMI UCP Module %s (%s) in NAL!' % (padder(padding), uaf_tag, nal_dict[uaf_tag])) + + # Generate @UAF Module File name, depending on whether decompression will be required + uaf_fname = os.path.join(extract_path, safe_name(uaf_name + ('.temp' if is_comp else uaf_fext))) + + if is_chk16: chk16_validate(uaf_data_all, uaf_tag, padding + 4) + + # Parse Utility Identification Information @UAF Module (@UII) + if uaf_tag == '@UII': + info_hdr = get_struct(uaf_data_raw, 0, UiiHeader) # Parse @UII Module Raw Structure + + info_data = uaf_data_raw[max(UII_HDR_LEN,info_hdr.InfoSize):info_hdr.UIISize] # @UII Module Info Data + + # Get @UII Module Info/Description text field + info_desc = info_data.decode('utf-8','ignore').strip('\x00 ') + + print('\n%sUtility Identification Information:\n' % padder(padding + 4)) + + info_hdr.struct_print(padding + 8, info_desc) # Print @UII Module Info + + if is_chk16: chk16_validate(uaf_data_raw, '@UII > Info', padding + 8) + + # Store/Save @UII Module Info in file + with open(uaf_fname[:-4] + '.txt', 'a', encoding='utf-8') as uii_out: + with contextlib.redirect_stdout(uii_out): + info_hdr.struct_print(0, info_desc) # Store @UII Module Info + + # Adjust @UAF Module Raw Data for extraction + if is_comp: + # Some Compressed @UAF Module EFI data lack necessary EOF padding + if uaf_mod.CompressSize > len(uaf_data_raw): + comp_padd = b'\x00' * (uaf_mod.CompressSize - len(uaf_data_raw)) + uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw + comp_padd # Add missing padding for decompression + else: + uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw # Add the EFI/Tiano Compression info before Raw Data + else: + uaf_data_raw = uaf_data_raw[:uaf_mod.OriginalSize] # No compression, extend to end of Original @UAF Module size + + # Store/Save @UAF Module file + if uaf_tag != '@UII': # Skip @UII binary, already parsed + with open(uaf_fname, 'wb') as uaf_out: uaf_out.write(uaf_data_raw) + + # @UAF Module EFI/Tiano Decompression + if is_comp and is_efi_compressed(uaf_data_raw, False): + dec_fname = uaf_fname.replace('.temp', uaf_fext) # Decompressed @UAF Module file path + + if efi_decompress(uaf_fname, dec_fname, padding + 4) == 0: + with open(dec_fname, 'rb') as dec: uaf_data_raw = dec.read() # Read back the @UAF Module decompressed Raw data + + os.remove(uaf_fname) # Successful decompression, delete compressed @UAF Module file + + uaf_fname = dec_fname # Adjust @UAF Module file path to the decompressed one + + # Process and Print known text only @UAF Modules (after EFI/Tiano Decompression) + if uaf_tag in UAF_TAG_DICT and UAF_TAG_DICT[uaf_tag][2] == 'Text': + print('\n%s%s:' % (padder(padding + 4), UAF_TAG_DICT[uaf_tag][1])) + print('\n%s%s' % (padder(padding + 8), uaf_data_raw.decode('utf-8','ignore'))) + + # Parse Default Command Status @UAF Module (@DIS) + if len(uaf_data_raw) and uaf_tag == '@DIS': + dis_hdr = get_struct(uaf_data_raw, 0x0, DisHeader) # Parse @DIS Module Raw Header Structure + + print('\n%sDefault Command Status Header:\n' % padder(padding + 4)) + + dis_hdr.struct_print(padding + 8) # Print @DIS Module Raw Header Info + + # Store/Save @DIS Module Header Info in file + with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis: + with contextlib.redirect_stdout(dis): + dis_hdr.struct_print(0) # Store @DIS Module Header Info + + dis_data = uaf_data_raw[DIS_HDR_LEN:] # @DIS Module Entries Data + + # Parse all @DIS Module Entries + for mod_idx in range(dis_hdr.EntryCount): + dis_mod = get_struct(dis_data, mod_idx * DIS_MOD_LEN, DisModule) # Parse @DIS Module Raw Entry Structure + + print('\n%sDefault Command Status Entry %0.2d/%0.2d:\n' % (padder(padding + 8), mod_idx + 1, dis_hdr.EntryCount)) + + dis_mod.struct_print(padding + 12) # Print @DIS Module Raw Entry Info + + # Store/Save @DIS Module Entry Info in file + with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis: + with contextlib.redirect_stdout(dis): + print() + dis_mod.struct_print(4) # Store @DIS Module Entry Info + + os.remove(uaf_fname) # Delete @DIS Module binary, info exported as text + + # Parse Name|Non-AMI List (?) @UAF Module (@NAL) + if len(uaf_data_raw) >= 5 and (uaf_tag,uaf_data_raw[0],uaf_data_raw[4]) == ('@NAL',0x40,0x3A): + nal_info = uaf_data_raw.decode('utf-8','ignore').replace('\r','').strip().split('\n') + + print('\n%s@UAF Module Name List:\n' % padder(padding + 4)) + + # Parse all @NAL Module Entries + for info in nal_info: + info_tag,info_val = info.split(':',1) + + print('%s%s : %s' % (padder(padding + 8), info_tag, info_val)) # Print @NAL Module Tag-Path Info + + nal_dict[info_tag] = os.path.basename(info_val) # Assign a file name (w/o path) to each Tag + + # Parse Insyde BIOS @UAF Module (@INS) + if uaf_tag == '@INS' and is_7z_supported(uaf_fname): + ins_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory + + print('\n%sInsyde BIOS 7z SFX Archive:' % padder(padding + 4)) + + if a7z_decompress(uaf_fname, ins_dir, '7z SFX', padding + 8) == 0: + os.remove(uaf_fname) # Successful extraction, delete @INS Module file/archive + + # Detect & Unpack AMI BIOS Guard (PFAT) BIOS image + pfat_match,pfat_buffer = get_ami_pfat(uaf_data_raw) + + if pfat_match: + pfat_dir = os.path.join(extract_path, safe_name(uaf_name)) + + parse_pfat_file(pfat_buffer, pfat_dir, padding + 4) + + os.remove(uaf_fname) # Delete PFAT Module file after extraction + + # Detect Intel Engine firmware image and show ME Analyzer advice + if uaf_tag.startswith('@ME') and PAT_INTEL_ENG.search(uaf_data_raw): + print('\n%sIntel Management Engine (ME) Firmware:\n' % padder(padding + 4)) + print('%sUse "ME Analyzer" from https://github.com/platomav/MEAnalyzer' % padder(padding + 8)) + + # Get best Nested AMI UCP Pattern match based on @UAF Size + nested_uaf_off,nested_uaf_bin = get_ami_ucp(uaf_data_raw) + + # Parse Nested AMI UCP Structure + if nested_uaf_off: + uaf_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory + + ucp_extract(nested_uaf_bin, uaf_dir, padding + 4, is_chk16) # Call recursively + + os.remove(uaf_fname) # Delete raw nested AMI UCP Structure after successful recursion/extraction + + return nal_dict + +# Get common ctypes Structure Sizes +UAF_HDR_LEN = ctypes.sizeof(UafHeader) +UAF_MOD_LEN = ctypes.sizeof(UafModule) +DIS_HDR_LEN = ctypes.sizeof(DisHeader) +DIS_MOD_LEN = ctypes.sizeof(DisModule) +UII_HDR_LEN = ctypes.sizeof(UiiHeader) + +# AMI UCP Tag Dictionary +UAF_TAG_DICT = { + '@3FI' : ['HpBiosUpdate32.efi', '', ''], + '@3S2' : ['HpBiosUpdate32.s12', '', ''], + '@3S4' : ['HpBiosUpdate32.s14', '', ''], + '@3S9' : ['HpBiosUpdate32.s09', '', ''], + '@3SG' : ['HpBiosUpdate32.sig', '', ''], + '@AMI' : ['UCP_Nested.bin', 'Nested AMI UCP', ''], + '@B12' : ['BiosMgmt.s12', '', ''], + '@B14' : ['BiosMgmt.s14', '', ''], + '@B32' : ['BiosMgmt32.s12', '', ''], + '@B34' : ['BiosMgmt32.s14', '', ''], + '@B39' : ['BiosMgmt32.s09', '', ''], + '@B3E' : ['BiosMgmt32.efi', '', ''], + '@BM9' : ['BiosMgmt.s09', '', ''], + '@BME' : ['BiosMgmt.efi', '', ''], + '@CKV' : ['Check_Version.txt', 'Check Version', 'Text'], + '@CMD' : ['AFU_Command.txt', 'AMI AFU Command', 'Text'], + '@CPM' : ['AC_Message.txt', 'Confirm Power Message', ''], + '@DCT' : ['DevCon32.exe', 'Device Console WIN32', ''], + '@DCX' : ['DevCon64.exe', 'Device Console WIN64', ''], + '@DFE' : ['HpDevFwUpdate.efi', '', ''], + '@DFS' : ['HpDevFwUpdate.s12', '', ''], + '@DIS' : ['Command_Status.bin', 'Default Command Status', ''], + '@ENB' : ['ENBG64.exe', '', ''], + '@INS' : ['Insyde_Nested.bin', 'Nested Insyde SFX', ''], + '@M32' : ['HpBiosMgmt32.s12', '', ''], + '@M34' : ['HpBiosMgmt32.s14', '', ''], + '@M39' : ['HpBiosMgmt32.s09', '', ''], + '@M3I' : ['HpBiosMgmt32.efi', '', ''], + '@MEC' : ['FWUpdLcl.txt', 'Intel FWUpdLcl Command', 'Text'], + '@MED' : ['FWUpdLcl_DOS.exe', 'Intel FWUpdLcl DOS', ''], + '@MET' : ['FWUpdLcl_WIN32.exe', 'Intel FWUpdLcl WIN32', ''], + '@MFI' : ['HpBiosMgmt.efi', '', ''], + '@MS2' : ['HpBiosMgmt.s12', '', ''], + '@MS4' : ['HpBiosMgmt.s14', '', ''], + '@MS9' : ['HpBiosMgmt.s09', '', ''], + '@NAL' : ['UAF_List.txt', 'Name List', ''], + '@OKM' : ['OK_Message.txt', 'OK Message', ''], + '@PFC' : ['BGT_Command.txt', 'AMI BGT Command', 'Text'], + '@R3I' : ['CryptRSA32.efi', '', ''], + '@RFI' : ['CryptRSA.efi', '', ''], + '@UAF' : ['UCP_Main.bin', 'Utility Auxiliary File', ''], + '@UFI' : ['HpBiosUpdate.efi', '', ''], + '@UII' : ['UCP_Info.txt', 'Utility Identification Information', ''], + '@US2' : ['HpBiosUpdate.s12', '', ''], + '@US4' : ['HpBiosUpdate.s14', '', ''], + '@US9' : ['HpBiosUpdate.s09', '', ''], + '@USG' : ['HpBiosUpdate.sig', '', ''], + '@VER' : ['OEM_Version.txt', 'OEM Version', 'Text'], + '@VXD' : ['amifldrv.vxd', '', ''], + '@W32' : ['amifldrv32.sys', '', ''], + '@W64' : ['amifldrv64.sys', '', ''], + } + +if __name__ == '__main__': + # Show script title + show_title(title) + + # Set argparse Arguments + argparser = argparse_init() + argparser.add_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true') + arguments = argparser.parse_args() + + # Pretty Python exception handler (must be after argparse) + sys.excepthook = nice_exc_handler + + # Check Python Version (must be after argparse) + check_sys_py() + + # Check OS Platform (must be after argparse) + check_sys_os() + + # Process input files and generate output path + input_files,output_path = process_input_files(arguments, sys.argv) + + # Initial output padding count + padding = 4 + + for input_file in input_files: + input_name = os.path.basename(input_file) + + print('\n*** %s' % input_name) + + with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + + # Get best AMI UCP Pattern match based on @UAF Size + main_uaf_off,main_uaf_bin = get_ami_ucp(input_buffer) + + if not main_uaf_off: + print('\n%sError: This is not an AMI UCP BIOS executable!' % padder(padding)) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + ucp_extract(main_uaf_bin, extract_path, padding, arguments.checksum) + + print('\n%sExtracted AMI UCP BIOS executable!' % padder(padding)) + + print_input('\nDone!') diff --git a/README.md b/README.md index e51f5ac..55c1efc 100644 --- a/README.md +++ b/README.md @@ -5,175 +5,39 @@ BIOS Utilities Donation via Paypal or Debit/Credit Card -* [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) * [**AMI UCP BIOS Extractor**](#ami-ucp-bios-extractor) * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) -* [**Phoenix SCT BIOS Extractor**](#phoenix-sct-bios-extractor) -* [**Insyde iFlash Image Extractor**](#insyde-iflash-image-extractor) -* [**Portwell EFI BIOS Extractor**](#portwell-efi-bios-extractor) -* [**Panasonic BIOS Update Extractor**](#panasonic-bios-update-extractor) -* [**VAIO Packaging Manager Extractor**](#vaio-packaging-manager-extractor) -* [**Fujitsu UPC BIOS Extractor**](#fujitsu-upc-bios-extractor) -* [**Fujitsu SFX BIOS Extractor**](#fujitsu-sfx-bios-extractor) -* [**Award BIOS Module Extractor**](#award-bios-module-extractor) -* [**Apple EFI Package Grabber**](#apple-efi-package-grabber) -* [**Apple EFI File Renamer**](#apple-efi-file-renamer) -* [**Apple EFI IM4P Splitter**](#apple-efi-im4p-splitter) -* [**Apple EFI Package Extractor**](#apple-efi-package-extractor) - -## **Dell PFS Update Extractor** - -![](https://i.imgur.com/5WaGPPl.png) - -#### **Description** - -Parses Dell PFS Update images and extracts their Firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and Utilities (e.g. Flasher etc) component sections. It supports all Dell PFS revisions and formats, including those which are originally LZMA compressed in ThinOS packages, ZLIB compressed or Intel BIOS Guard (PFAT) protected. The output comprises only final firmware components which are directly usable by end users. An optional advanced user mode is available as well, which additionally extracts firmware Signatures and more Metadata. - -#### **Usage** - -You can either Drag & Drop or manually enter the full path of a folder containing Dell PFS BIOS images. Optional arguments: - -* -h or --help : show help message and exit -* -a or --advanced : extract in advanced user mode -* -v or --verbose : show PFS structure information -* -e or --auto-exit : skip press enter to exit prompts -* -o or --output-dir : extract in given output directory -* -i or --input-dir : extract from given input directory - -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - -#### **Compatibility** - -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. - -#### **Prerequisites** - -To decompile the Intel BIOS Guard Scripts via the Python script, you need to additionally have the following 3rd party Python utility at the same directory: - -* [BIOS Guard Script Tool](https://github.com/allowitsme/big-tool/tree/sdk-compat) (i.e. big_script_tool.py) - -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Copy BIOS Guard Script Tool dependency to build directory: - -> Dell_PFS_Extract.py, big_script_tool.py - -4. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile Dell_PFS_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - -#### **Pictures** - -![](https://i.imgur.com/LCsUknA.png) - -![](https://i.imgur.com/TcARQpk.png) - -![](https://i.imgur.com/UWCx75g.png) - -![](https://i.imgur.com/1rokMss.png) ## **AMI UCP BIOS Extractor** -![](https://i.imgur.com/6YWoMGk.png) +![]() #### **Description** -Parses AMI UCP (Utility Configuration Program) BIOS images, extracts their SPI/BIOS/UEFI firmware components and shows all relevant info. It supports all AMI UCP revisions and formats, including those with nested AMI UCP or Insyde SFX structures. The output comprises only final firmware components and utilities which are directly usable by end users. - -Note that AMI UCP BIOS images are protected by various checksums but, due to algorithmic performance reasons, AMI UCP BIOS Extractor does not check them by default. An optional parameter is provided though, for verifying all checksums during extraction. +Parses AMI UCP (Utility Configuration Program) BIOS executables, extracts their firmware components (e.g. SPI/BIOS/UEFI, EC, ME etc) and shows all relevant info. It supports all AMI UCP revisions and formats, including those with nested AMI PFAT, AMI UCP or Insyde SFX structures. The output comprises only final firmware components and utilities which are directly usable by end users. #### **Usage** -You can either Drag & Drop or manually enter the full path of a folder containing AMI UCP BIOS images. Optional arguments: +You can either Drag & Drop or manually enter AMI UCP BIOS executable file(s). Optional arguments: * -h or --help : show help message and exit -* -p or --path : parse files within given folder +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts * -c or --checksum : verify AMI UCP Checksums (slow) -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - #### **Compatibility** Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. #### **Prerequisites** -To run the python script, you need to have the following 3rd party tools placed at the same directory: +To run the utility, you must have the following 3rd party tools at the "external" project directory: -* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (i.e. [TianoCompress.exe](https://github.com/tianocore/edk2-BaseTools-win32/)) -* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe) +* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress) +* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zz|7zzs) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile AMI_UCP_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - -#### **Pictures** - -![](https://i.imgur.com/3PaWy3M.png) - -## **AMI BIOS Guard Extractor** - -![](https://i.imgur.com/p0rrlqv.png) - -#### **Description** - -Parses AMI BIOS Guard (a.k.a. PFAT, Platform Firmware Armoring Technology) images, extracts their SPI/BIOS/UEFI firmware components and decompiles the Intel BIOS Guard Scripts. It supports all AMI PFAT revisions and formats, including those with nested AMI PFAT structures. The output comprises only final firmware components which are directly usable by end users. - -Note that the AMI PFAT structure does not have an explicit component order. AMI's BIOS Guard Firmware Update Tool (AFUBGT) updates components based on the user/OEM provided Parameters and Options. That means that merging all the components together does not usually yield a proper SPI/BIOS/UEFI image. The utility does generate such a merged file with the name "X_00 -- AMI_PFAT_X_DATA_ALL.bin" but it is up to the end user to determine its usefulness. Moreover, any custom OEM data after the AMI PFAT structure are additionally stored in a file with the name "X_YY -- AMI_PFAT_X_DATA_END.bin" and it is once again up to the end user to determine its usefulness. In cases where the trailing custom OEM data include a nested AMI PFAT structure, the utility will process and extract it automatically as well. - -#### **Usage** - -You can either Drag & Drop or manually enter the full path of a folder containing AMI BIOS Guard (PFAT) images. - -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - -#### **Compatibility** - -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. - -#### **Prerequisites** - -To decompile the Intel BIOS Guard Scripts via the Python script, you need to additionally have the following 3rd party Python utility at the same directory: +Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (when applicable), you must have the following 3rd party utility at the "external" project directory: * [BIOS Guard Script Tool](https://github.com/allowitsme/big-tool/tree/sdk-compat) (i.e. big_script_tool.py) @@ -189,13 +53,13 @@ PyInstaller can build/freeze/compile the utility at all three supported platform > pip3 install pyinstaller -3. Copy BIOS Guard Script Tool dependency to build directory: +3. Place any appropriate prerequisite at the project directory: -> AMI_PFAT_Extract.py, big_script_tool.py +> TianoCompress, 7-Zip Console, BIOS Guard Script Tool 4. Build/Freeze/Compile: -> pyinstaller --noupx --onefile AMI_PFAT_Extract.py +> pyinstaller --noupx --onefile \\/AMI_UCP_Extract.py At dist folder you should find the final utility executable @@ -205,85 +69,26 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con #### **Pictures** -![](https://i.imgur.com/iZD3GY0.png) +![]() -## **Phoenix SCT BIOS Extractor** +## **AMI BIOS Guard Extractor** -![](https://i.imgur.com/z4VM06J.png) +![]() #### **Description** -Parses Phoenix SecureCore Technology (SCT) BIOS images and extracts their SPI/BIOS/UEFI firmware components. It supports all Phoenix SCT revisions and formats, including those which are originally LZMA compressed. The output comprises only final firmware components which are directly usable by end users. +Parses AMI BIOS Guard (a.k.a. PFAT, Platform Firmware Armoring Technology) images, extracts their SPI/BIOS/UEFI firmware components and decompiles the Intel BIOS Guard Scripts. It supports all AMI PFAT revisions and formats, including those with Index Information tables or nested AMI PFAT structures. The output comprises only final firmware components which are directly usable by end users. -![](https://i.imgur.com/p6s8L6j.png) -*Icon owned by Phoenix* +Note that the AMI PFAT structure may not have an explicit component order. AMI's BIOS Guard Firmware Update Tool (AFUBGT) updates components based on the user/OEM provided Parameters and Options or Index Information table, when applicable. That means that merging all the components together does not usually yield a proper SPI/BIOS/UEFI image. The utility does generate such a merged file with the name "00 -- \\_ALL.bin" but it is up to the end user to determine its usefulness. Moreover, any custom OEM data after the AMI PFAT structure are additionally stored in the last file with the name "\ -- \_OOB.bin" and it is once again up to the end user to determine its usefulness. In cases where the trailing custom OEM data include a nested AMI PFAT structure, the utility will process and extract it automatically as well. #### **Usage** -You can either Drag & Drop or manually enter the full path of a folder containing Phoenix SCT BIOS images. Optional arguments: +You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s). Optional arguments: * -h or --help : show help message and exit -* -p or --path : parse files within given folder - -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - -#### **Compatibility** - -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. - -#### **Prerequisites** - -To run the utility, you do not need any 3rd party tool. - -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile Phoenix_SCT_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - -#### **Pictures** - -![](https://i.imgur.com/Td6F5mm.png) - -## **Insyde iFlash Image Extractor** - -![](https://i.imgur.com/13GJjwO.png) - -#### **Description** - -Parses Insyde iFlash images and extracts their raw components (e.g. SPI/BIOS/UEFI, EC, ME, Flasher, Configuration etc). - -#### **Usage** - -You can either Drag & Drop or manually enter the full path of a folder containing Insyde iFlash images. Optional arguments: - -* -h or --help : show help message and exit -* -v or --verbose : show iFlash structure information -* -e or --auto-exit : skip press enter to exit prompts -* -o or --output-dir : extract in given output directory * -i or --input-dir : extract from given input directory - -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts #### **Compatibility** @@ -291,7 +96,9 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 #### **Prerequisites** -To run the utility, you do not need any 3rd party tool. +Optionally, to decompile the AMI PFAT \> Intel BIOS Guard Scripts, you must have the following 3rd party utility at the "external" project directory: + +* [BIOS Guard Script Tool](https://github.com/allowitsme/big-tool/tree/sdk-compat) (i.e. big_script_tool.py) #### **Build/Freeze/Compile with PyInstaller** @@ -305,74 +112,13 @@ PyInstaller can build/freeze/compile the utility at all three supported platform > pip3 install pyinstaller -3. Build/Freeze/Compile: +3. Place any appropriate prerequisite at the project directory: -> pyinstaller --noupx --onefile Insyde_iFlash_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - -#### **Pictures** - -![](https://i.imgur.com/Pn4JNiG.png) - -## **Portwell EFI BIOS Extractor** - -![](https://i.imgur.com/ySdUSgf.png) - -#### **Description** - -Parses Portwell UEFI Unpacker EFI images (usually named "Update.efi"), extracts their SPI/BIOS/UEFI/EC firmware components and shows all relevant info. It supports all Portwell UEFI Unpacker revisions and formats, including those which contain Tiano compressed files. The output comprises only final firmware components and utilities which are directly usable by end users. - -#### **Usage** - -You can either Drag & Drop or manually enter the full path of a folder containing Portwell UEFI Unpacker EFI images. Optional arguments: - -* -h or --help : show help message and exit -* -p or --path : parse files within given folder - -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - -#### **Compatibility** - -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. - -#### **Prerequisites** - -To run the python script, you need to have the following 3rd party Python module installed: - -* [pefile](https://pypi.org/project/pefile/) - -> pip3 install pefile - -To run the python script or its built/frozen/compiled binary, you need to additionally have the following 3rd party tool at the same directory: - -* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (i.e. [TianoCompress.exe](https://github.com/tianocore/edk2-BaseTools-win32/)) - -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Use pip to install pefile: - -> pip3 install pefile +> BIOS Guard Script Tool 4. Build/Freeze/Compile: -> pyinstaller --noupx --onefile Portwell_EFI_Extract.py +> pyinstaller --noupx --onefile \\/AMI_PFAT_Extract.py At dist folder you should find the final utility executable @@ -382,456 +128,7 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con #### **Pictures** -![](https://i.imgur.com/EhCzMLk.png) - -## **Apple EFI Package Grabber** - -![](https://i.imgur.com/BaHrjGi.png) - -#### **Description** - -Parses user-provided (DB) list of Apple Software Update CatalogURL .sucatalog links and saves all newer (since last run) EFI firmware package links into a text file. It removes any xml formatting, ignores false positives, removes duplicate links and sorts them in alphabetical order for easy comparison afterwards. - -#### **Usage** - -First, you need to familiarize a bit with the DB (i.e. Apple_EFI_Grab.dat file). It consists of 3 sections: Last run DateTime (YYYY-MM-DD HH:MM:SS), Sucatalog links to check and EFI Package links which have been gathered so far across all runs. Before running the utility for the fist time, you need to insert the Sucatalog links into the DB, below the 1st line (DateTime). The Sucatalog links in the DB are stored in partial form, starting from "index" string. For example: "https://swscan.apple.com/content/catalogs/others/index-12.merged-1.sucatalog" must be stored as "index-12.merged-1.sucatalog" in the DB. The Sucatalog links are not pre-included in the DB but you can find them online (e.g. https://github.com/zhangyoufu/swscan.apple.com/blob/master/url.txt). - -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - -#### **Compatibility** - -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. - -#### **Prerequisites** - -To run the utility, you do not need any 3rd party tool. - -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile Apple_EFI_Grab.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - -## **Apple EFI File Renamer** - -![](https://i.imgur.com/mWGhWja.png) - -#### **Description** - -Parses Apple EFI files and renames them based on Intel's official $IBIOSI$ tag as follows: Model_Version_Build_Year_Month_Day_Hour_Minute_Checksum. The checksum is calculated and added by the utility in order to differentiate any EFI files with the same $IBIOSI$ tag. In rare cases in which the $IBIOSI$ tag is compressed, the utility automatically first uses [LongSoft's UEFIFind and UEFIExtract](https://github.com/LongSoft/UEFITool) tools. - -#### **Usage** - -You can either Drag & Drop or manually enter the full path of a folder containing Apple EFI firmware. - -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - -#### **Compatibility** - -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. - -#### **Prerequisites** - -To run the python script or its built/frozen/compiled binary, you need to have the following 3rd party tools at the same directory: - -* [UEFIFind](https://github.com/LongSoft/UEFITool) (i.e. UEFIFind.exe) -* [UEFIExtract](https://github.com/LongSoft/UEFITool) (i.e. UEFIExtract.exe) - -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile Apple_EFI_Rename.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - -## **Apple EFI IM4P Splitter** - -![](https://i.imgur.com/G5RkXQk.png) - -#### **Description** - -Parses Apple multiple EFI firmware .im4p files and splits all detected EFI firmware into separate SPI/BIOS images. - -#### **Usage** - -You can either Drag & Drop or manually enter the full path of a folder containing Apple EFI IM4P firmware. - -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - -#### **Compatibility** - -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. - -#### **Prerequisites** - -To run the utility, you do not need any 3rd party tool. - -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile Apple_EFI_Split.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - -## **Apple EFI Package Extractor** - -![](https://i.imgur.com/pufGuZ4.png) - -#### **Description** - -Parses Apple EFI firmware packages (i.e. FirmwareUpdate.pkg, BridgeOSUpdateCustomer.pkg), extracts their EFI images, splits those in IM4P format and renames the final SPI/BIOS images accordingly. The utility automatically uses the free version of [AnyToISO](https://www.crystalidea.com/anytoiso) to extract the EFI .pkg files. The subsequent IM4P splitting and EFI renaming requires the presence of "Apple EFI IM4P Splitter" and "Apple EFI File Renamer" utilities. - -#### **Usage** - -You can either Drag & Drop or manually enter the full path of a folder containing Apple EFI firmware package (.pkg) files. Depending on where AnyToISO is installed on your system, you must change the "anytoiso_path" variable accordingly. - -#### **Download** - -An already built/frozen/compiled binary is **not** provided because the script requires the user to set the AnyToISO executable path variable. Remember that you need to include prerequisites such as AnyToISO, Apple EFI IM4P Splitter and Apple EFI File Renamer for the utility to work. - -#### **Compatibility** - -Should work at all Windows & macOS operating systems which have Python 3.7 and AnyToISO support. - -#### **Prerequisites** - -To run the python script, you need to have the following 3rd party tools installed or placed at the same directory: - -* [AnyToISO](https://www.crystalidea.com/anytoiso) (i.e. anytoiso.exe) -* [UEFIFind](https://github.com/LongSoft/UEFITool) (i.e. UEFIFind.exe) -* [UEFIExtract](https://github.com/LongSoft/UEFITool) (i.e. UEFIExtract.exe) - -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. Note that, due to this utility's nature, you may need to perform some small script changes for a built/frozen/compiled binary to work. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile Apple_EFI_Package.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - -## **Panasonic BIOS Update Extractor** - -![](https://i.imgur.com/uZAoMGR.png) -*Icon owned by Panasonic* - -#### **Description** - -Parses Panasonic BIOS Update executables and extracts their SPI/BIOS image. The utility automatically uses [Rustam Abdullaev's unpack_lznt1](https://github.com/rustyx/unpack_lznt1) tool in order to decompress the initially Microsoft LZNT1 compressed resource data. - -#### **Usage** - -You can either Drag & Drop or manually enter the full path of a folder containing Panasonic BIOS Update executables. - -#### **Download** - -An already built/frozen/compiled Windows binary is provided by me. Thus, **you don't need to manually build/freeze/compile it**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - -#### **Compatibility** - -Should work at all Windows operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. - -#### **Prerequisites** - -To run the python script, you need to have the following 3rd party Python module installed: - -* [pefile](https://pypi.org/project/pefile/) - -> pip3 install pefile - -To run the python script or its built/frozen/compiled binary, you need to additionally have the following 3rd party tool at the same directory: - -* [unpack_lznt1](https://github.com/rustyx/unpack_lznt1) (i.e. unpack_lznt1.exe) - -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at Windows, it is simple to run and gets updated often. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Use pip to install pefile: - -> pip3 install pefile - -4. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile Panasonic_BIOS_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - -## **VAIO Packaging Manager Extractor** - -![](https://i.imgur.com/rg4xrxJ.png) -*Icon owned by VAIO* - -#### **Description** - -Parses VAIO Packaging Manager executables and extracts their contents. If direct extraction fails, it unlocks the executable in order to run at all systems and allow the user to choose the extraction location. The utility automatically uses [Igor Pavlov's 7-Zip](https://www.7-zip.org/) tool in order to decompress the initially obfuscated Microsoft CAB compressed contents. - -#### **Usage** - -You can either Drag & Drop or manually enter the full path of a folder containing VAIO Packaging Manager executables. - -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - -#### **Compatibility** - -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. - -#### **Prerequisites** - -To run the python script or its built/frozen/compiled binary, you need to have the following 3rd party tool at the same directory: - -* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe) - -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile VAIO_Package_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - -## **Fujitsu UPC BIOS Extractor** - -![](https://i.imgur.com/JqrlxfE.png) - -#### **Description** - -Parses Fujitsu UPC images and extracts their Tiano compressed SPI/BIOS firmware component. The output comprises only a final firmware component which is directly usable by end users. - -#### **Usage** - -You can either Drag & Drop or manually enter the full path of a folder containing Portwell UEFI Unpacker EFI images. Optional arguments: - -* -h or --help : show help message and exit -* -p or --path : parse files within given folder - -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - -#### **Compatibility** - -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. - -#### **Prerequisites** - -To run the python script or its built/frozen/compiled binary, you need to additionally have the following 3rd party tool at the same directory: - -* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (i.e. [TianoCompress.exe](https://github.com/tianocore/edk2-BaseTools-win32/)) - -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile Fujitsu_UPC_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - -#### **Pictures** - -![](https://i.imgur.com/FE8MNi2.png) - -## **Fujitsu SFX BIOS Extractor** - -![](https://i.imgur.com/NlZGBsy.png) -*Icon owned by FUJITSU* - -#### **Description** - -Parses Fujitsu SFX BIOS executables and extracts their contents. The utility automatically uses [Igor Pavlov's 7-Zip](https://www.7-zip.org/) tool in order to decompress the initially obfuscated Microsoft CAB compressed contents. - -#### **Usage** - -You can either Drag & Drop or manually enter the full path of a folder containing Fujitsu SFX BIOS executables. - -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - -#### **Compatibility** - -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. - -#### **Prerequisites** - -To run the python script or its built/frozen/compiled binary, you need to have the following 3rd party tool at the same directory: - -* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe) - -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile Fujitsu_SFX_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - -## **Award BIOS Module Extractor** - -#### **Description** - -Parses Award BIOS images and extracts their modules. The utility automatically uses [Igor Pavlov's 7-Zip](https://www.7-zip.org/) tool in order to decompress the initially LZH compressed sub-modules. - -#### **Usage** - -You can either Drag & Drop or manually enter the full path of a folder containing Award BIOS firmware. - -#### **Download** - -An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites. - -#### **Compatibility** - -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. - -#### **Prerequisites** - -To run the python script or its built/frozen/compiled binary, you need to have the following 3rd party tool at the same directory: - -* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe) - -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.7.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile Award_BIOS_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. +![]() ###### _Donate Button Card Image: [Credit and Loan Pack](https://flaticon.com/free-icon/credit-card_3898076) by **Freepik** under Flaticon license_ ###### _Donate Button Paypal Image: [Credit Cards Pack](https://flaticon.com/free-icon/paypal_349278) by **Freepik** under Flaticon license_ \ No newline at end of file diff --git a/common/a7z_comp.py b/common/a7z_comp.py new file mode 100644 index 0000000..41aa476 --- /dev/null +++ b/common/a7z_comp.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +import os +import subprocess + +from common.script_get import get_script_dir +from common.system import get_os_ver +from common.text_ops import padder + +# Get 7z path +def get_7z_path(static=False): + exec_name = '7z.exe' if get_os_ver()[1] else ('7zzs' if static else '7zz') + + exec_path = os.path.join(get_script_dir(), '..', 'external', exec_name) + + return exec_path + +# Check if file is 7z supported +def is_7z_supported(in_path, static=False): + try: + subprocess.run([get_7z_path(static), 't', in_path, '-bso0', '-bse0', '-bsp0'], check=True) + + except: + return False + + return True + +# Archive decompression via 7-Zip +def a7z_decompress(in_path, out_path, in_name, padding, static=False): + if not in_name: in_name = 'archive' + + try: + subprocess.run([get_7z_path(static), 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + out_path, in_path], check=True) + + if not os.path.isdir(out_path): raise Exception('EXTRACT_DIR_MISSING') + + except: + print('\n%sError: 7-Zip could not extract %s file %s!' % (padder(padding), in_name, in_path)) + + return 1 + + print('\n%sSuccesfull %s decompression via 7-Zip!' % (padder(padding), in_name)) + + return 0 \ No newline at end of file diff --git a/common/checksums.py b/common/checksums.py new file mode 100644 index 0000000..c3c7385 --- /dev/null +++ b/common/checksums.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +# Get Checksum 16-bit +def checksum16(data): + chk16 = 0 + + for idx in range(0, len(data), 2): + chk16 += int.from_bytes(data[idx:idx + 2], 'little') + + chk16 &= 0xFFFF + + return chk16 \ No newline at end of file diff --git a/common/efi_comp.py b/common/efi_comp.py new file mode 100644 index 0000000..6d3bb42 --- /dev/null +++ b/common/efi_comp.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +import os +import subprocess + +from common.script_get import get_script_dir +from common.system import get_os_ver +from common.text_ops import padder + +def get_compress_sizes(data): + size_compress = int.from_bytes(data[0x0:0x4], 'little') + size_original = int.from_bytes(data[0x4:0x8], 'little') + + return size_compress, size_original + +def is_efi_compressed(data, strict=True): + size_comp,size_orig = get_compress_sizes(data) + + check_diff = size_comp < size_orig + + if strict: check_size = size_comp + 0x8 == len(data) + else: check_size = size_comp + 0x8 <= len(data) + + return check_diff and check_size + +# Get TianoCompress path +def tianocompress_path(): + exec_name = 'TianoCompress' + ('.exe' if get_os_ver()[1] else '') + + exec_path = os.path.join(get_script_dir(), '..', 'external', exec_name) + + return exec_path + +# EFI/Tiano Decompression via TianoCompress +def efi_decompress(in_path, out_path, padding, comp_type='--uefi'): + try: + subprocess.run([tianocompress_path(), '-d', in_path, '-o', out_path, '-q', comp_type], check=True, stdout=subprocess.DEVNULL) + + with open(in_path, 'rb') as file: _,size_orig = get_compress_sizes(file.read()) + + if os.path.getsize(out_path) != size_orig: raise Exception('EFI_DECOMPRESS_ERROR') + + except: + print('\n%sError: TianoCompress could not extract file %s!' % (padder(padding), in_path)) + + return 1 + + print('\n%sSuccesfull EFI/Tiano decompression via TianoCompress!' % padder(padding)) + + return 0 \ No newline at end of file diff --git a/common/externals.py b/common/externals.py new file mode 100644 index 0000000..1ce7156 --- /dev/null +++ b/common/externals.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +# https://github.com/allowitsme/big-tool by Dmitry Frolov +def get_bgs_tool(): + try: + from external.big_script_tool import BigScript + except: + BigScript = None + + return BigScript \ No newline at end of file diff --git a/common/num_ops.py b/common/num_ops.py new file mode 100644 index 0000000..7272fc6 --- /dev/null +++ b/common/num_ops.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +# https://leancrew.com/all-this/2020/06/ordinals-in-python/ by Dr. Drang + +def get_ordinal(number): + s = ('th', 'st', 'nd', 'rd') + ('th',) * 10 + + v = number % 100 + + return f'{number}{s[v % 10]}' if v > 13 else f'{number}{s[v]}' \ No newline at end of file diff --git a/common/path_ops.py b/common/path_ops.py new file mode 100644 index 0000000..a86e4a5 --- /dev/null +++ b/common/path_ops.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +import os +import re +import argparse +from pathlib import Path + +from common.script_get import get_script_dir + +# Fix illegal/reserved Windows characters +def safe_name(in_name): + raw_name = repr(in_name).strip("'") + + fix_name = re.sub(r'[\\/*?:"<>|]', '_', raw_name) + + return fix_name + +# Walk path to get all files +def get_path_files(in_path): + path_files = [] + + for root, _, files in os.walk(in_path): + for name in files: + path_files.append(os.path.join(root, name)) + + return path_files + +# Get parent of path +def get_path_parent(in_path): + return Path(in_path).parent.absolute() + +# Get absolute file path (argparse object) +def get_absolute_path(argparse_path): + script_dir = get_path_parent(get_script_dir()) + + if not argparse_path: + absolute_path = script_dir # Use input file directory if no user path is specified + else: + # Check if user specified path is absolute, otherwise convert it to input file relative + if Path(argparse_path).is_absolute(): absolute_path = argparse_path + else: absolute_path = os.path.join(script_dir, argparse_path) + + return absolute_path + +# Initialize common argparse arguments +def argparse_init(): + argparser = argparse.ArgumentParser() + + argparser.add_argument('files', type=argparse.FileType('r'), nargs='*') + argparser.add_argument('-e', '--auto-exit', help='skip press enter to exit prompts', action='store_true') + argparser.add_argument('-o', '--output-dir', help='extract in given output directory') + argparser.add_argument('-i', '--input-dir', help='extract from given input directory') + + return argparser + +# Process input files (argparse object) +def process_input_files(argparse_args, sys_argv=None): + if sys_argv is None: sys_argv = [] + + if len(sys_argv) >= 2: + # Drag & Drop or CLI + if argparse_args.input_dir: + input_path_user = argparse_args.input_dir + input_path_full = get_absolute_path(input_path_user) if input_path_user else '' + input_files = get_path_files(input_path_full) + else: + input_files = [file.name for file in argparse_args.files] + + output_path = get_absolute_path(argparse_args.output_dir or argparse_args.input_dir) + else: + # Script w/o parameters + input_path_user = input('\nEnter input directory path: ') + input_path_full = get_absolute_path(input_path_user) if input_path_user else '' + input_files = get_path_files(input_path_full) + + output_path = get_absolute_path(input('\nEnter output directory path: ')) + + return input_files, output_path \ No newline at end of file diff --git a/common/patterns.py b/common/patterns.py new file mode 100644 index 0000000..60bc0bd --- /dev/null +++ b/common/patterns.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +import re + +PAT_AMI_PFAT = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) +PAT_AMI_UCP = re.compile(br'\x40\x55\x41\x46.{12}\x40', re.DOTALL) +PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) \ No newline at end of file diff --git a/common/script_get.py b/common/script_get.py new file mode 100644 index 0000000..a09b896 --- /dev/null +++ b/common/script_get.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +# https://stackoverflow.com/a/22881871 by jfs + +import os +import sys +import inspect + +def get_script_dir(follow_symlinks=True): + if getattr(sys, 'frozen', False): + path = os.path.abspath(sys.executable) + else: + path = inspect.getabsfile(get_script_dir) + + if follow_symlinks: + path = os.path.realpath(path) + + return os.path.dirname(path) \ No newline at end of file diff --git a/common/struct_ops.py b/common/struct_ops.py new file mode 100644 index 0000000..9cb63fe --- /dev/null +++ b/common/struct_ops.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky + +import ctypes + +char = ctypes.c_char +uint8_t = ctypes.c_ubyte +uint16_t = ctypes.c_ushort +uint32_t = ctypes.c_uint +uint64_t = ctypes.c_uint64 + +def get_struct(buffer, start_offset, class_name, param_list=None): + if param_list is None: param_list = [] + + structure = class_name(*param_list) # Unpack parameter list + struct_len = ctypes.sizeof(structure) + struct_data = buffer[start_offset:start_offset + struct_len] + fit_len = min(len(struct_data), struct_len) + + ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len) + + return structure \ No newline at end of file diff --git a/common/system.py b/common/system.py new file mode 100644 index 0000000..33412fc --- /dev/null +++ b/common/system.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +import sys +import ctypes +import traceback + +# Get Python Version (tuple) +def get_py_ver(): + return sys.version_info + +# Get OS Platform (string) +def get_os_ver(): + sys_os = sys.platform + + is_win = sys_os == 'win32' + is_lnx = sys_os.startswith('linux') or sys_os == 'darwin' or sys_os.find('bsd') != -1 + + return sys_os, is_win, is_win or is_lnx + +# Check for --auto-exit|-e +def is_auto_exit(): + return '--auto-exit' in sys.argv or '-e' in sys.argv + +# Check Python Version +def check_sys_py(): + sys_py = get_py_ver() + + if sys_py < (3,7): + sys.stdout.write('\nError: Python >= 3.7 required, not %d.%d!' % (sys_py[0], sys_py[1])) + + if not is_auto_exit(): + # noinspection PyUnresolvedReferences + (raw_input if sys_py[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 + + sys.exit(1) + +# Check OS Platform +def check_sys_os(): + os_tag,os_win,os_sup = get_os_ver() + + if not os_sup: + print('\nError: Unsupported platform "%s"!' % os_tag) + + if not is_auto_exit(): + input('\nPress enter to exit') + + sys.exit(2) + + # Fix Windows Unicode console redirection + if os_win: sys.stdout.reconfigure(encoding='utf-8') + +# Show Script Title +def show_title(title): + print('\n' + title) + + _,os_win,_ = get_os_ver() + + # Set console/shell window title + if os_win: ctypes.windll.kernel32.SetConsoleTitleW(title) + else: sys.stdout.write('\x1b]2;' + title + '\x07') + +# https://stackoverflow.com/a/781074 by Torsten Marek +def nice_exc_handler(exc_type, exc_value, tb): + if exc_type is KeyboardInterrupt: + print('\n') + else: + print('\nError: Script crashed, please report the following:\n') + + traceback.print_exception(exc_type, exc_value, tb) + + if not is_auto_exit(): + input('\nPress enter to exit') + + sys.exit(3) + +# Print or Input Message based on --auto-exit|-e +def print_input(msg): + (print if is_auto_exit() else input)(msg) \ No newline at end of file diff --git a/common/text_ops.py b/common/text_ops.py new file mode 100644 index 0000000..b6ada83 --- /dev/null +++ b/common/text_ops.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +# Generate padding (spaces or tabs) +def padder(count, tab=False): + return ('\t' if tab else ' ') * count \ No newline at end of file From 7c00479a9e01cd6c39b31201413f3c4b93c4225e Mon Sep 17 00:00:00 2001 From: platomav Date: Fri, 1 Apr 2022 17:48:20 +0300 Subject: [PATCH 06/52] cleanup old files --- .gitattributes | 2 - AMI BIOS Guard Extractor/AMI_PFAT_Extract.py | 321 ----- AMI UCP BIOS Extractor/AMI_UCP_Extract.py | 610 -------- Apple EFI File Renamer/Apple_EFI_Rename.py | 104 -- Apple EFI IM4P Splitter/Apple_EFI_Split.py | 146 -- .../Apple_EFI_Package.py | 101 -- Apple EFI Package Grabber/Apple_EFI_Grab.dat | 4 - Apple EFI Package Grabber/Apple_EFI_Grab.py | 129 -- .../Award_BIOS_Extract.py | 65 - Dell PFS Update Extractor/Dell_PFS_Extract.py | 1245 ----------------- .../Fujitsu_SFX_Extract.py | 82 -- .../Fujitsu_UPC_Extract.py | 108 -- .../Insyde_iFlash_Extract.py | 270 ---- .../Panasonic_BIOS_Extract.py | 74 - .../Phoenix_SCT_Extract.py | 213 --- .../Portwell_EFI_Extract.py | 174 --- .../VAIO_Package_Extract.py | 126 -- 17 files changed, 3774 deletions(-) delete mode 100644 .gitattributes delete mode 100644 AMI BIOS Guard Extractor/AMI_PFAT_Extract.py delete mode 100644 AMI UCP BIOS Extractor/AMI_UCP_Extract.py delete mode 100644 Apple EFI File Renamer/Apple_EFI_Rename.py delete mode 100644 Apple EFI IM4P Splitter/Apple_EFI_Split.py delete mode 100644 Apple EFI Package Extractor/Apple_EFI_Package.py delete mode 100644 Apple EFI Package Grabber/Apple_EFI_Grab.dat delete mode 100644 Apple EFI Package Grabber/Apple_EFI_Grab.py delete mode 100644 Award BIOS Module Extractor/Award_BIOS_Extract.py delete mode 100644 Dell PFS Update Extractor/Dell_PFS_Extract.py delete mode 100644 Fujitsu SFX BIOS Extractor/Fujitsu_SFX_Extract.py delete mode 100644 Fujitsu UPC BIOS Extractor/Fujitsu_UPC_Extract.py delete mode 100644 Insyde iFlash Image Extractor/Insyde_iFlash_Extract.py delete mode 100644 Panasonic BIOS Update Extractor/Panasonic_BIOS_Extract.py delete mode 100644 Phoenix SCT BIOS Extractor/Phoenix_SCT_Extract.py delete mode 100644 Portwell EFI BIOS Extractor/Portwell_EFI_Extract.py delete mode 100644 VAIO Packaging Manager Extractor/VAIO_Package_Extract.py diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index dfe0770..0000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto diff --git a/AMI BIOS Guard Extractor/AMI_PFAT_Extract.py b/AMI BIOS Guard Extractor/AMI_PFAT_Extract.py deleted file mode 100644 index bd1a1f0..0000000 --- a/AMI BIOS Guard Extractor/AMI_PFAT_Extract.py +++ /dev/null @@ -1,321 +0,0 @@ -#!/usr/bin/env python3 -#coding=utf-8 - -""" -AMI PFAT Extract -AMI BIOS Guard Extractor -Copyright (C) 2018-2021 Plato Mavropoulos -""" - -print('AMI BIOS Guard Extractor v3.2') - -import sys - -# Detect Python version -sys_ver = sys.version_info -if sys_ver < (3,7) : - sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1])) - (raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 - sys.exit(1) - -import os -import re -import ctypes -import shutil -import traceback - -# https://stackoverflow.com/a/781074 by Torsten Marek -def show_exception_and_exit(exc_type, exc_value, tb) : - if exc_type is KeyboardInterrupt : - print('\n') - else : - print('\nError: ABGE crashed, please report the following:\n') - traceback.print_exception(exc_type, exc_value, tb) - input('\nPress enter to exit') - sys.exit(1) - -# Pause after any unexpected python exception -sys.excepthook = show_exception_and_exit - -sys.dont_write_bytecode = True - -# https://github.com/allowitsme/big-tool by Dmitry Frolov -try : - from big_script_tool import BigScript - is_bgst = True -except : - is_bgst = False - -# Set ctypes Structure types -char = ctypes.c_char -uint8_t = ctypes.c_ubyte -uint16_t = ctypes.c_ushort -uint32_t = ctypes.c_uint -uint64_t = ctypes.c_uint64 - -class PFAT_Header(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('Size', uint32_t), # 0x00 - ('Checksum', uint32_t), # 0x04 Unknown 16-bits - ('Tag', char*8), # 0x04 _AMIPFAT - ('Flags', uint8_t), # 0x10 - # 0x11 - ] - - def pfat_print(self) : - print('\n PFAT Main Header:\n') - print(' Size : 0x%X' % self.Size) - print(' Checksum : 0x%0.4X' % self.Checksum) - print(' Tag : %s' % self.Tag.decode('utf-8')) - print(' Flags : 0x%0.2X' % self.Flags) - -class PFAT_Block_Header(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('PFATVerMajor', uint16_t), # 0x00 - ('PFATVerMinor', uint16_t), # 0x02 - ('PlatformID', uint8_t*16), # 0x04 - ('Attributes', uint32_t), # 0x14 - ('ScriptVerMajor', uint16_t), # 0x16 - ('ScriptVerMinor', uint16_t), # 0x18 - ('ScriptSize', uint32_t), # 0x1C - ('DataSize', uint32_t), # 0x20 - ('BIOSSVN', uint32_t), # 0x24 - ('ECSVN', uint32_t), # 0x28 - ('VendorInfo', uint32_t), # 0x2C - # 0x30 - ] - - def __init__(self, count, *args, **kwargs): - super().__init__(*args, **kwargs) - self.count = count - - def get_flags(self) : - attr = PFAT_Block_Header_GetAttributes() - attr.asbytes = self.Attributes - - return attr.b.SFAM, attr.b.ProtectEC, attr.b.GFXMitDis, attr.b.FTU, attr.b.Reserved - - def pfat_print(self) : - no_yes = ['No','Yes'] - f1,f2,f3,f4,f5 = self.get_flags() - - PlatformID = bytes(self.PlatformID).strip(b'\x00') - if PlatformID.isalpha() : # STRING - PlatformID = PlatformID.decode('utf-8', 'ignore') - else : # GUID - PlatformID = '%0.*X' % (0x10 * 2, int.from_bytes(self.PlatformID, 'big')) - PlatformID = '{%s-%s-%s-%s-%s}' % (PlatformID[:8], PlatformID[8:12], PlatformID[12:16], PlatformID[16:20], PlatformID[20:]) - - print('\n PFAT Block %s Header:\n' % self.count) - print(' PFAT Version : %d.%d' % (self.PFATVerMajor, self.PFATVerMinor)) - print(' Platform ID : %s' % PlatformID) - print(' Signed Flash Address Map : %s' % no_yes[f1]) - print(' Protected EC OpCodes : %s' % no_yes[f2]) - print(' Graphics Security Disable : %s' % no_yes[f3]) - print(' Fault Tolerant Update : %s' % no_yes[f4]) - print(' Attributes Reserved : 0x%X' % f5) - print(' Script Version : %d.%d' % (self.ScriptVerMajor, self.ScriptVerMinor)) - print(' Script Size : 0x%X' % self.ScriptSize) - print(' Data Size : 0x%X' % self.DataSize) - print(' BIOS SVN : 0x%X' % self.BIOSSVN) - print(' EC SVN : 0x%X' % self.ECSVN) - print(' Vendor Info : 0x%X' % self.VendorInfo) - -class PFAT_Block_Header_Attributes(ctypes.LittleEndianStructure): - _fields_ = [ - ('SFAM', uint32_t, 1), # Signed Flash Address Map - ('ProtectEC', uint32_t, 1), # Protected EC OpCodes - ('GFXMitDis', uint32_t, 1), # GFX Security Disable - ('FTU', uint32_t, 1), # Fault Tolerant Update - ('Reserved', uint32_t, 28) - ] - -class PFAT_Block_Header_GetAttributes(ctypes.Union): - _fields_ = [ - ('b', PFAT_Block_Header_Attributes), - ('asbytes', uint32_t) - ] - -class PFAT_Block_RSA(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('Unknown0', uint32_t), # 0x00 - ('Unknown1', uint32_t), # 0x04 - ('PublicKey', uint32_t*64), # 0x08 - ('Exponent', uint32_t), # 0x108 - ('Signature', uint32_t*64), # 0x10C - # 0x20C - ] - - def __init__(self, count, *args, **kwargs): - super().__init__(*args, **kwargs) - self.count = count - - def pfat_print(self) : - PublicKey = '%0.*X' % (0x100 * 2, int.from_bytes(self.PublicKey, 'little')) - Signature = '%0.*X' % (0x100 * 2, int.from_bytes(self.Signature, 'little')) - - print('\n PFAT Block %s Signature:\n' % self.count) - print(' Unknown 0 : 0x%X' % self.Unknown0) - print(' Unknown 1 : 0x%X' % self.Unknown1) - print(' Public Key : %s [...]' % PublicKey[:8]) - print(' Exponent : 0x%X' % self.Exponent) - print(' Signature : %s [...]' % Signature[:8]) - -# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky -def get_struct(buffer, start_offset, class_name, param_list = None) : - if param_list is None : param_list = [] - - structure = class_name(*param_list) # Unpack parameter list - struct_len = ctypes.sizeof(structure) - struct_data = buffer[start_offset:start_offset + struct_len] - fit_len = min(len(struct_data), struct_len) - - if (start_offset >= len(buffer)) or (fit_len < struct_len) : - input('\n Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name.__name__)) - sys.exit(1) - - ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len) - - return structure - -if len(sys.argv) >= 2 : - # Drag & Drop or CLI - ami_pfat = sys.argv[1:] -else : - # Folder path - ami_pfat = [] - in_path = input('\nEnter the full folder path: ') - print('\nWorking...') - for root, _, files in os.walk(in_path): - for name in files : - ami_pfat.append(os.path.join(root, name)) - -pfat_index = 1 -input_name = '' -input_extension = '' -output_path = '' -block_hdr_size = ctypes.sizeof(PFAT_Block_Header) -block_rsa_size = ctypes.sizeof(PFAT_Block_RSA) -pfat_pat = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) - -for input_file in ami_pfat : - file_data = b'' - final_data = b'' - block_name = '' - block_count = 0 - file_index = 0 - blocks = [] - - with open(input_file, 'rb') as in_file : buffer = in_file.read() - - pfat_match = pfat_pat.search(buffer) - - if pfat_index == 1 : - input_name,input_extension = os.path.splitext(os.path.basename(input_file)) - input_dir = os.path.dirname(os.path.abspath(input_file)) - - print('\n*** %s%s' % (input_name, input_extension)) - - if not pfat_match : - print('\n Error: This is not an AMI BIOS Guard (PFAT) image!') - continue - - output_path = os.path.join(input_dir, '%s%s' % (input_name, input_extension) + '_extracted') # Set extraction directory - - if os.path.isdir(output_path) : shutil.rmtree(output_path) # Delete any existing extraction directory - - os.mkdir(output_path) # Create extraction directory - - if not pfat_match : continue - - buffer = buffer[pfat_match.start() - 0x8:] - - pfat_hdr = get_struct(buffer, 0, PFAT_Header) - - hdr_size = pfat_hdr.Size - hdr_data = buffer[0x11:hdr_size].decode('utf-8').splitlines() - - pfat_hdr.pfat_print() - print(' Title : %s' % hdr_data[0]) - - file_path = os.path.join(output_path, '%s%s -- %d' % (input_name, input_extension, pfat_index)) - - for entry in hdr_data[1:] : - entry_data = entry.split(' ') - entry_data = [s for s in entry_data if s != ''] - entry_flags = int(entry_data[0]) - entry_param = entry_data[1] - entry_blocks = int(entry_data[2]) - entry_name = entry_data[3][1:] - - for i in range(entry_blocks) : blocks.append([entry_name, entry_param, entry_flags, i + 1, entry_blocks]) - - block_count += entry_blocks - - block_start = hdr_size - for i in range(block_count) : - is_file_start = blocks[i][0] != block_name - - if is_file_start : print('\n %s (Parameter: %s, Flags: 0x%X)' % (blocks[i][0], blocks[i][1], blocks[i][2])) - - block_hdr = get_struct(buffer, block_start, PFAT_Block_Header, ['%d/%d' % (blocks[i][3], blocks[i][4])]) - block_hdr.pfat_print() - - block_script_size = block_hdr.ScriptSize - block_script_data = buffer[block_start + block_hdr_size:block_start + block_hdr_size + block_script_size] - block_data_start = block_start + block_hdr_size + block_script_size - block_data_end = block_data_start + block_hdr.DataSize - block_data = buffer[block_data_start:block_data_end] - - block_rsa = get_struct(buffer, block_data_end, PFAT_Block_RSA, ['%d/%d' % (blocks[i][3], blocks[i][4])]) - block_rsa.pfat_print() - - print('\n PFAT Block %d/%d Script:\n' % (blocks[i][3], blocks[i][4])) - is_opcode_div = len(block_script_data) % 8 == 0 - is_begin_end = block_script_data[:8] + block_script_data[-8:] == b'\x01' + b'\x00' * 7 + b'\xFF' + b'\x00' * 7 - if is_opcode_div and is_begin_end and is_bgst : - block_script_decomp = BigScript(code_bytes=block_script_data) - block_script_lines = block_script_decomp.to_string().replace('\t',' ').split('\n') - for line in block_script_lines : - spacing = ' ' * 16 if line.endswith(('begin','end',':')) else ' ' * 24 - operands = [op for op in line.split(' ') if op != ''] - print(spacing + ('{:<12s}' + '{:<11s}' * (len(operands) - 1)).format(*operands)) - elif not is_opcode_div : - print(' Error: Script not divisible by OpCode length!') - elif not is_begin_end : - print(' Error: Script lacks Begin and/or End OpCodes!') - elif not is_bgst : - print(' Error: BIOS Guard Script Tool dependency missing!') - - file_data += block_data - final_data += block_data - - if i and is_file_start and file_data : - file_index += 1 - with open('%s_%0.2d -- %s' % (file_path, file_index, block_name), 'wb') as o : o.write(file_data) - file_data = b'' - - block_name = blocks[i][0] - block_start = block_data_end + block_rsa_size - - with open('%s_%0.2d -- %s' % (file_path, file_index + 1, block_name), 'wb') as o : o.write(file_data) # Last File - - eof_data = buffer[block_start:] # Store any data after the end of PFAT - - with open('%s_00 -- AMI_PFAT_%d_DATA_ALL.bin' % (file_path, pfat_index), 'wb') as final : final.write(final_data + eof_data) - - if eof_data[:-0x100] != b'\xFF' * (len(eof_data) - 0x100) : - eof_path = '%s_%0.2d -- AMI_PFAT_%d_DATA_END.bin' % (file_path, file_index + 2, pfat_index) - with open(eof_path, 'wb') as final : final.write(eof_data) - - if pfat_pat.search(eof_data) : - pfat_index += 1 - ami_pfat_index = ami_pfat.index(input_file) + 1 - ami_pfat.insert(ami_pfat_index, eof_path) - else : - pfat_index = 1 - -input('\nDone!') \ No newline at end of file diff --git a/AMI UCP BIOS Extractor/AMI_UCP_Extract.py b/AMI UCP BIOS Extractor/AMI_UCP_Extract.py deleted file mode 100644 index 7f845b0..0000000 --- a/AMI UCP BIOS Extractor/AMI_UCP_Extract.py +++ /dev/null @@ -1,610 +0,0 @@ -#!/usr/bin/env python3 -#coding=utf-8 - -""" -AMI UCP Extract -AMI UCP BIOS Extractor -Copyright (C) 2021 Plato Mavropoulos -""" - -title = 'AMI UCP BIOS Extractor v1.2' - -print('\n' + title) # Print script title - -import sys - -# Detect Python version -sys_ver = sys.version_info -if sys_ver < (3,7) : - sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1])) - (raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 - sys.exit(1) - -import os -import re -import shutil -import ctypes -import argparse -import traceback -import subprocess -import contextlib - -# Pause after any unexpected Python exception -# https://stackoverflow.com/a/781074 by Torsten Marek -def show_exception_and_exit(exc_type, exc_value, tb) : - if exc_type is KeyboardInterrupt : - print('\n') - else : - print('\nError: %s crashed, please report the following:\n' % title) - traceback.print_exception(exc_type, exc_value, tb) - input('\nPress enter to exit') - - sys.exit(1) - -# Set pause-able Python exception handler -sys.excepthook = show_exception_and_exit - -# Set console/shell window title -user_os = sys.platform -if user_os == 'win32' : ctypes.windll.kernel32.SetConsoleTitleW(title) -elif user_os.startswith('linux') or user_os == 'darwin' or user_os.find('bsd') != -1 : sys.stdout.write('\x1b]2;' + title + '\x07') - -# Set argparse Arguments -ucp_parser = argparse.ArgumentParser() -ucp_parser.add_argument('executables', type=argparse.FileType('r'), nargs='*') -ucp_parser.add_argument('-p', '--path', help='parse files within given folder', type=str) -ucp_parser.add_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true') -ucp_params = ucp_parser.parse_args() - -verify_chk16 = bool(ucp_params.checksum) # Get Checksum16 Verification optional argument - -# Get all files within path -def get_files(path) : - inputs = [] - - for root, _, files in os.walk(path): - for name in files : - inputs.append(os.path.join(root, name)) - - return inputs - -if len(sys.argv) >= 2 : - if bool(ucp_params.path) : - ucp_exec = get_files(ucp_params.path) # CLI with --path - else : - ucp_exec = [] - for executable in ucp_params.executables : - ucp_exec.append(executable.name) # Drag & Drop -else : - in_path = input('\nEnter the full folder path: ') - ucp_exec = get_files(in_path) # Direct Run - -# Set ctypes Structure types -char = ctypes.c_char -uint8_t = ctypes.c_ubyte -uint16_t = ctypes.c_ushort -uint32_t = ctypes.c_uint -uint64_t = ctypes.c_uint64 - -class UAF_HDR(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('ModuleTag', char*4), # 0x00 - ('ModuleSize', uint32_t), # 0x04 - ('Checksum', uint16_t), # 0x08 - ('Unknown0', uint8_t), # 0x0A - ('Unknown1', uint8_t), # 0x0A - ('Reserved', uint32_t), # 0x0C - # 0x10 - ] - - def __init__(self, padd, *args, **kwargs) : - super().__init__(*args, **kwargs) - self.p = padd - - def ucp_print(self, chk16) : - print('\n%s Utility Auxiliary File:\n' % self.p) - print('%s Module Tag : %s' % (self.p, self.ModuleTag.decode('utf-8'))) - print('%s Module Size : 0x%X' % (self.p, self.ModuleSize)) - print('%s Checksum : 0x%0.4X (%s)' % (self.p, self.Checksum, chk16)) - print('%s Unknown 0 : 0x%0.2X' % (self.p, self.Unknown0)) - print('%s Unknown 1 : 0x%0.2X' % (self.p, self.Unknown1)) - print('%s Reserved : 0x%0.8X' % (self.p, self.Reserved)) - -class UAF_MOD(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('CompressSize', uint32_t), # 0x00 - ('OriginalSize', uint32_t), # 0x04 - # 0x08 - ] - - def __init__(self, padd, *args, **kwargs) : - super().__init__(*args, **kwargs) - self.p = padd - - def ucp_print(self) : - print('%s Compress Size : 0x%X' % (self.p, self.CompressSize)) - print('%s Original Size : 0x%X' % (self.p, self.OriginalSize)) - -class UII_HDR(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('UIISize', uint16_t), # 0x00 - ('Checksum', uint16_t), # 0x02 - ('UtilityVersion', uint32_t), # 0x04 i.e. AFU (Unknown Encoding, Signed) - ('InfoSize', uint16_t), # 0x08 - ('SupportBIOS', uint8_t), # 0x0A - ('SupportOS', uint8_t), # 0x0B - ('DataBusWidth', uint8_t), # 0x0C - ('ProgramType', uint8_t), # 0x0D - ('ProgramMode', uint8_t), # 0x0E - ('SourceSafeRelease', uint8_t), # 0x0F - # 0x10 - ] - - def __init__(self, padd, *args, **kwargs) : - super().__init__(*args, **kwargs) - self.p = padd - - def ucp_print(self, chk16) : - sbios = {1: 'ALL', 2: 'AMIBIOS8', 3: 'UEFI', 4: 'AMIBIOS8/UEFI'} - sos = {1: 'DOS', 2: 'EFI', 3: 'Windows', 4: 'Linux', 5: 'FreeBSD', 6: 'MacOS', 128: 'Multi-Platform'} - dbwidth = {1: '16b', 2: '16/32b', 3: '32b', 4: '64b'} - ptype = {1: 'Executable', 2: 'Library', 3: 'Driver'} - pmode = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'} - - SupportBIOS = sbios[self.SupportBIOS] if self.SupportBIOS in sbios else 'Unknown (%d)' % self.SupportBIOS - SupportOS = sos[self.SupportOS] if self.SupportOS in sos else 'Unknown (%d)' % self.SupportOS - DataBusWidth = dbwidth[self.DataBusWidth] if self.DataBusWidth in dbwidth else 'Unknown (%d)' % self.DataBusWidth - ProgramType = ptype[self.ProgramType] if self.ProgramType in ptype else 'Unknown (%d)' % self.ProgramType - ProgramMode = pmode[self.ProgramMode] if self.ProgramMode in pmode else 'Unknown (%d)' % self.ProgramMode - - print('\n%s Utility Identification Information:\n' % self.p) - print('%s UII Size : 0x%X' % (self.p, self.UIISize)) - print('%s Checksum : 0x%0.4X (%s)' % (self.p, self.Checksum, chk16)) - print('%s Tool Version : 0x%0.8X (Unknown)' % (self.p, self.UtilityVersion)) - print('%s Info Size : 0x%X' % (self.p, self.InfoSize)) - print('%s Supported BIOS : %s' % (self.p, SupportBIOS)) - print('%s Supported OS : %s' % (self.p, SupportOS)) - print('%s Data Bus Width : %s' % (self.p, DataBusWidth)) - print('%s Program Type : %s' % (self.p, ProgramType)) - print('%s Program Mode : %s' % (self.p, ProgramMode)) - print('%s SourceSafe Tag : %0.2d' % (self.p, self.SourceSafeRelease)) - -class DIS_HDR(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('PasswordSize', uint16_t), # 0x00 - ('EntryCount', uint16_t), # 0x02 - ('Password', char*12), # 0x04 - # 0x10 - ] - - def __init__(self, padd, *args, **kwargs) : - super().__init__(*args, **kwargs) - self.p = padd - - def ucp_print(self) : - print('\n%s Default Command Status Header:\n' % self.p) - print('%s Password Size : 0x%X' % (self.p, self.PasswordSize)) - print('%s Entry Count : %d' % (self.p, self.EntryCount)) - print('%s Password : %s' % (self.p, self.Password.decode('utf-8'))) - -class DIS_MOD(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('EnabledDisabled', uint8_t), # 0x00 - ('ShownHidden', uint8_t), # 0x01 - ('Command', char*32), # 0x02 - ('Description', char*256), # 0x22 - # 0x122 - ] - - def __init__(self, padd, *args, **kwargs) : - super().__init__(*args, **kwargs) - self.p = padd - - def ucp_print(self) : - enabled = {0: 'Disabled', 1: 'Enabled'} - shown = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'} - - EnabledDisabled = enabled[self.EnabledDisabled] if self.EnabledDisabled in enabled else 'Unknown (%d)' % self.EnabledDisabled - ShownHidden = shown[self.ShownHidden] if self.ShownHidden in shown else 'Unknown (%d)' % self.ShownHidden - - print('\n%s Default Command Status Entry:\n' % self.p) - print('%s State : %s' % (self.p, EnabledDisabled)) - print('%s Display : %s' % (self.p, ShownHidden)) - print('%s Command : %s' % (self.p, self.Command.decode('utf-8').strip())) - print('%s Description : %s' % (self.p, self.Description.decode('utf-8').strip())) - -# Process ctypes Structure Classes -# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky -def get_struct(buffer, start_offset, class_name, param_list = None) : - if param_list is None : param_list = [] - - structure = class_name(*param_list) # Unpack parameter list - struct_len = ctypes.sizeof(structure) - struct_data = buffer[start_offset:start_offset + struct_len] - fit_len = min(len(struct_data), struct_len) - - if (start_offset >= len(buffer)) or (fit_len < struct_len) : - print('\n Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name.__name__)) - - input('\n Press enter to exit') - - sys.exit(1) - - ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len) - - return structure - -# Get Checksum16 validity result -def checksum16(buffer, check) : - if not check : return 'Skipped' - - chk16 = 0 - - for idx in range(0, len(buffer), 2) : - chk16 += int.from_bytes(buffer[idx:idx + 2], 'little') - - chk16 &= 0xFFFF - - return 'Good' if chk16 == 0 else 'Bad' - -# Get all input file AMI UCP patterns -def get_matches(buffer) : - uaf_len_max = 0 # Length of largest detected @UAF - uaf_hdr_off = 0 # Offset of largest detected @UAF - - for uaf in ami_ucp_pat.finditer(buffer) : - uaf_len_cur = int.from_bytes(buffer[uaf.start() + 0x4:uaf.start() + 0x8], 'little') - - if uaf_len_cur > uaf_len_max : - uaf_len_max = uaf_len_cur - uaf_hdr_off = uaf.start() - - return uaf_hdr_off, uaf_len_max - -# Parse & Extract AMI UCP structures -def ucp_extract(buffer, out_dir, level, padd) : - nal_dict = {} # Initialize @NAL Dictionary per UCP - - uaf_hdr = get_struct(buffer, 0, UAF_HDR, [padd]) # Parse @UAF Header Structure - - uaf_chk = checksum16(buffer, verify_chk16) # Get @UAF Header Checksum16 - - # Print @UAF Header Info - uaf_hdr.ucp_print(uaf_chk) - print('%s Compress Size : 0x%X' % (padd, len(buffer))) - print('%s Original Size : 0x%X' % (padd, len(buffer))) - print('%s Module Name : %s' % (padd, tag_dict['UAF'])) - - if uaf_chk == 'Bad' : - input('\n%s Error: Invalid AMI UCP Module UAF Checksum!' % padd) - - uaf_off = uaf_hdr_len # Parsed @UAF, next Modules - uaf_all = [] # Initialize list of all UAF Modules - is_pfat = False # Initialize PFAT BIOS detection - is_dual = False # Initialize AMI/Insyde detection - - while buffer[uaf_off] == 0x40 : # ASCII of @ is 0x40 - uaf_hdr = get_struct(buffer, uaf_off, UAF_HDR, [padd]) # Parse UAF Module Structure - - uaf_tag = uaf_hdr.ModuleTag.decode('utf-8')[1:] # Get unique UAF Module Tag - - if uaf_tag == 'PFC' : is_pfat = True # Detect if UAF Module has PFAT BIOS - - if uaf_tag == 'AMI' : is_dual = True # Detect if UAF Module has dual AMI/Insyde BIOS - - uaf_all.append([uaf_tag, uaf_off, uaf_hdr]) # Store UAF Module Info - - uaf_off += uaf_hdr.ModuleSize # Adjust to next UAF Module offset - - if uaf_off >= len(buffer) : break # Stop parsing at EOF - - # Check if UAF Module NAL exists and place it first - # Parsing NAL first allows naming all UAF Modules - for i in range(len(uaf_all)) : - if uaf_all[i][0] == 'NAL' : - uaf_all.insert(1, uaf_all.pop(i)) # After UII for visual purposes - break # NAL found, skip the rest - - # Parse all UAF Modules - for uaf in uaf_all : - uaf_tag = uaf[0] # Store UAF Module Tag - uaf_off = uaf[1] # Store UAF Module Offset - uaf_hdr = uaf[2] # Store UAF Module Struct - - uaf_data_all = buffer[uaf_off:uaf_off + uaf_hdr.ModuleSize] # UAF Module Entire Data - - uaf_data_mod = uaf_data_all[uaf_hdr_len:] # UAF Module EFI Data - - uaf_data_raw = uaf_data_mod[uaf_mod_len:] # UAF Module Raw Data - - uaf_chk = checksum16(uaf_data_all, verify_chk16) # Get UAF Module Checksum16 - - uaf_hdr.ucp_print(uaf_chk) # Print UAF Module Info - - uaf_mod = get_struct(buffer, uaf_off + uaf_hdr_len, UAF_MOD, [padd]) # Parse UAF Module EFI Structure - - uaf_mod.ucp_print() # Print UAF Module EFI Info - - is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect UAF Module EFI Compression - - rom_name = 'PFAT' if is_pfat else 'BIOS' # Set UAF Module BIOS/ROM name based on PFAT state - - if uaf_tag in nal_dict : uaf_name = nal_dict[uaf_tag] # Always prefer NAL naming first - elif uaf_tag in tag_dict : uaf_name = tag_dict[uaf_tag] # Otherwise use built-in naming - elif uaf_tag == 'ROM' : uaf_name = '%s.bin' % rom_name # BIOS/PFAT Firmware - elif uaf_tag.startswith('R0') : uaf_name = '%s_0%s.bin' % (rom_name, uaf_tag[2:]) # BIOS/PFAT Firmware - elif uaf_tag.startswith('S0') : uaf_name = '%s_0%s.sig' % (rom_name, uaf_tag[2:]) # BIOS/PFAT Signature - elif uaf_tag.startswith('DR') : uaf_name = 'DROM_0%s.bin' % uaf_tag[2:] # Thunderbolt Retimer Firmware - elif uaf_tag.startswith('DS') : uaf_name = 'DROM_0%s.sig' % uaf_tag[2:] # Thunderbolt Retimer Signature - elif uaf_tag.startswith('EC') : uaf_name = 'EC_0%s.bin' % uaf_tag[2:] # Embedded Controller Firmware - elif uaf_tag.startswith('ME') : uaf_name = 'ME_0%s.bin' % uaf_tag[2:] # Management Engine Firmware - else : uaf_name = uaf_tag # Could not name the UAF Module, use Tag instead - - if uaf_name != uaf_tag : - uaf_fext = '' # File extension included in name - print('%s Module Name : %s' % (padd, uaf_name)) - elif uaf_tag in ['CMD','PFC','VER','MEC','NAL','CKV'] : - uaf_fext = '.txt' # Known Text files - print('%s Module Name : %s%s (Unknown)' % (padd, uaf_name, uaf_fext)) - else : - uaf_fext = '.bin' # Unknown files, assume binary - print('%s Module Name : %s%s (Unknown)' % (padd, uaf_name, uaf_fext)) - - # Check if unknown UAF Module Tag is present in NAL but not in built-in dictionary - if uaf_tag in nal_dict and uaf_tag not in tag_dict and not uaf_tag.startswith(('ROM','R0','S0','DR','DS')) : - input('\n%s Note: Detected new AMI UCP Module %s (%s) in NAL!' % (padd, uaf_tag, nal_dict[uaf_tag])) - - # Generate UAF Module File name, depending on whether decompression will be required - uaf_fname = os.path.join(out_dir, '%s%s' % (uaf_name, '.temp' if is_comp else uaf_fext)) - - if uaf_chk == 'Bad' : - input('\n%s Error: Invalid AMI UCP Module %s Checksum!' % (padd, uaf_tag)) - - # Parse Utility Identification Information UAF Module (UII) - if uaf_tag == 'UII' : - info_hdr = get_struct(uaf_data_raw, 0, UII_HDR, [padd]) # Parse UII Module Raw Structure - - info_chk = checksum16(uaf_data_raw, verify_chk16) # Get UII Module Checksum16 - - info_hdr.ucp_print(info_chk) # Print UII Module Info - - # Get UII Module Description text field - desc = uaf_data_raw[info_hdr.InfoSize:info_hdr.UIISize].strip(b'\x00').decode('utf-8') - - print('%s Description : %s' % (padd, desc)) # Print UII Module Description - - if info_chk == 'Bad' : - input('\n%s Error: Invalid AMI UCP Module %s > Info Checksum!' % (padd, uaf_tag)) - - # Store/Save UII Module Info in file - with open(uaf_fname[:-3] + 'txt', 'a') as uii : - with contextlib.redirect_stdout(uii) : - info_hdr.ucp_print(info_chk) # Store UII Module Info - - print('%s Description : %s' % (padd, desc)) # Store UII Module Description - - # Adjust UAF Module Raw Data for extraction - if is_comp : - # Some Compressed UAF Module EFI data lack necessary padding in the end - if uaf_mod.CompressSize > len(uaf_data_raw) : - comp_padd = b'\x00' * (uaf_mod.CompressSize - len(uaf_data_raw)) - uaf_data_raw = uaf_data_mod[:uaf_mod_len] + uaf_data_raw + comp_padd # Add missing padding for decompression - else : - uaf_data_raw = uaf_data_mod[:uaf_mod_len] + uaf_data_raw # Add the EFI/Tiano Compression info before Raw Data - else : - uaf_data_raw = uaf_data_raw[:uaf_mod.OriginalSize] # No compression, extend to end of Original UAF Module size - - # Store/Save UAF Module file - if uaf_tag != 'UII' : # Skip UII binary, already parsed - with open(uaf_fname, 'wb') as out : out.write(uaf_data_raw) - - # UAF Module EFI/Tiano Decompression - if is_comp : - try : - dec_fname = uaf_fname[:-5] + uaf_fext # Decompressed UAF Module file path - subprocess.run(['TianoCompress', '-d', uaf_fname, '-o', dec_fname, '--uefi', '-q'], check = True, stdout = subprocess.DEVNULL) - - with open(dec_fname, 'rb') as dec : uaf_data_raw = dec.read() # Read back the UAF Module decompressed Raw data - - if len(uaf_data_raw) == 0 : raise Exception('DECOMP_OUT_EMPTY') # If decompressed file is empty, something went wrong - - os.remove(uaf_fname) # Successful decompression, delete compressed UAF Module file - - uaf_fname = dec_fname # Adjust UAF Module file path to the decompressed one - except : - print('\n%s Error: Could not extract AMI UCP Module %s via TianoCompress!' % (padd, uaf_tag)) - input('%s Make sure that "TianoCompress" executable exists!' % padd) - - # Process and Print known text only UAF Modules (after EFI/Tiano Decompression) - if uaf_tag in ['CMD','PFC','VER','MEC','CKV'] : # Always referenced in tag_desc - text_data = uaf_data_raw.decode('utf-8') - print('\n%s %s:\n\n%s %s' % (padd, tag_desc[uaf_tag], padd, text_data)) - - # Parse Default Command Status UAF Module (DIS) - if len(uaf_data_raw) and uaf_tag == 'DIS' : - dis_hdr = get_struct(uaf_data_raw, 0, DIS_HDR, [padd]) # Parse DIS Module Raw Header Structure - dis_hdr.ucp_print() # Print DIS Module Raw Header Info - - # Store/Save DIS Module Header Info in file - with open(uaf_fname[:-3] + 'txt', 'a') as dis : - with contextlib.redirect_stdout(dis) : - dis_hdr.ucp_print() # Store DIS Module Header Info - - dis_data = uaf_data_raw[uaf_hdr_len:] # DIS Module Entries Data - - # Parse all DIS Module Entries - for e_idx in range(dis_hdr.EntryCount) : - dis_mod = get_struct(dis_data, e_idx * 0x122, DIS_MOD, [padd]) # Parse DIS Module Raw Entry Structure - dis_mod.ucp_print() # Print DIS Module Raw Entry Info - - # Store/Save DIS Module Entry Info in file - with open(uaf_fname[:-3] + 'txt', 'a') as dis : - with contextlib.redirect_stdout(dis) : - dis_mod.ucp_print() # Store DIS Module Entry Info - - os.remove(uaf_fname) # Delete DIS Module binary, info exported as text - - # Parse Non-AMI List (?) UAF Module (NAL) - if len(uaf_data_raw) >= 5 and (uaf_tag,uaf_data_raw[0],uaf_data_raw[4]) == ('NAL',0x40,0x3A) : - nal_info = uaf_data_raw.decode('utf-8').strip().replace('\r','').split('\n') - - print('\n%s UAF List:\n' % padd) - - # Parse all NAL Module Entries - for info in nal_info : - print('%s %s : %s' % (padd, info[1:4], info[5:])) # Print NAL Module Tag-Path Info - nal_dict[info[1:4]] = os.path.basename(info[5:]) # Assign a file name (w/o path) to each Tag - - # Parse Insyde BIOS UAF Module (INS) - if len(uaf_data_raw) >= 2 and (uaf_tag,is_dual,uaf_data_raw[:2]) == ('INS',True,b'\x4D\x5A') : - ins_dir = os.path.join(out_dir, '%s_extracted (SFX)' % uaf_tag) # Generate extraction directory - - print('\n%s Insyde BIOS 7-Zip SFX Archive:\n\n%s 7-Zip will be used for extraction' % (padd, padd)) - - # INS Module extraction - try : - subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + ins_dir, uaf_fname], check = True, stdout = subprocess.DEVNULL) - - if not os.path.isdir(ins_dir) : raise Exception('EXTR_DIR_MISSING') # If extraction folder is missing, something went wrong - - os.remove(uaf_fname) # Successful extraction, delete archived INS Module file - except : - print('\n%s Error: Could not extract AMI UCP Module %s via 7-Zip!' % (padd, uaf_tag)) - input('%s Make sure that "7z" executable exists!' % padd) - - # Detect AMI BIOS Guard (PFAT) image and print extraction instructions/utility - if len(uaf_data_raw) >= 16 and (is_pfat,uaf_data_raw[0x8:0x10]) == (True,b'_AMIPFAT') : - print('\n%s AMI BIOS Guard (PFAT) Image:\n' % padd) - print('%s Use "AMI BIOS Guard Extractor" from https://github.com/platomav/BIOSUtilities' % padd) - - # Detect Intel Management Engine (ME) image and print parsing instructions/utility - if len(uaf_data_raw) and uaf_name.startswith('ME_0') : - print('\n%s Intel Management Engine (ME) Firmware:\n' % padd) - print('%s Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer' % padd) - - # Get best Nested AMI UCP Pattern match based on @UAF Size - uaf_hdr_off,uaf_len_max = get_matches(uaf_data_raw) - - # Parse Nested AMI UCP Structure - if uaf_hdr_off : - level += 1 # Increase structure Level to control output padding - uaf_dir = os.path.join(out_dir, '%s_extracted (UCP)' % uaf_tag) # Generate extraction directory - os.mkdir(uaf_dir) # Create extraction directory - ucp_extract(uaf_data_raw[uaf_hdr_off:uaf_hdr_off + uaf_len_max], uaf_dir, level, ' ' * level) # Call recursively - os.remove(uaf_fname) # Delete raw nested AMI UCP Structure after successful recursion/extraction - -# Utility Auxiliary File (@UAF) and Utility Identification Information (@UII) -ami_ucp_pat = re.compile(br'\x40\x55\x41\x46.{12}\x40\x55\x49\x49', re.DOTALL) - -# Get common ctypes Structure Sizes -uaf_hdr_len = ctypes.sizeof(UAF_HDR) -uaf_mod_len = ctypes.sizeof(UAF_MOD) - -# User friendly Tag Descriptions -tag_desc = { - 'CMD' : 'AMI AFU Command', - 'PFC' : 'AMI BGT Command', - 'VER' : 'OEM Version', - 'CKV' : 'Check Version', - 'MEC' : 'ME FWUpdLcl Command', - } - -# AMI UCP Tag-File Dictionary -tag_dict = { - 'W32' : 'amifldrv32.sys', - 'W64' : 'amifldrv64.sys', - 'VXD' : 'amifldrv.vxd', - 'DCT' : 'DevCon32.exe', - 'DCX' : 'DevCon64.exe', - 'CMD' : 'AFU_Command.txt', - 'PFC' : 'BGT_Command.txt', - 'VER' : 'OEM_Version.txt', - 'CKV' : 'Check_Version.txt', - 'OKM' : 'OK_Message.txt', - 'CPM' : 'AC_Message.txt', - 'DIS' : 'Command_Status.bin', - 'UAF' : 'UCP_Main.bin', - 'UII' : 'UCP_Info.txt', - 'NAL' : 'UAF_List.txt', - 'MEC' : 'FWUpdLcl.txt', - 'MED' : 'FWUpdLcl_DOS.exe', - 'MET' : 'FWUpdLcl_WIN.exe', - 'AMI' : 'UCP_Nested.bin', - 'INS' : 'Insyde_Nested.bin', - 'RFI' : 'CryptRSA.efi', - 'R3I' : 'CryptRSA32.efi', - 'UFI' : 'HpBiosUpdate.efi', - 'US9' : 'HpBiosUpdate.s09', - 'US2' : 'HpBiosUpdate.s12', - 'USG' : 'HpBiosUpdate.sig', - '3FI' : 'HpBiosUpdate32.efi', - '3S9' : 'HpBiosUpdate32.s09', - '3S2' : 'HpBiosUpdate32.s12', - '3SG' : 'HpBiosUpdate32.sig', - 'MFI' : 'HpBiosMgmt.efi', - 'MS9' : 'HpBiosMgmt.s09', - 'MS2' : 'HpBiosMgmt.s12', - 'US4' : 'HpBiosUpdate.s14', - '3S4' : 'HpBiosUpdate32.s14', - 'MS4' : 'HpBiosMgmt.s14', - 'M3I' : 'HpBiosMgmt32.efi', - 'M39' : 'HpBiosMgmt32.s09', - 'M32' : 'HpBiosMgmt32.s12', - 'M34' : 'HpBiosMgmt32.s14', - 'BME' : 'BiosMgmt.efi', - 'BM9' : 'BiosMgmt.s09', - 'B12' : 'BiosMgmt.s12', - 'B14' : 'BiosMgmt.s14', - 'B3E' : 'BiosMgmt32.efi', - 'B39' : 'BiosMgmt32.s09', - 'B32' : 'BiosMgmt32.s12', - 'B34' : 'BiosMgmt32.s14', - 'DFE' : 'HpDevFwUpdate.efi', - 'DFS' : 'HpDevFwUpdate.s12', - 'ENB' : 'ENBG64.exe', - } - -# Process each input AMI UCP BIOS executable -for input_file in ucp_exec : - input_name,input_extension = os.path.splitext(os.path.basename(input_file)) - input_dir = os.path.dirname(os.path.abspath(input_file)) - - print('\n*** %s%s' % (input_name, input_extension)) - - # Check if input file exists - if not os.path.isfile(input_file) : - print('\n Error: This input file does not exist!') - continue # Next input file - - with open(input_file, 'rb') as in_file : input_data = in_file.read() - - # Get best AMI UCP Pattern match based on @UAF Size - uaf_hdr_off,uaf_len_max = get_matches(input_data) - - # Check if AMI UCP Pattern was found on executable - if not uaf_hdr_off : - print('\n Error: This is not an AMI UCP BIOS executable!') - continue # Next input file - - output_path = os.path.join(input_dir, '%s%s' % (input_name, input_extension) + '_extracted') # Set extraction directory - - if os.path.isdir(output_path) : shutil.rmtree(output_path) # Delete any existing extraction directory - - os.mkdir(output_path) # Create extraction directory - - print('\n AMI Utility Configuration Program') - - level = 0 # Set initial AMI UCP structure Level to control padding in nested ones - - ucp_extract(input_data[uaf_hdr_off:uaf_hdr_off + uaf_len_max], output_path, level, '') # Call the AMI UCP Extractor function - - print('\n Extracted AMI UCP BIOS executable!') - -input('\nDone!') - -sys.exit(0) \ No newline at end of file diff --git a/Apple EFI File Renamer/Apple_EFI_Rename.py b/Apple EFI File Renamer/Apple_EFI_Rename.py deleted file mode 100644 index ac69452..0000000 --- a/Apple EFI File Renamer/Apple_EFI_Rename.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 - -""" -Apple EFI Rename -Apple EFI File Renamer -Copyright (C) 2018-2019 Plato Mavropoulos -https://github.com/tianocore/edk2/blob/master/Vlv2TbltDevicePkg/Include/Library/BiosIdLib.h -""" - -print('Apple EFI File Renamer v1.3\n') - -import os -import re -import sys -import zlib -import shutil -import subprocess - -pattern = re.compile(br'\x24\x49\x42\x49\x4F\x53\x49\x24') # Intel $IBIOSI$ - -if len(sys.argv) >= 3 and sys.argv[1] == '-skip' : - # Called via Apple_EFI_Package - apple_efi = sys.argv[2:] - skip_pause = True -elif len(sys.argv) >= 2 : - # Drag & Drop or CLI - apple_efi = sys.argv[1:] - skip_pause = False -else : - # Folder path - apple_efi = [] - skip_pause = False - in_path = input('\nEnter the full folder path: ') - print('\nWorking...\n') - for root, dirs, files in os.walk(in_path): - for name in files : - apple_efi.append(os.path.join(root, name)) - -for input_file in apple_efi : - file_path = os.path.abspath(input_file) - file_name = os.path.basename(input_file) - file_dir = os.path.dirname(file_path) - file_ext = os.path.splitext(file_path)[1] - error = False - - with open(input_file, 'rb') as in_file : buffer = in_file.read() - - is_ibiosi = pattern.search(buffer) # Detect $IBIOSI$ pattern - - if not is_ibiosi : - - # On some Apple EFI, the $IBIOSI$ pattern is within compressed modules so we need to use UEFIFind and UEFIExtract - - try : - uefifind = subprocess.check_output(['UEFIFind', file_path, 'body', 'list', '244942494F534924'], universal_newlines=True) - uefiextr = subprocess.run(['UEFIExtract', file_path, uefifind[0:36], '-o', '_$IBIOSI$_', '-m', 'body'], stdout=subprocess.DEVNULL) - - with open(os.path.join('_$IBIOSI$_', 'body.bin'), 'rb') as in_file : buffer = in_file.read() - - is_ibiosi = pattern.search(buffer) # Detect decompressed $IBIOSI$ pattern - - shutil.rmtree('_$IBIOSI$_') # Remove temporary folder - - except : - error = True - - if not error : - - bios_info = buffer[is_ibiosi.end():is_ibiosi.end() + 0x42].decode('utf-16') - - BoardID = bios_info[:7].strip() - BoardRev = bios_info[7] - OEMID = bios_info[9:12] # 88Z - MajorVer = bios_info[13:17] - BuildType = bios_info[18] # B - MinorVer = bios_info[19:21] - Year = bios_info[22:24] - Month = bios_info[24:26] - Day = bios_info[26:28] - Hour = bios_info[28:30] - Minute = bios_info[30:32] - - file_chk = zlib.adler32(buffer) # Checksum for EFI with same $IBIOSI$ but different PRD/PRE status - - new_name = '%s%s_%s_%s%s_20%s-%s-%s_%s-%s_%0.8X%s' % (BoardID, BoardRev, MajorVer, BuildType, MinorVer, Year, Month, Day, Hour, Minute, file_chk, file_ext) - - file_path_new = os.path.join(file_dir, new_name) - - if not os.path.isfile(file_path_new) : os.replace(file_path, file_path_new) # Rename input EFI with proper name - - print(new_name) - print('\nBoard Identity: %s%s' % (BoardID, BoardRev)) - print('Apple Identity: %s' % OEMID) - print('Major Version: %s' % MajorVer) - print('Minor Version: %s' % MinorVer) - print('Build Type: %s' % BuildType) - print('Build Date: 20%s-%s-%s' % (Year, Month, Day)) - print('Build Time: %s:%s\n' % (Hour, Minute)) - - else : - print('\nError: Could not find $IBIOSI$ pattern at %s!' % file_name) - print(' Make sure that "UEFIFind" and "UEFIExtract" executables exist!\n') - -if not skip_pause : input('Done!') \ No newline at end of file diff --git a/Apple EFI IM4P Splitter/Apple_EFI_Split.py b/Apple EFI IM4P Splitter/Apple_EFI_Split.py deleted file mode 100644 index cb6662a..0000000 --- a/Apple EFI IM4P Splitter/Apple_EFI_Split.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python3 - -""" -Apple EFI Split -Apple EFI IM4P Splitter -Copyright (C) 2018-2020 Plato Mavropoulos -""" - -title = 'Apple EFI IM4P Splitter v2.1' - -import os -import re -import sys - -im4p = re.compile(br'\x16\x04\x49\x4D\x34\x50\x16\x04') # Apple IM4P -ifd = re.compile(br'\x5A\xA5\xF0\x0F.{172}\xFF{16}', re.DOTALL) # Intel Flash Descriptor (Z¥π. + [0xAC] + 0xFF * 16) - -# Flash Descriptor Component Sizes -comp_dict = { - 0 : 0x80000, # 512 KB - 1 : 0x100000, # 1 MB - 2 : 0x200000, # 2 MB - 3 : 0x400000, # 4 MB - 4 : 0x800000, # 8 MB - 5 : 0x1000000, # 16 MB - 6 : 0x2000000, # 32 MB - 7 : 0x4000000, # 64 MB - 8 : 0x8000000, # 128 MB - 9 : 0x10000000, # 256 MB - } - -# Get input catalog file paths -if len(sys.argv) >= 3 and sys.argv[1] == '-skip' : - # Called via Apple_EFI_Package - apple_im4p = sys.argv[2:] - skip_pause = True - skip_space = ' ' - print('\n%s%s' % (skip_space, title)) # Print Title -elif len(sys.argv) >= 2 : - # Drag & Drop or CLI - apple_im4p = sys.argv[1:] - skip_pause = False - skip_space = '' - print('\n%s%s' % (skip_space, title)) # Print Title -else : - # Folder path - apple_im4p = [] - skip_pause = False - skip_space = '' - print('\n%s%s' % (skip_space, title)) # Print Title - in_path = input('\nEnter the full folder path: ') - print('\nWorking...') - for root, dirs, files in os.walk(in_path): - for name in files : - apple_im4p.append(os.path.join(root, name)) - -for input_file in apple_im4p : - file_path = os.path.abspath(input_file) - file_name = os.path.basename(input_file) - file_dir = os.path.dirname(file_path) - file_ext = os.path.splitext(file_path)[1] - - print('\n%sFile: %s' % (skip_space, file_name)) # Print File Name - - # Must be IM4P file because its size is 0x0 dependent - if file_ext not in ('.im4p','.IM4P') : - print('\n%s Error: Could not find IM4P file extension at %s!' % (skip_space, file_name)) - continue # Critical error - - with open(input_file, 'rb') as in_file : buffer = in_file.read() - - is_im4p = im4p.search(buffer) # Detect IM4P pattern - - if not is_im4p : - print('\n%s Error: Could not find IM4P pattern at %s!' % (skip_space, file_name)) - continue # Critical error - - im4p_size = int.from_bytes(buffer[2:is_im4p.start()], 'big') # Variable, from 0x2 - IM4P - im4p_type = buffer[is_im4p.end():is_im4p.end() + 0x4].decode('utf-8') # mefi - - if im4p_type != 'mefi' : - print('\n%s Error: Could not find "mefi" IM4P Type at %s!' % (skip_space, file_name)) - continue # Critical error - - # After IM4P mefi (0x15), multi EFI payloads have _MEFIBIN (0x100) which is difficult to reverse without varying samples. - # However, _MEFIBIN is not required for splitting SPI/EFI images because Intel Flash Descriptor Component Density exists. - mefi_data_start = is_im4p.start() + buffer[is_im4p.start() - 0x1] # IM4P mefi payload start offset - mefi_data_size = int.from_bytes(buffer[is_im4p.end() + 0x9:is_im4p.end() + 0xD], 'big') # IM4P mefi payload size - mefibin_exist = buffer[mefi_data_start:mefi_data_start + 0x8] == b'_MEFIBIN' # Check if mefi is followed by _MEFIBIN - efi_data_start = mefi_data_start + 0x100 if mefibin_exist else mefi_data_start # Actual multi EFI payloads start after _MEFIBIN - efi_data_size = mefi_data_size - 0x100 if mefibin_exist else mefi_data_size # Actual multi EFI payloads size without _MEFIBIN - buffer = buffer[efi_data_start:efi_data_start + efi_data_size] # Adjust input file buffer to actual multi EFI payloads data - - fd_matches = list(ifd.finditer(buffer)) # Find Intel Flash Descriptor pattern matches - fd_count = len(fd_matches) # Count found Intel Flash Descriptor pattern matches - fd_final = [] # Initialize final Intel Flash Descriptor info storage - - # Parse Intel Flash Descriptor pattern matches - for fd_idx in range(fd_count) : - fd = fd_matches[fd_idx] # Get Intel Flash Descriptor match object - - fd_flmap0_fcba = buffer[fd.start() + 0x4] * 0x10 # Component Base Address from FD start (ICH8-ICH10 = 1, IBX = 2, CPT+ = 3) - - # I/O Controller Hub (ICH) - if fd_flmap0_fcba == 0x10 : - start_substruct = 0x0 # At ICH, Flash Descriptor starts at 0x0 - end_substruct = 0xBC # 0xBC for [0xAC] + 0xFF * 16 sanity check - # Platform Controller Hub (PCH) - else : - start_substruct = 0x10 # At PCH, Flash Descriptor starts at 0x10 - end_substruct = 0xBC # 0xBC for [0xAC] + 0xFF * 16 sanity check - - fd_match_start = fd.start() - start_substruct # Actual Flash Descriptor Start Offset - fd_match_end = fd.end() - end_substruct # Actual Flash Descriptor End Offset - - # Calculate Intel Flash Descriptor Flash Component Total Size - fd_flmap0_nc = ((int.from_bytes(buffer[fd_match_end:fd_match_end + 0x4], 'little') >> 8) & 3) + 1 # Component Count (00 = 1, 01 = 2) - fd_flmap1_isl = buffer[fd_match_end + 0x7] # PCH/ICH Strap Length (ME 2-8 & TXE 0-2 & SPS 1-2 <= 0x12, ME 9+ & TXE 3+ & SPS 3+ >= 0x13) - fd_comp_den = buffer[fd_match_start + fd_flmap0_fcba] # Component Density Byte (ME 2-8 & TXE 0-2 & SPS 1-2 = 0:5, ME 9+ & TXE 3+ & SPS 3+ = 0:7) - fd_comp_1_bitwise = 0xF if fd_flmap1_isl >= 0x13 else 0x7 # Component 1 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4) - fd_comp_2_bitwise = 0x4 if fd_flmap1_isl >= 0x13 else 0x3 # Component 2 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4) - fd_comp_all_size = comp_dict[fd_comp_den & fd_comp_1_bitwise] # Component 1 Density (FCBA > C0DEN) - if fd_flmap0_nc == 2 : fd_comp_all_size += comp_dict[fd_comp_den >> fd_comp_2_bitwise] # Component 2 Density (FCBA > C1DEN) - - fd_final.append((fd_match_start,fd_comp_all_size)) # Store Intel Flash Descriptor final info - - # Split IM4P via the final Intel Flash Descriptor matches - for fd_idx in range(fd_count) : - fd = fd_final[fd_idx] # Get Intel Flash Descriptor final info [FD Start, FD Component(s) Size] - - # The Intel Flash Descriptor Flash Component Total Size should be enough to split the IM4P. - # However, for sanity, its Size can be compared to the Size different of Next - Current FD. - fd_diff_size = len(buffer) - fd[0] if fd_idx == fd_count - 1 else fd_final[fd_idx + 1][0] - fd[0] # Last FD ends at mefi payload end - if fd[1] != fd_diff_size : # FD Total Component Size should be equal to Next-Current FD Difference Size - print('\n%s Error: Intel FD %d/%d Component Size 0x%X != Next-Current FD Size Difference 0x%X!' - % (skip_space, fd_idx + 1, fd_count, fd[1], fd_diff_size)) - - file_data = buffer[fd[0]:fd[0] + max(fd[1], fd_diff_size)] # Split EFI image file data (use largest FD Size just in case) - - file_path_new = os.path.join(file_dir, '%s_%d.fd' % (file_name[:-5], fd_idx + 1)) # Split EFI image file path - - with open(file_path_new, 'wb') as spi_image : spi_image.write(file_data) # Store split EFI image file - - print('\n%s Split IM4P into %d EFI image(s)!' % (skip_space, fd_count)) - -if not skip_pause : input('\nDone!') \ No newline at end of file diff --git a/Apple EFI Package Extractor/Apple_EFI_Package.py b/Apple EFI Package Extractor/Apple_EFI_Package.py deleted file mode 100644 index 902a926..0000000 --- a/Apple EFI Package Extractor/Apple_EFI_Package.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python3 - -""" -Apple EFI Package -Apple EFI Package Extractor -Copyright (C) 2019 Plato Mavropoulos -""" - -print('Apple EFI Package Extractor v1.1') - -import os -import sys -import zlib -import shutil -import subprocess - -if len(sys.argv) >= 2 : - pkg = sys.argv[1:] -else : - pkg = [] - in_path = input('\nEnter the full folder path: ') - print('\nWorking...') - for root, dirs, files in os.walk(in_path): - for name in files : - pkg.append(os.path.join(root, name)) - -anytoiso_path = 'C:\\Program Files (x86)\\AnyToISO\\anytoiso.exe' - -final_path = os.path.join(os.getcwd(), 'AppleEFI') -if os.path.exists(final_path) : shutil.rmtree(final_path) - -for input_file in pkg : - file_path = os.path.abspath(input_file) - file_name = os.path.basename(input_file) - file_dir = os.path.dirname(file_path) - file_ext = os.path.splitext(file_path)[1] - - print('\nFile: %s\n' % file_name) - - with open(input_file, 'rb') as in_buff : file_adler = zlib.adler32(in_buff.read()) & 0xFFFFFFFF - - pkg_payload = os.path.join(final_path, '%s_%0.8X' % (file_name, file_adler)) - pkg_temp = os.path.join(final_path, '__TEMP_%s_%0.8X' % (file_name, file_adler)) - os.makedirs(pkg_temp) - - subprocess.run([anytoiso_path, '/extract', file_path, pkg_temp], check = True, stdout=subprocess.DEVNULL) - - if os.path.isfile(os.path.join(pkg_temp, 'Scripts')) : - scripts_init = os.path.join(pkg_temp, 'Scripts') - scripts_cpgz = os.path.join(pkg_temp, 'Scripts.cpgz') - scripts_extr = os.path.join(pkg_temp, 'Scripts', '') - efi_path = os.path.join(scripts_extr, 'Tools', 'EFIPayloads', '') - - os.replace(scripts_init, scripts_cpgz) - - subprocess.run([anytoiso_path, '/extract', scripts_cpgz, scripts_extr], check = True, stdout=subprocess.DEVNULL) - - shutil.copytree(efi_path, pkg_payload) - - elif os.path.isfile(os.path.join(pkg_temp, 'Payload')) : - payload_init = os.path.join(pkg_temp, 'Payload') - payload_pbzx = os.path.join(pkg_temp, 'Payload.pbzx') - payload_extr = os.path.join(pkg_temp, 'Payload', '') - zip_path = os.path.join(payload_extr, 'usr', 'standalone', 'firmware', 'bridgeOSCustomer.bundle', 'Contents', 'Resources', 'UpdateBundle') - efi_path = os.path.join(zip_path, 'boot', 'Firmware', 'MacEFI', '') - - os.replace(payload_init, payload_pbzx) - - subprocess.run([anytoiso_path, '/extract', payload_pbzx, payload_extr], check = True, stdout=subprocess.DEVNULL) - - subprocess.run([anytoiso_path, '/extract', zip_path + '.zip', zip_path], check = True, stdout=subprocess.DEVNULL) - - if os.path.exists(efi_path) : shutil.copytree(efi_path, pkg_payload) - - shutil.rmtree(pkg_temp) - - im4p_files = [] - for root, dirs, files in os.walk(pkg_payload): - for name in files : - if name.endswith('.im4p') : - im4p_files.append(os.path.join(root, name)) - - if im4p_files : subprocess.run(['python', 'Apple_EFI_Split.py', '-skip', *im4p_files], check = True, stdout=subprocess.DEVNULL) - for im4p in im4p_files : os.remove(im4p) - - final_files = [] - for root, dirs, files in os.walk(pkg_payload): - for name in files : - final_files.append(os.path.join(root, name)) - - if final_files : subprocess.run(['python', 'Apple_EFI_Rename.py', '-skip', *final_files], check = True, stdout=subprocess.DEVNULL) - - for root, dirs, files in os.walk(pkg_payload): - for name in files : - if not os.path.isfile(os.path.join(final_path, name)) : - shutil.copy2(os.path.join(root, name), os.path.join(final_path, name)) - - shutil.rmtree(pkg_payload) - -else : - input('\nDone!') \ No newline at end of file diff --git a/Apple EFI Package Grabber/Apple_EFI_Grab.dat b/Apple EFI Package Grabber/Apple_EFI_Grab.dat deleted file mode 100644 index 5740129..0000000 --- a/Apple EFI Package Grabber/Apple_EFI_Grab.dat +++ /dev/null @@ -1,4 +0,0 @@ -2021-01-01 00:00:00 - - - diff --git a/Apple EFI Package Grabber/Apple_EFI_Grab.py b/Apple EFI Package Grabber/Apple_EFI_Grab.py deleted file mode 100644 index c4d544b..0000000 --- a/Apple EFI Package Grabber/Apple_EFI_Grab.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python3 -#coding=utf-8 - -""" -Apple EFI Grab -Apple EFI Package Grabber -Copyright (C) 2018-2021 Plato Mavropoulos -""" - -title = 'Apple EFI Package Grabber v2.0' - -print('\n' + title) - -import sys - -sys_ver = sys.version_info -if sys_ver < (3,7): - sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1])) - (raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 - sys.exit(1) - -import traceback - -def show_exception_and_exit(exc_type, exc_value, tb): - if exc_type is KeyboardInterrupt: - print('\nNote: Keyboard Interrupt!') - else: - print('\nError: %s crashed, please report the following:\n' % title) - traceback.print_exception(exc_type, exc_value, tb) - input('\nPress enter to exit') - - sys.exit(1) - -sys.excepthook = show_exception_and_exit - -import datetime -import urllib.request -from multiprocessing.pool import ThreadPool - -def fetch_cat_info(name): - url = cat_url[:-len('others/')] + name if name in ['index.sucatalog','index-1.sucatalog'] else cat_url + name - with urllib.request.urlopen(urllib.request.Request(url, method='HEAD')) as head : mod = head.headers['last-modified'] - - return name, url, mod - -def fetch_cat_links(cat_file): - cat_links = [] - - with urllib.request.urlopen(cat_file[1]) as link: fdata = link.readlines() - - cat_lines = [l.decode('utf-8').strip('\n') for l in fdata] - - for line in cat_lines: - if ('.pkg' in line or '.tar' in line) and ('FirmwareUpd' in line or '/BridgeOSUpdateCustomer' in line or 'EFIUpd' in line) \ - and 'Bluetooth' not in line and 'DPVGA' not in line and 'Thunderbolt' not in line and 'PMG5' not in line and 'HardDrive' not in line: - down_link = line[line.find('http'):(line.find('.pkg') if '.pkg' in line else line.find('.tar')) + 4] - down_link = down_link.replace('http:','https:') - cat_links.append(down_link) - - return cat_links - -dat_db = 'Apple_EFI_Grab.dat' -cat_url = 'https://swscan.apple.com/content/catalogs/others/' -apple_cat = [] -down_links = [] -svr_date = None -thread_num = 2 - -with open(dat_db, 'r', encoding='utf-8') as dat: db_lines = dat.readlines() -db_lines = [line.strip('\n') for line in db_lines] - -db_date = datetime.datetime.strptime(db_lines[0], '%Y-%m-%d %H:%M:%S') -db_links = set([line for line in db_lines if line.startswith('https')]) -db_sucat = [line for line in db_lines if line.startswith('index')] - -print('\nGetting Catalog Listing...') - -if not db_sucat: - input('\nError: Failed to retrieve Catalogs from DB!\n\nDone!') - sys.exit(1) - -apple_mod = ThreadPool(thread_num).imap_unordered(fetch_cat_info, db_sucat) - -for name, url, mod in apple_mod: - dt = datetime.datetime.strptime(mod, '%a, %d %b %Y %H:%M:%S %Z') - if not svr_date or dt > svr_date : svr_date = dt - - apple_cat.append((name, url, dt)) - -if not svr_date: - input('\nError: Failed to retrieve Current Catalog Datetime!\n\nDone!') - sys.exit(1) - -print('\n Previous Catalog Datetime :', db_date) -print(' Current Catalog Datetime :', svr_date) - -if svr_date <= db_date: - input('\nNothing new since %s!\n\nDone!' % db_date) - sys.exit() - -print('\nGetting Catalog Links...') - -down_links = ThreadPool(thread_num).imap_unordered(fetch_cat_links, apple_cat) -down_links = [item for sublist in down_links for item in sublist] - -if not down_links: - input('\nError: Failed to retrieve Catalog Links!\n\nDone!') - sys.exit(1) - -new_links = sorted(list(dict.fromkeys([link for link in down_links if link not in db_links]))) - -if new_links: - print('\nFound %d new link(s) between %s and %s!' % (len(new_links), db_date, svr_date)) - - cur_date = datetime.datetime.utcnow().isoformat(timespec='seconds').replace('-','').replace('T','').replace(':','') # Local UTC Unix - - with open('Apple_%s.txt' % cur_date, 'w', encoding='utf-8') as lout: lout.write('\n'.join(map(str, new_links))) -else: - print('\nThere are no new links between %s and %s!' % (db_date, svr_date)) - -new_db_sucat = '\n'.join(map(str, db_sucat)) - -new_db_links = '\n'.join(map(str, sorted(list(dict.fromkeys(down_links))))) - -new_db_lines = '%s\n\n%s\n\n%s' % (svr_date, new_db_sucat, new_db_links) - -with open(dat_db, 'w', encoding='utf-8') as dbout: dbout.write(new_db_lines) - -input('\nDone!') \ No newline at end of file diff --git a/Award BIOS Module Extractor/Award_BIOS_Extract.py b/Award BIOS Module Extractor/Award_BIOS_Extract.py deleted file mode 100644 index 5a39788..0000000 --- a/Award BIOS Module Extractor/Award_BIOS_Extract.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python3 - -""" -Award BIOS Extract -Award BIOS Module Extractor -Copyright (C) 2018-2019 Plato Mavropoulos -http://www.onicos.com/staff/iz/formats/lzh.html -https://ist.uwaterloo.ca/~schepers/formats/LHA.TXT -https://sites.google.com/site/pinczakko/pinczakko-s-guide-to-award-bios-reverse-engineering -""" - -print('Award BIOS Module Extractor v1.2\n') - -import os -import re -import sys -import subprocess - -if len(sys.argv) >= 2 : - # Drag & Drop or CLI - awd_images = sys.argv[1:] -else : - # Folder path - awd_images = [] - in_path = input('\nEnter the full folder path: ') - print('\nWorking...') - for root, dirs, files in os.walk(in_path): - for name in files : - awd_images.append(os.path.join(root, name)) - -pat_lzh = re.compile(br'\x2D\x6C((\x68(([\x30-\x37])|(\x64)))|(\x7A([\x34\x73])))\x2D') # All 11 LZH Method IDs (Award probably used LH0 and LH5 only) - -# Create output folder -extr_path = os.path.join(os.getcwd(), 'AWD_Extracted') -if not os.path.exists(extr_path) : os.makedirs(extr_path) - -for in_file in awd_images : - file_path = os.path.abspath(in_file) - file_name = os.path.basename(in_file) - file_dir = os.path.dirname(file_path) - file_ext = os.path.splitext(file_path)[1] - match_lzh_list = [] - - print('\nFile: %s%s' % (file_name, file_ext)) - - with open(in_file, 'rb') as awd_img : buffer = awd_img.read() - - match_lzh_list += pat_lzh.finditer(buffer) # Detect LZH patterns - - for match_lzh in match_lzh_list : - hdr_size = buffer[match_lzh.start() - 0x2] # From LZH Tag (0x2+) - comp_size = int.from_bytes(buffer[match_lzh.end():match_lzh.end() + 0x4], 'little') # From LZH Header end - mod_data = buffer[match_lzh.start() - 0x2:match_lzh.start() + hdr_size + comp_size] - - with open('mod_temp.bin', 'wb') as lzh_img : lzh_img.write(mod_data) - - try : - decomp = subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o%s' % os.path.join(extr_path, file_name), 'mod_temp.bin']) # 7-Zip - except : - print('Error: Could not decompress LZH image at %s!' % file_name) - print(' Make sure that "7z" executable exists!\n') - - os.remove('mod_temp.bin') # Remove temporary LZH image - -input('\nDone!') \ No newline at end of file diff --git a/Dell PFS Update Extractor/Dell_PFS_Extract.py b/Dell PFS Update Extractor/Dell_PFS_Extract.py deleted file mode 100644 index af1ad75..0000000 --- a/Dell PFS Update Extractor/Dell_PFS_Extract.py +++ /dev/null @@ -1,1245 +0,0 @@ -#!/usr/bin/env python3 -#coding=utf-8 - -""" -Dell PFS Extract -Dell PFS Update Extractor -Copyright (C) 2018-2021 Plato Mavropoulos -""" - -title = 'Dell PFS Update Extractor v5.1' - -import sys - -# Detect Python version -sys_py = sys.version_info - -# Check Python version -if sys_py < (3,7) : - sys.stdout.write('%s\n\nError: Python >= 3.7 required, not %d.%d!\n' % (title, sys_py[0], sys_py[1])) - - if '--auto-exit' not in sys.argv and '-e' not in sys.argv : - (raw_input if sys_py[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 - - sys.exit(1) - -# Detect OS platform -sys_os = sys.platform - -# Check OS platform -if sys_os == 'win32' : - sys.stdout.reconfigure(encoding='utf-8') # Fix Windows Unicode console redirection -elif sys_os.startswith('linux') or sys_os == 'darwin' or sys_os.find('bsd') != -1 : - pass # Supported/Tested -else : - print('%s\n\nError: Unsupported platform "%s"!\n' % (title, sys_os)) - - if '--auto-exit' not in sys.argv and '-e' not in sys.argv : input('Press enter to exit') - - sys.exit(1) - -# Skip __pycache__ generation -sys.dont_write_bytecode = True - -# Python imports -import os -import re -import zlib -import lzma -import shutil -import ctypes -import inspect -import pathlib -import argparse -import traceback - -# Optional imports -try : - from big_script_tool import BigScript - is_bgst = True -except : - is_bgst = False - -# Set ctypes Structure types -char = ctypes.c_char -uint8_t = ctypes.c_ubyte -uint16_t = ctypes.c_ushort -uint32_t = ctypes.c_uint -uint64_t = ctypes.c_uint64 - -# Dell PFS Header Structure -class PFS_DELL_HDR(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('Tag', char*8), # 0x00 - ('HeaderVersion', uint32_t), # 0x08 - ('PayloadSize', uint32_t), # 0x0C - # 0x10 - ] - - def pfs_print(self, padd) : - print('\n%sPFS Header:\n' % (' ' * (padd - 4))) - print('%sHeader Tag : %s' % (' ' * padd, self.Tag.decode('utf-8'))) - print('%sHeader Version : %d' % (' ' * padd, self.HeaderVersion)) - print('%sPayload Size : 0x%X' % (' ' * padd, self.PayloadSize)) - -# Dell PFS Footer Structure -class PFS_DELL_FTR(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('PayloadSize', uint32_t), # 0x00 - ('Checksum', uint32_t), # 0x04 ~CRC32 w/ Vector 0 - ('Tag', char*8), # 0x08 - # 0x10 - ] - - def pfs_print(self, padd) : - print('\n%sPFS Footer:\n' % (' ' * (padd - 4))) - print('%sPayload Size : 0x%X' % (' ' * padd, self.PayloadSize)) - print('%sPayload Checksum : 0x%0.8X' % (' ' * padd, self.Checksum)) - print('%sFooter Tag : %s' % (' ' * padd, self.Tag.decode('utf-8'))) - -# Dell PFS Entry Revision 1 Structure -class PFS_ENTRY_R1(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('GUID', uint32_t*4), # 0x00 Little Endian - ('HeaderVersion', uint32_t), # 0x10 1 - ('VersionType', uint8_t*4), # 0x14 - ('Version', uint16_t*4), # 0x18 - ('Reserved', uint64_t), # 0x20 - ('DataSize', uint32_t), # 0x28 - ('DataSigSize', uint32_t), # 0x2C - ('DataMetSize', uint32_t), # 0x30 - ('DataMetSigSize', uint32_t), # 0x34 - ('Unknown', uint32_t*4), # 0x38 - # 0x48 - ] - - def pfs_print(self, padd) : - GUID = '%0.*X' % (0x10 * 2, int.from_bytes(self.GUID, 'little')) - Unknown = '%0.*X' % (0x10 * 2, int.from_bytes(self.Unknown, 'little')) - Version = get_entry_ver(self.Version, self.VersionType, padd - 4) - - print('\n%sPFS Entry:\n' % (' ' * (padd - 4))) - print('%sEntry GUID : %s' % (' ' * padd, GUID)) - print('%sEntry Version : %d' % (' ' * padd, self.HeaderVersion)) - print('%sPayload Version : %s' % (' ' * padd, Version)) - print('%sReserved : 0x%X' % (' ' * padd, self.Reserved)) - print('%sPayload Data Size : 0x%X' % (' ' * padd, self.DataSize)) - print('%sPayload Signature Size : 0x%X' % (' ' * padd, self.DataSigSize)) - print('%sMetadata Data Size : 0x%X' % (' ' * padd, self.DataMetSize)) - print('%sMetadata Signature Size : 0x%X' % (' ' * padd, self.DataMetSigSize)) - print('%sUnknown : %s' % (' ' * padd, Unknown)) - -# Dell PFS Entry Revision 2 Structure -class PFS_ENTRY_R2(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('GUID', uint32_t*4), # 0x00 Little Endian - ('HeaderVersion', uint32_t), # 0x10 2 - ('VersionType', uint8_t*4), # 0x14 - ('Version', uint16_t*4), # 0x18 - ('Reserved', uint64_t), # 0x20 - ('DataSize', uint32_t), # 0x28 - ('DataSigSize', uint32_t), # 0x2C - ('DataMetSize', uint32_t), # 0x30 - ('DataMetSigSize', uint32_t), # 0x34 - ('Unknown', uint32_t*8), # 0x38 - # 0x58 - ] - - def pfs_print(self, padd) : - GUID = '%0.*X' % (0x10 * 2, int.from_bytes(self.GUID, 'little')) - Unknown = '%0.*X' % (0x20 * 2, int.from_bytes(self.Unknown, 'little')) - Version = get_entry_ver(self.Version, self.VersionType, padd - 4) - - print('\n%sPFS Entry:\n' % (' ' * (padd - 4))) - print('%sEntry GUID : %s' % (' ' * padd, GUID)) - print('%sEntry Version : %d' % (' ' * padd, self.HeaderVersion)) - print('%sPayload Version : %s' % (' ' * padd, Version)) - print('%sReserved : 0x%X' % (' ' * padd, self.Reserved)) - print('%sPayload Data Size : 0x%X' % (' ' * padd, self.DataSize)) - print('%sPayload Signature Size : 0x%X' % (' ' * padd, self.DataSigSize)) - print('%sMetadata Data Size : 0x%X' % (' ' * padd, self.DataMetSize)) - print('%sMetadata Signature Size : 0x%X' % (' ' * padd, self.DataMetSigSize)) - print('%sUnknown : %s' % (' ' * padd, Unknown)) - -# Dell PFS Information Header Structure -class PFS_INFO_HDR(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('HeaderVersion', uint32_t), # 0x00 - ('GUID', uint32_t*4), # 0x04 Little Endian - # 0x14 - ] - - def pfs_print(self, padd) : - GUID = '%0.*X' % (0x10 * 2, int.from_bytes(self.GUID, 'little')) - - print('\n%sPFS Information Header:\n' % (' ' * (padd - 4))) - print('%sInfo Version : %d' % (' ' * padd, self.HeaderVersion)) - print('%sEntry GUID : %s' % (' ' * padd, GUID)) - -# Dell PFS FileName Header Structure -class PFS_NAME_HDR(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('Version', uint16_t*4), # 0x00 - ('VersionType', uint8_t*4), # 0x08 - ('CharacterCount', uint16_t), # 0x0C UTF-16 2-byte Characters - # 0x0E - ] - - def pfs_print(self, padd) : - Version = get_entry_ver(self.Version, self.VersionType, padd - 4) - - print('\n%sPFS FileName Entry:\n' % (' ' * (padd - 4))) - print('%sPayload Version : %s' % (' ' * padd, Version)) - print('%sCharacter Count : %d' % (' ' * padd, self.CharacterCount)) - -# Dell PFS Metadata Header Structure -class PFS_META_HDR(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('ModelIDs', char*501), # 0x000 - ('FileName', char*100), # 0x1F5 - ('FileVersion', char*33), # 0x259 - ('Date', char*33), # 0x27A - ('Brand', char*80), # 0x29B - ('ModelFile', char*80), # 0x2EB - ('ModelName', char*100), # 0x33B - ('ModelVersion', char*33), # 0x39F - # 0x3C0 - ] - - def pfs_print(self, padd) : - print('\n%sPFS Metadata Information:\n' % (' ' * (padd - 4))) - print('%sModel IDs : %s' % (' ' * padd, self.ModelIDs.decode('utf-8').strip(',END'))) - print('%sFile Name : %s' % (' ' * padd, self.FileName.decode('utf-8'))) - print('%sFile Version : %s' % (' ' * padd, self.FileVersion.decode('utf-8'))) - print('%sDate : %s' % (' ' * padd, self.Date.decode('utf-8'))) - print('%sBrand : %s' % (' ' * padd, self.Brand.decode('utf-8'))) - print('%sModel File : %s' % (' ' * padd, self.ModelFile.decode('utf-8'))) - print('%sModel Name : %s' % (' ' * padd, self.ModelName.decode('utf-8'))) - print('%sModel Version : %s' % (' ' * padd, self.ModelVersion.decode('utf-8'))) - - def pfs_write(self) : - return '%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s' % (self.ModelIDs.decode('utf-8').strip(',END'), self.FileName.decode('utf-8'), - self.FileVersion.decode('utf-8'), self.Date.decode('utf-8'), self.Brand.decode('utf-8'), self.ModelFile.decode('utf-8'), - self.ModelName.decode('utf-8'), self.ModelVersion.decode('utf-8')) - -# Dell PFS BIOS Guard Header Structure -class PFS_PFAT_HDR(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('PFATVerMajor', uint16_t), # 0x00 - ('PFATVerMinor', uint16_t), # 0x02 - ('PlatformID', uint8_t*16), # 0x04 - ('Attributes', uint32_t), # 0x14 - ('ScriptVerMajor', uint16_t), # 0x16 - ('ScriptVerMinor', uint16_t), # 0x18 - ('ScriptSize', uint32_t), # 0x1C - ('DataSize', uint32_t), # 0x20 - ('BIOSSVN', uint32_t), # 0x24 - ('ECSVN', uint32_t), # 0x28 - ('VendorInfo', uint32_t), # 0x2C - # 0x30 - ] - - def __init__(self, count, *args, **kwargs): - super().__init__(*args, **kwargs) - self.count = count - - def get_flags(self) : - attr = PFS_PFAT_HDR_ATTRIBUTES_GET() - attr.asbytes = self.Attributes - - return attr.b.SFAM, attr.b.ProtectEC, attr.b.GFXMitDis, attr.b.FTU, attr.b.Reserved - - def pfs_print(self, padd) : - no_yes = ['No','Yes'] - f1,f2,f3,f4,f5 = self.get_flags() - - PlatformID = bytes(self.PlatformID).strip(b'\x00') - try : # STRING - PlatformID = PlatformID.decode('utf-8') - except : # GUID - PlatformID = '%0.*X' % (0x10 * 2, int.from_bytes(self.PlatformID, 'big')) - PlatformID = '{%s-%s-%s-%s-%s}' % (PlatformID[:8], PlatformID[8:12], PlatformID[12:16], PlatformID[16:20], PlatformID[20:]) - - print('\n%sPFAT Block %d Header:\n' % (' ' * (padd - 4), self.count)) - print('%sPFAT Version : %d.%d' % (' ' * padd, self.PFATVerMajor, self.PFATVerMinor)) - print('%sPlatform ID : %s' % (' ' * padd, PlatformID)) - print('%sSigned Flash Address Map : %s' % (' ' * padd, no_yes[f1])) - print('%sProtected EC OpCodes : %s' % (' ' * padd, no_yes[f2])) - print('%sGraphics Security Disable : %s' % (' ' * padd, no_yes[f3])) - print('%sFault Tolerant Update : %s' % (' ' * padd, no_yes[f4])) - print('%sAttributes Reserved : 0x%X' % (' ' * padd, f5)) - print('%sScript Version : %d.%d' % (' ' * padd, self.ScriptVerMajor, self.ScriptVerMinor)) - print('%sScript Size : 0x%X' % (' ' * padd, self.ScriptSize)) - print('%sData Size : 0x%X' % (' ' * padd, self.DataSize)) - print('%sBIOS SVN : 0x%X' % (' ' * padd, self.BIOSSVN)) - print('%sEC SVN : 0x%X' % (' ' * padd, self.ECSVN)) - print('%sVendor Info : 0x%X' % (' ' * padd, self.VendorInfo)) - -# Dell PFS BIOS Guard Attributes Flags Structure -class PFS_PFAT_HDR_ATTRIBUTES(ctypes.LittleEndianStructure): - _fields_ = [ - ('SFAM', uint32_t, 1), # Signed Flash Address Map - ('ProtectEC', uint32_t, 1), # Protected EC OpCodes - ('GFXMitDis', uint32_t, 1), # GFX Security Disable - ('FTU', uint32_t, 1), # Fault Tolerant Update - ('Reserved', uint32_t, 28) - ] - -# Dell PFS BIOS Guard Attributes Get Structure -class PFS_PFAT_HDR_ATTRIBUTES_GET(ctypes.Union): - _fields_ = [ - ('b', PFS_PFAT_HDR_ATTRIBUTES), - ('asbytes', uint32_t) - ] - -# Dell PFS BIOS Guard Signature Structure -class PFS_PFAT_SIG(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('Unknown0', uint32_t), # 0x00 - ('Unknown1', uint32_t), # 0x04 - ('PublicKey', uint32_t*64), # 0x08 - ('Exponent', uint32_t), # 0x108 - ('Signature', uint32_t*64), # 0x10C - # 0x20C - ] - - def __init__(self, count, *args, **kwargs): - super().__init__(*args, **kwargs) - self.count = count - - def pfs_print(self, padd) : - PublicKey = '%0.*X' % (0x100 * 2, int.from_bytes(self.PublicKey, 'little')) - Signature = '%0.*X' % (0x100 * 2, int.from_bytes(self.Signature, 'little')) - - print('\n%sPFAT Block %d Signature:\n' % (' ' * (padd - 4), self.count)) - print('%sUnknown 0 : 0x%X' % (' ' * padd, self.Unknown0)) - print('%sUnknown 1 : 0x%X' % (' ' * padd, self.Unknown1)) - print('%sPublic Key : %s [...]' % (' ' * padd, PublicKey[:32])) - print('%sExponent : 0x%X' % (' ' * padd, self.Exponent)) - print('%sSignature : %s [...]' % (' ' * padd, Signature[:32])) - -# Dell PFS BIOS Guard Metadata Structure -class PFS_PFAT_MET(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('OffsetTop', uint32_t), # 0x00 - ('Unknown0', uint32_t), # 0x04 - ('OffsetBase', uint32_t), # 0x08 - ('BlockSize', uint32_t), # 0x0C - ('Unknown1', uint32_t), # 0x10 - ('Unknown2', uint32_t), # 0x14 - ('Unknown3', uint8_t), # 0x18 - # 0x19 - ] - - def __init__(self, count, *args, **kwargs): - super().__init__(*args, **kwargs) - self.count = count - - def pfs_print(self, padd) : - print('\n%sPFAT Block %d Metadata:\n' % (' ' * (padd - 4), self.count)) - print('%sOffset Top : 0x%X' % (' ' * padd, self.OffsetTop)) - print('%sUnknown 0 : 0x%X' % (' ' * padd, self.Unknown0)) - print('%sOffset Base : 0x%X' % (' ' * padd, self.OffsetBase)) - print('%sBlock Size : 0x%X' % (' ' * padd, self.BlockSize)) - print('%sUnknown 1 : 0x%X' % (' ' * padd, self.Unknown1)) - print('%sUnknown 2 : 0x%X' % (' ' * padd, self.Unknown2)) - print('%sUnknown 3 : 0x%X' % (' ' * padd, self.Unknown3)) - -# Dell PFS Update Analysis -def main(exit_code, pfs_input_images) : - # Process each input Dell PFS update image - for input_file in pfs_input_images : - input_name,input_ext = os.path.splitext(os.path.basename(input_file)) - input_dir = os.path.dirname(os.path.abspath(input_file)) - - print('\n*** %s%s' % (input_name, input_ext)) - - # Check if input file exists - if not os.path.isfile(input_file) : - print('\n Error: This input file does not exist!') - continue # Next input file - - with open(input_file, 'rb') as in_file : input_data = in_file.read() - - # Search input image for ThinOS PKG 7zXZ section header - lzma_pkg_hdr_match = lzma_pkg_header.search(input_data) - - # Decompress ThinOS PKG 7zXZ section first, if present - if lzma_pkg_hdr_match : - lzma_len_off = lzma_pkg_hdr_match.start() + 0x10 - lzma_len_int = int.from_bytes(input_data[lzma_len_off:lzma_len_off + 0x4], 'little') - lzma_bin_off = lzma_pkg_hdr_match.end() - 0x5 - lzma_bin_dat = input_data[lzma_bin_off:lzma_bin_off + lzma_len_int] - - # Check if the compressed 7zXZ stream is complete, based on header - if len(lzma_bin_dat) != lzma_len_int : - print('\n Error: This Dell ThinOS PKG update image is corrupted!') - continue # Next input file - - input_data = lzma.decompress(lzma_bin_dat) - - # Search input image for PFS ZLIB Sections - pfs_zlib_offsets = get_section_offsets(input_data) - - if not pfs_zlib_offsets : - print('\n Error: This is not a Dell PFS update image!') - continue # Next input file - - # Set user extraction path - extract_path_user = get_absolute_path(args.output_dir) - - # Set main extraction path (optional user specified path taken into account) - extract_path_main = os.path.join(extract_path_user, '%s%s' % (input_name, input_ext) + '_extracted') - - # Parse each PFS ZLIB Section - for offset in pfs_zlib_offsets : - # Call the PFS ZLIB Section Parser function - pfs_section_parse(input_data, offset, extract_path_main, ' ' + input_name, 1, 1, False, 4) - - exit_code -= 1 # Adjust exit code to reflect extraction progress - - if not bool(args.auto_exit) : input('\nDone!') - - return exit_code - -# Get PFS ZLIB Section Offsets -def get_section_offsets(buffer) : - pfs_zlib_init = list(pfs_zlib_header.finditer(buffer)) - - if not pfs_zlib_init : return [] # No PFS ZLIB detected - - pfs_zlib_list = [] # Initialize PFS ZLIB offset list - - # Remove duplicate/nested PFS ZLIB offsets - for zlib_c in pfs_zlib_init : - is_duplicate = False # Initialize duplicate/nested PFS ZLIB offset - - for zlib_o in pfs_zlib_init : - zlib_o_size = int.from_bytes(buffer[zlib_o.start() - 0x5:zlib_o.start() - 0x1], 'little') - - # If current PFS ZLIB offset is within another PFS ZLIB range (start-end), set as duplicate - if zlib_o.start() < zlib_c.start() < zlib_o.start() + zlib_o_size : is_duplicate = True - - if not is_duplicate : pfs_zlib_list.append(zlib_c.start()) - - return pfs_zlib_list - -# Dell PFS ZLIB Section Parser -def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padd) : - is_zlib_error = False # Initialize PFS ZLIB-related error state - - section_type = zlib_data[zlib_start - 0x1] # Byte before PFS ZLIB Section pattern is Section Type (e.g. AA, BB) - section_name = section_dict[section_type] if section_type in section_dict else 'Unknown (%0.2X)' % section_type - - # Set PFS ZLIB Section extraction sub-directory path - section_path = os.path.join(output_path, section_name) - - # Delete existing extraction sub-directory (not in recursions) - if os.path.isdir(section_path) and not is_rec : shutil.rmtree(section_path) - - # Create extraction sub-directory - if not os.path.isdir(section_path) : os.makedirs(section_path) - - # Store the compressed zlib stream start offset - compressed_start = zlib_start + 0xB - - # Store the PFS ZLIB section header start offset - header_start = zlib_start - 0x5 - - # Store the PFS ZLIB section header contents (16 bytes) - header_data = zlib_data[header_start:compressed_start] - - # Check if the PFS ZLIB section header Checksum XOR 8 is valid - if chk_xor_8(header_data[:0xF], 0) != header_data[0xF] : - print('\n%sError: Invalid Dell PFS ZLIB section Header Checksum!' % (' ' * padd)) - is_zlib_error = True - - # Store the compressed zlib stream size from the header contents - compressed_size_hdr = int.from_bytes(header_data[:0x4], 'little') - - # Store the compressed zlib stream end offset - compressed_end = compressed_start + compressed_size_hdr - - # Store the compressed zlib stream contents - compressed_data = zlib_data[compressed_start:compressed_end] - - # Check if the compressed zlib stream is complete, based on header - if len(compressed_data) != compressed_size_hdr : - print('\n%sError: Incomplete Dell PFS ZLIB section data (Header)!' % (' ' * padd)) - is_zlib_error = True - - # Store the PFS ZLIB section footer contents (16 bytes) - footer_data = zlib_data[compressed_end:compressed_end + 0x10] - - # Search input section for PFS ZLIB section footer - pfs_zlib_footer_match = pfs_zlib_footer.search(footer_data) - - # Check if PFS ZLIB section footer was found in the section - if not pfs_zlib_footer_match : - print('\n%sError: This Dell PFS ZLIB section is corrupted!' % (' ' * padd)) - is_zlib_error = True - - # Check if the PFS ZLIB section footer Checksum XOR 8 is valid - if chk_xor_8(footer_data[:0xF], 0) != footer_data[0xF] : - print('\n%sError: Invalid Dell PFS ZLIB section Footer Checksum!' % (' ' * padd)) - is_zlib_error = True - - # Store the compressed zlib stream size from the footer contents - compressed_size_ftr = int.from_bytes(footer_data[:0x4], 'little') - - # Check if the compressed zlib stream is complete, based on footer - if compressed_size_ftr != compressed_size_hdr : - print('\n%sError: Incomplete Dell PFS ZLIB section data (Footer)!' % (' ' * padd)) - is_zlib_error = True - - # Decompress PFS ZLIB section payload - try : - assert not is_zlib_error # ZLIB errors are critical - section_data = zlib.decompress(compressed_data) # ZLIB decompression - except : - section_data = zlib_data # Fallback to raw ZLIB data upon critical error - - # Call the PFS Extract function on the decompressed PFS ZLIB Section - pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padd) - - # Show extraction complete message for each main PFS ZLIB Section - print('\n%sExtracted Dell PFS %d >%s > %s section!' % (' ' * padd, pfs_index, pfs_name, section_name)) - -# Parse & Extract Dell PFS Volume -def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd) : - if is_verbose : print('\n%sPFS Volume:' % (' ' * pfs_padd)) - - # Get PFS Header Structure values - pfs_hdr = get_struct(buffer, 0, PFS_DELL_HDR, None, pfs_padd + 4) - - # Validate that a PFS Header was parsed - if pfs_hdr.Tag != b'PFS.HDR.' : - msg_print(pfs_padd + 4, 'Error: PFS Header could not be found!') - return # Critical error, abort - - # Show PFS Header Structure info - if is_verbose : pfs_hdr.pfs_print(pfs_padd + 8) - - # Validate that a known PFS Header Version was encountered - chk_hdr_ver(pfs_hdr.HeaderVersion, 'PFS', pfs_padd + 8) - - # Get PFS Payload Data - pfs_payload = buffer[dpfs_hdr_size:dpfs_hdr_size + pfs_hdr.PayloadSize] - - # Parse all PFS Payload Entries/Components - entry_index = 1 # Index number of each PFS Entry - entry_start = 0 # Increasing PFS Entry starting offset - entries_all = [] # Storage for each PFS Entry details - filename_info = [] # Buffer for FileName Information Entry Data - signature_info = [] # Buffer for Signature Information Entry Data - pfs_entry_struct, pfs_entry_size = get_pfs_entry(pfs_payload, entry_start) # Get PFS Entry Info - while len(pfs_payload[entry_start:entry_start + pfs_entry_size]) == pfs_entry_size : - # Analyze PFS Entry Structure and get relevant info - pfs_entry,entry_version,entry_guid,entry_data,entry_data_sig,entry_met,entry_met_sig,next_entry = \ - parse_pfs_entry(pfs_payload, entry_start, pfs_entry_size, pfs_entry_struct, None, 'PFS Entry', pfs_padd) - - entry_type = 'OTHER' # Adjusted later if PFS Entry is Zlib, PFAT, PFS Info, Model Info - - # Get PFS Information from the PFS Entry with GUID E0717CE3A9BB25824B9F0DC8FD041960 or B033CB16EC9B45A14055F80E4D583FD3 - if entry_guid in ['E0717CE3A9BB25824B9F0DC8FD041960','B033CB16EC9B45A14055F80E4D583FD3'] : - filename_info = entry_data - entry_type = 'NAME_INFO' - - # Get Model Information from the PFS Entry with GUID 6F1D619A22A6CB924FD4DA68233AE3FB - elif entry_guid == '6F1D619A22A6CB924FD4DA68233AE3FB' : - entry_type = 'MODEL_INFO' - - # Get Signature Information from the PFS Entry with GUID D086AFEE3ADBAEA94D5CED583C880BB7 - elif entry_guid == 'D086AFEE3ADBAEA94D5CED583C880BB7' : - signature_info = entry_data - entry_type = 'SIG_INFO' - - # Get Nested PFS from the PFS Entry with GUID 900FAE60437F3AB14055F456AC9FDA84 - elif entry_guid == '900FAE60437F3AB14055F456AC9FDA84' : - entry_type = 'NESTED_PFS' # Nested PFS are usually zlib-compressed so it might change to 'ZLIB' later - - # Store all relevant PFS Entry details - entries_all.append([entry_index, entry_guid, entry_version, entry_type, entry_data, entry_data_sig, entry_met, entry_met_sig]) - - entry_index += 1 # Increase PFS Entry Index number for user-friendly output and name duplicates - entry_start = next_entry # Next PFS Entry starts after PFS Entry Metadata Signature - - # Parse all PFS Information Entries/Descriptors - info_start = 0 # Increasing PFS Information Entry starting offset - info_all = [] # Storage for each PFS Information Entry details - while len(filename_info[info_start:info_start + info_hdr_size]) == info_hdr_size : - # Get PFS Information Header Structure info - entry_info_hdr = get_struct(filename_info, info_start, PFS_INFO_HDR, None, pfs_padd + 8) - - # Show PFS Information Header Structure info - if is_verbose : entry_info_hdr.pfs_print(pfs_padd + 8) - - # Validate that a known PFS Information Header Version was encountered - if entry_info_hdr.HeaderVersion != 1 : - msg_print(pfs_padd + 8, 'Error: Unknown PFS Information Header Version %d!' % entry_info_hdr.HeaderVersion) - break # Skip PFS Information Entries/Descriptors in case of unknown PFS Information Header Version - - # Get PFS Information Header GUID in Big Endian format to match each Info to the equivalent stored PFS Entry details - entry_guid = '%0.*X' % (0x10 * 2, int.from_bytes(entry_info_hdr.GUID, 'little')) - - # Get PFS FileName Structure values - entry_info_mod = get_struct(filename_info, info_start + info_hdr_size, PFS_NAME_HDR, None, pfs_padd + 8) - - # Show PFS FileName Structure info - if is_verbose : entry_info_mod.pfs_print(pfs_padd + 12) - - # The PFS FileName Structure is not complete by itself. The size of the last field (Entry Name) is determined from - # CharacterCount multiplied by 2 due to usage of UTF-16 2-byte Characters. Any Entry Name leading and/or trailing - # space/null characters are stripped and common Windows reserved/illegal filename characters are replaced - name_start = info_start + info_hdr_size + name_hdr_size # PFS Entry's FileName start offset - name_size = entry_info_mod.CharacterCount * 2 # PFS Entry's FileName buffer total size - name_data = filename_info[name_start:name_start + name_size] # PFS Entry's FileName buffer - entry_name = re.sub(win_char_bad, '_', name_data.decode('utf-16').strip()) # PFS Entry's FileName value - - # Show PFS FileName Name info (padding matches the one from PFS FileName Structure info) - if is_verbose : print('%sPayload Name%s: %s' % (' ' * (pfs_padd + 12), ' ' * 4, entry_name)) - - # Get PFS FileName Version string via "Version" and "VersionType" fields - # PFS FileName Version string must be preferred over PFS Entry's Version - entry_version = get_entry_ver(entry_info_mod.Version, entry_info_mod.VersionType, pfs_padd + 12) - - # Store all relevant PFS FileName details - info_all.append([entry_guid, entry_name, entry_version]) - - # The next PFS Information Header starts after the calculated FileName size - # Two space/null characters seem to always exist after each FileName value - info_start += (info_hdr_size + name_hdr_size + name_size + 0x2) - - # Parse Nested PFS Metadata when its PFS Information Entry is missing - for index in range(len(entries_all)) : - if entries_all[index][3] == 'NESTED_PFS' and not filename_info : - entry_guid = entries_all[index][1] # Nested PFS Entry GUID in Big Endian format - entry_metadata = entries_all[index][6] # Use Metadata as PFS Information Entry - - # When PFS Information Entry exists, Nested PFS Metadata contains only Model IDs - # When it's missing, the Metadata structure is large and contains equivalent info - if len(entry_metadata) >= meta_hdr_size : - # Get Nested PFS Metadata Structure values - entry_info = get_struct(entry_metadata, 0, PFS_META_HDR, None, pfs_padd + 4) - - # Show Nested PFS Metadata Structure info - if is_verbose : entry_info.pfs_print(pfs_padd + 8) - - # As Nested PFS Entry Name, we'll use the actual PFS File Name - # Replace common Windows reserved/illegal filename characters - entry_name = re.sub(win_char_bad, '_', entry_info.FileName.decode('utf-8').strip('.exe')) - - # As Nested PFS Entry Version, we'll use the actual PFS File Version - entry_version = entry_info.FileVersion.decode('utf-8') - - # Store all relevant Nested PFS Metadata/Information details - info_all.append([entry_guid, entry_name, entry_version]) - - # Re-set Nested PFS Entry Version from Metadata - entries_all[index][2] = entry_version - - # Parse all PFS Signature Entries/Descriptors - sign_start = 0 # Increasing PFS Signature Entry starting offset - while len(signature_info[sign_start:sign_start + info_hdr_size]) == info_hdr_size : - # Get PFS Information Header Structure info - entry_info_hdr = get_struct(signature_info, sign_start, PFS_INFO_HDR, None, pfs_padd + 8) - - # Show PFS Information Header Structure info - if is_verbose : entry_info_hdr.pfs_print(pfs_padd + 8) - - # Validate that a known PFS Information Header Version was encountered - if entry_info_hdr.HeaderVersion != 1 : - msg_print(pfs_padd + 8, 'Error: Unknown PFS Information Header Version %d!' % entry_info_hdr.HeaderVersion) - break # Skip PFS Signature Entries/Descriptors in case of unknown Header Version - - # PFS Signature Entries/Descriptors have PFS_INFO_HDR + PFS_ENTRY_R* + Sign Size [0x2] + Sign Data [Sig Size] - pfs_entry_struct, pfs_entry_size = get_pfs_entry(signature_info, sign_start + info_hdr_size) # Get PFS Entry Info - - # Get PFS Entry Header Structure info - entry_hdr = get_struct(signature_info, sign_start + info_hdr_size, pfs_entry_struct, None, pfs_padd + 8) - - # Show PFS Information Header Structure info - if is_verbose : entry_hdr.pfs_print(pfs_padd + 12) - - # Show PFS Signature Size & Data (after PFS_ENTRY_R*) - sign_info_start = sign_start + info_hdr_size + pfs_entry_size - sign_size = int.from_bytes(signature_info[sign_info_start:sign_info_start + 0x2], 'little') - sign_data_raw = signature_info[sign_info_start + 0x2:sign_info_start + 0x2 + sign_size] - sign_data_txt = '%0.*X' % (sign_size * 2, int.from_bytes(sign_data_raw, 'little')) - if is_verbose : - print('\n%sSignature Information:\n' % (' ' * (pfs_padd + 8))) - print('%sSignature Size : 0x%X' % (' ' * (pfs_padd + 12), sign_size)) - print('%sSignature Data : %s [...]' % (' ' * (pfs_padd + 12), sign_data_txt[:32])) - - # The next PFS Signature Entry/Descriptor starts after the previous Signature Data - sign_start += (info_hdr_size + pfs_entry_size + 0x2 + sign_size) - - # Parse each PFS Entry Data for special types (zlib or PFAT) - for index in range(len(entries_all)) : - entry_data = entries_all[index][4] # Get PFS Entry Data - entry_type = entries_all[index][3] # Get PFS Entry Type - - # Very small PFS Entry Data cannot be of special type - if len(entry_data) < dpfs_hdr_size : continue - - # Check if PFS Entry contains zlib-compressed sub-PFS Volume - pfs_zlib_offsets = get_section_offsets(entry_data) - - # Check if PFS Entry contains sub-PFS Volume with PFAT Payload - is_pfat = False # Initial PFAT state for sub-PFS Entry - _, pfat_entry_size = get_pfs_entry(entry_data, dpfs_hdr_size) # Get possible PFS PFAT Entry Size - pfat_hdr_off = dpfs_hdr_size + pfat_entry_size # Possible PFAT Header starts after PFS Header & Entry - pfat_entry_hdr = get_struct(entry_data, 0, PFS_DELL_HDR, None, pfs_padd + 8) # Possible PFS PFAT Entry - if len(entry_data) - pfat_hdr_off >= pfat_hdr_size : - pfat_hdr = get_struct(entry_data, pfat_hdr_off, PFS_PFAT_HDR, [0], pfs_padd + 8) - is_pfat = bytes(pfat_hdr.PlatformID).upper().startswith(b'DELL') - - # Parse PFS Entry which contains sub-PFS Volume with PFAT Payload - if pfat_entry_hdr.Tag == b'PFS.HDR.' and is_pfat : - entry_type = 'PFAT' # Re-set PFS Entry Type from OTHER to PFAT, to use such info afterwards - - entry_data = parse_pfat_pfs(pfat_entry_hdr, entry_data, pfs_padd) # Parse sub-PFS PFAT Volume - - # Parse PFS Entry which contains zlib-compressed sub-PFS Volume - elif pfs_zlib_offsets : - entry_type = 'ZLIB' # Re-set PFS Entry Type from OTHER to ZLIB, to use such info afterwards - pfs_count += 1 # Increase the count/index of parsed main PFS structures by one - - # Parse each sub-PFS ZLIB Section - for offset in pfs_zlib_offsets : - # Get the Name of the zlib-compressed full PFS structure via the already stored PFS Information - # The zlib-compressed full PFS structure(s) are used to contain multiple FW (CombineBiosNameX) - # When zlib-compressed full PFS structure(s) exist within the main/first full PFS structure, - # its PFS Information should contain their names (CombineBiosNameX). Since the main/first - # full PFS structure has count/index 1, the rest start at 2+ and thus, their PFS Information - # names can be retrieved in order by subtracting 2 from the main/first PFS Information values - sub_pfs_name = ' %s v%s' % (info_all[pfs_count - 2][1], info_all[pfs_count - 2][2]) if info_all else ' UNKNOWN' - - # Set the sub-PFS output path (create sub-folders for each sub-PFS and its ZLIB sections) - sub_pfs_path = os.path.join(output_path, str(pfs_count) + sub_pfs_name) - - # Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count) - pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4) - - entries_all[index][4] = entry_data # Adjust PFS Entry Data after parsing PFAT (same ZLIB raw data, not stored afterwards) - entries_all[index][3] = entry_type # Adjust PFS Entry Type from OTHER to PFAT or ZLIB (ZLIB is ignored at file extraction) - - # Name & Store each PFS Entry/Component Data, Data Signature, Metadata, Metadata Signature - for entry_index in range(len(entries_all)) : - file_index = entries_all[entry_index][0] - file_guid = entries_all[entry_index][1] - file_version = entries_all[entry_index][2] - file_type = entries_all[entry_index][3] - file_data = entries_all[entry_index][4] - file_data_sig = entries_all[entry_index][5] - file_meta = entries_all[entry_index][6] - file_meta_sig = entries_all[entry_index][7] - - # Give Names to special PFS Entries, not covered by PFS Information - if file_type == 'MODEL_INFO' : - file_name = 'Model Information' - elif file_type == 'NAME_INFO' : - file_name = 'Filename Information' - if not is_advanced : continue # Don't store Filename Information in non-advanced user mode - elif file_type == 'SIG_INFO' : - file_name = 'Signature Information' - if not is_advanced : continue # Don't store Signature Information in non-advanced user mode - else : - file_name = '' - - # Most PFS Entry Names & Versions are found at PFS Information via their GUID - # Version can be found at PFS_ENTRY_R* but prefer PFS Information when possible - for info_index in range(len(info_all)) : - info_guid = info_all[info_index][0] - info_name = info_all[info_index][1] - info_version = info_all[info_index][2] - - # Give proper Name & Version info if Entry/Information GUIDs match - if info_guid == file_guid : - file_name = info_name - file_version = info_version - - info_all[info_index][0] = 'USED' # PFS with zlib-compressed sub-PFS use the same GUID - break # Break at 1st Name match to not rename again from next zlib-compressed sub-PFS with the same GUID - - # For both advanced & non-advanced users, the goal is to store final/usable files only - # so empty or intermediate files such as sub-PFS, PFS w/ PFAT or zlib-PFS are skipped - # Main/First PFS CombineBiosNameX Metadata files must be kept for accurate Model Information - # All users should check these files in order to choose the correct CombineBiosNameX modules - write_files = [] # Initialize list of output PFS Entry files to be written/extracted - - is_zlib = bool(file_type == 'ZLIB') # Determine if PFS Entry Data was zlib-compressed - - if file_data and not is_zlib : write_files.append([file_data, 'data']) # PFS Entry Data Payload - if file_data_sig and is_advanced : write_files.append([file_data_sig, 'sign_data']) # PFS Entry Data Signature - if file_meta and (is_zlib or is_advanced) : write_files.append([file_meta, 'meta']) # PFS Entry Metadata Payload - if file_meta_sig and is_advanced : write_files.append([file_meta_sig, 'sign_meta']) # PFS Entry Metadata Signature - - # Write/Extract PFS Entry files - for file in write_files : - pfs_file_write(file[0], file[1], file_type, output_path, pfs_padd, pfs_index, pfs_name, file_index, file_name, file_version, output_path) - - # Get PFS Footer Data after PFS Header Payload - pfs_footer = buffer[dpfs_hdr_size + pfs_hdr.PayloadSize:dpfs_hdr_size + pfs_hdr.PayloadSize + dpfs_ftr_size] - - # Analyze PFS Footer Structure - chk_pfs_ftr(pfs_footer, pfs_payload, pfs_hdr.PayloadSize, 'PFS', pfs_padd) - -# Analyze Dell PFS Entry Structure -def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, struct_args, text, padd) : - # Get PFS Entry Structure values - pfs_entry = get_struct(entry_buffer, entry_start, entry_struct, struct_args, padd + 4) - - # Show PFS Entry Structure info - if is_verbose : pfs_entry.pfs_print(padd + 8) - - # Validate that a known PFS Entry Header Version was encountered - chk_hdr_ver(pfs_entry.HeaderVersion, text, padd + 8) - - # Validate that the PFS Entry Reserved field is empty - if pfs_entry.Reserved != 0 : - msg_print(padd + 8, 'Error: Detected non-empty %s Reserved field!' % text) - - # Get PFS Entry Version string via "Version" and "VersionType" fields - entry_version = get_entry_ver(pfs_entry.Version, pfs_entry.VersionType, padd + 8) - - # Get PFS Entry GUID in Big Endian format - entry_guid = '%0.*X' % (0x10 * 2, int.from_bytes(pfs_entry.GUID, 'little')) - - # PFS Entry Data starts after the PFS Entry Structure - entry_data_start = entry_start + entry_size - entry_data_end = entry_data_start + pfs_entry.DataSize - - # PFS Entry Data Signature starts after PFS Entry Data - entry_data_sig_start = entry_data_end - entry_data_sig_end = entry_data_sig_start + pfs_entry.DataSigSize - - # PFS Entry Metadata starts after PFS Entry Data Signature - entry_met_start = entry_data_sig_end - entry_met_end = entry_met_start + pfs_entry.DataMetSize - - # PFS Entry Metadata Signature starts after PFS Entry Metadata - entry_met_sig_start = entry_met_end - entry_met_sig_end = entry_met_sig_start + pfs_entry.DataMetSigSize - - entry_data = entry_buffer[entry_data_start:entry_data_end] # Store PFS Entry Data - entry_data_sig = entry_buffer[entry_data_sig_start:entry_data_sig_end] # Store PFS Entry Data Signature - entry_met = entry_buffer[entry_met_start:entry_met_end] # Store PFS Entry Metadata - entry_met_sig = entry_buffer[entry_met_sig_start:entry_met_sig_end] # Store PFS Entry Metadata Signature - - return pfs_entry, entry_version, entry_guid, entry_data, entry_data_sig, entry_met, entry_met_sig, entry_met_sig_end - -# Parse Dell PFS Volume with PFAT Payload -def parse_pfat_pfs(entry_hdr, entry_data, padd) : - if is_verbose : print('\n%sPFS Volume:' % (' ' * (padd + 4))) - - # Show sub-PFS Header Structure Info - if is_verbose : entry_hdr.pfs_print(padd + 12) - - # Validate that a known sub-PFS Header Version was encountered - chk_hdr_ver(entry_hdr.HeaderVersion, 'sub-PFS', padd + 12) - - # Get sub-PFS Payload Data - pfat_payload = entry_data[dpfs_hdr_size:dpfs_hdr_size + entry_hdr.PayloadSize] - - # Get sub-PFS Footer Data after sub-PFS Header Payload (must be retrieved at the initial entry_data, before PFAT parsing) - pfat_footer = entry_data[dpfs_hdr_size + entry_hdr.PayloadSize:dpfs_hdr_size + entry_hdr.PayloadSize + dpfs_ftr_size] - - # Parse all sub-PFS Payload PFAT Entries - pfat_data_all = [] # Storage for all sub-PFS PFAT Entries Order/Offset & Payload/Raw Data - pfat_entry_start = 0 # Increasing sub-PFS PFAT Entry start offset - pfat_entry_index = 0 # Increasing sub-PFS PFAT Entry count index - _, pfs_entry_size = get_pfs_entry(pfat_payload, 0) # Get initial PFS PFAT Entry Size for loop - while len(pfat_payload[pfat_entry_start:pfat_entry_start + pfs_entry_size]) == pfs_entry_size : - # Get sub-PFS PFAT Entry Structure & Size info - pfat_entry_struct, pfat_entry_size = get_pfs_entry(pfat_payload, pfat_entry_start) - - # Analyze sub-PFS PFAT Entry Structure and get relevant info - pfat_entry,pfat_entry_version,pfat_entry_guid,pfat_entry_data,pfat_entry_data_sig,pfat_entry_met,pfat_entry_met_sig,pfat_next_entry = \ - parse_pfs_entry(pfat_payload, pfat_entry_start, pfat_entry_size, pfat_entry_struct, None, 'sub-PFS PFAT Entry', padd + 4) - - # Each sub-PFS PFAT Entry includes an AMI BIOS Guard (a.k.a. PFAT) block at the beginning - # We need to parse the PFAT block and remove its contents from the final Payload/Raw Data - pfat_hdr_off = pfat_entry_start + pfat_entry_size # PFAT block starts after PFS Entry - - # Get sub-PFS PFAT Header Structure values - pfat_hdr = get_struct(pfat_payload, pfat_hdr_off, PFS_PFAT_HDR, [pfat_entry_index], padd + 12) - - # Show sub-PFS PFAT Header Structure info - if is_verbose : pfat_hdr.pfs_print(padd + 16) - - # Get PFAT Header Flags (SFAM, ProtectEC, GFXMitDis, FTU, Reserved) - pfat_flag_sig,_,_,_,_ = pfat_hdr.get_flags() - - pfat_script_start = pfat_hdr_off + pfat_hdr_size # PFAT Block Script Start - pfat_script_end = pfat_script_start + pfat_hdr.ScriptSize # PFAT Block Script End - pfat_script_data = pfat_payload[pfat_script_start:pfat_script_end] # PFAT Block Script Data - pfat_payload_start = pfat_script_end # PFAT Block Payload Start (at Script end) - pfat_payload_end = pfat_script_end + pfat_hdr.DataSize # PFAT Block Data End - pfat_payload_data = pfat_payload[pfat_payload_start:pfat_payload_end] # PFAT Block Raw Data - pfat_hdr_bgs_size = pfat_hdr_size + pfat_hdr.ScriptSize # PFAT Block Header & Script Size - - # The PFAT Script End should match the total Entry Data Size w/o PFAT block - if pfat_hdr_bgs_size != pfat_entry.DataSize - pfat_hdr.DataSize : - msg_print(padd + 16, 'Error: Detected sub-PFS PFAT Entry Header & PFAT Size mismatch!') - - # Parse sub-PFS PFAT Signature, if applicable (only when PFAT Header > SFAM flag is set) - if pfat_flag_sig and len(pfat_payload[pfat_payload_end:pfat_payload_end + pfat_sig_size]) == pfat_sig_size : - # Get sub-PFS PFAT Signature Structure values - pfat_sig = get_struct(pfat_payload, pfat_payload_end, PFS_PFAT_SIG, [pfat_entry_index], padd + 12) - - # Show sub-PFS PFAT Signature Structure info - if is_verbose : pfat_sig.pfs_print(padd + 16) - - # Show PFAT Script via BIOS Guard Script Tool - # https://github.com/allowitsme/big-tool by Dmitry Frolov - if is_verbose : - print('\n%sPFAT Block %d Script:\n' % (' ' * (padd + 12), pfat_entry_index)) - is_opcode_div = len(pfat_script_data) % 8 == 0 - is_begin_end = pfat_script_data[:8] + pfat_script_data[-8:] == b'\x01' + b'\x00' * 7 + b'\xFF' + b'\x00' * 7 - if is_opcode_div and is_begin_end and is_bgst : - pfat_script_decomp = BigScript(code_bytes=pfat_script_data) - pfat_script_lines = pfat_script_decomp.to_string().replace('\t',' ').split('\n') - for line in pfat_script_lines : - spacing = ' ' * (padd + 16) if line.endswith(('begin','end',':')) else ' ' * (padd + 24) - operands = [op for op in line.split(' ') if op != ''] - print(spacing + ('{:<12s}' + '{:<11s}' * (len(operands) - 1)).format(*operands)) - elif not is_opcode_div : - print('%sError: Script not divisible by OpCode length!' % (' ' * (padd + 16))) - elif not is_begin_end : - print('%sError: Script lacks Begin and/or End OpCodes!' % (' ' * (padd + 16))) - elif not is_bgst : - print('%sError: BIOS Guard Script Tool dependency missing!' % (' ' * (padd + 16))) - - # The payload of sub-PFS PFAT Entries is not in proper order by default - # We can get each payload's order from PFAT Script > OpCode #2 (set I0 imm) - # PFAT Script OpCode #2 > Operand #3 stores the payload Offset in final image - pfat_entry_off = int.from_bytes(pfat_script_data[0xC:0x10], 'little') - - # Parse sub-PFS PFAT Entry/Block Metadata - if len(pfat_entry_met) >= pfat_met_size : - # Get sub-PFS PFAT Metadata Structure values - pfat_met = get_struct(pfat_entry_met, 0, PFS_PFAT_MET, [pfat_entry_index], padd + 12) - - # Show sub-PFS PFAT Metadata Structure info - if is_verbose : pfat_met.pfs_print(padd + 16) - - # Another way to get each PFAT Entry payload's Order is from its Metadata at 0x8-0xC, if applicable - # Check that the PFAT Entry payload Order/Offset from PFAT Script matches the one from PFAT Metadata - if pfat_entry_off != pfat_met.OffsetBase : - msg_print(padd + 16, 'Error: Detected sub-PFS PFAT Entry Metadata & PFAT Base Offset mismatch!') - pfat_entry_off = pfat_met.OffsetBase # Prefer Offset from Metadata, in case PFAT Script differs - - # Check that the PFAT Entry payload Size from PFAT Header matches the one from PFAT Metadata - if pfat_hdr.DataSize != pfat_met.BlockSize : - msg_print(padd + 16, 'Error: Detected sub-PFS PFAT Entry Metadata & PFAT Block Size mismatch!') - - # Get sub-PFS Entry Raw Data by subtracting PFAT Header & Script from PFAT Entry Data - pfat_entry_data_raw = pfat_entry_data[pfat_hdr_bgs_size:] - - # The sub-PFS Entry Raw Data (w/o PFAT Header & Script) should match with the PFAT Block payload - if pfat_entry_data_raw != pfat_payload_data : - msg_print(padd + 16, 'Error: Detected sub-PFS PFAT Entry w/o PFAT & PFAT Block Data mismatch!') - pfat_entry_data_raw = pfat_payload_data # Prefer Data from PFAT Block, in case PFAT Entry differs - - # Store each sub-PFS PFAT Entry Order/Offset and Payload/Raw Data (w/o PFAT) - pfat_data_all.append((pfat_entry_off, pfat_entry_data_raw)) - - pfat_entry_start = pfat_next_entry # Next sub-PFS PFAT Entry starts after sub-PFS Entry Metadata Signature - - pfat_entry_index += 1 - - pfat_data_all.sort() # Sort all sub-PFS PFAT Entries payloads/data based on their Order/Offset - - entry_data = b'' # Initialize new sub-PFS Entry Data - for pfat_data in pfat_data_all : entry_data += pfat_data[1] # Merge all sub-PFS PFAT Entry Payload/Raw into the final sub-PFS Entry Data - - # Verify that the Order/Offset of the last PFAT Entry w/ its Size matches the final sub-PFS Entry Data Size - if len(entry_data) != pfat_data_all[-1][0] + len(pfat_data_all[-1][1]) : - msg_print(padd + 8, 'Error: Detected sub-PFS PFAT Entry Buffer & Last Offset Size mismatch!') - - # Analyze sub-PFS Footer Structure - chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padd + 4) - - return entry_data - -# Get Dell PFS Entry Structure & Size via its Version -def get_pfs_entry(buffer, offset) : - pfs_entry_ver = int.from_bytes(buffer[offset + 0x10:offset + 0x14], 'little') # PFS Entry Version - - if pfs_entry_ver == 1 : return PFS_ENTRY_R1, ctypes.sizeof(PFS_ENTRY_R1) - if pfs_entry_ver == 2 : return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2) - - return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2) - -# Determine Dell PFS Entry Version string -def get_entry_ver(version_fields, version_types, msg_padd) : - version = '' # Initialize Version string - - # Each Version Type (1 byte) determines the type of each Version Value (2 bytes) - # Version Type 'N' is Number, 'A' is Text and ' ' is Empty/Unused - for idx in range(len(version_fields)) : - eol = '' if idx == len(version_fields) - 1 else '.' - - if version_types[idx] == 65 : version += '%X%s' % (version_fields[idx], eol) # 0x41 = ASCII - elif version_types[idx] == 78 : version += '%d%s' % (version_fields[idx], eol) # 0x4E = Number - elif version_types[idx] in (0, 32) : version = version.strip('.') # 0x00 or 0x20 = Unused - else : - version += '%X%s' % (version_fields[idx], eol) # Unknown - msg_print(msg_padd, 'Error: Unknown PFS Entry Version Type 0x%0.2X!' % version_types[idx]) - - return version - -# Check if Dell PFS Header Version is known -def chk_hdr_ver(version, text, padd) : - if version in (1,2) : return - - msg_print(padd, 'Error: Unknown %s Header Version %d!' % (text, version)) - -# Analyze Dell PFS Footer Structure -def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padd) : - # Get PFS Footer Structure values - pfs_ftr = get_struct(footer_buffer, 0, PFS_DELL_FTR, None, padd + 8) - - # Validate that a PFS Footer was parsed - if pfs_ftr.Tag == b'PFS.FTR.' : - # Show PFS Footer Structure info - if is_verbose : pfs_ftr.pfs_print(padd + 8) - else : - msg_print(padd + 4, 'Error: %s Footer could not be found!' % text) - - # Validate that PFS Header Payload Size matches the one at PFS Footer - if data_size != pfs_ftr.PayloadSize : - msg_print(padd + 4, 'Error: %s Header & Footer Payload Size mismatch!' % text) - - # Calculate the PFS Payload Data CRC-32 w/ Vector 0 - pfs_ftr_crc = ~zlib.crc32(data_buffer, 0) & 0xFFFFFFFF - - # Validate PFS Payload Data Checksum via PFS Footer - if pfs_ftr.Checksum != pfs_ftr_crc : - msg_print(padd + 4, 'Error: Invalid %s Footer Payload Checksum!' % text) - -# Write/Extract Dell PFS Entry Files (Data, Metadata, Signature) -def pfs_file_write(bin_buff, bin_name, bin_type, out_path, padd, pfs_idx, pfs_name, file_idx, file_name, file_ver, output_path) : - full_name = '%d%s -- %d %s v%s' % (pfs_idx, pfs_name, file_idx, file_name, file_ver) # Full PFS Entry Name - safe_name = re.sub(win_char_bad, '_', full_name) # Replace common Windows reserved/illegal filename characters - - # Store Data/Metadata Signature (advanced users only) - if bin_name.startswith('sign') : - final_name = '%s.%s.sig' % (safe_name, bin_name.split('_')[1]) - final_path = os.path.join(output_path, final_name) - - with open(final_path, 'wb') as pfs_out : pfs_out.write(bin_buff) # Write final Data/Metadata Signature - - return # Skip further processing for Signatures - - # Store Data/Metadata Payload - bin_ext = '.%s.bin' % bin_name if is_advanced else '.bin' # Simpler Data/Metadata Extension for non-advanced users - - # Some Data may be Text or XML files with useful information for non-advanced users - is_text,final_data,file_ext,write_mode = bin_is_text(bin_buff, bin_type, bin_name == 'meta', is_advanced, is_verbose, padd) - - final_name = '%s%s' % (safe_name, bin_ext[:-4] + file_ext if is_text else bin_ext) - final_path = os.path.join(out_path, final_name) - - with open(final_path, write_mode) as pfs_out : pfs_out.write(final_data) # Write final Data/Metadata Payload - -# Check if Dell PFS Entry file/data is Text/XML and Convert -def bin_is_text(buffer, file_type, is_metadata, is_advanced, is_verbose, pfs_padd) : - is_text = False - write_mode = 'wb' - extension = '.bin' - buffer_in = buffer - - if b',END' in buffer[-0x8:] : # Text Type 1 - is_text = True - write_mode = 'w' - extension = '.txt' - buffer = buffer.decode('utf-8').split(',END')[0].replace(';','\n') - elif buffer.startswith(b'VendorName=Dell') : # Text Type 2 - is_text = True - write_mode = 'w' - extension = '.txt' - buffer = buffer.split(b'\x00')[0].decode('utf-8').replace(';','\n') - elif b'|]' - -# Initialize Dell PFS input file list -pfs_input_images = [] - -# Process input files -if len(sys.argv) >= 2 : - # Drag & Drop or CLI - if args.input_dir : - input_path_user = get_absolute_path(args.input_dir) - pfs_input_images = get_path_files(input_path_user) - else : - pfs_input_images = [image.name for image in args.images] -else : - # Script w/o parameters - input_path_user = get_absolute_path(input('\nEnter input directory path: ')) - pfs_input_images = get_path_files(input_path_user) - -# Initialize global variables -exit_code = len(pfs_input_images) # Initialize exit code with input file count -is_advanced = bool(args.advanced) # Set Advanced user mode optional argument -is_verbose = bool(args.verbose) # Set Verbose output mode optional argument - -# Initialize Dell PFS Update Extractor -if __name__ == '__main__': - sys.exit(main(exit_code, pfs_input_images)) \ No newline at end of file diff --git a/Fujitsu SFX BIOS Extractor/Fujitsu_SFX_Extract.py b/Fujitsu SFX BIOS Extractor/Fujitsu_SFX_Extract.py deleted file mode 100644 index 4cc657f..0000000 --- a/Fujitsu SFX BIOS Extractor/Fujitsu_SFX_Extract.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 - -""" -Fujitsu SFX Extractor -Fujitsu SFX BIOS Extractor -Copyright (C) 2019-2021 Plato Mavropoulos -""" - -print('Fujitsu SFX BIOS Extractor v2.1') - -import os -import re -import sys -import subprocess - -if len(sys.argv) >= 2 : - # Drag & Drop or CLI - fjsfx_exec = sys.argv[1:] -else : - # Folder path - fjsfx_exec = [] - in_path = input('\nEnter the full folder path: ') - print('\nWorking...') - for root, dirs, files in os.walk(in_path): - for name in files : - fjsfx_exec.append(os.path.join(root, name)) - -# "FjSfxBinay" + Microsoft CAB Header XOR 0xFF (Tag[4] + Res[4] + Size[4] + Res[4] + Offset[4] + Res[4] + Ver[2]) pattern -mscf_pattern = re.compile(br'\x46\x6A\x53\x66\x78\x42\x69\x6E\x61\x79\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL) - -for input_file in fjsfx_exec : - file_path = os.path.abspath(input_file) - file_dir = os.path.dirname(file_path) - file_name = os.path.basename(file_path) - - print('\nFile: ' + file_name) - - # Open Fujitsu SFX Binary Packager executable as mutable bytearray - with open(input_file, 'rb') as in_file : FjSfx = bytearray(in_file.read()) - - match_mscf = mscf_pattern.search(FjSfx) # Search for Fujitsu Microsoft CAB Header XOR 0xFF pattern - - # Check if Microsoft CAB Header XOR 0xFF pattern exists - if match_mscf : - print('\n Detected Obfuscation!') - - mscf_start = match_mscf.start() + 0xA # Microsoft CAB Header XOR 0xFF starts after "FjSfxBinay" signature - - # Determine the Microsoft CAB image Size - cab_size = int.from_bytes(FjSfx[mscf_start + 0x8:mscf_start + 0xC], 'little') # Get LE XOR-ed CAB Size - xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB Size XOR value - cab_size = cab_size ^ xor_size # Perform XOR 0xFF and get actual CAB Size - - print('\n Removing Obfuscation...') - - # Determine the Microsoft CAB image Data - cab_data = int.from_bytes(FjSfx[mscf_start:mscf_start + cab_size], 'big') # Get BE XOR-ed CAB Data - xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB Data XOR value - cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB Data - - print('\n Extracting...') - - with open('fjsfx_temp.cab', 'wb') as cab_file : cab_file.write(cab_data) # Create temporary CAB image - - extr_path = os.path.join(file_dir, file_name[:-4], '') # Create CAB image extraction path - - try : - decomp = subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + extr_path, 'fjsfx_temp.cab']) # 7-Zip - - print('\n Extracted!') - except : - print('\n Error: Could not decompress Microsoft CAB image!') - print(' Make sure that "7z" executable exists!') - - os.remove('fjsfx_temp.cab') # Remove temporary CAB image - - else : - print('\n Error: This is not a Fujitsu SFX BIOS image!') - continue # Next input file - -else : - input('\nDone!') \ No newline at end of file diff --git a/Fujitsu UPC BIOS Extractor/Fujitsu_UPC_Extract.py b/Fujitsu UPC BIOS Extractor/Fujitsu_UPC_Extract.py deleted file mode 100644 index 6bb9993..0000000 --- a/Fujitsu UPC BIOS Extractor/Fujitsu_UPC_Extract.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python3 -#coding=utf-8 - -""" -Fujitsu UPC Extract -Fujitsu UPC BIOS Extractor -Copyright (C) 2021 Plato Mavropoulos -""" - -title = 'Fujitsu UPC BIOS Extractor v1.0' - -print('\n' + title) # Print script title - -import sys - -# Detect Python version -sys_ver = sys.version_info -if sys_ver < (3,7) : - sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1])) - (raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 - sys.exit(1) - -import os -import ctypes -import argparse -import traceback -import subprocess - -# Pause after any unexpected Python exception -# https://stackoverflow.com/a/781074 by Torsten Marek -def show_exception_and_exit(exc_type, exc_value, tb) : - if exc_type is KeyboardInterrupt : - print('\n') - else : - print('\nError: %s crashed, please report the following:\n' % title) - traceback.print_exception(exc_type, exc_value, tb) - input('\nPress enter to exit') - - sys.exit(1) - -# Set pause-able Python exception handler -sys.excepthook = show_exception_and_exit - -# Set console/shell window title -user_os = sys.platform -if user_os == 'win32' : ctypes.windll.kernel32.SetConsoleTitleW(title) -elif user_os.startswith('linux') or user_os == 'darwin' or user_os.find('bsd') != -1 : sys.stdout.write('\x1b]2;' + title + '\x07') - -# Set argparse Arguments -upc_parser = argparse.ArgumentParser() -upc_parser.add_argument('upc', type=argparse.FileType('r'), nargs='*') -upc_parser.add_argument('-p', '--path', help='parse files within given folder', type=str) -upc_params = upc_parser.parse_args() - -# Get all files within path -def get_files(path) : - inputs = [] - - for root, _, files in os.walk(path): - for name in files : - inputs.append(os.path.join(root, name)) - - return inputs - -if len(sys.argv) >= 2 : - if bool(upc_params.path) : - upc_exec = get_files(upc_params.path) # CLI with --path - else : - upc_exec = [] - for executable in upc_params.upc : - upc_exec.append(executable.name) # Drag & Drop -else : - in_path = input('\nEnter the full folder path: ') - upc_exec = get_files(in_path) # Direct Run - -# Process each input Fujitsu UPC BIOS image -for input_file in upc_exec : - input_name,input_extension = os.path.splitext(os.path.basename(input_file)) - - print('\n*** %s%s' % (input_name, input_extension)) - - # Check if input file exists - if not os.path.isfile(input_file) : - print('\n Error: This input file does not exist!') - continue # Next input file - - with open(input_file, 'rb') as in_file : upc_data = in_file.read() - - if input_extension.upper() != '.UPC' or int.from_bytes(upc_data[0x0:0x4], 'little') + 0x8 != len(upc_data) : - print('\n Error: This is not a Fujitsu UPC BIOS image!') - continue # Next input file - - output_file = input_file[:-4] + '.bin' # Decompressed filename - - # EFI/Tiano Decompression - try : - subprocess.run(['TianoCompress', '-d', input_file, '-o', output_file, '--uefi', '-q'], check = True, stdout = subprocess.DEVNULL) - - if os.path.getsize(output_file) != int.from_bytes(upc_data[0x4:0x8], 'little') : raise Exception('EFI_DECOMP_ERROR') - except : - print('\n Error: Could not extract input file via TianoCompress!') - input(' Make sure that "TianoCompress" executable exists!') - - print('\n Extracted Fujitsu UPC BIOS image!') - -input('\nDone!') - -sys.exit(0) \ No newline at end of file diff --git a/Insyde iFlash Image Extractor/Insyde_iFlash_Extract.py b/Insyde iFlash Image Extractor/Insyde_iFlash_Extract.py deleted file mode 100644 index d8ca64c..0000000 --- a/Insyde iFlash Image Extractor/Insyde_iFlash_Extract.py +++ /dev/null @@ -1,270 +0,0 @@ -#!/usr/bin/env python3 -#coding=utf-8 - -""" -Insyde iFlash Extract -Insyde iFlash Image Extractor -Copyright (C) 2022 Plato Mavropoulos -""" - -title = 'Insyde iFlash Image Extractor v1.0' - -import sys - -# Detect Python version -sys_py = sys.version_info - -# Check Python version -if sys_py < (3,7): - sys.stdout.write('%s\n\nError: Python >= 3.7 required, not %d.%d!\n' % (title, sys_py[0], sys_py[1])) - - if '--auto-exit' not in sys.argv and '-e' not in sys.argv: - (raw_input if sys_py[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 - - sys.exit(1) - -# Detect OS platform -sys_os = sys.platform - -# Check OS platform -if sys_os == 'win32': - sys.stdout.reconfigure(encoding='utf-8') # Fix Windows Unicode console redirection -elif sys_os.startswith('linux') or sys_os == 'darwin' or sys_os.find('bsd') != -1: - pass # Supported/Tested -else: - print('%s\n\nError: Unsupported platform "%s"!\n' % (title, sys_os)) - - if '--auto-exit' not in sys.argv and '-e' not in sys.argv: input('Press enter to exit') - - sys.exit(1) - -# Python imports -import os -import re -import ctypes -import inspect -import pathlib -import argparse -import traceback - -# Set ctypes Structure types -char = ctypes.c_char -uint32_t = ctypes.c_uint - -class IflashHeader(ctypes.LittleEndianStructure): - _pack_ = 1 - _fields_ = [ - ('Signature', char*9), # 0x00 $_IFLASH_ - ('ImageTag', char*7), # 0x08 - ('TotalSize', uint32_t), # 0x10 from header end - ('ImageSize', uint32_t), # 0x14 from header end - # 0x18 - ] - - def ifl_print(self, padd): - p = ' ' * (padd - 1) - - print(p, 'Signature : %s' % self.Signature.decode('utf-8','ignore')) - print(p, 'Image Name: %s' % self.ImageTag.decode('utf-8','ignore')) - print(p, 'Total Size: 0x%X' % self.TotalSize) - print(p, 'Image Size: 0x%X' % self.ImageSize) - -class InsydeIflash: - def __init__(self, in_data, out_path, in_padd, in_verbose): - self.fw_data = in_data - self.ex_path = out_path - self.padding = in_padd - self.verbose = in_verbose - - self.hdr_len = ctypes.sizeof(IflashHeader) - - self.mod_names = { - 'DRV_IMG':['isflash','efi'], - 'INI_IMG':['platform','ini'], - 'BIOSIMG':['BIOS','bin'], - 'ME_IMG_':['ME','bin'], - 'EC_IMG_':['EC','bin'], - 'OEM_ID_':['OEM_ID','bin'], - 'BIOSCER':['Certificate','bin'], - 'BIOSCR2':['Certificate_2','bin'], - } - - def iflash_parse(self): - all_ins_ifl = pat_ins_ifl.finditer(self.fw_data) - - if not all_ins_ifl: return 1 - - if not os.path.isdir(self.ex_path): os.mkdir(self.ex_path) - - for ins_ifl in all_ins_ifl: - ifl_off = ins_ifl.start() - - ifl_hdr = get_struct(self.fw_data, ifl_off, IflashHeader) - - if self.verbose: - print('\n%sInsyde iFlash Module @ 0x%0.8X\n' % (' ' * self.padding, ifl_off)) - - ifl_hdr.ifl_print(self.padding + 4) - - mod_bgn = ifl_off + self.hdr_len - mod_end = mod_bgn + ifl_hdr.ImageSize - mod_bin = self.fw_data[mod_bgn:mod_end] - - if not mod_bin: continue # Empty/Missing Module - - mod_tag = ifl_hdr.ImageTag.decode('utf-8','ignore') - out_tag = self.mod_names[mod_tag][0] if mod_tag in self.mod_names else mod_tag - out_ext = self.mod_names[mod_tag][1] if mod_tag in self.mod_names else 'bin' - - out_name = get_safe_name('%s [0x%0.8X-0x%0.8X].%s' % (out_tag, mod_bgn, mod_end, out_ext)) - out_path = os.path.join(self.ex_path, out_name) - - with open(out_path, 'wb') as out: out.write(mod_bin) - - print('\n%sExtracted' % (' ' * (self.padding + 8 if self.verbose else self.padding)), out_name) - - return 0 - -# Process ctypes Structure Classes -# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky -def get_struct(buffer, start_offset, class_name, param_list=None): - if param_list is None: param_list = [] - - structure = class_name(*param_list) # Unpack parameter list - struct_len = ctypes.sizeof(structure) - struct_data = buffer[start_offset:start_offset + struct_len] - fit_len = min(len(struct_data), struct_len) - - ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len) - - return structure - -# Get absolute file path (argparse object) -def get_absolute_path(argparse_path): - if not argparse_path: - absolute_path = get_script_dir() # Use input file directory if no user path is specified - else: - # Check if user specified path is absolute, otherwise convert it to input file relative - if pathlib.Path(argparse_path).is_absolute(): absolute_path = argparse_path - else: absolute_path = os.path.join(get_script_dir(), argparse_path) - - return absolute_path - -# Get list of files from absolute path -def get_path_files(abs_path): - file_list = [] # Initialize list of files - - # Traverse input absolute path - for root,_,files in os.walk(abs_path): - file_list = [os.path.join(root, name) for name in files] - - return file_list - -# Fix illegal/reserved Windows characters -def get_safe_name(file_name): - raw_name = repr(file_name).strip("'") - - return re.sub(r'[\\/*?:"<>|]', '_', raw_name) - -# Get python script working directory -# https://stackoverflow.com/a/22881871 by jfs -def get_script_dir(follow_symlinks=True): - if getattr(sys, 'frozen', False): - path = os.path.abspath(sys.executable) - else: - path = inspect.getabsfile(get_script_dir) - if follow_symlinks: - path = os.path.realpath(path) - - return os.path.dirname(path) - -# Pause after any unexpected Python exception -# https://stackoverflow.com/a/781074 by Torsten Marek -def show_exception_and_exit(exc_type, exc_value, tb): - if exc_type is KeyboardInterrupt : - print('\n') - else: - print('\nError: %s crashed, please report the following:\n' % title) - traceback.print_exception(exc_type, exc_value, tb) - if not bool(args.auto_exit): input('\nPress enter to exit') - - sys.exit(1) # Crash exceptions are critical - -# Insyde iFlash Section Signature -pat_ins_ifl = re.compile(br'\$_IFLASH_') - -if __name__ == '__main__': - # Show script title - print('\n' + title) - - # Set console/shell window title - user_os = sys.platform - if user_os == 'win32': ctypes.windll.kernel32.SetConsoleTitleW(title) - elif user_os.startswith('linux') or user_os == 'darwin' or user_os.find('bsd') != -1: sys.stdout.write('\x1b]2;' + title + '\x07') - - # Set argparse Arguments - parser = argparse.ArgumentParser() - parser.add_argument('images', type=argparse.FileType('r'), nargs='*') - parser.add_argument('-v', '--verbose', help='show iFlash structure information', action='store_true') - parser.add_argument('-e', '--auto-exit', help='skip press enter to exit prompts', action='store_true') - parser.add_argument('-o', '--output-dir', help='extract in given output directory') - parser.add_argument('-i', '--input-dir', help='extract from given input directory') - args = parser.parse_args() - - # Set pause-able Python exception handler (must be after args) - sys.excepthook = show_exception_and_exit - - # Initialize Dell PFS input file list - iflash_input_images = [] - - # Process input files - if len(sys.argv) >= 2: - # Drag & Drop or CLI - if args.input_dir: - input_path_user = get_absolute_path(args.input_dir) - iflash_input_images = get_path_files(input_path_user) - else: - iflash_input_images = [image.name for image in args.images] - - output_path_user = get_absolute_path(args.output_dir or args.input_dir) - else: - # Script w/o parameters - input_path_user = get_absolute_path(input('\nEnter input directory path: ')) - iflash_input_images = get_path_files(input_path_user) - - output_path_user = get_absolute_path(input('\nEnter output directory path: ')) - - # Initialize global variables - exit_code = len(iflash_input_images) # Initialize exit code with input file count - is_verbose = bool(args.verbose) # Set Verbose output mode optional argument - - for input_file in iflash_input_images: - input_name = os.path.basename(input_file) - input_padd = 8 - - print('\n*** %s' % input_name) - - # Check if input file exists - if not os.path.isfile(input_file): - print('\n%sError: This input file does not exist!' % (' ' * input_padd)) - continue # Next input file - - with open(input_file, 'rb') as in_file: input_data = in_file.read() - - # Search input image for Insyde iFlash Sections - is_ins_ifl = pat_ins_ifl.search(input_data) - - if not is_ins_ifl: - print('\n%sError: This is not an Insyde iFlash image!' % (' ' * input_padd)) - continue # Next input file - - # Set main extraction path (optional user specified path taken into account) - output_path = os.path.join(output_path_user, input_name + '_extracted') - - InsydeIflash(input_data, output_path, input_padd, is_verbose).iflash_parse() - - exit_code -= 1 # Adjust exit code to reflect extraction progress - - if not bool(args.auto_exit): input('\nDone!') - - sys.exit(exit_code) diff --git a/Panasonic BIOS Update Extractor/Panasonic_BIOS_Extract.py b/Panasonic BIOS Update Extractor/Panasonic_BIOS_Extract.py deleted file mode 100644 index 414bad4..0000000 --- a/Panasonic BIOS Update Extractor/Panasonic_BIOS_Extract.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 - -""" -Panasonic BIOS Extract -Panasonic BIOS Update Extractor -Copyright (C) 2018 Plato Mavropoulos -""" - -print('Panasonic BIOS Update Extractor v1.0') - -import os -import sys -import shutil -import pefile -import subprocess - -if len(sys.argv) >= 2 : - # Drag & Drop or CLI - panasonic = sys.argv[1:] -else : - # Folder path - panasonic = [] - in_path = input('\nEnter the full folder path: ') - print('\nWorking...') - for root, dirs, files in os.walk(in_path): - for name in files : - panasonic.append(os.path.join(root, name)) - -for input_file in panasonic : - file_path = os.path.abspath(input_file) - file_name = os.path.basename(input_file) - file_dir = os.path.dirname(file_path) - file_ext = os.path.splitext(file_path)[1] - - # Create output folder - extr_path = os.path.join(os.getcwd(), 'RCDATA') - if os.path.exists(extr_path) : shutil.rmtree(extr_path) - os.makedirs(extr_path) - - max_size = 0 - max_file = None - pe = pefile.PE(input_file) # Analyze Portable Executable (PE) - for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries : - # Parse Resource Data directories only - if entry.struct.name == 'IMAGE_RESOURCE_DIRECTORY_ENTRY' and entry.struct.Id == 10 : # RCDATA ID = 10 - for resource in entry.directory.entries : - offset = resource.directory.entries[0].data.struct.OffsetToData - size = resource.directory.entries[0].data.struct.Size - data = pe.get_data(offset, size) - file = os.path.join(extr_path, '%X_%X.bin' % (offset, size)) - with open(file, 'wb') as out_file : out_file.write(data) - - # Remember largest resource (SPI/BIOS) - if size > max_size : - max_size = size - max_file = file - - if not max_file : - print('\nError: No Panasonic BIOS Update at %s!' % file_name) - shutil.rmtree(extr_path) # Remove temporary folder - continue # Next input file - - # Call Rustam Abdullaev's unpack_lznt1 to extract the LZNT1-compressed SPI/BIOS resource at 0x8 onwards - try : - subprocess.run(['unpack_lznt1', max_file, os.path.join(file_dir, file_name[:-4] + '.bin'), '8'], check = True, stdout = subprocess.DEVNULL) - print('\nExtracted %s via unpack_lznt1' % (file_name[:-4] + '.bin')) - except : - print('\nError: Could not extract %s via unpack_lznt1!' % (file_name[:-4] + '.bin')) - print(' Make sure that "unpack_lznt1.exe" executable exists!') - - shutil.rmtree(extr_path) # Remove temporary folder - -else : - input('\nDone!') \ No newline at end of file diff --git a/Phoenix SCT BIOS Extractor/Phoenix_SCT_Extract.py b/Phoenix SCT BIOS Extractor/Phoenix_SCT_Extract.py deleted file mode 100644 index 9d873d6..0000000 --- a/Phoenix SCT BIOS Extractor/Phoenix_SCT_Extract.py +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env python3 -#coding=utf-8 - -""" -Phoenix SCT Extract -Phoenix SCT BIOS Extractor -Copyright (C) 2021 Plato Mavropoulos -""" - -title = 'Phoenix SCT BIOS Extractor v1.0' - -print('\n' + title) # Print script title - -import sys - -# Detect Python version -sys_ver = sys.version_info -if sys_ver < (3,7) : - sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1])) - (raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 - sys.exit(1) - -import os -import re -import lzma -import shutil -import ctypes -import argparse -import traceback - -# Pause after any unexpected Python exception -# https://stackoverflow.com/a/781074 by Torsten Marek -def show_exception_and_exit(exc_type, exc_value, tb) : - if exc_type is KeyboardInterrupt : - print('\n') - else : - print('\nError: %s crashed, please report the following:\n' % title) - traceback.print_exception(exc_type, exc_value, tb) - input('\nPress enter to exit') - - sys.exit(1) - -# Set pause-able Python exception handler -sys.excepthook = show_exception_and_exit - -# Set console/shell window title -user_os = sys.platform -if user_os == 'win32' : ctypes.windll.kernel32.SetConsoleTitleW(title) -elif user_os.startswith('linux') or user_os == 'darwin' or user_os.find('bsd') != -1 : sys.stdout.write('\x1b]2;' + title + '\x07') - -# Set argparse Arguments -sct_parser = argparse.ArgumentParser() -sct_parser.add_argument('executables', type=argparse.FileType('r'), nargs='*') -sct_parser.add_argument('-p', '--path', help='parse files within given folder', type=str) -sct_params = sct_parser.parse_args() - -# Get all files within path -def get_files(path) : - inputs = [] - - for root, _, files in os.walk(path): - for name in files : - inputs.append(os.path.join(root, name)) - - return inputs - -if len(sys.argv) >= 2 : - if bool(sct_params.path) : - sct_exec = get_files(sct_params.path) # CLI with --path - else : - sct_exec = [] - for executable in sct_params.executables : - sct_exec.append(executable.name) # Drag & Drop -else : - in_path = input('\nEnter the full folder path: ') - sct_exec = get_files(in_path) # Direct Run - -# Set ctypes Structure types -char = ctypes.c_char -uint8_t = ctypes.c_ubyte -uint16_t = ctypes.c_ushort -uint32_t = ctypes.c_uint -uint64_t = ctypes.c_uint64 - -class SCT_HDR(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('Tag', char*8), # 0x00 - ('Size', uint32_t), # 0x08 - ('Count', uint32_t), # 0x0C - # 0x10 - ] - - def sct_print(self) : - print('\n Phoenix SCT Header:\n') - print(' Tag : %s' % self.Tag.decode('utf-8','replace').strip()) - print(' Size : 0x%X' % self.Size) - print(' Count : %d' % self.Count) - -class SCT_MOD(ctypes.LittleEndianStructure) : - _pack_ = 1 - _fields_ = [ - ('Name', char*256), # 0x000 - ('Offset', uint32_t), # 0x100 - ('Size', uint32_t), # 0x104 - ('Compressed', uint32_t), # 0x108 - ('Reserved', uint32_t), # 0x10C - # 0x110 - ] - - def sct_print(self) : - print('\n Phoenix SCT Entry:\n') - print(' Name : %s' % self.Name.decode('utf-8','replace').strip()) - print(' Offset : 0x%X' % self.Offset) - print(' Size : 0x%X' % self.Size) - print(' Compressed : %s' % ['No','Yes'][self.Compressed]) - print(' Reserved : 0x%X' % self.Reserved) - -# Process ctypes Structure Classes -# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky -def get_struct(buffer, start_offset, class_name, param_list = None) : - if param_list is None : param_list = [] - - structure = class_name(*param_list) # Unpack parameter list - struct_len = ctypes.sizeof(structure) - struct_data = buffer[start_offset:start_offset + struct_len] - fit_len = min(len(struct_data), struct_len) - - if (start_offset >= len(buffer)) or (fit_len < struct_len) : - print('\n Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name.__name__)) - - input('\n Press enter to exit') - - sys.exit(1) - - ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len) - - return structure - -# Phoenix SCT BIOS Package Pattern ($PACK + Size + Count) -sct_pat = re.compile(br'\x24\x50\x41\x43\x4B\x00{3}..\x00{2}.\x00{3}', re.DOTALL) - -# Get common ctypes Structure Sizes -sct_hdr_len = ctypes.sizeof(SCT_HDR) -sct_mod_len = ctypes.sizeof(SCT_MOD) - -# Size of dummy/placeholder SCT Entries -sct_dummy_len = 0x200 # Top 2, Names only - -# Process each input Phoenix SCT BIOS executable -for input_file in sct_exec : - input_name,input_extension = os.path.splitext(os.path.basename(input_file)) - input_dir = os.path.dirname(os.path.abspath(input_file)) - - print('\n*** %s%s' % (input_name, input_extension)) - - # Check if input file exists - if not os.path.isfile(input_file) : - print('\n Error: This input file does not exist!') - continue # Next input file - - with open(input_file, 'rb') as in_file : input_data = in_file.read() - - sct_match = sct_pat.search(input_data) # Search for Phoenix SCT BIOS Pattern - - # Check if Phoenix SCT BIOS Pattern was found on executable - if not sct_match : - print('\n Error: This is not a Phoenix SCT BIOS executable!') - continue # Next input file - - output_path = os.path.join(input_dir, '%s%s' % (input_name, input_extension) + '_extracted') # Set extraction directory - - if os.path.isdir(output_path) : shutil.rmtree(output_path) # Delete any existing extraction directory - - os.mkdir(output_path) # Create extraction directory - - print('\n Phoenix SecureCore Technology') - - sct_hdr = get_struct(input_data, sct_match.start(), SCT_HDR) # Parse SCT Header Structure - sct_hdr.sct_print() # Print SCT Header Info - - # Check if reported SCT Header Size matches manual SCT Entry Count calculation - if sct_hdr.Size != sct_hdr_len + sct_dummy_len + sct_hdr.Count * sct_mod_len : - input('\n Error: This Phoenix SCT BIOS image is corrupted!') - continue # Next input file - - # Store all SCT $PACK Data w/o initial dummy/placeholder Entries - pack_data = input_data[sct_match.end() + sct_dummy_len:sct_match.start() + sct_hdr.Size] - - # Parse each SCT Entry - for e_idx in range(sct_hdr.Count) : - mod_hdr = get_struct(pack_data, e_idx * sct_mod_len, SCT_MOD) # Parse SCT Entry Structure - mod_hdr.sct_print() # Print SCT Entry Info - - mod_data = input_data[mod_hdr.Offset:mod_hdr.Offset + mod_hdr.Size] # Store SCT Entry Raw Data - - # Check if SCT Entry Raw Data is complete - if len(mod_data) != mod_hdr.Size : - input('\n Error: This Phoenix SCT BIOS image is incomplete!') - - # Store SCT Entry LZMA Decompressed Data, when applicable - if mod_hdr.Compressed : mod_data = lzma.LZMADecompressor().decompress(mod_data) - - # Replace common Windows reserved/illegal filename characters - mod_fname = re.sub(r'[\\/*?:"<>|]', '_', mod_hdr.Name.decode('utf-8','replace').strip()) - - with open(os.path.join(output_path, mod_fname), 'wb') as out : out.write(mod_data) # Store SCT Entry Data/File - - print('\n Extracted Phoenix SCT BIOS executable!') - -input('\nDone!') - -sys.exit(0) \ No newline at end of file diff --git a/Portwell EFI BIOS Extractor/Portwell_EFI_Extract.py b/Portwell EFI BIOS Extractor/Portwell_EFI_Extract.py deleted file mode 100644 index 7c939b7..0000000 --- a/Portwell EFI BIOS Extractor/Portwell_EFI_Extract.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python3 -#coding=utf-8 - -""" -Portwell EFI Extract -Portwell EFI BIOS Extractor -Copyright (C) 2021 Plato Mavropoulos -""" - -title = 'Portwell EFI BIOS Extractor v1.0' - -print('\n' + title) # Print script title - -import sys - -# Detect Python version -sys_ver = sys.version_info -if sys_ver < (3,7) : - sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1])) - (raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 - sys.exit(1) - -import os -import pefile -import shutil -import ctypes -import argparse -import traceback -import subprocess - -# Pause after any unexpected Python exception -# https://stackoverflow.com/a/781074 by Torsten Marek -def show_exception_and_exit(exc_type, exc_value, tb) : - if exc_type is KeyboardInterrupt : - print('\n') - else : - print('\nError: %s crashed, please report the following:\n' % title) - traceback.print_exception(exc_type, exc_value, tb) - input('\nPress enter to exit') - - sys.exit(1) - -# Set pause-able Python exception handler -sys.excepthook = show_exception_and_exit - -# Set console/shell window title -user_os = sys.platform -if user_os == 'win32' : ctypes.windll.kernel32.SetConsoleTitleW(title) -elif user_os.startswith('linux') or user_os == 'darwin' or user_os.find('bsd') != -1 : sys.stdout.write('\x1b]2;' + title + '\x07') - -# Set argparse Arguments -efi_parser = argparse.ArgumentParser() -efi_parser.add_argument('efi', type=argparse.FileType('r'), nargs='*') -efi_parser.add_argument('-p', '--path', help='parse files within given folder', type=str) -efi_params = efi_parser.parse_args() - -# Get all files within path -def get_files(path) : - inputs = [] - - for root, _, files in os.walk(path): - for name in files : - inputs.append(os.path.join(root, name)) - - return inputs - -if len(sys.argv) >= 2 : - if bool(efi_params.path) : - efi_exec = get_files(efi_params.path) # CLI with --path - else : - efi_exec = [] - for executable in efi_params.efi : - efi_exec.append(executable.name) # Drag & Drop -else : - in_path = input('\nEnter the full folder path: ') - efi_exec = get_files(in_path) # Direct Run - -# Portwell UEFI Unpacker File Names (v1.1 - v1.2) -file_names = {0 : 'Flash.efi', 1 : 'Fparts.txt', 2 : 'Update.nsh', 3 : 'Temp.bin', 4 : 'SaveDmiData.efi'} - -# Process each input Portwell EFI Update Package -for input_file in efi_exec : - input_name,input_extension = os.path.splitext(os.path.basename(input_file)) - input_dir = os.path.dirname(os.path.abspath(input_file)) - - print('\n*** %s%s' % (input_name, input_extension)) - - # Check if input file exists - if not os.path.isfile(input_file) : - print('\n Error: This input file does not exist!') - continue # Next input file - - with open(input_file, 'rb') as in_file : input_data = in_file.read() - - try : - assert input_data[0x0:0x2] == b'\x4D\x5A' # EFI images start with DOS Header MZ - - pe = pefile.PE(input_file, fast_load=True) # Analyze EFI Portable Executable (PE) - - payload_data = input_data[pe.OPTIONAL_HEADER.SizeOfImage:] # Skip EFI executable (pylint: disable=E1101) - - assert payload_data[0x0:0x4] == b'\x3C\x55\x55\x3E' # Portwell EFI files start with - except : - print('\n Error: This is not a Portwell EFI Update Package!') - continue # Next input file - - output_path = os.path.join(input_dir, '%s%s' % (input_name, input_extension) + '_extracted') # Set extraction directory - - if os.path.isdir(output_path) : shutil.rmtree(output_path) # Delete any existing extraction directory - - os.mkdir(output_path) # Create extraction directory - - pack_tag = 'UEFI Unpacker' # Initialize Portwell UEFI Unpacker tag - - # Get Portwell UEFI Unpacker tag - for s in pe.sections : - if s.Name.startswith(b'.data') : # Unpacker Tag, Version, Strings etc are found in .data PE section - # Decode any valid UTF-16 .data PE section info to a parsable text buffer - info = input_data[s.PointerToRawData:s.PointerToRawData + s.SizeOfRawData].decode('utf-16','ignore') - - # Search .data for UEFI Unpacker tag - pack_tag_off = info.find('UEFI Unpacker') - if pack_tag_off != -1 : - pack_tag_len = info[pack_tag_off:].find('=') - if pack_tag_len != -1 : - # Found full UEFI Unpacker tag, store and slightly beautify the resulting text - pack_tag = info[pack_tag_off:pack_tag_off + pack_tag_len].strip().replace(' ',' ').replace('<',' <') - - break # Found PE .data section, skip the rest - - print('\n Portwell %s' % pack_tag) # Print Portwell UEFI Unpacker tag - - efi_files = payload_data.split(b'\x3C\x55\x55\x3E')[1:] # Split EFI Payload into file chunks - - # Parse each EFI Payload File - for i in range(len(efi_files)) : - file_data = efi_files[i] # Store EFI File data - - if len(file_data) == 0 or file_data == b'NULL' : continue # Skip empty/unused files - - is_known = i in file_names # Check if EFI file is known & Store result - - file_name = file_names[i] if is_known else 'Unknown_%d.bin' % i # Assign Name to EFI file - - print('\n %s' % file_name) # Print EFI file name, indicate progress - - if not is_known : input('\n Note: Detected unknown Portwell EFI file with ID %d!' % i) # Report new EFI files - - file_path = os.path.join(output_path, file_name) # Store EFI file output path - - with open(file_path, 'wb') as o : o.write(file_data) # Store EFI file data to drive - - # Attempt to detect EFI/Tiano Compression & Decompress when applicable - if int.from_bytes(file_data[0x0:0x4], 'little') + 0x8 == len(file_data) : - try : - comp_fname = file_path + '.temp' # Store temporary compressed file name - - os.replace(file_path, comp_fname) # Rename initial/compressed file - - subprocess.run(['TianoCompress', '-d', comp_fname, '-o', file_path, '--uefi', '-q'], check = True, stdout = subprocess.DEVNULL) - - if os.path.getsize(file_path) != int.from_bytes(file_data[0x4:0x8], 'little') : raise Exception('EFI_DECOMP_ERROR') - - os.remove(comp_fname) # Successful decompression, delete initial/compressed file - - except : - print('\n Error: Could not extract file %s via TianoCompress!' % file_name) - input(' Make sure that "TianoCompress" executable exists!') - - print('\n Extracted Portwell EFI Update Package!') - -input('\nDone!') - -sys.exit(0) \ No newline at end of file diff --git a/VAIO Packaging Manager Extractor/VAIO_Package_Extract.py b/VAIO Packaging Manager Extractor/VAIO_Package_Extract.py deleted file mode 100644 index 0d7758f..0000000 --- a/VAIO Packaging Manager Extractor/VAIO_Package_Extract.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python3 - -""" -VAIO Package Extractor -VAIO Packaging Manager Extractor -Copyright (C) 2019-2020 Plato Mavropoulos -""" - -print('VAIO Packaging Manager Extractor v2.0') - -import os -import re -import sys -import subprocess - -if len(sys.argv) >= 2 : - # Drag & Drop or CLI - vaio_exec = sys.argv[1:] -else : - # Folder path - vaio_exec = [] - in_path = input('\nEnter the full folder path: ') - print('\nWorking...') - for root, dirs, files in os.walk(in_path): - for name in files : - vaio_exec.append(os.path.join(root, name)) - -# Microsoft CAB Header XOR 0xFF (Tag[4] + Res[4] + Size[4] + Res[4] + Offset[4] + Res[4] + Ver[2]) pattern -mscf_pattern = re.compile(br'\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL) - -# VAIO Packaging Manager Configuration file ("[Setting]" + Windows_new_line) pattern -vaio_pattern = re.compile(br'\x5B\x53\x65\x74\x74\x69\x6E\x67\x5D\x0D\x0A') - -# VAIO Packaging Manager Configuration file entry "UseVAIOCheck" pattern -check_pattern = re.compile(br'\x0A\x55\x73\x65\x56\x41\x49\x4F\x43\x68\x65\x63\x6B\x3D') - -# VAIO Packaging Manager Configuration file entry "ExtractPathByUser" pattern -path_pattern = re.compile(br'\x0A\x45\x78\x74\x72\x61\x63\x74\x50\x61\x74\x68\x42\x79\x55\x73\x65\x72\x3D') - -for input_file in vaio_exec : - file_path = os.path.abspath(input_file) - file_dir = os.path.dirname(file_path) - file_name = os.path.basename(file_path) - - print('\nFile: ' + file_name) - - # Open Locked VAIO Packaging Manager executable as mutable bytearray - with open(input_file, 'rb') as in_file : vaio_data = bytearray(in_file.read()) - - match_mscf = mscf_pattern.search(vaio_data) # Search for Microsoft CAB Header XOR 0xFF pattern - - match_vaio = vaio_pattern.search(vaio_data) if not match_mscf else None # Search for VAIO Packaging Manager Configuration file - - # Check if Microsoft CAB Header XOR 0xFF pattern exists - if match_mscf : - print('\n Detected Obfuscation!') - - # Determine the Microsoft CAB image Size - cab_size = int.from_bytes(vaio_data[match_mscf.start() + 0x8:match_mscf.start() + 0xC], 'little') # Get LE XOR-ed CAB Size - xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB Size XOR value - cab_size = cab_size ^ xor_size # Perform XOR 0xFF and get actual CAB Size - - print('\n Removing Obfuscation...') - - # Determine the Microsoft CAB image Data - cab_data = int.from_bytes(vaio_data[match_mscf.start():match_mscf.start() + cab_size], 'big') # Get BE XOR-ed CAB Data - xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB Data XOR value - cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB Data - - print('\n Extracting...') - - with open('vaio_temp.cab', 'wb') as cab_file : cab_file.write(cab_data) # Create temporary CAB image - - extr_path = os.path.join(file_dir, file_name[:-4], '') # Create CAB image extraction path - - try : - decomp = subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + extr_path, 'vaio_temp.cab']) # 7-Zip - - print('\n Extracted!') - except : - print('\n Error: Could not decompress Microsoft CAB image!') - print(' Make sure that "7z" executable exists!') - - os.remove('vaio_temp.cab') # Remove temporary CAB image - - # Check if VAIO Packaging Manager Configuration file pattern exists - elif match_vaio : - print('\n Error: Failed to Extract, attempting to Unlock instead...') - - # Initialize VAIO Package Configuration file variables (assume overkill size of 0x500) - info_start, info_end, val_false, val_true = [match_vaio.start(), match_vaio.start() + 0x500, b'', b''] - - # Get VAIO Package Configuration file info, split at new_line and stop at payload DOS header (EOF) - vaio_info = vaio_data[info_start:info_end].split(b'\x0D\x0A\x4D\x5A')[0].replace(b'\x0D',b'').split(b'\x0A') - - # Determine VAIO Package Configuration file True & False values - for info in vaio_info : - if info.startswith(b'ExtractPathByUser=') : val_false = bytearray(b'0' if info[18:] in (b'0',b'1') else info[18:]) # Should be 0/No/False - if info.startswith(b'UseCompression=') : val_true = bytearray(b'1' if info[15:] in (b'0',b'1') else info[15:]) # Should be 1/Yes/True - else : - if val_false == val_true or not val_false or not val_true : - print('\n Error: Could not determine True/False values!') - print(' Please report this VAIO Packaging Manager!') - continue # Next input file - - # Find and replace UseVAIOCheck entry from 1/Yes/True to 0/No/False - UseVAIOCheck = check_pattern.search(vaio_data[info_start:]) - if UseVAIOCheck : vaio_data[info_start + UseVAIOCheck.end():info_start + UseVAIOCheck.end() + len(val_false)] = val_false - else : print('\n Error: Could not find UseVAIOCheck entry!') - - # Find and replace ExtractPathByUser entry from 0/No/False to 1/Yes/True - ExtractPathByUser = path_pattern.search(vaio_data[info_start:]) - if ExtractPathByUser : vaio_data[info_start + ExtractPathByUser.end():info_start + ExtractPathByUser.end() + len(val_false)] = val_true - else : print('\n Error: Could not find ExtractPathByUser entry!') - - # Store Unlocked VAIO Packaging Manager executable - if UseVAIOCheck and ExtractPathByUser : - with open(os.path.join(file_dir, file_name + '_Unlocked.exe'), 'wb') as unl_file : unl_file.write(vaio_data) - print('\n Unlocked!') - - else : - print('\n Error: No VAIO Packaging Manager found!') - continue # Next input file - -else : - input('\nDone!') \ No newline at end of file From 46172a218b167fec6370b23bbe30d60653825301 Mon Sep 17 00:00:00 2001 From: platomav Date: Fri, 1 Apr 2022 18:00:39 +0300 Subject: [PATCH 07/52] add empty external directory --- external/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 external/.gitignore diff --git a/external/.gitignore b/external/.gitignore new file mode 100644 index 0000000..86d0cb2 --- /dev/null +++ b/external/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file From f2be70142336e3eaaa5f7b859ebd2c3b00572d9b Mon Sep 17 00:00:00 2001 From: platomav Date: Thu, 7 Apr 2022 01:13:07 +0300 Subject: [PATCH 08/52] Added Dell PFS Update Extractor v6.0_a1 Adjusted dependencies --- AMI_PFAT_Extract.py | 117 +++--- AMI_UCP_Extract.py | 164 ++++---- Dell_PFS_Extract.py | 976 +++++++++++++++++++++++++++++++++++++++++++ README.md | 68 ++- common/a7z_comp.py | 8 +- common/checksums.py | 20 +- common/efi_comp.py | 8 +- common/path_ops.py | 18 +- common/patterns.py | 3 + common/script_get.py | 19 - common/system.py | 48 ++- common/text_ops.py | 4 +- 12 files changed, 1242 insertions(+), 211 deletions(-) create mode 100644 Dell_PFS_Extract.py delete mode 100644 common/script_get.py diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py index 42e217b..c681d7a 100644 --- a/AMI_PFAT_Extract.py +++ b/AMI_PFAT_Extract.py @@ -7,7 +7,7 @@ AMI BIOS Guard Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'AMI BIOS Guard Extractor v4.0_a1' +title = 'AMI BIOS Guard Extractor v4.0_a2' import os import re @@ -18,13 +18,12 @@ import ctypes # Stop __pycache__ generation sys.dont_write_bytecode = True -from common.patterns import PAT_AMI_PFAT from common.externals import get_bgs_tool from common.num_ops import get_ordinal -from common.text_ops import padder -from common.path_ops import argparse_init, process_input_files, safe_name +from common.path_ops import argparse_init, safe_name +from common.patterns import PAT_AMI_PFAT from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t -from common.system import nice_exc_handler, check_sys_py, check_sys_os, show_title, print_input +from common.system import script_init, script_title, printer class AmiBiosGuardHeader(ctypes.LittleEndianStructure): _pack_ = 1 @@ -36,13 +35,11 @@ class AmiBiosGuardHeader(ctypes.LittleEndianStructure): # 0x11 ] - def struct_print(self, padding): - p = padder(padding) - - print(p + 'Size :', '0x%X' % self.Size) - print(p + 'Checksum:', '0x%0.4X' % self.Checksum) - print(p + 'Tag :', self.Tag.decode('utf-8')) - print(p + 'Flags :', '0x%0.2X' % self.Flags) + def struct_print(self, p): + printer(['Size :', '0x%X' % self.Size], p, False) + printer(['Checksum:', '0x%0.4X' % self.Checksum], p, False) + printer(['Tag :', self.Tag.decode('utf-8')], p, False) + printer(['Flags :', '0x%0.2X' % self.Flags], p, False) class IntelBiosGuardHeader(ctypes.LittleEndianStructure): _pack_ = 1 @@ -77,25 +74,23 @@ class IntelBiosGuardHeader(ctypes.LittleEndianStructure): return attr.b.SFAM, attr.b.ProtectEC, attr.b.GFXMitDis, attr.b.FTU, attr.b.Reserved - def struct_print(self, padding): - p = padder(padding) - + def struct_print(self, p): no_yes = ['No','Yes'] f1,f2,f3,f4,f5 = self.get_flags() - print(p + 'BIOS Guard Version :', '%d.%d' % (self.BGVerMajor, self.BGVerMinor)) - print(p + 'Platform Identity :', self.get_platform_id()) - print(p + 'Signed Flash Address Map :', no_yes[f1]) - print(p + 'Protected EC OpCodes :', no_yes[f2]) - print(p + 'Graphics Security Disable :', no_yes[f3]) - print(p + 'Fault Tolerant Update :', no_yes[f4]) - print(p + 'Attributes Reserved :', '0x%X' % f5) - print(p + 'Script Version :', '%d.%d' % (self.ScriptVerMajor, self.ScriptVerMinor)) - print(p + 'Script Size :', '0x%X' % self.ScriptSize) - print(p + 'Data Size :', '0x%X' % self.DataSize) - print(p + 'BIOS Security Version Number:', '0x%X' % self.BIOSSVN) - print(p + 'EC Security Version Number :', '0x%X' % self.ECSVN) - print(p + 'Vendor Information :', '0x%X' % self.VendorInfo) + printer(['BIOS Guard Version :', '%d.%d' % (self.BGVerMajor, self.BGVerMinor)], p, False) + printer(['Platform Identity :', self.get_platform_id()], p, False) + printer(['Signed Flash Address Map :', no_yes[f1]], p, False) + printer(['Protected EC OpCodes :', no_yes[f2]], p, False) + printer(['Graphics Security Disable :', no_yes[f3]], p, False) + printer(['Fault Tolerant Update :', no_yes[f4]], p, False) + printer(['Attributes Reserved :', '0x%X' % f5], p, False) + printer(['Script Version :', '%d.%d' % (self.ScriptVerMajor, self.ScriptVerMinor)], p, False) + printer(['Script Size :', '0x%X' % self.ScriptSize], p, False) + printer(['Data Size :', '0x%X' % self.DataSize], p, False) + printer(['BIOS Security Version Number:', '0x%X' % self.BIOSSVN], p, False) + printer(['EC Security Version Number :', '0x%X' % self.ECSVN], p, False) + printer(['Vendor Information :', '0x%X' % self.VendorInfo], p, False) class IntelBiosGuardHeaderAttributes(ctypes.LittleEndianStructure): _fields_ = [ @@ -123,17 +118,15 @@ class IntelBiosGuardSignature2k(ctypes.LittleEndianStructure): # 0x20C ] - def struct_print(self, padding): - p = padder(padding) - + def struct_print(self, p): Modulus = '%0.*X' % (0x100 * 2, int.from_bytes(self.Modulus, 'little')) Signature = '%0.*X' % (0x100 * 2, int.from_bytes(self.Signature, 'little')) - print(p + 'Unknown 0:', '0x%X' % self.Unknown0) - print(p + 'Unknown 1:', '0x%X' % self.Unknown1) - print(p + 'Modulus :', '%s [...]' % Modulus[:32]) - print(p + 'Exponent :', '0x%X' % self.Exponent) - print(p + 'Signature:', '%s [...]' % Signature[:32]) + printer(['Unknown 0:', '0x%X' % self.Unknown0], p, False) + printer(['Unknown 1:', '0x%X' % self.Unknown1], p, False) + printer(['Modulus :', '%s [...]' % Modulus[:32]], p, False) + printer(['Exponent :', '0x%X' % self.Exponent], p, False) + printer(['Signature:', '%s [...]' % Signature[:32]], p, False) def get_ami_pfat(input_buffer): match = PAT_AMI_PFAT.search(input_buffer) @@ -149,33 +142,33 @@ def parse_bg_script(script_data, padding): is_opcode_div = len(script_data) % 8 == 0 if not is_opcode_div: - print('%sError: Script not divisible by OpCode length!' % padder(padding)) + printer('Error: Script not divisible by OpCode length!', padding, False) return 1 is_begin_end = script_data[:8] + script_data[-8:] == b'\x01' + b'\x00' * 7 + b'\xFF' + b'\x00' * 7 if not is_begin_end: - print('%sError: Script lacks Begin and/or End OpCodes!' % padder(padding)) + printer('Error: Script lacks Begin and/or End OpCodes!', padding, False) return 2 BigScript = get_bgs_tool() if not BigScript: - print('%sError: BIOS Guard Script Tool dependency missing!' % padder(padding)) + printer('Error: BIOS Guard Script Tool dependency missing!', padding, False) return 3 script = BigScript(code_bytes=script_data).to_string().replace('\t',' ').split('\n') for opcode in script: - if opcode.endswith(('begin','end')): spacing = padder(padding) - elif opcode.endswith(':'): spacing = padder(padding + 4) - else: spacing = padder(padding + 12) + if opcode.endswith(('begin','end')): spacing = padding + elif opcode.endswith(':'): spacing = padding + 4 + else: spacing = padding + 12 operands = [operand for operand in opcode.split(' ') if operand] - print(spacing + ('{:<12s}' + '{:<11s}' * (len(operands) - 1)).format(*operands)) + printer(('{:<12s}' + '{:<11s}' * (len(operands) - 1)).format(*operands), spacing, False) return 0 @@ -188,7 +181,7 @@ def parse_pfat_hdr(buffer, padding): hdr_data = buffer[PFAT_AMI_HDR_LEN:hdr_size] hdr_text = hdr_data.decode('utf-8').splitlines() - print('\n%sAMI BIOS Guard Header:\n' % padder(padding)) + printer('AMI BIOS Guard Header:\n', padding) pfat_hdr.struct_print(padding + 4) @@ -198,7 +191,7 @@ def parse_pfat_hdr(buffer, padding): hdr_tag,*hdr_indexes = hdr_title.split('II') - print('\n%s%s\n' % (padder(padding + 4), hdr_tag)) + printer(hdr_tag + '\n', padding + 4) bgt_indexes = [int(h, 16) for h in re.findall(r'.{1,4}', hdr_indexes[0])] if hdr_indexes else [] @@ -218,7 +211,7 @@ def parse_pfat_hdr(buffer, padding): block_all += [(desc, name, order, param, flags, index, i, count) for i in range(count)] - _ = [print(padder(padding + 8) + block[0]) for block in block_all if block[6] == 0] + _ = [printer(block[0], padding + 8, False) for block in block_all if block[6] == 0] return block_all, hdr_size, files_count @@ -240,7 +233,7 @@ def parse_pfat_file(buffer, output_path, padding): file_desc,file_name,_,_,_,file_index,block_index,block_count = block if block_index == 0: - print('\n%s%s' % (padder(padding + 4), file_desc)) + printer(file_desc, padding + 4) file_path = os.path.join(extract_path, get_file_name(file_index + 1, file_name)) @@ -250,7 +243,7 @@ def parse_pfat_file(buffer, output_path, padding): bg_hdr = get_struct(buffer, block_off, IntelBiosGuardHeader) - print('\n%sIntel BIOS Guard %s Header:\n' % (padder(padding + 8), block_status)) + printer('Intel BIOS Guard %s Header:\n' % block_status, padding + 8) bg_hdr.struct_print(padding + 12) @@ -274,13 +267,13 @@ def parse_pfat_file(buffer, output_path, padding): if len(bg_sig_bin) == PFAT_BLK_S2K_LEN: bg_sig = get_struct(bg_sig_bin, 0x0, IntelBiosGuardSignature2k) - print('\n%sIntel BIOS Guard %s Signature:\n' % (padder(padding + 8), block_status)) + printer('Intel BIOS Guard %s Signature:\n' % block_status, padding + 8) bg_sig.struct_print(padding + 12) block_off = bg_sig_end # Adjust next block to start at data + signature end - print('\n%sIntel BIOS Guard %s Script:\n' % (padder(padding + 8), block_status)) + printer('Intel BIOS Guard %s Script:\n' % block_status, padding + 8) _ = parse_bg_script(bg_script_bin, padding + 12) @@ -310,38 +303,26 @@ PFAT_BLK_S2K_LEN = ctypes.sizeof(IntelBiosGuardSignature2k) if __name__ == '__main__': # Show script title - show_title(title) + script_title(title) # Set argparse Arguments argparser = argparse_init() arguments = argparser.parse_args() - # Pretty Python exception handler (must be after argparse) - sys.excepthook = nice_exc_handler - - # Check Python Version (must be after argparse) - check_sys_py() - - # Check OS Platform (must be after argparse) - check_sys_os() - - # Process input files and generate output path - input_files,output_path = process_input_files(arguments, sys.argv) - - # Initial output padding count - padding = 4 + # Initialize script (must be after argparse) + input_files,output_path,padding = script_init(arguments, 4) for input_file in input_files: input_name = os.path.basename(input_file) - print('\n*** %s' % input_name) + printer(['***', input_name], padding - 4) with open(input_file, 'rb') as in_file: input_buffer = in_file.read() pfat_match,pfat_buffer = get_ami_pfat(input_buffer) if not pfat_match: - print('\n%sError: This is not an AMI BIOS Guard (PFAT) image!' % padder(padding)) + printer('Error: This is not an AMI BIOS Guard (PFAT) image!', padding) continue # Next input file @@ -349,4 +330,4 @@ if __name__ == '__main__': parse_pfat_file(pfat_buffer, extract_path, padding) - print_input('\nDone!') + printer('Done!', pause=True) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index c16743a..ccee9d6 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -title = 'AMI UCP BIOS Extractor v2.0_a1' +title = 'AMI UCP BIOS Extractor v2.0_a2' import os import sys @@ -19,14 +19,14 @@ import contextlib # Stop __pycache__ generation sys.dont_write_bytecode = True -from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG -from common.checksums import checksum16 -from common.text_ops import padder from common.a7z_comp import a7z_decompress, is_7z_supported +from common.checksums import get_chk_16 from common.efi_comp import efi_decompress, is_efi_compressed -from common.path_ops import argparse_init, process_input_files, safe_name +from common.path_ops import argparse_init, safe_name +from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t -from common.system import nice_exc_handler, check_sys_py, check_sys_os, show_title, print_input +from common.system import script_init, script_title, printer + from AMI_PFAT_Extract import get_ami_pfat, parse_pfat_file class UafHeader(ctypes.LittleEndianStructure): @@ -41,15 +41,13 @@ class UafHeader(ctypes.LittleEndianStructure): # 0x10 ] - def struct_print(self, padding): - p = padder(padding) - - print(p + 'Tag :', self.ModuleTag.decode('utf-8')) - print(p + 'Size :', '0x%X' % self.ModuleSize) - print(p + 'Checksum :', '0x%0.4X' % self.Checksum) - print(p + 'Unknown 0 :', '0x%0.2X' % self.Unknown0) - print(p + 'Unknown 1 :', '0x%0.2X' % self.Unknown1) - print(p + 'Reserved :', '0x%0.8X' % self.Reserved) + def struct_print(self, p): + printer(['Tag :', self.ModuleTag.decode('utf-8')], p, False) + printer(['Size :', '0x%X' % self.ModuleSize], p, False) + printer(['Checksum :', '0x%0.4X' % self.Checksum], p, False) + printer(['Unknown 0 :', '0x%0.2X' % self.Unknown0], p, False) + printer(['Unknown 1 :', '0x%0.2X' % self.Unknown1], p, False) + printer(['Reserved :', '0x%0.8X' % self.Reserved], p, False) class UafModule(ctypes.LittleEndianStructure): _pack_ = 1 @@ -59,12 +57,10 @@ class UafModule(ctypes.LittleEndianStructure): # 0x08 ] - def struct_print(self, padding, filename): - p = padder(padding) - - print(p + 'Compress Size:', '0x%X' % self.CompressSize) - print(p + 'Original Size:', '0x%X' % self.OriginalSize) - print(p + 'File Name :', filename) + def struct_print(self, p, filename): + printer(['Compress Size:', '0x%X' % self.CompressSize], p, False) + printer(['Original Size:', '0x%X' % self.OriginalSize], p, False) + printer(['File Name :', filename], p, False) class UiiHeader(ctypes.LittleEndianStructure): _pack_ = 1 @@ -88,26 +84,24 @@ class UiiHeader(ctypes.LittleEndianStructure): PTP = {1: 'Executable', 2: 'Library', 3: 'Driver'} PMD = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'} - def struct_print(self, padding, description): - p = padder(padding) - + def struct_print(self, p, description): SupportBIOS = self.SBI.get(self.SupportBIOS, 'Unknown (%d)' % self.SupportBIOS) SupportOS = self.SOS.get(self.SupportOS, 'Unknown (%d)' % self.SupportOS) DataBusWidth = self.DBW.get(self.DataBusWidth, 'Unknown (%d)' % self.DataBusWidth) ProgramType = self.PTP.get(self.ProgramType, 'Unknown (%d)' % self.ProgramType) ProgramMode = self.PMD.get(self.ProgramMode, 'Unknown (%d)' % self.ProgramMode) - print(p + 'UII Size :', '0x%X' % self.UIISize) - print(p + 'Checksum :', '0x%0.4X' % self.Checksum) - print(p + 'Tool Version :', '0x%0.8X' % self.UtilityVersion) - print(p + 'Info Size :', '0x%X' % self.InfoSize) - print(p + 'Supported BIOS:', SupportBIOS) - print(p + 'Supported OS :', SupportOS) - print(p + 'Data Bus Width:', DataBusWidth) - print(p + 'Program Type :', ProgramType) - print(p + 'Program Mode :', ProgramMode) - print(p + 'SourceSafe Tag:', '%0.2d' % self.SourceSafeRel) - print(p + 'Description :', description) + printer(['UII Size :', '0x%X' % self.UIISize], p, False) + printer(['Checksum :', '0x%0.4X' % self.Checksum], p, False) + printer(['Tool Version :', '0x%0.8X' % self.UtilityVersion], p, False) + printer(['Info Size :', '0x%X' % self.InfoSize], p, False) + printer(['Supported BIOS:', SupportBIOS], p, False) + printer(['Supported OS :', SupportOS], p, False) + printer(['Data Bus Width:', DataBusWidth], p, False) + printer(['Program Type :', ProgramType], p, False) + printer(['Program Mode :', ProgramMode], p, False) + printer(['SourceSafe Tag:', '%0.2d' % self.SourceSafeRel], p, False) + printer(['Description :', description], p, False) class DisHeader(ctypes.LittleEndianStructure): _pack_ = 1 @@ -118,12 +112,10 @@ class DisHeader(ctypes.LittleEndianStructure): # 0x10 ] - def struct_print(self, padding): - p = padder(padding) - - print(p + 'Password Size:', '0x%X' % self.PasswordSize) - print(p + 'Entry Count :', self.EntryCount) - print(p + 'Password :', self.Password.decode('utf-8')) + def struct_print(self, p): + printer(['Password Size:', '0x%X' % self.PasswordSize], p, False) + printer(['Entry Count :', self.EntryCount], p, False) + printer(['Password :', self.Password.decode('utf-8')], p, False) class DisModule(ctypes.LittleEndianStructure): _pack_ = 1 @@ -138,23 +130,21 @@ class DisModule(ctypes.LittleEndianStructure): ENDIS = {0: 'Disabled', 1: 'Enabled'} SHOWN = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'} - def struct_print(self, padding): - p = padder(padding) - + def struct_print(self, p): EnabledDisabled = self.ENDIS.get(self.EnabledDisabled, 'Unknown (%d)' % self.EnabledDisabled) ShownHidden = self.SHOWN.get(self.ShownHidden, 'Unknown (%d)' % self.ShownHidden) - print(p + 'State :', EnabledDisabled) - print(p + 'Display :', ShownHidden) - print(p + 'Command :', self.Command.decode('utf-8').strip()) - print(p + 'Description:', self.Description.decode('utf-8').strip()) + printer(['State :', EnabledDisabled], p, False) + printer(['Display :', ShownHidden], p, False) + printer(['Command :', self.Command.decode('utf-8').strip()], p, False) + printer(['Description:', self.Description.decode('utf-8').strip()], p, False) # Validate @UAF Module Checksum-16 def chk16_validate(data, tag, padd=0): - if checksum16(data) != 0: - print_input('\n%sError: Invalid UCP Module %s Checksum!' % (padder(padd), tag)) + if get_chk_16(data) != 0: + printer('Error: Invalid UCP Module %s Checksum!' % tag, padd, pause=True) else: - print('\n%sChecksum of UCP Module %s is valid!' % (padder(padd), tag)) + printer('Checksum of UCP Module %s is valid!' % tag, padd) # Get all input file AMI UCP patterns def get_ami_ucp(buffer): @@ -197,10 +187,10 @@ def get_uaf_mod(buffer, uaf_off=0x0): return uaf_all # Parse & Extract AMI UCP structures -def ucp_extract(buffer, output_path, padding=0, is_chk16=False): +def ucp_extract(buffer, output_path, padding=0, is_checksum=False): nal_dict = {} # Initialize @NAL Dictionary per UCP - print('\n%sUtility Configuration Program' % padder(padding)) + printer('Utility Configuration Program', padding) extract_path = os.path.join(output_path + '_extracted', '') @@ -210,7 +200,7 @@ def ucp_extract(buffer, output_path, padding=0, is_chk16=False): uaf_hdr = get_struct(buffer, 0, UafHeader) # Parse @UAF Header Structure - print('\n%sUtility Auxiliary File > @UAF:\n' % padder(padding + 4)) + printer('Utility Auxiliary File > @UAF:\n', padding + 4) uaf_hdr.struct_print(padding + 8) @@ -220,15 +210,15 @@ def ucp_extract(buffer, output_path, padding=0, is_chk16=False): uaf_mod.struct_print(padding + 8, UAF_TAG_DICT['@UAF'][0]) # Print @UAF Module EFI Info - if is_chk16: chk16_validate(buffer, '@UAF', padding + 8) + if is_checksum: chk16_validate(buffer, '@UAF', padding + 8) uaf_all = get_uaf_mod(buffer, UAF_HDR_LEN) for mod_info in uaf_all: - nal_dict = uaf_extract(buffer, extract_path, mod_info, padding + 8, is_chk16, nal_dict) + nal_dict = uaf_extract(buffer, extract_path, mod_info, padding + 8, is_checksum, nal_dict) # Parse & Extract AMI UCP > @UAF Module/Section -def uaf_extract(buffer, extract_path, mod_info, padding=0, is_chk16=False, nal_dict=None): +def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, nal_dict=None): if nal_dict is None: nal_dict = {} uaf_tag,uaf_off,uaf_hdr = mod_info @@ -239,7 +229,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_chk16=False, nal_d uaf_data_raw = uaf_data_mod[UAF_MOD_LEN:] # @UAF Module Raw Data - print('\n%sUtility Auxiliary File > %s:\n' % (padder(padding), uaf_tag)) + printer('Utility Auxiliary File > %s:\n' % uaf_tag, padding) uaf_hdr.struct_print(padding + 4) # Print @UAF Module Info @@ -264,12 +254,12 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_chk16=False, nal_d # Check if unknown @UAF Module Tag is present in NAL but not in built-in dictionary if uaf_tag in nal_dict and uaf_tag not in UAF_TAG_DICT and not uaf_tag.startswith(('@ROM','@R0','@S0','@DR','@DS')): - print_input('\n%sNote: Detected new AMI UCP Module %s (%s) in NAL!' % (padder(padding), uaf_tag, nal_dict[uaf_tag])) + printer('Note: Detected new AMI UCP Module %s (%s) in NAL!' % (uaf_tag, nal_dict[uaf_tag]), padding, pause=True) # Generate @UAF Module File name, depending on whether decompression will be required uaf_fname = os.path.join(extract_path, safe_name(uaf_name + ('.temp' if is_comp else uaf_fext))) - if is_chk16: chk16_validate(uaf_data_all, uaf_tag, padding + 4) + if is_checksum: chk16_validate(uaf_data_all, uaf_tag, padding + 4) # Parse Utility Identification Information @UAF Module (@UII) if uaf_tag == '@UII': @@ -280,11 +270,11 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_chk16=False, nal_d # Get @UII Module Info/Description text field info_desc = info_data.decode('utf-8','ignore').strip('\x00 ') - print('\n%sUtility Identification Information:\n' % padder(padding + 4)) + printer('Utility Identification Information:\n', padding + 4) info_hdr.struct_print(padding + 8, info_desc) # Print @UII Module Info - if is_chk16: chk16_validate(uaf_data_raw, '@UII > Info', padding + 8) + if is_checksum: chk16_validate(uaf_data_raw, '@UII > Info', padding + 8) # Store/Save @UII Module Info in file with open(uaf_fname[:-4] + '.txt', 'a', encoding='utf-8') as uii_out: @@ -319,14 +309,14 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_chk16=False, nal_d # Process and Print known text only @UAF Modules (after EFI/Tiano Decompression) if uaf_tag in UAF_TAG_DICT and UAF_TAG_DICT[uaf_tag][2] == 'Text': - print('\n%s%s:' % (padder(padding + 4), UAF_TAG_DICT[uaf_tag][1])) - print('\n%s%s' % (padder(padding + 8), uaf_data_raw.decode('utf-8','ignore'))) + printer(UAF_TAG_DICT[uaf_tag][1] + ':', padding + 4) + printer(uaf_data_raw.decode('utf-8','ignore'), padding + 8) # Parse Default Command Status @UAF Module (@DIS) if len(uaf_data_raw) and uaf_tag == '@DIS': dis_hdr = get_struct(uaf_data_raw, 0x0, DisHeader) # Parse @DIS Module Raw Header Structure - print('\n%sDefault Command Status Header:\n' % padder(padding + 4)) + printer('Default Command Status Header:\n', padding + 4) dis_hdr.struct_print(padding + 8) # Print @DIS Module Raw Header Info @@ -341,14 +331,14 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_chk16=False, nal_d for mod_idx in range(dis_hdr.EntryCount): dis_mod = get_struct(dis_data, mod_idx * DIS_MOD_LEN, DisModule) # Parse @DIS Module Raw Entry Structure - print('\n%sDefault Command Status Entry %0.2d/%0.2d:\n' % (padder(padding + 8), mod_idx + 1, dis_hdr.EntryCount)) + printer('Default Command Status Entry %0.2d/%0.2d:\n' % (mod_idx + 1, dis_hdr.EntryCount), padding + 8) dis_mod.struct_print(padding + 12) # Print @DIS Module Raw Entry Info # Store/Save @DIS Module Entry Info in file with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis: with contextlib.redirect_stdout(dis): - print() + printer() dis_mod.struct_print(4) # Store @DIS Module Entry Info os.remove(uaf_fname) # Delete @DIS Module binary, info exported as text @@ -357,13 +347,13 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_chk16=False, nal_d if len(uaf_data_raw) >= 5 and (uaf_tag,uaf_data_raw[0],uaf_data_raw[4]) == ('@NAL',0x40,0x3A): nal_info = uaf_data_raw.decode('utf-8','ignore').replace('\r','').strip().split('\n') - print('\n%s@UAF Module Name List:\n' % padder(padding + 4)) + printer('@UAF Module Name List:\n', padding + 4) # Parse all @NAL Module Entries for info in nal_info: info_tag,info_val = info.split(':',1) - print('%s%s : %s' % (padder(padding + 8), info_tag, info_val)) # Print @NAL Module Tag-Path Info + printer(info_tag + ' : ' + info_val, padding + 8, False) # Print @NAL Module Tag-Path Info nal_dict[info_tag] = os.path.basename(info_val) # Assign a file name (w/o path) to each Tag @@ -371,7 +361,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_chk16=False, nal_d if uaf_tag == '@INS' and is_7z_supported(uaf_fname): ins_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory - print('\n%sInsyde BIOS 7z SFX Archive:' % padder(padding + 4)) + printer('Insyde BIOS 7z SFX Archive:', padding + 4) if a7z_decompress(uaf_fname, ins_dir, '7z SFX', padding + 8) == 0: os.remove(uaf_fname) # Successful extraction, delete @INS Module file/archive @@ -388,8 +378,8 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_chk16=False, nal_d # Detect Intel Engine firmware image and show ME Analyzer advice if uaf_tag.startswith('@ME') and PAT_INTEL_ENG.search(uaf_data_raw): - print('\n%sIntel Management Engine (ME) Firmware:\n' % padder(padding + 4)) - print('%sUse "ME Analyzer" from https://github.com/platomav/MEAnalyzer' % padder(padding + 8)) + printer('Intel Management Engine (ME) Firmware:\n', padding + 4) + printer('Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer', padding + 8, False) # Get best Nested AMI UCP Pattern match based on @UAF Size nested_uaf_off,nested_uaf_bin = get_ami_ucp(uaf_data_raw) @@ -398,7 +388,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_chk16=False, nal_d if nested_uaf_off: uaf_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory - ucp_extract(nested_uaf_bin, uaf_dir, padding + 4, is_chk16) # Call recursively + ucp_extract(nested_uaf_bin, uaf_dir, padding + 4, is_checksum) # Call recursively os.remove(uaf_fname) # Delete raw nested AMI UCP Structure after successful recursion/extraction @@ -468,32 +458,22 @@ UAF_TAG_DICT = { if __name__ == '__main__': # Show script title - show_title(title) + script_title(title) # Set argparse Arguments argparser = argparse_init() argparser.add_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true') arguments = argparser.parse_args() - # Pretty Python exception handler (must be after argparse) - sys.excepthook = nice_exc_handler + is_checksum = arguments.checksum # Set Checksum verification optional argument - # Check Python Version (must be after argparse) - check_sys_py() - - # Check OS Platform (must be after argparse) - check_sys_os() - - # Process input files and generate output path - input_files,output_path = process_input_files(arguments, sys.argv) - - # Initial output padding count - padding = 4 + # Initialize script (must be after argparse) + input_files,output_path,padding = script_init(arguments, 4) for input_file in input_files: input_name = os.path.basename(input_file) - print('\n*** %s' % input_name) + printer(['***', input_name], padding - 4) with open(input_file, 'rb') as in_file: input_buffer = in_file.read() @@ -501,14 +481,14 @@ if __name__ == '__main__': main_uaf_off,main_uaf_bin = get_ami_ucp(input_buffer) if not main_uaf_off: - print('\n%sError: This is not an AMI UCP BIOS executable!' % padder(padding)) + printer('Error: This is not an AMI UCP BIOS executable!', padding) continue # Next input file extract_path = os.path.join(output_path, input_name) - ucp_extract(main_uaf_bin, extract_path, padding, arguments.checksum) + ucp_extract(main_uaf_bin, extract_path, padding, is_checksum) - print('\n%sExtracted AMI UCP BIOS executable!' % padder(padding)) + printer('Extracted AMI UCP BIOS executable!', padding) - print_input('\nDone!') + printer('Done!', pause=True) diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py new file mode 100644 index 0000000..012c84a --- /dev/null +++ b/Dell_PFS_Extract.py @@ -0,0 +1,976 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Dell PFS Extract +Dell PFS Update Extractor +Copyright (C) 2018-2022 Plato Mavropoulos +""" + +title = 'Dell PFS Update Extractor v6.0_a1' + +import os +import io +import sys +import lzma +import zlib +import shutil +import ctypes +import contextlib + +# Skip __pycache__ generation +sys.dont_write_bytecode = True + +from common.checksums import get_chk_8_xor +from common.path_ops import argparse_init, safe_name +from common.patterns import PAT_DELL_HDR, PAT_DELL_FTR, PAT_DELL_PKG +from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t, uint64_t +from common.system import script_init, script_title, printer + +from AMI_PFAT_Extract import IntelBiosGuardHeader, IntelBiosGuardSignature2k, parse_bg_script + +# Dell PFS Header Structure +class DellPfsHeader(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Tag', char*8), # 0x00 + ('HeaderVersion', uint32_t), # 0x08 + ('PayloadSize', uint32_t), # 0x0C + # 0x10 + ] + + def struct_print(self, p): + printer(['Header Tag :', self.Tag.decode('utf-8')], p, False) + printer(['Header Version:', self.HeaderVersion], p, False) + printer(['Payload Size :', '0x%X' % self.PayloadSize], p, False) + +# Dell PFS Footer Structure +class DellPfsFooter(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('PayloadSize', uint32_t), # 0x00 + ('Checksum', uint32_t), # 0x04 ~CRC32 w/ Vector 0 + ('Tag', char*8), # 0x08 + # 0x10 + ] + + def struct_print(self, p): + printer(['Payload Size :', '0x%X' % self.PayloadSize], p, False) + printer(['Payload Checksum:', '0x%0.8X' % self.Checksum], p, False) + printer(['Footer Tag :', self.Tag.decode('utf-8')], p, False) + +# Dell PFS Entry Base Structure +class DellPfsEntryBase(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('GUID', uint32_t*4), # 0x00 Little Endian + ('HeaderVersion', uint32_t), # 0x10 1 or 2 + ('VersionType', uint8_t*4), # 0x14 + ('Version', uint16_t*4), # 0x18 + ('Reserved', uint64_t), # 0x20 + ('DataSize', uint32_t), # 0x28 + ('DataSigSize', uint32_t), # 0x2C + ('DataMetSize', uint32_t), # 0x30 + ('DataMetSigSize', uint32_t), # 0x34 + # 0x38 (parent class, base) + ] + + def struct_print(self, p): + GUID = '%0.*X' % (0x10 * 2, int.from_bytes(self.GUID, 'little')) + Unknown = '%0.*X' % (len(self.Unknown) * 8, int.from_bytes(self.Unknown, 'little')) + Version = get_entry_ver(self.Version, self.VersionType, padding - 4) + + printer(['Entry GUID :', GUID], p, False) + printer(['Entry Version :', self.HeaderVersion], p, False) + printer(['Payload Version :', Version], p, False) + printer(['Reserved :', '0x%X' % self.Reserved], p, False) + printer(['Payload Data Size :', '0x%X' % self.DataSize], p, False) + printer(['Payload Signature Size :', '0x%X' % self.DataSigSize], p, False) + printer(['Metadata Data Size :', '0x%X' % self.DataMetSize], p, False) + printer(['Metadata Signature Size:', '0x%X' % self.DataMetSigSize], p, False) + printer(['Unknown :', '0x%s' % Unknown], p, False) + +# Dell PFS Entry Revision 1 Structure +class DellPfsEntryR1(DellPfsEntryBase): + _pack_ = 1 + _fields_ = [ + ('Unknown', uint32_t*4), # 0x38 + # 0x48 (child class, R1) + ] + +# Dell PFS Entry Revision 2 Structure +class DellPfsEntryR2(DellPfsEntryBase): + _pack_ = 1 + _fields_ = [ + ('Unknown', uint32_t*8), # 0x38 + # 0x58 (child class, R2) + ] + +# Dell PFS Information Header Structure +class DellPfsInfo(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('HeaderVersion', uint32_t), # 0x00 + ('GUID', uint32_t*4), # 0x04 Little Endian + # 0x14 + ] + + def struct_print(self, p): + GUID = '%0.*X' % (0x10 * 2, int.from_bytes(self.GUID, 'little')) + + printer(['Info Version:', self.HeaderVersion], p, False) + printer(['Entry GUID :', GUID], p, False) + +# Dell PFS FileName Header Structure +class DellPfsName(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Version', uint16_t*4), # 0x00 + ('VersionType', uint8_t*4), # 0x08 + ('CharacterCount', uint16_t), # 0x0C UTF-16 2-byte Characters + # 0x0E + ] + + def struct_print(self, p): + Version = get_entry_ver(self.Version, self.VersionType, padding - 4) + + printer(['Payload Version:', Version], p, False) + printer(['Character Count:', self.CharacterCount], p, False) + +# Dell PFS Metadata Header Structure +class DellPfsMetadata(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('ModelIDs', char*501), # 0x000 + ('FileName', char*100), # 0x1F5 + ('FileVersion', char*33), # 0x259 + ('Date', char*33), # 0x27A + ('Brand', char*80), # 0x29B + ('ModelFile', char*80), # 0x2EB + ('ModelName', char*100), # 0x33B + ('ModelVersion', char*33), # 0x39F + # 0x3C0 + ] + + def struct_print(self, p): + printer(['Model IDs :', self.ModelIDs.decode('utf-8').strip(',END')], p, False) + printer(['File Name :', self.FileName.decode('utf-8')], p, False) + printer(['File Version :', self.FileVersion.decode('utf-8')], p, False) + printer(['Date :', self.Date.decode('utf-8')], p, False) + printer(['Brand :', self.Brand.decode('utf-8')], p, False) + printer(['Model File :', self.ModelFile.decode('utf-8')], p, False) + printer(['Model Name :', self.ModelName.decode('utf-8')], p, False) + printer(['Model Version:', self.ModelVersion.decode('utf-8')], p, False) + +# Dell PFS BIOS Guard Metadata Structure +class DellPfsPfatMetadata(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('OffsetTop', uint32_t), # 0x00 + ('Unknown0', uint32_t), # 0x04 + ('OffsetBase', uint32_t), # 0x08 + ('BlockSize', uint32_t), # 0x0C + ('Unknown1', uint32_t), # 0x10 + ('Unknown2', uint32_t), # 0x14 + ('Unknown3', uint8_t), # 0x18 + # 0x19 + ] + + def struct_print(self, p): + printer(['Offset Top :', '0x%X' % self.OffsetTop], p, False) + printer(['Unknown 0 :', '0x%X' % self.Unknown0], p, False) + printer(['Offset Base:', '0x%X' % self.OffsetBase], p, False) + printer(['Block Size :', '0x%X' % self.BlockSize], p, False) + printer(['Unknown 1 :', '0x%X' % self.Unknown1], p, False) + printer(['Unknown 2 :', '0x%X' % self.Unknown2], p, False) + printer(['Unknown 3 :', '0x%X' % self.Unknown3], p, False) + +# The Dell ThinOS PKG update images usually contain multiple sections. +# Each section starts with a 0x30 header, which begins with pattern 72135500. +# The section length is found at 0x10-0x14 and its (optional) MD5 hash at 0x20-0x30. +# Section data can be raw or LZMA2 (7zXZ) compressed. The latter contains the PFS update image. +def is_dell_pfs_pkg(in_buffer): + return PAT_DELL_PKG.search(in_buffer) + +# The Dell PFS update images usually contain multiple sections. +# Each section is zlib-compressed with header pattern ********++EEAA761BECBB20F1E651--789C, +# where ******** is the zlib stream size, ++ is the section type and -- the header Checksum XOR 8. +# The "Firmware" section has type AA and its files are stored in PFS format. +# The "Utility" section has type BB and its files are stored in PFS, BIN or 7z formats. +def is_dell_pfs_hdr(in_buffer): + return list(PAT_DELL_HDR.finditer(in_buffer)) + +# Each section is followed by the footer pattern ********EEAAEE8F491BE8AE143790--, +# where ******** is the zlib stream size and ++ the footer Checksum XOR 8. +def is_dell_pfs_ftr(in_buffer): + return PAT_DELL_FTR.search(in_buffer) + +# Get PFS ZLIB Section Offsets +def get_section_offsets(buffer): + pfs_zlib_init = is_dell_pfs_hdr(buffer) + + if not pfs_zlib_init: return [] # No PFS ZLIB detected + + pfs_zlib_list = [] # Initialize PFS ZLIB offset list + + # Remove duplicate/nested PFS ZLIB offsets + for zlib_c in pfs_zlib_init: + is_duplicate = False # Initialize duplicate/nested PFS ZLIB offset + + for zlib_o in pfs_zlib_init: + zlib_o_size = int.from_bytes(buffer[zlib_o.start() - 0x5:zlib_o.start() - 0x1], 'little') + + # If current PFS ZLIB offset is within another PFS ZLIB range (start-end), set as duplicate + if zlib_o.start() < zlib_c.start() < zlib_o.start() + zlib_o_size: is_duplicate = True + + if not is_duplicate: pfs_zlib_list.append(zlib_c.start()) + + return pfs_zlib_list + +# Dell PFS ZLIB Section Parser +def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padding, is_verbose=True, is_advanced=True): + is_zlib_error = False # Initialize PFS ZLIB-related error state + + section_type = zlib_data[zlib_start - 0x1] # Byte before PFS ZLIB Section pattern is Section Type (e.g. AA, BB) + section_name = {0xAA:'Firmware', 0xBB:'Utilities'}.get(section_type, 'Unknown (%0.2X)' % section_type) + + # Show extraction complete message for each main PFS ZLIB Section + printer('Extracting Dell PFS %d >%s > %s' % (pfs_index, pfs_name, section_name), padding) + + # Set PFS ZLIB Section extraction sub-directory path + section_path = os.path.join(output_path, section_name) + + # Delete existing extraction sub-directory (not in recursions) + if os.path.isdir(section_path) and not is_rec: shutil.rmtree(section_path) + + # Create extraction sub-directory + if not os.path.isdir(section_path): os.makedirs(section_path) + + # Store the compressed zlib stream start offset + compressed_start = zlib_start + 0xB + + # Store the PFS ZLIB section header start offset + header_start = zlib_start - 0x5 + + # Store the PFS ZLIB section header contents (16 bytes) + header_data = zlib_data[header_start:compressed_start] + + # Check if the PFS ZLIB section header Checksum XOR 8 is valid + if get_chk_8_xor(header_data[:0xF]) != header_data[0xF]: + printer('Error: Invalid Dell PFS ZLIB section Header Checksum!', padding) + is_zlib_error = True + + # Store the compressed zlib stream size from the header contents + compressed_size_hdr = int.from_bytes(header_data[:0x4], 'little') + + # Store the compressed zlib stream end offset + compressed_end = compressed_start + compressed_size_hdr + + # Store the compressed zlib stream contents + compressed_data = zlib_data[compressed_start:compressed_end] + + # Check if the compressed zlib stream is complete, based on header + if len(compressed_data) != compressed_size_hdr: + printer('Error: Incomplete Dell PFS ZLIB section data (Header)!', padding) + is_zlib_error = True + + # Store the PFS ZLIB section footer contents (16 bytes) + footer_data = zlib_data[compressed_end:compressed_end + 0x10] + + # Search input section for PFS ZLIB section footer + pfs_zlib_footer_match = is_dell_pfs_ftr(footer_data) + + # Check if PFS ZLIB section footer was found in the section + if not pfs_zlib_footer_match: + printer('Error: This Dell PFS ZLIB section is corrupted!', padding) + is_zlib_error = True + + # Check if the PFS ZLIB section footer Checksum XOR 8 is valid + if get_chk_8_xor(footer_data[:0xF]) != footer_data[0xF]: + printer('Error: Invalid Dell PFS ZLIB section Footer Checksum!', padding) + is_zlib_error = True + + # Store the compressed zlib stream size from the footer contents + compressed_size_ftr = int.from_bytes(footer_data[:0x4], 'little') + + # Check if the compressed zlib stream is complete, based on footer + if compressed_size_ftr != compressed_size_hdr: + printer('Error: Incomplete Dell PFS ZLIB section data (Footer)!', padding) + is_zlib_error = True + + # Decompress PFS ZLIB section payload + try: + assert not is_zlib_error # ZLIB errors are critical + section_data = zlib.decompress(compressed_data) # ZLIB decompression + except: + section_data = zlib_data # Fallback to raw ZLIB data upon critical error + + # Call the PFS Extract function on the decompressed PFS ZLIB Section + pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padding, is_verbose, is_advanced) + +# Parse & Extract Dell PFS Volume +def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, is_verbose=True, is_advanced=True): + # Show PFS Volume indicator + if is_verbose: + printer('PFS Volume:', pfs_padd) + + # Get PFS Header Structure values + pfs_hdr = get_struct(buffer, 0, DellPfsHeader) + + # Validate that a PFS Header was parsed + if pfs_hdr.Tag != b'PFS.HDR.': + printer('Error: PFS Header could not be found!', pfs_padd + 4) + + return # Critical error, abort + + # Show PFS Header Structure info + if is_verbose: + printer('PFS Header:\n', pfs_padd + 4) + pfs_hdr.struct_print(pfs_padd + 8) + + # Validate that a known PFS Header Version was encountered + chk_hdr_ver(pfs_hdr.HeaderVersion, 'PFS', pfs_padd + 8) + + # Get PFS Payload Data + pfs_payload = buffer[PFS_HEAD_LEN:PFS_HEAD_LEN + pfs_hdr.PayloadSize] + + # Parse all PFS Payload Entries/Components + entry_index = 1 # Index number of each PFS Entry + entry_start = 0 # Increasing PFS Entry starting offset + entries_all = [] # Storage for each PFS Entry details + filename_info = [] # Buffer for FileName Information Entry Data + signature_info = [] # Buffer for Signature Information Entry Data + pfs_entry_struct,pfs_entry_size = get_pfs_entry(pfs_payload, entry_start) # Get PFS Entry Info + while len(pfs_payload[entry_start:entry_start + pfs_entry_size]) == pfs_entry_size: + # Analyze PFS Entry Structure and get relevant info + _,entry_version,entry_guid,entry_data,entry_data_sig,entry_met,entry_met_sig,next_entry = \ + parse_pfs_entry(pfs_payload, entry_start, pfs_entry_size, pfs_entry_struct, 'PFS Entry', pfs_padd, is_verbose) + + entry_type = 'OTHER' # Adjusted later if PFS Entry is Zlib, PFAT, PFS Info, Model Info + + # Get PFS Information from the PFS Entry with GUID E0717CE3A9BB25824B9F0DC8FD041960 or B033CB16EC9B45A14055F80E4D583FD3 + if entry_guid in ['E0717CE3A9BB25824B9F0DC8FD041960','B033CB16EC9B45A14055F80E4D583FD3']: + filename_info = entry_data + entry_type = 'NAME_INFO' + + # Get Model Information from the PFS Entry with GUID 6F1D619A22A6CB924FD4DA68233AE3FB + elif entry_guid == '6F1D619A22A6CB924FD4DA68233AE3FB': + entry_type = 'MODEL_INFO' + + # Get Signature Information from the PFS Entry with GUID D086AFEE3ADBAEA94D5CED583C880BB7 + elif entry_guid == 'D086AFEE3ADBAEA94D5CED583C880BB7': + signature_info = entry_data + entry_type = 'SIG_INFO' + + # Get Nested PFS from the PFS Entry with GUID 900FAE60437F3AB14055F456AC9FDA84 + elif entry_guid == '900FAE60437F3AB14055F456AC9FDA84': + entry_type = 'NESTED_PFS' # Nested PFS are usually zlib-compressed so it might change to 'ZLIB' later + + # Store all relevant PFS Entry details + entries_all.append([entry_index, entry_guid, entry_version, entry_type, entry_data, entry_data_sig, entry_met, entry_met_sig]) + + entry_index += 1 # Increase PFS Entry Index number for user-friendly output and name duplicates + entry_start = next_entry # Next PFS Entry starts after PFS Entry Metadata Signature + + # Parse all PFS Information Entries/Descriptors + info_start = 0 # Increasing PFS Information Entry starting offset + info_all = [] # Storage for each PFS Information Entry details + while len(filename_info[info_start:info_start + PFS_INFO_LEN]) == PFS_INFO_LEN: + # Get PFS Information Header Structure info + entry_info_hdr = get_struct(filename_info, info_start, DellPfsInfo) + + # Show PFS Information Header Structure info + if is_verbose: + printer('PFS Information Header:\n', pfs_padd + 4) + entry_info_hdr.struct_print(pfs_padd + 8) + + # Validate that a known PFS Information Header Version was encountered + if entry_info_hdr.HeaderVersion != 1: + printer('Error: Unknown PFS Information Header Version %d!' % entry_info_hdr.HeaderVersion, pfs_padd + 8) + break # Skip PFS Information Entries/Descriptors in case of unknown PFS Information Header Version + + # Get PFS Information Header GUID in Big Endian format to match each Info to the equivalent stored PFS Entry details + entry_guid = '%0.*X' % (0x10 * 2, int.from_bytes(entry_info_hdr.GUID, 'little')) + + # Get PFS FileName Structure values + entry_info_mod = get_struct(filename_info, info_start + PFS_INFO_LEN, DellPfsName) + + # Show PFS FileName Structure info + if is_verbose: + printer('PFS FileName Entry:\n', pfs_padd + 8) + entry_info_mod.struct_print(pfs_padd + 12) + + # The PFS FileName Structure is not complete by itself. The size of the last field (Entry Name) is determined from + # CharacterCount multiplied by 2 due to usage of UTF-16 2-byte Characters. Any Entry Name leading and/or trailing + # space/null characters are stripped and common Windows reserved/illegal filename characters are replaced + name_start = info_start + PFS_INFO_LEN + PFS_NAME_LEN # PFS Entry's FileName start offset + name_size = entry_info_mod.CharacterCount * 2 # PFS Entry's FileName buffer total size + name_data = filename_info[name_start:name_start + name_size] # PFS Entry's FileName buffer + entry_name = safe_name(name_data.decode('utf-16').strip()) # PFS Entry's FileName value + + # Show PFS FileName Name info (padding matches the one from PFS FileName Structure info) + if is_verbose: + printer('Payload Name : %s' % entry_name, pfs_padd + 12, False) + + # Get PFS FileName Version string via "Version" and "VersionType" fields + # PFS FileName Version string must be preferred over PFS Entry's Version + entry_version = get_entry_ver(entry_info_mod.Version, entry_info_mod.VersionType, pfs_padd + 12) + + # Store all relevant PFS FileName details + info_all.append([entry_guid, entry_name, entry_version]) + + # The next PFS Information Header starts after the calculated FileName size + # Two space/null characters seem to always exist after each FileName value + info_start += (PFS_INFO_LEN + PFS_NAME_LEN + name_size + 0x2) + + # Parse Nested PFS Metadata when its PFS Information Entry is missing + for index in range(len(entries_all)): + if entries_all[index][3] == 'NESTED_PFS' and not filename_info: + entry_guid = entries_all[index][1] # Nested PFS Entry GUID in Big Endian format + entry_metadata = entries_all[index][6] # Use Metadata as PFS Information Entry + + # When PFS Information Entry exists, Nested PFS Metadata contains only Model IDs + # When it's missing, the Metadata structure is large and contains equivalent info + if len(entry_metadata) >= PFS_META_LEN: + # Get Nested PFS Metadata Structure values + entry_info = get_struct(entry_metadata, 0, DellPfsMetadata) + + # Show Nested PFS Metadata Structure info + if is_verbose: + printer('PFS Metadata Information:\n', pfs_padd + 4) + entry_info.struct_print(pfs_padd + 8) + + # As Nested PFS Entry Name, we'll use the actual PFS File Name + # Replace common Windows reserved/illegal filename characters + entry_name = safe_name(entry_info.FileName.decode('utf-8').strip('.exe')) + + # As Nested PFS Entry Version, we'll use the actual PFS File Version + entry_version = entry_info.FileVersion.decode('utf-8') + + # Store all relevant Nested PFS Metadata/Information details + info_all.append([entry_guid, entry_name, entry_version]) + + # Re-set Nested PFS Entry Version from Metadata + entries_all[index][2] = entry_version + + # Parse all PFS Signature Entries/Descriptors + sign_start = 0 # Increasing PFS Signature Entry starting offset + while len(signature_info[sign_start:sign_start + PFS_INFO_LEN]) == PFS_INFO_LEN: + # Get PFS Information Header Structure info + entry_info_hdr = get_struct(signature_info, sign_start, DellPfsInfo) + + # Show PFS Information Header Structure info + if is_verbose: + printer('PFS Information Header:\n', pfs_padd + 4) + entry_info_hdr.struct_print(pfs_padd + 8) + + # Validate that a known PFS Information Header Version was encountered + if entry_info_hdr.HeaderVersion != 1: + printer('Error: Unknown PFS Information Header Version %d!' % entry_info_hdr.HeaderVersion, pfs_padd + 8) + break # Skip PFS Signature Entries/Descriptors in case of unknown Header Version + + # PFS Signature Entries/Descriptors have DellPfsInfo + DellPfsEntryR* + Sign Size [0x2] + Sign Data [Sig Size] + pfs_entry_struct, pfs_entry_size = get_pfs_entry(signature_info, sign_start + PFS_INFO_LEN) # Get PFS Entry Info + + # Get PFS Entry Header Structure info + entry_hdr = get_struct(signature_info, sign_start + PFS_INFO_LEN, pfs_entry_struct) + + # Show PFS Information Header Structure info + if is_verbose: + printer('PFS Information Entry:\n', pfs_padd + 8) + entry_hdr.struct_print(pfs_padd + 12) + + # Show PFS Signature Size & Data (after DellPfsEntryR*) + sign_info_start = sign_start + PFS_INFO_LEN + pfs_entry_size + sign_size = int.from_bytes(signature_info[sign_info_start:sign_info_start + 0x2], 'little') + sign_data_raw = signature_info[sign_info_start + 0x2:sign_info_start + 0x2 + sign_size] + sign_data_txt = '%0.*X' % (sign_size * 2, int.from_bytes(sign_data_raw, 'little')) + + if is_verbose: + printer('Signature Information:\n', pfs_padd + 8) + printer('Signature Size: 0x%X' % sign_size, pfs_padd + 12, False) + printer('Signature Data: %s [...]' % sign_data_txt[:32], pfs_padd + 12, False) + + # The next PFS Signature Entry/Descriptor starts after the previous Signature Data + sign_start += (PFS_INFO_LEN + pfs_entry_size + 0x2 + sign_size) + + # Parse each PFS Entry Data for special types (zlib or PFAT) + for index in range(len(entries_all)): + entry_data = entries_all[index][4] # Get PFS Entry Data + entry_type = entries_all[index][3] # Get PFS Entry Type + + # Very small PFS Entry Data cannot be of special type + if len(entry_data) < PFS_HEAD_LEN: continue + + # Check if PFS Entry contains zlib-compressed sub-PFS Volume + pfs_zlib_offsets = get_section_offsets(entry_data) + + # Check if PFS Entry contains sub-PFS Volume with PFAT Payload + is_pfat = False # Initial PFAT state for sub-PFS Entry + _, pfat_entry_size = get_pfs_entry(entry_data, PFS_HEAD_LEN) # Get possible PFS PFAT Entry Size + pfat_hdr_off = PFS_HEAD_LEN + pfat_entry_size # Possible PFAT Header starts after PFS Header & Entry + pfat_entry_hdr = get_struct(entry_data, 0, DellPfsHeader) # Possible PFS PFAT Entry + if len(entry_data) - pfat_hdr_off >= PFAT_HDR_LEN: + pfat_hdr = get_struct(entry_data, pfat_hdr_off, IntelBiosGuardHeader) + is_pfat = pfat_hdr.get_platform_id().upper().startswith('DELL') + + # Parse PFS Entry which contains sub-PFS Volume with PFAT Payload + if pfat_entry_hdr.Tag == b'PFS.HDR.' and is_pfat: + entry_type = 'PFAT' # Re-set PFS Entry Type from OTHER to PFAT, to use such info afterwards + + entry_data = parse_pfat_pfs(pfat_entry_hdr, entry_data, pfs_padd, is_verbose) # Parse sub-PFS PFAT Volume + + # Parse PFS Entry which contains zlib-compressed sub-PFS Volume + elif pfs_zlib_offsets: + entry_type = 'ZLIB' # Re-set PFS Entry Type from OTHER to ZLIB, to use such info afterwards + pfs_count += 1 # Increase the count/index of parsed main PFS structures by one + + # Parse each sub-PFS ZLIB Section + for offset in pfs_zlib_offsets: + # Get the Name of the zlib-compressed full PFS structure via the already stored PFS Information + # The zlib-compressed full PFS structure(s) are used to contain multiple FW (CombineBiosNameX) + # When zlib-compressed full PFS structure(s) exist within the main/first full PFS structure, + # its PFS Information should contain their names (CombineBiosNameX). Since the main/first + # full PFS structure has count/index 1, the rest start at 2+ and thus, their PFS Information + # names can be retrieved in order by subtracting 2 from the main/first PFS Information values + sub_pfs_name = ' %s v%s' % (info_all[pfs_count - 2][1], info_all[pfs_count - 2][2]) if info_all else ' UNKNOWN' + + # Set the sub-PFS output path (create sub-folders for each sub-PFS and its ZLIB sections) + sub_pfs_path = os.path.join(output_path, str(pfs_count) + sub_pfs_name) + + # Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count) + pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4, is_verbose, is_advanced) + + entries_all[index][4] = entry_data # Adjust PFS Entry Data after parsing PFAT (same ZLIB raw data, not stored afterwards) + entries_all[index][3] = entry_type # Adjust PFS Entry Type from OTHER to PFAT or ZLIB (ZLIB is ignored at file extraction) + + # Name & Store each PFS Entry/Component Data, Data Signature, Metadata, Metadata Signature + for entry_index in range(len(entries_all)): + file_index = entries_all[entry_index][0] + file_guid = entries_all[entry_index][1] + file_version = entries_all[entry_index][2] + file_type = entries_all[entry_index][3] + file_data = entries_all[entry_index][4] + file_data_sig = entries_all[entry_index][5] + file_meta = entries_all[entry_index][6] + file_meta_sig = entries_all[entry_index][7] + + # Give Names to special PFS Entries, not covered by PFS Information + if file_type == 'MODEL_INFO': + file_name = 'Model Information' + elif file_type == 'NAME_INFO': + file_name = 'Filename Information' + if not is_advanced: continue # Don't store Filename Information in non-advanced user mode + elif file_type == 'SIG_INFO': + file_name = 'Signature Information' + if not is_advanced: continue # Don't store Signature Information in non-advanced user mode + else: + file_name = '' + + # Most PFS Entry Names & Versions are found at PFS Information via their GUID + # Version can be found at DellPfsEntryR* but prefer PFS Information when possible + for info_index in range(len(info_all)): + info_guid = info_all[info_index][0] + info_name = info_all[info_index][1] + info_version = info_all[info_index][2] + + # Give proper Name & Version info if Entry/Information GUIDs match + if info_guid == file_guid: + file_name = info_name + file_version = info_version + + info_all[info_index][0] = 'USED' # PFS with zlib-compressed sub-PFS use the same GUID + break # Break at 1st Name match to not rename again from next zlib-compressed sub-PFS with the same GUID + + # For both advanced & non-advanced users, the goal is to store final/usable files only + # so empty or intermediate files such as sub-PFS, PFS w/ PFAT or zlib-PFS are skipped + # Main/First PFS CombineBiosNameX Metadata files must be kept for accurate Model Information + # All users should check these files in order to choose the correct CombineBiosNameX modules + write_files = [] # Initialize list of output PFS Entry files to be written/extracted + + is_zlib = bool(file_type == 'ZLIB') # Determine if PFS Entry Data was zlib-compressed + + if file_data and not is_zlib: write_files.append([file_data, 'data']) # PFS Entry Data Payload + if file_data_sig and is_advanced: write_files.append([file_data_sig, 'sign_data']) # PFS Entry Data Signature + if file_meta and (is_zlib or is_advanced): write_files.append([file_meta, 'meta']) # PFS Entry Metadata Payload + if file_meta_sig and is_advanced: write_files.append([file_meta_sig, 'sign_meta']) # PFS Entry Metadata Signature + + # Write/Extract PFS Entry files + for file in write_files: + full_name = '%d%s -- %d %s v%s' % (pfs_index, pfs_name, file_index, file_name, file_version) # Full PFS Entry Name + pfs_file_write(file[0], file[1], file_type, full_name, output_path, pfs_padd, is_verbose, is_advanced) + + # Get PFS Footer Data after PFS Header Payload + pfs_footer = buffer[PFS_HEAD_LEN + pfs_hdr.PayloadSize:PFS_HEAD_LEN + pfs_hdr.PayloadSize + PFS_FOOT_LEN] + + # Analyze PFS Footer Structure + chk_pfs_ftr(pfs_footer, pfs_payload, pfs_hdr.PayloadSize, 'PFS', pfs_padd, is_verbose) + +# Analyze Dell PFS Entry Structure +def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, padding, is_verbose=True): + # Get PFS Entry Structure values + pfs_entry = get_struct(entry_buffer, entry_start, entry_struct) + + # Show PFS Entry Structure info + if is_verbose: + printer('PFS Entry:\n', padding + 4) + pfs_entry.struct_print(padding + 8) + + # Validate that a known PFS Entry Header Version was encountered + chk_hdr_ver(pfs_entry.HeaderVersion, text, padding + 8) + + # Validate that the PFS Entry Reserved field is empty + if pfs_entry.Reserved != 0: + printer('Error: Detected non-empty %s Reserved field!' % text, padding + 8) + + # Get PFS Entry Version string via "Version" and "VersionType" fields + entry_version = get_entry_ver(pfs_entry.Version, pfs_entry.VersionType, padding + 8) + + # Get PFS Entry GUID in Big Endian format + entry_guid = '%0.*X' % (0x10 * 2, int.from_bytes(pfs_entry.GUID, 'little')) + + # PFS Entry Data starts after the PFS Entry Structure + entry_data_start = entry_start + entry_size + entry_data_end = entry_data_start + pfs_entry.DataSize + + # PFS Entry Data Signature starts after PFS Entry Data + entry_data_sig_start = entry_data_end + entry_data_sig_end = entry_data_sig_start + pfs_entry.DataSigSize + + # PFS Entry Metadata starts after PFS Entry Data Signature + entry_met_start = entry_data_sig_end + entry_met_end = entry_met_start + pfs_entry.DataMetSize + + # PFS Entry Metadata Signature starts after PFS Entry Metadata + entry_met_sig_start = entry_met_end + entry_met_sig_end = entry_met_sig_start + pfs_entry.DataMetSigSize + + entry_data = entry_buffer[entry_data_start:entry_data_end] # Store PFS Entry Data + entry_data_sig = entry_buffer[entry_data_sig_start:entry_data_sig_end] # Store PFS Entry Data Signature + entry_met = entry_buffer[entry_met_start:entry_met_end] # Store PFS Entry Metadata + entry_met_sig = entry_buffer[entry_met_sig_start:entry_met_sig_end] # Store PFS Entry Metadata Signature + + return pfs_entry, entry_version, entry_guid, entry_data, entry_data_sig, entry_met, entry_met_sig, entry_met_sig_end + +# Parse Dell PFS Volume with PFAT Payload +def parse_pfat_pfs(entry_hdr, entry_data, padding, is_verbose=True): + # Show PFS Volume indicator + if is_verbose: + printer('PFS Volume:', padding + 4) + + # Show sub-PFS Header Structure Info + if is_verbose: + printer('PFS Header:\n', padding + 8) + entry_hdr.struct_print(padding + 12) + + # Validate that a known sub-PFS Header Version was encountered + chk_hdr_ver(entry_hdr.HeaderVersion, 'sub-PFS', padding + 12) + + # Get sub-PFS Payload Data + pfat_payload = entry_data[PFS_HEAD_LEN:PFS_HEAD_LEN + entry_hdr.PayloadSize] + + # Get sub-PFS Footer Data after sub-PFS Header Payload (must be retrieved at the initial entry_data, before PFAT parsing) + pfat_footer = entry_data[PFS_HEAD_LEN + entry_hdr.PayloadSize:PFS_HEAD_LEN + entry_hdr.PayloadSize + PFS_FOOT_LEN] + + # Parse all sub-PFS Payload PFAT Entries + pfat_data_all = [] # Storage for all sub-PFS PFAT Entries Order/Offset & Payload/Raw Data + pfat_entry_start = 0 # Increasing sub-PFS PFAT Entry start offset + pfat_entry_index = 0 # Increasing sub-PFS PFAT Entry count index + _, pfs_entry_size = get_pfs_entry(pfat_payload, 0) # Get initial PFS PFAT Entry Size for loop + while len(pfat_payload[pfat_entry_start:pfat_entry_start + pfs_entry_size]) == pfs_entry_size: + # Get sub-PFS PFAT Entry Structure & Size info + pfat_entry_struct, pfat_entry_size = get_pfs_entry(pfat_payload, pfat_entry_start) + + # Analyze sub-PFS PFAT Entry Structure and get relevant info + pfat_entry,_,_,pfat_entry_data,_,pfat_entry_met,_,pfat_next_entry = parse_pfs_entry(pfat_payload, + pfat_entry_start, pfat_entry_size, pfat_entry_struct, 'sub-PFS PFAT Entry', padding + 4, is_verbose) + + # Each sub-PFS PFAT Entry includes an AMI BIOS Guard (a.k.a. PFAT) block at the beginning + # We need to parse the PFAT block and remove its contents from the final Payload/Raw Data + pfat_hdr_off = pfat_entry_start + pfat_entry_size # PFAT block starts after PFS Entry + + # Get sub-PFS PFAT Header Structure values + pfat_hdr = get_struct(pfat_payload, pfat_hdr_off, IntelBiosGuardHeader) + + # Show sub-PFS PFAT Header Structure info + if is_verbose: + printer('PFAT Block %d Header:\n' % pfat_entry_index, padding + 12) + pfat_hdr.struct_print(padding + 16) + + pfat_script_start = pfat_hdr_off + PFAT_HDR_LEN # PFAT Block Script Start + pfat_script_end = pfat_script_start + pfat_hdr.ScriptSize # PFAT Block Script End + pfat_script_data = pfat_payload[pfat_script_start:pfat_script_end] # PFAT Block Script Data + pfat_payload_start = pfat_script_end # PFAT Block Payload Start (at Script end) + pfat_payload_end = pfat_script_end + pfat_hdr.DataSize # PFAT Block Data End + pfat_payload_data = pfat_payload[pfat_payload_start:pfat_payload_end] # PFAT Block Raw Data + pfat_hdr_bgs_size = PFAT_HDR_LEN + pfat_hdr.ScriptSize # PFAT Block Header & Script Size + + # The PFAT Script End should match the total Entry Data Size w/o PFAT block + if pfat_hdr_bgs_size != pfat_entry.DataSize - pfat_hdr.DataSize: + printer('Error: Detected sub-PFS PFAT Entry Header & PFAT Size mismatch!', padding + 16) + + # Get PFAT Header Flags (SFAM, ProtectEC, GFXMitDis, FTU, Reserved) + is_sfam,_,_,_,_ = pfat_hdr.get_flags() + + # Parse sub-PFS PFAT Signature, if applicable (only when PFAT Header > SFAM flag is set) + if is_sfam and len(pfat_payload[pfat_payload_end:pfat_payload_end + PFAT_SIG_LEN]) == PFAT_SIG_LEN: + # Get sub-PFS PFAT Signature Structure values + pfat_sig = get_struct(pfat_payload, pfat_payload_end, IntelBiosGuardSignature2k) + + # Show sub-PFS PFAT Signature Structure info + if is_verbose: + printer('PFAT Block %d Signature:\n' % pfat_entry_index, padding + 12) + pfat_sig.struct_print(padding + 16) + + # Show PFAT Script via BIOS Guard Script Tool + if is_verbose: + printer('PFAT Block %d Script:\n' % pfat_entry_index, padding + 12) + + # https://github.com/allowitsme/big-tool by Dmitry Frolov + _ = parse_bg_script(pfat_script_data, padding + 16) + + # The payload of sub-PFS PFAT Entries is not in proper order by default + # We can get each payload's order from PFAT Script > OpCode #2 (set I0 imm) + # PFAT Script OpCode #2 > Operand #3 stores the payload Offset in final image + pfat_entry_off = int.from_bytes(pfat_script_data[0xC:0x10], 'little') + + # Parse sub-PFS PFAT Entry/Block Metadata + if len(pfat_entry_met) >= PFS_PFAT_LEN: + # Get sub-PFS PFAT Metadata Structure values + pfat_met = get_struct(pfat_entry_met, 0, DellPfsPfatMetadata) + + # Show sub-PFS PFAT Metadata Structure info + if is_verbose: + printer('PFAT Block %d Metadata:\n' % pfat_entry_index, padding + 12) + pfat_met.struct_print(padding + 16) + + # Another way to get each PFAT Entry payload's Order is from its Metadata at 0x8-0xC, if applicable + # Check that the PFAT Entry payload Order/Offset from PFAT Script matches the one from PFAT Metadata + if pfat_entry_off != pfat_met.OffsetBase: + printer('Error: Detected sub-PFS PFAT Entry Metadata & PFAT Base Offset mismatch!', padding + 16) + pfat_entry_off = pfat_met.OffsetBase # Prefer Offset from Metadata, in case PFAT Script differs + + # Check that the PFAT Entry payload Size from PFAT Header matches the one from PFAT Metadata + if pfat_hdr.DataSize != pfat_met.BlockSize: + printer('Error: Detected sub-PFS PFAT Entry Metadata & PFAT Block Size mismatch!', padding + 16) + + # Get sub-PFS Entry Raw Data by subtracting PFAT Header & Script from PFAT Entry Data + pfat_entry_data_raw = pfat_entry_data[pfat_hdr_bgs_size:] + + # The sub-PFS Entry Raw Data (w/o PFAT Header & Script) should match with the PFAT Block payload + if pfat_entry_data_raw != pfat_payload_data: + printer('Error: Detected sub-PFS PFAT Entry w/o PFAT & PFAT Block Data mismatch!', padding + 16) + pfat_entry_data_raw = pfat_payload_data # Prefer Data from PFAT Block, in case PFAT Entry differs + + # Store each sub-PFS PFAT Entry Order/Offset and Payload/Raw Data (w/o PFAT) + pfat_data_all.append((pfat_entry_off, pfat_entry_data_raw)) + + pfat_entry_start = pfat_next_entry # Next sub-PFS PFAT Entry starts after sub-PFS Entry Metadata Signature + + pfat_entry_index += 1 + + pfat_data_all.sort() # Sort all sub-PFS PFAT Entries payloads/data based on their Order/Offset + + entry_data = b'' # Initialize new sub-PFS Entry Data + for pfat_data in pfat_data_all: entry_data += pfat_data[1] # Merge all sub-PFS PFAT Entry Payload/Raw into the final sub-PFS Entry Data + + # Verify that the Order/Offset of the last PFAT Entry w/ its Size matches the final sub-PFS Entry Data Size + if len(entry_data) != pfat_data_all[-1][0] + len(pfat_data_all[-1][1]): + printer('Error: Detected sub-PFS PFAT Entry Buffer & Last Offset Size mismatch!', padding + 8) + + # Analyze sub-PFS Footer Structure + chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padding + 4, is_verbose) + + return entry_data + +# Get Dell PFS Entry Structure & Size via its Version +def get_pfs_entry(buffer, offset): + pfs_entry_ver = int.from_bytes(buffer[offset + 0x10:offset + 0x14], 'little') # PFS Entry Version + + if pfs_entry_ver == 1: return DellPfsEntryR1, ctypes.sizeof(DellPfsEntryR1) + if pfs_entry_ver == 2: return DellPfsEntryR2, ctypes.sizeof(DellPfsEntryR2) + + return DellPfsEntryR2, ctypes.sizeof(DellPfsEntryR2) + +# Determine Dell PFS Entry Version string +def get_entry_ver(version_fields, version_types, msg_padd): + version = '' # Initialize Version string + + # Each Version Type (1 byte) determines the type of each Version Value (2 bytes) + # Version Type 'N' is Number, 'A' is Text and ' ' is Empty/Unused + for idx in range(len(version_fields)): + eol = '' if idx == len(version_fields) - 1 else '.' + + if version_types[idx] == 65: version += '%X%s' % (version_fields[idx], eol) # 0x41 = ASCII + elif version_types[idx] == 78: version += '%d%s' % (version_fields[idx], eol) # 0x4E = Number + elif version_types[idx] in (0, 32): version = version.strip('.') # 0x00 or 0x20 = Unused + else: + version += '%X%s' % (version_fields[idx], eol) # Unknown + printer('Error: Unknown PFS Entry Version Type 0x%0.2X!' % version_types[idx], msg_padd) + + return version + +# Check if Dell PFS Header Version is known +def chk_hdr_ver(version, text, padding): + if version in (1,2): return + + printer('Error: Unknown %s Header Version %d!' % (text, version), padding) + +# Analyze Dell PFS Footer Structure +def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_verbose=True): + # Get PFS Footer Structure values + pfs_ftr = get_struct(footer_buffer, 0, DellPfsFooter) + + # Validate that a PFS Footer was parsed + if pfs_ftr.Tag == b'PFS.FTR.': + # Show PFS Footer Structure info + if is_verbose: + printer('PFS Footer:\n', padding + 4) + pfs_ftr.struct_print(padding + 8) + else: + printer('Error: %s Footer could not be found!' % text, padding + 4) + + # Validate that PFS Header Payload Size matches the one at PFS Footer + if data_size != pfs_ftr.PayloadSize: + printer('Error: %s Header & Footer Payload Size mismatch!' % text, padding + 4) + + # Calculate the PFS Payload Data CRC-32 w/ Vector 0 + pfs_ftr_crc = ~zlib.crc32(data_buffer, 0) & 0xFFFFFFFF + + # Validate PFS Payload Data Checksum via PFS Footer + if pfs_ftr.Checksum != pfs_ftr_crc: + printer('Error: Invalid %s Footer Payload Checksum!' % text, padding + 4) + +# Write/Extract Dell PFS Entry Files (Data, Metadata, Signature) +def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, is_verbose=True, is_advanced=True): + # Store Data/Metadata Signature (advanced users only) + if bin_name.startswith('sign'): + final_name = '%s.%s.sig' % (safe_name(full_name), bin_name.split('_')[1]) + final_path = os.path.join(out_path, final_name) + + with open(final_path, 'wb') as pfs_out: pfs_out.write(bin_buff) # Write final Data/Metadata Signature + + return # Skip further processing for Signatures + + # Store Data/Metadata Payload + bin_ext = '.%s.bin' % bin_name if is_advanced else '.bin' # Simpler Data/Metadata Extension for non-advanced users + + # Some Data may be Text or XML files with useful information for non-advanced users + is_text,final_data,file_ext,write_mode = bin_is_text(bin_buff, bin_type, bin_name == 'meta', padding, is_verbose, is_advanced) + + final_name = '%s%s' % (safe_name(full_name), bin_ext[:-4] + file_ext if is_text else bin_ext) + final_path = os.path.join(out_path, final_name) + + with open(final_path, write_mode) as pfs_out: pfs_out.write(final_data) # Write final Data/Metadata Payload + +# Check if Dell PFS Entry file/data is Text/XML and Convert +def bin_is_text(buffer, file_type, is_metadata, pfs_padd, is_verbose=True, is_advanced=True): + is_text = False + write_mode = 'wb' + extension = '.bin' + buffer_in = buffer + + if b',END' in buffer[-0x8:]: # Text Type 1 + is_text = True + write_mode = 'w' + extension = '.txt' + buffer = buffer.decode('utf-8').split(',END')[0].replace(';','\n') + elif buffer.startswith(b'VendorName=Dell'): # Text Type 2 + is_text = True + write_mode = 'w' + extension = '.txt' + buffer = buffer.split(b'\x00')[0].decode('utf-8').replace(';','\n') + elif b'BIOS Utilities Donation via Paypal or Debit/Credit Card +* [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) * [**AMI UCP BIOS Extractor**](#ami-ucp-bios-extractor) * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) +## **Dell PFS Update Extractor** + +![]() + +#### **Description** + +Parses Dell PFS Update images and extracts their Firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and Utilities (e.g. Flasher etc) component sections. It supports all Dell PFS revisions and formats, including those which are originally LZMA compressed in ThinOS packages, ZLIB compressed or Intel BIOS Guard (PFAT) protected. The output comprises only final firmware components which are directly usable by end users. + +#### **Usage** + +You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional arguments: + +* -h or --help : show help message and exit +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts +* -a or --advanced : extract signatures and metadata +* -v or --verbose : show PFS structure information + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. + +#### **Prerequisites** + +Optionally, to decompile the Intel BIOS Guard (PFAT) Scripts, you must have the following 3rd party utility at the "external" project directory: + +* [BIOS Guard Script Tool](https://github.com/allowitsme/big-tool/tree/sdk-compat) (i.e. big_script_tool.py) + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.7.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Place any appropriate prerequisite at the project directory: + +> BIOS Guard Script Tool + +4. Build/Freeze/Compile: + +> pyinstaller --noupx --onefile \\/Dell_PFS_Extract.py + +You should find the final utility executable at "dist" folder + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + ## **AMI UCP BIOS Extractor** ![]() @@ -28,7 +88,7 @@ You can either Drag & Drop or manually enter AMI UCP BIOS executable file(s). Op #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. +Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. #### **Prerequisites** @@ -61,7 +121,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform > pyinstaller --noupx --onefile \\/AMI_UCP_Extract.py -At dist folder you should find the final utility executable +You should find the final utility executable at "dist" folder #### **Anti-Virus False Positives** @@ -92,7 +152,7 @@ You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s) #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries. +Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. #### **Prerequisites** @@ -120,7 +180,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform > pyinstaller --noupx --onefile \\/AMI_PFAT_Extract.py -At dist folder you should find the final utility executable +You should find the final utility executable at "dist" folder #### **Anti-Virus False Positives** diff --git a/common/a7z_comp.py b/common/a7z_comp.py index 41aa476..7e6364e 100644 --- a/common/a7z_comp.py +++ b/common/a7z_comp.py @@ -4,9 +4,9 @@ import os import subprocess -from common.script_get import get_script_dir +from common.path_ops import get_script_dir from common.system import get_os_ver -from common.text_ops import padder +from common.system import printer # Get 7z path def get_7z_path(static=False): @@ -36,10 +36,10 @@ def a7z_decompress(in_path, out_path, in_name, padding, static=False): if not os.path.isdir(out_path): raise Exception('EXTRACT_DIR_MISSING') except: - print('\n%sError: 7-Zip could not extract %s file %s!' % (padder(padding), in_name, in_path)) + printer('Error: 7-Zip could not extract %s file %s!' % (in_name, in_path), padding) return 1 - print('\n%sSuccesfull %s decompression via 7-Zip!' % (padder(padding), in_name)) + printer('Succesfull %s decompression via 7-Zip!' % in_name, padding) return 0 \ No newline at end of file diff --git a/common/checksums.py b/common/checksums.py index c3c7385..02b6a74 100644 --- a/common/checksums.py +++ b/common/checksums.py @@ -2,12 +2,20 @@ #coding=utf-8 # Get Checksum 16-bit -def checksum16(data): - chk16 = 0 - +def get_chk_16(data, value=0, order='little'): for idx in range(0, len(data), 2): - chk16 += int.from_bytes(data[idx:idx + 2], 'little') + # noinspection PyTypeChecker + value += int.from_bytes(data[idx:idx + 2], order) - chk16 &= 0xFFFF + value &= 0xFFFF - return chk16 \ No newline at end of file + return value + +# Get Checksum 8-bit XOR +def get_chk_8_xor(data, value=0): + for byte in data: + value ^= byte + + value ^= 0x0 + + return value \ No newline at end of file diff --git a/common/efi_comp.py b/common/efi_comp.py index 6d3bb42..853f15d 100644 --- a/common/efi_comp.py +++ b/common/efi_comp.py @@ -4,9 +4,9 @@ import os import subprocess -from common.script_get import get_script_dir +from common.path_ops import get_script_dir from common.system import get_os_ver -from common.text_ops import padder +from common.system import printer def get_compress_sizes(data): size_compress = int.from_bytes(data[0x0:0x4], 'little') @@ -42,10 +42,10 @@ def efi_decompress(in_path, out_path, padding, comp_type='--uefi'): if os.path.getsize(out_path) != size_orig: raise Exception('EFI_DECOMPRESS_ERROR') except: - print('\n%sError: TianoCompress could not extract file %s!' % (padder(padding), in_path)) + printer('Error: TianoCompress could not extract file %s!' % in_path, padding) return 1 - print('\n%sSuccesfull EFI/Tiano decompression via TianoCompress!' % padder(padding)) + printer('Succesfull EFI/Tiano decompression via TianoCompress!', padding) return 0 \ No newline at end of file diff --git a/common/path_ops.py b/common/path_ops.py index a86e4a5..c6e0709 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -3,11 +3,11 @@ import os import re +import sys +import inspect import argparse from pathlib import Path -from common.script_get import get_script_dir - # Fix illegal/reserved Windows characters def safe_name(in_name): raw_name = repr(in_name).strip("'") @@ -76,4 +76,16 @@ def process_input_files(argparse_args, sys_argv=None): output_path = get_absolute_path(input('\nEnter output directory path: ')) - return input_files, output_path \ No newline at end of file + return input_files, output_path + +# https://stackoverflow.com/a/22881871 by jfs +def get_script_dir(follow_symlinks=True): + if getattr(sys, 'frozen', False): + path = os.path.abspath(sys.executable) + else: + path = inspect.getabsfile(get_script_dir) + + if follow_symlinks: + path = os.path.realpath(path) + + return os.path.dirname(path) \ No newline at end of file diff --git a/common/patterns.py b/common/patterns.py index 60bc0bd..0f7d81e 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -5,4 +5,7 @@ import re PAT_AMI_PFAT = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) PAT_AMI_UCP = re.compile(br'\x40\x55\x41\x46.{12}\x40', re.DOTALL) +PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}\x37\x7A\x58\x5A', re.DOTALL) +PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) +PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) \ No newline at end of file diff --git a/common/script_get.py b/common/script_get.py deleted file mode 100644 index a09b896..0000000 --- a/common/script_get.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python3 -#coding=utf-8 - -# https://stackoverflow.com/a/22881871 by jfs - -import os -import sys -import inspect - -def get_script_dir(follow_symlinks=True): - if getattr(sys, 'frozen', False): - path = os.path.abspath(sys.executable) - else: - path = inspect.getabsfile(get_script_dir) - - if follow_symlinks: - path = os.path.realpath(path) - - return os.path.dirname(path) \ No newline at end of file diff --git a/common/system.py b/common/system.py index 33412fc..68d3ce7 100644 --- a/common/system.py +++ b/common/system.py @@ -5,6 +5,9 @@ import sys import ctypes import traceback +from common.text_ops import padder +from common.path_ops import process_input_files + # Get Python Version (tuple) def get_py_ver(): return sys.version_info @@ -20,7 +23,7 @@ def get_os_ver(): # Check for --auto-exit|-e def is_auto_exit(): - return '--auto-exit' in sys.argv or '-e' in sys.argv + return bool('--auto-exit' in sys.argv or '-e' in sys.argv) # Check Python Version def check_sys_py(): @@ -40,7 +43,7 @@ def check_sys_os(): os_tag,os_win,os_sup = get_os_ver() if not os_sup: - print('\nError: Unsupported platform "%s"!' % os_tag) + printer('Error: Unsupported platform "%s"!' % os_tag) if not is_auto_exit(): input('\nPress enter to exit') @@ -51,8 +54,8 @@ def check_sys_os(): if os_win: sys.stdout.reconfigure(encoding='utf-8') # Show Script Title -def show_title(title): - print('\n' + title) +def script_title(title): + printer(title) _,os_win,_ = get_os_ver() @@ -60,12 +63,28 @@ def show_title(title): if os_win: ctypes.windll.kernel32.SetConsoleTitleW(title) else: sys.stdout.write('\x1b]2;' + title + '\x07') +# Initialize Script +def script_init(arguments, padding=0): + # Pretty Python exception handler (must be after argparse) + sys.excepthook = nice_exc_handler + + # Check Python Version (must be after argparse) + check_sys_py() + + # Check OS Platform (must be after argparse) + check_sys_os() + + # Process input files and generate output path + input_files,output_path = process_input_files(arguments, sys.argv) + + return input_files, output_path, padding + # https://stackoverflow.com/a/781074 by Torsten Marek def nice_exc_handler(exc_type, exc_value, tb): if exc_type is KeyboardInterrupt: - print('\n') + printer('') else: - print('\nError: Script crashed, please report the following:\n') + printer('Error: Script crashed, please report the following:\n') traceback.print_exception(exc_type, exc_value, tb) @@ -74,6 +93,17 @@ def nice_exc_handler(exc_type, exc_value, tb): sys.exit(3) -# Print or Input Message based on --auto-exit|-e -def print_input(msg): - (print if is_auto_exit() else input)(msg) \ No newline at end of file +# Show message(s) while controlling padding, newline, pausing & separator +def printer(in_message='', padd_count=0, new_line=True, pause=False, sep_char=' '): + if type(in_message).__name__ in ('list','tuple'): + message = sep_char.join(map(str, in_message)) + else: + message = str(in_message) + + padding = padder(padd_count) + + newline = '\n' if new_line else '' + + output = newline + padding + message + + (input if pause and not is_auto_exit() else print)(output) \ No newline at end of file diff --git a/common/text_ops.py b/common/text_ops.py index b6ada83..adc9e6e 100644 --- a/common/text_ops.py +++ b/common/text_ops.py @@ -2,5 +2,5 @@ #coding=utf-8 # Generate padding (spaces or tabs) -def padder(count, tab=False): - return ('\t' if tab else ' ') * count \ No newline at end of file +def padder(padd_count, tab=False): + return ('\t' if tab else ' ') * padd_count \ No newline at end of file From 2a98460d71b4277bf9753084d9d864b67c5097c6 Mon Sep 17 00:00:00 2001 From: platomav Date: Sat, 9 Apr 2022 22:22:25 +0300 Subject: [PATCH 09/52] AMI UCP BIOS Extractor v2.0_a3 Added support for HP-modded AMI UCP (HP Flash Utility v4) --- AMI_UCP_Extract.py | 211 +++++++++++++++++++++++++-------------------- common/patterns.py | 2 +- 2 files changed, 117 insertions(+), 96 deletions(-) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index ccee9d6..b35616b 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,9 +7,10 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -title = 'AMI UCP BIOS Extractor v2.0_a2' +title = 'AMI UCP BIOS Extractor v2.0_a3' import os +import re import sys import shutil import struct @@ -37,17 +38,28 @@ class UafHeader(ctypes.LittleEndianStructure): ('Checksum', uint16_t), # 0x08 ('Unknown0', uint8_t), # 0x0A ('Unknown1', uint8_t), # 0x0A - ('Reserved', uint32_t), # 0x0C + ('Reserved', uint8_t*4), # 0x0C # 0x10 ] + def _get_reserved(self): + res_bytes = bytes(self.Reserved) + + res_str = re.sub(r'[\n\t\r\x00 ]', '', res_bytes.decode('utf-8','ignore')) + + res_hex = '0x%0.*X' % (0x4 * 2, int.from_bytes(res_bytes, 'big')) + + res_out = res_hex + (' (%s)' % res_str if len(res_str) else '') + + return res_out + def struct_print(self, p): printer(['Tag :', self.ModuleTag.decode('utf-8')], p, False) printer(['Size :', '0x%X' % self.ModuleSize], p, False) printer(['Checksum :', '0x%0.4X' % self.Checksum], p, False) printer(['Unknown 0 :', '0x%0.2X' % self.Unknown0], p, False) printer(['Unknown 1 :', '0x%0.2X' % self.Unknown1], p, False) - printer(['Reserved :', '0x%0.8X' % self.Reserved], p, False) + printer(['Reserved :', self._get_reserved()], p, False) class UafModule(ctypes.LittleEndianStructure): _pack_ = 1 @@ -57,10 +69,11 @@ class UafModule(ctypes.LittleEndianStructure): # 0x08 ] - def struct_print(self, p, filename): + def struct_print(self, p, filename, description): printer(['Compress Size:', '0x%X' % self.CompressSize], p, False) printer(['Original Size:', '0x%X' % self.OriginalSize], p, False) - printer(['File Name :', filename], p, False) + printer(['Filename :', filename], p, False) + printer(['Description :', description], p, False) class UiiHeader(ctypes.LittleEndianStructure): _pack_ = 1 @@ -139,7 +152,7 @@ class DisModule(ctypes.LittleEndianStructure): printer(['Command :', self.Command.decode('utf-8').strip()], p, False) printer(['Description:', self.Description.decode('utf-8').strip()], p, False) -# Validate @UAF Module Checksum-16 +# Validate UCP Module Checksum-16 def chk16_validate(data, tag, padd=0): if get_chk_16(data) != 0: printer('Error: Invalid UCP Module %s Checksum!' % tag, padd, pause=True) @@ -148,9 +161,10 @@ def chk16_validate(data, tag, padd=0): # Get all input file AMI UCP patterns def get_ami_ucp(buffer): - uaf_len_max = 0x0 # Length of largest detected @UAF - uaf_hdr_off = 0x0 # Offset of largest detected @UAF - uaf_buf_bin = b'' # Buffer of largest detected @UAF + uaf_len_max = 0x0 # Length of largest detected @UAF|@HPU + uaf_hdr_off = 0x0 # Offset of largest detected @UAF|@HPU + uaf_buf_bin = b'' # Buffer of largest detected @UAF|@HPU + uaf_buf_tag = '@UAF' # Tag of largest detected @UAF|@HPU for uaf in PAT_AMI_UCP.finditer(buffer): uaf_len_cur = int.from_bytes(buffer[uaf.start() + 0x4:uaf.start() + 0x8], 'little') @@ -159,85 +173,89 @@ def get_ami_ucp(buffer): uaf_len_max = uaf_len_cur uaf_hdr_off = uaf.start() uaf_buf_bin = buffer[uaf_hdr_off:uaf_hdr_off + uaf_len_max] + uaf_buf_tag = uaf.group(0)[:4].decode('utf-8','ignore') - return uaf_hdr_off, uaf_buf_bin + return uaf_hdr_off, uaf_buf_bin, uaf_buf_tag -# Get list of @UAF Modules +# Get list of @UAF|@HPU Modules def get_uaf_mod(buffer, uaf_off=0x0): - uaf_all = [] # Initialize list of all @UAF Modules + uaf_all = [] # Initialize list of all @UAF|@HPU Modules while buffer[uaf_off] == 0x40: # ASCII of @ is 0x40 - uaf_hdr = get_struct(buffer, uaf_off, UafHeader) # Parse @UAF Module Structure + uaf_hdr = get_struct(buffer, uaf_off, UafHeader) # Parse @UAF|@HPU Module Structure - uaf_tag = uaf_hdr.ModuleTag.decode('utf-8') # Get unique @UAF Module Tag + uaf_tag = uaf_hdr.ModuleTag.decode('utf-8') # Get unique @UAF|@HPU Module Tag - uaf_all.append([uaf_tag, uaf_off, uaf_hdr]) # Store @UAF Module Info + uaf_all.append([uaf_tag, uaf_off, uaf_hdr]) # Store @UAF|@HPU Module Info - uaf_off += uaf_hdr.ModuleSize # Adjust to next @UAF Module offset + uaf_off += uaf_hdr.ModuleSize # Adjust to next @UAF|@HPU Module offset if uaf_off >= len(buffer): break # Stop parsing at EOF - # Check if @UAF Module NAL exists and place it first - # Parsing NAL first allows naming all @UAF Modules + # Check if @UAF|@HPU Module @NAL exists and place it first + # Parsing @NAL first allows naming all @UAF|@HPU Modules for mod_idx,mod_val in enumerate(uaf_all): if mod_val[0] == '@NAL': uaf_all.insert(1, uaf_all.pop(mod_idx)) # After UII for visual purposes - break # NAL found, skip the rest + break # @NAL found, skip the rest return uaf_all # Parse & Extract AMI UCP structures -def ucp_extract(buffer, output_path, padding=0, is_checksum=False): +def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False): nal_dict = {} # Initialize @NAL Dictionary per UCP printer('Utility Configuration Program', padding) - extract_path = os.path.join(output_path + '_extracted', '') + extract_path = os.path.join(out_path + '_extracted', '') if os.path.isdir(extract_path): shutil.rmtree(extract_path) os.mkdir(extract_path) - uaf_hdr = get_struct(buffer, 0, UafHeader) # Parse @UAF Header Structure + uaf_hdr = get_struct(buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure - printer('Utility Auxiliary File > @UAF:\n', padding + 4) + printer('Utility Auxiliary File > %s:\n' % ucp_tag, padding + 4) uaf_hdr.struct_print(padding + 8) fake = struct.pack(' @UAF Module/Section +# Parse & Extract AMI UCP > @UAF|@HPU Module/Section def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, nal_dict=None): if nal_dict is None: nal_dict = {} uaf_tag,uaf_off,uaf_hdr = mod_info - uaf_data_all = buffer[uaf_off:uaf_off + uaf_hdr.ModuleSize] # @UAF Module Entire Data + uaf_data_all = buffer[uaf_off:uaf_off + uaf_hdr.ModuleSize] # @UAF|@HPU Module Entire Data - uaf_data_mod = uaf_data_all[UAF_HDR_LEN:] # @UAF Module EFI Data + uaf_data_mod = uaf_data_all[UAF_HDR_LEN:] # @UAF|@HPU Module EFI Data - uaf_data_raw = uaf_data_mod[UAF_MOD_LEN:] # @UAF Module Raw Data + uaf_data_raw = uaf_data_mod[UAF_MOD_LEN:] # @UAF|@HPU Module Raw Data printer('Utility Auxiliary File > %s:\n' % uaf_tag, padding) - uaf_hdr.struct_print(padding + 4) # Print @UAF Module Info + uaf_hdr.struct_print(padding + 4) # Print @UAF|@HPU Module Info uaf_mod = get_struct(buffer, uaf_off + UAF_HDR_LEN, UafModule) # Parse UAF Module EFI Structure - is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect @UAF Module EFI Compression + is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect @UAF|@HPU Module EFI Compression - if uaf_tag in nal_dict: uaf_name = nal_dict[uaf_tag] # Always prefer NAL naming first + if uaf_tag in nal_dict: uaf_name = nal_dict[uaf_tag] # Always prefer @NAL naming first elif uaf_tag in UAF_TAG_DICT: uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming elif uaf_tag == '@ROM': uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature) elif uaf_tag.startswith('@R0'): uaf_name = 'BIOS_0%s.bin' % uaf_tag[3:] # BIOS/PFAT Firmware @@ -246,22 +264,24 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na elif uaf_tag.startswith('@DS'): uaf_name = 'DROM_0%s.sig' % uaf_tag[3:] # Thunderbolt Retimer Signature elif uaf_tag.startswith('@EC'): uaf_name = 'EC_0%s.bin' % uaf_tag[3:] # Embedded Controller Firmware elif uaf_tag.startswith('@ME'): uaf_name = 'ME_0%s.bin' % uaf_tag[3:] # Management Engine Firmware - else: uaf_name = uaf_tag # Could not name the @UAF Module, use Tag instead + else: uaf_name = uaf_tag # Could not name the @UAF|@HPU Module, use Tag instead uaf_fext = '' if uaf_name != uaf_tag else '.bin' - uaf_mod.struct_print(padding + 4, uaf_name + uaf_fext) # Print @UAF Module EFI Info + uaf_fdesc = UAF_TAG_DICT[uaf_tag][1] if uaf_tag in UAF_TAG_DICT else uaf_name - # Check if unknown @UAF Module Tag is present in NAL but not in built-in dictionary + uaf_mod.struct_print(padding + 4, uaf_name + uaf_fext, uaf_fdesc) # Print @UAF|@HPU Module EFI Info + + # Check if unknown @UAF|@HPU Module Tag is present in @NAL but not in built-in dictionary if uaf_tag in nal_dict and uaf_tag not in UAF_TAG_DICT and not uaf_tag.startswith(('@ROM','@R0','@S0','@DR','@DS')): - printer('Note: Detected new AMI UCP Module %s (%s) in NAL!' % (uaf_tag, nal_dict[uaf_tag]), padding, pause=True) + printer('Note: Detected new AMI UCP Module %s (%s) in @NAL!' % (uaf_tag, nal_dict[uaf_tag]), padding + 4, pause=True) - # Generate @UAF Module File name, depending on whether decompression will be required + # Generate @UAF|@HPU Module File name, depending on whether decompression will be required uaf_fname = os.path.join(extract_path, safe_name(uaf_name + ('.temp' if is_comp else uaf_fext))) if is_checksum: chk16_validate(uaf_data_all, uaf_tag, padding + 4) - # Parse Utility Identification Information @UAF Module (@UII) + # Parse Utility Identification Information @UAF|@HPU Module (@UII) if uaf_tag == '@UII': info_hdr = get_struct(uaf_data_raw, 0, UiiHeader) # Parse @UII Module Raw Structure @@ -281,38 +301,38 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na with contextlib.redirect_stdout(uii_out): info_hdr.struct_print(0, info_desc) # Store @UII Module Info - # Adjust @UAF Module Raw Data for extraction + # Adjust @UAF|@HPU Module Raw Data for extraction if is_comp: - # Some Compressed @UAF Module EFI data lack necessary EOF padding + # Some Compressed @UAF|@HPU Module EFI data lack necessary EOF padding if uaf_mod.CompressSize > len(uaf_data_raw): comp_padd = b'\x00' * (uaf_mod.CompressSize - len(uaf_data_raw)) uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw + comp_padd # Add missing padding for decompression else: uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw # Add the EFI/Tiano Compression info before Raw Data else: - uaf_data_raw = uaf_data_raw[:uaf_mod.OriginalSize] # No compression, extend to end of Original @UAF Module size + uaf_data_raw = uaf_data_raw[:uaf_mod.OriginalSize] # No compression, extend to end of Original @UAF|@HPU Module size - # Store/Save @UAF Module file + # Store/Save @UAF|@HPU Module file if uaf_tag != '@UII': # Skip @UII binary, already parsed with open(uaf_fname, 'wb') as uaf_out: uaf_out.write(uaf_data_raw) - # @UAF Module EFI/Tiano Decompression + # @UAF|@HPU Module EFI/Tiano Decompression if is_comp and is_efi_compressed(uaf_data_raw, False): - dec_fname = uaf_fname.replace('.temp', uaf_fext) # Decompressed @UAF Module file path + dec_fname = uaf_fname.replace('.temp', uaf_fext) # Decompressed @UAF|@HPU Module file path if efi_decompress(uaf_fname, dec_fname, padding + 4) == 0: - with open(dec_fname, 'rb') as dec: uaf_data_raw = dec.read() # Read back the @UAF Module decompressed Raw data + with open(dec_fname, 'rb') as dec: uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data - os.remove(uaf_fname) # Successful decompression, delete compressed @UAF Module file + os.remove(uaf_fname) # Successful decompression, delete compressed @UAF|@HPU Module file - uaf_fname = dec_fname # Adjust @UAF Module file path to the decompressed one + uaf_fname = dec_fname # Adjust @UAF|@HPU Module file path to the decompressed one - # Process and Print known text only @UAF Modules (after EFI/Tiano Decompression) + # Process and Print known text only @UAF|@HPU Modules (after EFI/Tiano Decompression) if uaf_tag in UAF_TAG_DICT and UAF_TAG_DICT[uaf_tag][2] == 'Text': printer(UAF_TAG_DICT[uaf_tag][1] + ':', padding + 4) printer(uaf_data_raw.decode('utf-8','ignore'), padding + 8) - # Parse Default Command Status @UAF Module (@DIS) + # Parse Default Command Status @UAF|@HPU Module (@DIS) if len(uaf_data_raw) and uaf_tag == '@DIS': dis_hdr = get_struct(uaf_data_raw, 0x0, DisHeader) # Parse @DIS Module Raw Header Structure @@ -343,11 +363,11 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na os.remove(uaf_fname) # Delete @DIS Module binary, info exported as text - # Parse Name|Non-AMI List (?) @UAF Module (@NAL) + # Parse Name List @UAF|@HPU Module (@NAL) if len(uaf_data_raw) >= 5 and (uaf_tag,uaf_data_raw[0],uaf_data_raw[4]) == ('@NAL',0x40,0x3A): nal_info = uaf_data_raw.decode('utf-8','ignore').replace('\r','').strip().split('\n') - printer('@UAF Module Name List:\n', padding + 4) + printer('AMI UCP Module Name List:\n', padding + 4) # Parse all @NAL Module Entries for info in nal_info: @@ -357,7 +377,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na nal_dict[info_tag] = os.path.basename(info_val) # Assign a file name (w/o path) to each Tag - # Parse Insyde BIOS @UAF Module (@INS) + # Parse Insyde BIOS @UAF|@HPU Module (@INS) if uaf_tag == '@INS' and is_7z_supported(uaf_fname): ins_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory @@ -381,14 +401,14 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na printer('Intel Management Engine (ME) Firmware:\n', padding + 4) printer('Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer', padding + 8, False) - # Get best Nested AMI UCP Pattern match based on @UAF Size - nested_uaf_off,nested_uaf_bin = get_ami_ucp(uaf_data_raw) + # Get best Nested AMI UCP Pattern match based on @UAF|@HPU Size + nested_uaf_off,nested_uaf_bin,nested_uaf_tag = get_ami_ucp(uaf_data_raw) # Parse Nested AMI UCP Structure if nested_uaf_off: uaf_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory - ucp_extract(nested_uaf_bin, uaf_dir, padding + 4, is_checksum) # Call recursively + ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum) # Call recursively os.remove(uaf_fname) # Delete raw nested AMI UCP Structure after successful recursion/extraction @@ -403,57 +423,58 @@ UII_HDR_LEN = ctypes.sizeof(UiiHeader) # AMI UCP Tag Dictionary UAF_TAG_DICT = { - '@3FI' : ['HpBiosUpdate32.efi', '', ''], - '@3S2' : ['HpBiosUpdate32.s12', '', ''], - '@3S4' : ['HpBiosUpdate32.s14', '', ''], - '@3S9' : ['HpBiosUpdate32.s09', '', ''], - '@3SG' : ['HpBiosUpdate32.sig', '', ''], + '@3FI' : ['HpBiosUpdate32.efi', 'HpBiosUpdate32.efi', ''], + '@3S2' : ['HpBiosUpdate32.s12', 'HpBiosUpdate32.s12', ''], + '@3S4' : ['HpBiosUpdate32.s14', 'HpBiosUpdate32.s14', ''], + '@3S9' : ['HpBiosUpdate32.s09', 'HpBiosUpdate32.s09', ''], + '@3SG' : ['HpBiosUpdate32.sig', 'HpBiosUpdate32.sig', ''], '@AMI' : ['UCP_Nested.bin', 'Nested AMI UCP', ''], - '@B12' : ['BiosMgmt.s12', '', ''], - '@B14' : ['BiosMgmt.s14', '', ''], - '@B32' : ['BiosMgmt32.s12', '', ''], - '@B34' : ['BiosMgmt32.s14', '', ''], - '@B39' : ['BiosMgmt32.s09', '', ''], - '@B3E' : ['BiosMgmt32.efi', '', ''], - '@BM9' : ['BiosMgmt.s09', '', ''], - '@BME' : ['BiosMgmt.efi', '', ''], + '@B12' : ['BiosMgmt.s12', 'BiosMgmt.s12', ''], + '@B14' : ['BiosMgmt.s14', 'BiosMgmt.s14', ''], + '@B32' : ['BiosMgmt32.s12', 'BiosMgmt32.s12', ''], + '@B34' : ['BiosMgmt32.s14', 'BiosMgmt32.s14', ''], + '@B39' : ['BiosMgmt32.s09', 'BiosMgmt32.s09', ''], + '@B3E' : ['BiosMgmt32.efi', 'BiosMgmt32.efi', ''], + '@BM9' : ['BiosMgmt.s09', 'BiosMgmt.s09', ''], + '@BME' : ['BiosMgmt.efi', 'BiosMgmt.efi', ''], '@CKV' : ['Check_Version.txt', 'Check Version', 'Text'], '@CMD' : ['AFU_Command.txt', 'AMI AFU Command', 'Text'], '@CPM' : ['AC_Message.txt', 'Confirm Power Message', ''], '@DCT' : ['DevCon32.exe', 'Device Console WIN32', ''], '@DCX' : ['DevCon64.exe', 'Device Console WIN64', ''], - '@DFE' : ['HpDevFwUpdate.efi', '', ''], - '@DFS' : ['HpDevFwUpdate.s12', '', ''], + '@DFE' : ['HpDevFwUpdate.efi', 'HpDevFwUpdate.efi', ''], + '@DFS' : ['HpDevFwUpdate.s12', 'HpDevFwUpdate.s12', ''], '@DIS' : ['Command_Status.bin', 'Default Command Status', ''], - '@ENB' : ['ENBG64.exe', '', ''], + '@ENB' : ['ENBG64.exe', 'ENBG64.exe', ''], + '@HPU' : ['UCP_Main.bin', 'Utility Auxiliary File (HP)', ''], '@INS' : ['Insyde_Nested.bin', 'Nested Insyde SFX', ''], - '@M32' : ['HpBiosMgmt32.s12', '', ''], - '@M34' : ['HpBiosMgmt32.s14', '', ''], - '@M39' : ['HpBiosMgmt32.s09', '', ''], - '@M3I' : ['HpBiosMgmt32.efi', '', ''], + '@M32' : ['HpBiosMgmt32.s12', 'HpBiosMgmt32.s12', ''], + '@M34' : ['HpBiosMgmt32.s14', 'HpBiosMgmt32.s14', ''], + '@M39' : ['HpBiosMgmt32.s09', 'HpBiosMgmt32.s09', ''], + '@M3I' : ['HpBiosMgmt32.efi', 'HpBiosMgmt32.efi', ''], '@MEC' : ['FWUpdLcl.txt', 'Intel FWUpdLcl Command', 'Text'], '@MED' : ['FWUpdLcl_DOS.exe', 'Intel FWUpdLcl DOS', ''], '@MET' : ['FWUpdLcl_WIN32.exe', 'Intel FWUpdLcl WIN32', ''], - '@MFI' : ['HpBiosMgmt.efi', '', ''], - '@MS2' : ['HpBiosMgmt.s12', '', ''], - '@MS4' : ['HpBiosMgmt.s14', '', ''], - '@MS9' : ['HpBiosMgmt.s09', '', ''], - '@NAL' : ['UAF_List.txt', 'Name List', ''], + '@MFI' : ['HpBiosMgmt.efi', 'HpBiosMgmt.efi', ''], + '@MS2' : ['HpBiosMgmt.s12', 'HpBiosMgmt.s12', ''], + '@MS4' : ['HpBiosMgmt.s14', 'HpBiosMgmt.s14', ''], + '@MS9' : ['HpBiosMgmt.s09', 'HpBiosMgmt.s09', ''], + '@NAL' : ['UCP_List.txt', 'AMI UCP Module Name List', ''], '@OKM' : ['OK_Message.txt', 'OK Message', ''], '@PFC' : ['BGT_Command.txt', 'AMI BGT Command', 'Text'], - '@R3I' : ['CryptRSA32.efi', '', ''], - '@RFI' : ['CryptRSA.efi', '', ''], - '@UAF' : ['UCP_Main.bin', 'Utility Auxiliary File', ''], - '@UFI' : ['HpBiosUpdate.efi', '', ''], + '@R3I' : ['CryptRSA32.efi', 'CryptRSA32.efi', ''], + '@RFI' : ['CryptRSA.efi', 'CryptRSA.efi', ''], + '@UAF' : ['UCP_Main.bin', 'Utility Auxiliary File (AMI)', ''], + '@UFI' : ['HpBiosUpdate.efi', 'HpBiosUpdate.efi', ''], '@UII' : ['UCP_Info.txt', 'Utility Identification Information', ''], - '@US2' : ['HpBiosUpdate.s12', '', ''], - '@US4' : ['HpBiosUpdate.s14', '', ''], - '@US9' : ['HpBiosUpdate.s09', '', ''], - '@USG' : ['HpBiosUpdate.sig', '', ''], + '@US2' : ['HpBiosUpdate.s12', 'HpBiosUpdate.s12', ''], + '@US4' : ['HpBiosUpdate.s14', 'HpBiosUpdate.s14', ''], + '@US9' : ['HpBiosUpdate.s09', 'HpBiosUpdate.s09', ''], + '@USG' : ['HpBiosUpdate.sig', 'HpBiosUpdate.sig', ''], '@VER' : ['OEM_Version.txt', 'OEM Version', 'Text'], - '@VXD' : ['amifldrv.vxd', '', ''], - '@W32' : ['amifldrv32.sys', '', ''], - '@W64' : ['amifldrv64.sys', '', ''], + '@VXD' : ['amifldrv.vxd', 'amifldrv.vxd', ''], + '@W32' : ['amifldrv32.sys', 'amifldrv32.sys', ''], + '@W64' : ['amifldrv64.sys', 'amifldrv64.sys', ''], } if __name__ == '__main__': @@ -477,8 +498,8 @@ if __name__ == '__main__': with open(input_file, 'rb') as in_file: input_buffer = in_file.read() - # Get best AMI UCP Pattern match based on @UAF Size - main_uaf_off,main_uaf_bin = get_ami_ucp(input_buffer) + # Get best AMI UCP Pattern match based on @UAF|@HPU Size + main_uaf_off,main_uaf_bin,main_uaf_tag = get_ami_ucp(input_buffer) if not main_uaf_off: printer('Error: This is not an AMI UCP BIOS executable!', padding) @@ -487,7 +508,7 @@ if __name__ == '__main__': extract_path = os.path.join(output_path, input_name) - ucp_extract(main_uaf_bin, extract_path, padding, is_checksum) + ucp_extract(main_uaf_bin, extract_path, main_uaf_tag, padding, is_checksum) printer('Extracted AMI UCP BIOS executable!', padding) diff --git a/common/patterns.py b/common/patterns.py index 0f7d81e..25e8cec 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -4,7 +4,7 @@ import re PAT_AMI_PFAT = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) -PAT_AMI_UCP = re.compile(br'\x40\x55\x41\x46.{12}\x40', re.DOTALL) +PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}\x40', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}\x37\x7A\x58\x5A', re.DOTALL) PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') From 96e87455deffc57ceae21f52f5c0787c78f3b12a Mon Sep 17 00:00:00 2001 From: platomav Date: Sat, 9 Apr 2022 22:27:47 +0300 Subject: [PATCH 10/52] Small AMI UCP pattern improvement --- common/patterns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/patterns.py b/common/patterns.py index 25e8cec..7a0db49 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -4,7 +4,7 @@ import re PAT_AMI_PFAT = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) -PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}\x40', re.DOTALL) +PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}@', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}\x37\x7A\x58\x5A', re.DOTALL) PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') From d6e8d31391763bde4dd04ccb0602dbe75d4c233f Mon Sep 17 00:00:00 2001 From: platomav Date: Wed, 13 Apr 2022 14:04:38 +0300 Subject: [PATCH 11/52] Dell PFS Update Extractor v6.0_a2 Added --version parameter Structure fixes and improvements --- AMI_PFAT_Extract.py | 6 +- AMI_UCP_Extract.py | 6 +- Dell_PFS_Extract.py | 131 ++++++++++++++++++++++---------------------- README.md | 5 +- common/path_ops.py | 12 ---- common/patterns.py | 2 +- common/system.py | 24 ++++++-- 7 files changed, 96 insertions(+), 90 deletions(-) diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py index c681d7a..82131b0 100644 --- a/AMI_PFAT_Extract.py +++ b/AMI_PFAT_Extract.py @@ -7,7 +7,7 @@ AMI BIOS Guard Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'AMI BIOS Guard Extractor v4.0_a2' +title = 'AMI BIOS Guard Extractor v4.0_a3' import os import re @@ -20,10 +20,10 @@ sys.dont_write_bytecode = True from common.externals import get_bgs_tool from common.num_ops import get_ordinal -from common.path_ops import argparse_init, safe_name +from common.path_ops import safe_name from common.patterns import PAT_AMI_PFAT from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t -from common.system import script_init, script_title, printer +from common.system import script_init, script_title, argparse_init, printer class AmiBiosGuardHeader(ctypes.LittleEndianStructure): _pack_ = 1 diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index b35616b..c72db2e 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -title = 'AMI UCP BIOS Extractor v2.0_a3' +title = 'AMI UCP BIOS Extractor v2.0_a4' import os import re @@ -23,10 +23,10 @@ sys.dont_write_bytecode = True from common.a7z_comp import a7z_decompress, is_7z_supported from common.checksums import get_chk_16 from common.efi_comp import efi_decompress, is_efi_compressed -from common.path_ops import argparse_init, safe_name +from common.path_ops import safe_name from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t -from common.system import script_init, script_title, printer +from common.system import script_init, script_title, argparse_init, printer from AMI_PFAT_Extract import get_ami_pfat, parse_pfat_file diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index 012c84a..d29808e 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Dell PFS Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'Dell PFS Update Extractor v6.0_a1' +title = 'Dell PFS Update Extractor v6.0_a2' import os import io @@ -22,10 +22,10 @@ import contextlib sys.dont_write_bytecode = True from common.checksums import get_chk_8_xor -from common.path_ops import argparse_init, safe_name +from common.path_ops import safe_name from common.patterns import PAT_DELL_HDR, PAT_DELL_FTR, PAT_DELL_PKG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t, uint64_t -from common.system import script_init, script_title, printer +from common.system import script_init, script_title, argparse_init, printer from AMI_PFAT_Extract import IntelBiosGuardHeader, IntelBiosGuardSignature2k, parse_bg_script @@ -78,7 +78,7 @@ class DellPfsEntryBase(ctypes.LittleEndianStructure): def struct_print(self, p): GUID = '%0.*X' % (0x10 * 2, int.from_bytes(self.GUID, 'little')) Unknown = '%0.*X' % (len(self.Unknown) * 8, int.from_bytes(self.Unknown, 'little')) - Version = get_entry_ver(self.Version, self.VersionType, padding - 4) + Version = get_entry_ver(self.Version, self.VersionType) printer(['Entry GUID :', GUID], p, False) printer(['Entry Version :', self.HeaderVersion], p, False) @@ -131,11 +131,12 @@ class DellPfsName(ctypes.LittleEndianStructure): # 0x0E ] - def struct_print(self, p): - Version = get_entry_ver(self.Version, self.VersionType, padding - 4) + def struct_print(self, p, name): + Version = get_entry_ver(self.Version, self.VersionType) printer(['Payload Version:', Version], p, False) printer(['Character Count:', self.CharacterCount], p, False) + printer(['Payload Name :', name], p, False) # Dell PFS Metadata Header Structure class DellPfsMetadata(ctypes.LittleEndianStructure): @@ -189,7 +190,7 @@ class DellPfsPfatMetadata(ctypes.LittleEndianStructure): # Each section starts with a 0x30 header, which begins with pattern 72135500. # The section length is found at 0x10-0x14 and its (optional) MD5 hash at 0x20-0x30. # Section data can be raw or LZMA2 (7zXZ) compressed. The latter contains the PFS update image. -def is_dell_pfs_pkg(in_buffer): +def is_pfs_pkg(in_buffer): return PAT_DELL_PKG.search(in_buffer) # The Dell PFS update images usually contain multiple sections. @@ -197,17 +198,21 @@ def is_dell_pfs_pkg(in_buffer): # where ******** is the zlib stream size, ++ is the section type and -- the header Checksum XOR 8. # The "Firmware" section has type AA and its files are stored in PFS format. # The "Utility" section has type BB and its files are stored in PFS, BIN or 7z formats. -def is_dell_pfs_hdr(in_buffer): +def is_pfs_hdr(in_buffer): return list(PAT_DELL_HDR.finditer(in_buffer)) # Each section is followed by the footer pattern ********EEAAEE8F491BE8AE143790--, # where ******** is the zlib stream size and ++ the footer Checksum XOR 8. -def is_dell_pfs_ftr(in_buffer): +def is_pfs_ftr(in_buffer): return PAT_DELL_FTR.search(in_buffer) +# Check if input buffer is Dell PFS/PKG image +def is_dell_pfs(in_buffer): + return is_pfs_hdr(in_buffer) or is_pfs_pkg(in_buffer) + # Get PFS ZLIB Section Offsets def get_section_offsets(buffer): - pfs_zlib_init = is_dell_pfs_hdr(buffer) + pfs_zlib_init = is_pfs_hdr(buffer) if not pfs_zlib_init: return [] # No PFS ZLIB detected @@ -228,7 +233,7 @@ def get_section_offsets(buffer): return pfs_zlib_list # Dell PFS ZLIB Section Parser -def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padding, is_verbose=True, is_advanced=True): +def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padding, is_structure=True, is_advanced=True): is_zlib_error = False # Initialize PFS ZLIB-related error state section_type = zlib_data[zlib_start - 0x1] # Byte before PFS ZLIB Section pattern is Section Type (e.g. AA, BB) @@ -278,7 +283,7 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p footer_data = zlib_data[compressed_end:compressed_end + 0x10] # Search input section for PFS ZLIB section footer - pfs_zlib_footer_match = is_dell_pfs_ftr(footer_data) + pfs_zlib_footer_match = is_pfs_ftr(footer_data) # Check if PFS ZLIB section footer was found in the section if not pfs_zlib_footer_match: @@ -306,12 +311,12 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p section_data = zlib_data # Fallback to raw ZLIB data upon critical error # Call the PFS Extract function on the decompressed PFS ZLIB Section - pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padding, is_verbose, is_advanced) + pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padding, is_structure, is_advanced) # Parse & Extract Dell PFS Volume -def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, is_verbose=True, is_advanced=True): +def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, is_structure=True, is_advanced=True): # Show PFS Volume indicator - if is_verbose: + if is_structure: printer('PFS Volume:', pfs_padd) # Get PFS Header Structure values @@ -324,7 +329,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i return # Critical error, abort # Show PFS Header Structure info - if is_verbose: + if is_structure: printer('PFS Header:\n', pfs_padd + 4) pfs_hdr.struct_print(pfs_padd + 8) @@ -344,7 +349,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i while len(pfs_payload[entry_start:entry_start + pfs_entry_size]) == pfs_entry_size: # Analyze PFS Entry Structure and get relevant info _,entry_version,entry_guid,entry_data,entry_data_sig,entry_met,entry_met_sig,next_entry = \ - parse_pfs_entry(pfs_payload, entry_start, pfs_entry_size, pfs_entry_struct, 'PFS Entry', pfs_padd, is_verbose) + parse_pfs_entry(pfs_payload, entry_start, pfs_entry_size, pfs_entry_struct, 'PFS Entry', pfs_padd, is_structure) entry_type = 'OTHER' # Adjusted later if PFS Entry is Zlib, PFAT, PFS Info, Model Info @@ -380,7 +385,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i entry_info_hdr = get_struct(filename_info, info_start, DellPfsInfo) # Show PFS Information Header Structure info - if is_verbose: + if is_structure: printer('PFS Information Header:\n', pfs_padd + 4) entry_info_hdr.struct_print(pfs_padd + 8) @@ -395,11 +400,6 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i # Get PFS FileName Structure values entry_info_mod = get_struct(filename_info, info_start + PFS_INFO_LEN, DellPfsName) - # Show PFS FileName Structure info - if is_verbose: - printer('PFS FileName Entry:\n', pfs_padd + 8) - entry_info_mod.struct_print(pfs_padd + 12) - # The PFS FileName Structure is not complete by itself. The size of the last field (Entry Name) is determined from # CharacterCount multiplied by 2 due to usage of UTF-16 2-byte Characters. Any Entry Name leading and/or trailing # space/null characters are stripped and common Windows reserved/illegal filename characters are replaced @@ -408,13 +408,14 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i name_data = filename_info[name_start:name_start + name_size] # PFS Entry's FileName buffer entry_name = safe_name(name_data.decode('utf-16').strip()) # PFS Entry's FileName value - # Show PFS FileName Name info (padding matches the one from PFS FileName Structure info) - if is_verbose: - printer('Payload Name : %s' % entry_name, pfs_padd + 12, False) + # Show PFS FileName Structure info + if is_structure: + printer('PFS FileName Entry:\n', pfs_padd + 8) + entry_info_mod.struct_print(pfs_padd + 12, entry_name) # Get PFS FileName Version string via "Version" and "VersionType" fields # PFS FileName Version string must be preferred over PFS Entry's Version - entry_version = get_entry_ver(entry_info_mod.Version, entry_info_mod.VersionType, pfs_padd + 12) + entry_version = get_entry_ver(entry_info_mod.Version, entry_info_mod.VersionType) # Store all relevant PFS FileName details info_all.append([entry_guid, entry_name, entry_version]) @@ -436,7 +437,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i entry_info = get_struct(entry_metadata, 0, DellPfsMetadata) # Show Nested PFS Metadata Structure info - if is_verbose: + if is_structure: printer('PFS Metadata Information:\n', pfs_padd + 4) entry_info.struct_print(pfs_padd + 8) @@ -460,7 +461,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i entry_info_hdr = get_struct(signature_info, sign_start, DellPfsInfo) # Show PFS Information Header Structure info - if is_verbose: + if is_structure: printer('PFS Information Header:\n', pfs_padd + 4) entry_info_hdr.struct_print(pfs_padd + 8) @@ -476,7 +477,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i entry_hdr = get_struct(signature_info, sign_start + PFS_INFO_LEN, pfs_entry_struct) # Show PFS Information Header Structure info - if is_verbose: + if is_structure: printer('PFS Information Entry:\n', pfs_padd + 8) entry_hdr.struct_print(pfs_padd + 12) @@ -486,7 +487,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i sign_data_raw = signature_info[sign_info_start + 0x2:sign_info_start + 0x2 + sign_size] sign_data_txt = '%0.*X' % (sign_size * 2, int.from_bytes(sign_data_raw, 'little')) - if is_verbose: + if is_structure: printer('Signature Information:\n', pfs_padd + 8) printer('Signature Size: 0x%X' % sign_size, pfs_padd + 12, False) printer('Signature Data: %s [...]' % sign_data_txt[:32], pfs_padd + 12, False) @@ -518,7 +519,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i if pfat_entry_hdr.Tag == b'PFS.HDR.' and is_pfat: entry_type = 'PFAT' # Re-set PFS Entry Type from OTHER to PFAT, to use such info afterwards - entry_data = parse_pfat_pfs(pfat_entry_hdr, entry_data, pfs_padd, is_verbose) # Parse sub-PFS PFAT Volume + entry_data = parse_pfat_pfs(pfat_entry_hdr, entry_data, pfs_padd, is_structure) # Parse sub-PFS PFAT Volume # Parse PFS Entry which contains zlib-compressed sub-PFS Volume elif pfs_zlib_offsets: @@ -539,7 +540,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i sub_pfs_path = os.path.join(output_path, str(pfs_count) + sub_pfs_name) # Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count) - pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4, is_verbose, is_advanced) + pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4, is_structure, is_advanced) entries_all[index][4] = entry_data # Adjust PFS Entry Data after parsing PFAT (same ZLIB raw data, not stored afterwards) entries_all[index][3] = entry_type # Adjust PFS Entry Type from OTHER to PFAT or ZLIB (ZLIB is ignored at file extraction) @@ -598,21 +599,21 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i # Write/Extract PFS Entry files for file in write_files: full_name = '%d%s -- %d %s v%s' % (pfs_index, pfs_name, file_index, file_name, file_version) # Full PFS Entry Name - pfs_file_write(file[0], file[1], file_type, full_name, output_path, pfs_padd, is_verbose, is_advanced) + pfs_file_write(file[0], file[1], file_type, full_name, output_path, pfs_padd, is_structure, is_advanced) # Get PFS Footer Data after PFS Header Payload pfs_footer = buffer[PFS_HEAD_LEN + pfs_hdr.PayloadSize:PFS_HEAD_LEN + pfs_hdr.PayloadSize + PFS_FOOT_LEN] # Analyze PFS Footer Structure - chk_pfs_ftr(pfs_footer, pfs_payload, pfs_hdr.PayloadSize, 'PFS', pfs_padd, is_verbose) + chk_pfs_ftr(pfs_footer, pfs_payload, pfs_hdr.PayloadSize, 'PFS', pfs_padd, is_structure) # Analyze Dell PFS Entry Structure -def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, padding, is_verbose=True): +def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, padding, is_structure=True): # Get PFS Entry Structure values pfs_entry = get_struct(entry_buffer, entry_start, entry_struct) # Show PFS Entry Structure info - if is_verbose: + if is_structure: printer('PFS Entry:\n', padding + 4) pfs_entry.struct_print(padding + 8) @@ -624,7 +625,7 @@ def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, p printer('Error: Detected non-empty %s Reserved field!' % text, padding + 8) # Get PFS Entry Version string via "Version" and "VersionType" fields - entry_version = get_entry_ver(pfs_entry.Version, pfs_entry.VersionType, padding + 8) + entry_version = get_entry_ver(pfs_entry.Version, pfs_entry.VersionType) # Get PFS Entry GUID in Big Endian format entry_guid = '%0.*X' % (0x10 * 2, int.from_bytes(pfs_entry.GUID, 'little')) @@ -653,13 +654,13 @@ def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, p return pfs_entry, entry_version, entry_guid, entry_data, entry_data_sig, entry_met, entry_met_sig, entry_met_sig_end # Parse Dell PFS Volume with PFAT Payload -def parse_pfat_pfs(entry_hdr, entry_data, padding, is_verbose=True): +def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True): # Show PFS Volume indicator - if is_verbose: + if is_structure: printer('PFS Volume:', padding + 4) # Show sub-PFS Header Structure Info - if is_verbose: + if is_structure: printer('PFS Header:\n', padding + 8) entry_hdr.struct_print(padding + 12) @@ -683,7 +684,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_verbose=True): # Analyze sub-PFS PFAT Entry Structure and get relevant info pfat_entry,_,_,pfat_entry_data,_,pfat_entry_met,_,pfat_next_entry = parse_pfs_entry(pfat_payload, - pfat_entry_start, pfat_entry_size, pfat_entry_struct, 'sub-PFS PFAT Entry', padding + 4, is_verbose) + pfat_entry_start, pfat_entry_size, pfat_entry_struct, 'sub-PFS PFAT Entry', padding + 4, is_structure) # Each sub-PFS PFAT Entry includes an AMI BIOS Guard (a.k.a. PFAT) block at the beginning # We need to parse the PFAT block and remove its contents from the final Payload/Raw Data @@ -693,7 +694,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_verbose=True): pfat_hdr = get_struct(pfat_payload, pfat_hdr_off, IntelBiosGuardHeader) # Show sub-PFS PFAT Header Structure info - if is_verbose: + if is_structure: printer('PFAT Block %d Header:\n' % pfat_entry_index, padding + 12) pfat_hdr.struct_print(padding + 16) @@ -718,12 +719,12 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_verbose=True): pfat_sig = get_struct(pfat_payload, pfat_payload_end, IntelBiosGuardSignature2k) # Show sub-PFS PFAT Signature Structure info - if is_verbose: + if is_structure: printer('PFAT Block %d Signature:\n' % pfat_entry_index, padding + 12) pfat_sig.struct_print(padding + 16) # Show PFAT Script via BIOS Guard Script Tool - if is_verbose: + if is_structure: printer('PFAT Block %d Script:\n' % pfat_entry_index, padding + 12) # https://github.com/allowitsme/big-tool by Dmitry Frolov @@ -740,7 +741,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_verbose=True): pfat_met = get_struct(pfat_entry_met, 0, DellPfsPfatMetadata) # Show sub-PFS PFAT Metadata Structure info - if is_verbose: + if is_structure: printer('PFAT Block %d Metadata:\n' % pfat_entry_index, padding + 12) pfat_met.struct_print(padding + 16) @@ -779,7 +780,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_verbose=True): printer('Error: Detected sub-PFS PFAT Entry Buffer & Last Offset Size mismatch!', padding + 8) # Analyze sub-PFS Footer Structure - chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padding + 4, is_verbose) + chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padding + 4, is_structure) return entry_data @@ -793,20 +794,18 @@ def get_pfs_entry(buffer, offset): return DellPfsEntryR2, ctypes.sizeof(DellPfsEntryR2) # Determine Dell PFS Entry Version string -def get_entry_ver(version_fields, version_types, msg_padd): +def get_entry_ver(version_fields, version_types): version = '' # Initialize Version string # Each Version Type (1 byte) determines the type of each Version Value (2 bytes) # Version Type 'N' is Number, 'A' is Text and ' ' is Empty/Unused - for idx in range(len(version_fields)): - eol = '' if idx == len(version_fields) - 1 else '.' + for index,field in enumerate(version_fields): + eol = '' if index == len(version_fields) - 1 else '.' - if version_types[idx] == 65: version += '%X%s' % (version_fields[idx], eol) # 0x41 = ASCII - elif version_types[idx] == 78: version += '%d%s' % (version_fields[idx], eol) # 0x4E = Number - elif version_types[idx] in (0, 32): version = version.strip('.') # 0x00 or 0x20 = Unused - else: - version += '%X%s' % (version_fields[idx], eol) # Unknown - printer('Error: Unknown PFS Entry Version Type 0x%0.2X!' % version_types[idx], msg_padd) + if version_types[index] == 65: version += '%X%s' % (field, eol) # 0x41 = ASCII + elif version_types[index] == 78: version += '%d%s' % (field, eol) # 0x4E = Number + elif version_types[index] in (0, 32): version = version.strip('.') # 0x00 or 0x20 = Unused + else: version += '%X%s' % (field, eol) # Unknown return version @@ -817,14 +816,14 @@ def chk_hdr_ver(version, text, padding): printer('Error: Unknown %s Header Version %d!' % (text, version), padding) # Analyze Dell PFS Footer Structure -def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_verbose=True): +def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_structure=True): # Get PFS Footer Structure values pfs_ftr = get_struct(footer_buffer, 0, DellPfsFooter) # Validate that a PFS Footer was parsed if pfs_ftr.Tag == b'PFS.FTR.': # Show PFS Footer Structure info - if is_verbose: + if is_structure: printer('PFS Footer:\n', padding + 4) pfs_ftr.struct_print(padding + 8) else: @@ -842,7 +841,7 @@ def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_verbose printer('Error: Invalid %s Footer Payload Checksum!' % text, padding + 4) # Write/Extract Dell PFS Entry Files (Data, Metadata, Signature) -def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, is_verbose=True, is_advanced=True): +def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, is_structure=True, is_advanced=True): # Store Data/Metadata Signature (advanced users only) if bin_name.startswith('sign'): final_name = '%s.%s.sig' % (safe_name(full_name), bin_name.split('_')[1]) @@ -856,7 +855,7 @@ def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, i bin_ext = '.%s.bin' % bin_name if is_advanced else '.bin' # Simpler Data/Metadata Extension for non-advanced users # Some Data may be Text or XML files with useful information for non-advanced users - is_text,final_data,file_ext,write_mode = bin_is_text(bin_buff, bin_type, bin_name == 'meta', padding, is_verbose, is_advanced) + is_text,final_data,file_ext,write_mode = bin_is_text(bin_buff, bin_type, bin_name == 'meta', padding, is_structure, is_advanced) final_name = '%s%s' % (safe_name(full_name), bin_ext[:-4] + file_ext if is_text else bin_ext) final_path = os.path.join(out_path, final_name) @@ -864,7 +863,7 @@ def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, i with open(final_path, write_mode) as pfs_out: pfs_out.write(final_data) # Write final Data/Metadata Payload # Check if Dell PFS Entry file/data is Text/XML and Convert -def bin_is_text(buffer, file_type, is_metadata, pfs_padd, is_verbose=True, is_advanced=True): +def bin_is_text(buffer, file_type, is_metadata, pfs_padd, is_structure=True, is_advanced=True): is_text = False write_mode = 'wb' extension = '.bin' @@ -894,7 +893,7 @@ def bin_is_text(buffer, file_type, is_metadata, pfs_padd, is_verbose=True, is_ad buffer = text_buffer.getvalue() # Show Model/PCR XML Information, if applicable - if is_verbose and is_text and not is_metadata: # Metadata is shown at initial DellPfsMetadata analysis + if is_structure and is_text and not is_metadata: # Metadata is shown at initial DellPfsMetadata analysis printer('PFS %s Information:\n' % {'.txt': 'Model', '.xml': 'PCR XML'}[extension], pfs_padd + 8) _ = [printer(line.strip('\r'), pfs_padd + 12, False) for line in buffer.split('\n') if line] @@ -920,11 +919,11 @@ if __name__ == '__main__': # Set argparse arguments argparser = argparse_init() argparser.add_argument('-a', '--advanced', help='extract signatures and metadata', action='store_true') - argparser.add_argument('-v', '--verbose', help='show PFS structure information', action='store_true') + argparser.add_argument('-s', '--structure', help='show PFS structure information', action='store_true') arguments = argparser.parse_args() is_advanced = arguments.advanced # Set Advanced user mode optional argument - is_verbose = arguments.verbose # Set Verbose output mode optional argument + is_structure = arguments.structure # Set Structure output mode optional argument # Initialize script (must be after argparse) input_files,output_path,padding = script_init(arguments, 4) @@ -937,7 +936,7 @@ if __name__ == '__main__': with open(input_file, 'rb') as in_file: input_buffer = in_file.read() # Search input image for ThinOS PKG 7zXZ section header - lzma_pkg_hdr_match = is_dell_pfs_pkg(input_buffer) + lzma_pkg_hdr_match = is_pfs_pkg(input_buffer) # Decompress ThinOS PKG 7zXZ section first, if present if lzma_pkg_hdr_match: @@ -969,7 +968,7 @@ if __name__ == '__main__': # Parse each PFS ZLIB Section for offset in pfs_zlib_offsets: # Call the PFS ZLIB Section Parser function - pfs_section_parse(input_buffer, offset, extract_path, extract_name, 1, 1, False, padding, is_verbose, is_advanced) + pfs_section_parse(input_buffer, offset, extract_path, extract_name, 1, 1, False, padding, is_structure, is_advanced) printer('Extracted Dell PFS Update image!', padding) diff --git a/README.md b/README.md index b817e70..8873988 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,12 @@ Parses Dell PFS Update images and extracts their Firmware (e.g. SPI, BIOS/UEFI, You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional arguments: * -h or --help : show help message and exit +* -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts * -a or --advanced : extract signatures and metadata -* -v or --verbose : show PFS structure information +* -s or --structure : show PFS structure information #### **Compatibility** @@ -81,6 +82,7 @@ Parses AMI UCP (Utility Configuration Program) BIOS executables, extracts their You can either Drag & Drop or manually enter AMI UCP BIOS executable file(s). Optional arguments: * -h or --help : show help message and exit +* -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts @@ -146,6 +148,7 @@ Note that the AMI PFAT structure may not have an explicit component order. AMI's You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s). Optional arguments: * -h or --help : show help message and exit +* -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts diff --git a/common/path_ops.py b/common/path_ops.py index c6e0709..efbf018 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -5,7 +5,6 @@ import os import re import sys import inspect -import argparse from pathlib import Path # Fix illegal/reserved Windows characters @@ -43,17 +42,6 @@ def get_absolute_path(argparse_path): return absolute_path -# Initialize common argparse arguments -def argparse_init(): - argparser = argparse.ArgumentParser() - - argparser.add_argument('files', type=argparse.FileType('r'), nargs='*') - argparser.add_argument('-e', '--auto-exit', help='skip press enter to exit prompts', action='store_true') - argparser.add_argument('-o', '--output-dir', help='extract in given output directory') - argparser.add_argument('-i', '--input-dir', help='extract from given input directory') - - return argparser - # Process input files (argparse object) def process_input_files(argparse_args, sys_argv=None): if sys_argv is None: sys_argv = [] diff --git a/common/patterns.py b/common/patterns.py index 7a0db49..56bee4f 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -5,7 +5,7 @@ import re PAT_AMI_PFAT = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}@', re.DOTALL) -PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}\x37\x7A\x58\x5A', re.DOTALL) +PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) \ No newline at end of file diff --git a/common/system.py b/common/system.py index 68d3ce7..dc2e9c8 100644 --- a/common/system.py +++ b/common/system.py @@ -3,6 +3,7 @@ import sys import ctypes +import argparse import traceback from common.text_ops import padder @@ -53,6 +54,18 @@ def check_sys_os(): # Fix Windows Unicode console redirection if os_win: sys.stdout.reconfigure(encoding='utf-8') +# Initialize common argparse arguments +def argparse_init(): + argparser = argparse.ArgumentParser() + + argparser.add_argument('files', type=argparse.FileType('r'), nargs='*') + argparser.add_argument('-e', '--auto-exit', help='skip press enter to exit prompts', action='store_true') + argparser.add_argument('-v', '--version', help='show utility name and version', action='store_true') + argparser.add_argument('-o', '--output-dir', help='extract in given output directory') + argparser.add_argument('-i', '--input-dir', help='extract from given input directory') + + return argparser + # Show Script Title def script_title(title): printer(title) @@ -63,17 +76,20 @@ def script_title(title): if os_win: ctypes.windll.kernel32.SetConsoleTitleW(title) else: sys.stdout.write('\x1b]2;' + title + '\x07') -# Initialize Script +# Initialize Script (must be after argparse) def script_init(arguments, padding=0): - # Pretty Python exception handler (must be after argparse) + # Pretty Python exception handler sys.excepthook = nice_exc_handler - # Check Python Version (must be after argparse) + # Check Python Version check_sys_py() - # Check OS Platform (must be after argparse) + # Check OS Platform check_sys_os() + # Show Utility Version on demand + if arguments.version: sys.exit(0) + # Process input files and generate output path input_files,output_path = process_input_files(arguments, sys.argv) From d02de2ac57c4f9ce5e708727a6400303258d4771 Mon Sep 17 00:00:00 2001 From: platomav Date: Thu, 14 Apr 2022 14:06:23 +0300 Subject: [PATCH 12/52] Add quick format check functions for PFAT, UCP, PFS --- AMI_PFAT_Extract.py | 5 ++++- AMI_UCP_Extract.py | 6 +++++- Dell_PFS_Extract.py | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py index 82131b0..f1f9aeb 100644 --- a/AMI_PFAT_Extract.py +++ b/AMI_PFAT_Extract.py @@ -7,7 +7,7 @@ AMI BIOS Guard Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'AMI BIOS Guard Extractor v4.0_a3' +title = 'AMI BIOS Guard Extractor v4.0_a4' import os import re @@ -128,6 +128,9 @@ class IntelBiosGuardSignature2k(ctypes.LittleEndianStructure): printer(['Exponent :', '0x%X' % self.Exponent], p, False) printer(['Signature:', '%s [...]' % Signature[:32]], p, False) +def is_ami_pfat(input_buffer): + return bool(get_ami_pfat(input_buffer)[0]) + def get_ami_pfat(input_buffer): match = PAT_AMI_PFAT.search(input_buffer) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index c72db2e..e1da43f 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -title = 'AMI UCP BIOS Extractor v2.0_a4' +title = 'AMI UCP BIOS Extractor v2.0_a5' import os import re @@ -159,6 +159,10 @@ def chk16_validate(data, tag, padd=0): else: printer('Checksum of UCP Module %s is valid!' % tag, padd) +# Check if input file is AMI UCP image +def is_ami_ucp(buffer): + return bool(get_ami_ucp(buffer)[0]) + # Get all input file AMI UCP patterns def get_ami_ucp(buffer): uaf_len_max = 0x0 # Length of largest detected @UAF|@HPU diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index d29808e..fb71caf 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Dell PFS Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'Dell PFS Update Extractor v6.0_a2' +title = 'Dell PFS Update Extractor v6.0_a3' import os import io @@ -208,7 +208,7 @@ def is_pfs_ftr(in_buffer): # Check if input buffer is Dell PFS/PKG image def is_dell_pfs(in_buffer): - return is_pfs_hdr(in_buffer) or is_pfs_pkg(in_buffer) + return bool(is_pfs_hdr(in_buffer) or is_pfs_pkg(in_buffer)) # Get PFS ZLIB Section Offsets def get_section_offsets(buffer): From 672b4b2321d7c4c270424e6f51eedd497497af3a Mon Sep 17 00:00:00 2001 From: platomav Date: Thu, 14 Apr 2022 16:07:34 +0300 Subject: [PATCH 13/52] Fixes at title/version display --- AMI_PFAT_Extract.py | 9 +++------ AMI_UCP_Extract.py | 11 +++-------- Dell_PFS_Extract.py | 13 ++++--------- common/system.py | 18 +++++++----------- 4 files changed, 17 insertions(+), 34 deletions(-) diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py index f1f9aeb..651e155 100644 --- a/AMI_PFAT_Extract.py +++ b/AMI_PFAT_Extract.py @@ -7,7 +7,7 @@ AMI BIOS Guard Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'AMI BIOS Guard Extractor v4.0_a4' +title = 'AMI BIOS Guard Extractor v4.0_a5' import os import re @@ -23,7 +23,7 @@ from common.num_ops import get_ordinal from common.path_ops import safe_name from common.patterns import PAT_AMI_PFAT from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t -from common.system import script_init, script_title, argparse_init, printer +from common.system import script_init, argparse_init, printer class AmiBiosGuardHeader(ctypes.LittleEndianStructure): _pack_ = 1 @@ -305,15 +305,12 @@ PFAT_BLK_HDR_LEN = ctypes.sizeof(IntelBiosGuardHeader) PFAT_BLK_S2K_LEN = ctypes.sizeof(IntelBiosGuardSignature2k) if __name__ == '__main__': - # Show script title - script_title(title) - # Set argparse Arguments argparser = argparse_init() arguments = argparser.parse_args() # Initialize script (must be after argparse) - input_files,output_path,padding = script_init(arguments, 4) + input_files,output_path,padding = script_init(title, arguments, 4) for input_file in input_files: input_name = os.path.basename(input_file) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index e1da43f..2a72353 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -title = 'AMI UCP BIOS Extractor v2.0_a5' +title = 'AMI UCP BIOS Extractor v2.0_a6' import os import re @@ -26,7 +26,7 @@ from common.efi_comp import efi_decompress, is_efi_compressed from common.path_ops import safe_name from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t -from common.system import script_init, script_title, argparse_init, printer +from common.system import script_init, argparse_init, printer from AMI_PFAT_Extract import get_ami_pfat, parse_pfat_file @@ -482,9 +482,6 @@ UAF_TAG_DICT = { } if __name__ == '__main__': - # Show script title - script_title(title) - # Set argparse Arguments argparser = argparse_init() argparser.add_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true') @@ -493,7 +490,7 @@ if __name__ == '__main__': is_checksum = arguments.checksum # Set Checksum verification optional argument # Initialize script (must be after argparse) - input_files,output_path,padding = script_init(arguments, 4) + input_files,output_path,padding = script_init(title, arguments, 4) for input_file in input_files: input_name = os.path.basename(input_file) @@ -514,6 +511,4 @@ if __name__ == '__main__': ucp_extract(main_uaf_bin, extract_path, main_uaf_tag, padding, is_checksum) - printer('Extracted AMI UCP BIOS executable!', padding) - printer('Done!', pause=True) diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index fb71caf..601b95a 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Dell PFS Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'Dell PFS Update Extractor v6.0_a3' +title = 'Dell PFS Update Extractor v6.0_a4' import os import io @@ -25,7 +25,7 @@ from common.checksums import get_chk_8_xor from common.path_ops import safe_name from common.patterns import PAT_DELL_HDR, PAT_DELL_FTR, PAT_DELL_PKG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t, uint64_t -from common.system import script_init, script_title, argparse_init, printer +from common.system import script_init, argparse_init, printer from AMI_PFAT_Extract import IntelBiosGuardHeader, IntelBiosGuardSignature2k, parse_bg_script @@ -912,10 +912,7 @@ PFS_PFAT_LEN = ctypes.sizeof(DellPfsPfatMetadata) PFAT_HDR_LEN = ctypes.sizeof(IntelBiosGuardHeader) PFAT_SIG_LEN = ctypes.sizeof(IntelBiosGuardSignature2k) -if __name__ == '__main__': - # Show script title - script_title(title) - +if __name__ == '__main__': # Set argparse arguments argparser = argparse_init() argparser.add_argument('-a', '--advanced', help='extract signatures and metadata', action='store_true') @@ -926,7 +923,7 @@ if __name__ == '__main__': is_structure = arguments.structure # Set Structure output mode optional argument # Initialize script (must be after argparse) - input_files,output_path,padding = script_init(arguments, 4) + input_files,output_path,padding = script_init(title, arguments, 4) for input_file in input_files: input_name = os.path.basename(input_file) @@ -969,7 +966,5 @@ if __name__ == '__main__': for offset in pfs_zlib_offsets: # Call the PFS ZLIB Section Parser function pfs_section_parse(input_buffer, offset, extract_path, extract_name, 1, 1, False, padding, is_structure, is_advanced) - - printer('Extracted Dell PFS Update image!', padding) printer('Done!', pause=True) diff --git a/common/system.py b/common/system.py index dc2e9c8..449e1b7 100644 --- a/common/system.py +++ b/common/system.py @@ -66,18 +66,8 @@ def argparse_init(): return argparser -# Show Script Title -def script_title(title): - printer(title) - - _,os_win,_ = get_os_ver() - - # Set console/shell window title - if os_win: ctypes.windll.kernel32.SetConsoleTitleW(title) - else: sys.stdout.write('\x1b]2;' + title + '\x07') - # Initialize Script (must be after argparse) -def script_init(arguments, padding=0): +def script_init(title, arguments, padding=0): # Pretty Python exception handler sys.excepthook = nice_exc_handler @@ -87,9 +77,15 @@ def script_init(arguments, padding=0): # Check OS Platform check_sys_os() + # Show Script Title + printer(title, new_line=False) + # Show Utility Version on demand if arguments.version: sys.exit(0) + # Set console/terminal window title (Windows only) + if get_os_ver()[1]: ctypes.windll.kernel32.SetConsoleTitleW(title) + # Process input files and generate output path input_files,output_path = process_input_files(arguments, sys.argv) From 40686d5edf7504838667334efa2ca3e2e96d33d6 Mon Sep 17 00:00:00 2001 From: platomav Date: Fri, 15 Apr 2022 18:17:58 +0300 Subject: [PATCH 14/52] Improved AMI UCP > NAL unpacking Fix potential illegal path traversals --- AMI_PFAT_Extract.py | 8 ++++---- AMI_UCP_Extract.py | 36 +++++++++++++++++++++++------------ Dell_PFS_Extract.py | 16 ++++++++-------- common/a7z_comp.py | 2 +- common/checksums.py | 2 +- common/efi_comp.py | 2 +- common/externals.py | 2 +- common/num_ops.py | 2 +- common/path_ops.py | 45 ++++++++++++++++++++++++++++++++++++++++++-- common/patterns.py | 6 +++--- common/struct_ops.py | 2 +- common/system.py | 11 ++++------- common/text_ops.py | 11 ++++++++++- 13 files changed, 102 insertions(+), 43 deletions(-) diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py index 651e155..8a0bbc5 100644 --- a/AMI_PFAT_Extract.py +++ b/AMI_PFAT_Extract.py @@ -7,7 +7,7 @@ AMI BIOS Guard Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'AMI BIOS Guard Extractor v4.0_a5' +title = 'AMI BIOS Guard Extractor v4.0_a6' import os import re @@ -20,7 +20,7 @@ sys.dont_write_bytecode = True from common.externals import get_bgs_tool from common.num_ops import get_ordinal -from common.path_ops import safe_name +from common.path_ops import get_safe_name from common.patterns import PAT_AMI_PFAT from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t from common.system import script_init, argparse_init, printer @@ -139,7 +139,7 @@ def get_ami_pfat(input_buffer): return match, buffer def get_file_name(index, title): - return safe_name('%0.2d -- %s' % (index, title)) + return get_safe_name('%0.2d -- %s' % (index, title)) def parse_bg_script(script_data, padding): is_opcode_div = len(script_data) % 8 == 0 @@ -224,7 +224,7 @@ def parse_pfat_file(buffer, output_path, padding): extract_name = os.path.basename(output_path) - extract_path = os.path.join(output_path + '_extracted', '') + extract_path = os.path.join(output_path + '_extracted') if os.path.isdir(extract_path): shutil.rmtree(extract_path) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 2a72353..53b8b3b 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -title = 'AMI UCP BIOS Extractor v2.0_a6' +title = 'AMI UCP BIOS Extractor v2.0_a7' import os import re @@ -16,6 +16,7 @@ import shutil import struct import ctypes import contextlib +from pathlib import Path, PurePath # Stop __pycache__ generation sys.dont_write_bytecode = True @@ -23,10 +24,11 @@ sys.dont_write_bytecode = True from common.a7z_comp import a7z_decompress, is_7z_supported from common.checksums import get_chk_16 from common.efi_comp import efi_decompress, is_efi_compressed -from common.path_ops import safe_name +from common.path_ops import get_safe_name, get_safe_path from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t from common.system import script_init, argparse_init, printer +from common.text_ops import to_string from AMI_PFAT_Extract import get_ami_pfat, parse_pfat_file @@ -211,7 +213,7 @@ def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False): printer('Utility Configuration Program', padding) - extract_path = os.path.join(out_path + '_extracted', '') + extract_path = os.path.join(out_path + '_extracted') if os.path.isdir(extract_path): shutil.rmtree(extract_path) @@ -259,7 +261,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect @UAF|@HPU Module EFI Compression - if uaf_tag in nal_dict: uaf_name = nal_dict[uaf_tag] # Always prefer @NAL naming first + if uaf_tag in nal_dict: uaf_name = nal_dict[uaf_tag][1] # Always prefer @NAL naming first elif uaf_tag in UAF_TAG_DICT: uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming elif uaf_tag == '@ROM': uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature) elif uaf_tag.startswith('@R0'): uaf_name = 'BIOS_0%s.bin' % uaf_tag[3:] # BIOS/PFAT Firmware @@ -278,10 +280,16 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na # Check if unknown @UAF|@HPU Module Tag is present in @NAL but not in built-in dictionary if uaf_tag in nal_dict and uaf_tag not in UAF_TAG_DICT and not uaf_tag.startswith(('@ROM','@R0','@S0','@DR','@DS')): - printer('Note: Detected new AMI UCP Module %s (%s) in @NAL!' % (uaf_tag, nal_dict[uaf_tag]), padding + 4, pause=True) + printer('Note: Detected new AMI UCP Module %s (%s) in @NAL!' % (uaf_tag, nal_dict[uaf_tag][1]), padding + 4, pause=True) # Generate @UAF|@HPU Module File name, depending on whether decompression will be required - uaf_fname = os.path.join(extract_path, safe_name(uaf_name + ('.temp' if is_comp else uaf_fext))) + uaf_sname = get_safe_name(uaf_name + ('.temp' if is_comp else uaf_fext)) + if uaf_tag in nal_dict: + uaf_npath = get_safe_path(extract_path, nal_dict[uaf_tag][0]) + Path.mkdir(Path(uaf_npath), parents=True, exist_ok=True) + uaf_fname = get_safe_path(uaf_npath, uaf_sname) + else: + uaf_fname = get_safe_path(extract_path, uaf_sname) if is_checksum: chk16_validate(uaf_data_all, uaf_tag, padding + 4) @@ -375,15 +383,19 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na # Parse all @NAL Module Entries for info in nal_info: - info_tag,info_val = info.split(':',1) + info_tag,info_value = info.split(':',1) - printer(info_tag + ' : ' + info_val, padding + 8, False) # Print @NAL Module Tag-Path Info + printer(info_tag + ' : ' + info_value, padding + 8, False) # Print @NAL Module Tag-Path Info - nal_dict[info_tag] = os.path.basename(info_val) # Assign a file name (w/o path) to each Tag + info_part = PurePath(info_value.replace('\\', os.sep)).parts # Split path in parts + info_path = to_string(info_part[1:-1], os.sep) # Get path without drive/root or file + info_name = info_part[-1] # Get file from last path part + + nal_dict[info_tag] = (info_path,info_name) # Assign a file path & name to each Tag # Parse Insyde BIOS @UAF|@HPU Module (@INS) if uaf_tag == '@INS' and is_7z_supported(uaf_fname): - ins_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory + ins_dir = os.path.join(extract_path, get_safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory printer('Insyde BIOS 7z SFX Archive:', padding + 4) @@ -394,7 +406,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na pfat_match,pfat_buffer = get_ami_pfat(uaf_data_raw) if pfat_match: - pfat_dir = os.path.join(extract_path, safe_name(uaf_name)) + pfat_dir = os.path.join(extract_path, get_safe_name(uaf_name)) parse_pfat_file(pfat_buffer, pfat_dir, padding + 4) @@ -410,7 +422,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na # Parse Nested AMI UCP Structure if nested_uaf_off: - uaf_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory + uaf_dir = os.path.join(extract_path, get_safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum) # Call recursively diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index 601b95a..b679fdb 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Dell PFS Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'Dell PFS Update Extractor v6.0_a4' +title = 'Dell PFS Update Extractor v6.0_a5' import os import io @@ -22,7 +22,7 @@ import contextlib sys.dont_write_bytecode = True from common.checksums import get_chk_8_xor -from common.path_ops import safe_name +from common.path_ops import get_safe_name from common.patterns import PAT_DELL_HDR, PAT_DELL_FTR, PAT_DELL_PKG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t, uint64_t from common.system import script_init, argparse_init, printer @@ -243,7 +243,7 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p printer('Extracting Dell PFS %d >%s > %s' % (pfs_index, pfs_name, section_name), padding) # Set PFS ZLIB Section extraction sub-directory path - section_path = os.path.join(output_path, section_name) + section_path = os.path.join(output_path, get_safe_name(section_name)) # Delete existing extraction sub-directory (not in recursions) if os.path.isdir(section_path) and not is_rec: shutil.rmtree(section_path) @@ -406,7 +406,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i name_start = info_start + PFS_INFO_LEN + PFS_NAME_LEN # PFS Entry's FileName start offset name_size = entry_info_mod.CharacterCount * 2 # PFS Entry's FileName buffer total size name_data = filename_info[name_start:name_start + name_size] # PFS Entry's FileName buffer - entry_name = safe_name(name_data.decode('utf-16').strip()) # PFS Entry's FileName value + entry_name = get_safe_name(name_data.decode('utf-16').strip()) # PFS Entry's FileName value # Show PFS FileName Structure info if is_structure: @@ -443,7 +443,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i # As Nested PFS Entry Name, we'll use the actual PFS File Name # Replace common Windows reserved/illegal filename characters - entry_name = safe_name(entry_info.FileName.decode('utf-8').strip('.exe')) + entry_name = get_safe_name(entry_info.FileName.decode('utf-8').strip('.exe')) # As Nested PFS Entry Version, we'll use the actual PFS File Version entry_version = entry_info.FileVersion.decode('utf-8') @@ -537,7 +537,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i sub_pfs_name = ' %s v%s' % (info_all[pfs_count - 2][1], info_all[pfs_count - 2][2]) if info_all else ' UNKNOWN' # Set the sub-PFS output path (create sub-folders for each sub-PFS and its ZLIB sections) - sub_pfs_path = os.path.join(output_path, str(pfs_count) + sub_pfs_name) + sub_pfs_path = os.path.join(output_path, str(pfs_count) + get_safe_name(sub_pfs_name)) # Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count) pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4, is_structure, is_advanced) @@ -844,7 +844,7 @@ def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_structu def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, is_structure=True, is_advanced=True): # Store Data/Metadata Signature (advanced users only) if bin_name.startswith('sign'): - final_name = '%s.%s.sig' % (safe_name(full_name), bin_name.split('_')[1]) + final_name = '%s.%s.sig' % (get_safe_name(full_name), bin_name.split('_')[1]) final_path = os.path.join(out_path, final_name) with open(final_path, 'wb') as pfs_out: pfs_out.write(bin_buff) # Write final Data/Metadata Signature @@ -857,7 +857,7 @@ def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, i # Some Data may be Text or XML files with useful information for non-advanced users is_text,final_data,file_ext,write_mode = bin_is_text(bin_buff, bin_type, bin_name == 'meta', padding, is_structure, is_advanced) - final_name = '%s%s' % (safe_name(full_name), bin_ext[:-4] + file_ext if is_text else bin_ext) + final_name = '%s%s' % (get_safe_name(full_name), bin_ext[:-4] + file_ext if is_text else bin_ext) final_path = os.path.join(out_path, final_name) with open(final_path, write_mode) as pfs_out: pfs_out.write(final_data) # Write final Data/Metadata Payload diff --git a/common/a7z_comp.py b/common/a7z_comp.py index 7e6364e..7226015 100644 --- a/common/a7z_comp.py +++ b/common/a7z_comp.py @@ -42,4 +42,4 @@ def a7z_decompress(in_path, out_path, in_name, padding, static=False): printer('Succesfull %s decompression via 7-Zip!' % in_name, padding) - return 0 \ No newline at end of file + return 0 diff --git a/common/checksums.py b/common/checksums.py index 02b6a74..ea150e9 100644 --- a/common/checksums.py +++ b/common/checksums.py @@ -18,4 +18,4 @@ def get_chk_8_xor(data, value=0): value ^= 0x0 - return value \ No newline at end of file + return value diff --git a/common/efi_comp.py b/common/efi_comp.py index 853f15d..ed18cf5 100644 --- a/common/efi_comp.py +++ b/common/efi_comp.py @@ -48,4 +48,4 @@ def efi_decompress(in_path, out_path, padding, comp_type='--uefi'): printer('Succesfull EFI/Tiano decompression via TianoCompress!', padding) - return 0 \ No newline at end of file + return 0 diff --git a/common/externals.py b/common/externals.py index 1ce7156..674b65f 100644 --- a/common/externals.py +++ b/common/externals.py @@ -8,4 +8,4 @@ def get_bgs_tool(): except: BigScript = None - return BigScript \ No newline at end of file + return BigScript diff --git a/common/num_ops.py b/common/num_ops.py index 7272fc6..35e8044 100644 --- a/common/num_ops.py +++ b/common/num_ops.py @@ -8,4 +8,4 @@ def get_ordinal(number): v = number % 100 - return f'{number}{s[v % 10]}' if v > 13 else f'{number}{s[v]}' \ No newline at end of file + return f'{number}{s[v % 10]}' if v > 13 else f'{number}{s[v]}' diff --git a/common/path_ops.py b/common/path_ops.py index efbf018..9722a63 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -7,14 +7,55 @@ import sys import inspect from pathlib import Path +from common.text_ops import to_string + # Fix illegal/reserved Windows characters -def safe_name(in_name): +def get_safe_name(in_name): raw_name = repr(in_name).strip("'") fix_name = re.sub(r'[\\/*?:"<>|]', '_', raw_name) return fix_name +# Check and attempt to fix illegal/unsafe OS path traversals +def get_safe_path(base_path, user_paths, follow_symlinks=False): + # Convert user path(s) to string w/ OS separators + user_path = to_string(user_paths, os.sep) + + # Create target path from base + requested user path + target_path = get_norm_path(base_path, user_path) + + # Check if target path is OS illegal/unsafe + if is_safe_path(base_path, target_path, follow_symlinks): + return target_path + + # Re-create target path from base + leveled/safe illegal "path" (now file) + nuked_path = get_norm_path(base_path, get_safe_name(user_path)) + + # Check if illegal path leveling worked + if is_safe_path(base_path, nuked_path, follow_symlinks): + return nuked_path + + # Still illegal, create fallback base path + placeholder file + failed_path = get_norm_path(base_path, 'illegal_path_traversal') + + return failed_path + +# Check for illegal/unsafe OS path traversal +def is_safe_path(base_path, target_path, follow_symlinks=True): + if follow_symlinks: + actual_path = os.path.realpath(target_path) + else: + actual_path = os.path.abspath(target_path) + + common_path = os.path.commonpath((base_path, actual_path)) + + return base_path == common_path + +# Create normalized base path + OS separator + user path +def get_norm_path(base_path, user_path): + return os.path.normpath(base_path + os.sep + user_path) + # Walk path to get all files def get_path_files(in_path): path_files = [] @@ -76,4 +117,4 @@ def get_script_dir(follow_symlinks=True): if follow_symlinks: path = os.path.realpath(path) - return os.path.dirname(path) \ No newline at end of file + return os.path.dirname(path) diff --git a/common/patterns.py b/common/patterns.py index 56bee4f..2e416f8 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -5,7 +5,7 @@ import re PAT_AMI_PFAT = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}@', re.DOTALL) -PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) -PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') -PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) \ No newline at end of file +PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) +PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) +PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) diff --git a/common/struct_ops.py b/common/struct_ops.py index 9cb63fe..bd418da 100644 --- a/common/struct_ops.py +++ b/common/struct_ops.py @@ -21,4 +21,4 @@ def get_struct(buffer, start_offset, class_name, param_list=None): ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len) - return structure \ No newline at end of file + return structure diff --git a/common/system.py b/common/system.py index 449e1b7..8fcf156 100644 --- a/common/system.py +++ b/common/system.py @@ -6,7 +6,7 @@ import ctypes import argparse import traceback -from common.text_ops import padder +from common.text_ops import padder, to_string from common.path_ops import process_input_files # Get Python Version (tuple) @@ -106,11 +106,8 @@ def nice_exc_handler(exc_type, exc_value, tb): sys.exit(3) # Show message(s) while controlling padding, newline, pausing & separator -def printer(in_message='', padd_count=0, new_line=True, pause=False, sep_char=' '): - if type(in_message).__name__ in ('list','tuple'): - message = sep_char.join(map(str, in_message)) - else: - message = str(in_message) +def printer(in_message='', padd_count=0, new_line=True, pause=False, sep_char=' '): + message = to_string(in_message, sep_char) padding = padder(padd_count) @@ -118,4 +115,4 @@ def printer(in_message='', padd_count=0, new_line=True, pause=False, sep_char=' output = newline + padding + message - (input if pause and not is_auto_exit() else print)(output) \ No newline at end of file + (input if pause and not is_auto_exit() else print)(output) diff --git a/common/text_ops.py b/common/text_ops.py index adc9e6e..dbb40a4 100644 --- a/common/text_ops.py +++ b/common/text_ops.py @@ -3,4 +3,13 @@ # Generate padding (spaces or tabs) def padder(padd_count, tab=False): - return ('\t' if tab else ' ') * padd_count \ No newline at end of file + return ('\t' if tab else ' ') * padd_count + +# Get String from given input object +def to_string(input_object, sep_char=''): + if type(input_object).__name__ in ('list','tuple'): + output_string = sep_char.join(map(str, input_object)) + else: + output_string = str(input_object) + + return output_string From 44546a67c5ec2b687ac95c6e99c561b321faeb67 Mon Sep 17 00:00:00 2001 From: platomav Date: Sat, 16 Apr 2022 23:39:56 +0300 Subject: [PATCH 15/52] Fixed path symlink resolutions --- AMI_UCP_Extract.py | 6 ++--- common/path_ops.py | 57 +++++++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 53b8b3b..32c4ae2 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -title = 'AMI UCP BIOS Extractor v2.0_a7' +title = 'AMI UCP BIOS Extractor v2.0_a8' import os import re @@ -24,7 +24,7 @@ sys.dont_write_bytecode = True from common.a7z_comp import a7z_decompress, is_7z_supported from common.checksums import get_chk_16 from common.efi_comp import efi_decompress, is_efi_compressed -from common.path_ops import get_safe_name, get_safe_path +from common.path_ops import get_comp_path, get_safe_name, get_safe_path from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t from common.system import script_init, argparse_init, printer @@ -387,7 +387,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na printer(info_tag + ' : ' + info_value, padding + 8, False) # Print @NAL Module Tag-Path Info - info_part = PurePath(info_value.replace('\\', os.sep)).parts # Split path in parts + info_part = PurePath(get_comp_path(info_value)).parts # Split OS agnostic path in parts info_path = to_string(info_part[1:-1], os.sep) # Get path without drive/root or file info_name = info_part[-1] # Get file from last path part diff --git a/common/path_ops.py b/common/path_ops.py index 9722a63..0254a1c 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -11,29 +11,30 @@ from common.text_ops import to_string # Fix illegal/reserved Windows characters def get_safe_name(in_name): - raw_name = repr(in_name).strip("'") + name_repr = repr(in_name).strip("'") - fix_name = re.sub(r'[\\/*?:"<>|]', '_', raw_name) - - return fix_name + return re.sub(r'[\\/:"*?<>|]+', '_', name_repr) # Check and attempt to fix illegal/unsafe OS path traversals -def get_safe_path(base_path, user_paths, follow_symlinks=False): - # Convert user path(s) to string w/ OS separators +def get_safe_path(base_path, user_paths): + # Convert base path to absolute path + base_path = get_real_path(base_path) + + # Convert user path(s) to absolute path with OS separators user_path = to_string(user_paths, os.sep) # Create target path from base + requested user path target_path = get_norm_path(base_path, user_path) # Check if target path is OS illegal/unsafe - if is_safe_path(base_path, target_path, follow_symlinks): + if is_safe_path(base_path, target_path): return target_path # Re-create target path from base + leveled/safe illegal "path" (now file) nuked_path = get_norm_path(base_path, get_safe_name(user_path)) # Check if illegal path leveling worked - if is_safe_path(base_path, nuked_path, follow_symlinks): + if is_safe_path(base_path, nuked_path): return nuked_path # Still illegal, create fallback base path + placeholder file @@ -42,13 +43,12 @@ def get_safe_path(base_path, user_paths, follow_symlinks=False): return failed_path # Check for illegal/unsafe OS path traversal -def is_safe_path(base_path, target_path, follow_symlinks=True): - if follow_symlinks: - actual_path = os.path.realpath(target_path) - else: - actual_path = os.path.abspath(target_path) +def is_safe_path(base_path, target_path): + base_path = get_real_path(base_path) - common_path = os.path.commonpath((base_path, actual_path)) + target_path = get_real_path(target_path) + + common_path = os.path.commonpath((base_path, target_path)) return base_path == common_path @@ -56,6 +56,14 @@ def is_safe_path(base_path, target_path, follow_symlinks=True): def get_norm_path(base_path, user_path): return os.path.normpath(base_path + os.sep + user_path) +# Get absolute path, resolving any symlinks +def get_real_path(in_path): + return str(Path(in_path).resolve()) + +# Get Windows/Posix OS compatible path +def get_comp_path(in_path): + return in_path.replace('\\', os.sep) + # Walk path to get all files def get_path_files(in_path): path_files = [] @@ -70,8 +78,8 @@ def get_path_files(in_path): def get_path_parent(in_path): return Path(in_path).parent.absolute() -# Get absolute file path (argparse object) -def get_absolute_path(argparse_path): +# Get absolute file path of argparse object +def get_argparse_path(argparse_path): script_dir = get_path_parent(get_script_dir()) if not argparse_path: @@ -91,30 +99,27 @@ def process_input_files(argparse_args, sys_argv=None): # Drag & Drop or CLI if argparse_args.input_dir: input_path_user = argparse_args.input_dir - input_path_full = get_absolute_path(input_path_user) if input_path_user else '' + input_path_full = get_argparse_path(input_path_user) if input_path_user else '' input_files = get_path_files(input_path_full) else: input_files = [file.name for file in argparse_args.files] - output_path = get_absolute_path(argparse_args.output_dir or argparse_args.input_dir) + output_path = get_argparse_path(argparse_args.output_dir or argparse_args.input_dir) else: # Script w/o parameters input_path_user = input('\nEnter input directory path: ') - input_path_full = get_absolute_path(input_path_user) if input_path_user else '' + input_path_full = get_argparse_path(input_path_user) if input_path_user else '' input_files = get_path_files(input_path_full) - output_path = get_absolute_path(input('\nEnter output directory path: ')) + output_path = get_argparse_path(input('\nEnter output directory path: ')) return input_files, output_path # https://stackoverflow.com/a/22881871 by jfs -def get_script_dir(follow_symlinks=True): +def get_script_dir(): if getattr(sys, 'frozen', False): - path = os.path.abspath(sys.executable) + path = sys.executable else: path = inspect.getabsfile(get_script_dir) - - if follow_symlinks: - path = os.path.realpath(path) - return os.path.dirname(path) + return os.path.dirname(get_real_path(path)) From a2eca0aac61fa41e5ad158beebc9b8be186f1946 Mon Sep 17 00:00:00 2001 From: platomav Date: Sun, 17 Apr 2022 20:48:43 +0300 Subject: [PATCH 16/52] Revamped path-related operations Fixed dependencies detecton Fixed frozen state support --- AMI_PFAT_Extract.py | 11 ++--- AMI_UCP_Extract.py | 30 ++++++------ Dell_PFS_Extract.py | 24 ++++------ README.md | 6 +-- common/a7z_comp.py | 18 ++++---- common/checksums.py | 4 ++ common/efi_comp.py | 17 +++---- common/externals.py | 4 ++ common/num_ops.py | 5 +- common/path_ops.py | 106 +++++++++++++++++++++++++++---------------- common/patterns.py | 4 ++ common/struct_ops.py | 5 +- common/system.py | 4 ++ common/text_ops.py | 4 ++ 14 files changed, 144 insertions(+), 98 deletions(-) diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py index 8a0bbc5..1ce55f9 100644 --- a/AMI_PFAT_Extract.py +++ b/AMI_PFAT_Extract.py @@ -7,12 +7,11 @@ AMI BIOS Guard Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'AMI BIOS Guard Extractor v4.0_a6' +title = 'AMI BIOS Guard Extractor v4.0_a7' import os import re import sys -import shutil import ctypes # Stop __pycache__ generation @@ -20,7 +19,7 @@ sys.dont_write_bytecode = True from common.externals import get_bgs_tool from common.num_ops import get_ordinal -from common.path_ops import get_safe_name +from common.path_ops import safe_name, make_dirs from common.patterns import PAT_AMI_PFAT from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t from common.system import script_init, argparse_init, printer @@ -139,7 +138,7 @@ def get_ami_pfat(input_buffer): return match, buffer def get_file_name(index, title): - return get_safe_name('%0.2d -- %s' % (index, title)) + return safe_name('%0.2d -- %s' % (index, title)) def parse_bg_script(script_data, padding): is_opcode_div = len(script_data) % 8 == 0 @@ -226,9 +225,7 @@ def parse_pfat_file(buffer, output_path, padding): extract_path = os.path.join(output_path + '_extracted') - if os.path.isdir(extract_path): shutil.rmtree(extract_path) - - os.mkdir(extract_path) + make_dirs(extract_path, delete=True) block_all,block_off,file_count = parse_pfat_hdr(buffer, padding) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 32c4ae2..ae2587e 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,16 +7,14 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -title = 'AMI UCP BIOS Extractor v2.0_a8' +title = 'AMI UCP BIOS Extractor v2.0_a9' import os import re import sys -import shutil import struct import ctypes import contextlib -from pathlib import Path, PurePath # Stop __pycache__ generation sys.dont_write_bytecode = True @@ -24,7 +22,7 @@ sys.dont_write_bytecode = True from common.a7z_comp import a7z_decompress, is_7z_supported from common.checksums import get_chk_16 from common.efi_comp import efi_decompress, is_efi_compressed -from common.path_ops import get_comp_path, get_safe_name, get_safe_path +from common.path_ops import agnostic_path, safe_name, safe_path, make_dirs from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t from common.system import script_init, argparse_init, printer @@ -215,9 +213,7 @@ def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False): extract_path = os.path.join(out_path + '_extracted') - if os.path.isdir(extract_path): shutil.rmtree(extract_path) - - os.mkdir(extract_path) + make_dirs(extract_path, delete=True) uaf_hdr = get_struct(buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure @@ -283,13 +279,13 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na printer('Note: Detected new AMI UCP Module %s (%s) in @NAL!' % (uaf_tag, nal_dict[uaf_tag][1]), padding + 4, pause=True) # Generate @UAF|@HPU Module File name, depending on whether decompression will be required - uaf_sname = get_safe_name(uaf_name + ('.temp' if is_comp else uaf_fext)) + uaf_sname = safe_name(uaf_name + ('.temp' if is_comp else uaf_fext)) if uaf_tag in nal_dict: - uaf_npath = get_safe_path(extract_path, nal_dict[uaf_tag][0]) - Path.mkdir(Path(uaf_npath), parents=True, exist_ok=True) - uaf_fname = get_safe_path(uaf_npath, uaf_sname) + uaf_npath = safe_path(extract_path, nal_dict[uaf_tag][0]) + make_dirs(uaf_npath, exist_ok=True) + uaf_fname = safe_path(uaf_npath, uaf_sname) else: - uaf_fname = get_safe_path(extract_path, uaf_sname) + uaf_fname = safe_path(extract_path, uaf_sname) if is_checksum: chk16_validate(uaf_data_all, uaf_tag, padding + 4) @@ -387,15 +383,15 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na printer(info_tag + ' : ' + info_value, padding + 8, False) # Print @NAL Module Tag-Path Info - info_part = PurePath(get_comp_path(info_value)).parts # Split OS agnostic path in parts + info_part = agnostic_path(info_value).parts # Split OS agnostic path in parts info_path = to_string(info_part[1:-1], os.sep) # Get path without drive/root or file info_name = info_part[-1] # Get file from last path part nal_dict[info_tag] = (info_path,info_name) # Assign a file path & name to each Tag # Parse Insyde BIOS @UAF|@HPU Module (@INS) - if uaf_tag == '@INS' and is_7z_supported(uaf_fname): - ins_dir = os.path.join(extract_path, get_safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory + if uaf_tag == '@INS' and is_7z_supported(uaf_fname, padding + 4): + ins_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory printer('Insyde BIOS 7z SFX Archive:', padding + 4) @@ -406,7 +402,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na pfat_match,pfat_buffer = get_ami_pfat(uaf_data_raw) if pfat_match: - pfat_dir = os.path.join(extract_path, get_safe_name(uaf_name)) + pfat_dir = os.path.join(extract_path, safe_name(uaf_name)) parse_pfat_file(pfat_buffer, pfat_dir, padding + 4) @@ -422,7 +418,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na # Parse Nested AMI UCP Structure if nested_uaf_off: - uaf_dir = os.path.join(extract_path, get_safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory + uaf_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum) # Call recursively diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index b679fdb..2e0610f 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,14 +7,13 @@ Dell PFS Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'Dell PFS Update Extractor v6.0_a5' +title = 'Dell PFS Update Extractor v6.0_a6' import os import io import sys import lzma import zlib -import shutil import ctypes import contextlib @@ -22,7 +21,7 @@ import contextlib sys.dont_write_bytecode = True from common.checksums import get_chk_8_xor -from common.path_ops import get_safe_name +from common.path_ops import safe_name, make_dirs from common.patterns import PAT_DELL_HDR, PAT_DELL_FTR, PAT_DELL_PKG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t, uint64_t from common.system import script_init, argparse_init, printer @@ -243,13 +242,10 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p printer('Extracting Dell PFS %d >%s > %s' % (pfs_index, pfs_name, section_name), padding) # Set PFS ZLIB Section extraction sub-directory path - section_path = os.path.join(output_path, get_safe_name(section_name)) + section_path = os.path.join(output_path, safe_name(section_name)) - # Delete existing extraction sub-directory (not in recursions) - if os.path.isdir(section_path) and not is_rec: shutil.rmtree(section_path) - - # Create extraction sub-directory - if not os.path.isdir(section_path): os.makedirs(section_path) + # Create extraction sub-directory and delete old (if present, not in recursions) + make_dirs(section_path, delete=(not is_rec), parents=True, exist_ok=True) # Store the compressed zlib stream start offset compressed_start = zlib_start + 0xB @@ -406,7 +402,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i name_start = info_start + PFS_INFO_LEN + PFS_NAME_LEN # PFS Entry's FileName start offset name_size = entry_info_mod.CharacterCount * 2 # PFS Entry's FileName buffer total size name_data = filename_info[name_start:name_start + name_size] # PFS Entry's FileName buffer - entry_name = get_safe_name(name_data.decode('utf-16').strip()) # PFS Entry's FileName value + entry_name = safe_name(name_data.decode('utf-16').strip()) # PFS Entry's FileName value # Show PFS FileName Structure info if is_structure: @@ -443,7 +439,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i # As Nested PFS Entry Name, we'll use the actual PFS File Name # Replace common Windows reserved/illegal filename characters - entry_name = get_safe_name(entry_info.FileName.decode('utf-8').strip('.exe')) + entry_name = safe_name(entry_info.FileName.decode('utf-8').strip('.exe')) # As Nested PFS Entry Version, we'll use the actual PFS File Version entry_version = entry_info.FileVersion.decode('utf-8') @@ -537,7 +533,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i sub_pfs_name = ' %s v%s' % (info_all[pfs_count - 2][1], info_all[pfs_count - 2][2]) if info_all else ' UNKNOWN' # Set the sub-PFS output path (create sub-folders for each sub-PFS and its ZLIB sections) - sub_pfs_path = os.path.join(output_path, str(pfs_count) + get_safe_name(sub_pfs_name)) + sub_pfs_path = os.path.join(output_path, str(pfs_count) + safe_name(sub_pfs_name)) # Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count) pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4, is_structure, is_advanced) @@ -844,7 +840,7 @@ def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_structu def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, is_structure=True, is_advanced=True): # Store Data/Metadata Signature (advanced users only) if bin_name.startswith('sign'): - final_name = '%s.%s.sig' % (get_safe_name(full_name), bin_name.split('_')[1]) + final_name = '%s.%s.sig' % (safe_name(full_name), bin_name.split('_')[1]) final_path = os.path.join(out_path, final_name) with open(final_path, 'wb') as pfs_out: pfs_out.write(bin_buff) # Write final Data/Metadata Signature @@ -857,7 +853,7 @@ def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, i # Some Data may be Text or XML files with useful information for non-advanced users is_text,final_data,file_ext,write_mode = bin_is_text(bin_buff, bin_type, bin_name == 'meta', padding, is_structure, is_advanced) - final_name = '%s%s' % (get_safe_name(full_name), bin_ext[:-4] + file_ext if is_text else bin_ext) + final_name = '%s%s' % (safe_name(full_name), bin_ext[:-4] + file_ext if is_text else bin_ext) final_path = os.path.join(out_path, final_name) with open(final_path, write_mode) as pfs_out: pfs_out.write(final_data) # Write final Data/Metadata Payload diff --git a/README.md b/README.md index 8873988..2748655 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 4. Build/Freeze/Compile: -> pyinstaller --noupx --onefile \\/Dell_PFS_Extract.py +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Dell_PFS_Extract.py You should find the final utility executable at "dist" folder @@ -121,7 +121,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 4. Build/Freeze/Compile: -> pyinstaller --noupx --onefile \\/AMI_UCP_Extract.py +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/AMI_UCP_Extract.py You should find the final utility executable at "dist" folder @@ -181,7 +181,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 4. Build/Freeze/Compile: -> pyinstaller --noupx --onefile \\/AMI_PFAT_Extract.py +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/AMI_PFAT_Extract.py You should find the final utility executable at "dist" folder diff --git a/common/a7z_comp.py b/common/a7z_comp.py index 7226015..99de48a 100644 --- a/common/a7z_comp.py +++ b/common/a7z_comp.py @@ -1,10 +1,14 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + import os import subprocess -from common.path_ops import get_script_dir +from common.path_ops import project_root, safe_path from common.system import get_os_ver from common.system import printer @@ -12,29 +16,27 @@ from common.system import printer def get_7z_path(static=False): exec_name = '7z.exe' if get_os_ver()[1] else ('7zzs' if static else '7zz') - exec_path = os.path.join(get_script_dir(), '..', 'external', exec_name) - - return exec_path + return safe_path(project_root(), ['external',exec_name]) # Check if file is 7z supported -def is_7z_supported(in_path, static=False): +def is_7z_supported(in_path, padding=0, static=False): try: subprocess.run([get_7z_path(static), 't', in_path, '-bso0', '-bse0', '-bsp0'], check=True) - except: + printer('Error: 7-Zip could not check support for file %s!' % in_path, padding) + return False return True # Archive decompression via 7-Zip -def a7z_decompress(in_path, out_path, in_name, padding, static=False): +def a7z_decompress(in_path, out_path, in_name, padding=0, static=False): if not in_name: in_name = 'archive' try: subprocess.run([get_7z_path(static), 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + out_path, in_path], check=True) if not os.path.isdir(out_path): raise Exception('EXTRACT_DIR_MISSING') - except: printer('Error: 7-Zip could not extract %s file %s!' % (in_name, in_path), padding) diff --git a/common/checksums.py b/common/checksums.py index ea150e9..3e958ab 100644 --- a/common/checksums.py +++ b/common/checksums.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + # Get Checksum 16-bit def get_chk_16(data, value=0, order='little'): for idx in range(0, len(data), 2): diff --git a/common/efi_comp.py b/common/efi_comp.py index ed18cf5..97c05f9 100644 --- a/common/efi_comp.py +++ b/common/efi_comp.py @@ -1,10 +1,14 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + import os import subprocess -from common.path_ops import get_script_dir +from common.path_ops import project_root, safe_path from common.system import get_os_ver from common.system import printer @@ -25,22 +29,19 @@ def is_efi_compressed(data, strict=True): return check_diff and check_size # Get TianoCompress path -def tianocompress_path(): +def get_tiano_path(): exec_name = 'TianoCompress' + ('.exe' if get_os_ver()[1] else '') - exec_path = os.path.join(get_script_dir(), '..', 'external', exec_name) - - return exec_path + return safe_path(project_root(), ['external',exec_name]) # EFI/Tiano Decompression via TianoCompress -def efi_decompress(in_path, out_path, padding, comp_type='--uefi'): +def efi_decompress(in_path, out_path, padding=0, comp_type='--uefi'): try: - subprocess.run([tianocompress_path(), '-d', in_path, '-o', out_path, '-q', comp_type], check=True, stdout=subprocess.DEVNULL) + subprocess.run([get_tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type], check=True, stdout=subprocess.DEVNULL) with open(in_path, 'rb') as file: _,size_orig = get_compress_sizes(file.read()) if os.path.getsize(out_path) != size_orig: raise Exception('EFI_DECOMPRESS_ERROR') - except: printer('Error: TianoCompress could not extract file %s!' % in_path, padding) diff --git a/common/externals.py b/common/externals.py index 674b65f..89d6a9e 100644 --- a/common/externals.py +++ b/common/externals.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + # https://github.com/allowitsme/big-tool by Dmitry Frolov def get_bgs_tool(): try: diff --git a/common/num_ops.py b/common/num_ops.py index 35e8044..c37e4d7 100644 --- a/common/num_ops.py +++ b/common/num_ops.py @@ -1,8 +1,11 @@ #!/usr/bin/env python3 #coding=utf-8 -# https://leancrew.com/all-this/2020/06/ordinals-in-python/ by Dr. Drang +""" +Copyright (C) 2022 Plato Mavropoulos +""" +# https://leancrew.com/all-this/2020/06/ordinals-in-python/ by Dr. Drang def get_ordinal(number): s = ('th', 'st', 'nd', 'rd') + ('th',) * 10 diff --git a/common/path_ops.py b/common/path_ops.py index 0254a1c..d08cfb1 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -1,68 +1,89 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + import os import re import sys -import inspect -from pathlib import Path +import shutil +from pathlib import Path, PurePath from common.text_ops import to_string # Fix illegal/reserved Windows characters -def get_safe_name(in_name): +def safe_name(in_name): name_repr = repr(in_name).strip("'") return re.sub(r'[\\/:"*?<>|]+', '_', name_repr) # Check and attempt to fix illegal/unsafe OS path traversals -def get_safe_path(base_path, user_paths): +def safe_path(base_path, user_paths): # Convert base path to absolute path - base_path = get_real_path(base_path) + base_path = real_path(base_path) - # Convert user path(s) to absolute path with OS separators + # Merge user path(s) to string with OS separators user_path = to_string(user_paths, os.sep) # Create target path from base + requested user path - target_path = get_norm_path(base_path, user_path) + target_path = norm_path(base_path, user_path) # Check if target path is OS illegal/unsafe if is_safe_path(base_path, target_path): return target_path # Re-create target path from base + leveled/safe illegal "path" (now file) - nuked_path = get_norm_path(base_path, get_safe_name(user_path)) + nuked_path = norm_path(base_path, safe_name(user_path)) # Check if illegal path leveling worked if is_safe_path(base_path, nuked_path): return nuked_path - # Still illegal, create fallback base path + placeholder file - failed_path = get_norm_path(base_path, 'illegal_path_traversal') - - return failed_path + # Still illegal, raise exception to halt execution + raise Exception(f'ILLEGAL_PATH_TRAVERSAL: {user_path}') # Check for illegal/unsafe OS path traversal def is_safe_path(base_path, target_path): - base_path = get_real_path(base_path) + base_path = real_path(base_path) - target_path = get_real_path(target_path) + target_path = real_path(target_path) common_path = os.path.commonpath((base_path, target_path)) return base_path == common_path # Create normalized base path + OS separator + user path -def get_norm_path(base_path, user_path): +def norm_path(base_path, user_path): return os.path.normpath(base_path + os.sep + user_path) # Get absolute path, resolving any symlinks -def get_real_path(in_path): - return str(Path(in_path).resolve()) +def real_path(in_path): + return os.path.realpath(in_path) -# Get Windows/Posix OS compatible path -def get_comp_path(in_path): - return in_path.replace('\\', os.sep) +# Get Windows/Posix OS agnostic path +def agnostic_path(in_path): + return PurePath(in_path.replace('\\', os.sep)) + +# Get absolute parent of path +def path_parent(in_path): + return Path(in_path).parent.absolute() + +# Check if path is absolute +def is_path_absolute(in_path): + return Path(in_path).is_absolute() + +# Create folder(s), controlling parents, existence and prior deletion +def make_dirs(in_path, parents=True, exist_ok=False, delete=False): + if delete: del_dirs(in_path) + + Path.mkdir(Path(in_path), parents=parents, exist_ok=exist_ok) + +# Delete folder(s), if present +def del_dirs(in_path): + if Path(in_path).is_dir(): + shutil.rmtree(in_path) # Walk path to get all files def get_path_files(in_path): @@ -74,20 +95,18 @@ def get_path_files(in_path): return path_files -# Get parent of path -def get_path_parent(in_path): - return Path(in_path).parent.absolute() - # Get absolute file path of argparse object def get_argparse_path(argparse_path): - script_dir = get_path_parent(get_script_dir()) - if not argparse_path: - absolute_path = script_dir # Use input file directory if no user path is specified + # Use runtime directory if no user path is specified + absolute_path = runtime_root() else: - # Check if user specified path is absolute, otherwise convert it to input file relative - if Path(argparse_path).is_absolute(): absolute_path = argparse_path - else: absolute_path = os.path.join(script_dir, argparse_path) + # Check if user specified path is absolute + if is_path_absolute(argparse_path): + absolute_path = argparse_path + # Otherwise, make it runtime directory relative + else: + absolute_path = safe_path(runtime_root(), argparse_path) return absolute_path @@ -104,22 +123,31 @@ def process_input_files(argparse_args, sys_argv=None): else: input_files = [file.name for file in argparse_args.files] - output_path = get_argparse_path(argparse_args.output_dir or argparse_args.input_dir) + # Set output path via argparse Output Path or argparse Input Path or first input file Path + output_path = argparse_args.output_dir or argparse_args.input_dir or path_parent(input_files[0]) else: # Script w/o parameters input_path_user = input('\nEnter input directory path: ') input_path_full = get_argparse_path(input_path_user) if input_path_user else '' input_files = get_path_files(input_path_full) - output_path = get_argparse_path(input('\nEnter output directory path: ')) + output_path = input('\nEnter output directory path: ') - return input_files, output_path + output_path_final = get_argparse_path(output_path) + + return input_files, output_path_final -# https://stackoverflow.com/a/22881871 by jfs -def get_script_dir(): +# Get project's root directory +def project_root(): + root = Path(__file__).parent.parent + + return real_path(root) + +# Get runtime's root directory +def runtime_root(): if getattr(sys, 'frozen', False): - path = sys.executable + root = Path(sys.executable).parent else: - path = inspect.getabsfile(get_script_dir) - - return os.path.dirname(get_real_path(path)) + root = project_root() + + return real_path(root) diff --git a/common/patterns.py b/common/patterns.py index 2e416f8..d66f268 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + import re PAT_AMI_PFAT = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) diff --git a/common/struct_ops.py b/common/struct_ops.py index bd418da..31c6e5b 100644 --- a/common/struct_ops.py +++ b/common/struct_ops.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 #coding=utf-8 -# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky +""" +Copyright (C) 2022 Plato Mavropoulos +""" import ctypes @@ -11,6 +13,7 @@ uint16_t = ctypes.c_ushort uint32_t = ctypes.c_uint uint64_t = ctypes.c_uint64 +# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky def get_struct(buffer, start_offset, class_name, param_list=None): if param_list is None: param_list = [] diff --git a/common/system.py b/common/system.py index 8fcf156..3094ea7 100644 --- a/common/system.py +++ b/common/system.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + import sys import ctypes import argparse diff --git a/common/text_ops.py b/common/text_ops.py index dbb40a4..4a8ea41 100644 --- a/common/text_ops.py +++ b/common/text_ops.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + # Generate padding (spaces or tabs) def padder(padd_count, tab=False): return ('\t' if tab else ' ') * padd_count From 982e3f3fc950daf3b14a99fdea9c0eb18ae96fc4 Mon Sep 17 00:00:00 2001 From: platomav Date: Thu, 21 Apr 2022 13:59:40 +0300 Subject: [PATCH 17/52] Format detectors now accept input bytes or path --- AMI_PFAT_Extract.py | 7 +++++-- AMI_UCP_Extract.py | 10 ++++++---- Dell_PFS_Extract.py | 9 ++++++--- common/text_ops.py | 10 ++++++++++ 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py index 1ce55f9..dd22a66 100644 --- a/AMI_PFAT_Extract.py +++ b/AMI_PFAT_Extract.py @@ -7,7 +7,7 @@ AMI BIOS Guard Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'AMI BIOS Guard Extractor v4.0_a7' +title = 'AMI BIOS Guard Extractor v4.0_a8' import os import re @@ -23,6 +23,7 @@ from common.path_ops import safe_name, make_dirs from common.patterns import PAT_AMI_PFAT from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t from common.system import script_init, argparse_init, printer +from common.text_ops import file_to_bytes class AmiBiosGuardHeader(ctypes.LittleEndianStructure): _pack_ = 1 @@ -127,7 +128,9 @@ class IntelBiosGuardSignature2k(ctypes.LittleEndianStructure): printer(['Exponent :', '0x%X' % self.Exponent], p, False) printer(['Signature:', '%s [...]' % Signature[:32]], p, False) -def is_ami_pfat(input_buffer): +def is_ami_pfat(in_file): + input_buffer = file_to_bytes(in_file) + return bool(get_ami_pfat(input_buffer)[0]) def get_ami_pfat(input_buffer): diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index ae2587e..76386ba 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -title = 'AMI UCP BIOS Extractor v2.0_a9' +title = 'AMI UCP BIOS Extractor v2.0_a10' import os import re @@ -26,7 +26,7 @@ from common.path_ops import agnostic_path, safe_name, safe_path, make_dirs from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t from common.system import script_init, argparse_init, printer -from common.text_ops import to_string +from common.text_ops import file_to_bytes, to_string from AMI_PFAT_Extract import get_ami_pfat, parse_pfat_file @@ -159,8 +159,10 @@ def chk16_validate(data, tag, padd=0): else: printer('Checksum of UCP Module %s is valid!' % tag, padd) -# Check if input file is AMI UCP image -def is_ami_ucp(buffer): +# Check if input path or buffer is AMI UCP image +def is_ami_ucp(in_file): + buffer = file_to_bytes(in_file) + return bool(get_ami_ucp(buffer)[0]) # Get all input file AMI UCP patterns diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index 2e0610f..5e27f6a 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Dell PFS Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'Dell PFS Update Extractor v6.0_a6' +title = 'Dell PFS Update Extractor v6.0_a7' import os import io @@ -25,6 +25,7 @@ from common.path_ops import safe_name, make_dirs from common.patterns import PAT_DELL_HDR, PAT_DELL_FTR, PAT_DELL_PKG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t, uint64_t from common.system import script_init, argparse_init, printer +from common.text_ops import file_to_bytes from AMI_PFAT_Extract import IntelBiosGuardHeader, IntelBiosGuardSignature2k, parse_bg_script @@ -205,8 +206,10 @@ def is_pfs_hdr(in_buffer): def is_pfs_ftr(in_buffer): return PAT_DELL_FTR.search(in_buffer) -# Check if input buffer is Dell PFS/PKG image -def is_dell_pfs(in_buffer): +# Check if input path or buffer is Dell PFS/PKG image +def is_dell_pfs(in_file): + in_buffer = file_to_bytes(in_file) + return bool(is_pfs_hdr(in_buffer) or is_pfs_pkg(in_buffer)) # Get PFS ZLIB Section Offsets diff --git a/common/text_ops.py b/common/text_ops.py index 4a8ea41..8384ee8 100644 --- a/common/text_ops.py +++ b/common/text_ops.py @@ -17,3 +17,13 @@ def to_string(input_object, sep_char=''): output_string = str(input_object) return output_string + +# Get Bytes from given buffer or file path +def file_to_bytes(in_object): + object_bytes = in_object + + if type(in_object).__name__ not in ('bytes','bytearray'): + with open(to_string(in_object), 'rb') as object_data: + object_bytes = object_data.read() + + return object_bytes From 9b29c37c65e987872aaed527510725598e111b57 Mon Sep 17 00:00:00 2001 From: platomav Date: Sun, 1 May 2022 01:33:43 +0300 Subject: [PATCH 18/52] Fix handling of quote-encased user input paths --- common/path_ops.py | 15 ++++++++++++--- common/text_ops.py | 14 +++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/common/path_ops.py b/common/path_ops.py index d08cfb1..06b97af 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -11,7 +11,7 @@ import sys import shutil from pathlib import Path, PurePath -from common.text_ops import to_string +from common.text_ops import is_encased, to_string # Fix illegal/reserved Windows characters def safe_name(in_name): @@ -95,6 +95,15 @@ def get_path_files(in_path): return path_files +# Get path without leading/trailing quotes +def get_dequoted_path(in_path): + out_path = to_string(in_path).strip() + + if len(out_path) >= 2 and is_encased(out_path, ("'",'"')): + out_path = out_path[1:-1] + + return out_path + # Get absolute file path of argparse object def get_argparse_path(argparse_path): if not argparse_path: @@ -127,11 +136,11 @@ def process_input_files(argparse_args, sys_argv=None): output_path = argparse_args.output_dir or argparse_args.input_dir or path_parent(input_files[0]) else: # Script w/o parameters - input_path_user = input('\nEnter input directory path: ') + input_path_user = get_dequoted_path(input('\nEnter input directory path: ')) input_path_full = get_argparse_path(input_path_user) if input_path_user else '' input_files = get_path_files(input_path_full) - output_path = input('\nEnter output directory path: ') + output_path = get_dequoted_path(input('\nEnter output directory path: ')) output_path_final = get_argparse_path(output_path) diff --git a/common/text_ops.py b/common/text_ops.py index 8384ee8..f007051 100644 --- a/common/text_ops.py +++ b/common/text_ops.py @@ -10,13 +10,13 @@ def padder(padd_count, tab=False): return ('\t' if tab else ' ') * padd_count # Get String from given input object -def to_string(input_object, sep_char=''): - if type(input_object).__name__ in ('list','tuple'): - output_string = sep_char.join(map(str, input_object)) +def to_string(in_object, sep_char=''): + if type(in_object).__name__ in ('list','tuple'): + out_string = sep_char.join(map(str, in_object)) else: - output_string = str(input_object) + out_string = str(in_object) - return output_string + return out_string # Get Bytes from given buffer or file path def file_to_bytes(in_object): @@ -27,3 +27,7 @@ def file_to_bytes(in_object): object_bytes = object_data.read() return object_bytes + +# Check if string starts and ends with given character(s) +def is_encased(in_string, chars): + return in_string.startswith(chars) and in_string.endswith(chars) From cf88fc7a5d6843713d53e78b9d5f3413485d8db2 Mon Sep 17 00:00:00 2001 From: platomav Date: Fri, 6 May 2022 13:54:48 +0300 Subject: [PATCH 19/52] Added relevant exit codes at utilities Fixed missing output path value crash Increased minimum Python version to 3.8 --- AMI_PFAT_Extract.py | 12 ++++++++---- AMI_UCP_Extract.py | 8 ++++++-- Dell_PFS_Extract.py | 8 ++++++-- README.md | 12 ++++++------ common/path_ops.py | 7 +++++-- common/system.py | 15 +++++++++------ 6 files changed, 40 insertions(+), 22 deletions(-) diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py index dd22a66..34de9c2 100644 --- a/AMI_PFAT_Extract.py +++ b/AMI_PFAT_Extract.py @@ -7,7 +7,7 @@ AMI BIOS Guard Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'AMI BIOS Guard Extractor v4.0_a8' +TITLE = 'AMI BIOS Guard Extractor v4.0_a9' import os import re @@ -140,8 +140,8 @@ def get_ami_pfat(input_buffer): return match, buffer -def get_file_name(index, title): - return safe_name('%0.2d -- %s' % (index, title)) +def get_file_name(index, name): + return safe_name('%0.2d -- %s' % (index, name)) def parse_bg_script(script_data, padding): is_opcode_div = len(script_data) % 8 == 0 @@ -310,7 +310,7 @@ if __name__ == '__main__': arguments = argparser.parse_args() # Initialize script (must be after argparse) - input_files,output_path,padding = script_init(title, arguments, 4) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) for input_file in input_files: input_name = os.path.basename(input_file) @@ -329,5 +329,9 @@ if __name__ == '__main__': extract_path = os.path.join(output_path, input_name) parse_pfat_file(pfat_buffer, extract_path, padding) + + exit_code -= 1 printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 76386ba..1d4408a 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -title = 'AMI UCP BIOS Extractor v2.0_a10' +TITLE = 'AMI UCP BIOS Extractor v2.0_a11' import os import re @@ -500,7 +500,7 @@ if __name__ == '__main__': is_checksum = arguments.checksum # Set Checksum verification optional argument # Initialize script (must be after argparse) - input_files,output_path,padding = script_init(title, arguments, 4) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) for input_file in input_files: input_name = os.path.basename(input_file) @@ -520,5 +520,9 @@ if __name__ == '__main__': extract_path = os.path.join(output_path, input_name) ucp_extract(main_uaf_bin, extract_path, main_uaf_tag, padding, is_checksum) + + exit_code -= 1 printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index 5e27f6a..cae76c9 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Dell PFS Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'Dell PFS Update Extractor v6.0_a7' +TITLE = 'Dell PFS Update Extractor v6.0_a8' import os import io @@ -922,7 +922,7 @@ if __name__ == '__main__': is_structure = arguments.structure # Set Structure output mode optional argument # Initialize script (must be after argparse) - input_files,output_path,padding = script_init(title, arguments, 4) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) for input_file in input_files: input_name = os.path.basename(input_file) @@ -965,5 +965,9 @@ if __name__ == '__main__': for offset in pfs_zlib_offsets: # Call the PFS ZLIB Section Parser function pfs_section_parse(input_buffer, offset, extract_path, extract_name, 1, 1, False, padding, is_structure, is_advanced) + + exit_code -= 1 printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/README.md b/README.md index 2748655..68997dc 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. #### **Prerequisites** @@ -43,7 +43,7 @@ Optionally, to decompile the Intel BIOS Guard (PFAT) Scripts, you must have the PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.7.0 or newer is installed: +1. Make sure Python 3.8.0 or newer is installed: > python --version @@ -90,7 +90,7 @@ You can either Drag & Drop or manually enter AMI UCP BIOS executable file(s). Op #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. #### **Prerequisites** @@ -107,7 +107,7 @@ Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (wh PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.7.0 or newer is installed: +1. Make sure Python 3.8.0 or newer is installed: > python --version @@ -155,7 +155,7 @@ You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s) #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. #### **Prerequisites** @@ -167,7 +167,7 @@ Optionally, to decompile the AMI PFAT \> Intel BIOS Guard Scripts, you must have PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.7.0 or newer is installed: +1. Make sure Python 3.8.0 or newer is installed: > python --version diff --git a/common/path_ops.py b/common/path_ops.py index 06b97af..0890f1d 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -132,8 +132,11 @@ def process_input_files(argparse_args, sys_argv=None): else: input_files = [file.name for file in argparse_args.files] - # Set output path via argparse Output Path or argparse Input Path or first input file Path - output_path = argparse_args.output_dir or argparse_args.input_dir or path_parent(input_files[0]) + # Set output fallback value for missing argparse Output and Input Path + output_fallback = path_parent(input_files[0]) if input_files else None + + # Set output path via argparse Output path or argparse Input path or first input file path + output_path = argparse_args.output_dir or argparse_args.input_dir or output_fallback else: # Script w/o parameters input_path_user = get_dequoted_path(input('\nEnter input directory path: ')) diff --git a/common/system.py b/common/system.py index 3094ea7..e88b047 100644 --- a/common/system.py +++ b/common/system.py @@ -34,14 +34,14 @@ def is_auto_exit(): def check_sys_py(): sys_py = get_py_ver() - if sys_py < (3,7): - sys.stdout.write('\nError: Python >= 3.7 required, not %d.%d!' % (sys_py[0], sys_py[1])) + if sys_py < (3,8): + sys.stdout.write('\nError: Python >= 3.8 required, not %d.%d!' % (sys_py[0], sys_py[1])) if not is_auto_exit(): # noinspection PyUnresolvedReferences (raw_input if sys_py[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602 - sys.exit(1) + sys.exit(125) # Check OS Platform def check_sys_os(): @@ -53,7 +53,7 @@ def check_sys_os(): if not is_auto_exit(): input('\nPress enter to exit') - sys.exit(2) + sys.exit(126) # Fix Windows Unicode console redirection if os_win: sys.stdout.reconfigure(encoding='utf-8') @@ -93,7 +93,10 @@ def script_init(title, arguments, padding=0): # Process input files and generate output path input_files,output_path = process_input_files(arguments, sys.argv) - return input_files, output_path, padding + # Count input files for exit code + input_count = len(input_files) + + return input_count, input_files, output_path, padding # https://stackoverflow.com/a/781074 by Torsten Marek def nice_exc_handler(exc_type, exc_value, tb): @@ -107,7 +110,7 @@ def nice_exc_handler(exc_type, exc_value, tb): if not is_auto_exit(): input('\nPress enter to exit') - sys.exit(3) + sys.exit(127) # Show message(s) while controlling padding, newline, pausing & separator def printer(in_message='', padd_count=0, new_line=True, pause=False, sep_char=' '): From 7e96a62f4271805862bc3d2e871361ef22da9a77 Mon Sep 17 00:00:00 2001 From: platomav Date: Fri, 6 May 2022 14:58:00 +0300 Subject: [PATCH 20/52] Added --static optional parameter Allows usage of static-built external dependencies --- AMI_UCP_Extract.py | 17 +++++++++-------- common/system.py | 1 + 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 1d4408a..5e35f97 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'AMI UCP BIOS Extractor v2.0_a11' +TITLE = 'AMI UCP BIOS Extractor v2.0_a12' import os import re @@ -208,7 +208,7 @@ def get_uaf_mod(buffer, uaf_off=0x0): return uaf_all # Parse & Extract AMI UCP structures -def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False): +def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False, is_static=False): nal_dict = {} # Initialize @NAL Dictionary per UCP printer('Utility Configuration Program', padding) @@ -237,10 +237,10 @@ def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False): uaf_all = get_uaf_mod(buffer, UAF_HDR_LEN) for mod_info in uaf_all: - nal_dict = uaf_extract(buffer, extract_path, mod_info, padding + 8, is_checksum, nal_dict) + nal_dict = uaf_extract(buffer, extract_path, mod_info, padding + 8, is_checksum, is_static, nal_dict) # Parse & Extract AMI UCP > @UAF|@HPU Module/Section -def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, nal_dict=None): +def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, is_static=False, nal_dict=None): if nal_dict is None: nal_dict = {} uaf_tag,uaf_off,uaf_hdr = mod_info @@ -392,12 +392,12 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na nal_dict[info_tag] = (info_path,info_name) # Assign a file path & name to each Tag # Parse Insyde BIOS @UAF|@HPU Module (@INS) - if uaf_tag == '@INS' and is_7z_supported(uaf_fname, padding + 4): + if uaf_tag == '@INS' and is_7z_supported(uaf_fname, padding + 4, static=is_static): ins_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory printer('Insyde BIOS 7z SFX Archive:', padding + 4) - if a7z_decompress(uaf_fname, ins_dir, '7z SFX', padding + 8) == 0: + if a7z_decompress(uaf_fname, ins_dir, '7z SFX', padding + 8, static=is_static) == 0: os.remove(uaf_fname) # Successful extraction, delete @INS Module file/archive # Detect & Unpack AMI BIOS Guard (PFAT) BIOS image @@ -422,7 +422,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na if nested_uaf_off: uaf_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory - ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum) # Call recursively + ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum, is_static) # Call recursively os.remove(uaf_fname) # Delete raw nested AMI UCP Structure after successful recursion/extraction @@ -498,6 +498,7 @@ if __name__ == '__main__': arguments = argparser.parse_args() is_checksum = arguments.checksum # Set Checksum verification optional argument + is_static = arguments.static # Set Static dependencies usage optional argument # Initialize script (must be after argparse) exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) @@ -519,7 +520,7 @@ if __name__ == '__main__': extract_path = os.path.join(output_path, input_name) - ucp_extract(main_uaf_bin, extract_path, main_uaf_tag, padding, is_checksum) + ucp_extract(main_uaf_bin, extract_path, main_uaf_tag, padding, is_checksum, is_static) exit_code -= 1 diff --git a/common/system.py b/common/system.py index e88b047..b08ae1f 100644 --- a/common/system.py +++ b/common/system.py @@ -67,6 +67,7 @@ def argparse_init(): argparser.add_argument('-v', '--version', help='show utility name and version', action='store_true') argparser.add_argument('-o', '--output-dir', help='extract in given output directory') argparser.add_argument('-i', '--input-dir', help='extract from given input directory') + argparser.add_argument('--static', help='use static-built dependencies', action='store_true') return argparser From ec73b9e9500dc60d3399555e303bd36a2b8457b1 Mon Sep 17 00:00:00 2001 From: platomav Date: Sat, 7 May 2022 00:27:02 +0300 Subject: [PATCH 21/52] Added --static parameter to README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 68997dc..946c3d8 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts +* --static : use static-built external dependencies * -a or --advanced : extract signatures and metadata * -s or --structure : show PFS structure information @@ -86,6 +87,7 @@ You can either Drag & Drop or manually enter AMI UCP BIOS executable file(s). Op * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts +* --static : use static-built external dependencies * -c or --checksum : verify AMI UCP Checksums (slow) #### **Compatibility** @@ -152,6 +154,7 @@ You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s) * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts +* --static : use static-built external dependencies #### **Compatibility** From 2fb8ff913b4c38e2a14aba21c64fde0c7ef4aca3 Mon Sep 17 00:00:00 2001 From: platomav Date: Mon, 9 May 2022 00:53:36 +0300 Subject: [PATCH 22/52] Improved README --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 946c3d8..8863b97 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# BIOSUtilities +# BIOSUtilities [Refactor - WIP] **Various BIOS Utilities for Modding/Research** [BIOS Utilities News Feed](https://twitter.com/platomaniac) @@ -38,7 +38,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 Optionally, to decompile the Intel BIOS Guard (PFAT) Scripts, you must have the following 3rd party utility at the "external" project directory: -* [BIOS Guard Script Tool](https://github.com/allowitsme/big-tool/tree/sdk-compat) (i.e. big_script_tool.py) +* [BIOS Guard Script Tool](https://github.com/platomav/BGScriptTool) (i.e. big_script_tool.py) #### **Build/Freeze/Compile with PyInstaller** @@ -52,9 +52,9 @@ PyInstaller can build/freeze/compile the utility at all three supported platform > pip3 install pyinstaller -3. Place any appropriate prerequisite at the project directory: +3. Place prerequisites at the "external" project directory: -> BIOS Guard Script Tool +> BIOS Guard Script Tool (optional) 4. Build/Freeze/Compile: @@ -103,7 +103,7 @@ To run the utility, you must have the following 3rd party tools at the "external Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (when applicable), you must have the following 3rd party utility at the "external" project directory: -* [BIOS Guard Script Tool](https://github.com/allowitsme/big-tool/tree/sdk-compat) (i.e. big_script_tool.py) +* [BIOS Guard Script Tool](https://github.com/platomav/BGScriptTool) (i.e. big_script_tool.py) #### **Build/Freeze/Compile with PyInstaller** @@ -117,9 +117,11 @@ PyInstaller can build/freeze/compile the utility at all three supported platform > pip3 install pyinstaller -3. Place any appropriate prerequisite at the project directory: +3. Place prerequisites at the "external" project directory: -> TianoCompress, 7-Zip Console, BIOS Guard Script Tool +> TianoCompress +> \7-Zip Console +> BIOS Guard Script Tool (optional) 4. Build/Freeze/Compile: @@ -164,7 +166,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 Optionally, to decompile the AMI PFAT \> Intel BIOS Guard Scripts, you must have the following 3rd party utility at the "external" project directory: -* [BIOS Guard Script Tool](https://github.com/allowitsme/big-tool/tree/sdk-compat) (i.e. big_script_tool.py) +* [BIOS Guard Script Tool](https://github.com/platomav/BGScriptTool) (i.e. big_script_tool.py) #### **Build/Freeze/Compile with PyInstaller** @@ -178,9 +180,9 @@ PyInstaller can build/freeze/compile the utility at all three supported platform > pip3 install pyinstaller -3. Place any appropriate prerequisite at the project directory: +3. Place prerequisites at the "external" project directory: -> BIOS Guard Script Tool +> BIOS Guard Script Tool (optional) 4. Build/Freeze/Compile: From dd250a8595375e1c28dd1f1157bf7e797a7667d7 Mon Sep 17 00:00:00 2001 From: platomav Date: Mon, 9 May 2022 00:56:14 +0300 Subject: [PATCH 23/52] Fix README MD --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8863b97..23924e2 100644 --- a/README.md +++ b/README.md @@ -46,19 +46,19 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 1. Make sure Python 3.8.0 or newer is installed: -> python --version +* python --version 2. Use pip to install PyInstaller: -> pip3 install pyinstaller +* pip3 install pyinstaller 3. Place prerequisites at the "external" project directory: -> BIOS Guard Script Tool (optional) +* BIOS Guard Script Tool (optional) 4. Build/Freeze/Compile: -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Dell_PFS_Extract.py +* pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Dell_PFS_Extract.py You should find the final utility executable at "dist" folder @@ -99,7 +99,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 To run the utility, you must have the following 3rd party tools at the "external" project directory: * [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress) -* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zz|7zzs) +* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zz|7zzs for Linux) Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (when applicable), you must have the following 3rd party utility at the "external" project directory: @@ -111,21 +111,21 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 1. Make sure Python 3.8.0 or newer is installed: -> python --version +* python --version 2. Use pip to install PyInstaller: -> pip3 install pyinstaller +* pip3 install pyinstaller 3. Place prerequisites at the "external" project directory: -> TianoCompress -> \7-Zip Console -> BIOS Guard Script Tool (optional) +* TianoCompress +* \7-Zip Console +* BIOS Guard Script Tool (optional) 4. Build/Freeze/Compile: -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/AMI_UCP_Extract.py +* pyinstaller --add-data="external/*;external/" --noupx --onefile \\/AMI_UCP_Extract.py You should find the final utility executable at "dist" folder @@ -174,15 +174,15 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 1. Make sure Python 3.8.0 or newer is installed: -> python --version +* python --version 2. Use pip to install PyInstaller: -> pip3 install pyinstaller +* pip3 install pyinstaller 3. Place prerequisites at the "external" project directory: -> BIOS Guard Script Tool (optional) +* BIOS Guard Script Tool (optional) 4. Build/Freeze/Compile: From 1480d663beecee8dc4cccbcacaa8b6af1fc23e63 Mon Sep 17 00:00:00 2001 From: platomav Date: Mon, 9 May 2022 01:02:52 +0300 Subject: [PATCH 24/52] Fix README MD v2 --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 23924e2..23e637b 100644 --- a/README.md +++ b/README.md @@ -46,19 +46,19 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 1. Make sure Python 3.8.0 or newer is installed: -* python --version +> python --version 2. Use pip to install PyInstaller: -* pip3 install pyinstaller +> pip3 install pyinstaller 3. Place prerequisites at the "external" project directory: -* BIOS Guard Script Tool (optional) +> BIOS Guard Script Tool (optional) 4. Build/Freeze/Compile: -* pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Dell_PFS_Extract.py +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Dell_PFS_Extract.py You should find the final utility executable at "dist" folder @@ -111,21 +111,21 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 1. Make sure Python 3.8.0 or newer is installed: -* python --version +> python --version 2. Use pip to install PyInstaller: -* pip3 install pyinstaller +> pip3 install pyinstaller 3. Place prerequisites at the "external" project directory: -* TianoCompress -* \7-Zip Console -* BIOS Guard Script Tool (optional) +> TianoCompress\ +> 7-Zip Console\ +> BIOS Guard Script Tool (optional) 4. Build/Freeze/Compile: -* pyinstaller --add-data="external/*;external/" --noupx --onefile \\/AMI_UCP_Extract.py +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/AMI_UCP_Extract.py You should find the final utility executable at "dist" folder @@ -174,15 +174,15 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 1. Make sure Python 3.8.0 or newer is installed: -* python --version +> python --version 2. Use pip to install PyInstaller: -* pip3 install pyinstaller +> pip3 install pyinstaller 3. Place prerequisites at the "external" project directory: -* BIOS Guard Script Tool (optional) +> BIOS Guard Script Tool (optional) 4. Build/Freeze/Compile: From 8d262318dd4f1a68113330d5d1639c2c166b85c7 Mon Sep 17 00:00:00 2001 From: platomav Date: Sun, 15 May 2022 19:40:54 +0300 Subject: [PATCH 25/52] Portwell EFI BIOS Extractor v2.0_a4 Replaced any assertions --- AMI_UCP_Extract.py | 2 +- Dell_PFS_Extract.py | 6 +- Portwell_EFI_Extract.py | 153 ++++++++++++++++++++++++++++++++++++++++ README.md | 72 ++++++++++++++++++- common/efi_comp.py | 2 +- 5 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 Portwell_EFI_Extract.py diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 5e35f97..769048b 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -159,7 +159,7 @@ def chk16_validate(data, tag, padd=0): else: printer('Checksum of UCP Module %s is valid!' % tag, padd) -# Check if input path or buffer is AMI UCP image +# Check if input is AMI UCP image def is_ami_ucp(in_file): buffer = file_to_bytes(in_file) diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index cae76c9..fb9af1a 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Dell PFS Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Dell PFS Update Extractor v6.0_a8' +TITLE = 'Dell PFS Update Extractor v6.0_a9' import os import io @@ -206,7 +206,7 @@ def is_pfs_hdr(in_buffer): def is_pfs_ftr(in_buffer): return PAT_DELL_FTR.search(in_buffer) -# Check if input path or buffer is Dell PFS/PKG image +# Check if input is Dell PFS/PKG image def is_dell_pfs(in_file): in_buffer = file_to_bytes(in_file) @@ -304,7 +304,7 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p # Decompress PFS ZLIB section payload try: - assert not is_zlib_error # ZLIB errors are critical + if is_zlib_error: raise Exception('ZLIB_ERROR') # ZLIB errors are critical section_data = zlib.decompress(compressed_data) # ZLIB decompression except: section_data = zlib_data # Fallback to raw ZLIB data upon critical error diff --git a/Portwell_EFI_Extract.py b/Portwell_EFI_Extract.py new file mode 100644 index 0000000..3546065 --- /dev/null +++ b/Portwell_EFI_Extract.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Portwell EFI Extract +Portwell EFI BIOS Extractor +Copyright (C) 2021-2022 Plato Mavropoulos +""" + +TITLE = 'Portwell EFI BIOS Extractor v2.0_a4' + +import os +import sys +import pefile + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.efi_comp import efi_decompress, is_efi_compressed +from common.path_ops import safe_name, make_dirs +from common.system import script_init, argparse_init, printer +from common.text_ops import file_to_bytes + +PEFI_MAGIC = br'MZ' + +FILE_MAGIC = br'' + +FILE_NAMES = { + 0 : 'Flash.efi', + 1 : 'Fparts.txt', + 2 : 'Update.nsh', + 3 : 'Temp.bin', + 4 : 'SaveDmiData.efi' + } + +# Check if input is Portwell EFI executable +def is_portwell_efi(in_file): + in_buffer = file_to_bytes(in_file) + + try: pe_buffer = get_portwell_pe(in_buffer)[1] + except: pe_buffer = b'' + + is_mz = in_buffer.startswith(PEFI_MAGIC) # EFI images start with PE Header MZ + is_uu = pe_buffer.startswith(FILE_MAGIC) # Portwell EFI files start with + + return is_mz and is_uu + +# Get PE of Portwell EFI executable +def get_portwell_pe(in_buffer): + pe_file = pefile.PE(data=in_buffer, fast_load=True) # Analyze EFI Portable Executable (PE) + + pe_data = in_buffer[pe_file.OPTIONAL_HEADER.SizeOfImage:] # Skip EFI executable (pylint: disable=E1101) + + return pe_file, pe_data + +# Parse & Extract Portwell UEFI Unpacker +def portwell_efi_extract(input_buffer, out_path, padding=0): + extract_path = os.path.join(out_path + '_extracted') + + make_dirs(extract_path, delete=True) + + pe_file,pe_data = get_portwell_pe(input_buffer) + + efi_title = get_unpacker_tag(input_buffer, pe_file) + + printer(efi_title, padding) + + efi_files = pe_data.split(FILE_MAGIC) # Split EFI Payload into file chunks + + parse_efi_files(extract_path, efi_files[1:], padding) + +# Get Portwell UEFI Unpacker tag +def get_unpacker_tag(input_buffer, pe_file): + unpacker_tag_txt = '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 = pe_section.PointerToRawData + pe_data_end = pe_data_bgn + pe_section.SizeOfRawData + + # Decode any valid UTF-16 .data PE section info to a parsable text buffer + pe_data_txt = input_buffer[pe_data_bgn:pe_data_end].decode('utf-16','ignore') + + # Search .data for UEFI Unpacker tag + unpacker_tag_bgn = pe_data_txt.find(unpacker_tag_txt) + if unpacker_tag_bgn != -1: + unpacker_tag_len = pe_data_txt[unpacker_tag_bgn:].find('=') + if unpacker_tag_len != -1: + unpacker_tag_end = unpacker_tag_bgn + unpacker_tag_len + unpacker_tag_raw = 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 + +# Process Portwell UEFI Unpacker payload files +def parse_efi_files(extract_path, efi_files, padding): + for file_index,file_data in enumerate(efi_files): + if file_data in (b'', b'NULL'): continue # Skip empty/unused files + + file_name = FILE_NAMES.get(file_index, f'Unknown_{file_index}.bin') # Assign Name to EFI file + + printer(file_name, padding + 4) # Print EFI file name, indicate progress + + if file_name.startswith('Unknown_'): + printer(f'Note: Detected new Portwell EFI file ID {file_index}!', padding + 8, pause=True) # Report new EFI files + + file_path = os.path.join(extract_path, safe_name(file_name)) # Store EFI file output path + + with open(file_path, 'wb') as out_file: out_file.write(file_data) # Store EFI file data to drive + + # Attempt to detect EFI compression & decompress when applicable + if is_efi_compressed(file_data): + comp_fname = file_path + '.temp' # Store temporary compressed file name + + os.replace(file_path, comp_fname) # Rename initial/compressed file + + if efi_decompress(comp_fname, file_path, padding + 8) == 0: + os.remove(comp_fname) # Successful decompression, delete compressed file + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + + if not is_portwell_efi(input_buffer): + printer('Error: This is not a Portwell EFI Update Package!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + portwell_efi_extract(input_buffer, extract_path, padding) + + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/README.md b/README.md index 23e637b..df5392a 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ * [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) * [**AMI UCP BIOS Extractor**](#ami-ucp-bios-extractor) * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) +* [**Portwell EFI BIOS Extractor**](#portwell-efi-bios-extractor) ## **Dell PFS Update Extractor** @@ -98,7 +99,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 To run the utility, you must have the following 3rd party tools at the "external" project directory: -* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress) +* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux) * [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zz|7zzs for Linux) Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (when applicable), you must have the following 3rd party utility at the "external" project directory: @@ -198,5 +199,74 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() +## **Portwell EFI BIOS Extractor** + +![]() + +#### **Description** + +Parses Portwell UEFI Unpacker EFI executables (usually named "Update.efi") and extracts their firmware (e.g. SPI, BIOS/UEFI, EC etc) and utilities (e.g. Flasher etc) components. It supports all known Portwell UEFI Unpacker revisions (v1.1, v1.2, v2.0) and formats (used, empty, null), including those which contain EFI compressed files. The output comprises only final firmware components and utilities which are directly usable by end users. + +#### **Usage** + +You can either Drag & Drop or manually enter Portwell UEFI Unpacker EFI executable file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts +* --static : use static-built external dependencies + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. + +#### **Prerequisites** + +To run the utility, you must have the following 3rd party Python module installed: + +* [pefile](https://pypi.org/project/pefile/) + +> pip3 install pefile + +Moreover, you must have the following 3rd party tool at the "external" project directory: + +* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux) + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.8.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Use pip to install pefile: + +> pip3 install pefile + +4. Place prerequisite at the "external" project directory: + +> TianoCompress + +5. Build/Freeze/Compile: + +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Portwell_EFI_Extract.py + +You should find the final utility executable at "dist" folder + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + ###### _Donate Button Card Image: [Credit and Loan Pack](https://flaticon.com/free-icon/credit-card_3898076) by **Freepik** under Flaticon license_ ###### _Donate Button Paypal Image: [Credit Cards Pack](https://flaticon.com/free-icon/paypal_349278) by **Freepik** under Flaticon license_ \ No newline at end of file diff --git a/common/efi_comp.py b/common/efi_comp.py index 97c05f9..e2c283f 100644 --- a/common/efi_comp.py +++ b/common/efi_comp.py @@ -47,6 +47,6 @@ def efi_decompress(in_path, out_path, padding=0, comp_type='--uefi'): return 1 - printer('Succesfull EFI/Tiano decompression via TianoCompress!', padding) + printer('Succesfull EFI decompression via TianoCompress!', padding) return 0 From b4a93513f71179d45835a7844635de4d73b19262 Mon Sep 17 00:00:00 2001 From: platomav Date: Sun, 15 May 2022 19:56:33 +0300 Subject: [PATCH 26/52] Added pip requirements file --- external/.gitignore | 4 ---- external/requirements.txt | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 external/.gitignore create mode 100644 external/requirements.txt diff --git a/external/.gitignore b/external/.gitignore deleted file mode 100644 index 86d0cb2..0000000 --- a/external/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore everything in this directory -* -# Except this file -!.gitignore \ No newline at end of file diff --git a/external/requirements.txt b/external/requirements.txt new file mode 100644 index 0000000..2cd8e04 --- /dev/null +++ b/external/requirements.txt @@ -0,0 +1 @@ +pefile==2021.9.3 From 7bb0c5f9a976f50d0fcb790381b0639a1a0e60ed Mon Sep 17 00:00:00 2001 From: platomav Date: Sun, 22 May 2022 00:24:20 +0300 Subject: [PATCH 27/52] Added Phoenix TDK Packer Extractor v2.0_a4 f-strings all the things! --- AMI_PFAT_Extract.py | 66 ++++++++------- AMI_UCP_Extract.py | 86 +++++++++---------- Dell_PFS_Extract.py | 96 ++++++++++----------- Phoenix_TDK_Extract.py | 182 ++++++++++++++++++++++++++++++++++++++++ Portwell_EFI_Extract.py | 8 +- README.md | 66 +++++++++++++-- common/a7z_comp.py | 6 +- common/efi_comp.py | 2 +- common/patterns.py | 1 + common/system.py | 4 +- 10 files changed, 379 insertions(+), 138 deletions(-) create mode 100644 Phoenix_TDK_Extract.py diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py index 34de9c2..e6dd03c 100644 --- a/AMI_PFAT_Extract.py +++ b/AMI_PFAT_Extract.py @@ -7,7 +7,7 @@ AMI BIOS Guard Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'AMI BIOS Guard Extractor v4.0_a9' +TITLE = 'AMI BIOS Guard Extractor v4.0_a10' import os import re @@ -36,10 +36,10 @@ class AmiBiosGuardHeader(ctypes.LittleEndianStructure): ] def struct_print(self, p): - printer(['Size :', '0x%X' % self.Size], p, False) - printer(['Checksum:', '0x%0.4X' % self.Checksum], p, False) + printer(['Size :', f'0x{self.Size:X}'], p, False) + printer(['Checksum:', f'0x{self.Checksum:04X}'], p, False) printer(['Tag :', self.Tag.decode('utf-8')], p, False) - printer(['Flags :', '0x%0.2X' % self.Flags], p, False) + printer(['Flags :', f'0x{self.Flags:02X}'], p, False) class IntelBiosGuardHeader(ctypes.LittleEndianStructure): _pack_ = 1 @@ -63,10 +63,10 @@ class IntelBiosGuardHeader(ctypes.LittleEndianStructure): id_text = re.sub(r'[\n\t\r\x00 ]', '', id_byte.decode('utf-8','ignore')) - id_hexs = '%0.*X' % (0x10 * 2, int.from_bytes(id_byte, 'big')) - id_guid = '{%s-%s-%s-%s-%s}' % (id_hexs[:8], id_hexs[8:12], id_hexs[12:16], id_hexs[16:20], id_hexs[20:]) + id_hexs = f'{int.from_bytes(id_byte, "big"):0{0x10 * 2}X}' + id_guid = f'{{{id_hexs[:8]}-{id_hexs[8:12]}-{id_hexs[12:16]}-{id_hexs[16:20]}-{id_hexs[20:]}}}' - return '%s %s' % (id_text, id_guid) + return f'{id_text} {id_guid}' def get_flags(self): attr = IntelBiosGuardHeaderGetAttributes() @@ -78,19 +78,19 @@ class IntelBiosGuardHeader(ctypes.LittleEndianStructure): no_yes = ['No','Yes'] f1,f2,f3,f4,f5 = self.get_flags() - printer(['BIOS Guard Version :', '%d.%d' % (self.BGVerMajor, self.BGVerMinor)], p, False) + printer(['BIOS Guard Version :', f'{self.BGVerMajor}.{self.BGVerMinor}'], p, False) printer(['Platform Identity :', self.get_platform_id()], p, False) printer(['Signed Flash Address Map :', no_yes[f1]], p, False) printer(['Protected EC OpCodes :', no_yes[f2]], p, False) printer(['Graphics Security Disable :', no_yes[f3]], p, False) printer(['Fault Tolerant Update :', no_yes[f4]], p, False) - printer(['Attributes Reserved :', '0x%X' % f5], p, False) - printer(['Script Version :', '%d.%d' % (self.ScriptVerMajor, self.ScriptVerMinor)], p, False) - printer(['Script Size :', '0x%X' % self.ScriptSize], p, False) - printer(['Data Size :', '0x%X' % self.DataSize], p, False) - printer(['BIOS Security Version Number:', '0x%X' % self.BIOSSVN], p, False) - printer(['EC Security Version Number :', '0x%X' % self.ECSVN], p, False) - printer(['Vendor Information :', '0x%X' % self.VendorInfo], p, False) + printer(['Attributes Reserved :', f'0x{f5:X}'], p, False) + printer(['Script Version :', f'{self.ScriptVerMajor}.{self.ScriptVerMinor}'], p, False) + printer(['Script Size :', f'0x{self.ScriptSize:X}'], p, False) + printer(['Data Size :', f'0x{self.DataSize:X}'], p, False) + printer(['BIOS Security Version Number:', f'0x{self.BIOSSVN:X}'], p, False) + printer(['EC Security Version Number :', f'0x{self.ECSVN:X}'], p, False) + printer(['Vendor Information :', f'0x{self.VendorInfo:X}'], p, False) class IntelBiosGuardHeaderAttributes(ctypes.LittleEndianStructure): _fields_ = [ @@ -119,14 +119,14 @@ class IntelBiosGuardSignature2k(ctypes.LittleEndianStructure): ] def struct_print(self, p): - Modulus = '%0.*X' % (0x100 * 2, int.from_bytes(self.Modulus, 'little')) - Signature = '%0.*X' % (0x100 * 2, int.from_bytes(self.Signature, 'little')) + Modulus = f'{int.from_bytes(self.Modulus, "little"):0{0x100 * 2}X}' + Signature = f'{int.from_bytes(self.Signature, "little"):0{0x100 * 2}X}' - printer(['Unknown 0:', '0x%X' % self.Unknown0], p, False) - printer(['Unknown 1:', '0x%X' % self.Unknown1], p, False) - printer(['Modulus :', '%s [...]' % Modulus[:32]], p, False) - printer(['Exponent :', '0x%X' % self.Exponent], p, False) - printer(['Signature:', '%s [...]' % Signature[:32]], p, False) + printer(['Unknown 0:', f'0x{self.Unknown0:X}'], p, False) + printer(['Unknown 1:', f'0x{self.Unknown1:X}'], p, False) + printer(['Modulus :', f'{Modulus[:32]} [...]'], p, False) + printer(['Exponent :', f'0x{self.Exponent:X}'], p, False) + printer(['Signature:', f'{Signature[:32]} [...]'], p, False) def is_ami_pfat(in_file): input_buffer = file_to_bytes(in_file) @@ -141,7 +141,7 @@ def get_ami_pfat(input_buffer): return match, buffer def get_file_name(index, name): - return safe_name('%0.2d -- %s' % (index, name)) + return safe_name(f'{index:02d} -- {name}') def parse_bg_script(script_data, padding): is_opcode_div = len(script_data) % 8 == 0 @@ -212,7 +212,7 @@ def parse_pfat_hdr(buffer, padding): order = get_ordinal((bgt_indexes[index] if bgt_indexes else index) + 1) - desc = '%s (Index: %0.2d, Flash: %s, Parameter: %s, Flags: 0x%X, Blocks: %d)' % (name, index + 1, order, param, flags, count) + desc = f'{name} (Index: {index + 1:02d}, Flash: {order}, Parameter: {param}, Flags: 0x{flags:X}, Blocks: {count})' block_all += [(desc, name, order, param, flags, index, i, count) for i in range(count)] @@ -226,7 +226,7 @@ def parse_pfat_file(buffer, output_path, padding): extract_name = os.path.basename(output_path) - extract_path = os.path.join(output_path + '_extracted') + extract_path = os.path.join(f'{output_path}_extracted') make_dirs(extract_path, delete=True) @@ -242,11 +242,11 @@ def parse_pfat_file(buffer, output_path, padding): all_blocks_dict[file_index] = b'' - block_status = '%d/%d' % (block_index + 1, block_count) + block_status = f'{block_index + 1}/{block_count}' bg_hdr = get_struct(buffer, block_off, IntelBiosGuardHeader) - printer('Intel BIOS Guard %s Header:\n' % block_status, padding + 8) + printer(f'Intel BIOS Guard {block_status} Header:\n', padding + 8) bg_hdr.struct_print(padding + 12) @@ -270,13 +270,13 @@ def parse_pfat_file(buffer, output_path, padding): if len(bg_sig_bin) == PFAT_BLK_S2K_LEN: bg_sig = get_struct(bg_sig_bin, 0x0, IntelBiosGuardSignature2k) - printer('Intel BIOS Guard %s Signature:\n' % block_status, padding + 8) + printer(f'Intel BIOS Guard {block_status} Signature:\n', padding + 8) bg_sig.struct_print(padding + 12) block_off = bg_sig_end # Adjust next block to start at data + signature end - printer('Intel BIOS Guard %s Script:\n' % block_status, padding + 8) + printer(f'Intel BIOS Guard {block_status} Script:\n', padding + 8) _ = parse_bg_script(bg_script_bin, padding + 12) @@ -286,7 +286,9 @@ def parse_pfat_file(buffer, output_path, padding): pfat_oob_data = buffer[block_off:] # Store out-of-bounds data after the end of PFAT files - pfat_oob_path = os.path.join(extract_path, get_file_name(file_count + 1, extract_name + '_OOB.bin')) + pfat_oob_name = get_file_name(file_count + 1, f'{extract_name}_OOB.bin') + + pfat_oob_path = os.path.join(extract_path, pfat_oob_name) with open(pfat_oob_path, 'wb') as out_oob: out_oob.write(pfat_oob_data) @@ -296,7 +298,9 @@ def parse_pfat_file(buffer, output_path, padding): in_all_data = b''.join([block[1] for block in sorted(all_blocks_dict.items())]) - in_all_path = os.path.join(extract_path, get_file_name(0, extract_name + '_ALL.bin')) + in_all_name = get_file_name(0, f'{extract_name}_ALL.bin') + + in_all_path = os.path.join(extract_path, in_all_name) with open(in_all_path, 'wb') as out_all: out_all.write(in_all_data + pfat_oob_data) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 769048b..f4a48d0 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -3,11 +3,11 @@ """ AMI UCP Extract -AMI UCP BIOS Extractor +AMI UCP Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'AMI UCP BIOS Extractor v2.0_a12' +TITLE = 'AMI UCP Update Extractor v2.0_a13' import os import re @@ -45,20 +45,20 @@ class UafHeader(ctypes.LittleEndianStructure): def _get_reserved(self): res_bytes = bytes(self.Reserved) + res_hex = f'0x{int.from_bytes(res_bytes, "big"):0{0x4 * 2}X}' + res_str = re.sub(r'[\n\t\r\x00 ]', '', res_bytes.decode('utf-8','ignore')) - res_hex = '0x%0.*X' % (0x4 * 2, int.from_bytes(res_bytes, 'big')) + res_txt = f' ({res_str})' if len(res_str) else '' - res_out = res_hex + (' (%s)' % res_str if len(res_str) else '') - - return res_out + return f'{res_hex}{res_txt}' def struct_print(self, p): printer(['Tag :', self.ModuleTag.decode('utf-8')], p, False) - printer(['Size :', '0x%X' % self.ModuleSize], p, False) - printer(['Checksum :', '0x%0.4X' % self.Checksum], p, False) - printer(['Unknown 0 :', '0x%0.2X' % self.Unknown0], p, False) - printer(['Unknown 1 :', '0x%0.2X' % self.Unknown1], p, False) + printer(['Size :', f'0x{self.ModuleSize:X}'], p, False) + printer(['Checksum :', f'0x{self.Checksum:04X}'], p, False) + printer(['Unknown 0 :', f'0x{self.Unknown0:02X}'], p, False) + printer(['Unknown 1 :', f'0x{self.Unknown1:02X}'], p, False) printer(['Reserved :', self._get_reserved()], p, False) class UafModule(ctypes.LittleEndianStructure): @@ -70,8 +70,8 @@ class UafModule(ctypes.LittleEndianStructure): ] def struct_print(self, p, filename, description): - printer(['Compress Size:', '0x%X' % self.CompressSize], p, False) - printer(['Original Size:', '0x%X' % self.OriginalSize], p, False) + printer(['Compress Size:', f'0x{self.CompressSize:X}'], p, False) + printer(['Original Size:', f'0x{self.OriginalSize:X}'], p, False) printer(['Filename :', filename], p, False) printer(['Description :', description], p, False) @@ -98,22 +98,22 @@ class UiiHeader(ctypes.LittleEndianStructure): PMD = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'} def struct_print(self, p, description): - SupportBIOS = self.SBI.get(self.SupportBIOS, 'Unknown (%d)' % self.SupportBIOS) - SupportOS = self.SOS.get(self.SupportOS, 'Unknown (%d)' % self.SupportOS) - DataBusWidth = self.DBW.get(self.DataBusWidth, 'Unknown (%d)' % self.DataBusWidth) - ProgramType = self.PTP.get(self.ProgramType, 'Unknown (%d)' % self.ProgramType) - ProgramMode = self.PMD.get(self.ProgramMode, 'Unknown (%d)' % self.ProgramMode) + SupportBIOS = self.SBI.get(self.SupportBIOS, f'Unknown ({self.SupportBIOS})') + SupportOS = self.SOS.get(self.SupportOS, f'Unknown ({self.SupportOS})') + DataBusWidth = self.DBW.get(self.DataBusWidth, f'Unknown ({self.DataBusWidth})') + ProgramType = self.PTP.get(self.ProgramType, f'Unknown ({self.ProgramType})') + ProgramMode = self.PMD.get(self.ProgramMode, f'Unknown ({self.ProgramMode})') - printer(['UII Size :', '0x%X' % self.UIISize], p, False) - printer(['Checksum :', '0x%0.4X' % self.Checksum], p, False) - printer(['Tool Version :', '0x%0.8X' % self.UtilityVersion], p, False) - printer(['Info Size :', '0x%X' % self.InfoSize], p, False) + printer(['UII Size :', f'0x{self.UIISize:X}'], p, False) + printer(['Checksum :', f'0x{self.Checksum:04X}'], p, False) + printer(['Tool Version :', f'0x{self.UtilityVersion:08X}'], p, False) + printer(['Info Size :', f'0x{self.InfoSize:X}'], p, False) printer(['Supported BIOS:', SupportBIOS], p, False) printer(['Supported OS :', SupportOS], p, False) printer(['Data Bus Width:', DataBusWidth], p, False) printer(['Program Type :', ProgramType], p, False) printer(['Program Mode :', ProgramMode], p, False) - printer(['SourceSafe Tag:', '%0.2d' % self.SourceSafeRel], p, False) + printer(['SourceSafe Tag:', f'{self.SourceSafeRel:02d}'], p, False) printer(['Description :', description], p, False) class DisHeader(ctypes.LittleEndianStructure): @@ -126,7 +126,7 @@ class DisHeader(ctypes.LittleEndianStructure): ] def struct_print(self, p): - printer(['Password Size:', '0x%X' % self.PasswordSize], p, False) + printer(['Password Size:', f'0x{self.PasswordSize:X}'], p, False) printer(['Entry Count :', self.EntryCount], p, False) printer(['Password :', self.Password.decode('utf-8')], p, False) @@ -144,8 +144,8 @@ class DisModule(ctypes.LittleEndianStructure): SHOWN = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'} def struct_print(self, p): - EnabledDisabled = self.ENDIS.get(self.EnabledDisabled, 'Unknown (%d)' % self.EnabledDisabled) - ShownHidden = self.SHOWN.get(self.ShownHidden, 'Unknown (%d)' % self.ShownHidden) + EnabledDisabled = self.ENDIS.get(self.EnabledDisabled, f'Unknown ({self.EnabledDisabled})') + ShownHidden = self.SHOWN.get(self.ShownHidden, f'Unknown ({self.ShownHidden})') printer(['State :', EnabledDisabled], p, False) printer(['Display :', ShownHidden], p, False) @@ -155,9 +155,9 @@ class DisModule(ctypes.LittleEndianStructure): # Validate UCP Module Checksum-16 def chk16_validate(data, tag, padd=0): if get_chk_16(data) != 0: - printer('Error: Invalid UCP Module %s Checksum!' % tag, padd, pause=True) + printer(f'Error: Invalid UCP Module {tag} Checksum!', padd, pause=True) else: - printer('Checksum of UCP Module %s is valid!' % tag, padd) + printer(f'Checksum of UCP Module {tag} is valid!', padd) # Check if input is AMI UCP image def is_ami_ucp(in_file): @@ -213,13 +213,13 @@ def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False, printer('Utility Configuration Program', padding) - extract_path = os.path.join(out_path + '_extracted') + extract_path = os.path.join(f'{out_path}_extracted') make_dirs(extract_path, delete=True) uaf_hdr = get_struct(buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure - printer('Utility Auxiliary File > %s:\n' % ucp_tag, padding + 4) + printer(f'Utility Auxiliary File > {ucp_tag}:\n', padding + 4) uaf_hdr.struct_print(padding + 8) @@ -251,7 +251,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, is uaf_data_raw = uaf_data_mod[UAF_MOD_LEN:] # @UAF|@HPU Module Raw Data - printer('Utility Auxiliary File > %s:\n' % uaf_tag, padding) + printer(f'Utility Auxiliary File > {uaf_tag}:\n', padding) uaf_hdr.struct_print(padding + 4) # Print @UAF|@HPU Module Info @@ -262,12 +262,12 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, is if uaf_tag in nal_dict: uaf_name = nal_dict[uaf_tag][1] # Always prefer @NAL naming first elif uaf_tag in UAF_TAG_DICT: uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming elif uaf_tag == '@ROM': uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature) - elif uaf_tag.startswith('@R0'): uaf_name = 'BIOS_0%s.bin' % uaf_tag[3:] # BIOS/PFAT Firmware - elif uaf_tag.startswith('@S0'): uaf_name = 'BIOS_0%s.sig' % uaf_tag[3:] # BIOS/PFAT Signature - elif uaf_tag.startswith('@DR'): uaf_name = 'DROM_0%s.bin' % uaf_tag[3:] # Thunderbolt Retimer Firmware - elif uaf_tag.startswith('@DS'): uaf_name = 'DROM_0%s.sig' % uaf_tag[3:] # Thunderbolt Retimer Signature - elif uaf_tag.startswith('@EC'): uaf_name = 'EC_0%s.bin' % uaf_tag[3:] # Embedded Controller Firmware - elif uaf_tag.startswith('@ME'): uaf_name = 'ME_0%s.bin' % uaf_tag[3:] # Management Engine Firmware + elif uaf_tag.startswith('@R0'): uaf_name = f'BIOS_0{uaf_tag[3:]}.bin' # BIOS/PFAT Firmware + elif uaf_tag.startswith('@S0'): uaf_name = f'BIOS_0{uaf_tag[3:]}.sig' # BIOS/PFAT Signature + elif uaf_tag.startswith('@DR'): uaf_name = f'DROM_0{uaf_tag[3:]}.bin' # Thunderbolt Retimer Firmware + elif uaf_tag.startswith('@DS'): uaf_name = f'DROM_0{uaf_tag[3:]}.sig' # Thunderbolt Retimer Signature + elif uaf_tag.startswith('@EC'): uaf_name = f'EC_0{uaf_tag[3:]}.bin' # Embedded Controller Firmware + elif uaf_tag.startswith('@ME'): uaf_name = f'ME_0{uaf_tag[3:]}.bin' # Management Engine Firmware else: uaf_name = uaf_tag # Could not name the @UAF|@HPU Module, use Tag instead uaf_fext = '' if uaf_name != uaf_tag else '.bin' @@ -278,7 +278,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, is # Check if unknown @UAF|@HPU Module Tag is present in @NAL but not in built-in dictionary if uaf_tag in nal_dict and uaf_tag not in UAF_TAG_DICT and not uaf_tag.startswith(('@ROM','@R0','@S0','@DR','@DS')): - printer('Note: Detected new AMI UCP Module %s (%s) in @NAL!' % (uaf_tag, nal_dict[uaf_tag][1]), padding + 4, pause=True) + printer(f'Note: Detected new AMI UCP Module {uaf_tag} ({nal_dict[uaf_tag][1]}) in @NAL!', padding + 4, pause=True) # Generate @UAF|@HPU Module File name, depending on whether decompression will be required uaf_sname = safe_name(uaf_name + ('.temp' if is_comp else uaf_fext)) @@ -339,7 +339,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, is # Process and Print known text only @UAF|@HPU Modules (after EFI/Tiano Decompression) if uaf_tag in UAF_TAG_DICT and UAF_TAG_DICT[uaf_tag][2] == 'Text': - printer(UAF_TAG_DICT[uaf_tag][1] + ':', padding + 4) + printer(f'{UAF_TAG_DICT[uaf_tag][1]}:', padding + 4) printer(uaf_data_raw.decode('utf-8','ignore'), padding + 8) # Parse Default Command Status @UAF|@HPU Module (@DIS) @@ -361,7 +361,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, is for mod_idx in range(dis_hdr.EntryCount): dis_mod = get_struct(dis_data, mod_idx * DIS_MOD_LEN, DisModule) # Parse @DIS Module Raw Entry Structure - printer('Default Command Status Entry %0.2d/%0.2d:\n' % (mod_idx + 1, dis_hdr.EntryCount), padding + 8) + printer(f'Default Command Status Entry {mod_idx + 1:02d}/{dis_hdr.EntryCount:02d}:\n', padding + 8) dis_mod.struct_print(padding + 12) # Print @DIS Module Raw Entry Info @@ -383,7 +383,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, is for info in nal_info: info_tag,info_value = info.split(':',1) - printer(info_tag + ' : ' + info_value, padding + 8, False) # Print @NAL Module Tag-Path Info + printer(f'{info_tag} : {info_value}', padding + 8, False) # Print @NAL Module Tag-Path Info info_part = agnostic_path(info_value).parts # Split OS agnostic path in parts info_path = to_string(info_part[1:-1], os.sep) # Get path without drive/root or file @@ -393,7 +393,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, is # Parse Insyde BIOS @UAF|@HPU Module (@INS) if uaf_tag == '@INS' and is_7z_supported(uaf_fname, padding + 4, static=is_static): - ins_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory + ins_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-SFX')) # Generate extraction directory printer('Insyde BIOS 7z SFX Archive:', padding + 4) @@ -420,7 +420,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, is # Parse Nested AMI UCP Structure if nested_uaf_off: - uaf_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory + uaf_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-UCP')) # Generate extraction directory ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum, is_static) # Call recursively @@ -514,7 +514,7 @@ if __name__ == '__main__': main_uaf_off,main_uaf_bin,main_uaf_tag = get_ami_ucp(input_buffer) if not main_uaf_off: - printer('Error: This is not an AMI UCP BIOS executable!', padding) + printer('Error: This is not an AMI UCP Update executable!', padding) continue # Next input file diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index fb9af1a..d322423 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Dell PFS Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Dell PFS Update Extractor v6.0_a9' +TITLE = 'Dell PFS Update Extractor v6.0_a10' import os import io @@ -42,7 +42,7 @@ class DellPfsHeader(ctypes.LittleEndianStructure): def struct_print(self, p): printer(['Header Tag :', self.Tag.decode('utf-8')], p, False) printer(['Header Version:', self.HeaderVersion], p, False) - printer(['Payload Size :', '0x%X' % self.PayloadSize], p, False) + printer(['Payload Size :', f'0x{self.PayloadSize:X}'], p, False) # Dell PFS Footer Structure class DellPfsFooter(ctypes.LittleEndianStructure): @@ -55,8 +55,8 @@ class DellPfsFooter(ctypes.LittleEndianStructure): ] def struct_print(self, p): - printer(['Payload Size :', '0x%X' % self.PayloadSize], p, False) - printer(['Payload Checksum:', '0x%0.8X' % self.Checksum], p, False) + printer(['Payload Size :', f'0x{self.PayloadSize:X}'], p, False) + printer(['Payload Checksum:', f'0x{self.Checksum:08X}'], p, False) printer(['Footer Tag :', self.Tag.decode('utf-8')], p, False) # Dell PFS Entry Base Structure @@ -76,19 +76,19 @@ class DellPfsEntryBase(ctypes.LittleEndianStructure): ] def struct_print(self, p): - GUID = '%0.*X' % (0x10 * 2, int.from_bytes(self.GUID, 'little')) - Unknown = '%0.*X' % (len(self.Unknown) * 8, int.from_bytes(self.Unknown, 'little')) + GUID = f'{int.from_bytes(self.GUID, "little"):0{0x10 * 2}X}' + Unknown = f'{int.from_bytes(self.Unknown, "little"):0{len(self.Unknown) * 8}X}' Version = get_entry_ver(self.Version, self.VersionType) printer(['Entry GUID :', GUID], p, False) printer(['Entry Version :', self.HeaderVersion], p, False) printer(['Payload Version :', Version], p, False) - printer(['Reserved :', '0x%X' % self.Reserved], p, False) - printer(['Payload Data Size :', '0x%X' % self.DataSize], p, False) - printer(['Payload Signature Size :', '0x%X' % self.DataSigSize], p, False) - printer(['Metadata Data Size :', '0x%X' % self.DataMetSize], p, False) - printer(['Metadata Signature Size:', '0x%X' % self.DataMetSigSize], p, False) - printer(['Unknown :', '0x%s' % Unknown], p, False) + printer(['Reserved :', f'0x{self.Reserved:X}'], p, False) + printer(['Payload Data Size :', f'0x{self.DataSize:X}'], p, False) + printer(['Payload Signature Size :', f'0x{self.DataSigSize:X}'], p, False) + printer(['Metadata Data Size :', f'0x{self.DataMetSize:X}'], p, False) + printer(['Metadata Signature Size:', f'0x{self.DataMetSigSize:X}'], p, False) + printer(['Unknown :', f'0x{Unknown}'], p, False) # Dell PFS Entry Revision 1 Structure class DellPfsEntryR1(DellPfsEntryBase): @@ -116,7 +116,7 @@ class DellPfsInfo(ctypes.LittleEndianStructure): ] def struct_print(self, p): - GUID = '%0.*X' % (0x10 * 2, int.from_bytes(self.GUID, 'little')) + GUID = f'{int.from_bytes(self.GUID, "little"):0{0x10 * 2}X}' printer(['Info Version:', self.HeaderVersion], p, False) printer(['Entry GUID :', GUID], p, False) @@ -178,13 +178,13 @@ class DellPfsPfatMetadata(ctypes.LittleEndianStructure): ] def struct_print(self, p): - printer(['Offset Top :', '0x%X' % self.OffsetTop], p, False) - printer(['Unknown 0 :', '0x%X' % self.Unknown0], p, False) - printer(['Offset Base:', '0x%X' % self.OffsetBase], p, False) - printer(['Block Size :', '0x%X' % self.BlockSize], p, False) - printer(['Unknown 1 :', '0x%X' % self.Unknown1], p, False) - printer(['Unknown 2 :', '0x%X' % self.Unknown2], p, False) - printer(['Unknown 3 :', '0x%X' % self.Unknown3], p, False) + printer(['Offset Top :', f'0x{self.OffsetTop:X}'], p, False) + printer(['Unknown 0 :', f'0x{self.Unknown0:X}'], p, False) + printer(['Offset Base:', f'0x{self.OffsetBase:X}'], p, False) + printer(['Block Size :', f'0x{self.BlockSize:X}'], p, False) + printer(['Unknown 1 :', f'0x{self.Unknown1:X}'], p, False) + printer(['Unknown 2 :', f'0x{self.Unknown2:X}'], p, False) + printer(['Unknown 3 :', f'0x{self.Unknown3:X}'], p, False) # The Dell ThinOS PKG update images usually contain multiple sections. # Each section starts with a 0x30 header, which begins with pattern 72135500. @@ -239,10 +239,10 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p is_zlib_error = False # Initialize PFS ZLIB-related error state section_type = zlib_data[zlib_start - 0x1] # Byte before PFS ZLIB Section pattern is Section Type (e.g. AA, BB) - section_name = {0xAA:'Firmware', 0xBB:'Utilities'}.get(section_type, 'Unknown (%0.2X)' % section_type) + section_name = {0xAA:'Firmware', 0xBB:'Utilities'}.get(section_type, f'Unknown ({section_type:02X})') # Show extraction complete message for each main PFS ZLIB Section - printer('Extracting Dell PFS %d >%s > %s' % (pfs_index, pfs_name, section_name), padding) + printer(f'Extracting Dell PFS {pfs_index} >{pfs_name} > {section_name}', padding) # Set PFS ZLIB Section extraction sub-directory path section_path = os.path.join(output_path, safe_name(section_name)) @@ -390,11 +390,11 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i # Validate that a known PFS Information Header Version was encountered if entry_info_hdr.HeaderVersion != 1: - printer('Error: Unknown PFS Information Header Version %d!' % entry_info_hdr.HeaderVersion, pfs_padd + 8) + printer(f'Error: Unknown PFS Information Header Version {entry_info_hdr.HeaderVersion}!', pfs_padd + 8) break # Skip PFS Information Entries/Descriptors in case of unknown PFS Information Header Version # Get PFS Information Header GUID in Big Endian format to match each Info to the equivalent stored PFS Entry details - entry_guid = '%0.*X' % (0x10 * 2, int.from_bytes(entry_info_hdr.GUID, 'little')) + entry_guid = f'{int.from_bytes(entry_info_hdr.GUID, "little"):0{0x10 * 2}X}' # Get PFS FileName Structure values entry_info_mod = get_struct(filename_info, info_start + PFS_INFO_LEN, DellPfsName) @@ -466,7 +466,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i # Validate that a known PFS Information Header Version was encountered if entry_info_hdr.HeaderVersion != 1: - printer('Error: Unknown PFS Information Header Version %d!' % entry_info_hdr.HeaderVersion, pfs_padd + 8) + printer(f'Error: Unknown PFS Information Header Version {entry_info_hdr.HeaderVersion}!', pfs_padd + 8) break # Skip PFS Signature Entries/Descriptors in case of unknown Header Version # PFS Signature Entries/Descriptors have DellPfsInfo + DellPfsEntryR* + Sign Size [0x2] + Sign Data [Sig Size] @@ -484,12 +484,12 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i sign_info_start = sign_start + PFS_INFO_LEN + pfs_entry_size sign_size = int.from_bytes(signature_info[sign_info_start:sign_info_start + 0x2], 'little') sign_data_raw = signature_info[sign_info_start + 0x2:sign_info_start + 0x2 + sign_size] - sign_data_txt = '%0.*X' % (sign_size * 2, int.from_bytes(sign_data_raw, 'little')) + sign_data_txt = f'{int.from_bytes(sign_data_raw, "little"):0{sign_size * 2}X}' if is_structure: printer('Signature Information:\n', pfs_padd + 8) - printer('Signature Size: 0x%X' % sign_size, pfs_padd + 12, False) - printer('Signature Data: %s [...]' % sign_data_txt[:32], pfs_padd + 12, False) + printer(f'Signature Size: 0x{sign_size:X}', pfs_padd + 12, False) + printer(f'Signature Data: {sign_data_txt[:32]} [...]', pfs_padd + 12, False) # The next PFS Signature Entry/Descriptor starts after the previous Signature Data sign_start += (PFS_INFO_LEN + pfs_entry_size + 0x2 + sign_size) @@ -533,7 +533,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i # its PFS Information should contain their names (CombineBiosNameX). Since the main/first # full PFS structure has count/index 1, the rest start at 2+ and thus, their PFS Information # names can be retrieved in order by subtracting 2 from the main/first PFS Information values - sub_pfs_name = ' %s v%s' % (info_all[pfs_count - 2][1], info_all[pfs_count - 2][2]) if info_all else ' UNKNOWN' + sub_pfs_name = f' {info_all[pfs_count - 2][1]} v{info_all[pfs_count - 2][2]}' if info_all else ' UNKNOWN' # Set the sub-PFS output path (create sub-folders for each sub-PFS and its ZLIB sections) sub_pfs_path = os.path.join(output_path, str(pfs_count) + safe_name(sub_pfs_name)) @@ -597,7 +597,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i # Write/Extract PFS Entry files for file in write_files: - full_name = '%d%s -- %d %s v%s' % (pfs_index, pfs_name, file_index, file_name, file_version) # Full PFS Entry Name + full_name = f'{pfs_index}{pfs_name} -- {file_index} {file_name} v{file_version}' # Full PFS Entry Name pfs_file_write(file[0], file[1], file_type, full_name, output_path, pfs_padd, is_structure, is_advanced) # Get PFS Footer Data after PFS Header Payload @@ -621,13 +621,13 @@ def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, p # Validate that the PFS Entry Reserved field is empty if pfs_entry.Reserved != 0: - printer('Error: Detected non-empty %s Reserved field!' % text, padding + 8) + printer(f'Error: Detected non-empty {text} Reserved field!', padding + 8) # Get PFS Entry Version string via "Version" and "VersionType" fields entry_version = get_entry_ver(pfs_entry.Version, pfs_entry.VersionType) # Get PFS Entry GUID in Big Endian format - entry_guid = '%0.*X' % (0x10 * 2, int.from_bytes(pfs_entry.GUID, 'little')) + entry_guid = f'{int.from_bytes(pfs_entry.GUID, "little"):0{0x10 * 2}X}' # PFS Entry Data starts after the PFS Entry Structure entry_data_start = entry_start + entry_size @@ -694,7 +694,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True): # Show sub-PFS PFAT Header Structure info if is_structure: - printer('PFAT Block %d Header:\n' % pfat_entry_index, padding + 12) + printer(f'PFAT Block {pfat_entry_index} Header:\n', padding + 12) pfat_hdr.struct_print(padding + 16) pfat_script_start = pfat_hdr_off + PFAT_HDR_LEN # PFAT Block Script Start @@ -719,12 +719,12 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True): # Show sub-PFS PFAT Signature Structure info if is_structure: - printer('PFAT Block %d Signature:\n' % pfat_entry_index, padding + 12) + printer(f'PFAT Block {pfat_entry_index} Signature:\n', padding + 12) pfat_sig.struct_print(padding + 16) # Show PFAT Script via BIOS Guard Script Tool if is_structure: - printer('PFAT Block %d Script:\n' % pfat_entry_index, padding + 12) + printer(f'PFAT Block {pfat_entry_index} Script:\n', padding + 12) # https://github.com/allowitsme/big-tool by Dmitry Frolov _ = parse_bg_script(pfat_script_data, padding + 16) @@ -741,7 +741,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True): # Show sub-PFS PFAT Metadata Structure info if is_structure: - printer('PFAT Block %d Metadata:\n' % pfat_entry_index, padding + 12) + printer(f'PFAT Block {pfat_entry_index} Metadata:\n', padding + 12) pfat_met.struct_print(padding + 16) # Another way to get each PFAT Entry payload's Order is from its Metadata at 0x8-0xC, if applicable @@ -801,10 +801,10 @@ def get_entry_ver(version_fields, version_types): for index,field in enumerate(version_fields): eol = '' if index == len(version_fields) - 1 else '.' - if version_types[index] == 65: version += '%X%s' % (field, eol) # 0x41 = ASCII - elif version_types[index] == 78: version += '%d%s' % (field, eol) # 0x4E = Number + if version_types[index] == 65: version += f'{field:X}{eol}' # 0x41 = ASCII + elif version_types[index] == 78: version += f'{field:d}{eol}' # 0x4E = Number elif version_types[index] in (0, 32): version = version.strip('.') # 0x00 or 0x20 = Unused - else: version += '%X%s' % (field, eol) # Unknown + else: version += f'{field:X}{eol}' # Unknown return version @@ -812,7 +812,7 @@ def get_entry_ver(version_fields, version_types): def chk_hdr_ver(version, text, padding): if version in (1,2): return - printer('Error: Unknown %s Header Version %d!' % (text, version), padding) + printer(f'Error: Unknown {text} Header Version {version}!', padding) # Analyze Dell PFS Footer Structure def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_structure=True): @@ -826,24 +826,24 @@ def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_structu printer('PFS Footer:\n', padding + 4) pfs_ftr.struct_print(padding + 8) else: - printer('Error: %s Footer could not be found!' % text, padding + 4) + printer(f'Error: {text} Footer could not be found!', padding + 4) # Validate that PFS Header Payload Size matches the one at PFS Footer if data_size != pfs_ftr.PayloadSize: - printer('Error: %s Header & Footer Payload Size mismatch!' % text, padding + 4) + printer(f'Error: {text} Header & Footer Payload Size mismatch!', padding + 4) # Calculate the PFS Payload Data CRC-32 w/ Vector 0 pfs_ftr_crc = ~zlib.crc32(data_buffer, 0) & 0xFFFFFFFF # Validate PFS Payload Data Checksum via PFS Footer if pfs_ftr.Checksum != pfs_ftr_crc: - printer('Error: Invalid %s Footer Payload Checksum!' % text, padding + 4) + printer(f'Error: Invalid {text} Footer Payload Checksum!', padding + 4) # Write/Extract Dell PFS Entry Files (Data, Metadata, Signature) def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, is_structure=True, is_advanced=True): # Store Data/Metadata Signature (advanced users only) if bin_name.startswith('sign'): - final_name = '%s.%s.sig' % (safe_name(full_name), bin_name.split('_')[1]) + final_name = f'{safe_name(full_name)}.{bin_name.split("_")[1]}.sig' final_path = os.path.join(out_path, final_name) with open(final_path, 'wb') as pfs_out: pfs_out.write(bin_buff) # Write final Data/Metadata Signature @@ -851,12 +851,12 @@ def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, i return # Skip further processing for Signatures # Store Data/Metadata Payload - bin_ext = '.%s.bin' % bin_name if is_advanced else '.bin' # Simpler Data/Metadata Extension for non-advanced users + bin_ext = f'.{bin_name}.bin' if is_advanced else '.bin' # Simpler Data/Metadata Extension for non-advanced users # Some Data may be Text or XML files with useful information for non-advanced users is_text,final_data,file_ext,write_mode = bin_is_text(bin_buff, bin_type, bin_name == 'meta', padding, is_structure, is_advanced) - final_name = '%s%s' % (safe_name(full_name), bin_ext[:-4] + file_ext if is_text else bin_ext) + final_name = f'{safe_name(full_name)}{bin_ext[:-4] + file_ext if is_text else bin_ext}' final_path = os.path.join(out_path, final_name) with open(final_path, write_mode) as pfs_out: pfs_out.write(final_data) # Write final Data/Metadata Payload @@ -893,7 +893,7 @@ def bin_is_text(buffer, file_type, is_metadata, pfs_padd, is_structure=True, is_ # Show Model/PCR XML Information, if applicable if is_structure and is_text and not is_metadata: # Metadata is shown at initial DellPfsMetadata analysis - printer('PFS %s Information:\n' % {'.txt': 'Model', '.xml': 'PCR XML'}[extension], pfs_padd + 8) + printer(f'PFS { {".txt": "Model", ".xml": "PCR XML"}[extension] } Information:\n', pfs_padd + 8) _ = [printer(line.strip('\r'), pfs_padd + 12, False) for line in buffer.split('\n') if line] # Only for non-advanced users due to signature (.sig) invalidation @@ -957,7 +957,7 @@ if __name__ == '__main__': continue # Next input file - extract_path = os.path.join(output_path, input_name + '_extracted') + extract_path = os.path.join(output_path, f'{input_name}_extracted') extract_name = ' ' + os.path.splitext(input_name)[0] diff --git a/Phoenix_TDK_Extract.py b/Phoenix_TDK_Extract.py new file mode 100644 index 0000000..85ddcff --- /dev/null +++ b/Phoenix_TDK_Extract.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Phoenix TDK Extract +Phoenix TDK Packer Extractor +Copyright (C) 2021-2022 Plato Mavropoulos +""" + +TITLE = 'Phoenix TDK Packer Extractor v2.0_a4' + +import os +import sys +import lzma +import ctypes + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.path_ops import safe_name, make_dirs +from common.patterns import PAT_PHOENIX_TDK +from common.struct_ops import get_struct, char, uint32_t +from common.system import script_init, argparse_init, printer +from common.text_ops import file_to_bytes + +class PhoenixTdkHeader(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Tag', char*8), # 0x00 + ('Size', uint32_t), # 0x08 + ('Count', uint32_t), # 0x0C + # 0x10 + ] + + def _get_tag(self): + return self.Tag.decode('utf-8','ignore').strip() + + def struct_print(self, p): + printer(['Tag :', self._get_tag()], p, False) + printer(['Size :', f'0x{self.Size:X}'], p, False) + printer(['Entries:', self.Count], p, False) + +class PhoenixTdkEntry(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Name', char*256), # 0x000 + ('Offset', uint32_t), # 0x100 + ('Size', uint32_t), # 0x104 + ('Compressed', uint32_t), # 0x108 + ('Reserved', uint32_t), # 0x10C + # 0x110 + ] + + COMP = {0: 'None', 1: 'LZMA'} + + def get_name(self): + return self.Name.decode('utf-8','replace').strip() + + def get_compression(self): + return self.COMP.get(self.Compressed, f'Unknown ({self.Compressed})') + + def struct_print(self, p): + printer(['Name :', self.get_name()], p, False) + printer(['Offset :', f'0x{self.Offset:X}'], p, False) + printer(['Size :', f'0x{self.Size:X}'], p, False) + printer(['Compression:', self.get_compression()], p, False) + printer(['Reserved :', f'0x{self.Reserved:X}'], p, False) + +# Scan input buffer for Phoenix TDK pattern +def get_phoenix_tdk(in_buffer): + return PAT_PHOENIX_TDK.search(in_buffer) + +# Check if input is Phoenix TDK image +def is_phoenix_tdk(in_file): + buffer = file_to_bytes(in_file) + + return bool(get_phoenix_tdk(buffer)) + +# Parse & Extract Phoenix Tools Development Kit (TDK) Packer +def phoenix_tdk_extract(input_buffer, output_path, padding=0): + exit_code = 0 + + extract_path = os.path.join(f'{output_path}_extracted') + + make_dirs(extract_path, delete=True) + + printer('Phoenix Tools Development Kit Packer', padding) + + # Search for Phoenix TDK Package pattern + tdk_match = get_phoenix_tdk(input_buffer) + + # Parse TDK Header structure + tdk_hdr = get_struct(input_buffer, tdk_match.start(), PhoenixTdkHeader) + + # Print TDK Header structure info + printer('Phoenix TDK Header:\n', padding + 4) + tdk_hdr.struct_print(padding + 8) + + # Check if reported TDK Header Size matches manual TDK Entry Count calculation + if tdk_hdr.Size != TDK_HDR_LEN + TDK_DUMMY_LEN + tdk_hdr.Count * TDK_MOD_LEN: + printer('Error: Phoenix TDK Header Size & Entry Count mismatch!\n', padding + 8, pause=True) + exit_code = 1 + + # Store TDK Entries offset after the dummy/placeholder data + entries_off = tdk_match.start() + TDK_HDR_LEN + TDK_DUMMY_LEN + + # Parse and extract each TDK Header Entry + for entry_index in range(tdk_hdr.Count): + # Parse TDK Entry structure + tdk_mod = get_struct(input_buffer, entries_off + entry_index * TDK_MOD_LEN, PhoenixTdkEntry) + + # Print TDK Entry structure info + printer(f'Phoenix TDK Entry ({entry_index + 1}/{tdk_hdr.Count}):\n', padding + 8) + tdk_mod.struct_print(padding + 12) + + # Store TDK Entry raw data (relative to 0x0, not TDK Header) + mod_data = input_buffer[tdk_mod.Offset:tdk_mod.Offset + tdk_mod.Size] + + # Check if TDK Entry raw data is complete + if len(mod_data) != tdk_mod.Size: + printer('Error: Phoenix TDK Entry > Data is truncated!\n', padding + 12, pause=True) + exit_code = 2 + + # Check if TDK Entry Reserved is present + if tdk_mod.Reserved: + printer('Error: Phoenix TDK Entry > Reserved is not empty!\n', padding + 12, pause=True) + exit_code = 3 + + # Decompress TDK Entry raw data, when applicable (i.e. LZMA) + if tdk_mod.get_compression() == 'LZMA': + mod_data = lzma.LZMADecompressor().decompress(mod_data) + + # Generate TDK Entry file name, avoid crash if Entry data is bad + mod_name = tdk_mod.get_name() or f'Unknown_{entry_index + 1:02d}.bin' + + # Generate TDK Entry file data output path + mod_file = os.path.join(extract_path, safe_name(mod_name)) + + # Account for potential duplicate file names + if os.path.isfile(mod_file): mod_file += f'_{entry_index + 1:02d}' + + # Save TDK Entry data to output file + with open(mod_file, 'wb') as out_file: out_file.write(mod_data) + + return exit_code + +# Get ctypes Structure Sizes +TDK_HDR_LEN = ctypes.sizeof(PhoenixTdkHeader) +TDK_MOD_LEN = ctypes.sizeof(PhoenixTdkEntry) + +# Set dummy/placeholder TDK Entries Size +TDK_DUMMY_LEN = 0x200 # Top 2, Names only + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + + # Check if Phoenix TDK Packer pattern was found on executable + if not is_phoenix_tdk(input_buffer): + printer('Error: This is not a Phoenix TDK Packer executable!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + if phoenix_tdk_extract(input_buffer, extract_path, padding) == 0: + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/Portwell_EFI_Extract.py b/Portwell_EFI_Extract.py index 3546065..c241347 100644 --- a/Portwell_EFI_Extract.py +++ b/Portwell_EFI_Extract.py @@ -3,11 +3,11 @@ """ Portwell EFI Extract -Portwell EFI BIOS Extractor +Portwell EFI Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Portwell EFI BIOS Extractor v2.0_a4' +TITLE = 'Portwell EFI Update Extractor v2.0_a5' import os import sys @@ -54,8 +54,8 @@ def get_portwell_pe(in_buffer): return pe_file, pe_data # Parse & Extract Portwell UEFI Unpacker -def portwell_efi_extract(input_buffer, out_path, padding=0): - extract_path = os.path.join(out_path + '_extracted') +def portwell_efi_extract(input_buffer, output_path, padding=0): + extract_path = os.path.join(f'{output_path}_extracted') make_dirs(extract_path, delete=True) diff --git a/README.md b/README.md index df5392a..6bb9277 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,10 @@ BIOS Utilities Donation via Paypal or Debit/Credit Card * [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) -* [**AMI UCP BIOS Extractor**](#ami-ucp-bios-extractor) +* [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) -* [**Portwell EFI BIOS Extractor**](#portwell-efi-bios-extractor) +* [**Phoenix TDK Packer Extractor**](#phoenix-tdk-packer-extractor) +* [**Portwell EFI Update Extractor**](#portwell-efi-update-extractor) ## **Dell PFS Update Extractor** @@ -71,17 +72,17 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() -## **AMI UCP BIOS Extractor** +## **AMI UCP Update Extractor** ![]() #### **Description** -Parses AMI UCP (Utility Configuration Program) BIOS executables, extracts their firmware components (e.g. SPI/BIOS/UEFI, EC, ME etc) and shows all relevant info. It supports all AMI UCP revisions and formats, including those with nested AMI PFAT, AMI UCP or Insyde SFX structures. The output comprises only final firmware components and utilities which are directly usable by end users. +Parses AMI UCP (Utility Configuration Program) Update executables, extracts their firmware components (e.g. SPI/BIOS/UEFI, EC, ME etc) and shows all relevant info. It supports all AMI UCP revisions and formats, including those with nested AMI PFAT, AMI UCP or Insyde SFX structures. The output comprises only final firmware components and utilities which are directly usable by end users. #### **Usage** -You can either Drag & Drop or manually enter AMI UCP BIOS executable file(s). Optional arguments: +You can either Drag & Drop or manually enter AMI UCP Update executable file(s). Optional arguments: * -h or --help : show help message and exit * -v or --version : show utility name and version @@ -199,7 +200,60 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() -## **Portwell EFI BIOS Extractor** +## **Phoenix TDK Packer Extractor** + +![]() + +#### **Description** + +Parses Phoenix Tools Development Kit (TDK) Packer executables and extracts their firmware (e.g. SPI, BIOS/UEFI, EC etc) and utilities (e.g. WinFlash etc) components. It supports all Phoenix TDK Packer revisions and formats, including those which contain LZMA compressed files. The output comprises only final firmware components which are directly usable by end users. + +#### **Usage** + +You can either Drag & Drop or manually enter Phoenix Tools Development Kit (TDK) Packer executable file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts +* --static : use static-built external dependencies + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. + +#### **Prerequisites** + +No prerequisites needed to run the utility. + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.8.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Build/Freeze/Compile: + +> pyinstaller --noupx --onefile \\/Phoenix_TDK_Extract.py + +At dist folder you should find the final utility executable + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + +## **Portwell EFI Update Extractor** ![]() diff --git a/common/a7z_comp.py b/common/a7z_comp.py index 99de48a..5293d68 100644 --- a/common/a7z_comp.py +++ b/common/a7z_comp.py @@ -23,7 +23,7 @@ def is_7z_supported(in_path, padding=0, static=False): try: subprocess.run([get_7z_path(static), 't', in_path, '-bso0', '-bse0', '-bsp0'], check=True) except: - printer('Error: 7-Zip could not check support for file %s!' % in_path, padding) + printer(f'Error: 7-Zip could not check support for file {in_path}!', padding) return False @@ -38,10 +38,10 @@ def a7z_decompress(in_path, out_path, in_name, padding=0, static=False): if not os.path.isdir(out_path): raise Exception('EXTRACT_DIR_MISSING') except: - printer('Error: 7-Zip could not extract %s file %s!' % (in_name, in_path), padding) + printer(f'Error: 7-Zip could not extract {in_name} file {in_path}!', padding) return 1 - printer('Succesfull %s decompression via 7-Zip!' % in_name, padding) + printer(f'Succesfull {in_name} decompression via 7-Zip!', padding) return 0 diff --git a/common/efi_comp.py b/common/efi_comp.py index e2c283f..da6b57f 100644 --- a/common/efi_comp.py +++ b/common/efi_comp.py @@ -43,7 +43,7 @@ def efi_decompress(in_path, out_path, padding=0, comp_type='--uefi'): if os.path.getsize(out_path) != size_orig: raise Exception('EFI_DECOMPRESS_ERROR') except: - printer('Error: TianoCompress could not extract file %s!' % in_path, padding) + printer(f'Error: TianoCompress could not extract file {in_path}!', padding) return 1 diff --git a/common/patterns.py b/common/patterns.py index d66f268..326cdcb 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -13,3 +13,4 @@ PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) +PAT_PHOENIX_TDK = re.compile(br'\$PACK\x00{3}..\x00{2}.\x00{3}', re.DOTALL) diff --git a/common/system.py b/common/system.py index b08ae1f..621755c 100644 --- a/common/system.py +++ b/common/system.py @@ -35,7 +35,7 @@ def check_sys_py(): sys_py = get_py_ver() if sys_py < (3,8): - sys.stdout.write('\nError: Python >= 3.8 required, not %d.%d!' % (sys_py[0], sys_py[1])) + sys.stdout.write(f'\nError: Python >= 3.8 required, not {sys_py[0]}.{sys_py[1]}!') if not is_auto_exit(): # noinspection PyUnresolvedReferences @@ -48,7 +48,7 @@ def check_sys_os(): os_tag,os_win,os_sup = get_os_ver() if not os_sup: - printer('Error: Unsupported platform "%s"!' % os_tag) + printer(f'Error: Unsupported platform "{os_tag}"!') if not is_auto_exit(): input('\nPress enter to exit') From 8b561640dbcbc67d27e0656486e594b9a89af507 Mon Sep 17 00:00:00 2001 From: platomav Date: Mon, 23 May 2022 21:04:05 +0300 Subject: [PATCH 28/52] Phoenix TDK Packer Extractor v2.0_a5 Added detection of TDK Packer executable base offset Improve TDK unpacking at weird images --- Phoenix_TDK_Extract.py | 129 ++++++++++++++++++++++++++++++++-------- Portwell_EFI_Extract.py | 13 ++-- README.md | 10 +++- common/patterns.py | 3 + 4 files changed, 120 insertions(+), 35 deletions(-) diff --git a/Phoenix_TDK_Extract.py b/Phoenix_TDK_Extract.py index 85ddcff..8133047 100644 --- a/Phoenix_TDK_Extract.py +++ b/Phoenix_TDK_Extract.py @@ -7,18 +7,19 @@ Phoenix TDK Packer Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Phoenix TDK Packer Extractor v2.0_a4' +TITLE = 'Phoenix TDK Packer Extractor v2.0_a5' import os import sys import lzma +import pefile import ctypes # Stop __pycache__ generation sys.dont_write_bytecode = True from common.path_ops import safe_name, make_dirs -from common.patterns import PAT_PHOENIX_TDK +from common.patterns import PAT_PHOENIX_TDK, PAT_MICROSOFT_MZ, PAT_MICROSOFT_PE from common.struct_ops import get_struct, char, uint32_t from common.system import script_init, argparse_init, printer from common.text_ops import file_to_bytes @@ -53,31 +54,98 @@ class PhoenixTdkEntry(ctypes.LittleEndianStructure): COMP = {0: 'None', 1: 'LZMA'} + def __init__(self, mz_base, *args, **kwargs): + super().__init__(*args, **kwargs) + self.Base = mz_base + def get_name(self): return self.Name.decode('utf-8','replace').strip() + def get_offset(self): + return self.Base + self.Offset + def get_compression(self): return self.COMP.get(self.Compressed, f'Unknown ({self.Compressed})') def struct_print(self, p): printer(['Name :', self.get_name()], p, False) - printer(['Offset :', f'0x{self.Offset:X}'], p, False) + printer(['Offset :', f'0x{self.get_offset():X}'], p, False) printer(['Size :', f'0x{self.Size:X}'], p, False) printer(['Compression:', self.get_compression()], p, False) printer(['Reserved :', f'0x{self.Reserved:X}'], p, False) -# Scan input buffer for Phoenix TDK pattern -def get_phoenix_tdk(in_buffer): - return PAT_PHOENIX_TDK.search(in_buffer) +# Get Phoenix TDK Executable (MZ) Base Offset +def get_tdk_base(in_buffer, pack_off): + tdk_base_off = None # Initialize Phoenix TDK Base MZ Offset + + # Scan input file for all Microsoft executable patterns (MZ) before TDK Header Offset + mz_all = [mz for mz in PAT_MICROSOFT_MZ.finditer(in_buffer) if mz.start() < pack_off] + + # Phoenix TDK Header structure is an index table for all TDK files + # Each TDK file is referenced from the TDK Packer executable base + # The TDK Header is always at the end of the TDK Packer executable + # Thus, prefer the TDK Packer executable (MZ) closest to TDK Header + # For speed, check MZ closest to (or at) 0x0 first (expected input) + mz_ord = [mz_all[0]] + list(reversed(mz_all[1:])) + + # Parse each detected MZ + for mz in mz_ord: + mz_off = mz.start() + + # MZ (DOS) > PE (NT) image offset is found at offset 0x3C-0x40 relative to MZ base + pe_off = mz_off + int.from_bytes(in_buffer[mz_off + 0x3C:mz_off + 0x40], 'little') + + # Check if potential MZ > PE image magic value is valid + if PAT_MICROSOFT_PE.search(in_buffer[pe_off:pe_off + 0x4]): + try: + # Analyze detected MZ > PE image buffer + pe_file = pefile.PE(data=in_buffer[mz_off:]) + + # Attempt to retrieve the PE > "Product Name" version string value + pe_name = pe_file.FileInfo[0][0].StringTable[0].entries[b'ProductName'] + except: + # Any error means no PE > "Product Name" retrieved + pe_name = b'' + + # Check for valid Phoenix TDK Packer PE > "Product Name" + # Expected value is "TDK Packer (Extractor for Windows)" + if pe_name.upper().startswith(b'TDK PACKER'): + # Set TDK Base Offset to valid TDK Packer MZ offset + tdk_base_off = mz_off + + # Stop parsing detected MZ once TDK Base Offset is found + if tdk_base_off: + break + else: + # No TDK Base Offset could be found, assume 0x0 + tdk_base_off = 0x0 + + return tdk_base_off -# Check if input is Phoenix TDK image +# Scan input buffer for valid Phoenix TDK image +def get_phoenix_tdk(in_buffer): + # Scan input buffer for Phoenix TDK pattern + tdk_match = PAT_PHOENIX_TDK.search(in_buffer) + + if not tdk_match: + return None, None + + # Set Phoenix TDK Header ($PACK) Offset + tdk_pack_off = tdk_match.start() + + # Get Phoenix TDK Executable (MZ) Base Offset + tdk_base_off = get_tdk_base(in_buffer, tdk_pack_off) + + return tdk_base_off, tdk_pack_off + +# Check if input contains valid Phoenix TDK image def is_phoenix_tdk(in_file): buffer = file_to_bytes(in_file) - return bool(get_phoenix_tdk(buffer)) + return bool(get_phoenix_tdk(buffer)[1]) # Parse & Extract Phoenix Tools Development Kit (TDK) Packer -def phoenix_tdk_extract(input_buffer, output_path, padding=0): +def phoenix_tdk_extract(input_buffer, output_path, pack_off, base_off=0, padding=0): exit_code = 0 extract_path = os.path.join(f'{output_path}_extracted') @@ -86,11 +154,8 @@ def phoenix_tdk_extract(input_buffer, output_path, padding=0): printer('Phoenix Tools Development Kit Packer', padding) - # Search for Phoenix TDK Package pattern - tdk_match = get_phoenix_tdk(input_buffer) - # Parse TDK Header structure - tdk_hdr = get_struct(input_buffer, tdk_match.start(), PhoenixTdkHeader) + tdk_hdr = get_struct(input_buffer, pack_off, PhoenixTdkHeader) # Print TDK Header structure info printer('Phoenix TDK Header:\n', padding + 4) @@ -101,34 +166,46 @@ def phoenix_tdk_extract(input_buffer, output_path, padding=0): printer('Error: Phoenix TDK Header Size & Entry Count mismatch!\n', padding + 8, pause=True) exit_code = 1 - # Store TDK Entries offset after the dummy/placeholder data - entries_off = tdk_match.start() + TDK_HDR_LEN + TDK_DUMMY_LEN + # Store TDK Entries offset after the placeholder data + entries_off = pack_off + TDK_HDR_LEN + TDK_DUMMY_LEN # Parse and extract each TDK Header Entry for entry_index in range(tdk_hdr.Count): # Parse TDK Entry structure - tdk_mod = get_struct(input_buffer, entries_off + entry_index * TDK_MOD_LEN, PhoenixTdkEntry) + tdk_mod = get_struct(input_buffer, entries_off + entry_index * TDK_MOD_LEN, PhoenixTdkEntry, [base_off]) # Print TDK Entry structure info printer(f'Phoenix TDK Entry ({entry_index + 1}/{tdk_hdr.Count}):\n', padding + 8) tdk_mod.struct_print(padding + 12) - # Store TDK Entry raw data (relative to 0x0, not TDK Header) - mod_data = input_buffer[tdk_mod.Offset:tdk_mod.Offset + tdk_mod.Size] + # Get TDK Entry raw data Offset (TDK Base + Entry Offset) + mod_off = tdk_mod.get_offset() + + # Check if TDK Entry raw data Offset is valid + if mod_off >= len(input_buffer): + printer('Error: Phoenix TDK Entry > Offset is out of bounds!\n', padding + 12, pause=True) + exit_code = 2 + + # Store TDK Entry raw data (relative to TDK Base, not TDK Header) + mod_data = input_buffer[mod_off:mod_off + tdk_mod.Size] # Check if TDK Entry raw data is complete if len(mod_data) != tdk_mod.Size: printer('Error: Phoenix TDK Entry > Data is truncated!\n', padding + 12, pause=True) - exit_code = 2 + exit_code = 3 # Check if TDK Entry Reserved is present if tdk_mod.Reserved: printer('Error: Phoenix TDK Entry > Reserved is not empty!\n', padding + 12, pause=True) - exit_code = 3 + exit_code = 4 # Decompress TDK Entry raw data, when applicable (i.e. LZMA) if tdk_mod.get_compression() == 'LZMA': - mod_data = lzma.LZMADecompressor().decompress(mod_data) + try: + mod_data = lzma.LZMADecompressor().decompress(mod_data) + except: + printer('Error: Phoenix TDK Entry > LZMA decompression failed!\n', padding + 12, pause=True) + exit_code = 5 # Generate TDK Entry file name, avoid crash if Entry data is bad mod_name = tdk_mod.get_name() or f'Unknown_{entry_index + 1:02d}.bin' @@ -148,8 +225,8 @@ def phoenix_tdk_extract(input_buffer, output_path, padding=0): TDK_HDR_LEN = ctypes.sizeof(PhoenixTdkHeader) TDK_MOD_LEN = ctypes.sizeof(PhoenixTdkEntry) -# Set dummy/placeholder TDK Entries Size -TDK_DUMMY_LEN = 0x200 # Top 2, Names only +# Set placeholder TDK Entries Size +TDK_DUMMY_LEN = 0x200 if __name__ == '__main__': # Set argparse Arguments @@ -166,15 +243,17 @@ if __name__ == '__main__': with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + tdk_base_off,tdk_pack_off = get_phoenix_tdk(input_buffer) + # Check if Phoenix TDK Packer pattern was found on executable - if not is_phoenix_tdk(input_buffer): + if not tdk_pack_off: printer('Error: This is not a Phoenix TDK Packer executable!', padding) continue # Next input file extract_path = os.path.join(output_path, input_name) - if phoenix_tdk_extract(input_buffer, extract_path, padding) == 0: + if phoenix_tdk_extract(input_buffer, extract_path, tdk_pack_off, tdk_base_off, padding) == 0: exit_code -= 1 printer('Done!', pause=True) diff --git a/Portwell_EFI_Extract.py b/Portwell_EFI_Extract.py index c241347..c0abac9 100644 --- a/Portwell_EFI_Extract.py +++ b/Portwell_EFI_Extract.py @@ -7,7 +7,7 @@ Portwell EFI Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Portwell EFI Update Extractor v2.0_a5' +TITLE = 'Portwell EFI Update Extractor v2.0_a6' import os import sys @@ -18,13 +18,10 @@ sys.dont_write_bytecode = True from common.efi_comp import efi_decompress, is_efi_compressed from common.path_ops import safe_name, make_dirs +from common.patterns import PAT_PORTWELL_EFI, PAT_MICROSOFT_MZ from common.system import script_init, argparse_init, printer from common.text_ops import file_to_bytes -PEFI_MAGIC = br'MZ' - -FILE_MAGIC = br'' - FILE_NAMES = { 0 : 'Flash.efi', 1 : 'Fparts.txt', @@ -40,8 +37,8 @@ def is_portwell_efi(in_file): try: pe_buffer = get_portwell_pe(in_buffer)[1] except: pe_buffer = b'' - is_mz = in_buffer.startswith(PEFI_MAGIC) # EFI images start with PE Header MZ - is_uu = pe_buffer.startswith(FILE_MAGIC) # Portwell EFI files start with + is_mz = in_buffer.startswith(PAT_MICROSOFT_MZ.pattern) # EFI images start with PE Header MZ + is_uu = pe_buffer.startswith(PAT_PORTWELL_EFI.pattern) # Portwell EFI files start with return is_mz and is_uu @@ -65,7 +62,7 @@ def portwell_efi_extract(input_buffer, output_path, padding=0): printer(efi_title, padding) - efi_files = pe_data.split(FILE_MAGIC) # Split EFI Payload into file chunks + efi_files = pe_data.split(PAT_PORTWELL_EFI.pattern) # Split EFI Payload into file chunks parse_efi_files(extract_path, efi_files[1:], padding) diff --git a/README.md b/README.md index 6bb9277..7f0e12a 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,9 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 #### **Prerequisites** -No prerequisites needed to run the utility. +To run the utility, you must have the following 3rd party Python module installed: + +* [pefile](https://pypi.org/project/pefile/) #### **Build/Freeze/Compile with PyInstaller** @@ -239,7 +241,11 @@ PyInstaller can build/freeze/compile the utility at all three supported platform > pip3 install pyinstaller -3. Build/Freeze/Compile: +3. Use pip to install pefile: + +> pip3 install pefile + +4. Build/Freeze/Compile: > pyinstaller --noupx --onefile \\/Phoenix_TDK_Extract.py diff --git a/common/patterns.py b/common/patterns.py index 326cdcb..c26f02b 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -13,4 +13,7 @@ PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) +PAT_MICROSOFT_MZ = re.compile(br'MZ') +PAT_MICROSOFT_PE = re.compile(br'PE\x00\x00') PAT_PHOENIX_TDK = re.compile(br'\$PACK\x00{3}..\x00{2}.\x00{3}', re.DOTALL) +PAT_PORTWELL_EFI = re.compile(br'') From aea54aeaad200194d58b599579acf0c0127e9917 Mon Sep 17 00:00:00 2001 From: platomav Date: Tue, 24 May 2022 14:36:46 +0300 Subject: [PATCH 29/52] Phoenix TDK Packer Extractor v2.0_a6 Dramatically increase TDK Packer base offset detection speed Applied regex pattern improvements --- Phoenix_TDK_Extract.py | 21 ++++++++++++++------- Portwell_EFI_Extract.py | 17 ++++++++++++----- common/patterns.py | 6 +++--- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/Phoenix_TDK_Extract.py b/Phoenix_TDK_Extract.py index 8133047..2d93793 100644 --- a/Phoenix_TDK_Extract.py +++ b/Phoenix_TDK_Extract.py @@ -7,7 +7,7 @@ Phoenix TDK Packer Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Phoenix TDK Packer Extractor v2.0_a5' +TITLE = 'Phoenix TDK Packer Extractor v2.0_a6' import os import sys @@ -92,29 +92,36 @@ def get_tdk_base(in_buffer, pack_off): for mz in mz_ord: mz_off = mz.start() - # MZ (DOS) > PE (NT) image offset is found at offset 0x3C-0x40 relative to MZ base + # MZ (DOS) > PE (NT) image Offset is found at offset 0x3C-0x40 relative to MZ base pe_off = mz_off + int.from_bytes(in_buffer[mz_off + 0x3C:mz_off + 0x40], 'little') + # Skip MZ (DOS) with bad PE (NT) image Offset + if pe_off == mz_off or pe_off >= pack_off: + continue + # Check if potential MZ > PE image magic value is valid if PAT_MICROSOFT_PE.search(in_buffer[pe_off:pe_off + 0x4]): try: - # Analyze detected MZ > PE image buffer - pe_file = pefile.PE(data=in_buffer[mz_off:]) + # Analyze detected MZ > PE image buffer quickly (fast_load) + pe_file = pefile.PE(data=in_buffer[mz_off:], fast_load=True) + + # Since fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed for FileInfo > StringTable + pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']]) # Attempt to retrieve the PE > "Product Name" version string value - pe_name = pe_file.FileInfo[0][0].StringTable[0].entries[b'ProductName'] + pe_name = pe_file.FileInfo[0][0].StringTable[0].entries[b'ProductName'].upper() except: # Any error means no PE > "Product Name" retrieved pe_name = b'' # Check for valid Phoenix TDK Packer PE > "Product Name" # Expected value is "TDK Packer (Extractor for Windows)" - if pe_name.upper().startswith(b'TDK PACKER'): + if pe_name.startswith(b'TDK PACKER'): # Set TDK Base Offset to valid TDK Packer MZ offset tdk_base_off = mz_off # Stop parsing detected MZ once TDK Base Offset is found - if tdk_base_off: + if tdk_base_off is not None: break else: # No TDK Base Offset could be found, assume 0x0 diff --git a/Portwell_EFI_Extract.py b/Portwell_EFI_Extract.py index c0abac9..edccf27 100644 --- a/Portwell_EFI_Extract.py +++ b/Portwell_EFI_Extract.py @@ -7,7 +7,7 @@ Portwell EFI Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Portwell EFI Update Extractor v2.0_a6' +TITLE = 'Portwell EFI Update Extractor v2.0_a7' import os import sys @@ -37,8 +37,8 @@ def is_portwell_efi(in_file): try: pe_buffer = get_portwell_pe(in_buffer)[1] except: pe_buffer = b'' - is_mz = in_buffer.startswith(PAT_MICROSOFT_MZ.pattern) # EFI images start with PE Header MZ - is_uu = pe_buffer.startswith(PAT_PORTWELL_EFI.pattern) # Portwell EFI files start with + is_mz = PAT_MICROSOFT_MZ.search(in_buffer[:0x2]) # EFI images start with PE Header MZ + is_uu = PAT_PORTWELL_EFI.search(pe_buffer[:0x4]) # Portwell EFI files start with return is_mz and is_uu @@ -52,6 +52,8 @@ def get_portwell_pe(in_buffer): # Parse & Extract Portwell UEFI Unpacker def portwell_efi_extract(input_buffer, output_path, padding=0): + efi_files = [] # Initialize EFI Payload file chunks + extract_path = os.path.join(f'{output_path}_extracted') make_dirs(extract_path, delete=True) @@ -62,9 +64,14 @@ def portwell_efi_extract(input_buffer, output_path, padding=0): printer(efi_title, padding) - efi_files = pe_data.split(PAT_PORTWELL_EFI.pattern) # Split EFI Payload into file chunks + # Split EFI Payload into file chunks + efi_list = list(PAT_PORTWELL_EFI.finditer(pe_data)) + for i,_ in enumerate(efi_list): + efi_bgn = efi_list[i].end() + efi_end = len(pe_data) if i == len(efi_list) - 1 else efi_list[i + 1].start() + efi_files.append(pe_data[efi_bgn:efi_end]) - parse_efi_files(extract_path, efi_files[1:], padding) + parse_efi_files(extract_path, efi_files, padding) # Get Portwell UEFI Unpacker tag def get_unpacker_tag(input_buffer, pe_file): diff --git a/common/patterns.py b/common/patterns.py index c26f02b..a94208a 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -7,13 +7,13 @@ Copyright (C) 2022 Plato Mavropoulos import re -PAT_AMI_PFAT = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) +PAT_AMI_PFAT = re.compile(br'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}@', re.DOTALL) PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) PAT_MICROSOFT_MZ = re.compile(br'MZ') -PAT_MICROSOFT_PE = re.compile(br'PE\x00\x00') +PAT_MICROSOFT_PE = re.compile(br'PE\x00{2}') PAT_PHOENIX_TDK = re.compile(br'\$PACK\x00{3}..\x00{2}.\x00{3}', re.DOTALL) -PAT_PORTWELL_EFI = re.compile(br'') +PAT_PORTWELL_EFI = re.compile(br'') From 711175776433d737ac972023d08d50c4e699dbcb Mon Sep 17 00:00:00 2001 From: platomav Date: Wed, 1 Jun 2022 02:22:59 +0300 Subject: [PATCH 30/52] Improved 7-Zip decompressor Removed --static argument Small fixes at variable names and f-strings --- AMI_UCP_Extract.py | 21 ++++++++++----------- Portwell_EFI_Extract.py | 4 ++-- README.md | 5 ----- common/{efi_comp.py => comp_efi.py} | 2 +- common/{a7z_comp.py => comp_szip.py} | 16 ++++++++-------- common/patterns.py | 4 ++++ common/system.py | 1 - 7 files changed, 25 insertions(+), 28 deletions(-) rename common/{efi_comp.py => comp_efi.py} (95%) rename common/{a7z_comp.py => comp_szip.py} (62%) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index f4a48d0..41c38cf 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'AMI UCP Update Extractor v2.0_a13' +TITLE = 'AMI UCP Update Extractor v2.0_a14' import os import re @@ -19,9 +19,9 @@ import contextlib # Stop __pycache__ generation sys.dont_write_bytecode = True -from common.a7z_comp import a7z_decompress, is_7z_supported from common.checksums import get_chk_16 -from common.efi_comp import efi_decompress, is_efi_compressed +from common.comp_efi import efi_decompress, is_efi_compressed +from common.comp_szip import is_szip_supported, szip_decompress from common.path_ops import agnostic_path, safe_name, safe_path, make_dirs from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t @@ -208,7 +208,7 @@ def get_uaf_mod(buffer, uaf_off=0x0): return uaf_all # Parse & Extract AMI UCP structures -def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False, is_static=False): +def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False): nal_dict = {} # Initialize @NAL Dictionary per UCP printer('Utility Configuration Program', padding) @@ -237,10 +237,10 @@ def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False, uaf_all = get_uaf_mod(buffer, UAF_HDR_LEN) for mod_info in uaf_all: - nal_dict = uaf_extract(buffer, extract_path, mod_info, padding + 8, is_checksum, is_static, nal_dict) + nal_dict = uaf_extract(buffer, extract_path, mod_info, padding + 8, is_checksum, nal_dict) # Parse & Extract AMI UCP > @UAF|@HPU Module/Section -def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, is_static=False, nal_dict=None): +def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, nal_dict=None): if nal_dict is None: nal_dict = {} uaf_tag,uaf_off,uaf_hdr = mod_info @@ -392,12 +392,12 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, is nal_dict[info_tag] = (info_path,info_name) # Assign a file path & name to each Tag # Parse Insyde BIOS @UAF|@HPU Module (@INS) - if uaf_tag == '@INS' and is_7z_supported(uaf_fname, padding + 4, static=is_static): + if uaf_tag == '@INS' and is_szip_supported(uaf_fname, padding + 4): ins_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-SFX')) # Generate extraction directory printer('Insyde BIOS 7z SFX Archive:', padding + 4) - if a7z_decompress(uaf_fname, ins_dir, '7z SFX', padding + 8, static=is_static) == 0: + if szip_decompress(uaf_fname, ins_dir, '7z SFX', padding + 8) == 0: os.remove(uaf_fname) # Successful extraction, delete @INS Module file/archive # Detect & Unpack AMI BIOS Guard (PFAT) BIOS image @@ -422,7 +422,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, is if nested_uaf_off: uaf_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-UCP')) # Generate extraction directory - ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum, is_static) # Call recursively + ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum) # Call recursively os.remove(uaf_fname) # Delete raw nested AMI UCP Structure after successful recursion/extraction @@ -498,7 +498,6 @@ if __name__ == '__main__': arguments = argparser.parse_args() is_checksum = arguments.checksum # Set Checksum verification optional argument - is_static = arguments.static # Set Static dependencies usage optional argument # Initialize script (must be after argparse) exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) @@ -520,7 +519,7 @@ if __name__ == '__main__': extract_path = os.path.join(output_path, input_name) - ucp_extract(main_uaf_bin, extract_path, main_uaf_tag, padding, is_checksum, is_static) + ucp_extract(main_uaf_bin, extract_path, main_uaf_tag, padding, is_checksum) exit_code -= 1 diff --git a/Portwell_EFI_Extract.py b/Portwell_EFI_Extract.py index edccf27..4efac6f 100644 --- a/Portwell_EFI_Extract.py +++ b/Portwell_EFI_Extract.py @@ -7,7 +7,7 @@ Portwell EFI Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Portwell EFI Update Extractor v2.0_a7' +TITLE = 'Portwell EFI Update Extractor v2.0_a8' import os import sys @@ -16,7 +16,7 @@ import pefile # Stop __pycache__ generation sys.dont_write_bytecode = True -from common.efi_comp import efi_decompress, is_efi_compressed +from common.comp_efi import efi_decompress, is_efi_compressed from common.path_ops import safe_name, make_dirs from common.patterns import PAT_PORTWELL_EFI, PAT_MICROSOFT_MZ from common.system import script_init, argparse_init, printer diff --git a/README.md b/README.md index 7f0e12a..0f6aa71 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts -* --static : use static-built external dependencies * -a or --advanced : extract signatures and metadata * -s or --structure : show PFS structure information @@ -89,7 +88,6 @@ You can either Drag & Drop or manually enter AMI UCP Update executable file(s). * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts -* --static : use static-built external dependencies * -c or --checksum : verify AMI UCP Checksums (slow) #### **Compatibility** @@ -158,7 +156,6 @@ You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s) * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts -* --static : use static-built external dependencies #### **Compatibility** @@ -217,7 +214,6 @@ You can either Drag & Drop or manually enter Phoenix Tools Development Kit (TDK) * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts -* --static : use static-built external dependencies #### **Compatibility** @@ -276,7 +272,6 @@ You can either Drag & Drop or manually enter Portwell UEFI Unpacker EFI executab * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts -* --static : use static-built external dependencies #### **Compatibility** diff --git a/common/efi_comp.py b/common/comp_efi.py similarity index 95% rename from common/efi_comp.py rename to common/comp_efi.py index da6b57f..75ca465 100644 --- a/common/efi_comp.py +++ b/common/comp_efi.py @@ -30,7 +30,7 @@ def is_efi_compressed(data, strict=True): # Get TianoCompress path def get_tiano_path(): - exec_name = 'TianoCompress' + ('.exe' if get_os_ver()[1] else '') + exec_name = f'TianoCompress{".exe" if get_os_ver()[1] else ""}' return safe_path(project_root(), ['external',exec_name]) diff --git a/common/a7z_comp.py b/common/comp_szip.py similarity index 62% rename from common/a7z_comp.py rename to common/comp_szip.py index 5293d68..542e5f4 100644 --- a/common/a7z_comp.py +++ b/common/comp_szip.py @@ -12,16 +12,16 @@ from common.path_ops import project_root, safe_path from common.system import get_os_ver from common.system import printer -# Get 7z path -def get_7z_path(static=False): - exec_name = '7z.exe' if get_os_ver()[1] else ('7zzs' if static else '7zz') +# Get 7-Zip path +def get_szip_path(): + exec_name = '7z.exe' if get_os_ver()[1] else '7zzs' return safe_path(project_root(), ['external',exec_name]) -# Check if file is 7z supported -def is_7z_supported(in_path, padding=0, static=False): +# Check if file is 7-Zip supported +def is_szip_supported(in_path, padding=0): try: - subprocess.run([get_7z_path(static), 't', in_path, '-bso0', '-bse0', '-bsp0'], check=True) + subprocess.run([get_szip_path(), 't', in_path, '-bso0', '-bse0', '-bsp0'], check=True) except: printer(f'Error: 7-Zip could not check support for file {in_path}!', padding) @@ -30,11 +30,11 @@ def is_7z_supported(in_path, padding=0, static=False): return True # Archive decompression via 7-Zip -def a7z_decompress(in_path, out_path, in_name, padding=0, static=False): +def szip_decompress(in_path, out_path, in_name, padding=0): if not in_name: in_name = 'archive' try: - subprocess.run([get_7z_path(static), 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + out_path, in_path], check=True) + subprocess.run([get_szip_path(), 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + out_path, in_path], check=True) if not os.path.isdir(out_path): raise Exception('EXTRACT_DIR_MISSING') except: diff --git a/common/patterns.py b/common/patterns.py index a94208a..b3ab80a 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -13,7 +13,11 @@ PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) +PAT_MICROSOFT_CAB_FF = re.compile(br'\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL) PAT_MICROSOFT_MZ = re.compile(br'MZ') PAT_MICROSOFT_PE = re.compile(br'PE\x00{2}') PAT_PHOENIX_TDK = re.compile(br'\$PACK\x00{3}..\x00{2}.\x00{3}', re.DOTALL) PAT_PORTWELL_EFI = re.compile(br'') +PAT_VAIO_CFG = re.compile(br'\[Setting]\x0D\x0A') +PAT_VAIO_CHK = re.compile(br'\x0AUseVAIOCheck=') +PAT_VAIO_EXT = re.compile(br'\x0AExtractPathByUser=') diff --git a/common/system.py b/common/system.py index 621755c..5dd4ff4 100644 --- a/common/system.py +++ b/common/system.py @@ -67,7 +67,6 @@ def argparse_init(): argparser.add_argument('-v', '--version', help='show utility name and version', action='store_true') argparser.add_argument('-o', '--output-dir', help='extract in given output directory') argparser.add_argument('-i', '--input-dir', help='extract from given input directory') - argparser.add_argument('--static', help='use static-built dependencies', action='store_true') return argparser From fddd33aafdaf17432c3ac1daf6505d9aa777b557 Mon Sep 17 00:00:00 2001 From: platomav Date: Thu, 2 Jun 2022 02:14:39 +0300 Subject: [PATCH 31/52] Added VAIO Packaging Manager Extractor v3.0_a4 Sort README utilities based on name --- README.md | 85 ++++++++++++++++---- VAIO_Package_Extract.py | 167 ++++++++++++++++++++++++++++++++++++++++ common/patterns.py | 2 +- 3 files changed, 237 insertions(+), 17 deletions(-) create mode 100644 VAIO_Package_Extract.py diff --git a/README.md b/README.md index 0f6aa71..14ea101 100644 --- a/README.md +++ b/README.md @@ -5,31 +5,32 @@ BIOS Utilities Donation via Paypal or Debit/Credit Card -* [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) -* [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) +* [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) +* [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) * [**Phoenix TDK Packer Extractor**](#phoenix-tdk-packer-extractor) * [**Portwell EFI Update Extractor**](#portwell-efi-update-extractor) +* [**VAIO Packaging Manager Extractor**](#vaio-packaging-manager-extractor) -## **Dell PFS Update Extractor** +## **AMI BIOS Guard Extractor** ![]() #### **Description** -Parses Dell PFS Update images and extracts their Firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and Utilities (e.g. Flasher etc) component sections. It supports all Dell PFS revisions and formats, including those which are originally LZMA compressed in ThinOS packages, ZLIB compressed or Intel BIOS Guard (PFAT) protected. The output comprises only final firmware components which are directly usable by end users. +Parses AMI BIOS Guard (a.k.a. PFAT, Platform Firmware Armoring Technology) images, extracts their SPI/BIOS/UEFI firmware components and decompiles the Intel BIOS Guard Scripts. It supports all AMI PFAT revisions and formats, including those with Index Information tables or nested AMI PFAT structures. The output comprises only final firmware components which are directly usable by end users. + +Note that the AMI PFAT structure may not have an explicit component order. AMI's BIOS Guard Firmware Update Tool (AFUBGT) updates components based on the user/OEM provided Parameters and Options or Index Information table, when applicable. That means that merging all the components together does not usually yield a proper SPI/BIOS/UEFI image. The utility does generate such a merged file with the name "00 -- \\_ALL.bin" but it is up to the end user to determine its usefulness. Moreover, any custom OEM data after the AMI PFAT structure are additionally stored in the last file with the name "\ -- \_OOB.bin" and it is once again up to the end user to determine its usefulness. In cases where the trailing custom OEM data include a nested AMI PFAT structure, the utility will process and extract it automatically as well. #### **Usage** -You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional arguments: +You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s). Optional arguments: * -h or --help : show help message and exit * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts -* -a or --advanced : extract signatures and metadata -* -s or --structure : show PFS structure information #### **Compatibility** @@ -37,7 +38,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 #### **Prerequisites** -Optionally, to decompile the Intel BIOS Guard (PFAT) Scripts, you must have the following 3rd party utility at the "external" project directory: +Optionally, to decompile the AMI PFAT \> Intel BIOS Guard Scripts, you must have the following 3rd party utility at the "external" project directory: * [BIOS Guard Script Tool](https://github.com/platomav/BGScriptTool) (i.e. big_script_tool.py) @@ -59,7 +60,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 4. Build/Freeze/Compile: -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Dell_PFS_Extract.py +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/AMI_PFAT_Extract.py You should find the final utility executable at "dist" folder @@ -137,25 +138,25 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() -## **AMI BIOS Guard Extractor** +## **Dell PFS Update Extractor** ![]() #### **Description** -Parses AMI BIOS Guard (a.k.a. PFAT, Platform Firmware Armoring Technology) images, extracts their SPI/BIOS/UEFI firmware components and decompiles the Intel BIOS Guard Scripts. It supports all AMI PFAT revisions and formats, including those with Index Information tables or nested AMI PFAT structures. The output comprises only final firmware components which are directly usable by end users. - -Note that the AMI PFAT structure may not have an explicit component order. AMI's BIOS Guard Firmware Update Tool (AFUBGT) updates components based on the user/OEM provided Parameters and Options or Index Information table, when applicable. That means that merging all the components together does not usually yield a proper SPI/BIOS/UEFI image. The utility does generate such a merged file with the name "00 -- \\_ALL.bin" but it is up to the end user to determine its usefulness. Moreover, any custom OEM data after the AMI PFAT structure are additionally stored in the last file with the name "\ -- \_OOB.bin" and it is once again up to the end user to determine its usefulness. In cases where the trailing custom OEM data include a nested AMI PFAT structure, the utility will process and extract it automatically as well. +Parses Dell PFS Update images and extracts their Firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and Utilities (e.g. Flasher etc) component sections. It supports all Dell PFS revisions and formats, including those which are originally LZMA compressed in ThinOS packages, ZLIB compressed or Intel BIOS Guard (PFAT) protected. The output comprises only final firmware components which are directly usable by end users. #### **Usage** -You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s). Optional arguments: +You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional arguments: * -h or --help : show help message and exit * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory * -e or --auto-exit : skip press enter to exit prompts +* -a or --advanced : extract signatures and metadata +* -s or --structure : show PFS structure information #### **Compatibility** @@ -163,7 +164,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 #### **Prerequisites** -Optionally, to decompile the AMI PFAT \> Intel BIOS Guard Scripts, you must have the following 3rd party utility at the "external" project directory: +Optionally, to decompile the Intel BIOS Guard (PFAT) Scripts, you must have the following 3rd party utility at the "external" project directory: * [BIOS Guard Script Tool](https://github.com/platomav/BGScriptTool) (i.e. big_script_tool.py) @@ -185,7 +186,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 4. Build/Freeze/Compile: -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/AMI_PFAT_Extract.py +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Dell_PFS_Extract.py You should find the final utility executable at "dist" folder @@ -323,5 +324,57 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() +## **VAIO Packaging Manager Extractor** + +![]() + +#### **Description** + +Parses VAIO Packaging Manager executables and extracts their firmware (e.g. SPI, BIOS/UEFI, EC, ME etc), utilities (e.g. WBFLASH etc) and driver (audio, video etc) components. If direct extraction fails, it attempts to unlock the executable in order to run at all non-VAIO systems and allow the user to choose the extraction location. It supports all VAIO Packaging Manager revisions and formats, including those which contain obfuscated Microsoft CAB archives or obfuscated unlock values. The output comprises only final firmware components which are directly usable by end users. + +#### **Usage** + +You can either Drag & Drop or manually enter VAIO Packaging Manager executable file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. + +#### **Prerequisites** + +To run the utility, you do not need any prerequisites. + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.8.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Build/Freeze/Compile: + +> pyinstaller --noupx --onefile \\/Phoenix_TDK_Extract.py + +At dist folder you should find the final utility executable + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + ###### _Donate Button Card Image: [Credit and Loan Pack](https://flaticon.com/free-icon/credit-card_3898076) by **Freepik** under Flaticon license_ ###### _Donate Button Paypal Image: [Credit Cards Pack](https://flaticon.com/free-icon/paypal_349278) by **Freepik** under Flaticon license_ \ No newline at end of file diff --git a/VAIO_Package_Extract.py b/VAIO_Package_Extract.py new file mode 100644 index 0000000..d788634 --- /dev/null +++ b/VAIO_Package_Extract.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +VAIO Package Extractor +VAIO Packaging Manager Extractor +Copyright (C) 2019-2022 Plato Mavropoulos +""" + +TITLE = 'VAIO Packaging Manager Extractor v3.0_a4' + +import os +import sys + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.comp_szip import is_szip_supported, szip_decompress +from common.path_ops import make_dirs +from common.patterns import PAT_VAIO_CFG, PAT_VAIO_CHK, PAT_VAIO_EXT, PAT_VAIO_CAB +from common.system import script_init, argparse_init, printer +from common.text_ops import file_to_bytes + +# Check if input is VAIO Packaging Manager +def is_vaio_pkg(in_file): + buffer = file_to_bytes(in_file) + + return bool(PAT_VAIO_CFG.search(buffer)) + +# Extract VAIO Packaging Manager executable +def vaio_cabinet(name, buffer, extract_path, padding=0): + match_cab = PAT_VAIO_CAB.search(buffer) # Microsoft CAB Header XOR 0xFF + + if not match_cab: return 1 + + printer('Detected obfuscated CAB archive!', padding) + + # Determine the Microsoft CAB image size + cab_size = int.from_bytes(buffer[match_cab.start() + 0x8:match_cab.start() + 0xC], 'little') # Get LE XOR-ed CAB size + xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB size XOR value + cab_size ^= xor_size # Perform XOR 0xFF and get actual CAB size + + printer('Removing obfuscation...', padding + 4) + + # Determine the Microsoft CAB image Data + cab_data = int.from_bytes(buffer[match_cab.start():match_cab.start() + cab_size], 'big') # Get BE XOR-ed CAB data + xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB data XOR value + cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB data + + printer('Extracting archive...', padding + 4) + + cab_path = os.path.join(extract_path, f'{name}_Temporary.cab') + + with open(cab_path, 'wb') as cab_file: cab_file.write(cab_data) # Create temporary CAB archive + + if is_szip_supported(cab_path, padding + 4): + if szip_decompress(cab_path, extract_path, 'CAB', padding + 8) == 0: + os.remove(cab_path) # Successful extraction, delete temporary CAB archive + else: + return 3 + else: + return 2 + + return 0 + +# Unlock VAIO Packaging Manager executable +def vaio_unlock(name, buffer, extract_path, padding=0): + match_cfg = PAT_VAIO_CFG.search(buffer) + + if not match_cfg: return 1 + + printer('Attempting to Unlock executable!', padding) + + # Initialize VAIO Package Configuration file variables (assume overkill size of 0x500) + cfg_bgn,cfg_end,cfg_false,cfg_true = [match_cfg.start(), match_cfg.start() + 0x500, b'', b''] + + # Get VAIO Package Configuration file info, split at new_line and stop at payload DOS header (EOF) + cfg_info = buffer[cfg_bgn:cfg_end].split(b'\x0D\x0A\x4D\x5A')[0].replace(b'\x0D',b'').split(b'\x0A') + + printer('Retrieving True/False values...', padding + 4) + + # Determine VAIO Package Configuration file True & False values + for info in cfg_info: + if info.startswith(b'ExtractPathByUser='): + cfg_false = bytearray(b'0' if info[18:] in (b'0',b'1') else info[18:]) # Should be 0/No/False + if info.startswith(b'UseCompression='): + cfg_true = bytearray(b'1' if info[15:] in (b'0',b'1') else info[15:]) # Should be 1/Yes/True + + # Check if valid True/False values have been retrieved + if cfg_false == cfg_true or not cfg_false or not cfg_true: + printer('Error: Could not retrieve True/False values!', padding + 8) + return 2 + + printer('Adjusting UseVAIOCheck entry...', padding + 4) + + # Find and replace UseVAIOCheck entry from 1/Yes/True to 0/No/False + vaio_check = PAT_VAIO_CHK.search(buffer[cfg_bgn:]) + if vaio_check: + buffer[cfg_bgn + vaio_check.end():cfg_bgn + vaio_check.end() + len(cfg_true)] = cfg_false + else: + printer('Error: Could not find entry UseVAIOCheck!', padding + 8) + return 3 + + printer('Adjusting ExtractPathByUser entry...', padding + 4) + + # Find and replace ExtractPathByUser entry from 0/No/False to 1/Yes/True + user_path = PAT_VAIO_EXT.search(buffer[cfg_bgn:]) + if user_path: + buffer[cfg_bgn + user_path.end():cfg_bgn + user_path.end() + len(cfg_false)] = cfg_true + else: + printer('Error: Could not find entry ExtractPathByUser!', padding + 8) + return 4 + + printer('Storing unlocked executable...', padding + 4) + + # Store Unlocked VAIO Packaging Manager executable + if vaio_check and user_path: + unlock_path = os.path.join(extract_path, f'{name}_Unlocked.exe') + with open(unlock_path, 'wb') as unl_file: unl_file.write(buffer) + + return 0 + +# Parse & Extract or Unlock VAIO Packaging Manager +def vaio_pkg_extract(name, buffer, output_path, padding=0): + extract_path = os.path.join(f'{output_path}_extracted') + + make_dirs(extract_path, delete=True) + + if vaio_cabinet(name, buffer, extract_path, padding) == 0: + printer('Successfully Extracted!', padding) + elif vaio_unlock(name, bytearray(buffer), extract_path, padding) == 0: + printer('Successfully Unlocked!', padding) + else: + printer('Error: Failed to Extract or Unlock executable!', padding) + return 1 + + return 0 + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + + # Check if VAIO Packaging Manager pattern was found on executable + if not is_vaio_pkg(input_buffer): + printer('Error: This is not a VAIO Packaging Manager executable!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + if vaio_pkg_extract(input_name, input_buffer, extract_path, padding) == 0: + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/common/patterns.py b/common/patterns.py index b3ab80a..2ec78c8 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -13,11 +13,11 @@ PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) -PAT_MICROSOFT_CAB_FF = re.compile(br'\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL) PAT_MICROSOFT_MZ = re.compile(br'MZ') PAT_MICROSOFT_PE = re.compile(br'PE\x00{2}') PAT_PHOENIX_TDK = re.compile(br'\$PACK\x00{3}..\x00{2}.\x00{3}', re.DOTALL) PAT_PORTWELL_EFI = re.compile(br'') +PAT_VAIO_CAB = re.compile(br'\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL) PAT_VAIO_CFG = re.compile(br'\[Setting]\x0D\x0A') PAT_VAIO_CHK = re.compile(br'\x0AUseVAIOCheck=') PAT_VAIO_EXT = re.compile(br'\x0AExtractPathByUser=') From f5905ec662e39f4bffe3f597e8d9271238555edd Mon Sep 17 00:00:00 2001 From: platomav Date: Thu, 16 Jun 2022 01:13:41 +0300 Subject: [PATCH 32/52] Fix main pattern detections, when found at offset 0x0 AMI UCP Update Extractor v2.0_a15 Phoenix TDK Packer Extractor v2.0_a7 Portwell EFI Update Extractor v2.0_a9 Fixes issue #13, thanks @PCRider for the report! --- AMI_UCP_Extract.py | 12 ++++++------ Phoenix_TDK_Extract.py | 6 +++--- Portwell_EFI_Extract.py | 4 ++-- README.md | 11 +++-------- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 41c38cf..496cbdd 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'AMI UCP Update Extractor v2.0_a14' +TITLE = 'AMI UCP Update Extractor v2.0_a15' import os import re @@ -163,13 +163,13 @@ def chk16_validate(data, tag, padd=0): def is_ami_ucp(in_file): buffer = file_to_bytes(in_file) - return bool(get_ami_ucp(buffer)[0]) + return bool(get_ami_ucp(buffer)[0] != None) # Get all input file AMI UCP patterns def get_ami_ucp(buffer): uaf_len_max = 0x0 # Length of largest detected @UAF|@HPU - uaf_hdr_off = 0x0 # Offset of largest detected @UAF|@HPU - uaf_buf_bin = b'' # Buffer of largest detected @UAF|@HPU + uaf_hdr_off = None # Offset of largest detected @UAF|@HPU + uaf_buf_bin = None # Buffer of largest detected @UAF|@HPU uaf_buf_tag = '@UAF' # Tag of largest detected @UAF|@HPU for uaf in PAT_AMI_UCP.finditer(buffer): @@ -419,7 +419,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na nested_uaf_off,nested_uaf_bin,nested_uaf_tag = get_ami_ucp(uaf_data_raw) # Parse Nested AMI UCP Structure - if nested_uaf_off: + if nested_uaf_off != None: uaf_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-UCP')) # Generate extraction directory ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum) # Call recursively @@ -512,7 +512,7 @@ if __name__ == '__main__': # Get best AMI UCP Pattern match based on @UAF|@HPU Size main_uaf_off,main_uaf_bin,main_uaf_tag = get_ami_ucp(input_buffer) - if not main_uaf_off: + if main_uaf_off == None: printer('Error: This is not an AMI UCP Update executable!', padding) continue # Next input file diff --git a/Phoenix_TDK_Extract.py b/Phoenix_TDK_Extract.py index 2d93793..d2edffd 100644 --- a/Phoenix_TDK_Extract.py +++ b/Phoenix_TDK_Extract.py @@ -7,7 +7,7 @@ Phoenix TDK Packer Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Phoenix TDK Packer Extractor v2.0_a6' +TITLE = 'Phoenix TDK Packer Extractor v2.0_a7' import os import sys @@ -149,7 +149,7 @@ def get_phoenix_tdk(in_buffer): def is_phoenix_tdk(in_file): buffer = file_to_bytes(in_file) - return bool(get_phoenix_tdk(buffer)[1]) + return bool(get_phoenix_tdk(buffer)[1] != None) # Parse & Extract Phoenix Tools Development Kit (TDK) Packer def phoenix_tdk_extract(input_buffer, output_path, pack_off, base_off=0, padding=0): @@ -253,7 +253,7 @@ if __name__ == '__main__': tdk_base_off,tdk_pack_off = get_phoenix_tdk(input_buffer) # Check if Phoenix TDK Packer pattern was found on executable - if not tdk_pack_off: + if tdk_pack_off == None: printer('Error: This is not a Phoenix TDK Packer executable!', padding) continue # Next input file diff --git a/Portwell_EFI_Extract.py b/Portwell_EFI_Extract.py index 4efac6f..fec6404 100644 --- a/Portwell_EFI_Extract.py +++ b/Portwell_EFI_Extract.py @@ -7,7 +7,7 @@ Portwell EFI Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Portwell EFI Update Extractor v2.0_a8' +TITLE = 'Portwell EFI Update Extractor v2.0_a9' import os import sys @@ -40,7 +40,7 @@ def is_portwell_efi(in_file): is_mz = PAT_MICROSOFT_MZ.search(in_buffer[:0x2]) # EFI images start with PE Header MZ is_uu = PAT_PORTWELL_EFI.search(pe_buffer[:0x4]) # Portwell EFI files start with - return is_mz and is_uu + return bool(is_mz and is_uu) # Get PE of Portwell EFI executable def get_portwell_pe(in_buffer): diff --git a/README.md b/README.md index 14ea101..a80e2f8 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ [BIOS Utilities News Feed](https://twitter.com/platomaniac) -BIOS Utilities Donation via Paypal or Debit/Credit Card - * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) * [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) * [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) @@ -100,7 +98,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 To run the utility, you must have the following 3rd party tools at the "external" project directory: * [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux) -* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zz|7zzs for Linux) +* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (when applicable), you must have the following 3rd party utility at the "external" project directory: @@ -364,7 +362,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 3. Build/Freeze/Compile: -> pyinstaller --noupx --onefile \\/Phoenix_TDK_Extract.py +> pyinstaller --noupx --onefile \\/VAIO_Package_Extract.py At dist folder you should find the final utility executable @@ -374,7 +372,4 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con #### **Pictures** -![]() - -###### _Donate Button Card Image: [Credit and Loan Pack](https://flaticon.com/free-icon/credit-card_3898076) by **Freepik** under Flaticon license_ -###### _Donate Button Paypal Image: [Credit Cards Pack](https://flaticon.com/free-icon/paypal_349278) by **Freepik** under Flaticon license_ \ No newline at end of file +![]() \ No newline at end of file From fc73921967c73706beac2efb7d71c46bbbba3c81 Mon Sep 17 00:00:00 2001 From: platomav Date: Tue, 21 Jun 2022 14:23:08 +0300 Subject: [PATCH 33/52] Added Panasonic BIOS Package Extractor v2.0_a7 New processes to better handle PE files Various common package fixes and improvements --- AMI_UCP_Extract.py | 8 +- Panasonic_BIOS_Extract.py | 237 ++++++++++++++++++++++++++++++++++++++ Phoenix_TDK_Extract.py | 26 ++--- Portwell_EFI_Extract.py | 16 +-- README.md | 68 +++++++++++ VAIO_Package_Extract.py | 4 +- common/comp_efi.py | 8 +- common/comp_szip.py | 13 ++- common/path_ops.py | 4 + common/patterns.py | 1 + common/pe_ops.py | 49 ++++++++ external/requirements.txt | 1 + 12 files changed, 400 insertions(+), 35 deletions(-) create mode 100644 Panasonic_BIOS_Extract.py create mode 100644 common/pe_ops.py diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 496cbdd..18b25db 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'AMI UCP Update Extractor v2.0_a15' +TITLE = 'AMI UCP Update Extractor v2.0_a16' import os import re @@ -163,7 +163,7 @@ def chk16_validate(data, tag, padd=0): def is_ami_ucp(in_file): buffer = file_to_bytes(in_file) - return bool(get_ami_ucp(buffer)[0] != None) + return bool(get_ami_ucp(buffer)[0] is not None) # Get all input file AMI UCP patterns def get_ami_ucp(buffer): @@ -419,7 +419,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na nested_uaf_off,nested_uaf_bin,nested_uaf_tag = get_ami_ucp(uaf_data_raw) # Parse Nested AMI UCP Structure - if nested_uaf_off != None: + if nested_uaf_off is not None: uaf_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-UCP')) # Generate extraction directory ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum) # Call recursively @@ -512,7 +512,7 @@ if __name__ == '__main__': # Get best AMI UCP Pattern match based on @UAF|@HPU Size main_uaf_off,main_uaf_bin,main_uaf_tag = get_ami_ucp(input_buffer) - if main_uaf_off == None: + if main_uaf_off is None: printer('Error: This is not an AMI UCP Update executable!', padding) continue # Next input file diff --git a/Panasonic_BIOS_Extract.py b/Panasonic_BIOS_Extract.py new file mode 100644 index 0000000..97290e9 --- /dev/null +++ b/Panasonic_BIOS_Extract.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Panasonic BIOS Extract +Panasonic BIOS Package Extractor +Copyright (C) 2018-2022 Plato Mavropoulos +""" + +TITLE = 'Panasonic BIOS Package Extractor v2.0_a7' + +import os +import io +import sys +import lznt1 +import pefile + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.comp_szip import is_szip_supported, szip_decompress +from common.path_ops import get_path_files, make_dirs, path_stem, safe_name +from common.pe_ops import get_pe_file, get_pe_info, is_pe_file, show_pe_info +from common.patterns import PAT_MICROSOFT_CAB +from common.system import script_init, argparse_init, printer +from common.text_ops import file_to_bytes + +from AMI_PFAT_Extract import get_ami_pfat, parse_pfat_file + +# Check if input is Panasonic BIOS Package PE +def is_panasonic_pkg(in_file): + in_buffer = file_to_bytes(in_file) + + pe_file = get_pe_file(in_buffer, fast=True) + + if not pe_file: + return False + + pe_info = get_pe_info(pe_file) + + if not pe_info: + return False + + if pe_info.get(b'FileDescription',b'').upper() != b'UNPACK UTILITY': + return False + + if not PAT_MICROSOFT_CAB.search(in_buffer): + return False + + return True + +# Search and Extract Panasonic BIOS Package PE CAB archive +def panasonic_cab_extract(buffer, extract_path, padding=0): + pe_path,pe_file,pe_info = [None] * 3 + + cab_bgn = PAT_MICROSOFT_CAB.search(buffer).start() + cab_len = int.from_bytes(buffer[cab_bgn + 0x8:cab_bgn + 0xC], 'little') + cab_end = cab_bgn + cab_len + cab_bin = buffer[cab_bgn:cab_end] + cab_tag = f'[0x{cab_bgn:06X}-0x{cab_end:06X}]' + + cab_path = os.path.join(extract_path, f'CAB_{cab_tag}.cab') + + with open(cab_path, 'wb') as cab_file: + cab_file.write(cab_bin) # Store CAB archive + + if is_szip_supported(cab_path, padding): + printer(f'Panasonic BIOS Package > PE > CAB {cab_tag}', padding) + + if szip_decompress(cab_path, extract_path, 'CAB', padding + 4) == 0: + os.remove(cab_path) # Successful extraction, delete CAB archive + else: + return pe_path, pe_file, pe_info + else: + return pe_path, pe_file, pe_info + + for file_path in get_path_files(extract_path): + pe_file = get_pe_file(file_path, fast=True) + if pe_file: + pe_info = get_pe_info(pe_file) + if pe_info.get(b'FileDescription',b'').upper() == b'BIOS UPDATE': + pe_path = file_path + break + else: + return pe_path, pe_file, pe_info + + return pe_path, pe_file, pe_info + +# Extract & Decompress Panasonic BIOS Update PE RCDATA (LZNT1) +def panasonic_res_extract(pe_name, pe_file, extract_path, padding=0): + is_rcdata = False + + # When fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed prior to RCDATA Directories + pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']]) + + # Parse all Resource Data Directories > RCDATA (ID = 10) + for entry in pe_file.DIRECTORY_ENTRY_RESOURCE.entries: + if entry.struct.name == 'IMAGE_RESOURCE_DIRECTORY_ENTRY' and entry.struct.Id == 0xA: + is_rcdata = True + for resource in entry.directory.entries: + res_bgn = resource.directory.entries[0].data.struct.OffsetToData + res_len = resource.directory.entries[0].data.struct.Size + res_end = res_bgn + res_len + res_bin = pe_file.get_data(res_bgn, res_len) + res_tag = f'{pe_name} [0x{res_bgn:06X}-0x{res_end:06X}]' + res_out = os.path.join(extract_path, f'{res_tag}') + + printer(res_tag, padding + 4) + + try: + res_raw = lznt1.decompress(res_bin[0x8:]) + + printer('Succesfull LZNT1 decompression via lznt1!', padding + 8) + except: + res_raw = res_bin + + printer('Succesfull PE Resource extraction!', padding + 8) + + # Detect & Unpack AMI BIOS Guard (PFAT) BIOS image + pfat_match,pfat_buffer = get_ami_pfat(res_raw) + + if pfat_match: + pfat_dir = os.path.join(extract_path, res_tag) + + parse_pfat_file(pfat_buffer, pfat_dir, padding + 12) + else: + if is_pe_file(res_raw): + res_ext = 'exe' + elif res_raw.startswith(b'[') and res_raw.endswith((b'\x0D\x0A',b'\x0A')): + res_ext = 'txt' + else: + res_ext = 'bin' + + if res_ext == 'txt': + printer(new_line=False) + for line in io.BytesIO(res_raw).readlines(): + line_text = line.decode('utf-8','ignore').rstrip() + printer(line_text, padding + 12, new_line=False) + + with open(f'{res_out}.{res_ext}', 'wb') as out_file: + out_file.write(res_raw) + + return is_rcdata + +# Extract Panasonic BIOS Update PE Data when RCDATA is not available +def panasonic_img_extract(pe_name, pe_path, pe_file, extract_path, padding=0): + pe_data = file_to_bytes(pe_path) + + sec_bgn = pe_file.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']].VirtualAddress + img_bgn = pe_file.OPTIONAL_HEADER.BaseOfData + pe_file.OPTIONAL_HEADER.SizeOfInitializedData + img_end = sec_bgn or len(pe_data) + img_bin = pe_data[img_bgn:img_end] + img_tag = f'{pe_name} [0x{img_bgn:X}-0x{img_end:X}]' + img_out = os.path.join(extract_path, f'{img_tag}.bin') + + printer(img_tag, padding + 4) + + with open(img_out, 'wb') as out_img: + out_img.write(img_bin) + + printer('Succesfull PE Data extraction!', padding + 8) + + return bool(img_bin) + +# Parse & Extract Panasonic BIOS Package PE +def panasonic_pkg_extract(input_path, input_buffer, output_path, padding=0): + is_upd_res,is_upd_img = [False] * 2 + + extract_path = os.path.join(f'{output_path}_extracted') + + make_dirs(extract_path, delete=True) + + pkg_pe_file = get_pe_file(input_buffer, fast=True) + + if not pkg_pe_file: + return 2 + + pkg_pe_info = get_pe_info(pkg_pe_file) + + if not pkg_pe_info: + return 3 + + pkg_pe_name = path_stem(input_path) + + printer(f'Panasonic BIOS Package > PE ({pkg_pe_name})\n', padding) + + show_pe_info(pkg_pe_info, padding + 4) + + upd_pe_path,upd_pe_file,upd_pe_info = panasonic_cab_extract(input_buffer, extract_path, padding + 4) + + if not (upd_pe_path and upd_pe_file and upd_pe_info): + return 4 + + upd_pe_name = safe_name(path_stem(upd_pe_path)) + + printer(f'Panasonic BIOS Update > PE ({upd_pe_name})\n', padding + 12) + + show_pe_info(upd_pe_info, padding + 16) + + is_upd_res = panasonic_res_extract(upd_pe_name, upd_pe_file, extract_path, padding + 16) + + if not is_upd_res: + is_upd_img = panasonic_img_extract(upd_pe_name, upd_pe_path, upd_pe_file, extract_path, padding + 16) + + os.remove(upd_pe_path) + + return 0 if is_upd_res or is_upd_img else 1 + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + + # Check if Panasonic BIOS Package pattern was found on executable + if not is_panasonic_pkg(input_buffer): + printer('Error: This is not a Panasonic BIOS Package executable!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + if panasonic_pkg_extract(input_file, input_buffer, extract_path, padding) == 0: + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/Phoenix_TDK_Extract.py b/Phoenix_TDK_Extract.py index d2edffd..b36bc91 100644 --- a/Phoenix_TDK_Extract.py +++ b/Phoenix_TDK_Extract.py @@ -7,18 +7,18 @@ Phoenix TDK Packer Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Phoenix TDK Packer Extractor v2.0_a7' +TITLE = 'Phoenix TDK Packer Extractor v2.0_a8' import os import sys import lzma -import pefile import ctypes # Stop __pycache__ generation sys.dont_write_bytecode = True from common.path_ops import safe_name, make_dirs +from common.pe_ops import get_pe_file, get_pe_info from common.patterns import PAT_PHOENIX_TDK, PAT_MICROSOFT_MZ, PAT_MICROSOFT_PE from common.struct_ops import get_struct, char, uint32_t from common.system import script_init, argparse_init, printer @@ -102,21 +102,21 @@ def get_tdk_base(in_buffer, pack_off): # Check if potential MZ > PE image magic value is valid if PAT_MICROSOFT_PE.search(in_buffer[pe_off:pe_off + 0x4]): try: - # Analyze detected MZ > PE image buffer quickly (fast_load) - pe_file = pefile.PE(data=in_buffer[mz_off:], fast_load=True) + # Parse detected MZ > PE > Image, quickly (fast_load) + pe_file = get_pe_file(in_buffer[mz_off:], fast=True) - # Since fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed for FileInfo > StringTable - pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']]) + # Parse detected MZ > PE > Info + pe_info = get_pe_info(pe_file) - # Attempt to retrieve the PE > "Product Name" version string value - pe_name = pe_file.FileInfo[0][0].StringTable[0].entries[b'ProductName'].upper() + # Parse detected MZ > PE > Info > Product Name + pe_name = pe_info.get(b'ProductName',b'') except: - # Any error means no PE > "Product Name" retrieved + # Any error means no MZ > PE > Info > Product Name pe_name = b'' - # Check for valid Phoenix TDK Packer PE > "Product Name" + # Check for valid Phoenix TDK Packer PE > Product Name # Expected value is "TDK Packer (Extractor for Windows)" - if pe_name.startswith(b'TDK PACKER'): + if pe_name.upper().startswith(b'TDK PACKER'): # Set TDK Base Offset to valid TDK Packer MZ offset tdk_base_off = mz_off @@ -149,7 +149,7 @@ def get_phoenix_tdk(in_buffer): def is_phoenix_tdk(in_file): buffer = file_to_bytes(in_file) - return bool(get_phoenix_tdk(buffer)[1] != None) + return bool(get_phoenix_tdk(buffer)[1] is not None) # Parse & Extract Phoenix Tools Development Kit (TDK) Packer def phoenix_tdk_extract(input_buffer, output_path, pack_off, base_off=0, padding=0): @@ -253,7 +253,7 @@ if __name__ == '__main__': tdk_base_off,tdk_pack_off = get_phoenix_tdk(input_buffer) # Check if Phoenix TDK Packer pattern was found on executable - if tdk_pack_off == None: + if tdk_pack_off is None: printer('Error: This is not a Phoenix TDK Packer executable!', padding) continue # Next input file diff --git a/Portwell_EFI_Extract.py b/Portwell_EFI_Extract.py index fec6404..1876f02 100644 --- a/Portwell_EFI_Extract.py +++ b/Portwell_EFI_Extract.py @@ -7,17 +7,17 @@ Portwell EFI Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Portwell EFI Update Extractor v2.0_a9' +TITLE = 'Portwell EFI Update Extractor v2.0_a10' import os import sys -import pefile # Stop __pycache__ generation sys.dont_write_bytecode = True from common.comp_efi import efi_decompress, is_efi_compressed from common.path_ops import safe_name, make_dirs +from common.pe_ops import get_pe_file from common.patterns import PAT_PORTWELL_EFI, PAT_MICROSOFT_MZ from common.system import script_init, argparse_init, printer from common.text_ops import file_to_bytes @@ -43,8 +43,8 @@ def is_portwell_efi(in_file): return bool(is_mz and is_uu) # Get PE of Portwell EFI executable -def get_portwell_pe(in_buffer): - pe_file = pefile.PE(data=in_buffer, fast_load=True) # Analyze EFI Portable Executable (PE) +def get_portwell_pe(in_buffer): + pe_file = get_pe_file(in_buffer, fast=True) # Analyze EFI Portable Executable (PE) pe_data = in_buffer[pe_file.OPTIONAL_HEADER.SizeOfImage:] # Skip EFI executable (pylint: disable=E1101) @@ -66,9 +66,9 @@ def portwell_efi_extract(input_buffer, output_path, padding=0): # Split EFI Payload into file chunks efi_list = list(PAT_PORTWELL_EFI.finditer(pe_data)) - for i,_ in enumerate(efi_list): - efi_bgn = efi_list[i].end() - efi_end = len(pe_data) if i == len(efi_list) - 1 else efi_list[i + 1].start() + for idx,val in enumerate(efi_list): + efi_bgn = val.end() + efi_end = len(pe_data) if idx == len(efi_list) - 1 else efi_list[idx + 1].start() efi_files.append(pe_data[efi_bgn:efi_end]) parse_efi_files(extract_path, efi_files, padding) @@ -108,7 +108,7 @@ def parse_efi_files(extract_path, efi_files, padding): file_name = FILE_NAMES.get(file_index, f'Unknown_{file_index}.bin') # Assign Name to EFI file - printer(file_name, padding + 4) # Print EFI file name, indicate progress + printer(f'[{file_index}] {file_name}', padding + 4) # Print EFI file name, indicate progress if file_name.startswith('Unknown_'): printer(f'Note: Detected new Portwell EFI file ID {file_index}!', padding + 8, pause=True) # Report new EFI files diff --git a/README.md b/README.md index a80e2f8..89bab1a 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) * [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) * [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) +* [**Panasonic BIOS Package Extractor**](#panasonic-bios-package-extractor) * [**Phoenix TDK Packer Extractor**](#phoenix-tdk-packer-extractor) * [**Portwell EFI Update Extractor**](#portwell-efi-update-extractor) * [**VAIO Packaging Manager Extractor**](#vaio-packaging-manager-extractor) @@ -196,6 +197,73 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() +## **Panasonic BIOS Package Extractor** + +![]() + +#### **Description** + +Parses Panasonic BIOS Package executables and extracts their firmware (e.g. SPI, BIOS/UEFI, EC etc) and utilities (e.g. winprom, configuration etc) components. It supports all Panasonic BIOS Package revisions and formats, including those which contain LZNT1 compressed files. The output comprises only final firmware components which are directly usable by end users. + +#### **Usage** + +You can either Drag & Drop or manually enter Panasonic BIOS Package executable file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. + +#### **Prerequisites** + +To run the utility, you must have the following 3rd party Python modules installed: + +* [pefile](https://pypi.org/project/pefile/) +* [lznt1](https://pypi.org/project/lznt1/) + +Moreover, you must have the following 3rd party tool at the "external" project directory: + +* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.8.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Use pip to install pefile and lznt1: + +> pip3 install pefile lznt1 + +4. Place prerequisite at the "external" project directory: + +> 7-Zip Console + +5. Build/Freeze/Compile: + +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Panasonic_BIOS_Extract.py + +At dist folder you should find the final utility executable + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + ## **Phoenix TDK Packer Extractor** ![]() diff --git a/VAIO_Package_Extract.py b/VAIO_Package_Extract.py index d788634..345d4b4 100644 --- a/VAIO_Package_Extract.py +++ b/VAIO_Package_Extract.py @@ -7,7 +7,7 @@ VAIO Packaging Manager Extractor Copyright (C) 2019-2022 Plato Mavropoulos """ -TITLE = 'VAIO Packaging Manager Extractor v3.0_a4' +TITLE = 'VAIO Packaging Manager Extractor v3.0_a5' import os import sys @@ -53,7 +53,7 @@ def vaio_cabinet(name, buffer, extract_path, padding=0): with open(cab_path, 'wb') as cab_file: cab_file.write(cab_data) # Create temporary CAB archive - if is_szip_supported(cab_path, padding + 4): + if is_szip_supported(cab_path, padding + 8): if szip_decompress(cab_path, extract_path, 'CAB', padding + 8) == 0: os.remove(cab_path) # Successful extraction, delete temporary CAB archive else: diff --git a/common/comp_efi.py b/common/comp_efi.py index 75ca465..d84ae75 100644 --- a/common/comp_efi.py +++ b/common/comp_efi.py @@ -35,7 +35,7 @@ def get_tiano_path(): return safe_path(project_root(), ['external',exec_name]) # EFI/Tiano Decompression via TianoCompress -def efi_decompress(in_path, out_path, padding=0, comp_type='--uefi'): +def efi_decompress(in_path, out_path, padding=0, silent=False, comp_type='--uefi'): try: subprocess.run([get_tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type], check=True, stdout=subprocess.DEVNULL) @@ -43,10 +43,12 @@ def efi_decompress(in_path, out_path, padding=0, comp_type='--uefi'): if os.path.getsize(out_path) != size_orig: raise Exception('EFI_DECOMPRESS_ERROR') except: - printer(f'Error: TianoCompress could not extract file {in_path}!', padding) + if not silent: + printer(f'Error: TianoCompress could not extract file {in_path}!', padding) return 1 - printer('Succesfull EFI decompression via TianoCompress!', padding) + if not silent: + printer('Succesfull EFI decompression via TianoCompress!', padding) return 0 diff --git a/common/comp_szip.py b/common/comp_szip.py index 542e5f4..c49b8b7 100644 --- a/common/comp_szip.py +++ b/common/comp_szip.py @@ -19,18 +19,19 @@ def get_szip_path(): return safe_path(project_root(), ['external',exec_name]) # Check if file is 7-Zip supported -def is_szip_supported(in_path, padding=0): +def is_szip_supported(in_path, padding=0, silent=False): try: subprocess.run([get_szip_path(), 't', in_path, '-bso0', '-bse0', '-bsp0'], check=True) except: - printer(f'Error: 7-Zip could not check support for file {in_path}!', padding) + if not silent: + printer(f'Error: 7-Zip could not check support for file {in_path}!', padding) return False return True # Archive decompression via 7-Zip -def szip_decompress(in_path, out_path, in_name, padding=0): +def szip_decompress(in_path, out_path, in_name, padding=0, silent=False): if not in_name: in_name = 'archive' try: @@ -38,10 +39,12 @@ def szip_decompress(in_path, out_path, in_name, padding=0): if not os.path.isdir(out_path): raise Exception('EXTRACT_DIR_MISSING') except: - printer(f'Error: 7-Zip could not extract {in_name} file {in_path}!', padding) + if not silent: + printer(f'Error: 7-Zip could not extract {in_name} file {in_path}!', padding) return 1 - printer(f'Succesfull {in_name} decompression via 7-Zip!', padding) + if not silent: + printer(f'Succesfull {in_name} decompression via 7-Zip!', padding) return 0 diff --git a/common/path_ops.py b/common/path_ops.py index 0890f1d..5cc4ebd 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -70,6 +70,10 @@ def agnostic_path(in_path): def path_parent(in_path): return Path(in_path).parent.absolute() +# Get final path component, w/o suffix +def path_stem(in_path): + return PurePath(in_path).stem + # Check if path is absolute def is_path_absolute(in_path): return Path(in_path).is_absolute() diff --git a/common/patterns.py b/common/patterns.py index 2ec78c8..df5e5be 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -13,6 +13,7 @@ PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) +PAT_MICROSOFT_CAB = re.compile(br'MSCF\x00{4}') PAT_MICROSOFT_MZ = re.compile(br'MZ') PAT_MICROSOFT_PE = re.compile(br'PE\x00{2}') PAT_PHOENIX_TDK = re.compile(br'\$PACK\x00{3}..\x00{2}.\x00{3}', re.DOTALL) diff --git a/common/pe_ops.py b/common/pe_ops.py new file mode 100644 index 0000000..572ac0e --- /dev/null +++ b/common/pe_ops.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Copyright (C) 2022 Plato Mavropoulos +""" + +import pefile + +from common.system import printer +from common.text_ops import file_to_bytes + +# Check if input is a PE file +def is_pe_file(in_file): + return bool(get_pe_file(in_file)) + +# Get pefile object from PE file +def get_pe_file(in_file, fast=True): + in_buffer = file_to_bytes(in_file) + + try: + # Analyze detected MZ > PE image buffer + pe_file = pefile.PE(data=in_buffer, fast_load=fast) + except: + pe_file = None + + return pe_file + +# Get PE info from pefile object +def get_pe_info(pe_file): + try: + # When fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed prior to FileInfo > StringTable + pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']]) + + # Retrieve MZ > PE > FileInfo > StringTable information + pe_info = pe_file.FileInfo[0][0].StringTable[0].entries + except: + pe_info = {} + + return pe_info + +# Print PE info from pefile StringTable +def show_pe_info(pe_info, padding=0): + if type(pe_info).__name__ == 'dict': + for title,value in pe_info.items(): + info_title = title.decode('utf-8','ignore').strip() + info_value = value.decode('utf-8','ignore').strip() + if info_title and info_value: + printer(f'{info_title}: {info_value}', padding, new_line=False) diff --git a/external/requirements.txt b/external/requirements.txt index 2cd8e04..06fc06e 100644 --- a/external/requirements.txt +++ b/external/requirements.txt @@ -1 +1,2 @@ +lznt1==0.2 pefile==2021.9.3 From 82cd4336bd9475969bcf33ec7db8590e44ea75e9 Mon Sep 17 00:00:00 2001 From: platomav Date: Sun, 26 Jun 2022 19:09:01 +0300 Subject: [PATCH 34/52] Added Award BIOS Module Extractor v2.0_a3 Improved 7-Zip exit code handling --- AMI_UCP_Extract.py | 6 +-- Award_BIOS_Extract.py | 102 ++++++++++++++++++++++++++++++++++++++ Panasonic_BIOS_Extract.py | 6 +-- README.md | 59 ++++++++++++++++++++++ VAIO_Package_Extract.py | 6 +-- common/comp_szip.py | 17 +++++-- common/patterns.py | 1 + 7 files changed, 184 insertions(+), 13 deletions(-) create mode 100644 Award_BIOS_Extract.py diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 18b25db..44b1f98 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'AMI UCP Update Extractor v2.0_a16' +TITLE = 'AMI UCP Update Extractor v2.0_a17' import os import re @@ -392,12 +392,12 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na nal_dict[info_tag] = (info_path,info_name) # Assign a file path & name to each Tag # Parse Insyde BIOS @UAF|@HPU Module (@INS) - if uaf_tag == '@INS' and is_szip_supported(uaf_fname, padding + 4): + if uaf_tag == '@INS' and is_szip_supported(uaf_fname, padding + 4, check=True): ins_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-SFX')) # Generate extraction directory printer('Insyde BIOS 7z SFX Archive:', padding + 4) - if szip_decompress(uaf_fname, ins_dir, '7z SFX', padding + 8) == 0: + if szip_decompress(uaf_fname, ins_dir, '7z SFX', padding + 8, check=True) == 0: os.remove(uaf_fname) # Successful extraction, delete @INS Module file/archive # Detect & Unpack AMI BIOS Guard (PFAT) BIOS image diff --git a/Award_BIOS_Extract.py b/Award_BIOS_Extract.py new file mode 100644 index 0000000..14189ff --- /dev/null +++ b/Award_BIOS_Extract.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Award BIOS Extract +Award BIOS Module Extractor +Copyright (C) 2018-2022 Plato Mavropoulos +""" + +TITLE = 'Award BIOS Module Extractor v2.0_a3' + +import os +import sys + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.comp_szip import szip_decompress +from common.path_ops import make_dirs, safe_name +from common.patterns import PAT_AWARD_LZH +from common.system import script_init, argparse_init, printer +from common.text_ops import file_to_bytes + +# Check if input is Award BIOS image +def is_award_bios(in_file): + in_buffer = file_to_bytes(in_file) + + return bool(PAT_AWARD_LZH.search(in_buffer)) + +# Parse & Extract Award BIOS image +def award_bios_extract(input_file, output_path, padding=0): + input_buffer = file_to_bytes(input_file) + + extract_path = os.path.join(f'{output_path}_extracted') + + make_dirs(extract_path, delete=True) + + for lzh_match in PAT_AWARD_LZH.finditer(input_buffer): + lzh_type = lzh_match.group(0).decode('utf-8') + lzh_text = f'LZH-{lzh_type.strip("-").upper()}' + + lzh_bgn = lzh_match.start() + + mod_bgn = lzh_bgn - 0x2 + hdr_len = input_buffer[mod_bgn] + mod_len = int.from_bytes(input_buffer[mod_bgn + 0x7:mod_bgn + 0xB], 'little') + mod_end = lzh_bgn + hdr_len + mod_len + mod_bin = input_buffer[mod_bgn:mod_end] + + tag_bgn = mod_bgn + 0x16 + tag_end = tag_bgn + input_buffer[mod_bgn + 0x15] + tag_txt = input_buffer[tag_bgn:tag_end].decode('utf-8','ignore') + + printer(f'{lzh_text} > {tag_txt} [0x{mod_bgn:06X}-0x{mod_end:06X}]', padding) + + mod_path = os.path.join(extract_path, safe_name(tag_txt)) + lzh_path = f'{mod_path}.lzh' + + with open(lzh_path, 'wb') as lzh_file: + lzh_file.write(mod_bin) # Store LZH archive + + # 7-Zip returns critical exit code (i.e. 2) if LZH CRC is wrong, do not check result + szip_decompress(lzh_path, extract_path, lzh_text, padding + 4, check=False) + + # Manually check if 7-Zip extracted LZH due to its CRC check issue + if os.path.isfile(mod_path): + os.remove(lzh_path) # Successful extraction, delete LZH archive + + # Extract any nested LZH archives + if is_award_bios(mod_path): + # Recursively extract nested Award BIOS modules + award_bios_extract(mod_path, mod_path, padding + 8) + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + + if not is_award_bios(input_buffer): + printer('Error: This is not an Award BIOS image!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + award_bios_extract(input_buffer, extract_path, padding) + + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/Panasonic_BIOS_Extract.py b/Panasonic_BIOS_Extract.py index 97290e9..3373f68 100644 --- a/Panasonic_BIOS_Extract.py +++ b/Panasonic_BIOS_Extract.py @@ -7,7 +7,7 @@ Panasonic BIOS Package Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Panasonic BIOS Package Extractor v2.0_a7' +TITLE = 'Panasonic BIOS Package Extractor v2.0_a8' import os import io @@ -64,10 +64,10 @@ def panasonic_cab_extract(buffer, extract_path, padding=0): with open(cab_path, 'wb') as cab_file: cab_file.write(cab_bin) # Store CAB archive - if is_szip_supported(cab_path, padding): + if is_szip_supported(cab_path, padding, check=True): printer(f'Panasonic BIOS Package > PE > CAB {cab_tag}', padding) - if szip_decompress(cab_path, extract_path, 'CAB', padding + 4) == 0: + if szip_decompress(cab_path, extract_path, 'CAB', padding + 4, check=True) == 0: os.remove(cab_path) # Successful extraction, delete CAB archive else: return pe_path, pe_file, pe_info diff --git a/README.md b/README.md index 89bab1a..88851a7 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) * [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) +* [**Award BIOS Module Extractor**](#award-bios-module-extractor) * [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) * [**Panasonic BIOS Package Extractor**](#panasonic-bios-package-extractor) * [**Phoenix TDK Packer Extractor**](#phoenix-tdk-packer-extractor) @@ -137,6 +138,64 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() +## **Award BIOS Module Extractor** + +![]() + +#### **Description** + +Parses Award BIOS images and extracts their modules (e.g. RAID, MEMINIT, \_EN_CODE, awardext etc). It supports all Award BIOS image revisions and formats, including those which contain LZH compressed files. The output comprises only final firmware components which are directly usable by end users. + +#### **Usage** + +You can either Drag & Drop or manually enter Award BIOS image file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. + +#### **Prerequisites** + +To run the utility, you must have the following 3rd party tool at the "external" project directory: + +* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.8.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Place prerequisite at the "external" project directory: + +> 7-Zip Console + +4. Build/Freeze/Compile: + +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Award_BIOS_Extract.py + +At dist folder you should find the final utility executable + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + ## **Dell PFS Update Extractor** ![]() diff --git a/VAIO_Package_Extract.py b/VAIO_Package_Extract.py index 345d4b4..108f4e3 100644 --- a/VAIO_Package_Extract.py +++ b/VAIO_Package_Extract.py @@ -7,7 +7,7 @@ VAIO Packaging Manager Extractor Copyright (C) 2019-2022 Plato Mavropoulos """ -TITLE = 'VAIO Packaging Manager Extractor v3.0_a5' +TITLE = 'VAIO Packaging Manager Extractor v3.0_a6' import os import sys @@ -53,8 +53,8 @@ def vaio_cabinet(name, buffer, extract_path, padding=0): with open(cab_path, 'wb') as cab_file: cab_file.write(cab_data) # Create temporary CAB archive - if is_szip_supported(cab_path, padding + 8): - if szip_decompress(cab_path, extract_path, 'CAB', padding + 8) == 0: + if is_szip_supported(cab_path, padding + 8, check=True): + if szip_decompress(cab_path, extract_path, 'CAB', padding + 8, check=True) == 0: os.remove(cab_path) # Successful extraction, delete temporary CAB archive else: return 3 diff --git a/common/comp_szip.py b/common/comp_szip.py index c49b8b7..e786622 100644 --- a/common/comp_szip.py +++ b/common/comp_szip.py @@ -18,10 +18,17 @@ def get_szip_path(): return safe_path(project_root(), ['external',exec_name]) +# Check 7-Zip bad exit codes (0 OK, 1 Warning) +def check_bad_exit_code(exit_code): + if exit_code not in (0,1): + raise Exception(f'BAD_EXIT_CODE_{exit_code}') + # Check if file is 7-Zip supported -def is_szip_supported(in_path, padding=0, silent=False): +def is_szip_supported(in_path, padding=0, check=False, silent=False): try: - subprocess.run([get_szip_path(), 't', in_path, '-bso0', '-bse0', '-bsp0'], check=True) + szip_t = subprocess.run([get_szip_path(), 't', in_path, '-bso0', '-bse0', '-bsp0'], check=False) + + if check: check_bad_exit_code(szip_t.returncode) except: if not silent: printer(f'Error: 7-Zip could not check support for file {in_path}!', padding) @@ -31,11 +38,13 @@ def is_szip_supported(in_path, padding=0, silent=False): return True # Archive decompression via 7-Zip -def szip_decompress(in_path, out_path, in_name, padding=0, silent=False): +def szip_decompress(in_path, out_path, in_name, padding=0, check=False, silent=False): if not in_name: in_name = 'archive' try: - subprocess.run([get_szip_path(), 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + out_path, in_path], check=True) + szip_x = subprocess.run([get_szip_path(), 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + out_path, in_path], check=False) + + if check: check_bad_exit_code(szip_x.returncode) if not os.path.isdir(out_path): raise Exception('EXTRACT_DIR_MISSING') except: diff --git a/common/patterns.py b/common/patterns.py index df5e5be..47846f2 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -9,6 +9,7 @@ import re PAT_AMI_PFAT = re.compile(br'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}@', re.DOTALL) +PAT_AWARD_LZH = re.compile(br'-lh[04567]-') PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) From be90f364d2cd72b7b40806f183e0e4de42f51e25 Mon Sep 17 00:00:00 2001 From: platomav Date: Wed, 29 Jun 2022 00:44:42 +0300 Subject: [PATCH 35/52] Added Fujitsu UPC BIOS Extractor v2.0_a2 --- Fujitsu_UPC_Extract.py | 72 ++++++++++++++++++++++++++++++++++++++++++ README.md | 59 ++++++++++++++++++++++++++++++++++ common/path_ops.py | 4 +++ 3 files changed, 135 insertions(+) create mode 100644 Fujitsu_UPC_Extract.py diff --git a/Fujitsu_UPC_Extract.py b/Fujitsu_UPC_Extract.py new file mode 100644 index 0000000..55929a8 --- /dev/null +++ b/Fujitsu_UPC_Extract.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Fujitsu UPC Extract +Fujitsu UPC BIOS Extractor +Copyright (C) 2021-2022 Plato Mavropoulos +""" + +TITLE = 'Fujitsu UPC BIOS Extractor v2.0_a2' + +import os +import sys + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.comp_efi import efi_decompress, is_efi_compressed +from common.path_ops import make_dirs, path_suffixes +from common.system import script_init, argparse_init, printer +from common.text_ops import file_to_bytes + +# Check if input is Fujitsu UPC image +def is_fujitsu_upc(in_file): + in_buffer = file_to_bytes(in_file) + + is_upc = path_suffixes(in_file)[-1].upper() == '.UPC' if os.path.isfile(in_file) else True + + is_efi = is_efi_compressed(in_buffer) + + return is_upc and is_efi + +# Parse & Extract Fujitsu UPC image +def fujitsu_upc_extract(input_file, output_path, padding=0): + extract_path = os.path.join(f'{output_path}_extracted') + + make_dirs(extract_path, delete=True) + + image_base = os.path.basename(input_file) + image_name = image_base[:-4] if image_base.upper().endswith('.UPC') else image_base + image_path = os.path.join(extract_path, f'{image_name}.bin') + + return efi_decompress(input_file, image_path, padding) + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + + if not is_fujitsu_upc(input_buffer): + printer('Error: This is not a Fujitsu UPC BIOS image!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + if fujitsu_upc_extract(input_file, extract_path, padding) == 0: + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/README.md b/README.md index 88851a7..27d926b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ * [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) * [**Award BIOS Module Extractor**](#award-bios-module-extractor) * [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) +* [**Fujitsu UPC BIOS Extractor**](#fujitsu-upc-bios-extractor) * [**Panasonic BIOS Package Extractor**](#panasonic-bios-package-extractor) * [**Phoenix TDK Packer Extractor**](#phoenix-tdk-packer-extractor) * [**Portwell EFI Update Extractor**](#portwell-efi-update-extractor) @@ -256,6 +257,64 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() +## **Fujitsu UPC BIOS Extractor** + +![]() + +#### **Description** + +Parses Fujitsu UPC images and extracts their EFI compressed SPI/BIOS/UEFI firmware component. The output comprises only a final firmware component which is directly usable by end users. + +#### **Usage** + +You can either Drag & Drop or manually enter Panasonic BIOS Package executable file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. + +#### **Prerequisites** + +To run the utility, you must have the following 3rd party tool at the "external" project directory: + +* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux) + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.8.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Place prerequisite at the "external" project directory: + +> TianoCompress + +4. Build/Freeze/Compile: + +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Fujitsu_UPC_Extract.py + +You should find the final utility executable at "dist" folder + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + ## **Panasonic BIOS Package Extractor** ![]() diff --git a/common/path_ops.py b/common/path_ops.py index 5cc4ebd..5e76eef 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -74,6 +74,10 @@ def path_parent(in_path): def path_stem(in_path): return PurePath(in_path).stem +# Get list of path file extensions +def path_suffixes(in_path): + return PurePath(in_path).suffixes + # Check if path is absolute def is_path_absolute(in_path): return Path(in_path).is_absolute() From cd2704f7435042374da5b0f1b357ced3e0b4cc4d Mon Sep 17 00:00:00 2001 From: platomav Date: Thu, 30 Jun 2022 01:20:21 +0300 Subject: [PATCH 36/52] Added Insyde iFlash Update Extractor v2.0_a2 Added Toshiba BIOS COM Extractor v2.0_a2 --- Fujitsu_UPC_Extract.py | 8 +-- Insyde_iFlash_Extract.py | 148 +++++++++++++++++++++++++++++++++++++++ README.md | 116 +++++++++++++++++++++++++++++- Toshiba_COM_Extract.py | 89 +++++++++++++++++++++++ common/comp_efi.py | 3 +- common/comp_szip.py | 3 +- common/patterns.py | 2 + 7 files changed, 359 insertions(+), 10 deletions(-) create mode 100644 Insyde_iFlash_Extract.py create mode 100644 Toshiba_COM_Extract.py diff --git a/Fujitsu_UPC_Extract.py b/Fujitsu_UPC_Extract.py index 55929a8..229e8e0 100644 --- a/Fujitsu_UPC_Extract.py +++ b/Fujitsu_UPC_Extract.py @@ -7,7 +7,7 @@ Fujitsu UPC BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Fujitsu UPC BIOS Extractor v2.0_a2' +TITLE = 'Fujitsu UPC BIOS Extractor v2.0_a3' import os import sys @@ -17,18 +17,18 @@ sys.dont_write_bytecode = True from common.comp_efi import efi_decompress, is_efi_compressed from common.path_ops import make_dirs, path_suffixes -from common.system import script_init, argparse_init, printer +from common.system import argparse_init, printer, script_init from common.text_ops import file_to_bytes # Check if input is Fujitsu UPC image def is_fujitsu_upc(in_file): in_buffer = file_to_bytes(in_file) - is_upc = path_suffixes(in_file)[-1].upper() == '.UPC' if os.path.isfile(in_file) else True + is_ext = path_suffixes(in_file)[-1].upper() == '.UPC' if os.path.isfile(in_file) else True is_efi = is_efi_compressed(in_buffer) - return is_upc and is_efi + return is_ext and is_efi # Parse & Extract Fujitsu UPC image def fujitsu_upc_extract(input_file, output_path, padding=0): diff --git a/Insyde_iFlash_Extract.py b/Insyde_iFlash_Extract.py new file mode 100644 index 0000000..7489e60 --- /dev/null +++ b/Insyde_iFlash_Extract.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Insyde iFlash Extract +Insyde iFlash Update Extractor +Copyright (C) 2022 Plato Mavropoulos +""" + +TITLE = 'Insyde iFlash Update Extractor v2.0_a2' + +import os +import sys +import ctypes + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.path_ops import make_dirs, safe_name +from common.patterns import PAT_INSYDE_IFL +from common.struct_ops import get_struct, char, uint32_t +from common.system import script_init, argparse_init, printer +from common.text_ops import file_to_bytes + +class IflashHeader(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Signature', char*9), # 0x00 $_IFLASH_ + ('ImageTag', char*7), # 0x08 + ('TotalSize', uint32_t), # 0x10 from header end + ('ImageSize', uint32_t), # 0x14 from header end + # 0x18 + ] + + def struct_print(self, p): + printer(['Signature :', self.Signature.decode('utf-8','ignore')], p, False) + printer(['Image Name:', self.ImageTag.decode('utf-8','ignore')], p, False) + printer(['Image Size:', f'0x{self.ImageSize:X}'], p, False) + printer(['Total Size:', f'0x{self.TotalSize:X}'], p, False) + +# Parse & Extract Insyde iFlash Update image +def insyde_iflash_extract(input_buffer, ins_ifl_all, output_path, padding=0): + extract_path = os.path.join(f'{output_path}_extracted') + + make_dirs(extract_path, delete=True) + + for ins_ifl_val in ins_ifl_all: + ins_ifl_off,ins_ifl_hdr = ins_ifl_val + + mod_bgn = ins_ifl_off + IFL_HDR_LEN + mod_end = mod_bgn + ins_ifl_hdr.ImageSize + mod_bin = input_buffer[mod_bgn:mod_end] + + mod_val = [ins_ifl_hdr.ImageTag.decode('utf-8','ignore'), 'bin'] + mod_tag,mod_ext = IFL_MOD_NAMES.get(mod_val[0], mod_val) + + mod_name = f'{mod_tag} [0x{mod_bgn:08X}-0x{mod_end:08X}]' + + printer(f'{mod_name}\n', padding) + + ins_ifl_hdr.struct_print(padding + 4) + + if mod_val == [mod_tag,mod_ext]: + printer(f'Note: Detected new Insyde iFlash image tag {mod_tag}!', padding + 8, pause=True) + + out_name = f'{mod_name}.{mod_ext}' + + out_path = os.path.join(extract_path, safe_name(out_name)) + + with open(out_path, 'wb') as out: out.write(mod_bin) + + printer('Succesfull Insyde iFlash image extraction!', padding + 8) + +# Get Insyde iFlash Update image matches +def get_insyde_iflash(in_file): + ins_ifl_all = [] + ins_ifl_nan = [0x0,0xFFFFFFFF] + + buffer = file_to_bytes(in_file) + + for ins_ifl_match in PAT_INSYDE_IFL.finditer(buffer): + ins_ifl_off = ins_ifl_match.start() + + if len(buffer[ins_ifl_off:]) <= IFL_HDR_LEN: + continue + + ins_ifl_hdr = get_struct(buffer, ins_ifl_off, IflashHeader) + + if ins_ifl_hdr.TotalSize in ins_ifl_nan \ + or ins_ifl_hdr.ImageSize in ins_ifl_nan \ + or ins_ifl_hdr.TotalSize <= ins_ifl_hdr.ImageSize: + continue + + ins_ifl_all.append([ins_ifl_off, ins_ifl_hdr]) + + return ins_ifl_all + +# Check if input is Insyde iFlash Update image +def is_insyde_iflash(in_file): + buffer = file_to_bytes(in_file) + + return bool(get_insyde_iflash(buffer)) + +IFL_MOD_NAMES = { + 'DRV_IMG' : ['isflash', 'efi'], + 'INI_IMG' : ['platform', 'ini'], + 'BIOSIMG' : ['BIOS-UEFI', 'bin'], + 'ME_IMG_' : ['Management Engine', 'bin'], + 'EC_IMG_' : ['Embedded Controller', 'bin'], + 'OEM_ID_' : ['OEM Identifier', 'bin'], + 'BIOSCER' : ['Certificate', 'bin'], + 'BIOSCR2' : ['Certificate 2nd', 'bin'], + } + +# Get common ctypes Structure Sizes +IFL_HDR_LEN = ctypes.sizeof(IflashHeader) + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + + ins_ifl_all = get_insyde_iflash(input_buffer) + + if not ins_ifl_all: + printer('Error: This is not an Insyde iFlash Update image!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + insyde_iflash_extract(input_buffer, ins_ifl_all, extract_path, padding) + + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/README.md b/README.md index 27d926b..17dbfef 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,11 @@ * [**Award BIOS Module Extractor**](#award-bios-module-extractor) * [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) * [**Fujitsu UPC BIOS Extractor**](#fujitsu-upc-bios-extractor) +* [**Insyde iFlash Update Extractor**](#insyde-iflash-update-extractor) * [**Panasonic BIOS Package Extractor**](#panasonic-bios-package-extractor) * [**Phoenix TDK Packer Extractor**](#phoenix-tdk-packer-extractor) * [**Portwell EFI Update Extractor**](#portwell-efi-update-extractor) +* [**Toshiba BIOS COM Extractor**](#toshiba-bios-com-extractor) * [**VAIO Packaging Manager Extractor**](#vaio-packaging-manager-extractor) ## **AMI BIOS Guard Extractor** @@ -263,11 +265,11 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con #### **Description** -Parses Fujitsu UPC images and extracts their EFI compressed SPI/BIOS/UEFI firmware component. The output comprises only a final firmware component which is directly usable by end users. +Parses Fujitsu UPC BIOS images and extracts their EFI compressed SPI/BIOS/UEFI firmware component. The output comprises only a final firmware component which is directly usable by end users. #### **Usage** -You can either Drag & Drop or manually enter Panasonic BIOS Package executable file(s). Optional arguments: +You can either Drag & Drop or manually enter Fujitsu UPC BIOS image file(s). Optional arguments: * -h or --help : show help message and exit * -v or --version : show utility name and version @@ -315,6 +317,58 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() +## **Insyde iFlash Update Extractor** + +![]() + +#### **Description** + +Parses Insyde iFlash Update images and extracts their firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and utilities (e.g. Flasher, Configuration etc) components. The output comprises only final firmware components which are directly usable by end users. + +#### **Usage** + +You can either Drag & Drop or manually enter Insyde iFlash Update image file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. + +#### **Prerequisites** + +To run the utility, you do not need any prerequisites. + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.8.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Build/Freeze/Compile: + +> pyinstaller --noupx --onefile \\/Insyde_iFlash_Extract.py + +At dist folder you should find the final utility executable + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + ## **Panasonic BIOS Package Extractor** ![]() @@ -508,6 +562,64 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() +## **Toshiba BIOS COM Extractor** + +![]() + +#### **Description** + +Parses Toshiba BIOS COM images and extracts their raw or compressed SPI/BIOS/UEFI firmware component. This utility is basically an easy to use python wrapper around [ToshibaComExtractor by LongSoft](https://github.com/LongSoft/ToshibaComExtractor). The output comprises only a final firmware component which is directly usable by end users. + +#### **Usage** + +You can either Drag & Drop or manually enter Toshiba BIOS COM image file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. + +#### **Prerequisites** + +To run the utility, you must have the following 3rd party tool at the "external" project directory: + +* [ToshibaComExtractor](https://github.com/LongSoft/ToshibaComExtractor) (e.g. comextract.exe for Windows or comextract for Linux) + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.8.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Place prerequisite at the "external" project directory: + +> ToshibaComExtractor + +4. Build/Freeze/Compile: + +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Toshiba_COM_Extract.py + +You should find the final utility executable at "dist" folder + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + ## **VAIO Packaging Manager Extractor** ![]() diff --git a/Toshiba_COM_Extract.py b/Toshiba_COM_Extract.py new file mode 100644 index 0000000..6af0770 --- /dev/null +++ b/Toshiba_COM_Extract.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Toshiba COM Extract +Toshiba BIOS COM Extractor +Copyright (C) 2018-2022 Plato Mavropoulos +""" + +TITLE = 'Toshiba BIOS COM Extractor v2.0_a2' + +import os +import sys +import subprocess + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.path_ops import make_dirs, path_stem, path_suffixes, project_root, safe_path +from common.patterns import PAT_TOSHIBA_COM +from common.system import argparse_init, get_os_ver, printer, script_init +from common.text_ops import file_to_bytes + +# Check if input is Toshiba BIOS COM image +def is_toshiba_com(in_file): + buffer = file_to_bytes(in_file) + + is_ext = path_suffixes(in_file)[-1].upper() == '.COM' if os.path.isfile(in_file) else True + + is_com = PAT_TOSHIBA_COM.search(buffer) + + return is_ext and is_com + +# Get ToshibaComExtractor path +def get_comextract_path(): + exec_name = 'comextract.exe' if get_os_ver()[1] else 'comextract' + + return safe_path(project_root(), ['external',exec_name]) + +# Parse & Extract Toshiba BIOS COM image +def toshiba_com_extract(input_file, output_path, padding=0): + extract_path = os.path.join(f'{output_path}_extracted') + + make_dirs(extract_path, delete=True) + + output_name = path_stem(input_file) + output_file = os.path.join(extract_path, f'{output_name}.bin') + + try: + subprocess.run([get_comextract_path(), input_file, output_file], check=True, stdout=subprocess.DEVNULL) + + if not os.path.isfile(output_file): raise Exception('EXTRACT_FILE_MISSING') + except: + printer(f'Error: ToshibaComExtractor could not extract file {input_file}!', padding) + + return 1 + + printer(f'Succesfull {output_name} extraction via ToshibaComExtractor!', padding) + + return 0 + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + + if not is_toshiba_com(input_file): + printer('Error: This is not a Toshiba BIOS COM image!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + if toshiba_com_extract(input_file, extract_path, padding) == 0: + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/common/comp_efi.py b/common/comp_efi.py index d84ae75..da2f214 100644 --- a/common/comp_efi.py +++ b/common/comp_efi.py @@ -9,8 +9,7 @@ import os import subprocess from common.path_ops import project_root, safe_path -from common.system import get_os_ver -from common.system import printer +from common.system import get_os_ver, printer def get_compress_sizes(data): size_compress = int.from_bytes(data[0x0:0x4], 'little') diff --git a/common/comp_szip.py b/common/comp_szip.py index e786622..4ac053a 100644 --- a/common/comp_szip.py +++ b/common/comp_szip.py @@ -9,8 +9,7 @@ import os import subprocess from common.path_ops import project_root, safe_path -from common.system import get_os_ver -from common.system import printer +from common.system import get_os_ver, printer # Get 7-Zip path def get_szip_path(): diff --git a/common/patterns.py b/common/patterns.py index 47846f2..797d467 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -13,12 +13,14 @@ PAT_AWARD_LZH = re.compile(br'-lh[04567]-') PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) +PAT_INSYDE_IFL = re.compile(br'\$_IFLASH_') PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) PAT_MICROSOFT_CAB = re.compile(br'MSCF\x00{4}') PAT_MICROSOFT_MZ = re.compile(br'MZ') PAT_MICROSOFT_PE = re.compile(br'PE\x00{2}') PAT_PHOENIX_TDK = re.compile(br'\$PACK\x00{3}..\x00{2}.\x00{3}', re.DOTALL) PAT_PORTWELL_EFI = re.compile(br'') +PAT_TOSHIBA_COM = re.compile(br'\x00{2}[\x00-\x02]BIOS.{20}[\x00\x01]', re.DOTALL) PAT_VAIO_CAB = re.compile(br'\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL) PAT_VAIO_CFG = re.compile(br'\[Setting]\x0D\x0A') PAT_VAIO_CHK = re.compile(br'\x0AUseVAIOCheck=') From 4749414f813d837d304025dae04b4007f129976b Mon Sep 17 00:00:00 2001 From: platomav Date: Wed, 6 Jul 2022 17:54:17 +0300 Subject: [PATCH 37/52] Insyde iFlash/iFdPacker Extractor v2.0_a8 Improved PFAT, UCP, PFS, TDK format check methods Cleanup/improvements to all utilities --- AMI_PFAT_Extract.py | 69 ++++++----- AMI_UCP_Extract.py | 129 ++++++++++--------- Award_BIOS_Extract.py | 7 +- Dell_PFS_Extract.py | 252 ++++++++++++++++++++++---------------- Fujitsu_UPC_Extract.py | 5 +- Insyde_IFD_Extract.py | 222 +++++++++++++++++++++++++++++++++ Insyde_iFlash_Extract.py | 148 ---------------------- Panasonic_BIOS_Extract.py | 15 ++- Phoenix_TDK_Extract.py | 28 +++-- Portwell_EFI_Extract.py | 24 ++-- README.md | 64 +++++----- Toshiba_COM_Extract.py | 8 +- VAIO_Package_Extract.py | 21 ++-- common/comp_efi.py | 12 +- common/comp_szip.py | 12 +- common/externals.py | 1 + common/path_ops.py | 6 +- common/patterns.py | 1 + common/struct_ops.py | 3 +- common/system.py | 9 +- external/requirements.txt | 2 +- 21 files changed, 603 insertions(+), 435 deletions(-) create mode 100644 Insyde_IFD_Extract.py delete mode 100644 Insyde_iFlash_Extract.py diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py index e6dd03c..bb9fdae 100644 --- a/AMI_PFAT_Extract.py +++ b/AMI_PFAT_Extract.py @@ -7,7 +7,7 @@ AMI BIOS Guard Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'AMI BIOS Guard Extractor v4.0_a10' +TITLE = 'AMI BIOS Guard Extractor v4.0_a11' import os import re @@ -19,10 +19,10 @@ sys.dont_write_bytecode = True from common.externals import get_bgs_tool from common.num_ops import get_ordinal -from common.path_ops import safe_name, make_dirs +from common.path_ops import make_dirs, safe_name from common.patterns import PAT_AMI_PFAT -from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t -from common.system import script_init, argparse_init, printer +from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t +from common.system import argparse_init, printer, script_init from common.text_ops import file_to_bytes class AmiBiosGuardHeader(ctypes.LittleEndianStructure): @@ -128,22 +128,22 @@ class IntelBiosGuardSignature2k(ctypes.LittleEndianStructure): printer(['Exponent :', f'0x{self.Exponent:X}'], p, False) printer(['Signature:', f'{Signature[:32]} [...]'], p, False) -def is_ami_pfat(in_file): - input_buffer = file_to_bytes(in_file) +def is_ami_pfat(input_file): + input_buffer = file_to_bytes(input_file) - return bool(get_ami_pfat(input_buffer)[0]) + return bool(get_ami_pfat(input_buffer)) -def get_ami_pfat(input_buffer): +def get_ami_pfat(input_file): + input_buffer = file_to_bytes(input_file) + match = PAT_AMI_PFAT.search(input_buffer) - buffer = input_buffer[match.start() - 0x8:] if match else b'' - - return match, buffer + return input_buffer[match.start() - 0x8:] if match else b'' def get_file_name(index, name): return safe_name(f'{index:02d} -- {name}') -def parse_bg_script(script_data, padding): +def parse_bg_script(script_data, padding=0): is_opcode_div = len(script_data) % 8 == 0 if not is_opcode_div: @@ -177,7 +177,7 @@ def parse_bg_script(script_data, padding): return 0 -def parse_pfat_hdr(buffer, padding): +def parse_pfat_hdr(buffer, padding=0): block_all = [] pfat_hdr = get_struct(buffer, 0x0, AmiBiosGuardHeader) @@ -220,7 +220,11 @@ def parse_pfat_hdr(buffer, padding): return block_all, hdr_size, files_count -def parse_pfat_file(buffer, output_path, padding): +def parse_pfat_file(input_file, output_path, padding=0): + input_buffer = file_to_bytes(input_file) + + pfat_buffer = get_ami_pfat(input_buffer) + file_path = '' all_blocks_dict = {} @@ -230,7 +234,7 @@ def parse_pfat_file(buffer, output_path, padding): make_dirs(extract_path, delete=True) - block_all,block_off,file_count = parse_pfat_hdr(buffer, padding) + block_all,block_off,file_count = parse_pfat_hdr(pfat_buffer, padding) for block in block_all: file_desc,file_name,_,_,_,file_index,block_index,block_count = block @@ -244,7 +248,7 @@ def parse_pfat_file(buffer, output_path, padding): block_status = f'{block_index + 1}/{block_count}' - bg_hdr = get_struct(buffer, block_off, IntelBiosGuardHeader) + bg_hdr = get_struct(pfat_buffer, block_off, IntelBiosGuardHeader) printer(f'Intel BIOS Guard {block_status} Header:\n', padding + 8) @@ -252,11 +256,11 @@ def parse_pfat_file(buffer, output_path, padding): bg_script_bgn = block_off + PFAT_BLK_HDR_LEN bg_script_end = bg_script_bgn + bg_hdr.ScriptSize - bg_script_bin = buffer[bg_script_bgn:bg_script_end] + bg_script_bin = pfat_buffer[bg_script_bgn:bg_script_end] bg_data_bgn = bg_script_end bg_data_end = bg_data_bgn + bg_hdr.DataSize - bg_data_bin = buffer[bg_data_bgn:bg_data_end] + bg_data_bin = pfat_buffer[bg_data_bgn:bg_data_end] block_off = bg_data_end # Assume next block starts at data end @@ -265,7 +269,7 @@ def parse_pfat_file(buffer, output_path, padding): if is_sfam: bg_sig_bgn = bg_data_end bg_sig_end = bg_sig_bgn + PFAT_BLK_S2K_LEN - bg_sig_bin = buffer[bg_sig_bgn:bg_sig_end] + bg_sig_bin = pfat_buffer[bg_sig_bgn:bg_sig_end] if len(bg_sig_bin) == PFAT_BLK_S2K_LEN: bg_sig = get_struct(bg_sig_bin, 0x0, IntelBiosGuardSignature2k) @@ -280,21 +284,22 @@ def parse_pfat_file(buffer, output_path, padding): _ = parse_bg_script(bg_script_bin, padding + 12) - with open(file_path, 'ab') as out_dat: out_dat.write(bg_data_bin) + with open(file_path, 'ab') as out_dat: + out_dat.write(bg_data_bin) all_blocks_dict[file_index] += bg_data_bin - pfat_oob_data = buffer[block_off:] # Store out-of-bounds data after the end of PFAT files + pfat_oob_data = pfat_buffer[block_off:] # Store out-of-bounds data after the end of PFAT files pfat_oob_name = get_file_name(file_count + 1, f'{extract_name}_OOB.bin') pfat_oob_path = os.path.join(extract_path, pfat_oob_name) - with open(pfat_oob_path, 'wb') as out_oob: out_oob.write(pfat_oob_data) + with open(pfat_oob_path, 'wb') as out_oob: + out_oob.write(pfat_oob_data) - oob_pfat_match,pfat_oob_buffer = get_ami_pfat(pfat_oob_data) - - if oob_pfat_match: parse_pfat_file(pfat_oob_buffer, pfat_oob_path, padding) + if is_ami_pfat(pfat_oob_data): + parse_pfat_file(pfat_oob_data, pfat_oob_path, padding) in_all_data = b''.join([block[1] for block in sorted(all_blocks_dict.items())]) @@ -302,7 +307,10 @@ def parse_pfat_file(buffer, output_path, padding): in_all_path = os.path.join(extract_path, in_all_name) - with open(in_all_path, 'wb') as out_all: out_all.write(in_all_data + pfat_oob_data) + with open(in_all_path, 'wb') as out_all: + out_all.write(in_all_data + pfat_oob_data) + + return 0 PFAT_AMI_HDR_LEN = ctypes.sizeof(AmiBiosGuardHeader) PFAT_BLK_HDR_LEN = ctypes.sizeof(IntelBiosGuardHeader) @@ -321,18 +329,17 @@ if __name__ == '__main__': printer(['***', input_name], padding - 4) - with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + with open(input_file, 'rb') as in_file: + input_buffer = in_file.read() - pfat_match,pfat_buffer = get_ami_pfat(input_buffer) - - if not pfat_match: + if not is_ami_pfat(input_buffer): printer('Error: This is not an AMI BIOS Guard (PFAT) image!', padding) continue # Next input file extract_path = os.path.join(output_path, input_name) - parse_pfat_file(pfat_buffer, extract_path, padding) + parse_pfat_file(input_buffer, extract_path, padding) exit_code -= 1 diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 44b1f98..0c9d03d 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'AMI UCP Update Extractor v2.0_a17' +TITLE = 'AMI UCP Update Extractor v2.0_a18' import os import re @@ -21,14 +21,14 @@ sys.dont_write_bytecode = True from common.checksums import get_chk_16 from common.comp_efi import efi_decompress, is_efi_compressed -from common.comp_szip import is_szip_supported, szip_decompress -from common.path_ops import agnostic_path, safe_name, safe_path, make_dirs +from common.path_ops import agnostic_path, make_dirs, safe_name, safe_path from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG -from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t -from common.system import script_init, argparse_init, printer +from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t +from common.system import argparse_init, printer, script_init from common.text_ops import file_to_bytes, to_string -from AMI_PFAT_Extract import get_ami_pfat, parse_pfat_file +from AMI_PFAT_Extract import is_ami_pfat, parse_pfat_file +from Insyde_IFD_Extract import insyde_ifd_extract, is_insyde_ifd class UafHeader(ctypes.LittleEndianStructure): _pack_ = 1 @@ -166,9 +166,10 @@ def is_ami_ucp(in_file): return bool(get_ami_ucp(buffer)[0] is not None) # Get all input file AMI UCP patterns -def get_ami_ucp(buffer): +def get_ami_ucp(in_file): + buffer = file_to_bytes(in_file) + uaf_len_max = 0x0 # Length of largest detected @UAF|@HPU - uaf_hdr_off = None # Offset of largest detected @UAF|@HPU uaf_buf_bin = None # Buffer of largest detected @UAF|@HPU uaf_buf_tag = '@UAF' # Tag of largest detected @UAF|@HPU @@ -181,7 +182,7 @@ def get_ami_ucp(buffer): uaf_buf_bin = buffer[uaf_hdr_off:uaf_hdr_off + uaf_len_max] uaf_buf_tag = uaf.group(0)[:4].decode('utf-8','ignore') - return uaf_hdr_off, uaf_buf_bin, uaf_buf_tag + return uaf_buf_bin, uaf_buf_tag # Get list of @UAF|@HPU Modules def get_uaf_mod(buffer, uaf_off=0x0): @@ -196,19 +197,23 @@ def get_uaf_mod(buffer, uaf_off=0x0): uaf_off += uaf_hdr.ModuleSize # Adjust to next @UAF|@HPU Module offset - if uaf_off >= len(buffer): break # Stop parsing at EOF + if uaf_off >= len(buffer): + break # Stop parsing at EOF # Check if @UAF|@HPU Module @NAL exists and place it first # Parsing @NAL first allows naming all @UAF|@HPU Modules for mod_idx,mod_val in enumerate(uaf_all): if mod_val[0] == '@NAL': uaf_all.insert(1, uaf_all.pop(mod_idx)) # After UII for visual purposes + break # @NAL found, skip the rest return uaf_all # Parse & Extract AMI UCP structures -def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False): +def ucp_extract(in_file, out_path, padding=0, checksum=False): + input_buffer = file_to_bytes(in_file) + nal_dict = {} # Initialize @NAL Dictionary per UCP printer('Utility Configuration Program', padding) @@ -217,13 +222,16 @@ def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False): make_dirs(extract_path, delete=True) - uaf_hdr = get_struct(buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure + # Get best AMI UCP Pattern match based on @UAF|@HPU Size + ucp_buffer,ucp_tag = get_ami_ucp(input_buffer) + + uaf_hdr = get_struct(ucp_buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure printer(f'Utility Auxiliary File > {ucp_tag}:\n', padding + 4) uaf_hdr.struct_print(padding + 8) - fake = struct.pack(' @UAF|@HPU Module/Section -def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, nal_dict=None): - if nal_dict is None: nal_dict = {} +def uaf_extract(buffer, extract_path, mod_info, padding=0, checksum=False, nal_dict=None): + if nal_dict is None: + nal_dict = {} uaf_tag,uaf_off,uaf_hdr = mod_info @@ -259,16 +269,26 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect @UAF|@HPU Module EFI Compression - if uaf_tag in nal_dict: uaf_name = nal_dict[uaf_tag][1] # Always prefer @NAL naming first - elif uaf_tag in UAF_TAG_DICT: uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming - elif uaf_tag == '@ROM': uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature) - elif uaf_tag.startswith('@R0'): uaf_name = f'BIOS_0{uaf_tag[3:]}.bin' # BIOS/PFAT Firmware - elif uaf_tag.startswith('@S0'): uaf_name = f'BIOS_0{uaf_tag[3:]}.sig' # BIOS/PFAT Signature - elif uaf_tag.startswith('@DR'): uaf_name = f'DROM_0{uaf_tag[3:]}.bin' # Thunderbolt Retimer Firmware - elif uaf_tag.startswith('@DS'): uaf_name = f'DROM_0{uaf_tag[3:]}.sig' # Thunderbolt Retimer Signature - elif uaf_tag.startswith('@EC'): uaf_name = f'EC_0{uaf_tag[3:]}.bin' # Embedded Controller Firmware - elif uaf_tag.startswith('@ME'): uaf_name = f'ME_0{uaf_tag[3:]}.bin' # Management Engine Firmware - else: uaf_name = uaf_tag # Could not name the @UAF|@HPU Module, use Tag instead + if uaf_tag in nal_dict: + uaf_name = nal_dict[uaf_tag][1] # Always prefer @NAL naming first + elif uaf_tag in UAF_TAG_DICT: + uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming + elif uaf_tag == '@ROM': + uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature) + elif uaf_tag.startswith('@R0'): + uaf_name = f'BIOS_0{uaf_tag[3:]}.bin' # BIOS/PFAT Firmware + elif uaf_tag.startswith('@S0'): + uaf_name = f'BIOS_0{uaf_tag[3:]}.sig' # BIOS/PFAT Signature + elif uaf_tag.startswith('@DR'): + uaf_name = f'DROM_0{uaf_tag[3:]}.bin' # Thunderbolt Retimer Firmware + elif uaf_tag.startswith('@DS'): + uaf_name = f'DROM_0{uaf_tag[3:]}.sig' # Thunderbolt Retimer Signature + elif uaf_tag.startswith('@EC'): + uaf_name = f'EC_0{uaf_tag[3:]}.bin' # Embedded Controller Firmware + elif uaf_tag.startswith('@ME'): + uaf_name = f'ME_0{uaf_tag[3:]}.bin' # Management Engine Firmware + else: + uaf_name = uaf_tag # Could not name the @UAF|@HPU Module, use Tag instead uaf_fext = '' if uaf_name != uaf_tag else '.bin' @@ -289,7 +309,8 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na else: uaf_fname = safe_path(extract_path, uaf_sname) - if is_checksum: chk16_validate(uaf_data_all, uaf_tag, padding + 4) + if checksum: + chk16_validate(uaf_data_all, uaf_tag, padding + 4) # Parse Utility Identification Information @UAF|@HPU Module (@UII) if uaf_tag == '@UII': @@ -304,7 +325,8 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na info_hdr.struct_print(padding + 8, info_desc) # Print @UII Module Info - if is_checksum: chk16_validate(uaf_data_raw, '@UII > Info', padding + 8) + if checksum: + chk16_validate(uaf_data_raw, '@UII > Info', padding + 8) # Store/Save @UII Module Info in file with open(uaf_fname[:-4] + '.txt', 'a', encoding='utf-8') as uii_out: @@ -324,14 +346,16 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na # Store/Save @UAF|@HPU Module file if uaf_tag != '@UII': # Skip @UII binary, already parsed - with open(uaf_fname, 'wb') as uaf_out: uaf_out.write(uaf_data_raw) + with open(uaf_fname, 'wb') as uaf_out: + uaf_out.write(uaf_data_raw) # @UAF|@HPU Module EFI/Tiano Decompression if is_comp and is_efi_compressed(uaf_data_raw, False): dec_fname = uaf_fname.replace('.temp', uaf_fext) # Decompressed @UAF|@HPU Module file path if efi_decompress(uaf_fname, dec_fname, padding + 4) == 0: - with open(dec_fname, 'rb') as dec: uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data + with open(dec_fname, 'rb') as dec: + uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data os.remove(uaf_fname) # Successful decompression, delete compressed @UAF|@HPU Module file @@ -392,39 +416,32 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na nal_dict[info_tag] = (info_path,info_name) # Assign a file path & name to each Tag # Parse Insyde BIOS @UAF|@HPU Module (@INS) - if uaf_tag == '@INS' and is_szip_supported(uaf_fname, padding + 4, check=True): - ins_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-SFX')) # Generate extraction directory + if uaf_tag == '@INS' and is_insyde_ifd(uaf_fname): + ins_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-IFD')) # Generate extraction directory - printer('Insyde BIOS 7z SFX Archive:', padding + 4) - - if szip_decompress(uaf_fname, ins_dir, '7z SFX', padding + 8, check=True) == 0: - os.remove(uaf_fname) # Successful extraction, delete @INS Module file/archive + if insyde_ifd_extract(uaf_fname, ins_dir, padding + 4) == 0: + os.remove(uaf_fname) # Delete raw nested Insyde IFD image after successful extraction # Detect & Unpack AMI BIOS Guard (PFAT) BIOS image - pfat_match,pfat_buffer = get_ami_pfat(uaf_data_raw) - - if pfat_match: + if is_ami_pfat(uaf_data_raw): pfat_dir = os.path.join(extract_path, safe_name(uaf_name)) - parse_pfat_file(pfat_buffer, pfat_dir, padding + 4) + parse_pfat_file(uaf_data_raw, pfat_dir, padding + 4) - os.remove(uaf_fname) # Delete PFAT Module file after extraction + os.remove(uaf_fname) # Delete raw PFAT BIOS image after successful extraction # Detect Intel Engine firmware image and show ME Analyzer advice if uaf_tag.startswith('@ME') and PAT_INTEL_ENG.search(uaf_data_raw): printer('Intel Management Engine (ME) Firmware:\n', padding + 4) printer('Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer', padding + 8, False) - # Get best Nested AMI UCP Pattern match based on @UAF|@HPU Size - nested_uaf_off,nested_uaf_bin,nested_uaf_tag = get_ami_ucp(uaf_data_raw) - - # Parse Nested AMI UCP Structure - if nested_uaf_off is not None: + # Parse Nested AMI UCP image + if is_ami_ucp(uaf_data_raw): uaf_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-UCP')) # Generate extraction directory - ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum) # Call recursively + ucp_extract(uaf_data_raw, uaf_dir, padding + 4, checksum) # Call recursively - os.remove(uaf_fname) # Delete raw nested AMI UCP Structure after successful recursion/extraction + os.remove(uaf_fname) # Delete raw nested AMI UCP image after successful extraction return nal_dict @@ -497,7 +514,7 @@ if __name__ == '__main__': argparser.add_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true') arguments = argparser.parse_args() - is_checksum = arguments.checksum # Set Checksum verification optional argument + checksum = arguments.checksum # Set Checksum verification optional argument # Initialize script (must be after argparse) exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) @@ -507,19 +524,17 @@ if __name__ == '__main__': printer(['***', input_name], padding - 4) - with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + with open(input_file, 'rb') as in_file: + input_buffer = in_file.read() - # Get best AMI UCP Pattern match based on @UAF|@HPU Size - main_uaf_off,main_uaf_bin,main_uaf_tag = get_ami_ucp(input_buffer) - - if main_uaf_off is None: + if not is_ami_ucp(input_buffer): printer('Error: This is not an AMI UCP Update executable!', padding) continue # Next input file extract_path = os.path.join(output_path, input_name) - ucp_extract(main_uaf_bin, extract_path, main_uaf_tag, padding, is_checksum) + ucp_extract(input_buffer, extract_path, padding, checksum) exit_code -= 1 diff --git a/Award_BIOS_Extract.py b/Award_BIOS_Extract.py index 14189ff..f06963f 100644 --- a/Award_BIOS_Extract.py +++ b/Award_BIOS_Extract.py @@ -7,7 +7,7 @@ Award BIOS Module Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Award BIOS Module Extractor v2.0_a3' +TITLE = 'Award BIOS Module Extractor v2.0_a4' import os import sys @@ -18,7 +18,7 @@ sys.dont_write_bytecode = True from common.comp_szip import szip_decompress from common.path_ops import make_dirs, safe_name from common.patterns import PAT_AWARD_LZH -from common.system import script_init, argparse_init, printer +from common.system import argparse_init, printer, script_init from common.text_ops import file_to_bytes # Check if input is Award BIOS image @@ -84,7 +84,8 @@ if __name__ == '__main__': printer(['***', input_name], padding - 4) - with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + with open(input_file, 'rb') as in_file: + input_buffer = in_file.read() if not is_award_bios(input_buffer): printer('Error: This is not an Award BIOS image!', padding) diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index d322423..18650a3 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -3,11 +3,11 @@ """ Dell PFS Extract -Dell PFS Update Extractor +Dell PFS/PKG Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Dell PFS Update Extractor v6.0_a10' +TITLE = 'Dell PFS/PKG Update Extractor v6.0_a11' import os import io @@ -21,10 +21,10 @@ import contextlib sys.dont_write_bytecode = True from common.checksums import get_chk_8_xor -from common.path_ops import safe_name, make_dirs -from common.patterns import PAT_DELL_HDR, PAT_DELL_FTR, PAT_DELL_PKG -from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t, uint64_t -from common.system import script_init, argparse_init, printer +from common.path_ops import make_dirs, path_stem, safe_name +from common.patterns import PAT_DELL_FTR, PAT_DELL_HDR, PAT_DELL_PKG +from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t, uint64_t +from common.system import argparse_init, printer, script_init from common.text_ops import file_to_bytes from AMI_PFAT_Extract import IntelBiosGuardHeader, IntelBiosGuardSignature2k, parse_bg_script @@ -190,7 +190,9 @@ class DellPfsPfatMetadata(ctypes.LittleEndianStructure): # Each section starts with a 0x30 header, which begins with pattern 72135500. # The section length is found at 0x10-0x14 and its (optional) MD5 hash at 0x20-0x30. # Section data can be raw or LZMA2 (7zXZ) compressed. The latter contains the PFS update image. -def is_pfs_pkg(in_buffer): +def is_pfs_pkg(in_file): + in_buffer = file_to_bytes(in_file) + return PAT_DELL_PKG.search(in_buffer) # The Dell PFS update images usually contain multiple sections. @@ -198,28 +200,57 @@ def is_pfs_pkg(in_buffer): # where ******** is the zlib stream size, ++ is the section type and -- the header Checksum XOR 8. # The "Firmware" section has type AA and its files are stored in PFS format. # The "Utility" section has type BB and its files are stored in PFS, BIN or 7z formats. -def is_pfs_hdr(in_buffer): - return list(PAT_DELL_HDR.finditer(in_buffer)) +def is_pfs_hdr(in_file): + in_buffer = file_to_bytes(in_file) + + return bool(PAT_DELL_HDR.search(in_buffer)) # Each section is followed by the footer pattern ********EEAAEE8F491BE8AE143790--, # where ******** is the zlib stream size and ++ the footer Checksum XOR 8. -def is_pfs_ftr(in_buffer): - return PAT_DELL_FTR.search(in_buffer) +def is_pfs_ftr(in_file): + in_buffer = file_to_bytes(in_file) + + return bool(PAT_DELL_FTR.search(in_buffer)) # Check if input is Dell PFS/PKG image def is_dell_pfs(in_file): in_buffer = file_to_bytes(in_file) - return bool(is_pfs_hdr(in_buffer) or is_pfs_pkg(in_buffer)) + is_pkg = is_pfs_pkg(in_buffer) + + is_hdr = is_pfs_hdr(in_buffer) + + is_ftr = is_pfs_ftr(in_buffer) + + return bool(is_pkg or is_hdr and is_ftr) + +# Extract Dell ThinOS PKG 7zXZ +def thinos_pkg_extract(in_file): + in_buffer = file_to_bytes(in_file) + + # Search input image for ThinOS PKG 7zXZ header + thinos_pkg_match = PAT_DELL_PKG.search(in_buffer) + + lzma_len_off = thinos_pkg_match.start() + 0x10 + lzma_len_int = int.from_bytes(in_buffer[lzma_len_off:lzma_len_off + 0x4], 'little') + lzma_bin_off = thinos_pkg_match.end() - 0x5 + lzma_bin_dat = in_buffer[lzma_bin_off:lzma_bin_off + lzma_len_int] + + # Check if the compressed 7zXZ stream is complete + if len(lzma_bin_dat) != lzma_len_int: + return in_buffer + + return lzma.decompress(lzma_bin_dat) # Get PFS ZLIB Section Offsets def get_section_offsets(buffer): - pfs_zlib_init = is_pfs_hdr(buffer) - - if not pfs_zlib_init: return [] # No PFS ZLIB detected - pfs_zlib_list = [] # Initialize PFS ZLIB offset list + pfs_zlib_init = list(PAT_DELL_HDR.finditer(buffer)) + + if not pfs_zlib_init: + return pfs_zlib_list # No PFS ZLIB detected + # Remove duplicate/nested PFS ZLIB offsets for zlib_c in pfs_zlib_init: is_duplicate = False # Initialize duplicate/nested PFS ZLIB offset @@ -228,14 +259,16 @@ def get_section_offsets(buffer): zlib_o_size = int.from_bytes(buffer[zlib_o.start() - 0x5:zlib_o.start() - 0x1], 'little') # If current PFS ZLIB offset is within another PFS ZLIB range (start-end), set as duplicate - if zlib_o.start() < zlib_c.start() < zlib_o.start() + zlib_o_size: is_duplicate = True + if zlib_o.start() < zlib_c.start() < zlib_o.start() + zlib_o_size: + is_duplicate = True - if not is_duplicate: pfs_zlib_list.append(zlib_c.start()) + if not is_duplicate: + pfs_zlib_list.append(zlib_c.start()) return pfs_zlib_list # Dell PFS ZLIB Section Parser -def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padding, is_structure=True, is_advanced=True): +def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padding, structure=True, advanced=True): is_zlib_error = False # Initialize PFS ZLIB-related error state section_type = zlib_data[zlib_start - 0x1] # Byte before PFS ZLIB Section pattern is Section Type (e.g. AA, BB) @@ -281,11 +314,8 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p # Store the PFS ZLIB section footer contents (16 bytes) footer_data = zlib_data[compressed_end:compressed_end + 0x10] - # Search input section for PFS ZLIB section footer - pfs_zlib_footer_match = is_pfs_ftr(footer_data) - # Check if PFS ZLIB section footer was found in the section - if not pfs_zlib_footer_match: + if not is_pfs_ftr(footer_data): printer('Error: This Dell PFS ZLIB section is corrupted!', padding) is_zlib_error = True @@ -304,18 +334,19 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p # Decompress PFS ZLIB section payload try: - if is_zlib_error: raise Exception('ZLIB_ERROR') # ZLIB errors are critical + if is_zlib_error: + raise Exception('ZLIB_ERROR') # ZLIB errors are critical section_data = zlib.decompress(compressed_data) # ZLIB decompression except: section_data = zlib_data # Fallback to raw ZLIB data upon critical error # Call the PFS Extract function on the decompressed PFS ZLIB Section - pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padding, is_structure, is_advanced) + pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padding, structure, advanced) # Parse & Extract Dell PFS Volume -def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, is_structure=True, is_advanced=True): +def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, structure=True, advanced=True): # Show PFS Volume indicator - if is_structure: + if structure: printer('PFS Volume:', pfs_padd) # Get PFS Header Structure values @@ -328,7 +359,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i return # Critical error, abort # Show PFS Header Structure info - if is_structure: + if structure: printer('PFS Header:\n', pfs_padd + 4) pfs_hdr.struct_print(pfs_padd + 8) @@ -348,7 +379,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i while len(pfs_payload[entry_start:entry_start + pfs_entry_size]) == pfs_entry_size: # Analyze PFS Entry Structure and get relevant info _,entry_version,entry_guid,entry_data,entry_data_sig,entry_met,entry_met_sig,next_entry = \ - parse_pfs_entry(pfs_payload, entry_start, pfs_entry_size, pfs_entry_struct, 'PFS Entry', pfs_padd, is_structure) + parse_pfs_entry(pfs_payload, entry_start, pfs_entry_size, pfs_entry_struct, 'PFS Entry', pfs_padd, structure) entry_type = 'OTHER' # Adjusted later if PFS Entry is Zlib, PFAT, PFS Info, Model Info @@ -384,7 +415,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i entry_info_hdr = get_struct(filename_info, info_start, DellPfsInfo) # Show PFS Information Header Structure info - if is_structure: + if structure: printer('PFS Information Header:\n', pfs_padd + 4) entry_info_hdr.struct_print(pfs_padd + 8) @@ -408,7 +439,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i entry_name = safe_name(name_data.decode('utf-16').strip()) # PFS Entry's FileName value # Show PFS FileName Structure info - if is_structure: + if structure: printer('PFS FileName Entry:\n', pfs_padd + 8) entry_info_mod.struct_print(pfs_padd + 12, entry_name) @@ -436,7 +467,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i entry_info = get_struct(entry_metadata, 0, DellPfsMetadata) # Show Nested PFS Metadata Structure info - if is_structure: + if structure: printer('PFS Metadata Information:\n', pfs_padd + 4) entry_info.struct_print(pfs_padd + 8) @@ -460,7 +491,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i entry_info_hdr = get_struct(signature_info, sign_start, DellPfsInfo) # Show PFS Information Header Structure info - if is_structure: + if structure: printer('PFS Information Header:\n', pfs_padd + 4) entry_info_hdr.struct_print(pfs_padd + 8) @@ -476,7 +507,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i entry_hdr = get_struct(signature_info, sign_start + PFS_INFO_LEN, pfs_entry_struct) # Show PFS Information Header Structure info - if is_structure: + if structure: printer('PFS Information Entry:\n', pfs_padd + 8) entry_hdr.struct_print(pfs_padd + 12) @@ -486,7 +517,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i sign_data_raw = signature_info[sign_info_start + 0x2:sign_info_start + 0x2 + sign_size] sign_data_txt = f'{int.from_bytes(sign_data_raw, "little"):0{sign_size * 2}X}' - if is_structure: + if structure: printer('Signature Information:\n', pfs_padd + 8) printer(f'Signature Size: 0x{sign_size:X}', pfs_padd + 12, False) printer(f'Signature Data: {sign_data_txt[:32]} [...]', pfs_padd + 12, False) @@ -500,7 +531,8 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i entry_type = entries_all[index][3] # Get PFS Entry Type # Very small PFS Entry Data cannot be of special type - if len(entry_data) < PFS_HEAD_LEN: continue + if len(entry_data) < PFS_HEAD_LEN: + continue # Check if PFS Entry contains zlib-compressed sub-PFS Volume pfs_zlib_offsets = get_section_offsets(entry_data) @@ -518,7 +550,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i if pfat_entry_hdr.Tag == b'PFS.HDR.' and is_pfat: entry_type = 'PFAT' # Re-set PFS Entry Type from OTHER to PFAT, to use such info afterwards - entry_data = parse_pfat_pfs(pfat_entry_hdr, entry_data, pfs_padd, is_structure) # Parse sub-PFS PFAT Volume + entry_data = parse_pfat_pfs(pfat_entry_hdr, entry_data, pfs_padd, structure) # Parse sub-PFS PFAT Volume # Parse PFS Entry which contains zlib-compressed sub-PFS Volume elif pfs_zlib_offsets: @@ -539,7 +571,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i sub_pfs_path = os.path.join(output_path, str(pfs_count) + safe_name(sub_pfs_name)) # Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count) - pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4, is_structure, is_advanced) + pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4, structure, advanced) entries_all[index][4] = entry_data # Adjust PFS Entry Data after parsing PFAT (same ZLIB raw data, not stored afterwards) entries_all[index][3] = entry_type # Adjust PFS Entry Type from OTHER to PFAT or ZLIB (ZLIB is ignored at file extraction) @@ -560,10 +592,12 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i file_name = 'Model Information' elif file_type == 'NAME_INFO': file_name = 'Filename Information' - if not is_advanced: continue # Don't store Filename Information in non-advanced user mode + if not advanced: + continue # Don't store Filename Information in non-advanced user mode elif file_type == 'SIG_INFO': file_name = 'Signature Information' - if not is_advanced: continue # Don't store Signature Information in non-advanced user mode + if not advanced: + continue # Don't store Signature Information in non-advanced user mode else: file_name = '' @@ -580,6 +614,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i file_version = info_version info_all[info_index][0] = 'USED' # PFS with zlib-compressed sub-PFS use the same GUID + break # Break at 1st Name match to not rename again from next zlib-compressed sub-PFS with the same GUID # For both advanced & non-advanced users, the goal is to store final/usable files only @@ -590,29 +625,36 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i is_zlib = bool(file_type == 'ZLIB') # Determine if PFS Entry Data was zlib-compressed - if file_data and not is_zlib: write_files.append([file_data, 'data']) # PFS Entry Data Payload - if file_data_sig and is_advanced: write_files.append([file_data_sig, 'sign_data']) # PFS Entry Data Signature - if file_meta and (is_zlib or is_advanced): write_files.append([file_meta, 'meta']) # PFS Entry Metadata Payload - if file_meta_sig and is_advanced: write_files.append([file_meta_sig, 'sign_meta']) # PFS Entry Metadata Signature + if file_data and not is_zlib: + write_files.append([file_data, 'data']) # PFS Entry Data Payload + + if file_data_sig and advanced: + write_files.append([file_data_sig, 'sign_data']) # PFS Entry Data Signature + + if file_meta and (is_zlib or advanced): + write_files.append([file_meta, 'meta']) # PFS Entry Metadata Payload + + if file_meta_sig and advanced: + write_files.append([file_meta_sig, 'sign_meta']) # PFS Entry Metadata Signature # Write/Extract PFS Entry files for file in write_files: full_name = f'{pfs_index}{pfs_name} -- {file_index} {file_name} v{file_version}' # Full PFS Entry Name - pfs_file_write(file[0], file[1], file_type, full_name, output_path, pfs_padd, is_structure, is_advanced) + pfs_file_write(file[0], file[1], file_type, full_name, output_path, pfs_padd, structure, advanced) # Get PFS Footer Data after PFS Header Payload pfs_footer = buffer[PFS_HEAD_LEN + pfs_hdr.PayloadSize:PFS_HEAD_LEN + pfs_hdr.PayloadSize + PFS_FOOT_LEN] # Analyze PFS Footer Structure - chk_pfs_ftr(pfs_footer, pfs_payload, pfs_hdr.PayloadSize, 'PFS', pfs_padd, is_structure) + chk_pfs_ftr(pfs_footer, pfs_payload, pfs_hdr.PayloadSize, 'PFS', pfs_padd, structure) # Analyze Dell PFS Entry Structure -def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, padding, is_structure=True): +def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, padding, structure=True): # Get PFS Entry Structure values pfs_entry = get_struct(entry_buffer, entry_start, entry_struct) # Show PFS Entry Structure info - if is_structure: + if structure: printer('PFS Entry:\n', padding + 4) pfs_entry.struct_print(padding + 8) @@ -653,13 +695,13 @@ def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, p return pfs_entry, entry_version, entry_guid, entry_data, entry_data_sig, entry_met, entry_met_sig, entry_met_sig_end # Parse Dell PFS Volume with PFAT Payload -def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True): +def parse_pfat_pfs(entry_hdr, entry_data, padding, structure=True): # Show PFS Volume indicator - if is_structure: + if structure: printer('PFS Volume:', padding + 4) # Show sub-PFS Header Structure Info - if is_structure: + if structure: printer('PFS Header:\n', padding + 8) entry_hdr.struct_print(padding + 12) @@ -679,11 +721,11 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True): _, pfs_entry_size = get_pfs_entry(pfat_payload, 0) # Get initial PFS PFAT Entry Size for loop while len(pfat_payload[pfat_entry_start:pfat_entry_start + pfs_entry_size]) == pfs_entry_size: # Get sub-PFS PFAT Entry Structure & Size info - pfat_entry_struct, pfat_entry_size = get_pfs_entry(pfat_payload, pfat_entry_start) + pfat_entry_struct,pfat_entry_size = get_pfs_entry(pfat_payload, pfat_entry_start) # Analyze sub-PFS PFAT Entry Structure and get relevant info pfat_entry,_,_,pfat_entry_data,_,pfat_entry_met,_,pfat_next_entry = parse_pfs_entry(pfat_payload, - pfat_entry_start, pfat_entry_size, pfat_entry_struct, 'sub-PFS PFAT Entry', padding + 4, is_structure) + pfat_entry_start, pfat_entry_size, pfat_entry_struct, 'sub-PFS PFAT Entry', padding + 4, structure) # Each sub-PFS PFAT Entry includes an AMI BIOS Guard (a.k.a. PFAT) block at the beginning # We need to parse the PFAT block and remove its contents from the final Payload/Raw Data @@ -693,7 +735,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True): pfat_hdr = get_struct(pfat_payload, pfat_hdr_off, IntelBiosGuardHeader) # Show sub-PFS PFAT Header Structure info - if is_structure: + if structure: printer(f'PFAT Block {pfat_entry_index} Header:\n', padding + 12) pfat_hdr.struct_print(padding + 16) @@ -718,15 +760,14 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True): pfat_sig = get_struct(pfat_payload, pfat_payload_end, IntelBiosGuardSignature2k) # Show sub-PFS PFAT Signature Structure info - if is_structure: + if structure: printer(f'PFAT Block {pfat_entry_index} Signature:\n', padding + 12) pfat_sig.struct_print(padding + 16) # Show PFAT Script via BIOS Guard Script Tool - if is_structure: + if structure: printer(f'PFAT Block {pfat_entry_index} Script:\n', padding + 12) - # https://github.com/allowitsme/big-tool by Dmitry Frolov _ = parse_bg_script(pfat_script_data, padding + 16) # The payload of sub-PFS PFAT Entries is not in proper order by default @@ -740,7 +781,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True): pfat_met = get_struct(pfat_entry_met, 0, DellPfsPfatMetadata) # Show sub-PFS PFAT Metadata Structure info - if is_structure: + if structure: printer(f'PFAT Block {pfat_entry_index} Metadata:\n', padding + 12) pfat_met.struct_print(padding + 16) @@ -772,14 +813,15 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True): pfat_data_all.sort() # Sort all sub-PFS PFAT Entries payloads/data based on their Order/Offset entry_data = b'' # Initialize new sub-PFS Entry Data - for pfat_data in pfat_data_all: entry_data += pfat_data[1] # Merge all sub-PFS PFAT Entry Payload/Raw into the final sub-PFS Entry Data + for pfat_data in pfat_data_all: + entry_data += pfat_data[1] # Merge all sub-PFS PFAT Entry Payload/Raw into the final sub-PFS Entry Data # Verify that the Order/Offset of the last PFAT Entry w/ its Size matches the final sub-PFS Entry Data Size if len(entry_data) != pfat_data_all[-1][0] + len(pfat_data_all[-1][1]): printer('Error: Detected sub-PFS PFAT Entry Buffer & Last Offset Size mismatch!', padding + 8) # Analyze sub-PFS Footer Structure - chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padding + 4, is_structure) + chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padding + 4, structure) return entry_data @@ -787,8 +829,11 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True): def get_pfs_entry(buffer, offset): pfs_entry_ver = int.from_bytes(buffer[offset + 0x10:offset + 0x14], 'little') # PFS Entry Version - if pfs_entry_ver == 1: return DellPfsEntryR1, ctypes.sizeof(DellPfsEntryR1) - if pfs_entry_ver == 2: return DellPfsEntryR2, ctypes.sizeof(DellPfsEntryR2) + if pfs_entry_ver == 1: + return DellPfsEntryR1, ctypes.sizeof(DellPfsEntryR1) + + if pfs_entry_ver == 2: + return DellPfsEntryR2, ctypes.sizeof(DellPfsEntryR2) return DellPfsEntryR2, ctypes.sizeof(DellPfsEntryR2) @@ -801,28 +846,35 @@ def get_entry_ver(version_fields, version_types): for index,field in enumerate(version_fields): eol = '' if index == len(version_fields) - 1 else '.' - if version_types[index] == 65: version += f'{field:X}{eol}' # 0x41 = ASCII - elif version_types[index] == 78: version += f'{field:d}{eol}' # 0x4E = Number - elif version_types[index] in (0, 32): version = version.strip('.') # 0x00 or 0x20 = Unused - else: version += f'{field:X}{eol}' # Unknown + if version_types[index] == 65: + version += f'{field:X}{eol}' # 0x41 = ASCII + elif version_types[index] == 78: + version += f'{field:d}{eol}' # 0x4E = Number + elif version_types[index] in (0, 32): + version = version.strip('.') # 0x00 or 0x20 = Unused + else: + version += f'{field:X}{eol}' # Unknown return version # Check if Dell PFS Header Version is known def chk_hdr_ver(version, text, padding): - if version in (1,2): return + if version in (1,2): + return printer(f'Error: Unknown {text} Header Version {version}!', padding) + + return # Analyze Dell PFS Footer Structure -def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_structure=True): +def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, structure=True): # Get PFS Footer Structure values pfs_ftr = get_struct(footer_buffer, 0, DellPfsFooter) # Validate that a PFS Footer was parsed if pfs_ftr.Tag == b'PFS.FTR.': # Show PFS Footer Structure info - if is_structure: + if structure: printer('PFS Footer:\n', padding + 4) pfs_ftr.struct_print(padding + 8) else: @@ -840,29 +892,31 @@ def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_structu printer(f'Error: Invalid {text} Footer Payload Checksum!', padding + 4) # Write/Extract Dell PFS Entry Files (Data, Metadata, Signature) -def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, is_structure=True, is_advanced=True): +def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, structure=True, advanced=True): # Store Data/Metadata Signature (advanced users only) if bin_name.startswith('sign'): final_name = f'{safe_name(full_name)}.{bin_name.split("_")[1]}.sig' final_path = os.path.join(out_path, final_name) - with open(final_path, 'wb') as pfs_out: pfs_out.write(bin_buff) # Write final Data/Metadata Signature + with open(final_path, 'wb') as pfs_out: + pfs_out.write(bin_buff) # Write final Data/Metadata Signature return # Skip further processing for Signatures # Store Data/Metadata Payload - bin_ext = f'.{bin_name}.bin' if is_advanced else '.bin' # Simpler Data/Metadata Extension for non-advanced users + bin_ext = f'.{bin_name}.bin' if advanced else '.bin' # Simpler Data/Metadata Extension for non-advanced users # Some Data may be Text or XML files with useful information for non-advanced users - is_text,final_data,file_ext,write_mode = bin_is_text(bin_buff, bin_type, bin_name == 'meta', padding, is_structure, is_advanced) + is_text,final_data,file_ext,write_mode = bin_is_text(bin_buff, bin_type, bin_name == 'meta', padding, structure, advanced) final_name = f'{safe_name(full_name)}{bin_ext[:-4] + file_ext if is_text else bin_ext}' final_path = os.path.join(out_path, final_name) - with open(final_path, write_mode) as pfs_out: pfs_out.write(final_data) # Write final Data/Metadata Payload + with open(final_path, write_mode) as pfs_out: + pfs_out.write(final_data) # Write final Data/Metadata Payload # Check if Dell PFS Entry file/data is Text/XML and Convert -def bin_is_text(buffer, file_type, is_metadata, pfs_padd, is_structure=True, is_advanced=True): +def bin_is_text(buffer, file_type, is_metadata, pfs_padd, structure=True, advanced=True): is_text = False write_mode = 'wb' extension = '.bin' @@ -892,12 +946,13 @@ def bin_is_text(buffer, file_type, is_metadata, pfs_padd, is_structure=True, is_ buffer = text_buffer.getvalue() # Show Model/PCR XML Information, if applicable - if is_structure and is_text and not is_metadata: # Metadata is shown at initial DellPfsMetadata analysis + if structure and is_text and not is_metadata: # Metadata is shown at initial DellPfsMetadata analysis printer(f'PFS { {".txt": "Model", ".xml": "PCR XML"}[extension] } Information:\n', pfs_padd + 8) _ = [printer(line.strip('\r'), pfs_padd + 12, False) for line in buffer.split('\n') if line] # Only for non-advanced users due to signature (.sig) invalidation - if is_advanced: return False, buffer_in, '.bin', 'wb' + if advanced: + return False, buffer_in, '.bin', 'wb' return is_text, buffer, extension, write_mode @@ -918,8 +973,8 @@ if __name__ == '__main__': argparser.add_argument('-s', '--structure', help='show PFS structure information', action='store_true') arguments = argparser.parse_args() - is_advanced = arguments.advanced # Set Advanced user mode optional argument - is_structure = arguments.structure # Set Structure output mode optional argument + advanced = arguments.advanced # Set Advanced user mode optional argument + structure = arguments.structure # Set Structure output mode optional argument # Initialize script (must be after argparse) exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) @@ -929,42 +984,25 @@ if __name__ == '__main__': printer(['***', input_name], padding - 4) - with open(input_file, 'rb') as in_file: input_buffer = in_file.read() - - # Search input image for ThinOS PKG 7zXZ section header - lzma_pkg_hdr_match = is_pfs_pkg(input_buffer) - - # Decompress ThinOS PKG 7zXZ section first, if present - if lzma_pkg_hdr_match: - lzma_len_off = lzma_pkg_hdr_match.start() + 0x10 - lzma_len_int = int.from_bytes(input_buffer[lzma_len_off:lzma_len_off + 0x4], 'little') - lzma_bin_off = lzma_pkg_hdr_match.end() - 0x5 - lzma_bin_dat = input_buffer[lzma_bin_off:lzma_bin_off + lzma_len_int] - - # Check if the compressed 7zXZ stream is complete, based on header - if len(lzma_bin_dat) != lzma_len_int: - printer('Error: This Dell ThinOS PKG update image is corrupted!', padding) - - continue # Next input file - - input_buffer = lzma.decompress(lzma_bin_dat) - - # Search input image for PFS ZLIB Sections - pfs_zlib_offsets = get_section_offsets(input_buffer) - - if not pfs_zlib_offsets: - printer('Error: This is not a Dell PFS update image!', padding) + with open(input_file, 'rb') as in_file: + input_buffer = in_file.read() + + if not is_dell_pfs(input_buffer): + printer('Error: This is not a Dell PFS/PKG Update image!', padding) continue # Next input file extract_path = os.path.join(output_path, f'{input_name}_extracted') - extract_name = ' ' + os.path.splitext(input_name)[0] + extract_name = ' ' + path_stem(input_file) + + if is_pfs_pkg(input_buffer): + input_buffer = thinos_pkg_extract(input_buffer) # Parse each PFS ZLIB Section - for offset in pfs_zlib_offsets: + for zlib_offset in get_section_offsets(input_buffer): # Call the PFS ZLIB Section Parser function - pfs_section_parse(input_buffer, offset, extract_path, extract_name, 1, 1, False, padding, is_structure, is_advanced) + pfs_section_parse(input_buffer, zlib_offset, extract_path, extract_name, 1, 1, False, padding, structure, advanced) exit_code -= 1 diff --git a/Fujitsu_UPC_Extract.py b/Fujitsu_UPC_Extract.py index 229e8e0..24a7ebd 100644 --- a/Fujitsu_UPC_Extract.py +++ b/Fujitsu_UPC_Extract.py @@ -7,7 +7,7 @@ Fujitsu UPC BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Fujitsu UPC BIOS Extractor v2.0_a3' +TITLE = 'Fujitsu UPC BIOS Extractor v2.0_a4' import os import sys @@ -55,7 +55,8 @@ if __name__ == '__main__': printer(['***', input_name], padding - 4) - with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + with open(input_file, 'rb') as in_file: + input_buffer = in_file.read() if not is_fujitsu_upc(input_buffer): printer('Error: This is not a Fujitsu UPC BIOS image!', padding) diff --git a/Insyde_IFD_Extract.py b/Insyde_IFD_Extract.py new file mode 100644 index 0000000..0189ed9 --- /dev/null +++ b/Insyde_IFD_Extract.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Insyde IFD Extract +Insyde iFlash/iFdPacker Extractor +Copyright (C) 2022 Plato Mavropoulos +""" + +TITLE = 'Insyde iFlash/iFdPacker Extractor v2.0_a8' + +import os +import sys +import ctypes + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.comp_szip import is_szip_supported, szip_decompress +from common.path_ops import get_path_files, make_dirs, safe_name +from common.patterns import PAT_INSYDE_IFL, PAT_INSYDE_SFX +from common.struct_ops import char, get_struct, uint32_t +from common.system import argparse_init, printer, script_init +from common.text_ops import file_to_bytes + +class IflashHeader(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Signature', char*8), # 0x00 $_IFLASH + ('ImageTag', char*8), # 0x08 + ('TotalSize', uint32_t), # 0x10 from header end + ('ImageSize', uint32_t), # 0x14 from header end + # 0x18 + ] + + def get_image_tag(self): + return self.ImageTag.decode('utf-8','ignore').strip('_') + + def struct_print(self, p): + printer(['Signature :', self.Signature.decode('utf-8')], p, False) + printer(['Image Name:', self.get_image_tag()], p, False) + printer(['Image Size:', f'0x{self.ImageSize:X}'], p, False) + printer(['Total Size:', f'0x{self.TotalSize:X}'], p, False) + +# Check if input is Insyde iFlash/iFdPacker Update image +def is_insyde_ifd(input_file): + input_buffer = file_to_bytes(input_file) + + is_ifl = bool(insyde_iflash_detect(input_buffer)) + + is_sfx = bool(PAT_INSYDE_SFX.search(input_buffer)) + + return is_ifl or is_sfx + +# Parse & Extract Insyde iFlash/iFdPacker Update images +def insyde_ifd_extract(input_file, output_path, padding=0): + input_buffer = file_to_bytes(input_file) + + extract_path = os.path.join(f'{output_path}_extracted') + + iflash_code = insyde_iflash_extract(input_buffer, extract_path, padding) + + ifdpack_path = os.path.join(extract_path, 'Insyde iFdPacker SFX') + + ifdpack_code = insyde_packer_extract(input_buffer, ifdpack_path, padding) + + return iflash_code and ifdpack_code + +# Detect Insyde iFlash Update image +def insyde_iflash_detect(input_buffer): + iflash_match_all = [] + iflash_match_nan = [0x0,0xFFFFFFFF] + + for iflash_match in PAT_INSYDE_IFL.finditer(input_buffer): + ifl_bgn = iflash_match.start() + + if len(input_buffer[ifl_bgn:]) <= IFL_HDR_LEN: + continue + + ifl_hdr = get_struct(input_buffer, ifl_bgn, IflashHeader) + + if ifl_hdr.TotalSize in iflash_match_nan \ + or ifl_hdr.ImageSize in iflash_match_nan \ + or ifl_hdr.TotalSize <= ifl_hdr.ImageSize: + continue + + iflash_match_all.append([ifl_bgn, ifl_hdr]) + + return iflash_match_all + +# Extract Insyde iFlash Update image +def insyde_iflash_extract(input_buffer, extract_path, padding=0): + insyde_iflash_all = insyde_iflash_detect(input_buffer) + + if not insyde_iflash_all: + return 1 + + printer('Detected Insyde iFlash Update image!', padding) + + make_dirs(extract_path, delete=True) + + for insyde_iflash in insyde_iflash_all: + ifl_bgn,ifl_hdr = insyde_iflash + + img_bgn = ifl_bgn + IFL_HDR_LEN + img_end = img_bgn + ifl_hdr.ImageSize + img_bin = input_buffer[img_bgn:img_end] + + img_val = [ifl_hdr.get_image_tag(), 'bin'] + img_tag,img_ext = IFL_IMG_NAMES.get(img_val[0], img_val) + + img_name = f'{img_tag} [0x{img_bgn:08X}-0x{img_end:08X}]' + + printer(f'{img_name}\n', padding + 4) + + ifl_hdr.struct_print(padding + 8) + + if img_val == [img_tag,img_ext]: + printer(f'Note: Detected new Insyde iFlash tag {img_tag}!', padding + 12, pause=True) + + out_name = f'{img_name}.{img_ext}' + + out_path = os.path.join(extract_path, safe_name(out_name)) + + with open(out_path, 'wb') as out_image: + out_image.write(img_bin) + + printer(f'Succesfull Insyde iFlash > {img_tag} extraction!', padding + 12) + + return 0 + +# Extract Insyde iFdPacker 7-Zip SFX 7z Update image +def insyde_packer_extract(input_buffer, extract_path, padding=0): + match_sfx = PAT_INSYDE_SFX.search(input_buffer) + + if not match_sfx: + return 127 + + printer('Detected Insyde iFdPacker Update image!', padding) + + make_dirs(extract_path, delete=True) + + sfx_buffer = bytearray(input_buffer[match_sfx.end() - 0x5:]) + + if sfx_buffer[:0x5] == b'\x6E\xF4\x79\x5F\x4E': + printer('Detected Insyde iFdPacker > 7-Zip SFX obfuscation!', padding + 4) + + for index,byte in enumerate(sfx_buffer): + sfx_buffer[index] = byte // 2 + (128 if byte % 2 else 0) + + printer('Removed Insyde iFdPacker > 7-Zip SFX obfuscation!', padding + 8) + + printer('Extracting Insyde iFdPacker > 7-Zip SFX archive...', padding + 4) + + sfx_path = os.path.join(extract_path, 'Insyde_iFdPacker_SFX.7z') + + with open(sfx_path, 'wb') as sfx_file: + sfx_file.write(sfx_buffer) + + if is_szip_supported(sfx_path, padding + 8, check=True): + if szip_decompress(sfx_path, extract_path, 'Insyde iFdPacker > 7-Zip SFX', padding + 8, check=True) == 0: + os.remove(sfx_path) + else: + return 125 + else: + return 126 + + exit_codes = [] + + for sfx_file in get_path_files(extract_path): + if is_insyde_ifd(sfx_file): + printer(f'{os.path.basename(sfx_file)}', padding + 12) + ifd_code = insyde_ifd_extract(sfx_file, sfx_file, padding + 16) + exit_codes.append(ifd_code) + + return sum(exit_codes) + +# Insyde iFlash Image Names +IFL_IMG_NAMES = { + 'BIOSCER' : ['Certificate', 'bin'], + 'BIOSCR2' : ['Certificate 2nd', 'bin'], + 'BIOSIMG' : ['BIOS-UEFI', 'bin'], + 'DRV_IMG' : ['isflash', 'efi'], + 'EC_IMG' : ['Embedded Controller', 'bin'], + 'INI_IMG' : ['platform', 'ini'], + 'ME_IMG' : ['Management Engine', 'bin'], + 'OEM_ID' : ['OEM Identifier', 'bin'], + } + +# Get common ctypes Structure Sizes +IFL_HDR_LEN = ctypes.sizeof(IflashHeader) + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + with open(input_file, 'rb') as in_file: + input_buffer = in_file.read() + + if not is_insyde_ifd(input_buffer): + printer('Error: This is not an Insyde iFlash/iFdPacker Update image!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + insyde_ifd_extract(input_buffer, extract_path, padding) + + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/Insyde_iFlash_Extract.py b/Insyde_iFlash_Extract.py deleted file mode 100644 index 7489e60..0000000 --- a/Insyde_iFlash_Extract.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python3 -#coding=utf-8 - -""" -Insyde iFlash Extract -Insyde iFlash Update Extractor -Copyright (C) 2022 Plato Mavropoulos -""" - -TITLE = 'Insyde iFlash Update Extractor v2.0_a2' - -import os -import sys -import ctypes - -# Stop __pycache__ generation -sys.dont_write_bytecode = True - -from common.path_ops import make_dirs, safe_name -from common.patterns import PAT_INSYDE_IFL -from common.struct_ops import get_struct, char, uint32_t -from common.system import script_init, argparse_init, printer -from common.text_ops import file_to_bytes - -class IflashHeader(ctypes.LittleEndianStructure): - _pack_ = 1 - _fields_ = [ - ('Signature', char*9), # 0x00 $_IFLASH_ - ('ImageTag', char*7), # 0x08 - ('TotalSize', uint32_t), # 0x10 from header end - ('ImageSize', uint32_t), # 0x14 from header end - # 0x18 - ] - - def struct_print(self, p): - printer(['Signature :', self.Signature.decode('utf-8','ignore')], p, False) - printer(['Image Name:', self.ImageTag.decode('utf-8','ignore')], p, False) - printer(['Image Size:', f'0x{self.ImageSize:X}'], p, False) - printer(['Total Size:', f'0x{self.TotalSize:X}'], p, False) - -# Parse & Extract Insyde iFlash Update image -def insyde_iflash_extract(input_buffer, ins_ifl_all, output_path, padding=0): - extract_path = os.path.join(f'{output_path}_extracted') - - make_dirs(extract_path, delete=True) - - for ins_ifl_val in ins_ifl_all: - ins_ifl_off,ins_ifl_hdr = ins_ifl_val - - mod_bgn = ins_ifl_off + IFL_HDR_LEN - mod_end = mod_bgn + ins_ifl_hdr.ImageSize - mod_bin = input_buffer[mod_bgn:mod_end] - - mod_val = [ins_ifl_hdr.ImageTag.decode('utf-8','ignore'), 'bin'] - mod_tag,mod_ext = IFL_MOD_NAMES.get(mod_val[0], mod_val) - - mod_name = f'{mod_tag} [0x{mod_bgn:08X}-0x{mod_end:08X}]' - - printer(f'{mod_name}\n', padding) - - ins_ifl_hdr.struct_print(padding + 4) - - if mod_val == [mod_tag,mod_ext]: - printer(f'Note: Detected new Insyde iFlash image tag {mod_tag}!', padding + 8, pause=True) - - out_name = f'{mod_name}.{mod_ext}' - - out_path = os.path.join(extract_path, safe_name(out_name)) - - with open(out_path, 'wb') as out: out.write(mod_bin) - - printer('Succesfull Insyde iFlash image extraction!', padding + 8) - -# Get Insyde iFlash Update image matches -def get_insyde_iflash(in_file): - ins_ifl_all = [] - ins_ifl_nan = [0x0,0xFFFFFFFF] - - buffer = file_to_bytes(in_file) - - for ins_ifl_match in PAT_INSYDE_IFL.finditer(buffer): - ins_ifl_off = ins_ifl_match.start() - - if len(buffer[ins_ifl_off:]) <= IFL_HDR_LEN: - continue - - ins_ifl_hdr = get_struct(buffer, ins_ifl_off, IflashHeader) - - if ins_ifl_hdr.TotalSize in ins_ifl_nan \ - or ins_ifl_hdr.ImageSize in ins_ifl_nan \ - or ins_ifl_hdr.TotalSize <= ins_ifl_hdr.ImageSize: - continue - - ins_ifl_all.append([ins_ifl_off, ins_ifl_hdr]) - - return ins_ifl_all - -# Check if input is Insyde iFlash Update image -def is_insyde_iflash(in_file): - buffer = file_to_bytes(in_file) - - return bool(get_insyde_iflash(buffer)) - -IFL_MOD_NAMES = { - 'DRV_IMG' : ['isflash', 'efi'], - 'INI_IMG' : ['platform', 'ini'], - 'BIOSIMG' : ['BIOS-UEFI', 'bin'], - 'ME_IMG_' : ['Management Engine', 'bin'], - 'EC_IMG_' : ['Embedded Controller', 'bin'], - 'OEM_ID_' : ['OEM Identifier', 'bin'], - 'BIOSCER' : ['Certificate', 'bin'], - 'BIOSCR2' : ['Certificate 2nd', 'bin'], - } - -# Get common ctypes Structure Sizes -IFL_HDR_LEN = ctypes.sizeof(IflashHeader) - -if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: input_buffer = in_file.read() - - ins_ifl_all = get_insyde_iflash(input_buffer) - - if not ins_ifl_all: - printer('Error: This is not an Insyde iFlash Update image!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - insyde_iflash_extract(input_buffer, ins_ifl_all, extract_path, padding) - - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) diff --git a/Panasonic_BIOS_Extract.py b/Panasonic_BIOS_Extract.py index 3373f68..900f1a6 100644 --- a/Panasonic_BIOS_Extract.py +++ b/Panasonic_BIOS_Extract.py @@ -7,7 +7,7 @@ Panasonic BIOS Package Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Panasonic BIOS Package Extractor v2.0_a8' +TITLE = 'Panasonic BIOS Package Extractor v2.0_a9' import os import io @@ -22,10 +22,10 @@ from common.comp_szip import is_szip_supported, szip_decompress from common.path_ops import get_path_files, make_dirs, path_stem, safe_name from common.pe_ops import get_pe_file, get_pe_info, is_pe_file, show_pe_info from common.patterns import PAT_MICROSOFT_CAB -from common.system import script_init, argparse_init, printer +from common.system import argparse_init, printer, script_init from common.text_ops import file_to_bytes -from AMI_PFAT_Extract import get_ami_pfat, parse_pfat_file +from AMI_PFAT_Extract import is_ami_pfat, parse_pfat_file # Check if input is Panasonic BIOS Package PE def is_panasonic_pkg(in_file): @@ -117,12 +117,10 @@ def panasonic_res_extract(pe_name, pe_file, extract_path, padding=0): printer('Succesfull PE Resource extraction!', padding + 8) # Detect & Unpack AMI BIOS Guard (PFAT) BIOS image - pfat_match,pfat_buffer = get_ami_pfat(res_raw) - - if pfat_match: + if is_ami_pfat(res_raw): pfat_dir = os.path.join(extract_path, res_tag) - parse_pfat_file(pfat_buffer, pfat_dir, padding + 12) + parse_pfat_file(res_raw, pfat_dir, padding + 12) else: if is_pe_file(res_raw): res_ext = 'exe' @@ -219,7 +217,8 @@ if __name__ == '__main__': printer(['***', input_name], padding - 4) - with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + with open(input_file, 'rb') as in_file: + input_buffer = in_file.read() # Check if Panasonic BIOS Package pattern was found on executable if not is_panasonic_pkg(input_buffer): diff --git a/Phoenix_TDK_Extract.py b/Phoenix_TDK_Extract.py index b36bc91..5d7456a 100644 --- a/Phoenix_TDK_Extract.py +++ b/Phoenix_TDK_Extract.py @@ -7,7 +7,7 @@ Phoenix TDK Packer Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Phoenix TDK Packer Extractor v2.0_a8' +TITLE = 'Phoenix TDK Packer Extractor v2.0_a9' import os import sys @@ -17,11 +17,11 @@ import ctypes # Stop __pycache__ generation sys.dont_write_bytecode = True -from common.path_ops import safe_name, make_dirs +from common.path_ops import make_dirs, safe_name from common.pe_ops import get_pe_file, get_pe_info -from common.patterns import PAT_PHOENIX_TDK, PAT_MICROSOFT_MZ, PAT_MICROSOFT_PE -from common.struct_ops import get_struct, char, uint32_t -from common.system import script_init, argparse_init, printer +from common.patterns import PAT_MICROSOFT_MZ, PAT_MICROSOFT_PE, PAT_PHOENIX_TDK +from common.struct_ops import char, get_struct, uint32_t +from common.system import argparse_init, printer, script_init from common.text_ops import file_to_bytes class PhoenixTdkHeader(ctypes.LittleEndianStructure): @@ -152,7 +152,9 @@ def is_phoenix_tdk(in_file): return bool(get_phoenix_tdk(buffer)[1] is not None) # Parse & Extract Phoenix Tools Development Kit (TDK) Packer -def phoenix_tdk_extract(input_buffer, output_path, pack_off, base_off=0, padding=0): +def phoenix_tdk_extract(input_file, output_path, padding=0): + input_buffer = file_to_bytes(input_file) + exit_code = 0 extract_path = os.path.join(f'{output_path}_extracted') @@ -161,6 +163,8 @@ def phoenix_tdk_extract(input_buffer, output_path, pack_off, base_off=0, padding printer('Phoenix Tools Development Kit Packer', padding) + base_off,pack_off = get_phoenix_tdk(input_buffer) + # Parse TDK Header structure tdk_hdr = get_struct(input_buffer, pack_off, PhoenixTdkHeader) @@ -224,7 +228,8 @@ def phoenix_tdk_extract(input_buffer, output_path, pack_off, base_off=0, padding if os.path.isfile(mod_file): mod_file += f'_{entry_index + 1:02d}' # Save TDK Entry data to output file - with open(mod_file, 'wb') as out_file: out_file.write(mod_data) + with open(mod_file, 'wb') as out_file: + out_file.write(mod_data) return exit_code @@ -248,19 +253,18 @@ if __name__ == '__main__': printer(['***', input_name], padding - 4) - with open(input_file, 'rb') as in_file: input_buffer = in_file.read() - - tdk_base_off,tdk_pack_off = get_phoenix_tdk(input_buffer) + with open(input_file, 'rb') as in_file: + input_buffer = in_file.read() # Check if Phoenix TDK Packer pattern was found on executable - if tdk_pack_off is None: + if not is_phoenix_tdk(input_buffer): printer('Error: This is not a Phoenix TDK Packer executable!', padding) continue # Next input file extract_path = os.path.join(output_path, input_name) - if phoenix_tdk_extract(input_buffer, extract_path, tdk_pack_off, tdk_base_off, padding) == 0: + if phoenix_tdk_extract(input_buffer, extract_path, padding) == 0: exit_code -= 1 printer('Done!', pause=True) diff --git a/Portwell_EFI_Extract.py b/Portwell_EFI_Extract.py index 1876f02..316181e 100644 --- a/Portwell_EFI_Extract.py +++ b/Portwell_EFI_Extract.py @@ -7,7 +7,7 @@ Portwell EFI Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Portwell EFI Update Extractor v2.0_a10' +TITLE = 'Portwell EFI Update Extractor v2.0_a11' import os import sys @@ -16,10 +16,10 @@ import sys sys.dont_write_bytecode = True from common.comp_efi import efi_decompress, is_efi_compressed -from common.path_ops import safe_name, make_dirs +from common.path_ops import make_dirs, safe_name from common.pe_ops import get_pe_file -from common.patterns import PAT_PORTWELL_EFI, PAT_MICROSOFT_MZ -from common.system import script_init, argparse_init, printer +from common.patterns import PAT_MICROSOFT_MZ, PAT_PORTWELL_EFI +from common.system import argparse_init, printer, script_init from common.text_ops import file_to_bytes FILE_NAMES = { @@ -34,10 +34,13 @@ FILE_NAMES = { def is_portwell_efi(in_file): in_buffer = file_to_bytes(in_file) - try: pe_buffer = get_portwell_pe(in_buffer)[1] - except: pe_buffer = b'' + try: + pe_buffer = get_portwell_pe(in_buffer)[1] + except: + pe_buffer = b'' is_mz = PAT_MICROSOFT_MZ.search(in_buffer[:0x2]) # EFI images start with PE Header MZ + is_uu = PAT_PORTWELL_EFI.search(pe_buffer[:0x4]) # Portwell EFI files start with return bool(is_mz and is_uu) @@ -104,7 +107,8 @@ def get_unpacker_tag(input_buffer, pe_file): # Process Portwell UEFI Unpacker payload files def parse_efi_files(extract_path, efi_files, padding): for file_index,file_data in enumerate(efi_files): - if file_data in (b'', b'NULL'): continue # Skip empty/unused files + if file_data in (b'', b'NULL'): + continue # Skip empty/unused files file_name = FILE_NAMES.get(file_index, f'Unknown_{file_index}.bin') # Assign Name to EFI file @@ -115,7 +119,8 @@ def parse_efi_files(extract_path, efi_files, padding): file_path = os.path.join(extract_path, safe_name(file_name)) # Store EFI file output path - with open(file_path, 'wb') as out_file: out_file.write(file_data) # Store EFI file data to drive + with open(file_path, 'wb') as out_file: + out_file.write(file_data) # Store EFI file data to drive # Attempt to detect EFI compression & decompress when applicable if is_efi_compressed(file_data): @@ -139,7 +144,8 @@ if __name__ == '__main__': printer(['***', input_name], padding - 4) - with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + with open(input_file, 'rb') as in_file: + input_buffer = in_file.read() if not is_portwell_efi(input_buffer): printer('Error: This is not a Portwell EFI Update Package!', padding) diff --git a/README.md b/README.md index 17dbfef..360342b 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) * [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) * [**Award BIOS Module Extractor**](#award-bios-module-extractor) -* [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) +* [**Dell PFS/PKG Update Extractor**](#dell-pfs-pkg-update-extractor) * [**Fujitsu UPC BIOS Extractor**](#fujitsu-upc-bios-extractor) -* [**Insyde iFlash Update Extractor**](#insyde-iflash-update-extractor) +* [**Insyde iFlash/iFdPacker Extractor**](#insyde-iflash-ifdpacker-update-extractor) * [**Panasonic BIOS Package Extractor**](#panasonic-bios-package-extractor) * [**Phoenix TDK Packer Extractor**](#phoenix-tdk-packer-extractor) * [**Portwell EFI Update Extractor**](#portwell-efi-update-extractor) @@ -37,7 +37,7 @@ You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s) #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. #### **Prerequisites** @@ -49,7 +49,7 @@ Optionally, to decompile the AMI PFAT \> Intel BIOS Guard Scripts, you must have PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.8.0 or newer is installed: +1. Make sure Python 3.10.0 or newer is installed: > python --version @@ -81,7 +81,7 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con #### **Description** -Parses AMI UCP (Utility Configuration Program) Update executables, extracts their firmware components (e.g. SPI/BIOS/UEFI, EC, ME etc) and shows all relevant info. It supports all AMI UCP revisions and formats, including those with nested AMI PFAT, AMI UCP or Insyde SFX structures. The output comprises only final firmware components and utilities which are directly usable by end users. +Parses AMI UCP (Utility Configuration Program) Update executables, extracts their firmware components (e.g. SPI/BIOS/UEFI, EC, ME etc) and shows all relevant info. It supports all AMI UCP revisions and formats, including those with nested AMI PFAT, AMI UCP or Insyde iFlash/iFdPacker structures. The output comprises only final firmware components and utilities which are directly usable by end users. #### **Usage** @@ -96,7 +96,7 @@ You can either Drag & Drop or manually enter AMI UCP Update executable file(s). #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. #### **Prerequisites** @@ -113,7 +113,7 @@ Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (wh PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.8.0 or newer is installed: +1. Make sure Python 3.10.0 or newer is installed: > python --version @@ -161,7 +161,7 @@ You can either Drag & Drop or manually enter Award BIOS image file(s). Optional #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. #### **Prerequisites** @@ -173,7 +173,7 @@ To run the utility, you must have the following 3rd party tool at the "external" PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.8.0 or newer is installed: +1. Make sure Python 3.10.0 or newer is installed: > python --version @@ -199,17 +199,17 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() -## **Dell PFS Update Extractor** +## **Dell PFS/PKG Update Extractor** ![]() #### **Description** -Parses Dell PFS Update images and extracts their Firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and Utilities (e.g. Flasher etc) component sections. It supports all Dell PFS revisions and formats, including those which are originally LZMA compressed in ThinOS packages, ZLIB compressed or Intel BIOS Guard (PFAT) protected. The output comprises only final firmware components which are directly usable by end users. +Parses Dell PFS/PKG Update images and extracts their Firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and Utilities (e.g. Flasher etc) component sections. It supports all Dell PFS/PKG revisions and formats, including those which are originally LZMA compressed in ThinOS packages, ZLIB compressed or Intel BIOS Guard (PFAT) protected. The output comprises only final firmware components which are directly usable by end users. #### **Usage** -You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional arguments: +You can either Drag & Drop or manually enter Dell PFS/PKG Update images(s). Optional arguments: * -h or --help : show help message and exit * -v or --version : show utility name and version @@ -221,7 +221,7 @@ You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. #### **Prerequisites** @@ -233,7 +233,7 @@ Optionally, to decompile the Intel BIOS Guard (PFAT) Scripts, you must have the PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.8.0 or newer is installed: +1. Make sure Python 3.10.0 or newer is installed: > python --version @@ -279,7 +279,7 @@ You can either Drag & Drop or manually enter Fujitsu UPC BIOS image file(s). Opt #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. #### **Prerequisites** @@ -291,7 +291,7 @@ To run the utility, you must have the following 3rd party tool at the "external" PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.8.0 or newer is installed: +1. Make sure Python 3.10.0 or newer is installed: > python --version @@ -317,17 +317,17 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() -## **Insyde iFlash Update Extractor** +## **Insyde iFlash/iFdPacker Extractor** ![]() #### **Description** -Parses Insyde iFlash Update images and extracts their firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and utilities (e.g. Flasher, Configuration etc) components. The output comprises only final firmware components which are directly usable by end users. +Parses Insyde iFlash/iFdPacker Update images and extracts their firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and utilities (e.g. Flasher, Configuration etc) components. It supports all Insyde iFlash/iFdPacker revisions and formats, including those which are \7-Zip SFX 7z compressed in raw or obfuscated form. The output comprises only final firmware components which are directly usable by end users. #### **Usage** -You can either Drag & Drop or manually enter Insyde iFlash Update image file(s). Optional arguments: +You can either Drag & Drop or manually enter Insyde iFlash/iFdPacker Update image file(s). Optional arguments: * -h or --help : show help message and exit * -v or --version : show utility name and version @@ -337,7 +337,7 @@ You can either Drag & Drop or manually enter Insyde iFlash Update image file(s). #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. #### **Prerequisites** @@ -347,7 +347,7 @@ To run the utility, you do not need any prerequisites. PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.8.0 or newer is installed: +1. Make sure Python 3.10.0 or newer is installed: > python --version @@ -357,7 +357,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 3. Build/Freeze/Compile: -> pyinstaller --noupx --onefile \\/Insyde_iFlash_Extract.py +> pyinstaller --noupx --onefile \\/Insyde_IFD_Extract.py At dist folder you should find the final utility executable @@ -389,7 +389,7 @@ You can either Drag & Drop or manually enter Panasonic BIOS Package executable f #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. #### **Prerequisites** @@ -406,7 +406,7 @@ Moreover, you must have the following 3rd party tool at the "external" project d PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.8.0 or newer is installed: +1. Make sure Python 3.10.0 or newer is installed: > python --version @@ -456,7 +456,7 @@ You can either Drag & Drop or manually enter Phoenix Tools Development Kit (TDK) #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. #### **Prerequisites** @@ -468,7 +468,7 @@ To run the utility, you must have the following 3rd party Python module installe PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.8.0 or newer is installed: +1. Make sure Python 3.10.0 or newer is installed: > python --version @@ -514,7 +514,7 @@ You can either Drag & Drop or manually enter Portwell UEFI Unpacker EFI executab #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. #### **Prerequisites** @@ -532,7 +532,7 @@ Moreover, you must have the following 3rd party tool at the "external" project d PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.8.0 or newer is installed: +1. Make sure Python 3.10.0 or newer is installed: > python --version @@ -582,7 +582,7 @@ You can either Drag & Drop or manually enter Toshiba BIOS COM image file(s). Opt #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. #### **Prerequisites** @@ -594,7 +594,7 @@ To run the utility, you must have the following 3rd party tool at the "external" PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.8.0 or newer is installed: +1. Make sure Python 3.10.0 or newer is installed: > python --version @@ -640,7 +640,7 @@ You can either Drag & Drop or manually enter VAIO Packaging Manager executable f #### **Compatibility** -Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support. +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. #### **Prerequisites** @@ -650,7 +650,7 @@ To run the utility, you do not need any prerequisites. PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. -1. Make sure Python 3.8.0 or newer is installed: +1. Make sure Python 3.10.0 or newer is installed: > python --version diff --git a/Toshiba_COM_Extract.py b/Toshiba_COM_Extract.py index 6af0770..4a6e1c2 100644 --- a/Toshiba_COM_Extract.py +++ b/Toshiba_COM_Extract.py @@ -7,7 +7,7 @@ Toshiba BIOS COM Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Toshiba BIOS COM Extractor v2.0_a2' +TITLE = 'Toshiba BIOS COM Extractor v2.0_a3' import os import sys @@ -49,7 +49,8 @@ def toshiba_com_extract(input_file, output_path, padding=0): try: subprocess.run([get_comextract_path(), input_file, output_file], check=True, stdout=subprocess.DEVNULL) - if not os.path.isfile(output_file): raise Exception('EXTRACT_FILE_MISSING') + if not os.path.isfile(output_file): + raise Exception('EXTRACT_FILE_MISSING') except: printer(f'Error: ToshibaComExtractor could not extract file {input_file}!', padding) @@ -72,7 +73,8 @@ if __name__ == '__main__': printer(['***', input_name], padding - 4) - with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + with open(input_file, 'rb') as in_file: + input_buffer = in_file.read() if not is_toshiba_com(input_file): printer('Error: This is not a Toshiba BIOS COM image!', padding) diff --git a/VAIO_Package_Extract.py b/VAIO_Package_Extract.py index 108f4e3..c8cec9c 100644 --- a/VAIO_Package_Extract.py +++ b/VAIO_Package_Extract.py @@ -7,7 +7,7 @@ VAIO Packaging Manager Extractor Copyright (C) 2019-2022 Plato Mavropoulos """ -TITLE = 'VAIO Packaging Manager Extractor v3.0_a6' +TITLE = 'VAIO Packaging Manager Extractor v3.0_a7' import os import sys @@ -17,8 +17,8 @@ sys.dont_write_bytecode = True from common.comp_szip import is_szip_supported, szip_decompress from common.path_ops import make_dirs -from common.patterns import PAT_VAIO_CFG, PAT_VAIO_CHK, PAT_VAIO_EXT, PAT_VAIO_CAB -from common.system import script_init, argparse_init, printer +from common.patterns import PAT_VAIO_CAB, PAT_VAIO_CFG, PAT_VAIO_CHK, PAT_VAIO_EXT +from common.system import argparse_init, printer, script_init from common.text_ops import file_to_bytes # Check if input is VAIO Packaging Manager @@ -31,7 +31,8 @@ def is_vaio_pkg(in_file): def vaio_cabinet(name, buffer, extract_path, padding=0): match_cab = PAT_VAIO_CAB.search(buffer) # Microsoft CAB Header XOR 0xFF - if not match_cab: return 1 + if not match_cab: + return 1 printer('Detected obfuscated CAB archive!', padding) @@ -51,7 +52,8 @@ def vaio_cabinet(name, buffer, extract_path, padding=0): cab_path = os.path.join(extract_path, f'{name}_Temporary.cab') - with open(cab_path, 'wb') as cab_file: cab_file.write(cab_data) # Create temporary CAB archive + with open(cab_path, 'wb') as cab_file: + cab_file.write(cab_data) # Create temporary CAB archive if is_szip_supported(cab_path, padding + 8, check=True): if szip_decompress(cab_path, extract_path, 'CAB', padding + 8, check=True) == 0: @@ -67,7 +69,8 @@ def vaio_cabinet(name, buffer, extract_path, padding=0): def vaio_unlock(name, buffer, extract_path, padding=0): match_cfg = PAT_VAIO_CFG.search(buffer) - if not match_cfg: return 1 + if not match_cfg: + return 1 printer('Attempting to Unlock executable!', padding) @@ -116,7 +119,8 @@ def vaio_unlock(name, buffer, extract_path, padding=0): # Store Unlocked VAIO Packaging Manager executable if vaio_check and user_path: unlock_path = os.path.join(extract_path, f'{name}_Unlocked.exe') - with open(unlock_path, 'wb') as unl_file: unl_file.write(buffer) + with open(unlock_path, 'wb') as unl_file: + unl_file.write(buffer) return 0 @@ -149,7 +153,8 @@ if __name__ == '__main__': printer(['***', input_name], padding - 4) - with open(input_file, 'rb') as in_file: input_buffer = in_file.read() + with open(input_file, 'rb') as in_file: + input_buffer = in_file.read() # Check if VAIO Packaging Manager pattern was found on executable if not is_vaio_pkg(input_buffer): diff --git a/common/comp_efi.py b/common/comp_efi.py index da2f214..3c14b71 100644 --- a/common/comp_efi.py +++ b/common/comp_efi.py @@ -22,8 +22,10 @@ def is_efi_compressed(data, strict=True): check_diff = size_comp < size_orig - if strict: check_size = size_comp + 0x8 == len(data) - else: check_size = size_comp + 0x8 <= len(data) + if strict: + check_size = size_comp + 0x8 == len(data) + else: + check_size = size_comp + 0x8 <= len(data) return check_diff and check_size @@ -38,9 +40,11 @@ def efi_decompress(in_path, out_path, padding=0, silent=False, comp_type='--uefi try: subprocess.run([get_tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type], check=True, stdout=subprocess.DEVNULL) - with open(in_path, 'rb') as file: _,size_orig = get_compress_sizes(file.read()) + with open(in_path, 'rb') as file: + _,size_orig = get_compress_sizes(file.read()) - if os.path.getsize(out_path) != size_orig: raise Exception('EFI_DECOMPRESS_ERROR') + if os.path.getsize(out_path) != size_orig: + raise Exception('EFI_DECOMPRESS_ERROR') except: if not silent: printer(f'Error: TianoCompress could not extract file {in_path}!', padding) diff --git a/common/comp_szip.py b/common/comp_szip.py index 4ac053a..a3cb6ad 100644 --- a/common/comp_szip.py +++ b/common/comp_szip.py @@ -27,7 +27,8 @@ def is_szip_supported(in_path, padding=0, check=False, silent=False): try: szip_t = subprocess.run([get_szip_path(), 't', in_path, '-bso0', '-bse0', '-bsp0'], check=False) - if check: check_bad_exit_code(szip_t.returncode) + if check: + check_bad_exit_code(szip_t.returncode) except: if not silent: printer(f'Error: 7-Zip could not check support for file {in_path}!', padding) @@ -38,14 +39,17 @@ def is_szip_supported(in_path, padding=0, check=False, silent=False): # Archive decompression via 7-Zip def szip_decompress(in_path, out_path, in_name, padding=0, check=False, silent=False): - if not in_name: in_name = 'archive' + if not in_name: + in_name = 'archive' try: szip_x = subprocess.run([get_szip_path(), 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + out_path, in_path], check=False) - if check: check_bad_exit_code(szip_x.returncode) + if check: + check_bad_exit_code(szip_x.returncode) - if not os.path.isdir(out_path): raise Exception('EXTRACT_DIR_MISSING') + if not os.path.isdir(out_path): + raise Exception('EXTRACT_DIR_MISSING') except: if not silent: printer(f'Error: 7-Zip could not extract {in_name} file {in_path}!', padding) diff --git a/common/externals.py b/common/externals.py index 89d6a9e..28b7631 100644 --- a/common/externals.py +++ b/common/externals.py @@ -6,6 +6,7 @@ Copyright (C) 2022 Plato Mavropoulos """ # https://github.com/allowitsme/big-tool by Dmitry Frolov +# https://github.com/platomav/BGScriptTool by Plato Mavropoulos def get_bgs_tool(): try: from external.big_script_tool import BigScript diff --git a/common/path_ops.py b/common/path_ops.py index 5e76eef..4357108 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -84,7 +84,8 @@ def is_path_absolute(in_path): # Create folder(s), controlling parents, existence and prior deletion def make_dirs(in_path, parents=True, exist_ok=False, delete=False): - if delete: del_dirs(in_path) + if delete: + del_dirs(in_path) Path.mkdir(Path(in_path), parents=parents, exist_ok=exist_ok) @@ -129,7 +130,8 @@ def get_argparse_path(argparse_path): # Process input files (argparse object) def process_input_files(argparse_args, sys_argv=None): - if sys_argv is None: sys_argv = [] + if sys_argv is None: + sys_argv = [] if len(sys_argv) >= 2: # Drag & Drop or CLI diff --git a/common/patterns.py b/common/patterns.py index 797d467..9252c91 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -14,6 +14,7 @@ PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) PAT_INSYDE_IFL = re.compile(br'\$_IFLASH_') +PAT_INSYDE_SFX = re.compile(br'\x0D\x0A;!@InstallEnd@!\x0D\x0A(7z\xBC\xAF\x27|\x6E\xF4\x79\x5F\x4E)') PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) PAT_MICROSOFT_CAB = re.compile(br'MSCF\x00{4}') PAT_MICROSOFT_MZ = re.compile(br'MZ') diff --git a/common/struct_ops.py b/common/struct_ops.py index 31c6e5b..b995ba1 100644 --- a/common/struct_ops.py +++ b/common/struct_ops.py @@ -15,7 +15,8 @@ uint64_t = ctypes.c_uint64 # https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky def get_struct(buffer, start_offset, class_name, param_list=None): - if param_list is None: param_list = [] + if param_list is None: + param_list = [] structure = class_name(*param_list) # Unpack parameter list struct_len = ctypes.sizeof(structure) diff --git a/common/system.py b/common/system.py index 5dd4ff4..fab02ef 100644 --- a/common/system.py +++ b/common/system.py @@ -56,7 +56,8 @@ def check_sys_os(): sys.exit(126) # Fix Windows Unicode console redirection - if os_win: sys.stdout.reconfigure(encoding='utf-8') + if os_win: + sys.stdout.reconfigure(encoding='utf-8') # Initialize common argparse arguments def argparse_init(): @@ -85,10 +86,12 @@ def script_init(title, arguments, padding=0): printer(title, new_line=False) # Show Utility Version on demand - if arguments.version: sys.exit(0) + if arguments.version: + sys.exit(0) # Set console/terminal window title (Windows only) - if get_os_ver()[1]: ctypes.windll.kernel32.SetConsoleTitleW(title) + if get_os_ver()[1]: + ctypes.windll.kernel32.SetConsoleTitleW(title) # Process input files and generate output path input_files,output_path = process_input_files(arguments, sys.argv) diff --git a/external/requirements.txt b/external/requirements.txt index 06fc06e..f6b760a 100644 --- a/external/requirements.txt +++ b/external/requirements.txt @@ -1,2 +1,2 @@ lznt1==0.2 -pefile==2021.9.3 +pefile==2022.5.30 From 0317009e09973d2503d21efe75fbca0bfb5e2aaf Mon Sep 17 00:00:00 2001 From: platomav Date: Wed, 6 Jul 2022 18:01:56 +0300 Subject: [PATCH 38/52] Fixed README index --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 360342b..75d31d7 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) * [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) * [**Award BIOS Module Extractor**](#award-bios-module-extractor) -* [**Dell PFS/PKG Update Extractor**](#dell-pfs-pkg-update-extractor) +* [**Dell PFS/PKG Update Extractor**](#dell-pfspkg-update-extractor) * [**Fujitsu UPC BIOS Extractor**](#fujitsu-upc-bios-extractor) -* [**Insyde iFlash/iFdPacker Extractor**](#insyde-iflash-ifdpacker-update-extractor) +* [**Insyde iFlash/iFdPacker Extractor**](#insyde-iflashifdpacker-extractor) * [**Panasonic BIOS Package Extractor**](#panasonic-bios-package-extractor) * [**Phoenix TDK Packer Extractor**](#phoenix-tdk-packer-extractor) * [**Portwell EFI Update Extractor**](#portwell-efi-update-extractor) From 69784889cb01fffa0ada19fa015c39a5cda8fac4 Mon Sep 17 00:00:00 2001 From: platomav Date: Wed, 6 Jul 2022 18:10:11 +0300 Subject: [PATCH 39/52] Fixed README > Insyde iFlash/iFdPacker Extractor Increased Python version to 3.10 and PEFile version to 2022.5.30 (performance) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 75d31d7..30530b6 100644 --- a/README.md +++ b/README.md @@ -323,7 +323,7 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con #### **Description** -Parses Insyde iFlash/iFdPacker Update images and extracts their firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and utilities (e.g. Flasher, Configuration etc) components. It supports all Insyde iFlash/iFdPacker revisions and formats, including those which are \7-Zip SFX 7z compressed in raw or obfuscated form. The output comprises only final firmware components which are directly usable by end users. +Parses Insyde iFlash/iFdPacker Update images and extracts their firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and utilities (e.g. H2OFFT, FlsHook, iscflash, platform.ini etc) components. It supports all Insyde iFlash/iFdPacker revisions and formats, including those which are 7-Zip SFX 7z compressed in raw or obfuscated form. The output comprises only final firmware components which are directly usable by end users. #### **Usage** From df47293d01173f086fb59d3799f0405f2e1cfc6c Mon Sep 17 00:00:00 2001 From: platomav Date: Thu, 7 Jul 2022 01:31:29 +0300 Subject: [PATCH 40/52] Insyde iFlash/iFdPacker Extractor v2.0_a9 Support for hardcoded Insyde 7-Zip SFX password Fixed 7-Zip hang on password protected files --- Insyde_IFD_Extract.py | 54 +++++++++++++++++++++++++++++++------------ README.md | 2 +- common/comp_szip.py | 12 ++++++---- common/patterns.py | 2 +- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/Insyde_IFD_Extract.py b/Insyde_IFD_Extract.py index 0189ed9..ba9c28e 100644 --- a/Insyde_IFD_Extract.py +++ b/Insyde_IFD_Extract.py @@ -7,7 +7,7 @@ Insyde iFlash/iFdPacker Extractor Copyright (C) 2022 Plato Mavropoulos """ -TITLE = 'Insyde iFlash/iFdPacker Extractor v2.0_a8' +TITLE = 'Insyde iFlash/iFdPacker Extractor v2.0_a9' import os import sys @@ -33,6 +33,9 @@ class IflashHeader(ctypes.LittleEndianStructure): # 0x18 ] + def _get_padd_len(self): + return self.TotalSize - self.ImageSize + def get_image_tag(self): return self.ImageTag.decode('utf-8','ignore').strip('_') @@ -41,6 +44,7 @@ class IflashHeader(ctypes.LittleEndianStructure): printer(['Image Name:', self.get_image_tag()], p, False) printer(['Image Size:', f'0x{self.ImageSize:X}'], p, False) printer(['Total Size:', f'0x{self.TotalSize:X}'], p, False) + printer(['Padd Size :', f'0x{self._get_padd_len():X}'], p, False) # Check if input is Insyde iFlash/iFdPacker Update image def is_insyde_ifd(input_file): @@ -73,15 +77,16 @@ def insyde_iflash_detect(input_buffer): for iflash_match in PAT_INSYDE_IFL.finditer(input_buffer): ifl_bgn = iflash_match.start() - - if len(input_buffer[ifl_bgn:]) <= IFL_HDR_LEN: + + if len(input_buffer[ifl_bgn:]) <= INS_IFL_LEN: continue ifl_hdr = get_struct(input_buffer, ifl_bgn, IflashHeader) if ifl_hdr.TotalSize in iflash_match_nan \ or ifl_hdr.ImageSize in iflash_match_nan \ - or ifl_hdr.TotalSize <= ifl_hdr.ImageSize: + or ifl_hdr.TotalSize < ifl_hdr.ImageSize \ + or ifl_bgn + INS_IFL_LEN + ifl_hdr.TotalSize > len(input_buffer): continue iflash_match_all.append([ifl_bgn, ifl_hdr]) @@ -93,21 +98,28 @@ def insyde_iflash_extract(input_buffer, extract_path, padding=0): insyde_iflash_all = insyde_iflash_detect(input_buffer) if not insyde_iflash_all: - return 1 + return 127 printer('Detected Insyde iFlash Update image!', padding) make_dirs(extract_path, delete=True) + exit_codes = [] + for insyde_iflash in insyde_iflash_all: + exit_code = 0 + ifl_bgn,ifl_hdr = insyde_iflash - img_bgn = ifl_bgn + IFL_HDR_LEN + img_bgn = ifl_bgn + INS_IFL_LEN img_end = img_bgn + ifl_hdr.ImageSize img_bin = input_buffer[img_bgn:img_end] + if len(img_bin) != ifl_hdr.ImageSize: + exit_code = 1 + img_val = [ifl_hdr.get_image_tag(), 'bin'] - img_tag,img_ext = IFL_IMG_NAMES.get(img_val[0], img_val) + img_tag,img_ext = INS_IFL_IMG.get(img_val[0], img_val) img_name = f'{img_tag} [0x{img_bgn:08X}-0x{img_end:08X}]' @@ -126,8 +138,10 @@ def insyde_iflash_extract(input_buffer, extract_path, padding=0): out_image.write(img_bin) printer(f'Succesfull Insyde iFlash > {img_tag} extraction!', padding + 12) + + exit_codes.append(exit_code) - return 0 + return sum(exit_codes) # Extract Insyde iFdPacker 7-Zip SFX 7z Update image def insyde_packer_extract(input_buffer, extract_path, padding=0): @@ -143,22 +157,27 @@ def insyde_packer_extract(input_buffer, extract_path, padding=0): sfx_buffer = bytearray(input_buffer[match_sfx.end() - 0x5:]) if sfx_buffer[:0x5] == b'\x6E\xF4\x79\x5F\x4E': - printer('Detected Insyde iFdPacker > 7-Zip SFX obfuscation!', padding + 4) + printer('Detected Insyde iFdPacker > 7-Zip SFX > Obfuscation!', padding + 4) for index,byte in enumerate(sfx_buffer): sfx_buffer[index] = byte // 2 + (128 if byte % 2 else 0) - printer('Removed Insyde iFdPacker > 7-Zip SFX obfuscation!', padding + 8) + printer('Removed Insyde iFdPacker > 7-Zip SFX > Obfuscation!', padding + 8) printer('Extracting Insyde iFdPacker > 7-Zip SFX archive...', padding + 4) + if bytes(INS_SFX_PWD, 'utf-16le') in input_buffer[:match_sfx.start()]: + printer('Detected Insyde iFdPacker > 7-Zip SFX > Password!', padding + 8) + printer(INS_SFX_PWD, padding + 12) + sfx_path = os.path.join(extract_path, 'Insyde_iFdPacker_SFX.7z') with open(sfx_path, 'wb') as sfx_file: sfx_file.write(sfx_buffer) - if is_szip_supported(sfx_path, padding + 8, check=True): - if szip_decompress(sfx_path, extract_path, 'Insyde iFdPacker > 7-Zip SFX', padding + 8, check=True) == 0: + if is_szip_supported(sfx_path, padding + 8, password=INS_SFX_PWD, check=True): + if szip_decompress(sfx_path, extract_path, 'Insyde iFdPacker > 7-Zip SFX', + padding + 8, password=INS_SFX_PWD, check=True) == 0: os.remove(sfx_path) else: return 125 @@ -170,13 +189,18 @@ def insyde_packer_extract(input_buffer, extract_path, padding=0): for sfx_file in get_path_files(extract_path): if is_insyde_ifd(sfx_file): printer(f'{os.path.basename(sfx_file)}', padding + 12) + ifd_code = insyde_ifd_extract(sfx_file, sfx_file, padding + 16) + exit_codes.append(ifd_code) return sum(exit_codes) -# Insyde iFlash Image Names -IFL_IMG_NAMES = { +# Insyde iFdPacker known 7-Zip SFX Password +INS_SFX_PWD = 'Y`t~i!L@i#t$U%h^s7A*l(f)E-d=y+S_n?i' + +# Insyde iFlash known Image Names +INS_IFL_IMG = { 'BIOSCER' : ['Certificate', 'bin'], 'BIOSCR2' : ['Certificate 2nd', 'bin'], 'BIOSIMG' : ['BIOS-UEFI', 'bin'], @@ -188,7 +212,7 @@ IFL_IMG_NAMES = { } # Get common ctypes Structure Sizes -IFL_HDR_LEN = ctypes.sizeof(IflashHeader) +INS_IFL_LEN = ctypes.sizeof(IflashHeader) if __name__ == '__main__': # Set argparse Arguments diff --git a/README.md b/README.md index 30530b6..96ecc1f 100644 --- a/README.md +++ b/README.md @@ -323,7 +323,7 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con #### **Description** -Parses Insyde iFlash/iFdPacker Update images and extracts their firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and utilities (e.g. H2OFFT, FlsHook, iscflash, platform.ini etc) components. It supports all Insyde iFlash/iFdPacker revisions and formats, including those which are 7-Zip SFX 7z compressed in raw or obfuscated form. The output comprises only final firmware components which are directly usable by end users. +Parses Insyde iFlash/iFdPacker Update images and extracts their firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and utilities (e.g. InsydeFlash, H2OFFT, FlsHook, iscflash, platform.ini etc) components. It supports all Insyde iFlash/iFdPacker revisions and formats, including those which are 7-Zip SFX 7z compressed in raw, obfuscated or password-protected form. The output comprises only final firmware components which are directly usable by end users. #### **Usage** diff --git a/common/comp_szip.py b/common/comp_szip.py index a3cb6ad..4a3d26b 100644 --- a/common/comp_szip.py +++ b/common/comp_szip.py @@ -23,9 +23,11 @@ def check_bad_exit_code(exit_code): raise Exception(f'BAD_EXIT_CODE_{exit_code}') # Check if file is 7-Zip supported -def is_szip_supported(in_path, padding=0, check=False, silent=False): +def is_szip_supported(in_path, padding=0, password='', check=False, silent=False): try: - szip_t = subprocess.run([get_szip_path(), 't', in_path, '-bso0', '-bse0', '-bsp0'], check=False) + szip_c = [get_szip_path(), 't', in_path, f'-p{password}', '-bso0', '-bse0', '-bsp0'] + + szip_t = subprocess.run(szip_c, check=False) if check: check_bad_exit_code(szip_t.returncode) @@ -38,12 +40,14 @@ def is_szip_supported(in_path, padding=0, check=False, silent=False): return True # Archive decompression via 7-Zip -def szip_decompress(in_path, out_path, in_name, padding=0, check=False, silent=False): +def szip_decompress(in_path, out_path, in_name, padding=0, password='', check=False, silent=False): if not in_name: in_name = 'archive' try: - szip_x = subprocess.run([get_szip_path(), 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + out_path, in_path], check=False) + szip_c = [get_szip_path(), 'x', f'-p{password}', '-aou', '-bso0', '-bse0', '-bsp0', f'-o{out_path}', in_path] + + szip_x = subprocess.run(szip_c, check=False) if check: check_bad_exit_code(szip_x.returncode) diff --git a/common/patterns.py b/common/patterns.py index 9252c91..3e11e84 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -13,7 +13,7 @@ PAT_AWARD_LZH = re.compile(br'-lh[04567]-') PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) -PAT_INSYDE_IFL = re.compile(br'\$_IFLASH_') +PAT_INSYDE_IFL = re.compile(br'\$_IFLASH') PAT_INSYDE_SFX = re.compile(br'\x0D\x0A;!@InstallEnd@!\x0D\x0A(7z\xBC\xAF\x27|\x6E\xF4\x79\x5F\x4E)') PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) PAT_MICROSOFT_CAB = re.compile(br'MSCF\x00{4}') From 48562b0f6857d8a32cb25a9d77f21741e198b622 Mon Sep 17 00:00:00 2001 From: platomav Date: Thu, 14 Jul 2022 01:32:25 +0300 Subject: [PATCH 41/52] Added Fujitsu SFX BIOS Extractor v3.0_a2 Fixed deletion of folders with read-only files Fixed missing README > Requirement for VAIO Packaging Manager Extractor --- Fujitsu_SFX_Extract.py | 118 +++++++++++++++++++++++++++++++++++++++++ README.md | 71 +++++++++++++++++++++++-- common/path_ops.py | 8 ++- common/patterns.py | 1 + 4 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 Fujitsu_SFX_Extract.py diff --git a/Fujitsu_SFX_Extract.py b/Fujitsu_SFX_Extract.py new file mode 100644 index 0000000..f27b984 --- /dev/null +++ b/Fujitsu_SFX_Extract.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Fujitsu SFX Extractor +Fujitsu SFX BIOS Extractor +Copyright (C) 2019-2022 Plato Mavropoulos +""" + +TITLE = 'Fujitsu SFX BIOS Extractor v3.0_a2' + +import os +import sys + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.comp_szip import is_szip_supported, szip_decompress +from common.path_ops import make_dirs +from common.patterns import PAT_FUJITSU_SFX +from common.system import argparse_init, printer, script_init +from common.text_ops import file_to_bytes + +# Check if input is Fujitsu SFX image +def is_fujitsu_sfx(in_file): + buffer = file_to_bytes(in_file) + + return bool(PAT_FUJITSU_SFX.search(buffer)) + +# Extract Fujitsu SFX image +def fujitsu_cabinet(in_file, extract_path, padding=0): + buffer = file_to_bytes(in_file) + + match_cab = PAT_FUJITSU_SFX.search(buffer) # Microsoft CAB Header XOR 0xFF + + if not match_cab: + return 1 + + printer('Detected obfuscated CAB archive!', padding) + + # Microsoft CAB Header XOR 0xFF starts after "FjSfxBinay" signature + cab_start = match_cab.start() + 0xA + + # Determine the Microsoft CAB image size + cab_size = int.from_bytes(buffer[cab_start + 0x8:cab_start + 0xC], 'little') # Get LE XOR-ed CAB size + xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB size XOR value + cab_size ^= xor_size # Perform XOR 0xFF and get actual CAB size + + printer('Removing obfuscation...', padding + 4) + + # Determine the Microsoft CAB image Data + cab_data = int.from_bytes(buffer[cab_start:cab_start + cab_size], 'big') # Get BE XOR-ed CAB data + xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB data XOR value + cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB data + + printer('Extracting archive...', padding + 4) + + cab_path = os.path.join(extract_path, 'FjSfxBinay.cab') + + with open(cab_path, 'wb') as cab_file: + cab_file.write(cab_data) # Create temporary CAB archive + + if is_szip_supported(cab_path, padding + 8, check=True): + if szip_decompress(cab_path, extract_path, 'CAB', padding + 8, check=True) == 0: + os.remove(cab_path) # Successful extraction, delete temporary CAB archive + else: + return 3 + else: + return 2 + + return 0 + +# Parse & Extract Fujitsu SFX image +def fujitsu_sfx_extract(in_file, output_path, padding=0): + buffer = file_to_bytes(in_file) + + extract_path = os.path.join(f'{output_path}_extracted') + + make_dirs(extract_path, delete=True) + + if fujitsu_cabinet(buffer, extract_path, padding) == 0: + printer('Successfully Extracted!', padding) + else: + printer('Error: Failed to Extract image!', padding) + return 1 + + return 0 + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + with open(input_file, 'rb') as in_file: + input_buffer = in_file.read() + + # Check if Fujitsu SFX pattern was found on image + if not is_fujitsu_sfx(input_buffer): + printer('Error: This is not a Fujitsu SFX image!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + if fujitsu_sfx_extract(input_buffer, extract_path, padding) == 0: + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/README.md b/README.md index 96ecc1f..7bfac61 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ * [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) * [**Award BIOS Module Extractor**](#award-bios-module-extractor) * [**Dell PFS/PKG Update Extractor**](#dell-pfspkg-update-extractor) +* [**Fujitsu SFX BIOS Extractor**](#fujitsu-sfx-bios-extractor) * [**Fujitsu UPC BIOS Extractor**](#fujitsu-upc-bios-extractor) * [**Insyde iFlash/iFdPacker Extractor**](#insyde-iflashifdpacker-extractor) * [**Panasonic BIOS Package Extractor**](#panasonic-bios-package-extractor) @@ -259,6 +260,64 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() +## **Fujitsu SFX BIOS Extractor** + +![]() + +#### **Description** + +Parses Fujitsu SFX BIOS images and extracts their obfuscated Microsoft CAB archived firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and utilities (e.g. WinPhlash, PHLASH.INI etc) components. The output comprises only final firmware components which are directly usable by end users. + +#### **Usage** + +You can either Drag & Drop or manually enter Fujitsu SFX BIOS image file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. + +#### **Prerequisites** + +To run the utility, you must have the following 3rd party tool at the "external" project directory: + +* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.10.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Place prerequisite at the "external" project directory: + +> 7-Zip Console + +4. Build/Freeze/Compile: + +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Fujitsu_SFX_Extract.py + +At dist folder you should find the final utility executable + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + ## **Fujitsu UPC BIOS Extractor** ![]() @@ -644,7 +703,9 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 #### **Prerequisites** -To run the utility, you do not need any prerequisites. +To run the utility, you must have the following 3rd party tool at the "external" project directory: + +* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) #### **Build/Freeze/Compile with PyInstaller** @@ -658,9 +719,13 @@ PyInstaller can build/freeze/compile the utility at all three supported platform > pip3 install pyinstaller -3. Build/Freeze/Compile: +3. Place prerequisite at the "external" project directory: -> pyinstaller --noupx --onefile \\/VAIO_Package_Extract.py +> 7-Zip Console + +4. Build/Freeze/Compile: + +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/VAIO_Package_Extract.py At dist folder you should find the final utility executable diff --git a/common/path_ops.py b/common/path_ops.py index 4357108..8381d56 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -8,6 +8,7 @@ Copyright (C) 2022 Plato Mavropoulos import os import re import sys +import stat import shutil from pathlib import Path, PurePath @@ -92,7 +93,12 @@ def make_dirs(in_path, parents=True, exist_ok=False, delete=False): # Delete folder(s), if present def del_dirs(in_path): if Path(in_path).is_dir(): - shutil.rmtree(in_path) + shutil.rmtree(in_path, onerror=clear_readonly) + +# Clear read-only file attribute (on shutil.rmtree error) +def clear_readonly(in_func, in_path, _): + os.chmod(in_path, stat.S_IWRITE) + in_func(in_path) # Walk path to get all files def get_path_files(in_path): diff --git a/common/patterns.py b/common/patterns.py index 3e11e84..92ab4a6 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -13,6 +13,7 @@ PAT_AWARD_LZH = re.compile(br'-lh[04567]-') PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL) +PAT_FUJITSU_SFX = re.compile(br'FjSfxBinay\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL) PAT_INSYDE_IFL = re.compile(br'\$_IFLASH') PAT_INSYDE_SFX = re.compile(br'\x0D\x0A;!@InstallEnd@!\x0D\x0A(7z\xBC\xAF\x27|\x6E\xF4\x79\x5F\x4E)') PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) From c144ad804c7077f5f603ed77ce9bf245658352bb Mon Sep 17 00:00:00 2001 From: platomav Date: Mon, 15 Aug 2022 18:29:58 +0300 Subject: [PATCH 42/52] Added Apple EFI Image Identifier v2.0_a3 Fixed argparse lock of input files --- Apple_EFI_Identify.py | 193 ++++++++++++++++++++++++++++++++++++++++++ README.md | 70 ++++++++++++++- common/externals.py | 15 ++++ common/path_ops.py | 9 +- common/patterns.py | 1 + 5 files changed, 283 insertions(+), 5 deletions(-) create mode 100644 Apple_EFI_Identify.py diff --git a/Apple_EFI_Identify.py b/Apple_EFI_Identify.py new file mode 100644 index 0000000..d203d5b --- /dev/null +++ b/Apple_EFI_Identify.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Apple EFI Identify +Apple EFI Image Identifier +Copyright (C) 2018-2022 Plato Mavropoulos +""" + +TITLE = 'Apple EFI Image Identifier v2.0_a3' + +import os +import sys +import zlib +import struct +import ctypes +import subprocess + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.externals import get_uefifind_path, get_uefiextract_path +from common.path_ops import del_dirs, path_parent, path_suffixes +from common.patterns import PAT_APPLE_EFI +from common.struct_ops import char, get_struct, uint8_t +from common.system import argparse_init, printer, script_init +from common.text_ops import file_to_bytes + +class IntelBiosId(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Signature', char*8), # 0x00 + ('BoardID', uint8_t*16), # 0x08 + ('Dot1', uint8_t*2), # 0x18 + ('BoardExt', uint8_t*6), # 0x1A + ('Dot2', uint8_t*2), # 0x20 + ('VersionMajor', uint8_t*8), # 0x22 + ('Dot3', uint8_t*2), # 0x2A + ('BuildType', uint8_t*2), # 0x2C + ('VersionMinor', uint8_t*4), # 0x2E + ('Dot4', uint8_t*2), # 0x32 + ('Year', uint8_t*4), # 0x34 + ('Month', uint8_t*4), # 0x38 + ('Day', uint8_t*4), # 0x3C + ('Hour', uint8_t*4), # 0x40 + ('Minute', uint8_t*4), # 0x44 + ('NullTerminator', uint8_t*2), # 0x48 + # 0x4A + ] + + # https://github.com/tianocore/edk2-platforms/blob/master/Platform/Intel/BoardModulePkg/Include/Guid/BiosId.h + + @staticmethod + def decode(field): + return struct.pack('B' * len(field), *field).decode('utf-16','ignore').strip('\x00 ') + + def get_bios_id(self): + BoardID = self.decode(self.BoardID) + BoardExt = self.decode(self.BoardExt) + VersionMajor = self.decode(self.VersionMajor) + BuildType = self.decode(self.BuildType) + VersionMinor = self.decode(self.VersionMinor) + BuildDate = f'20{self.decode(self.Year)}-{self.decode(self.Month)}-{self.decode(self.Day)}' + BuildTime = f'{self.decode(self.Hour)}-{self.decode(self.Minute)}' + + return BoardID, BoardExt, VersionMajor, BuildType, VersionMinor, BuildDate, BuildTime + + def struct_print(self, p): + BoardID,BoardExt,VersionMajor,BuildType,VersionMinor,BuildDate,BuildTime = self.get_bios_id() + + printer(['Intel Signature:', self.Signature.decode('utf-8')], p, False) + printer(['Board Identity: ', BoardID], p, False) + printer(['Apple Identity: ', BoardExt], p, False) + printer(['Major Version: ', VersionMajor], p, False) + printer(['Minor Version: ', VersionMinor], p, False) + printer(['Build Type: ', BuildType], p, False) + printer(['Build Date: ', BuildDate], p, False) + printer(['Build Time: ', BuildTime.replace('-',':')], p, False) + +# Check if input is Apple EFI image +def is_apple_efi(input_file): + input_buffer = file_to_bytes(input_file) + + if PAT_APPLE_EFI.search(input_buffer): + return True + + if not os.path.isfile(input_file): + return False + + try: + _ = subprocess.run([get_uefifind_path(), input_file, 'body', 'list', PAT_UEFIFIND], + check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + return True + except: + return False + +# Parse & Identify (or Rename) Apple EFI image +def apple_efi_identify(input_file, output_path, padding=0, rename=False): + if not os.path.isfile(input_file): + printer('Error: Could not find input file path!', padding) + + return 1 + + input_buffer = file_to_bytes(input_file) + + bios_id_match = PAT_APPLE_EFI.search(input_buffer) # Detect $IBIOSI$ pattern + + if bios_id_match: + bios_id_res = f'0x{bios_id_match.start():X}' + + bios_id_hdr = get_struct(input_buffer, bios_id_match.start(), IntelBiosId) + else: + # The $IBIOSI$ pattern is within EFI compressed modules so we need to use UEFIFind and UEFIExtract + try: + bios_id_res = subprocess.check_output([get_uefifind_path(), input_file, 'body', 'list', PAT_UEFIFIND], + text=True)[:36] + + temp_dir = os.path.join(f'{output_path}_uefiextract') + + del_dirs(temp_dir) # UEFIExtract must create its output folder itself, make sure it is not present + + _ = subprocess.run([get_uefiextract_path(), input_file, bios_id_res, '-o', temp_dir, '-m', 'body'], + check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + with open(os.path.join(temp_dir, 'body.bin'), 'rb') as raw_body: + body_buffer = raw_body.read() + + bios_id_match = PAT_APPLE_EFI.search(body_buffer) # Detect decompressed $IBIOSI$ pattern + + bios_id_hdr = get_struct(body_buffer, bios_id_match.start(), IntelBiosId) + + del_dirs(temp_dir) # Successful UEFIExtract extraction, remove its output (temp) folder + except: + printer('Error: Failed to parse compressed $IBIOSI$ pattern!', padding) + + return 2 + + printer(f'Detected $IBIOSI$ at {bios_id_res}\n', padding) + + bios_id_hdr.struct_print(padding + 4) + + if rename: + input_parent = path_parent(input_file) + + input_suffix = path_suffixes(input_file)[0] + + input_adler32 = zlib.adler32(input_buffer) + + ID,Ext,Major,Type,Minor,Date,Time = bios_id_hdr.get_bios_id() + + output_name = f'{ID}_{Ext}_{Major}_{Type}{Minor}_{Date}_{Time}_{input_adler32:08X}{input_suffix}' + + output_file = os.path.join(input_parent, output_name) + + if not os.path.isfile(output_file): + os.replace(input_file, output_file) # Rename input file based on its EFI tag + + printer(f'Renamed input to {output_name}', padding) + + return 0 + +PAT_UEFIFIND = f'244942494F534924{"."*32}2E00{"."*12}2E00{"."*16}2E00{"."*12}2E00{"."*40}0000' + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + argparser.add_argument('-r', '--rename', help='rename EFI image based on its tag', action='store_true') + arguments = argparser.parse_args() + + rename = arguments.rename # Set EFI image tag renaming optional argument + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + if not is_apple_efi(input_file): + printer('Error: This is not an Apple EFI image!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + if apple_efi_identify(input_file, extract_path, padding, rename) == 0: + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/README.md b/README.md index 7bfac61..d8eaaa1 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) * [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) +* [**Apple EFI Image Identifier**](#apple-efi-image-identifier) * [**Award BIOS Module Extractor**](#award-bios-module-extractor) * [**Dell PFS/PKG Update Extractor**](#dell-pfspkg-update-extractor) * [**Fujitsu SFX BIOS Extractor**](#fujitsu-sfx-bios-extractor) @@ -103,7 +104,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 To run the utility, you must have the following 3rd party tools at the "external" project directory: -* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux) +* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (i.e. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux) * [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (when applicable), you must have the following 3rd party utility at the "external" project directory: @@ -142,6 +143,67 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() +## **Apple EFI Image Identifier** + +![]() + +#### **Description** + +Parses Apple EFI images and identifies them based on Intel's official $IBIOSI$ tag, which contains info such as Model, Version, Build, Date and Time. Optionally, the utility can rename the input Apple EFI image based on the retrieved $IBIOSI$ tag info, while also making sure to differentiate any EFI images with the same $IBIOSI$ tag (e.g. Production, Pre-Production) by appending a checksum of their data. + +#### **Usage** + +You can either Drag & Drop or manually enter Apple EFI image file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts +* -r or --rename : rename EFI image based on its tag + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. + +#### **Prerequisites** + +To run the utility, you must have the following 3rd party tools at the "external" project directory: + +* [UEFIFind](https://github.com/LongSoft/UEFITool/) (i.e. [UEFIFind.exe for Windows or UEFIFind for Linux](https://github.com/LongSoft/UEFITool/releases)) +* [UEFIExtract](https://github.com/LongSoft/UEFITool/) (i.e. [UEFIExtract.exe for Windows or UEFIExtract for Linux](https://github.com/LongSoft/UEFITool/releases)) + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.10.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Place prerequisites at the "external" project directory: + +> UEFIFind\ +> UEFIExtract + +4. Build/Freeze/Compile: + +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Apple_EFI_Identify.py + +You should find the final utility executable at "dist" folder + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + ## **Award BIOS Module Extractor** ![]() @@ -344,7 +406,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 To run the utility, you must have the following 3rd party tool at the "external" project directory: -* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux) +* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (i.e. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux) #### **Build/Freeze/Compile with PyInstaller** @@ -585,7 +647,7 @@ To run the utility, you must have the following 3rd party Python module installe Moreover, you must have the following 3rd party tool at the "external" project directory: -* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux) +* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (i.e. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux) #### **Build/Freeze/Compile with PyInstaller** @@ -647,7 +709,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 To run the utility, you must have the following 3rd party tool at the "external" project directory: -* [ToshibaComExtractor](https://github.com/LongSoft/ToshibaComExtractor) (e.g. comextract.exe for Windows or comextract for Linux) +* [ToshibaComExtractor](https://github.com/LongSoft/ToshibaComExtractor) (i.e. [comextract.exe for Windows or comextract for Linux](https://github.com/LongSoft/ToshibaComExtractor/releases)) #### **Build/Freeze/Compile with PyInstaller** diff --git a/common/externals.py b/common/externals.py index 28b7631..5aa17ae 100644 --- a/common/externals.py +++ b/common/externals.py @@ -5,6 +5,9 @@ Copyright (C) 2022 Plato Mavropoulos """ +from common.path_ops import project_root, safe_path +from common.system import get_os_ver + # https://github.com/allowitsme/big-tool by Dmitry Frolov # https://github.com/platomav/BGScriptTool by Plato Mavropoulos def get_bgs_tool(): @@ -14,3 +17,15 @@ def get_bgs_tool(): BigScript = None return BigScript + +# Get UEFIFind path +def get_uefifind_path(): + exec_name = f'UEFIFind{".exe" if get_os_ver()[1] else ""}' + + return safe_path(project_root(), ['external',exec_name]) + +# Get UEFIExtract path +def get_uefiextract_path(): + exec_name = f'UEFIExtract{".exe" if get_os_ver()[1] else ""}' + + return safe_path(project_root(), ['external',exec_name]) diff --git a/common/path_ops.py b/common/path_ops.py index 8381d56..8e093fe 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -136,6 +136,8 @@ def get_argparse_path(argparse_path): # Process input files (argparse object) def process_input_files(argparse_args, sys_argv=None): + input_files = [] + if sys_argv is None: sys_argv = [] @@ -146,7 +148,12 @@ def process_input_files(argparse_args, sys_argv=None): input_path_full = get_argparse_path(input_path_user) if input_path_user else '' input_files = get_path_files(input_path_full) else: - input_files = [file.name for file in argparse_args.files] + # Parse list of input files (i.e. argparse FileType objects) + for file_object in argparse_args.files: + # Store each argparse FileType object's name (i.e. path) + input_files.append(file_object.name) + # Close each argparse FileType object (i.e. allow input file changes) + file_object.close() # Set output fallback value for missing argparse Output and Input Path output_fallback = path_parent(input_files[0]) if input_files else None diff --git a/common/patterns.py b/common/patterns.py index 92ab4a6..018833f 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -9,6 +9,7 @@ import re PAT_AMI_PFAT = re.compile(br'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}@', re.DOTALL) +PAT_APPLE_EFI = re.compile(br'\$IBIOSI\$.{16}\x2E\x00.{6}\x2E\x00.{8}\x2E\x00.{6}\x2E\x00.{20}\x00{2}', re.DOTALL) PAT_AWARD_LZH = re.compile(br'-lh[04567]-') PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) From 389c30bb657096143ddc25450933a5ddc0983f55 Mon Sep 17 00:00:00 2001 From: platomav Date: Wed, 17 Aug 2022 01:26:01 +0300 Subject: [PATCH 43/52] Added Apple EFI IM4P Splitter v3.0_a2 --- Apple_EFI_Split.py | 160 +++++++++++++++++++++++++++++++++++++++++++++ README.md | 53 +++++++++++++++ common/patterns.py | 2 + 3 files changed, 215 insertions(+) create mode 100644 Apple_EFI_Split.py diff --git a/Apple_EFI_Split.py b/Apple_EFI_Split.py new file mode 100644 index 0000000..930d54e --- /dev/null +++ b/Apple_EFI_Split.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Apple EFI Split +Apple EFI IM4P Splitter +Copyright (C) 2018-2022 Plato Mavropoulos +""" + +TITLE = 'Apple EFI IM4P Splitter v3.0_a2' + +import os +import sys + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.path_ops import make_dirs, path_stem +from common.patterns import PAT_APPLE_IM4P, PAT_INTEL_IFD +from common.system import argparse_init, printer, script_init +from common.text_ops import file_to_bytes + +# Check if input is Apple EFI IM4P image +def is_apple_im4p(input_file): + input_buffer = file_to_bytes(input_file) + + is_im4p = PAT_APPLE_IM4P.search(input_buffer) + + is_ifd = PAT_INTEL_IFD.search(input_buffer) + + return bool(is_im4p and is_ifd) + +# Parse & Split Apple EFI IM4P image +def apple_im4p_split(input_file, output_path, padding=0): + input_buffer = file_to_bytes(input_file) + + extract_path = os.path.join(f'{output_path}_extracted') + + make_dirs(extract_path, delete=True) + + # Detect IM4P EFI pattern + im4p_match = PAT_APPLE_IM4P.search(input_buffer) + + # After IM4P mefi (0x15), multi EFI payloads have _MEFIBIN (0x100) but is difficult to RE w/o varying samples. + # However, _MEFIBIN is not required for splitting SPI images due to Intel Flash Descriptor Components Density. + + # IM4P mefi payload start offset + mefi_data_bgn = im4p_match.start() + input_buffer[im4p_match.start() - 0x1] + + # IM4P mefi payload size + mefi_data_len = int.from_bytes(input_buffer[im4p_match.end() + 0x9:im4p_match.end() + 0xD], 'big') + + # Check if mefi is followed by _MEFIBIN + mefibin_exist = input_buffer[mefi_data_bgn:mefi_data_bgn + 0x8] == b'_MEFIBIN' + + # Actual multi EFI payloads start after _MEFIBIN + efi_data_bgn = mefi_data_bgn + 0x100 if mefibin_exist else mefi_data_bgn + + # Actual multi EFI payloads size without _MEFIBIN + efi_data_len = mefi_data_len - 0x100 if mefibin_exist else mefi_data_len + + # Adjust input file buffer to actual multi EFI payloads data + input_buffer = input_buffer[efi_data_bgn:efi_data_bgn + efi_data_len] + + # Parse Intel Flash Descriptor pattern matches + for ifd in PAT_INTEL_IFD.finditer(input_buffer): + # Component Base Address from FD start (ICH8-ICH10 = 1, IBX = 2, CPT+ = 3) + ifd_flmap0_fcba = input_buffer[ifd.start() + 0x4] * 0x10 + + # I/O Controller Hub (ICH) + if ifd_flmap0_fcba == 0x10: + # At ICH, Flash Descriptor starts at 0x0 + ifd_bgn_substruct = 0x0 + + # 0xBC for [0xAC] + 0xFF * 16 sanity check + ifd_end_substruct = 0xBC + + # Platform Controller Hub (PCH) + else: + # At PCH, Flash Descriptor starts at 0x10 + ifd_bgn_substruct = 0x10 + + # 0xBC for [0xAC] + 0xFF * 16 sanity check + ifd_end_substruct = 0xBC + + # Actual Flash Descriptor Start Offset + ifd_match_start = ifd.start() - ifd_bgn_substruct + + # Actual Flash Descriptor End Offset + ifd_match_end = ifd.end() - ifd_end_substruct + + # Calculate Intel Flash Descriptor Flash Component Total Size + + # Component Count (00 = 1, 01 = 2) + ifd_flmap0_nc = ((int.from_bytes(input_buffer[ifd_match_end:ifd_match_end + 0x4], 'little') >> 8) & 3) + 1 + + # PCH/ICH Strap Length (ME 2-8 & TXE 0-2 & SPS 1-2 <= 0x12, ME 9+ & TXE 3+ & SPS 3+ >= 0x13) + ifd_flmap1_isl = input_buffer[ifd_match_end + 0x7] + + # Component Density Byte (ME 2-8 & TXE 0-2 & SPS 1-2 = 0:5, ME 9+ & TXE 3+ & SPS 3+ = 0:7) + ifd_comp_den = input_buffer[ifd_match_start + ifd_flmap0_fcba] + + # Component 1 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4) + ifd_comp_1_bitwise = 0xF if ifd_flmap1_isl >= 0x13 else 0x7 + + # Component 2 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4) + ifd_comp_2_bitwise = 0x4 if ifd_flmap1_isl >= 0x13 else 0x3 + + # Component 1 Density (FCBA > C0DEN) + ifd_comp_all_size = IFD_COMP_LEN[ifd_comp_den & ifd_comp_1_bitwise] + + # Component 2 Density (FCBA > C1DEN) + if ifd_flmap0_nc == 2: + ifd_comp_all_size += IFD_COMP_LEN[ifd_comp_den >> ifd_comp_2_bitwise] + + ifd_data_bgn = ifd_match_start + ifd_data_end = ifd_data_bgn + ifd_comp_all_size + ifd_data_txt = f'0x{ifd_data_bgn:07X}-0x{ifd_data_end:07X}' + + output_data = input_buffer[ifd_data_bgn:ifd_data_end] + + output_name = path_stem(input_file) if os.path.isfile(input_file) else 'Part' + + output_path = os.path.join(extract_path, f'{output_name}_[{ifd_data_txt}].fd') + + with open(output_path, 'wb') as output_image: + output_image.write(output_data) + + printer(f'Split Apple EFI image at {ifd_data_txt}!', padding) + +# Intel Flash Descriptor Component Sizes (4MB, 8MB, 16MB and 32MB) +IFD_COMP_LEN = {3: 0x400000, 4: 0x800000, 5: 0x1000000, 6: 0x2000000} + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + if not is_apple_im4p(input_file): + printer('Error: This is not an Apple EFI IM4P image!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + apple_im4p_split(input_file, extract_path, padding) + + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/README.md b/README.md index d8eaaa1..897f3ad 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) * [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) +* [**Apple EFI IM4P Splitter**](#apple-efi-im4p-splitter) * [**Apple EFI Image Identifier**](#apple-efi-image-identifier) * [**Award BIOS Module Extractor**](#award-bios-module-extractor) * [**Dell PFS/PKG Update Extractor**](#dell-pfspkg-update-extractor) @@ -143,6 +144,58 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() +## **Apple EFI IM4P Splitter** + +![]() + +#### **Description** + +Parses Apple IM4P multi-EFI files and splits all detected EFI firmware into separate Intel SPI/BIOS images. + +#### **Usage** + +You can either Drag & Drop or manually enter Apple EFI IM4P file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. + +#### **Prerequisites** + +To run the utility, you do not need any prerequisites. + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.10.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Build/Freeze/Compile: + +> pyinstaller --noupx --onefile \\/Apple_EFI_Split.py + +At dist folder you should find the final utility executable + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + ## **Apple EFI Image Identifier** ![]() diff --git a/common/patterns.py b/common/patterns.py index 018833f..27405e8 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -10,6 +10,7 @@ import re PAT_AMI_PFAT = re.compile(br'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}@', re.DOTALL) PAT_APPLE_EFI = re.compile(br'\$IBIOSI\$.{16}\x2E\x00.{6}\x2E\x00.{8}\x2E\x00.{6}\x2E\x00.{20}\x00{2}', re.DOTALL) +PAT_APPLE_IM4P = re.compile(br'\x16\x04IM4P\x16\x04mefi') PAT_AWARD_LZH = re.compile(br'-lh[04567]-') PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) @@ -18,6 +19,7 @@ PAT_FUJITSU_SFX = re.compile(br'FjSfxBinay\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4} PAT_INSYDE_IFL = re.compile(br'\$_IFLASH') PAT_INSYDE_SFX = re.compile(br'\x0D\x0A;!@InstallEnd@!\x0D\x0A(7z\xBC\xAF\x27|\x6E\xF4\x79\x5F\x4E)') PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) +PAT_INTEL_IFD = re.compile(br'\x5A\xA5\xF0\x0F.{172}\xFF{16}', re.DOTALL) PAT_MICROSOFT_CAB = re.compile(br'MSCF\x00{4}') PAT_MICROSOFT_MZ = re.compile(br'MZ') PAT_MICROSOFT_PE = re.compile(br'PE\x00{2}') From 0e170334c642c829befa1a8292fe9c647de1a892 Mon Sep 17 00:00:00 2001 From: platomav Date: Sun, 28 Aug 2022 20:02:55 +0300 Subject: [PATCH 44/52] Added Apple EFI Package Extractor v2.0_a4 Added Apple EFI PBZX Extractor v1.0_a4 Updated Apple EFI Image Identifier v2.0_a4 Updated Apple EFI IM4P Splitter v3.0_a4 Updated Insyde iFlash/iFdPacker Extractor v2.0_a10 Improved 7-Zip parameter control --- Apple_EFI_Identify.py => Apple_EFI_ID.py | 8 +- Apple_EFI_Split.py => Apple_EFI_IM4P.py | 22 ++- Apple_EFI_PBZX.py | 143 +++++++++++++++++++ Apple_EFI_PKG.py | 172 +++++++++++++++++++++++ Insyde_IFD_Extract.py | 6 +- README.md | 124 +++++++++++++++- common/comp_szip.py | 14 +- common/path_ops.py | 11 ++ common/patterns.py | 2 + 9 files changed, 482 insertions(+), 20 deletions(-) rename Apple_EFI_Identify.py => Apple_EFI_ID.py (97%) rename Apple_EFI_Split.py => Apple_EFI_IM4P.py (91%) create mode 100644 Apple_EFI_PBZX.py create mode 100644 Apple_EFI_PKG.py diff --git a/Apple_EFI_Identify.py b/Apple_EFI_ID.py similarity index 97% rename from Apple_EFI_Identify.py rename to Apple_EFI_ID.py index d203d5b..1897560 100644 --- a/Apple_EFI_Identify.py +++ b/Apple_EFI_ID.py @@ -2,12 +2,12 @@ #coding=utf-8 """ -Apple EFI Identify +Apple EFI ID Apple EFI Image Identifier Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Apple EFI Image Identifier v2.0_a3' +TITLE = 'Apple EFI Image Identifier v2.0_a4' import os import sys @@ -143,7 +143,7 @@ def apple_efi_identify(input_file, output_path, padding=0, rename=False): if rename: input_parent = path_parent(input_file) - input_suffix = path_suffixes(input_file)[0] + input_suffix = path_suffixes(input_file)[-1] input_adler32 = zlib.adler32(input_buffer) @@ -156,7 +156,7 @@ def apple_efi_identify(input_file, output_path, padding=0, rename=False): if not os.path.isfile(output_file): os.replace(input_file, output_file) # Rename input file based on its EFI tag - printer(f'Renamed input to {output_name}', padding) + printer(f'Renamed to {output_name}', padding) return 0 diff --git a/Apple_EFI_Split.py b/Apple_EFI_IM4P.py similarity index 91% rename from Apple_EFI_Split.py rename to Apple_EFI_IM4P.py index 930d54e..fe2bd3e 100644 --- a/Apple_EFI_Split.py +++ b/Apple_EFI_IM4P.py @@ -2,12 +2,12 @@ #coding=utf-8 """ -Apple EFI Split +Apple EFI IM4P Apple EFI IM4P Splitter Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Apple EFI IM4P Splitter v3.0_a2' +TITLE = 'Apple EFI IM4P Splitter v3.0_a4' import os import sys @@ -32,6 +32,8 @@ def is_apple_im4p(input_file): # Parse & Split Apple EFI IM4P image def apple_im4p_split(input_file, output_path, padding=0): + exit_codes = [] + input_buffer = file_to_bytes(input_file) extract_path = os.path.join(f'{output_path}_extracted') @@ -48,7 +50,7 @@ def apple_im4p_split(input_file, output_path, padding=0): mefi_data_bgn = im4p_match.start() + input_buffer[im4p_match.start() - 0x1] # IM4P mefi payload size - mefi_data_len = int.from_bytes(input_buffer[im4p_match.end() + 0x9:im4p_match.end() + 0xD], 'big') + mefi_data_len = int.from_bytes(input_buffer[im4p_match.end() + 0x5:im4p_match.end() + 0x9], 'big') # Check if mefi is followed by _MEFIBIN mefibin_exist = input_buffer[mefi_data_bgn:mefi_data_bgn + 0x8] == b'_MEFIBIN' @@ -119,6 +121,8 @@ def apple_im4p_split(input_file, output_path, padding=0): output_data = input_buffer[ifd_data_bgn:ifd_data_end] + output_size = len(output_data) + output_name = path_stem(input_file) if os.path.isfile(input_file) else 'Part' output_path = os.path.join(extract_path, f'{output_name}_[{ifd_data_txt}].fd') @@ -127,6 +131,13 @@ def apple_im4p_split(input_file, output_path, padding=0): output_image.write(output_data) printer(f'Split Apple EFI image at {ifd_data_txt}!', padding) + + if output_size != ifd_comp_all_size: + printer(f'Error: Bad image size 0x{output_size:07X}, expected 0x{ifd_comp_all_size:07X}!', padding + 4) + + exit_codes.append(1) + + return sum(exit_codes) # Intel Flash Descriptor Component Sizes (4MB, 8MB, 16MB and 32MB) IFD_COMP_LEN = {3: 0x400000, 4: 0x800000, 5: 0x1000000, 6: 0x2000000} @@ -151,9 +162,8 @@ if __name__ == '__main__': extract_path = os.path.join(output_path, input_name) - apple_im4p_split(input_file, extract_path, padding) - - exit_code -= 1 + if apple_im4p_split(input_file, extract_path, padding) == 0: + exit_code -= 1 printer('Done!', pause=True) diff --git a/Apple_EFI_PBZX.py b/Apple_EFI_PBZX.py new file mode 100644 index 0000000..05e5eae --- /dev/null +++ b/Apple_EFI_PBZX.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Apple PBZX Extract +Apple EFI PBZX Extractor +Copyright (C) 2021-2022 Plato Mavropoulos +""" + +TITLE = 'Apple EFI PBZX Extractor v1.0_a4' + +import os +import sys +import lzma +import ctypes + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.comp_szip import is_szip_supported, szip_decompress +from common.path_ops import make_dirs, path_stem +from common.patterns import PAT_APPLE_PBZX +from common.struct_ops import get_struct, uint32_t +from common.system import argparse_init, printer, script_init +from common.text_ops import file_to_bytes + +class PbzxChunk(ctypes.BigEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Reserved0', uint32_t), # 0x00 + ('InitSize', uint32_t), # 0x04 + ('Reserved1', uint32_t), # 0x08 + ('CompSize', uint32_t), # 0x0C + # 0x10 + ] + + def struct_print(self, p): + printer(['Reserved 0 :', f'0x{self.Reserved0:X}'], p, False) + printer(['Initial Size :', f'0x{self.InitSize:X}'], p, False) + printer(['Reserved 1 :', f'0x{self.Reserved1:X}'], p, False) + printer(['Compressed Size:', f'0x{self.CompSize:X}'], p, False) + +# Check if input is Apple PBZX image +def is_apple_pbzx(input_file): + input_buffer = file_to_bytes(input_file) + + return bool(PAT_APPLE_PBZX.search(input_buffer[:0x4])) + +# Parse & Extract Apple PBZX image +def apple_pbzx_extract(input_file, output_path, padding=0): + input_buffer = file_to_bytes(input_file) + + extract_path = os.path.join(f'{output_path}_extracted') + + make_dirs(extract_path, delete=True) + + cpio_bin = b'' # Initialize PBZX > CPIO Buffer + cpio_len = 0x0 # Initialize PBZX > CPIO Length + + chunk_off = 0xC # First PBZX Chunk starts at 0xC + while chunk_off < len(input_buffer): + chunk_hdr = get_struct(input_buffer, chunk_off, PbzxChunk) + + printer(f'PBZX Chunk at 0x{chunk_off:08X}\n', padding) + + chunk_hdr.struct_print(padding + 4) + + # PBZX Chunk data starts after its Header + comp_bgn = chunk_off + PBZX_CHUNK_HDR_LEN + + # To avoid a potential infinite loop, double-check Compressed Size + comp_end = comp_bgn + max(chunk_hdr.CompSize, PBZX_CHUNK_HDR_LEN) + + comp_bin = input_buffer[comp_bgn:comp_end] + + try: + # Attempt XZ decompression, if applicable to Chunk data + cpio_bin += lzma.LZMADecompressor().decompress(comp_bin) + + printer('Successful LZMA decompression!', padding + 8) + except: + # Otherwise, Chunk data is not compressed + cpio_bin += comp_bin + + # Final CPIO size should match the sum of all Chunks > Initial Size + cpio_len += chunk_hdr.InitSize + + # Next Chunk starts at the end of current Chunk's data + chunk_off = comp_end + + # Check that CPIO size is valid based on all Chunks > Initial Size + if cpio_len != len(cpio_bin): + printer('Error: Unexpected CPIO archive size!', padding) + + return 1 + + cpio_name = path_stem(input_file) if os.path.isfile(input_file) else 'Payload' + + cpio_path = os.path.join(extract_path, f'{cpio_name}.cpio') + + with open(cpio_path, 'wb') as cpio_object: + cpio_object.write(cpio_bin) + + # Decompress PBZX > CPIO archive with 7-Zip + if is_szip_supported(cpio_path, padding, args=['-tCPIO'], check=True): + if szip_decompress(cpio_path, extract_path, 'CPIO', padding, args=['-tCPIO'], check=True) == 0: + os.remove(cpio_path) # Successful extraction, delete PBZX > CPIO archive + else: + return 3 + else: + return 2 + + return 0 + +# Get common ctypes Structure Sizes +PBZX_CHUNK_HDR_LEN = ctypes.sizeof(PbzxChunk) + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + if not is_apple_pbzx(input_file): + printer('Error: This is not an Apple PBZX image!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + if apple_pbzx_extract(input_file, extract_path, padding) == 0: + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/Apple_EFI_PKG.py b/Apple_EFI_PKG.py new file mode 100644 index 0000000..636f99e --- /dev/null +++ b/Apple_EFI_PKG.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Apple EFI PKG +Apple EFI Package Extractor +Copyright (C) 2019-2022 Plato Mavropoulos +""" + +TITLE = 'Apple EFI Package Extractor v2.0_a4' + +import os +import sys + +# Stop __pycache__ generation +sys.dont_write_bytecode = True + +from common.comp_szip import is_szip_supported, szip_decompress +from common.path_ops import copy_file, del_dirs, get_path_files, make_dirs, path_name, path_parent +from common.patterns import PAT_APPLE_PKG +from common.system import argparse_init, printer, script_init +from common.text_ops import file_to_bytes + +from Apple_EFI_ID import apple_efi_identify, is_apple_efi +from Apple_EFI_IM4P import apple_im4p_split, is_apple_im4p +from Apple_EFI_PBZX import apple_pbzx_extract, is_apple_pbzx + +# Check if input is Apple EFI PKG package +def is_apple_pkg(input_file): + input_buffer = file_to_bytes(input_file) + + return bool(PAT_APPLE_PKG.search(input_buffer[:0x4])) + +# Split Apple EFI image (if applicable) and Rename +def efi_split_rename(in_file, out_path, padding=0): + exit_codes = [] + + working_dir = f'{in_file}_extracted' + + if is_apple_im4p(in_file): + printer(f'Splitting IM4P via {is_apple_im4p.__module__}...', padding) + im4p_exit = apple_im4p_split(in_file, in_file, padding + 4) + exit_codes.append(im4p_exit) + else: + make_dirs(working_dir, delete=True) + copy_file(in_file, working_dir, True) + + for efi_file in get_path_files(working_dir): + if is_apple_efi(efi_file): + printer(f'Renaming EFI via {is_apple_efi.__module__}...', padding) + name_exit = apple_efi_identify(efi_file, efi_file, padding + 4, True) + exit_codes.append(name_exit) + + for named_file in get_path_files(working_dir): + copy_file(named_file, out_path, True) + + del_dirs(working_dir) + + return sum(exit_codes) + +# Parse & Extract Apple EFI PKG packages +def apple_pkg_extract(input_file, output_path, padding=0): + if not os.path.isfile(input_file): + printer('Error: Could not find input file path!', padding) + return 1 + + extract_path = os.path.join(f'{output_path}_extracted') + + make_dirs(extract_path, delete=True) + + xar_path = os.path.join(extract_path, 'xar') + + # Decompress PKG > XAR archive with 7-Zip + if is_szip_supported(input_file, padding, args=['-tXAR'], check=True): + if szip_decompress(input_file, xar_path, 'XAR', padding, args=['-tXAR'], check=True) != 0: + return 3 + else: + return 2 + + for xar_file in get_path_files(xar_path): + if path_name(xar_file) == 'Payload': + pbzx_module = is_apple_pbzx.__module__ + if is_apple_pbzx(xar_file): + printer(f'Extracting PBZX via {pbzx_module}...', padding + 4) + if apple_pbzx_extract(xar_file, xar_file, padding + 8) == 0: + printer(f'Succesfull PBZX extraction via {pbzx_module}!', padding + 4) + for pbzx_file in get_path_files(f'{xar_file}_extracted'): + if path_name(pbzx_file) == 'UpdateBundle.zip': + if is_szip_supported(pbzx_file, padding + 8, args=['-tZIP'], check=True): + zip_path = f'{pbzx_file}_extracted' + if szip_decompress(pbzx_file, zip_path, 'ZIP', padding + 8, args=['-tZIP'], check=True) == 0: + for zip_file in get_path_files(zip_path): + if path_name(path_parent(zip_file)) == 'MacEFI': + printer(path_name(zip_file), padding + 12) + if efi_split_rename(zip_file, extract_path, padding + 16) != 0: + printer(f'Error: Could not split and rename {path_name(zip_file)}!', padding) + return 10 + else: + return 9 + else: + return 8 + break # ZIP found, stop + else: + printer('Error: Could not find "UpdateBundle.zip" file!', padding) + return 7 + else: + printer(f'Error: Failed to extract PBZX file via {pbzx_module}!', padding) + return 6 + else: + printer(f'Error: Failed to detect file as PBZX via {pbzx_module}!', padding) + return 5 + + break # Payload found, stop searching + + if path_name(xar_file) == 'Scripts': + if is_szip_supported(xar_file, padding + 4, args=['-tGZIP'], check=True): + gzip_path = f'{xar_file}_extracted' + if szip_decompress(xar_file, gzip_path, 'GZIP', padding + 4, args=['-tGZIP'], check=True) == 0: + for gzip_file in get_path_files(gzip_path): + if is_szip_supported(gzip_file, padding + 8, args=['-tCPIO'], check=True): + cpio_path = f'{gzip_file}_extracted' + if szip_decompress(gzip_file, cpio_path, 'CPIO', padding + 8, args=['-tCPIO'], check=True) == 0: + for cpio_file in get_path_files(cpio_path): + if path_name(path_parent(cpio_file)) == 'EFIPayloads': + printer(path_name(cpio_file), padding + 12) + if efi_split_rename(cpio_file, extract_path, padding + 16) != 0: + printer(f'Error: Could not split and rename {path_name(cpio_file)}!', padding) + return 15 + else: + return 14 + else: + return 13 + else: + return 12 + else: + return 11 + + break # Scripts found, stop searching + else: + printer('Error: Could not find "Payload" or "Scripts" file!', padding) + return 4 + + del_dirs(xar_path) # Delete temporary/working XAR folder + + return 0 + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + if not is_apple_pkg(input_file): + printer('Error: This is not an Apple EFI PKG package!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + if apple_pkg_extract(input_file, extract_path, padding) == 0: + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/Insyde_IFD_Extract.py b/Insyde_IFD_Extract.py index ba9c28e..c68d4d4 100644 --- a/Insyde_IFD_Extract.py +++ b/Insyde_IFD_Extract.py @@ -7,7 +7,7 @@ Insyde iFlash/iFdPacker Extractor Copyright (C) 2022 Plato Mavropoulos """ -TITLE = 'Insyde iFlash/iFdPacker Extractor v2.0_a9' +TITLE = 'Insyde iFlash/iFdPacker Extractor v2.0_a10' import os import sys @@ -175,9 +175,9 @@ def insyde_packer_extract(input_buffer, extract_path, padding=0): with open(sfx_path, 'wb') as sfx_file: sfx_file.write(sfx_buffer) - if is_szip_supported(sfx_path, padding + 8, password=INS_SFX_PWD, check=True): + if is_szip_supported(sfx_path, padding + 8, args=[f'-p{INS_SFX_PWD}'], check=True): if szip_decompress(sfx_path, extract_path, 'Insyde iFdPacker > 7-Zip SFX', - padding + 8, password=INS_SFX_PWD, check=True) == 0: + padding + 8, args=[f'-p{INS_SFX_PWD}'], check=True) == 0: os.remove(sfx_path) else: return 125 diff --git a/README.md b/README.md index 897f3ad..0df5ebb 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ * [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) * [**Apple EFI IM4P Splitter**](#apple-efi-im4p-splitter) * [**Apple EFI Image Identifier**](#apple-efi-image-identifier) +* [**Apple EFI Package Extractor**](#apple-efi-package-extractor) +* [**Apple EFI PBZX Extractor**](#apple-efi-pbzx-extractor) * [**Award BIOS Module Extractor**](#award-bios-module-extractor) * [**Dell PFS/PKG Update Extractor**](#dell-pfspkg-update-extractor) * [**Fujitsu SFX BIOS Extractor**](#fujitsu-sfx-bios-extractor) @@ -150,7 +152,7 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con #### **Description** -Parses Apple IM4P multi-EFI files and splits all detected EFI firmware into separate Intel SPI/BIOS images. +Parses Apple IM4P multi-EFI files and splits all detected EFI firmware into separate Intel SPI/BIOS images. The output comprises only final firmware components and utilities which are directly usable by end users. #### **Usage** @@ -184,7 +186,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 3. Build/Freeze/Compile: -> pyinstaller --noupx --onefile \\/Apple_EFI_Split.py +> pyinstaller --noupx --onefile \\/Apple_EFI_IM4P.py At dist folder you should find the final utility executable @@ -245,7 +247,123 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 4. Build/Freeze/Compile: -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Apple_EFI_Identify.py +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Apple_EFI_ID.py + +You should find the final utility executable at "dist" folder + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + +## **Apple EFI Package Extractor** + +![]() + +#### **Description** + +Parses Apple EFI PKG firmware packages (i.e. FirmwareUpdate.pkg, BridgeOSUpdateCustomer.pkg), extracts their EFI images, splits those in IM4P format and identifies/renames the final Intel SPI/BIOS images accordingly. The output comprises only final firmware components which are directly usable by end users. + +#### **Usage** + +You can either Drag & Drop or manually enter Apple EFI PKG package file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. + +#### **Prerequisites** + +To run the utility, you must have the following 3rd party tools at the "external" project directory: + +* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.10.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Place prerequisites at the "external" project directory: + +> 7-Zip Console + +4. Build/Freeze/Compile: + +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Apple_EFI_PKG.py + +You should find the final utility executable at "dist" folder + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + +## **Apple EFI PBZX Extractor** + +![]() + +#### **Description** + +Parses Apple EFI PBZX images, re-assembles their CPIO payload and extracts its firmware components (e.g. IM4P, EFI, Utilities, Scripts etc). It supports CPIO re-assembly from both Raw and XZ compressed PBZX Chunks. The output comprises only final firmware components and utilities which are directly usable by end users. + +#### **Usage** + +You can either Drag & Drop or manually enter Apple EFI PBZX image file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. + +#### **Prerequisites** + +To run the utility, you must have the following 3rd party tools at the "external" project directory: + +* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.10.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Place prerequisites at the "external" project directory: + +> 7-Zip Console + +4. Build/Freeze/Compile: + +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Apple_EFI_PBZX.py You should find the final utility executable at "dist" folder diff --git a/common/comp_szip.py b/common/comp_szip.py index 4a3d26b..3db03a6 100644 --- a/common/comp_szip.py +++ b/common/comp_szip.py @@ -23,9 +23,12 @@ def check_bad_exit_code(exit_code): raise Exception(f'BAD_EXIT_CODE_{exit_code}') # Check if file is 7-Zip supported -def is_szip_supported(in_path, padding=0, password='', check=False, silent=False): +def is_szip_supported(in_path, padding=0, args=None, check=False, silent=False): try: - szip_c = [get_szip_path(), 't', in_path, f'-p{password}', '-bso0', '-bse0', '-bsp0'] + if args is None: + args = [] + + szip_c = [get_szip_path(), 't', in_path, *args, '-bso0', '-bse0', '-bsp0'] szip_t = subprocess.run(szip_c, check=False) @@ -40,12 +43,15 @@ def is_szip_supported(in_path, padding=0, password='', check=False, silent=False return True # Archive decompression via 7-Zip -def szip_decompress(in_path, out_path, in_name, padding=0, password='', check=False, silent=False): +def szip_decompress(in_path, out_path, in_name, padding=0, args=None, check=False, silent=False): if not in_name: in_name = 'archive' try: - szip_c = [get_szip_path(), 'x', f'-p{password}', '-aou', '-bso0', '-bse0', '-bsp0', f'-o{out_path}', in_path] + if args is None: + args = [] + + szip_c = [get_szip_path(), 'x', *args, '-aou', '-bso0', '-bse0', '-bsp0', f'-o{out_path}', in_path] szip_x = subprocess.run(szip_c, check=False) diff --git a/common/path_ops.py b/common/path_ops.py index 8e093fe..e2420e6 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -71,6 +71,10 @@ def agnostic_path(in_path): def path_parent(in_path): return Path(in_path).parent.absolute() +# Get final path component, with suffix +def path_name(in_path): + return PurePath(in_path).name + # Get final path component, w/o suffix def path_stem(in_path): return PurePath(in_path).stem @@ -95,6 +99,13 @@ def del_dirs(in_path): if Path(in_path).is_dir(): shutil.rmtree(in_path, onerror=clear_readonly) +# Copy file to path with or w/o metadata +def copy_file(in_path, out_path, meta=False): + if meta: + shutil.copy2(in_path, out_path) + else: + shutil.copy(in_path, out_path) + # Clear read-only file attribute (on shutil.rmtree error) def clear_readonly(in_func, in_path, _): os.chmod(in_path, stat.S_IWRITE) diff --git a/common/patterns.py b/common/patterns.py index 27405e8..ecdde39 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -11,6 +11,8 @@ PAT_AMI_PFAT = re.compile(br'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.D PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}@', re.DOTALL) PAT_APPLE_EFI = re.compile(br'\$IBIOSI\$.{16}\x2E\x00.{6}\x2E\x00.{8}\x2E\x00.{6}\x2E\x00.{20}\x00{2}', re.DOTALL) PAT_APPLE_IM4P = re.compile(br'\x16\x04IM4P\x16\x04mefi') +PAT_APPLE_PBZX = re.compile(br'pbzx') +PAT_APPLE_PKG = re.compile(br'xar!') PAT_AWARD_LZH = re.compile(br'-lh[04567]-') PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) From 0c6c35b35496cad09c2148b2926c245eaf7cbdc7 Mon Sep 17 00:00:00 2001 From: platomav Date: Wed, 31 Aug 2022 01:11:25 +0300 Subject: [PATCH 45/52] Updated Dell PFS/PKG Update Extractor v6.0_a12 Added support for Dell ThinOS PKG with multiple PFS --- Dell_PFS_Extract.py | 66 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index 18650a3..59c91f2 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Dell PFS/PKG Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Dell PFS/PKG Update Extractor v6.0_a11' +TITLE = 'Dell PFS/PKG Update Extractor v6.0_a12' import os import io @@ -21,7 +21,8 @@ import contextlib sys.dont_write_bytecode = True from common.checksums import get_chk_8_xor -from common.path_ops import make_dirs, path_stem, safe_name +from common.comp_szip import is_szip_supported, szip_decompress +from common.path_ops import del_dirs, get_path_files, make_dirs, path_name, path_parent, path_stem, safe_name from common.patterns import PAT_DELL_FTR, PAT_DELL_HDR, PAT_DELL_PKG from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t, uint64_t from common.system import argparse_init, printer, script_init @@ -225,9 +226,12 @@ def is_dell_pfs(in_file): return bool(is_pkg or is_hdr and is_ftr) # Extract Dell ThinOS PKG 7zXZ -def thinos_pkg_extract(in_file): +def thinos_pkg_extract(in_file, output_path): in_buffer = file_to_bytes(in_file) + # Initialize PFS results (Name: Buffer) + pfs_results = {} + # Search input image for ThinOS PKG 7zXZ header thinos_pkg_match = PAT_DELL_PKG.search(in_buffer) @@ -238,9 +242,33 @@ def thinos_pkg_extract(in_file): # Check if the compressed 7zXZ stream is complete if len(lzma_bin_dat) != lzma_len_int: - return in_buffer + return pfs_results - return lzma.decompress(lzma_bin_dat) + working_path = os.path.join(output_path, 'THINOS_PKG_TEMP') + + make_dirs(working_path, delete=True) + + pkg_tar_path = os.path.join(working_path, 'THINOS_PKG.TAR') + + with open(pkg_tar_path, 'wb') as pkg_payload: + pkg_payload.write(lzma.decompress(lzma_bin_dat)) + + if is_szip_supported(pkg_tar_path, 0, args=['-tTAR'], check=True, silent=True): + if szip_decompress(pkg_tar_path, working_path, 'TAR', 0, args=['-tTAR'], check=True, silent=True) == 0: + os.remove(pkg_tar_path) + else: + return pfs_results + else: + return pfs_results + + for pkg_file in get_path_files(working_path): + if is_pfs_hdr(pkg_file): + pfs_name = path_name(path_parent(pkg_file)) + pfs_results.update({pfs_name: file_to_bytes(pkg_file)}) + + del_dirs(working_path) + + return pfs_results # Get PFS ZLIB Section Offsets def get_section_offsets(buffer): @@ -275,7 +303,7 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p section_name = {0xAA:'Firmware', 0xBB:'Utilities'}.get(section_type, f'Unknown ({section_type:02X})') # Show extraction complete message for each main PFS ZLIB Section - printer(f'Extracting Dell PFS {pfs_index} >{pfs_name} > {section_name}', padding) + printer(f'Extracting Dell PFS {pfs_index} > {pfs_name} > {section_name}', padding) # Set PFS ZLIB Section extraction sub-directory path section_path = os.path.join(output_path, safe_name(section_name)) @@ -565,10 +593,10 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, s # its PFS Information should contain their names (CombineBiosNameX). Since the main/first # full PFS structure has count/index 1, the rest start at 2+ and thus, their PFS Information # names can be retrieved in order by subtracting 2 from the main/first PFS Information values - sub_pfs_name = f' {info_all[pfs_count - 2][1]} v{info_all[pfs_count - 2][2]}' if info_all else ' UNKNOWN' + sub_pfs_name = f'{info_all[pfs_count - 2][1]} v{info_all[pfs_count - 2][2]}' if info_all else ' UNKNOWN' # Set the sub-PFS output path (create sub-folders for each sub-PFS and its ZLIB sections) - sub_pfs_path = os.path.join(output_path, str(pfs_count) + safe_name(sub_pfs_name)) + sub_pfs_path = os.path.join(output_path, f'{pfs_count} {safe_name(sub_pfs_name)}') # Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count) pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4, structure, advanced) @@ -639,7 +667,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, s # Write/Extract PFS Entry files for file in write_files: - full_name = f'{pfs_index}{pfs_name} -- {file_index} {file_name} v{file_version}' # Full PFS Entry Name + full_name = f'{pfs_index} {pfs_name} -- {file_index} {file_name} v{file_version}' # Full PFS Entry Name pfs_file_write(file[0], file[1], file_type, full_name, output_path, pfs_padd, structure, advanced) # Get PFS Footer Data after PFS Header Payload @@ -994,15 +1022,21 @@ if __name__ == '__main__': extract_path = os.path.join(output_path, f'{input_name}_extracted') - extract_name = ' ' + path_stem(input_file) + is_dell_pkg = is_pfs_pkg(input_buffer) - if is_pfs_pkg(input_buffer): - input_buffer = thinos_pkg_extract(input_buffer) + if is_dell_pkg: + pfs_results = thinos_pkg_extract(input_buffer, extract_path) + else: + pfs_results = {path_stem(input_file): input_buffer} - # Parse each PFS ZLIB Section - for zlib_offset in get_section_offsets(input_buffer): - # Call the PFS ZLIB Section Parser function - pfs_section_parse(input_buffer, zlib_offset, extract_path, extract_name, 1, 1, False, padding, structure, advanced) + # Parse each Dell PFS image contained in the input file + for pfs_index,(pfs_name,pfs_buffer) in enumerate(pfs_results.items(), start=1): + # At ThinOS PKG packages, multiple PFS images may be included in separate model-named folders + pfs_path = os.path.join(extract_path, f'{pfs_index} {pfs_name}') if is_dell_pkg else extract_path + # Parse each PFS ZLIB section + for zlib_offset in get_section_offsets(pfs_buffer): + # Call the PFS ZLIB section parser function + pfs_section_parse(pfs_buffer, zlib_offset, pfs_path, pfs_name, pfs_index, 1, False, padding, structure, advanced) exit_code -= 1 From 5f364f4759020b72d8126525eb0f76d27d684735 Mon Sep 17 00:00:00 2001 From: platomav Date: Fri, 2 Sep 2022 00:37:10 +0300 Subject: [PATCH 46/52] Updated AMI UCP Update Extractor v2.0_a19 --- AMI_UCP_Extract.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 0c9d03d..7aef35e 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'AMI UCP Update Extractor v2.0_a18' +TITLE = 'AMI UCP Update Extractor v2.0_a19' import os import re @@ -470,6 +470,8 @@ UAF_TAG_DICT = { '@BME' : ['BiosMgmt.efi', 'BiosMgmt.efi', ''], '@CKV' : ['Check_Version.txt', 'Check Version', 'Text'], '@CMD' : ['AFU_Command.txt', 'AMI AFU Command', 'Text'], + '@CML' : ['CMOSD4.txt', 'CMOS Item Number-Value (MSI)', 'Text'], + '@CMS' : ['CMOSD4.exe', 'Get or Set CMOS Item (MSI)', ''], '@CPM' : ['AC_Message.txt', 'Confirm Power Message', ''], '@DCT' : ['DevCon32.exe', 'Device Console WIN32', ''], '@DCX' : ['DevCon64.exe', 'Device Console WIN64', ''], From 6de50c422f3f2600662b78b3bd8e1831cb930ec4 Mon Sep 17 00:00:00 2001 From: platomav Date: Mon, 12 Sep 2022 23:09:12 +0300 Subject: [PATCH 47/52] Created common template for executing all utilities Unified extracted output directory naming logic Multiple code fixes, refactors and improvements --- .gitignore | 5 + AMI_PFAT_Extract.py | 49 +---- AMI_UCP_Extract.py | 52 +---- Apple_EFI_ID.py | 52 ++--- Apple_EFI_IM4P.py | 35 +--- Apple_EFI_PBZX.py | 37 +--- Apple_EFI_PKG.py | 52 ++--- Award_BIOS_Extract.py | 43 +--- Dell_PFS_Extract.py | 153 ++++++-------- Fujitsu_SFX_Extract.py | 39 +--- Fujitsu_UPC_Extract.py | 37 +--- Insyde_IFD_Extract.py | 43 +--- Panasonic_BIOS_Extract.py | 47 +---- Phoenix_TDK_Extract.py | 45 +--- Portwell_EFI_Extract.py | 41 +--- README.md | 419 -------------------------------------- Toshiba_COM_Extract.py | 54 ++--- VAIO_Package_Extract.py | 45 +--- common/comp_efi.py | 2 +- common/comp_szip.py | 4 +- common/externals.py | 15 +- common/path_ops.py | 57 +----- common/pe_ops.py | 4 +- common/system.py | 60 ------ common/templates.py | 152 ++++++++++++++ external/requirements.txt | 4 +- 26 files changed, 377 insertions(+), 1169 deletions(-) create mode 100644 .gitignore create mode 100644 common/templates.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..238b83e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Skip all external files +external/* + +# Keep external > requirements file +!external/requirements.txt diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py index bb9fdae..026b74a 100644 --- a/AMI_PFAT_Extract.py +++ b/AMI_PFAT_Extract.py @@ -7,7 +7,7 @@ AMI BIOS Guard Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'AMI BIOS Guard Extractor v4.0_a11' +TITLE = 'AMI BIOS Guard Extractor v4.0_a12' import os import re @@ -19,10 +19,11 @@ sys.dont_write_bytecode = True from common.externals import get_bgs_tool from common.num_ops import get_ordinal -from common.path_ops import make_dirs, safe_name +from common.path_ops import make_dirs, safe_name, get_extract_path, extract_suffix from common.patterns import PAT_AMI_PFAT from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes class AmiBiosGuardHeader(ctypes.LittleEndianStructure): @@ -147,7 +148,7 @@ def parse_bg_script(script_data, padding=0): is_opcode_div = len(script_data) % 8 == 0 if not is_opcode_div: - printer('Error: Script not divisible by OpCode length!', padding, False) + printer('Error: Script is not divisible by OpCode length!', padding, False) return 1 @@ -161,7 +162,7 @@ def parse_bg_script(script_data, padding=0): BigScript = get_bgs_tool() if not BigScript: - printer('Error: BIOS Guard Script Tool dependency missing!', padding, False) + printer('Note: BIOS Guard Script Tool optional dependency is missing!', padding, False) return 3 @@ -220,7 +221,7 @@ def parse_pfat_hdr(buffer, padding=0): return block_all, hdr_size, files_count -def parse_pfat_file(input_file, output_path, padding=0): +def parse_pfat_file(input_file, extract_path, padding=0): input_buffer = file_to_bytes(input_file) pfat_buffer = get_ami_pfat(input_buffer) @@ -228,9 +229,7 @@ def parse_pfat_file(input_file, output_path, padding=0): file_path = '' all_blocks_dict = {} - extract_name = os.path.basename(output_path) - - extract_path = os.path.join(f'{output_path}_extracted') + extract_name = os.path.basename(extract_path).rstrip(extract_suffix()) make_dirs(extract_path, delete=True) @@ -299,7 +298,7 @@ def parse_pfat_file(input_file, output_path, padding=0): out_oob.write(pfat_oob_data) if is_ami_pfat(pfat_oob_data): - parse_pfat_file(pfat_oob_data, pfat_oob_path, padding) + parse_pfat_file(pfat_oob_data, get_extract_path(pfat_oob_path), padding) in_all_data = b''.join([block[1] for block in sorted(all_blocks_dict.items())]) @@ -317,32 +316,4 @@ PFAT_BLK_HDR_LEN = ctypes.sizeof(IntelBiosGuardHeader) PFAT_BLK_S2K_LEN = ctypes.sizeof(IntelBiosGuardSignature2k) if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: - input_buffer = in_file.read() - - if not is_ami_pfat(input_buffer): - printer('Error: This is not an AMI BIOS Guard (PFAT) image!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - parse_pfat_file(input_buffer, extract_path, padding) - - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_ami_pfat, parse_pfat_file).run_utility() diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 7aef35e..2f59e6f 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'AMI UCP Update Extractor v2.0_a19' +TITLE = 'AMI UCP Update Extractor v2.0_a20' import os import re @@ -21,10 +21,11 @@ sys.dont_write_bytecode = True from common.checksums import get_chk_16 from common.comp_efi import efi_decompress, is_efi_compressed -from common.path_ops import agnostic_path, make_dirs, safe_name, safe_path +from common.path_ops import agnostic_path, make_dirs, safe_name, safe_path, get_extract_path from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes, to_string from AMI_PFAT_Extract import is_ami_pfat, parse_pfat_file @@ -211,15 +212,13 @@ def get_uaf_mod(buffer, uaf_off=0x0): return uaf_all # Parse & Extract AMI UCP structures -def ucp_extract(in_file, out_path, padding=0, checksum=False): +def ucp_extract(in_file, extract_path, padding=0, checksum=False): input_buffer = file_to_bytes(in_file) nal_dict = {} # Initialize @NAL Dictionary per UCP printer('Utility Configuration Program', padding) - extract_path = os.path.join(f'{out_path}_extracted') - make_dirs(extract_path, delete=True) # Get best AMI UCP Pattern match based on @UAF|@HPU Size @@ -419,14 +418,14 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, checksum=False, nal_d if uaf_tag == '@INS' and is_insyde_ifd(uaf_fname): ins_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-IFD')) # Generate extraction directory - if insyde_ifd_extract(uaf_fname, ins_dir, padding + 4) == 0: + if insyde_ifd_extract(uaf_fname, get_extract_path(ins_dir), padding + 4) == 0: os.remove(uaf_fname) # Delete raw nested Insyde IFD image after successful extraction # Detect & Unpack AMI BIOS Guard (PFAT) BIOS image if is_ami_pfat(uaf_data_raw): pfat_dir = os.path.join(extract_path, safe_name(uaf_name)) - parse_pfat_file(uaf_data_raw, pfat_dir, padding + 4) + parse_pfat_file(uaf_data_raw, get_extract_path(pfat_dir), padding + 4) os.remove(uaf_fname) # Delete raw PFAT BIOS image after successful extraction @@ -439,7 +438,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, checksum=False, nal_d if is_ami_ucp(uaf_data_raw): uaf_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-UCP')) # Generate extraction directory - ucp_extract(uaf_data_raw, uaf_dir, padding + 4, checksum) # Call recursively + ucp_extract(uaf_data_raw, get_extract_path(uaf_dir), padding + 4, checksum) # Call recursively os.remove(uaf_fname) # Delete raw nested AMI UCP image after successful extraction @@ -511,35 +510,6 @@ UAF_TAG_DICT = { } if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - argparser.add_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true') - arguments = argparser.parse_args() - - checksum = arguments.checksum # Set Checksum verification optional argument - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: - input_buffer = in_file.read() - - if not is_ami_ucp(input_buffer): - printer('Error: This is not an AMI UCP Update executable!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - ucp_extract(input_buffer, extract_path, padding, checksum) - - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + utility = BIOSUtility(TITLE, is_ami_ucp, ucp_extract) + utility.parse_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true') + utility.run_utility() diff --git a/Apple_EFI_ID.py b/Apple_EFI_ID.py index 1897560..1003b67 100644 --- a/Apple_EFI_ID.py +++ b/Apple_EFI_ID.py @@ -7,7 +7,7 @@ Apple EFI Image Identifier Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Apple EFI Image Identifier v2.0_a4' +TITLE = 'Apple EFI Image Identifier v2.0_a5' import os import sys @@ -23,7 +23,8 @@ from common.externals import get_uefifind_path, get_uefiextract_path from common.path_ops import del_dirs, path_parent, path_suffixes from common.patterns import PAT_APPLE_EFI from common.struct_ops import char, get_struct, uint8_t -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes class IntelBiosId(ctypes.LittleEndianStructure): @@ -92,11 +93,11 @@ def is_apple_efi(input_file): check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return True - except: + except Exception: return False # Parse & Identify (or Rename) Apple EFI image -def apple_efi_identify(input_file, output_path, padding=0, rename=False): +def apple_efi_identify(input_file, extract_path, padding=0, rename=False): if not os.path.isfile(input_file): printer('Error: Could not find input file path!', padding) @@ -116,22 +117,20 @@ def apple_efi_identify(input_file, output_path, padding=0, rename=False): bios_id_res = subprocess.check_output([get_uefifind_path(), input_file, 'body', 'list', PAT_UEFIFIND], text=True)[:36] - temp_dir = os.path.join(f'{output_path}_uefiextract') + del_dirs(extract_path) # UEFIExtract must create its output folder itself, make sure it is not present - del_dirs(temp_dir) # UEFIExtract must create its output folder itself, make sure it is not present - - _ = subprocess.run([get_uefiextract_path(), input_file, bios_id_res, '-o', temp_dir, '-m', 'body'], + _ = subprocess.run([get_uefiextract_path(), input_file, bios_id_res, '-o', extract_path, '-m', 'body'], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - with open(os.path.join(temp_dir, 'body.bin'), 'rb') as raw_body: + with open(os.path.join(extract_path, 'body.bin'), 'rb') as raw_body: body_buffer = raw_body.read() bios_id_match = PAT_APPLE_EFI.search(body_buffer) # Detect decompressed $IBIOSI$ pattern bios_id_hdr = get_struct(body_buffer, bios_id_match.start(), IntelBiosId) - del_dirs(temp_dir) # Successful UEFIExtract extraction, remove its output (temp) folder - except: + del_dirs(extract_path) # Successful UEFIExtract extraction, remove its output (temp) folder + except Exception: printer('Error: Failed to parse compressed $IBIOSI$ pattern!', padding) return 2 @@ -163,31 +162,6 @@ def apple_efi_identify(input_file, output_path, padding=0, rename=False): PAT_UEFIFIND = f'244942494F534924{"."*32}2E00{"."*12}2E00{"."*16}2E00{"."*12}2E00{"."*40}0000' if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - argparser.add_argument('-r', '--rename', help='rename EFI image based on its tag', action='store_true') - arguments = argparser.parse_args() - - rename = arguments.rename # Set EFI image tag renaming optional argument - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - if not is_apple_efi(input_file): - printer('Error: This is not an Apple EFI image!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - if apple_efi_identify(input_file, extract_path, padding, rename) == 0: - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + utility = BIOSUtility(TITLE, is_apple_efi, apple_efi_identify) + utility.parse_argument('-r', '--rename', help='rename EFI image based on its tag', action='store_true') + utility.run_utility() diff --git a/Apple_EFI_IM4P.py b/Apple_EFI_IM4P.py index fe2bd3e..5dceefa 100644 --- a/Apple_EFI_IM4P.py +++ b/Apple_EFI_IM4P.py @@ -7,7 +7,7 @@ Apple EFI IM4P Splitter Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Apple EFI IM4P Splitter v3.0_a4' +TITLE = 'Apple EFI IM4P Splitter v3.0_a5' import os import sys @@ -17,7 +17,8 @@ sys.dont_write_bytecode = True from common.path_ops import make_dirs, path_stem from common.patterns import PAT_APPLE_IM4P, PAT_INTEL_IFD -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes # Check if input is Apple EFI IM4P image @@ -31,13 +32,11 @@ def is_apple_im4p(input_file): return bool(is_im4p and is_ifd) # Parse & Split Apple EFI IM4P image -def apple_im4p_split(input_file, output_path, padding=0): +def apple_im4p_split(input_file, extract_path, padding=0): exit_codes = [] input_buffer = file_to_bytes(input_file) - extract_path = os.path.join(f'{output_path}_extracted') - make_dirs(extract_path, delete=True) # Detect IM4P EFI pattern @@ -143,28 +142,4 @@ def apple_im4p_split(input_file, output_path, padding=0): IFD_COMP_LEN = {3: 0x400000, 4: 0x800000, 5: 0x1000000, 6: 0x2000000} if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - if not is_apple_im4p(input_file): - printer('Error: This is not an Apple EFI IM4P image!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - if apple_im4p_split(input_file, extract_path, padding) == 0: - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_apple_im4p, apple_im4p_split).run_utility() diff --git a/Apple_EFI_PBZX.py b/Apple_EFI_PBZX.py index 05e5eae..8e4f553 100644 --- a/Apple_EFI_PBZX.py +++ b/Apple_EFI_PBZX.py @@ -7,7 +7,7 @@ Apple EFI PBZX Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Apple EFI PBZX Extractor v1.0_a4' +TITLE = 'Apple EFI PBZX Extractor v1.0_a5' import os import sys @@ -21,7 +21,8 @@ from common.comp_szip import is_szip_supported, szip_decompress from common.path_ops import make_dirs, path_stem from common.patterns import PAT_APPLE_PBZX from common.struct_ops import get_struct, uint32_t -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes class PbzxChunk(ctypes.BigEndianStructure): @@ -47,11 +48,9 @@ def is_apple_pbzx(input_file): return bool(PAT_APPLE_PBZX.search(input_buffer[:0x4])) # Parse & Extract Apple PBZX image -def apple_pbzx_extract(input_file, output_path, padding=0): +def apple_pbzx_extract(input_file, extract_path, padding=0): input_buffer = file_to_bytes(input_file) - extract_path = os.path.join(f'{output_path}_extracted') - make_dirs(extract_path, delete=True) cpio_bin = b'' # Initialize PBZX > CPIO Buffer @@ -78,7 +77,7 @@ def apple_pbzx_extract(input_file, output_path, padding=0): cpio_bin += lzma.LZMADecompressor().decompress(comp_bin) printer('Successful LZMA decompression!', padding + 8) - except: + except Exception: # Otherwise, Chunk data is not compressed cpio_bin += comp_bin @@ -116,28 +115,4 @@ def apple_pbzx_extract(input_file, output_path, padding=0): PBZX_CHUNK_HDR_LEN = ctypes.sizeof(PbzxChunk) if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - if not is_apple_pbzx(input_file): - printer('Error: This is not an Apple PBZX image!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - if apple_pbzx_extract(input_file, extract_path, padding) == 0: - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_apple_pbzx, apple_pbzx_extract).run_utility() diff --git a/Apple_EFI_PKG.py b/Apple_EFI_PKG.py index 636f99e..a185547 100644 --- a/Apple_EFI_PKG.py +++ b/Apple_EFI_PKG.py @@ -7,7 +7,7 @@ Apple EFI Package Extractor Copyright (C) 2019-2022 Plato Mavropoulos """ -TITLE = 'Apple EFI Package Extractor v2.0_a4' +TITLE = 'Apple EFI Package Extractor v2.0_a5' import os import sys @@ -16,9 +16,10 @@ import sys sys.dont_write_bytecode = True from common.comp_szip import is_szip_supported, szip_decompress -from common.path_ops import copy_file, del_dirs, get_path_files, make_dirs, path_name, path_parent +from common.path_ops import copy_file, del_dirs, get_path_files, make_dirs, path_name, path_parent, get_extract_path from common.patterns import PAT_APPLE_PKG -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes from Apple_EFI_ID import apple_efi_identify, is_apple_efi @@ -35,11 +36,11 @@ def is_apple_pkg(input_file): def efi_split_rename(in_file, out_path, padding=0): exit_codes = [] - working_dir = f'{in_file}_extracted' + working_dir = get_extract_path(in_file) if is_apple_im4p(in_file): printer(f'Splitting IM4P via {is_apple_im4p.__module__}...', padding) - im4p_exit = apple_im4p_split(in_file, in_file, padding + 4) + im4p_exit = apple_im4p_split(in_file, working_dir, padding + 4) exit_codes.append(im4p_exit) else: make_dirs(working_dir, delete=True) @@ -59,13 +60,11 @@ def efi_split_rename(in_file, out_path, padding=0): return sum(exit_codes) # Parse & Extract Apple EFI PKG packages -def apple_pkg_extract(input_file, output_path, padding=0): +def apple_pkg_extract(input_file, extract_path, padding=0): if not os.path.isfile(input_file): printer('Error: Could not find input file path!', padding) return 1 - extract_path = os.path.join(f'{output_path}_extracted') - make_dirs(extract_path, delete=True) xar_path = os.path.join(extract_path, 'xar') @@ -82,12 +81,13 @@ def apple_pkg_extract(input_file, output_path, padding=0): pbzx_module = is_apple_pbzx.__module__ if is_apple_pbzx(xar_file): printer(f'Extracting PBZX via {pbzx_module}...', padding + 4) - if apple_pbzx_extract(xar_file, xar_file, padding + 8) == 0: + pbzx_path = get_extract_path(xar_file) + if apple_pbzx_extract(xar_file, pbzx_path, padding + 8) == 0: printer(f'Succesfull PBZX extraction via {pbzx_module}!', padding + 4) - for pbzx_file in get_path_files(f'{xar_file}_extracted'): + for pbzx_file in get_path_files(pbzx_path): if path_name(pbzx_file) == 'UpdateBundle.zip': if is_szip_supported(pbzx_file, padding + 8, args=['-tZIP'], check=True): - zip_path = f'{pbzx_file}_extracted' + zip_path = get_extract_path(pbzx_file) if szip_decompress(pbzx_file, zip_path, 'ZIP', padding + 8, args=['-tZIP'], check=True) == 0: for zip_file in get_path_files(zip_path): if path_name(path_parent(zip_file)) == 'MacEFI': @@ -114,11 +114,11 @@ def apple_pkg_extract(input_file, output_path, padding=0): if path_name(xar_file) == 'Scripts': if is_szip_supported(xar_file, padding + 4, args=['-tGZIP'], check=True): - gzip_path = f'{xar_file}_extracted' + gzip_path = get_extract_path(xar_file) if szip_decompress(xar_file, gzip_path, 'GZIP', padding + 4, args=['-tGZIP'], check=True) == 0: for gzip_file in get_path_files(gzip_path): if is_szip_supported(gzip_file, padding + 8, args=['-tCPIO'], check=True): - cpio_path = f'{gzip_file}_extracted' + cpio_path = get_extract_path(gzip_file) if szip_decompress(gzip_file, cpio_path, 'CPIO', padding + 8, args=['-tCPIO'], check=True) == 0: for cpio_file in get_path_files(cpio_path): if path_name(path_parent(cpio_file)) == 'EFIPayloads': @@ -145,28 +145,4 @@ def apple_pkg_extract(input_file, output_path, padding=0): return 0 if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - if not is_apple_pkg(input_file): - printer('Error: This is not an Apple EFI PKG package!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - if apple_pkg_extract(input_file, extract_path, padding) == 0: - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_apple_pkg, apple_pkg_extract).run_utility() diff --git a/Award_BIOS_Extract.py b/Award_BIOS_Extract.py index f06963f..12d1e96 100644 --- a/Award_BIOS_Extract.py +++ b/Award_BIOS_Extract.py @@ -7,7 +7,7 @@ Award BIOS Module Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Award BIOS Module Extractor v2.0_a4' +TITLE = 'Award BIOS Module Extractor v2.0_a5' import os import sys @@ -16,9 +16,10 @@ import sys sys.dont_write_bytecode = True from common.comp_szip import szip_decompress -from common.path_ops import make_dirs, safe_name +from common.path_ops import make_dirs, safe_name, get_extract_path from common.patterns import PAT_AWARD_LZH -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes # Check if input is Award BIOS image @@ -28,11 +29,9 @@ def is_award_bios(in_file): return bool(PAT_AWARD_LZH.search(in_buffer)) # Parse & Extract Award BIOS image -def award_bios_extract(input_file, output_path, padding=0): +def award_bios_extract(input_file, extract_path, padding=0): input_buffer = file_to_bytes(input_file) - extract_path = os.path.join(f'{output_path}_extracted') - make_dirs(extract_path, delete=True) for lzh_match in PAT_AWARD_LZH.finditer(input_buffer): @@ -69,35 +68,7 @@ def award_bios_extract(input_file, output_path, padding=0): # Extract any nested LZH archives if is_award_bios(mod_path): # Recursively extract nested Award BIOS modules - award_bios_extract(mod_path, mod_path, padding + 8) + award_bios_extract(mod_path, get_extract_path(mod_path), padding + 8) if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: - input_buffer = in_file.read() - - if not is_award_bios(input_buffer): - printer('Error: This is not an Award BIOS image!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - award_bios_extract(input_buffer, extract_path, padding) - - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_award_bios, award_bios_extract).run_utility() diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index 59c91f2..c0d981c 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Dell PFS/PKG Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Dell PFS/PKG Update Extractor v6.0_a12' +TITLE = 'Dell PFS/PKG Update Extractor v6.0_a13' import os import io @@ -25,7 +25,8 @@ from common.comp_szip import is_szip_supported, szip_decompress from common.path_ops import del_dirs, get_path_files, make_dirs, path_name, path_parent, path_stem, safe_name from common.patterns import PAT_DELL_FTR, PAT_DELL_HDR, PAT_DELL_PKG from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t, uint64_t -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes from AMI_PFAT_Extract import IntelBiosGuardHeader, IntelBiosGuardSignature2k, parse_bg_script @@ -225,6 +226,28 @@ def is_dell_pfs(in_file): return bool(is_pkg or is_hdr and is_ftr) +# Parse & Extract Dell PFS/PKG Update image +def pfs_pkg_parse(in_file, output_path, padding=0, structure=True, advanced=True): + in_buffer = file_to_bytes(in_file) + + make_dirs(output_path, delete=True) + + is_dell_pkg = is_pfs_pkg(in_buffer) + + if is_dell_pkg: + pfs_results = thinos_pkg_extract(in_buffer, output_path) + else: + pfs_results = {path_stem(in_file) if os.path.isfile(in_file) else 'Image': in_buffer} + + # Parse each Dell PFS image contained in the input file + for pfs_index,(pfs_name,pfs_buffer) in enumerate(pfs_results.items(), start=1): + # At ThinOS PKG packages, multiple PFS images may be included in separate model-named folders + pfs_path = os.path.join(output_path, f'{pfs_index} {pfs_name}') if is_dell_pkg else output_path + # Parse each PFS ZLIB section + for zlib_offset in get_section_offsets(pfs_buffer): + # Call the PFS ZLIB section parser function + pfs_section_parse(pfs_buffer, zlib_offset, pfs_path, pfs_name, pfs_index, 1, False, padding, structure, advanced) + # Extract Dell ThinOS PKG 7zXZ def thinos_pkg_extract(in_file, output_path): in_buffer = file_to_bytes(in_file) @@ -296,7 +319,7 @@ def get_section_offsets(buffer): return pfs_zlib_list # Dell PFS ZLIB Section Parser -def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padding, structure=True, advanced=True): +def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padding=0, structure=True, advanced=True): is_zlib_error = False # Initialize PFS ZLIB-related error state section_type = zlib_data[zlib_start - 0x1] # Byte before PFS ZLIB Section pattern is Section Type (e.g. AA, BB) @@ -365,34 +388,34 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p if is_zlib_error: raise Exception('ZLIB_ERROR') # ZLIB errors are critical section_data = zlib.decompress(compressed_data) # ZLIB decompression - except: + except Exception: section_data = zlib_data # Fallback to raw ZLIB data upon critical error # Call the PFS Extract function on the decompressed PFS ZLIB Section pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padding, structure, advanced) # Parse & Extract Dell PFS Volume -def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, structure=True, advanced=True): +def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, padding=0, structure=True, advanced=True): # Show PFS Volume indicator if structure: - printer('PFS Volume:', pfs_padd) + printer('PFS Volume:', padding) # Get PFS Header Structure values pfs_hdr = get_struct(buffer, 0, DellPfsHeader) # Validate that a PFS Header was parsed if pfs_hdr.Tag != b'PFS.HDR.': - printer('Error: PFS Header could not be found!', pfs_padd + 4) + printer('Error: PFS Header could not be found!', padding + 4) return # Critical error, abort # Show PFS Header Structure info if structure: - printer('PFS Header:\n', pfs_padd + 4) - pfs_hdr.struct_print(pfs_padd + 8) + printer('PFS Header:\n', padding + 4) + pfs_hdr.struct_print(padding + 8) # Validate that a known PFS Header Version was encountered - chk_hdr_ver(pfs_hdr.HeaderVersion, 'PFS', pfs_padd + 8) + chk_hdr_ver(pfs_hdr.HeaderVersion, 'PFS', padding + 8) # Get PFS Payload Data pfs_payload = buffer[PFS_HEAD_LEN:PFS_HEAD_LEN + pfs_hdr.PayloadSize] @@ -407,7 +430,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, s while len(pfs_payload[entry_start:entry_start + pfs_entry_size]) == pfs_entry_size: # Analyze PFS Entry Structure and get relevant info _,entry_version,entry_guid,entry_data,entry_data_sig,entry_met,entry_met_sig,next_entry = \ - parse_pfs_entry(pfs_payload, entry_start, pfs_entry_size, pfs_entry_struct, 'PFS Entry', pfs_padd, structure) + parse_pfs_entry(pfs_payload, entry_start, pfs_entry_size, pfs_entry_struct, 'PFS Entry', padding, structure) entry_type = 'OTHER' # Adjusted later if PFS Entry is Zlib, PFAT, PFS Info, Model Info @@ -444,12 +467,12 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, s # Show PFS Information Header Structure info if structure: - printer('PFS Information Header:\n', pfs_padd + 4) - entry_info_hdr.struct_print(pfs_padd + 8) + printer('PFS Information Header:\n', padding + 4) + entry_info_hdr.struct_print(padding + 8) # Validate that a known PFS Information Header Version was encountered if entry_info_hdr.HeaderVersion != 1: - printer(f'Error: Unknown PFS Information Header Version {entry_info_hdr.HeaderVersion}!', pfs_padd + 8) + printer(f'Error: Unknown PFS Information Header Version {entry_info_hdr.HeaderVersion}!', padding + 8) break # Skip PFS Information Entries/Descriptors in case of unknown PFS Information Header Version # Get PFS Information Header GUID in Big Endian format to match each Info to the equivalent stored PFS Entry details @@ -468,8 +491,8 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, s # Show PFS FileName Structure info if structure: - printer('PFS FileName Entry:\n', pfs_padd + 8) - entry_info_mod.struct_print(pfs_padd + 12, entry_name) + printer('PFS FileName Entry:\n', padding + 8) + entry_info_mod.struct_print(padding + 12, entry_name) # Get PFS FileName Version string via "Version" and "VersionType" fields # PFS FileName Version string must be preferred over PFS Entry's Version @@ -496,8 +519,8 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, s # Show Nested PFS Metadata Structure info if structure: - printer('PFS Metadata Information:\n', pfs_padd + 4) - entry_info.struct_print(pfs_padd + 8) + printer('PFS Metadata Information:\n', padding + 4) + entry_info.struct_print(padding + 8) # As Nested PFS Entry Name, we'll use the actual PFS File Name # Replace common Windows reserved/illegal filename characters @@ -520,12 +543,12 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, s # Show PFS Information Header Structure info if structure: - printer('PFS Information Header:\n', pfs_padd + 4) - entry_info_hdr.struct_print(pfs_padd + 8) + printer('PFS Information Header:\n', padding + 4) + entry_info_hdr.struct_print(padding + 8) # Validate that a known PFS Information Header Version was encountered if entry_info_hdr.HeaderVersion != 1: - printer(f'Error: Unknown PFS Information Header Version {entry_info_hdr.HeaderVersion}!', pfs_padd + 8) + printer(f'Error: Unknown PFS Information Header Version {entry_info_hdr.HeaderVersion}!', padding + 8) break # Skip PFS Signature Entries/Descriptors in case of unknown Header Version # PFS Signature Entries/Descriptors have DellPfsInfo + DellPfsEntryR* + Sign Size [0x2] + Sign Data [Sig Size] @@ -536,8 +559,8 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, s # Show PFS Information Header Structure info if structure: - printer('PFS Information Entry:\n', pfs_padd + 8) - entry_hdr.struct_print(pfs_padd + 12) + printer('PFS Information Entry:\n', padding + 8) + entry_hdr.struct_print(padding + 12) # Show PFS Signature Size & Data (after DellPfsEntryR*) sign_info_start = sign_start + PFS_INFO_LEN + pfs_entry_size @@ -546,9 +569,9 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, s sign_data_txt = f'{int.from_bytes(sign_data_raw, "little"):0{sign_size * 2}X}' if structure: - printer('Signature Information:\n', pfs_padd + 8) - printer(f'Signature Size: 0x{sign_size:X}', pfs_padd + 12, False) - printer(f'Signature Data: {sign_data_txt[:32]} [...]', pfs_padd + 12, False) + printer('Signature Information:\n', padding + 8) + printer(f'Signature Size: 0x{sign_size:X}', padding + 12, False) + printer(f'Signature Data: {sign_data_txt[:32]} [...]', padding + 12, False) # The next PFS Signature Entry/Descriptor starts after the previous Signature Data sign_start += (PFS_INFO_LEN + pfs_entry_size + 0x2 + sign_size) @@ -578,7 +601,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, s if pfat_entry_hdr.Tag == b'PFS.HDR.' and is_pfat: entry_type = 'PFAT' # Re-set PFS Entry Type from OTHER to PFAT, to use such info afterwards - entry_data = parse_pfat_pfs(pfat_entry_hdr, entry_data, pfs_padd, structure) # Parse sub-PFS PFAT Volume + entry_data = parse_pfat_pfs(pfat_entry_hdr, entry_data, padding, structure) # Parse sub-PFS PFAT Volume # Parse PFS Entry which contains zlib-compressed sub-PFS Volume elif pfs_zlib_offsets: @@ -599,7 +622,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, s sub_pfs_path = os.path.join(output_path, f'{pfs_count} {safe_name(sub_pfs_name)}') # Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count) - pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4, structure, advanced) + pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, padding + 4, structure, advanced) entries_all[index][4] = entry_data # Adjust PFS Entry Data after parsing PFAT (same ZLIB raw data, not stored afterwards) entries_all[index][3] = entry_type # Adjust PFS Entry Type from OTHER to PFAT or ZLIB (ZLIB is ignored at file extraction) @@ -668,16 +691,16 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, s # Write/Extract PFS Entry files for file in write_files: full_name = f'{pfs_index} {pfs_name} -- {file_index} {file_name} v{file_version}' # Full PFS Entry Name - pfs_file_write(file[0], file[1], file_type, full_name, output_path, pfs_padd, structure, advanced) + pfs_file_write(file[0], file[1], file_type, full_name, output_path, padding, structure, advanced) # Get PFS Footer Data after PFS Header Payload pfs_footer = buffer[PFS_HEAD_LEN + pfs_hdr.PayloadSize:PFS_HEAD_LEN + pfs_hdr.PayloadSize + PFS_FOOT_LEN] # Analyze PFS Footer Structure - chk_pfs_ftr(pfs_footer, pfs_payload, pfs_hdr.PayloadSize, 'PFS', pfs_padd, structure) + chk_pfs_ftr(pfs_footer, pfs_payload, pfs_hdr.PayloadSize, 'PFS', padding, structure) # Analyze Dell PFS Entry Structure -def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, padding, structure=True): +def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, padding=0, structure=True): # Get PFS Entry Structure values pfs_entry = get_struct(entry_buffer, entry_start, entry_struct) @@ -723,7 +746,7 @@ def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, p return pfs_entry, entry_version, entry_guid, entry_data, entry_data_sig, entry_met, entry_met_sig, entry_met_sig_end # Parse Dell PFS Volume with PFAT Payload -def parse_pfat_pfs(entry_hdr, entry_data, padding, structure=True): +def parse_pfat_pfs(entry_hdr, entry_data, padding=0, structure=True): # Show PFS Volume indicator if structure: printer('PFS Volume:', padding + 4) @@ -886,7 +909,7 @@ def get_entry_ver(version_fields, version_types): return version # Check if Dell PFS Header Version is known -def chk_hdr_ver(version, text, padding): +def chk_hdr_ver(version, text, padding=0): if version in (1,2): return @@ -895,7 +918,7 @@ def chk_hdr_ver(version, text, padding): return # Analyze Dell PFS Footer Structure -def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, structure=True): +def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding=0, structure=True): # Get PFS Footer Structure values pfs_ftr = get_struct(footer_buffer, 0, DellPfsFooter) @@ -920,7 +943,7 @@ def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, structure= printer(f'Error: Invalid {text} Footer Payload Checksum!', padding + 4) # Write/Extract Dell PFS Entry Files (Data, Metadata, Signature) -def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, structure=True, advanced=True): +def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding=0, structure=True, advanced=True): # Store Data/Metadata Signature (advanced users only) if bin_name.startswith('sign'): final_name = f'{safe_name(full_name)}.{bin_name.split("_")[1]}.sig' @@ -944,7 +967,7 @@ def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, s pfs_out.write(final_data) # Write final Data/Metadata Payload # Check if Dell PFS Entry file/data is Text/XML and Convert -def bin_is_text(buffer, file_type, is_metadata, pfs_padd, structure=True, advanced=True): +def bin_is_text(buffer, file_type, is_metadata, padding=0, structure=True, advanced=True): is_text = False write_mode = 'wb' extension = '.bin' @@ -975,8 +998,8 @@ def bin_is_text(buffer, file_type, is_metadata, pfs_padd, structure=True, advanc # Show Model/PCR XML Information, if applicable if structure and is_text and not is_metadata: # Metadata is shown at initial DellPfsMetadata analysis - printer(f'PFS { {".txt": "Model", ".xml": "PCR XML"}[extension] } Information:\n', pfs_padd + 8) - _ = [printer(line.strip('\r'), pfs_padd + 12, False) for line in buffer.split('\n') if line] + printer(f'PFS { {".txt": "Model", ".xml": "PCR XML"}[extension] } Information:\n', padding + 8) + _ = [printer(line.strip('\r'), padding + 12, False) for line in buffer.split('\n') if line] # Only for non-advanced users due to signature (.sig) invalidation if advanced: @@ -994,52 +1017,8 @@ PFS_PFAT_LEN = ctypes.sizeof(DellPfsPfatMetadata) PFAT_HDR_LEN = ctypes.sizeof(IntelBiosGuardHeader) PFAT_SIG_LEN = ctypes.sizeof(IntelBiosGuardSignature2k) -if __name__ == '__main__': - # Set argparse arguments - argparser = argparse_init() - argparser.add_argument('-a', '--advanced', help='extract signatures and metadata', action='store_true') - argparser.add_argument('-s', '--structure', help='show PFS structure information', action='store_true') - arguments = argparser.parse_args() - - advanced = arguments.advanced # Set Advanced user mode optional argument - structure = arguments.structure # Set Structure output mode optional argument - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: - input_buffer = in_file.read() - - if not is_dell_pfs(input_buffer): - printer('Error: This is not a Dell PFS/PKG Update image!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, f'{input_name}_extracted') - - is_dell_pkg = is_pfs_pkg(input_buffer) - - if is_dell_pkg: - pfs_results = thinos_pkg_extract(input_buffer, extract_path) - else: - pfs_results = {path_stem(input_file): input_buffer} - - # Parse each Dell PFS image contained in the input file - for pfs_index,(pfs_name,pfs_buffer) in enumerate(pfs_results.items(), start=1): - # At ThinOS PKG packages, multiple PFS images may be included in separate model-named folders - pfs_path = os.path.join(extract_path, f'{pfs_index} {pfs_name}') if is_dell_pkg else extract_path - # Parse each PFS ZLIB section - for zlib_offset in get_section_offsets(pfs_buffer): - # Call the PFS ZLIB section parser function - pfs_section_parse(pfs_buffer, zlib_offset, pfs_path, pfs_name, pfs_index, 1, False, padding, structure, advanced) - - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) +if __name__ == '__main__': + utility = BIOSUtility(TITLE, is_dell_pfs, pfs_pkg_parse) + utility.parse_argument('-a', '--advanced', help='extract signatures and metadata', action='store_true') + utility.parse_argument('-s', '--structure', help='show PFS structure information', action='store_true') + utility.run_utility() diff --git a/Fujitsu_SFX_Extract.py b/Fujitsu_SFX_Extract.py index f27b984..52ba7c9 100644 --- a/Fujitsu_SFX_Extract.py +++ b/Fujitsu_SFX_Extract.py @@ -7,7 +7,7 @@ Fujitsu SFX BIOS Extractor Copyright (C) 2019-2022 Plato Mavropoulos """ -TITLE = 'Fujitsu SFX BIOS Extractor v3.0_a2' +TITLE = 'Fujitsu SFX BIOS Extractor v3.0_a3' import os import sys @@ -18,7 +18,8 @@ sys.dont_write_bytecode = True from common.comp_szip import is_szip_supported, szip_decompress from common.path_ops import make_dirs from common.patterns import PAT_FUJITSU_SFX -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes # Check if input is Fujitsu SFX image @@ -71,11 +72,9 @@ def fujitsu_cabinet(in_file, extract_path, padding=0): return 0 # Parse & Extract Fujitsu SFX image -def fujitsu_sfx_extract(in_file, output_path, padding=0): +def fujitsu_sfx_extract(in_file, extract_path, padding=0): buffer = file_to_bytes(in_file) - extract_path = os.path.join(f'{output_path}_extracted') - make_dirs(extract_path, delete=True) if fujitsu_cabinet(buffer, extract_path, padding) == 0: @@ -87,32 +86,4 @@ def fujitsu_sfx_extract(in_file, output_path, padding=0): return 0 if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: - input_buffer = in_file.read() - - # Check if Fujitsu SFX pattern was found on image - if not is_fujitsu_sfx(input_buffer): - printer('Error: This is not a Fujitsu SFX image!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - if fujitsu_sfx_extract(input_buffer, extract_path, padding) == 0: - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_fujitsu_sfx, fujitsu_sfx_extract).run_utility() diff --git a/Fujitsu_UPC_Extract.py b/Fujitsu_UPC_Extract.py index 24a7ebd..01d6ac7 100644 --- a/Fujitsu_UPC_Extract.py +++ b/Fujitsu_UPC_Extract.py @@ -7,7 +7,7 @@ Fujitsu UPC BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Fujitsu UPC BIOS Extractor v2.0_a4' +TITLE = 'Fujitsu UPC BIOS Extractor v2.0_a5' import os import sys @@ -17,7 +17,7 @@ sys.dont_write_bytecode = True from common.comp_efi import efi_decompress, is_efi_compressed from common.path_ops import make_dirs, path_suffixes -from common.system import argparse_init, printer, script_init +from common.templates import BIOSUtility from common.text_ops import file_to_bytes # Check if input is Fujitsu UPC image @@ -31,9 +31,7 @@ def is_fujitsu_upc(in_file): return is_ext and is_efi # Parse & Extract Fujitsu UPC image -def fujitsu_upc_extract(input_file, output_path, padding=0): - extract_path = os.path.join(f'{output_path}_extracted') - +def fujitsu_upc_extract(input_file, extract_path, padding=0): make_dirs(extract_path, delete=True) image_base = os.path.basename(input_file) @@ -43,31 +41,4 @@ def fujitsu_upc_extract(input_file, output_path, padding=0): return efi_decompress(input_file, image_path, padding) if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: - input_buffer = in_file.read() - - if not is_fujitsu_upc(input_buffer): - printer('Error: This is not a Fujitsu UPC BIOS image!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - if fujitsu_upc_extract(input_file, extract_path, padding) == 0: - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_fujitsu_upc, fujitsu_upc_extract).run_utility() diff --git a/Insyde_IFD_Extract.py b/Insyde_IFD_Extract.py index c68d4d4..65593f4 100644 --- a/Insyde_IFD_Extract.py +++ b/Insyde_IFD_Extract.py @@ -7,7 +7,7 @@ Insyde iFlash/iFdPacker Extractor Copyright (C) 2022 Plato Mavropoulos """ -TITLE = 'Insyde iFlash/iFdPacker Extractor v2.0_a10' +TITLE = 'Insyde iFlash/iFdPacker Extractor v2.0_a11' import os import sys @@ -17,10 +17,11 @@ import ctypes sys.dont_write_bytecode = True from common.comp_szip import is_szip_supported, szip_decompress -from common.path_ops import get_path_files, make_dirs, safe_name +from common.path_ops import get_path_files, make_dirs, safe_name, get_extract_path from common.patterns import PAT_INSYDE_IFL, PAT_INSYDE_SFX from common.struct_ops import char, get_struct, uint32_t -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes class IflashHeader(ctypes.LittleEndianStructure): @@ -57,11 +58,9 @@ def is_insyde_ifd(input_file): return is_ifl or is_sfx # Parse & Extract Insyde iFlash/iFdPacker Update images -def insyde_ifd_extract(input_file, output_path, padding=0): +def insyde_ifd_extract(input_file, extract_path, padding=0): input_buffer = file_to_bytes(input_file) - extract_path = os.path.join(f'{output_path}_extracted') - iflash_code = insyde_iflash_extract(input_buffer, extract_path, padding) ifdpack_path = os.path.join(extract_path, 'Insyde iFdPacker SFX') @@ -190,7 +189,7 @@ def insyde_packer_extract(input_buffer, extract_path, padding=0): if is_insyde_ifd(sfx_file): printer(f'{os.path.basename(sfx_file)}', padding + 12) - ifd_code = insyde_ifd_extract(sfx_file, sfx_file, padding + 16) + ifd_code = insyde_ifd_extract(sfx_file, get_extract_path(sfx_file), padding + 16) exit_codes.append(ifd_code) @@ -215,32 +214,4 @@ INS_IFL_IMG = { INS_IFL_LEN = ctypes.sizeof(IflashHeader) if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: - input_buffer = in_file.read() - - if not is_insyde_ifd(input_buffer): - printer('Error: This is not an Insyde iFlash/iFdPacker Update image!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - insyde_ifd_extract(input_buffer, extract_path, padding) - - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_insyde_ifd, insyde_ifd_extract).run_utility() diff --git a/Panasonic_BIOS_Extract.py b/Panasonic_BIOS_Extract.py index 900f1a6..096bec3 100644 --- a/Panasonic_BIOS_Extract.py +++ b/Panasonic_BIOS_Extract.py @@ -7,7 +7,7 @@ Panasonic BIOS Package Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Panasonic BIOS Package Extractor v2.0_a9' +TITLE = 'Panasonic BIOS Package Extractor v2.0_a10' import os import io @@ -22,7 +22,8 @@ from common.comp_szip import is_szip_supported, szip_decompress from common.path_ops import get_path_files, make_dirs, path_stem, safe_name from common.pe_ops import get_pe_file, get_pe_info, is_pe_file, show_pe_info from common.patterns import PAT_MICROSOFT_CAB -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes from AMI_PFAT_Extract import is_ami_pfat, parse_pfat_file @@ -111,7 +112,7 @@ def panasonic_res_extract(pe_name, pe_file, extract_path, padding=0): res_raw = lznt1.decompress(res_bin[0x8:]) printer('Succesfull LZNT1 decompression via lznt1!', padding + 8) - except: + except Exception: res_raw = res_bin printer('Succesfull PE Resource extraction!', padding + 8) @@ -161,10 +162,8 @@ def panasonic_img_extract(pe_name, pe_path, pe_file, extract_path, padding=0): return bool(img_bin) # Parse & Extract Panasonic BIOS Package PE -def panasonic_pkg_extract(input_path, input_buffer, output_path, padding=0): - is_upd_res,is_upd_img = [False] * 2 - - extract_path = os.path.join(f'{output_path}_extracted') +def panasonic_pkg_extract(input_file, extract_path, padding=0): + input_buffer = file_to_bytes(input_file) make_dirs(extract_path, delete=True) @@ -178,7 +177,7 @@ def panasonic_pkg_extract(input_path, input_buffer, output_path, padding=0): if not pkg_pe_info: return 3 - pkg_pe_name = path_stem(input_path) + pkg_pe_name = path_stem(input_file) printer(f'Panasonic BIOS Package > PE ({pkg_pe_name})\n', padding) @@ -195,6 +194,8 @@ def panasonic_pkg_extract(input_path, input_buffer, output_path, padding=0): show_pe_info(upd_pe_info, padding + 16) + is_upd_res, is_upd_img = False, False + is_upd_res = panasonic_res_extract(upd_pe_name, upd_pe_file, extract_path, padding + 16) if not is_upd_res: @@ -205,32 +206,4 @@ def panasonic_pkg_extract(input_path, input_buffer, output_path, padding=0): return 0 if is_upd_res or is_upd_img else 1 if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: - input_buffer = in_file.read() - - # Check if Panasonic BIOS Package pattern was found on executable - if not is_panasonic_pkg(input_buffer): - printer('Error: This is not a Panasonic BIOS Package executable!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - if panasonic_pkg_extract(input_file, input_buffer, extract_path, padding) == 0: - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_panasonic_pkg, panasonic_pkg_extract).run_utility() diff --git a/Phoenix_TDK_Extract.py b/Phoenix_TDK_Extract.py index 5d7456a..3328ad4 100644 --- a/Phoenix_TDK_Extract.py +++ b/Phoenix_TDK_Extract.py @@ -7,7 +7,7 @@ Phoenix TDK Packer Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Phoenix TDK Packer Extractor v2.0_a9' +TITLE = 'Phoenix TDK Packer Extractor v2.0_a10' import os import sys @@ -21,7 +21,8 @@ from common.path_ops import make_dirs, safe_name from common.pe_ops import get_pe_file, get_pe_info from common.patterns import PAT_MICROSOFT_MZ, PAT_MICROSOFT_PE, PAT_PHOENIX_TDK from common.struct_ops import char, get_struct, uint32_t -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes class PhoenixTdkHeader(ctypes.LittleEndianStructure): @@ -110,7 +111,7 @@ def get_tdk_base(in_buffer, pack_off): # Parse detected MZ > PE > Info > Product Name pe_name = pe_info.get(b'ProductName',b'') - except: + except Exception: # Any error means no MZ > PE > Info > Product Name pe_name = b'' @@ -152,12 +153,10 @@ def is_phoenix_tdk(in_file): return bool(get_phoenix_tdk(buffer)[1] is not None) # Parse & Extract Phoenix Tools Development Kit (TDK) Packer -def phoenix_tdk_extract(input_file, output_path, padding=0): - input_buffer = file_to_bytes(input_file) - +def phoenix_tdk_extract(input_file, extract_path, padding=0): exit_code = 0 - extract_path = os.path.join(f'{output_path}_extracted') + input_buffer = file_to_bytes(input_file) make_dirs(extract_path, delete=True) @@ -214,7 +213,7 @@ def phoenix_tdk_extract(input_file, output_path, padding=0): if tdk_mod.get_compression() == 'LZMA': try: mod_data = lzma.LZMADecompressor().decompress(mod_data) - except: + except Exception: printer('Error: Phoenix TDK Entry > LZMA decompression failed!\n', padding + 12, pause=True) exit_code = 5 @@ -241,32 +240,4 @@ TDK_MOD_LEN = ctypes.sizeof(PhoenixTdkEntry) TDK_DUMMY_LEN = 0x200 if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: - input_buffer = in_file.read() - - # Check if Phoenix TDK Packer pattern was found on executable - if not is_phoenix_tdk(input_buffer): - printer('Error: This is not a Phoenix TDK Packer executable!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - if phoenix_tdk_extract(input_buffer, extract_path, padding) == 0: - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_phoenix_tdk, phoenix_tdk_extract).run_utility() diff --git a/Portwell_EFI_Extract.py b/Portwell_EFI_Extract.py index 316181e..bb40705 100644 --- a/Portwell_EFI_Extract.py +++ b/Portwell_EFI_Extract.py @@ -7,7 +7,7 @@ Portwell EFI Update Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -TITLE = 'Portwell EFI Update Extractor v2.0_a11' +TITLE = 'Portwell EFI Update Extractor v2.0_a12' import os import sys @@ -19,7 +19,8 @@ from common.comp_efi import efi_decompress, is_efi_compressed from common.path_ops import make_dirs, safe_name from common.pe_ops import get_pe_file from common.patterns import PAT_MICROSOFT_MZ, PAT_PORTWELL_EFI -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes FILE_NAMES = { @@ -36,7 +37,7 @@ def is_portwell_efi(in_file): try: pe_buffer = get_portwell_pe(in_buffer)[1] - except: + except Exception: pe_buffer = b'' is_mz = PAT_MICROSOFT_MZ.search(in_buffer[:0x2]) # EFI images start with PE Header MZ @@ -54,10 +55,10 @@ def get_portwell_pe(in_buffer): return pe_file, pe_data # Parse & Extract Portwell UEFI Unpacker -def portwell_efi_extract(input_buffer, output_path, padding=0): +def portwell_efi_extract(input_file, extract_path, padding=0): efi_files = [] # Initialize EFI Payload file chunks - extract_path = os.path.join(f'{output_path}_extracted') + input_buffer = file_to_bytes(input_file) make_dirs(extract_path, delete=True) @@ -132,32 +133,4 @@ def parse_efi_files(extract_path, efi_files, padding): os.remove(comp_fname) # Successful decompression, delete compressed file if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: - input_buffer = in_file.read() - - if not is_portwell_efi(input_buffer): - printer('Error: This is not a Portwell EFI Update Package!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - portwell_efi_extract(input_buffer, extract_path, padding) - - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_portwell_efi, portwell_efi_extract).run_utility() diff --git a/README.md b/README.md index 0df5ebb..6b5d67b 100644 --- a/README.md +++ b/README.md @@ -50,32 +50,6 @@ Optionally, to decompile the AMI PFAT \> Intel BIOS Guard Scripts, you must have * [BIOS Guard Script Tool](https://github.com/platomav/BGScriptTool) (i.e. big_script_tool.py) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Place prerequisites at the "external" project directory: - -> BIOS Guard Script Tool (optional) - -4. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/AMI_PFAT_Extract.py - -You should find the final utility executable at "dist" folder - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -114,34 +88,6 @@ Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (wh * [BIOS Guard Script Tool](https://github.com/platomav/BGScriptTool) (i.e. big_script_tool.py) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Place prerequisites at the "external" project directory: - -> TianoCompress\ -> 7-Zip Console\ -> BIOS Guard Script Tool (optional) - -4. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/AMI_UCP_Extract.py - -You should find the final utility executable at "dist" folder - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -172,28 +118,6 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 To run the utility, you do not need any prerequisites. -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile \\/Apple_EFI_IM4P.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -228,33 +152,6 @@ To run the utility, you must have the following 3rd party tools at the "external * [UEFIFind](https://github.com/LongSoft/UEFITool/) (i.e. [UEFIFind.exe for Windows or UEFIFind for Linux](https://github.com/LongSoft/UEFITool/releases)) * [UEFIExtract](https://github.com/LongSoft/UEFITool/) (i.e. [UEFIExtract.exe for Windows or UEFIExtract for Linux](https://github.com/LongSoft/UEFITool/releases)) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Place prerequisites at the "external" project directory: - -> UEFIFind\ -> UEFIExtract - -4. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Apple_EFI_ID.py - -You should find the final utility executable at "dist" folder - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -287,32 +184,6 @@ To run the utility, you must have the following 3rd party tools at the "external * [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Place prerequisites at the "external" project directory: - -> 7-Zip Console - -4. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Apple_EFI_PKG.py - -You should find the final utility executable at "dist" folder - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -345,32 +216,6 @@ To run the utility, you must have the following 3rd party tools at the "external * [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Place prerequisites at the "external" project directory: - -> 7-Zip Console - -4. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Apple_EFI_PBZX.py - -You should find the final utility executable at "dist" folder - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -403,32 +248,6 @@ To run the utility, you must have the following 3rd party tool at the "external" * [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Place prerequisite at the "external" project directory: - -> 7-Zip Console - -4. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Award_BIOS_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -463,32 +282,6 @@ Optionally, to decompile the Intel BIOS Guard (PFAT) Scripts, you must have the * [BIOS Guard Script Tool](https://github.com/platomav/BGScriptTool) (i.e. big_script_tool.py) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Place prerequisites at the "external" project directory: - -> BIOS Guard Script Tool (optional) - -4. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Dell_PFS_Extract.py - -You should find the final utility executable at "dist" folder - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -521,32 +314,6 @@ To run the utility, you must have the following 3rd party tool at the "external" * [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Place prerequisite at the "external" project directory: - -> 7-Zip Console - -4. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Fujitsu_SFX_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -579,32 +346,6 @@ To run the utility, you must have the following 3rd party tool at the "external" * [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (i.e. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Place prerequisite at the "external" project directory: - -> TianoCompress - -4. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Fujitsu_UPC_Extract.py - -You should find the final utility executable at "dist" folder - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -635,28 +376,6 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3 To run the utility, you do not need any prerequisites. -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile \\/Insyde_IFD_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -694,36 +413,6 @@ Moreover, you must have the following 3rd party tool at the "external" project d * [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Use pip to install pefile and lznt1: - -> pip3 install pefile lznt1 - -4. Place prerequisite at the "external" project directory: - -> 7-Zip Console - -5. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Panasonic_BIOS_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -756,32 +445,6 @@ To run the utility, you must have the following 3rd party Python module installe * [pefile](https://pypi.org/project/pefile/) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Use pip to install pefile: - -> pip3 install pefile - -4. Build/Freeze/Compile: - -> pyinstaller --noupx --onefile \\/Phoenix_TDK_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -820,36 +483,6 @@ Moreover, you must have the following 3rd party tool at the "external" project d * [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (i.e. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Use pip to install pefile: - -> pip3 install pefile - -4. Place prerequisite at the "external" project directory: - -> TianoCompress - -5. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Portwell_EFI_Extract.py - -You should find the final utility executable at "dist" folder - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -882,32 +515,6 @@ To run the utility, you must have the following 3rd party tool at the "external" * [ToshibaComExtractor](https://github.com/LongSoft/ToshibaComExtractor) (i.e. [comextract.exe for Windows or comextract for Linux](https://github.com/LongSoft/ToshibaComExtractor/releases)) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Place prerequisite at the "external" project directory: - -> ToshibaComExtractor - -4. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Toshiba_COM_Extract.py - -You should find the final utility executable at "dist" folder - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() @@ -940,32 +547,6 @@ To run the utility, you must have the following 3rd party tool at the "external" * [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux) -#### **Build/Freeze/Compile with PyInstaller** - -PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. - -1. Make sure Python 3.10.0 or newer is installed: - -> python --version - -2. Use pip to install PyInstaller: - -> pip3 install pyinstaller - -3. Place prerequisite at the "external" project directory: - -> 7-Zip Console - -4. Build/Freeze/Compile: - -> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/VAIO_Package_Extract.py - -At dist folder you should find the final utility executable - -#### **Anti-Virus False Positives** - -Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. - #### **Pictures** ![]() \ No newline at end of file diff --git a/Toshiba_COM_Extract.py b/Toshiba_COM_Extract.py index 4a6e1c2..da6ee43 100644 --- a/Toshiba_COM_Extract.py +++ b/Toshiba_COM_Extract.py @@ -7,7 +7,7 @@ Toshiba BIOS COM Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Toshiba BIOS COM Extractor v2.0_a3' +TITLE = 'Toshiba BIOS COM Extractor v2.0_a4' import os import sys @@ -16,9 +16,11 @@ import subprocess # Stop __pycache__ generation sys.dont_write_bytecode = True -from common.path_ops import make_dirs, path_stem, path_suffixes, project_root, safe_path +from common.externals import get_comextract_path +from common.path_ops import make_dirs, path_stem, path_suffixes from common.patterns import PAT_TOSHIBA_COM -from common.system import argparse_init, get_os_ver, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes # Check if input is Toshiba BIOS COM image @@ -31,15 +33,12 @@ def is_toshiba_com(in_file): return is_ext and is_com -# Get ToshibaComExtractor path -def get_comextract_path(): - exec_name = 'comextract.exe' if get_os_ver()[1] else 'comextract' - - return safe_path(project_root(), ['external',exec_name]) - # Parse & Extract Toshiba BIOS COM image -def toshiba_com_extract(input_file, output_path, padding=0): - extract_path = os.path.join(f'{output_path}_extracted') +def toshiba_com_extract(input_file, extract_path, padding=0): + if not os.path.isfile(input_file): + printer('Error: Could not find input file path!', padding) + + return 1 make_dirs(extract_path, delete=True) @@ -51,41 +50,14 @@ def toshiba_com_extract(input_file, output_path, padding=0): if not os.path.isfile(output_file): raise Exception('EXTRACT_FILE_MISSING') - except: + except Exception: printer(f'Error: ToshibaComExtractor could not extract file {input_file}!', padding) - return 1 + return 2 printer(f'Succesfull {output_name} extraction via ToshibaComExtractor!', padding) return 0 if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: - input_buffer = in_file.read() - - if not is_toshiba_com(input_file): - printer('Error: This is not a Toshiba BIOS COM image!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - if toshiba_com_extract(input_file, extract_path, padding) == 0: - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_toshiba_com, toshiba_com_extract).run_utility() diff --git a/VAIO_Package_Extract.py b/VAIO_Package_Extract.py index c8cec9c..9bb49bf 100644 --- a/VAIO_Package_Extract.py +++ b/VAIO_Package_Extract.py @@ -7,7 +7,7 @@ VAIO Packaging Manager Extractor Copyright (C) 2019-2022 Plato Mavropoulos """ -TITLE = 'VAIO Packaging Manager Extractor v3.0_a7' +TITLE = 'VAIO Packaging Manager Extractor v3.0_a8' import os import sys @@ -18,7 +18,8 @@ sys.dont_write_bytecode = True from common.comp_szip import is_szip_supported, szip_decompress from common.path_ops import make_dirs from common.patterns import PAT_VAIO_CAB, PAT_VAIO_CFG, PAT_VAIO_CHK, PAT_VAIO_EXT -from common.system import argparse_init, printer, script_init +from common.system import printer +from common.templates import BIOSUtility from common.text_ops import file_to_bytes # Check if input is VAIO Packaging Manager @@ -125,14 +126,16 @@ def vaio_unlock(name, buffer, extract_path, padding=0): return 0 # Parse & Extract or Unlock VAIO Packaging Manager -def vaio_pkg_extract(name, buffer, output_path, padding=0): - extract_path = os.path.join(f'{output_path}_extracted') +def vaio_pkg_extract(input_file, extract_path, padding=0): + input_buffer = file_to_bytes(input_file) + + input_name = os.path.basename(input_file) make_dirs(extract_path, delete=True) - if vaio_cabinet(name, buffer, extract_path, padding) == 0: + if vaio_cabinet(input_name, input_buffer, extract_path, padding) == 0: printer('Successfully Extracted!', padding) - elif vaio_unlock(name, bytearray(buffer), extract_path, padding) == 0: + elif vaio_unlock(input_name, bytearray(input_buffer), extract_path, padding) == 0: printer('Successfully Unlocked!', padding) else: printer('Error: Failed to Extract or Unlock executable!', padding) @@ -141,32 +144,4 @@ def vaio_pkg_extract(name, buffer, output_path, padding=0): return 0 if __name__ == '__main__': - # Set argparse Arguments - argparser = argparse_init() - arguments = argparser.parse_args() - - # Initialize script (must be after argparse) - exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) - - for input_file in input_files: - input_name = os.path.basename(input_file) - - printer(['***', input_name], padding - 4) - - with open(input_file, 'rb') as in_file: - input_buffer = in_file.read() - - # Check if VAIO Packaging Manager pattern was found on executable - if not is_vaio_pkg(input_buffer): - printer('Error: This is not a VAIO Packaging Manager executable!', padding) - - continue # Next input file - - extract_path = os.path.join(output_path, input_name) - - if vaio_pkg_extract(input_name, input_buffer, extract_path, padding) == 0: - exit_code -= 1 - - printer('Done!', pause=True) - - sys.exit(exit_code) + BIOSUtility(TITLE, is_vaio_pkg, vaio_pkg_extract).run_utility() diff --git a/common/comp_efi.py b/common/comp_efi.py index 3c14b71..2837898 100644 --- a/common/comp_efi.py +++ b/common/comp_efi.py @@ -45,7 +45,7 @@ def efi_decompress(in_path, out_path, padding=0, silent=False, comp_type='--uefi if os.path.getsize(out_path) != size_orig: raise Exception('EFI_DECOMPRESS_ERROR') - except: + except Exception: if not silent: printer(f'Error: TianoCompress could not extract file {in_path}!', padding) diff --git a/common/comp_szip.py b/common/comp_szip.py index 3db03a6..fb6041b 100644 --- a/common/comp_szip.py +++ b/common/comp_szip.py @@ -34,7 +34,7 @@ def is_szip_supported(in_path, padding=0, args=None, check=False, silent=False): if check: check_bad_exit_code(szip_t.returncode) - except: + except Exception: if not silent: printer(f'Error: 7-Zip could not check support for file {in_path}!', padding) @@ -60,7 +60,7 @@ def szip_decompress(in_path, out_path, in_name, padding=0, args=None, check=Fals if not os.path.isdir(out_path): raise Exception('EXTRACT_DIR_MISSING') - except: + except Exception: if not silent: printer(f'Error: 7-Zip could not extract {in_name} file {in_path}!', padding) diff --git a/common/externals.py b/common/externals.py index 5aa17ae..f81e3b9 100644 --- a/common/externals.py +++ b/common/externals.py @@ -12,8 +12,9 @@ from common.system import get_os_ver # https://github.com/platomav/BGScriptTool by Plato Mavropoulos def get_bgs_tool(): try: - from external.big_script_tool import BigScript - except: + # noinspection PyUnresolvedReferences + from external.big_script_tool import BigScript # pylint: disable=E0401,E0611 + except Exception: BigScript = None return BigScript @@ -22,10 +23,16 @@ def get_bgs_tool(): def get_uefifind_path(): exec_name = f'UEFIFind{".exe" if get_os_ver()[1] else ""}' - return safe_path(project_root(), ['external',exec_name]) + return safe_path(project_root(), ['external', exec_name]) # Get UEFIExtract path def get_uefiextract_path(): exec_name = f'UEFIExtract{".exe" if get_os_ver()[1] else ""}' - return safe_path(project_root(), ['external',exec_name]) + return safe_path(project_root(), ['external', exec_name]) + +# Get ToshibaComExtractor path +def get_comextract_path(): + exec_name = f'comextract{".exe" if get_os_ver()[1] else ""}' + + return safe_path(project_root(), ['external', exec_name]) diff --git a/common/path_ops.py b/common/path_ops.py index e2420e6..26eca72 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -130,58 +130,13 @@ def get_dequoted_path(in_path): return out_path -# Get absolute file path of argparse object -def get_argparse_path(argparse_path): - if not argparse_path: - # Use runtime directory if no user path is specified - absolute_path = runtime_root() - else: - # Check if user specified path is absolute - if is_path_absolute(argparse_path): - absolute_path = argparse_path - # Otherwise, make it runtime directory relative - else: - absolute_path = safe_path(runtime_root(), argparse_path) - - return absolute_path +# Set utility extraction stem +def extract_suffix(): + return '_extracted' -# Process input files (argparse object) -def process_input_files(argparse_args, sys_argv=None): - input_files = [] - - if sys_argv is None: - sys_argv = [] - - if len(sys_argv) >= 2: - # Drag & Drop or CLI - if argparse_args.input_dir: - input_path_user = argparse_args.input_dir - input_path_full = get_argparse_path(input_path_user) if input_path_user else '' - input_files = get_path_files(input_path_full) - else: - # Parse list of input files (i.e. argparse FileType objects) - for file_object in argparse_args.files: - # Store each argparse FileType object's name (i.e. path) - input_files.append(file_object.name) - # Close each argparse FileType object (i.e. allow input file changes) - file_object.close() - - # Set output fallback value for missing argparse Output and Input Path - output_fallback = path_parent(input_files[0]) if input_files else None - - # Set output path via argparse Output path or argparse Input path or first input file path - output_path = argparse_args.output_dir or argparse_args.input_dir or output_fallback - else: - # Script w/o parameters - input_path_user = get_dequoted_path(input('\nEnter input directory path: ')) - input_path_full = get_argparse_path(input_path_user) if input_path_user else '' - input_files = get_path_files(input_path_full) - - output_path = get_dequoted_path(input('\nEnter output directory path: ')) - - output_path_final = get_argparse_path(output_path) - - return input_files, output_path_final +# Get utility extraction path +def get_extract_path(in_path, suffix=extract_suffix()): + return f'{in_path}{suffix}' # Get project's root directory def project_root(): diff --git a/common/pe_ops.py b/common/pe_ops.py index 572ac0e..ba23828 100644 --- a/common/pe_ops.py +++ b/common/pe_ops.py @@ -21,7 +21,7 @@ def get_pe_file(in_file, fast=True): try: # Analyze detected MZ > PE image buffer pe_file = pefile.PE(data=in_buffer, fast_load=fast) - except: + except Exception: pe_file = None return pe_file @@ -34,7 +34,7 @@ def get_pe_info(pe_file): # Retrieve MZ > PE > FileInfo > StringTable information pe_info = pe_file.FileInfo[0][0].StringTable[0].entries - except: + except Exception: pe_info = {} return pe_info diff --git a/common/system.py b/common/system.py index fab02ef..32056e0 100644 --- a/common/system.py +++ b/common/system.py @@ -6,12 +6,8 @@ Copyright (C) 2022 Plato Mavropoulos """ import sys -import ctypes -import argparse -import traceback from common.text_ops import padder, to_string -from common.path_ops import process_input_files # Get Python Version (tuple) def get_py_ver(): @@ -59,62 +55,6 @@ def check_sys_os(): if os_win: sys.stdout.reconfigure(encoding='utf-8') -# Initialize common argparse arguments -def argparse_init(): - argparser = argparse.ArgumentParser() - - argparser.add_argument('files', type=argparse.FileType('r'), nargs='*') - argparser.add_argument('-e', '--auto-exit', help='skip press enter to exit prompts', action='store_true') - argparser.add_argument('-v', '--version', help='show utility name and version', action='store_true') - argparser.add_argument('-o', '--output-dir', help='extract in given output directory') - argparser.add_argument('-i', '--input-dir', help='extract from given input directory') - - return argparser - -# Initialize Script (must be after argparse) -def script_init(title, arguments, padding=0): - # Pretty Python exception handler - sys.excepthook = nice_exc_handler - - # Check Python Version - check_sys_py() - - # Check OS Platform - check_sys_os() - - # Show Script Title - printer(title, new_line=False) - - # Show Utility Version on demand - if arguments.version: - sys.exit(0) - - # Set console/terminal window title (Windows only) - if get_os_ver()[1]: - ctypes.windll.kernel32.SetConsoleTitleW(title) - - # Process input files and generate output path - input_files,output_path = process_input_files(arguments, sys.argv) - - # Count input files for exit code - input_count = len(input_files) - - return input_count, input_files, output_path, padding - -# https://stackoverflow.com/a/781074 by Torsten Marek -def nice_exc_handler(exc_type, exc_value, tb): - if exc_type is KeyboardInterrupt: - printer('') - else: - printer('Error: Script crashed, please report the following:\n') - - traceback.print_exception(exc_type, exc_value, tb) - - if not is_auto_exit(): - input('\nPress enter to exit') - - sys.exit(127) - # Show message(s) while controlling padding, newline, pausing & separator def printer(in_message='', padd_count=0, new_line=True, pause=False, sep_char=' '): message = to_string(in_message, sep_char) diff --git a/common/templates.py b/common/templates.py new file mode 100644 index 0000000..6eeeff3 --- /dev/null +++ b/common/templates.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Copyright (C) 2022 Plato Mavropoulos +""" + +import os +import sys +import ctypes +import argparse +import traceback + +from common.path_ops import runtime_root, is_path_absolute, safe_path, get_dequoted_path, get_path_files, path_parent, get_extract_path +from common.system import check_sys_py, check_sys_os, get_os_ver, printer, is_auto_exit + +class BIOSUtility: + + def __init__(self, title, check, main, padding=0): + self._title = title + self._main = main + self._check = check + self._padding = padding + self._arguments_kw = {} + + # Initialize argparse argument parser + self._argparser = argparse.ArgumentParser() + + self._argparser.add_argument('files', type=argparse.FileType('r', encoding='utf-8'), nargs='*') + self._argparser.add_argument('-e', '--auto-exit', help='skip press enter to exit prompts', action='store_true') + self._argparser.add_argument('-v', '--version', help='show utility name and version', action='store_true') + self._argparser.add_argument('-o', '--output-dir', help='extract in given output directory') + self._argparser.add_argument('-i', '--input-dir', help='extract from given input directory') + + self._arguments,self._arguments_unk = self._argparser.parse_known_args() + + # Managed Python exception handler + sys.excepthook = self._exception_handler + + # Check Python Version + check_sys_py() + + # Check OS Platform + check_sys_os() + + # Show Script Title + printer(self._title, new_line=False) + + # Show Utility Version on demand + if self._arguments.version: + sys.exit(0) + + # Set console/terminal window title (Windows only) + if get_os_ver()[1]: + ctypes.windll.kernel32.SetConsoleTitleW(self._title) + + # Process input files and generate output path + self._process_input_files() + + # Count input files for exit code + self.exit_code = len(self._input_files) + + def parse_argument(self, *args, **kwargs): + _dest = self._argparser.add_argument(*args, **kwargs).dest + self._arguments = self._argparser.parse_known_args(self._arguments_unk)[0] + self._arguments_kw.update({_dest: self._arguments.__dict__[_dest]}) + + def run_utility(self): + for _input_file in self._input_files: + _input_name = os.path.basename(_input_file) + + printer(['***', _input_name], self._padding) + + if not self._check(_input_file): + printer('Error: This is not a supported input!', self._padding + 4) + + continue # Next input file + + _extract_path = os.path.join(self._output_path, get_extract_path(_input_name)) + + if self._main(_input_file, _extract_path, self._padding + 4, **self._arguments_kw) in [0, None]: + self.exit_code -= 1 + + #print(self.exit_code) + + printer('Done!', pause=True) + + sys.exit(self.exit_code) + + # Process input files + def _process_input_files(self): + self._input_files = [] + + if len(sys.argv) >= 2: + # Drag & Drop or CLI + if self._arguments.input_dir: + _input_path_user = self._arguments.input_dir + _input_path_full = self._get_input_path(_input_path_user) if _input_path_user else '' + self._input_files = get_path_files(_input_path_full) + else: + # Parse list of input files (i.e. argparse FileType objects) + for _file_object in self._arguments.files: + # Store each argparse FileType object's name (i.e. path) + self._input_files.append(_file_object.name) + # Close each argparse FileType object (i.e. allow input file changes) + _file_object.close() + + # Set output fallback value for missing argparse Output and Input Path + _output_fallback = path_parent(self._input_files[0]) if self._input_files else None + + # Set output path via argparse Output path or argparse Input path or first input file path + _output_path = self._arguments.output_dir or self._arguments.input_dir or _output_fallback + else: + # Script w/o parameters + _input_path_user = get_dequoted_path(input('\nEnter input directory path: ')) + _input_path_full = self._get_input_path(_input_path_user) if _input_path_user else '' + self._input_files = get_path_files(_input_path_full) + + _output_path = get_dequoted_path(input('\nEnter output directory path: ')) + + self._output_path = self._get_input_path(_output_path) + + # Get absolute input file path + @staticmethod + def _get_input_path(input_path): + if not input_path: + # Use runtime directory if no user path is specified + absolute_path = runtime_root() + else: + # Check if user specified path is absolute + if is_path_absolute(input_path): + absolute_path = input_path + # Otherwise, make it runtime directory relative + else: + absolute_path = safe_path(runtime_root(), input_path) + + return absolute_path + + # https://stackoverflow.com/a/781074 by Torsten Marek + @staticmethod + def _exception_handler(exc_type, exc_value, exc_traceback): + if exc_type is KeyboardInterrupt: + printer('') + else: + printer('Error: Utility crashed, please report the following:\n') + + traceback.print_exception(exc_type, exc_value, exc_traceback) + + if not is_auto_exit(): + input('\nPress enter to exit') + + sys.exit(127) diff --git a/external/requirements.txt b/external/requirements.txt index f6b760a..798c16c 100644 --- a/external/requirements.txt +++ b/external/requirements.txt @@ -1,2 +1,2 @@ -lznt1==0.2 -pefile==2022.5.30 +lznt1 >= 0.2 +pefile >= 2022.5.30 From c1f4ab912167eca865121529efa3941d1d7c647c Mon Sep 17 00:00:00 2001 From: Plato Mavropoulos Date: Fri, 7 Oct 2022 01:32:30 +0300 Subject: [PATCH 48/52] Updated Dell PFS/PKG Update Extractor v6.0_a15 Padding is now added between sub-PFS PFAT Entry gaps Thanks for the help @NikolajSchlej --- Dell_PFS_Extract.py | 94 +++++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index c0d981c..04701f4 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Dell PFS/PKG Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Dell PFS/PKG Update Extractor v6.0_a13' +TITLE = 'Dell PFS/PKG Update Extractor v6.0_a15' import os import io @@ -192,82 +192,82 @@ class DellPfsPfatMetadata(ctypes.LittleEndianStructure): # Each section starts with a 0x30 header, which begins with pattern 72135500. # The section length is found at 0x10-0x14 and its (optional) MD5 hash at 0x20-0x30. # Section data can be raw or LZMA2 (7zXZ) compressed. The latter contains the PFS update image. -def is_pfs_pkg(in_file): - in_buffer = file_to_bytes(in_file) +def is_pfs_pkg(input_file): + input_buffer = file_to_bytes(input_file) - return PAT_DELL_PKG.search(in_buffer) + return PAT_DELL_PKG.search(input_buffer) # The Dell PFS update images usually contain multiple sections. # Each section is zlib-compressed with header pattern ********++EEAA761BECBB20F1E651--789C, # where ******** is the zlib stream size, ++ is the section type and -- the header Checksum XOR 8. # The "Firmware" section has type AA and its files are stored in PFS format. # The "Utility" section has type BB and its files are stored in PFS, BIN or 7z formats. -def is_pfs_hdr(in_file): - in_buffer = file_to_bytes(in_file) +def is_pfs_hdr(input_file): + input_buffer = file_to_bytes(input_file) - return bool(PAT_DELL_HDR.search(in_buffer)) + return bool(PAT_DELL_HDR.search(input_buffer)) # Each section is followed by the footer pattern ********EEAAEE8F491BE8AE143790--, # where ******** is the zlib stream size and ++ the footer Checksum XOR 8. -def is_pfs_ftr(in_file): - in_buffer = file_to_bytes(in_file) +def is_pfs_ftr(input_file): + input_buffer = file_to_bytes(input_file) - return bool(PAT_DELL_FTR.search(in_buffer)) + return bool(PAT_DELL_FTR.search(input_buffer)) # Check if input is Dell PFS/PKG image -def is_dell_pfs(in_file): - in_buffer = file_to_bytes(in_file) +def is_dell_pfs(input_file): + input_buffer = file_to_bytes(input_file) - is_pkg = is_pfs_pkg(in_buffer) + is_pkg = is_pfs_pkg(input_buffer) - is_hdr = is_pfs_hdr(in_buffer) + is_hdr = is_pfs_hdr(input_buffer) - is_ftr = is_pfs_ftr(in_buffer) + is_ftr = is_pfs_ftr(input_buffer) return bool(is_pkg or is_hdr and is_ftr) # Parse & Extract Dell PFS/PKG Update image -def pfs_pkg_parse(in_file, output_path, padding=0, structure=True, advanced=True): - in_buffer = file_to_bytes(in_file) +def pfs_pkg_parse(input_file, extract_path, padding=0, structure=True, advanced=True): + input_buffer = file_to_bytes(input_file) - make_dirs(output_path, delete=True) + make_dirs(extract_path, delete=True) - is_dell_pkg = is_pfs_pkg(in_buffer) + is_dell_pkg = is_pfs_pkg(input_buffer) if is_dell_pkg: - pfs_results = thinos_pkg_extract(in_buffer, output_path) + pfs_results = thinos_pkg_extract(input_buffer, extract_path) else: - pfs_results = {path_stem(in_file) if os.path.isfile(in_file) else 'Image': in_buffer} + pfs_results = {path_stem(input_file) if os.path.isfile(input_file) else 'Image': input_buffer} # Parse each Dell PFS image contained in the input file for pfs_index,(pfs_name,pfs_buffer) in enumerate(pfs_results.items(), start=1): # At ThinOS PKG packages, multiple PFS images may be included in separate model-named folders - pfs_path = os.path.join(output_path, f'{pfs_index} {pfs_name}') if is_dell_pkg else output_path + pfs_path = os.path.join(extract_path, f'{pfs_index} {pfs_name}') if is_dell_pkg else extract_path # Parse each PFS ZLIB section for zlib_offset in get_section_offsets(pfs_buffer): # Call the PFS ZLIB section parser function pfs_section_parse(pfs_buffer, zlib_offset, pfs_path, pfs_name, pfs_index, 1, False, padding, structure, advanced) # Extract Dell ThinOS PKG 7zXZ -def thinos_pkg_extract(in_file, output_path): - in_buffer = file_to_bytes(in_file) +def thinos_pkg_extract(input_file, extract_path): + input_buffer = file_to_bytes(input_file) # Initialize PFS results (Name: Buffer) pfs_results = {} # Search input image for ThinOS PKG 7zXZ header - thinos_pkg_match = PAT_DELL_PKG.search(in_buffer) + thinos_pkg_match = PAT_DELL_PKG.search(input_buffer) lzma_len_off = thinos_pkg_match.start() + 0x10 - lzma_len_int = int.from_bytes(in_buffer[lzma_len_off:lzma_len_off + 0x4], 'little') + lzma_len_int = int.from_bytes(input_buffer[lzma_len_off:lzma_len_off + 0x4], 'little') lzma_bin_off = thinos_pkg_match.end() - 0x5 - lzma_bin_dat = in_buffer[lzma_bin_off:lzma_bin_off + lzma_len_int] + lzma_bin_dat = input_buffer[lzma_bin_off:lzma_bin_off + lzma_len_int] # Check if the compressed 7zXZ stream is complete if len(lzma_bin_dat) != lzma_len_int: return pfs_results - working_path = os.path.join(output_path, 'THINOS_PKG_TEMP') + working_path = os.path.join(extract_path, 'THINOS_PKG_TEMP') make_dirs(working_path, delete=True) @@ -319,7 +319,7 @@ def get_section_offsets(buffer): return pfs_zlib_list # Dell PFS ZLIB Section Parser -def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padding=0, structure=True, advanced=True): +def pfs_section_parse(zlib_data, zlib_start, extract_path, pfs_name, pfs_index, pfs_count, is_rec, padding=0, structure=True, advanced=True): is_zlib_error = False # Initialize PFS ZLIB-related error state section_type = zlib_data[zlib_start - 0x1] # Byte before PFS ZLIB Section pattern is Section Type (e.g. AA, BB) @@ -329,7 +329,7 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p printer(f'Extracting Dell PFS {pfs_index} > {pfs_name} > {section_name}', padding) # Set PFS ZLIB Section extraction sub-directory path - section_path = os.path.join(output_path, safe_name(section_name)) + section_path = os.path.join(extract_path, safe_name(section_name)) # Create extraction sub-directory and delete old (if present, not in recursions) make_dirs(section_path, delete=(not is_rec), parents=True, exist_ok=True) @@ -395,7 +395,7 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padding, structure, advanced) # Parse & Extract Dell PFS Volume -def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, padding=0, structure=True, advanced=True): +def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, extract_path, padding=0, structure=True, advanced=True): # Show PFS Volume indicator if structure: printer('PFS Volume:', padding) @@ -619,7 +619,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, padding=0, sub_pfs_name = f'{info_all[pfs_count - 2][1]} v{info_all[pfs_count - 2][2]}' if info_all else ' UNKNOWN' # Set the sub-PFS output path (create sub-folders for each sub-PFS and its ZLIB sections) - sub_pfs_path = os.path.join(output_path, f'{pfs_count} {safe_name(sub_pfs_name)}') + sub_pfs_path = os.path.join(extract_path, f'{pfs_count} {safe_name(sub_pfs_name)}') # Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count) pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, padding + 4, structure, advanced) @@ -691,7 +691,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, padding=0, # Write/Extract PFS Entry files for file in write_files: full_name = f'{pfs_index} {pfs_name} -- {file_index} {file_name} v{file_version}' # Full PFS Entry Name - pfs_file_write(file[0], file[1], file_type, full_name, output_path, padding, structure, advanced) + pfs_file_write(file[0], file[1], file_type, full_name, extract_path, padding, structure, advanced) # Get PFS Footer Data after PFS Header Payload pfs_footer = buffer[PFS_HEAD_LEN + pfs_hdr.PayloadSize:PFS_HEAD_LEN + pfs_hdr.PayloadSize + PFS_FOOT_LEN] @@ -766,7 +766,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding=0, structure=True): pfat_footer = entry_data[PFS_HEAD_LEN + entry_hdr.PayloadSize:PFS_HEAD_LEN + entry_hdr.PayloadSize + PFS_FOOT_LEN] # Parse all sub-PFS Payload PFAT Entries - pfat_data_all = [] # Storage for all sub-PFS PFAT Entries Order/Offset & Payload/Raw Data + pfat_entries_all = [] # Storage for all sub-PFS PFAT Entries Order/Offset & Payload/Raw Data pfat_entry_start = 0 # Increasing sub-PFS PFAT Entry start offset pfat_entry_index = 0 # Increasing sub-PFS PFAT Entry count index _, pfs_entry_size = get_pfs_entry(pfat_payload, 0) # Get initial PFS PFAT Entry Size for loop @@ -855,26 +855,36 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding=0, structure=True): pfat_entry_data_raw = pfat_payload_data # Prefer Data from PFAT Block, in case PFAT Entry differs # Store each sub-PFS PFAT Entry Order/Offset and Payload/Raw Data (w/o PFAT) - pfat_data_all.append((pfat_entry_off, pfat_entry_data_raw)) + pfat_entries_all.append((pfat_entry_off, pfat_entry_data_raw)) pfat_entry_start = pfat_next_entry # Next sub-PFS PFAT Entry starts after sub-PFS Entry Metadata Signature pfat_entry_index += 1 - pfat_data_all.sort() # Sort all sub-PFS PFAT Entries payloads/data based on their Order/Offset + pfat_entries_all.sort() # Sort all sub-PFS PFAT Entries payloads/data based on their Order/Offset - entry_data = b'' # Initialize new sub-PFS Entry Data - for pfat_data in pfat_data_all: - entry_data += pfat_data[1] # Merge all sub-PFS PFAT Entry Payload/Raw into the final sub-PFS Entry Data + sorted_start_expected = pfat_entries_all[0][0] # Initialize sub-PFS PFAT Entry expected Offset + final_entry_data = b'' # Initialize final sub-PFS Entry Data from ordered PFAT Entries - # Verify that the Order/Offset of the last PFAT Entry w/ its Size matches the final sub-PFS Entry Data Size - if len(entry_data) != pfat_data_all[-1][0] + len(pfat_data_all[-1][1]): + # Parse all sorted sub-PFS PFAT Entries and merge their payload/data + for sorted_start,sorted_data in pfat_entries_all: + # Fill any data gaps between sorted sub-PFS PFAT Entries with padding + if sorted_start != sorted_start_expected: + # The sub-PFS PFAT Entry expected Start is the previous Offset + Size + final_entry_data += b'\xFF' * (sorted_start - sorted_start_expected) + + final_entry_data += sorted_data # Append sorted sub-PFS PFAT Entry payload/data + + sorted_start_expected = sorted_start + len(sorted_data) # Set next sub-PFS PFAT Entry expected Start + + # Verify that the end offset of the last PFAT Entry matches the final sub-PFS Entry Data Size + if len(final_entry_data) != pfat_entries_all[-1][0] + len(pfat_entries_all[-1][1]): printer('Error: Detected sub-PFS PFAT Entry Buffer & Last Offset Size mismatch!', padding + 8) # Analyze sub-PFS Footer Structure chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padding + 4, structure) - return entry_data + return final_entry_data # Get Dell PFS Entry Structure & Size via its Version def get_pfs_entry(buffer, offset): From 5d5dc5828b644ee82c1a41bf2c43aec414fcde5a Mon Sep 17 00:00:00 2001 From: Plato Mavropoulos Date: Sun, 16 Oct 2022 18:21:23 +0300 Subject: [PATCH 49/52] Updated Dell PFS Update Extractor v6.0_a16 Improved handling of sub-PFS PFAT Entries with offset conflicts and/or data gaps --- Dell_PFS_Extract.py | 115 ++++++++++++++++++++++++++++---------------- README.md | 8 +-- common/system.py | 4 +- 3 files changed, 80 insertions(+), 47 deletions(-) diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index 04701f4..571a35d 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -3,11 +3,11 @@ """ Dell PFS Extract -Dell PFS/PKG Update Extractor +Dell PFS Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -TITLE = 'Dell PFS/PKG Update Extractor v6.0_a15' +TITLE = 'Dell PFS Update Extractor v6.0_a16' import os import io @@ -22,6 +22,7 @@ sys.dont_write_bytecode = True from common.checksums import get_chk_8_xor from common.comp_szip import is_szip_supported, szip_decompress +from common.num_ops import get_ordinal from common.path_ops import del_dirs, get_path_files, make_dirs, path_name, path_parent, path_stem, safe_name from common.patterns import PAT_DELL_FTR, PAT_DELL_HDR, PAT_DELL_PKG from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t, uint64_t @@ -169,10 +170,10 @@ class DellPfsMetadata(ctypes.LittleEndianStructure): class DellPfsPfatMetadata(ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [ - ('OffsetTop', uint32_t), # 0x00 + ('Address', uint32_t), # 0x00 ('Unknown0', uint32_t), # 0x04 - ('OffsetBase', uint32_t), # 0x08 - ('BlockSize', uint32_t), # 0x0C + ('Offset', uint32_t), # 0x08 Matches BG Script > I0 + ('DataSize', uint32_t), # 0x0C Matches BG Script > I2 & Header > Data Size ('Unknown1', uint32_t), # 0x10 ('Unknown2', uint32_t), # 0x14 ('Unknown3', uint8_t), # 0x18 @@ -180,13 +181,13 @@ class DellPfsPfatMetadata(ctypes.LittleEndianStructure): ] def struct_print(self, p): - printer(['Offset Top :', f'0x{self.OffsetTop:X}'], p, False) - printer(['Unknown 0 :', f'0x{self.Unknown0:X}'], p, False) - printer(['Offset Base:', f'0x{self.OffsetBase:X}'], p, False) - printer(['Block Size :', f'0x{self.BlockSize:X}'], p, False) - printer(['Unknown 1 :', f'0x{self.Unknown1:X}'], p, False) - printer(['Unknown 2 :', f'0x{self.Unknown2:X}'], p, False) - printer(['Unknown 3 :', f'0x{self.Unknown3:X}'], p, False) + printer(['Address :', f'0x{self.Address:X}'], p, False) + printer(['Unknown 0:', f'0x{self.Unknown0:X}'], p, False) + printer(['Offset :', f'0x{self.Offset:X}'], p, False) + printer(['Length :', f'0x{self.DataSize:X}'], p, False) + printer(['Unknown 1:', f'0x{self.Unknown1:X}'], p, False) + printer(['Unknown 2:', f'0x{self.Unknown2:X}'], p, False) + printer(['Unknown 3:', f'0x{self.Unknown3:X}'], p, False) # The Dell ThinOS PKG update images usually contain multiple sections. # Each section starts with a 0x30 header, which begins with pattern 72135500. @@ -226,7 +227,7 @@ def is_dell_pfs(input_file): return bool(is_pkg or is_hdr and is_ftr) -# Parse & Extract Dell PFS/PKG Update image +# Parse & Extract Dell PFS Update image def pfs_pkg_parse(input_file, extract_path, padding=0, structure=True, advanced=True): input_buffer = file_to_bytes(input_file) @@ -768,7 +769,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding=0, structure=True): # Parse all sub-PFS Payload PFAT Entries pfat_entries_all = [] # Storage for all sub-PFS PFAT Entries Order/Offset & Payload/Raw Data pfat_entry_start = 0 # Increasing sub-PFS PFAT Entry start offset - pfat_entry_index = 0 # Increasing sub-PFS PFAT Entry count index + pfat_entry_index = 1 # Increasing sub-PFS PFAT Entry count index _, pfs_entry_size = get_pfs_entry(pfat_payload, 0) # Get initial PFS PFAT Entry Size for loop while len(pfat_payload[pfat_entry_start:pfat_entry_start + pfs_entry_size]) == pfs_entry_size: # Get sub-PFS PFAT Entry Structure & Size info @@ -785,9 +786,12 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding=0, structure=True): # Get sub-PFS PFAT Header Structure values pfat_hdr = get_struct(pfat_payload, pfat_hdr_off, IntelBiosGuardHeader) + # Get ordinal value of the sub-PFS PFAT Entry Index + pfat_entry_idx_ord = get_ordinal(pfat_entry_index) + # Show sub-PFS PFAT Header Structure info if structure: - printer(f'PFAT Block {pfat_entry_index} Header:\n', padding + 12) + printer(f'PFAT Block {pfat_entry_idx_ord} - Header:\n', padding + 12) pfat_hdr.struct_print(padding + 16) pfat_script_start = pfat_hdr_off + PFAT_HDR_LEN # PFAT Block Script Start @@ -800,7 +804,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding=0, structure=True): # The PFAT Script End should match the total Entry Data Size w/o PFAT block if pfat_hdr_bgs_size != pfat_entry.DataSize - pfat_hdr.DataSize: - printer('Error: Detected sub-PFS PFAT Entry Header & PFAT Size mismatch!', padding + 16) + printer(f'Error: Detected sub-PFS PFAT Block {pfat_entry_idx_ord} Header & PFAT Size mismatch!', padding + 16) # Get PFAT Header Flags (SFAM, ProtectEC, GFXMitDis, FTU, Reserved) is_sfam,_,_,_,_ = pfat_hdr.get_flags() @@ -812,12 +816,12 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding=0, structure=True): # Show sub-PFS PFAT Signature Structure info if structure: - printer(f'PFAT Block {pfat_entry_index} Signature:\n', padding + 12) + printer(f'PFAT Block {pfat_entry_idx_ord} - Signature:\n', padding + 12) pfat_sig.struct_print(padding + 16) # Show PFAT Script via BIOS Guard Script Tool if structure: - printer(f'PFAT Block {pfat_entry_index} Script:\n', padding + 12) + printer(f'PFAT Block {pfat_entry_idx_ord} - Script:\n', padding + 12) _ = parse_bg_script(pfat_script_data, padding + 16) @@ -826,65 +830,94 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding=0, structure=True): # PFAT Script OpCode #2 > Operand #3 stores the payload Offset in final image pfat_entry_off = int.from_bytes(pfat_script_data[0xC:0x10], 'little') + # We can get each payload's length from PFAT Script > OpCode #4 (set I2 imm) + # PFAT Script OpCode #4 > Operand #3 stores the payload Length in final image + pfat_entry_len = int.from_bytes(pfat_script_data[0x1C:0x20], 'little') + + # Check that the PFAT Entry Length from Header & Script match + if pfat_hdr.DataSize != pfat_entry_len: + printer(f'Error: Detected sub-PFS PFAT Block {pfat_entry_idx_ord} Header & Script Length mismatch!', padding + 12) + + # Initialize sub-PFS PFAT Entry Metadata Address + pfat_entry_adr = pfat_entry_off + # Parse sub-PFS PFAT Entry/Block Metadata if len(pfat_entry_met) >= PFS_PFAT_LEN: # Get sub-PFS PFAT Metadata Structure values pfat_met = get_struct(pfat_entry_met, 0, DellPfsPfatMetadata) + # Store sub-PFS PFAT Entry Metadata Address + pfat_entry_adr = pfat_met.Address + # Show sub-PFS PFAT Metadata Structure info if structure: - printer(f'PFAT Block {pfat_entry_index} Metadata:\n', padding + 12) + printer(f'PFAT Block {pfat_entry_idx_ord} - Metadata:\n', padding + 12) pfat_met.struct_print(padding + 16) - # Another way to get each PFAT Entry payload's Order is from its Metadata at 0x8-0xC, if applicable - # Check that the PFAT Entry payload Order/Offset from PFAT Script matches the one from PFAT Metadata - if pfat_entry_off != pfat_met.OffsetBase: - printer('Error: Detected sub-PFS PFAT Entry Metadata & PFAT Base Offset mismatch!', padding + 16) - pfat_entry_off = pfat_met.OffsetBase # Prefer Offset from Metadata, in case PFAT Script differs + # Another way to get each PFAT Entry Offset is from its Metadata, if applicable + # Check that the PFAT Entry Offsets from PFAT Script and PFAT Metadata match + if pfat_entry_off != pfat_met.Offset: + printer(f'Error: Detected sub-PFS PFAT Block {pfat_entry_idx_ord} Metadata & PFAT Offset mismatch!', padding + 16) + pfat_entry_off = pfat_met.Offset # Prefer Offset from Metadata, in case PFAT Script differs + + # Another way to get each PFAT Entry Length is from its Metadata, if applicable + # Check that the PFAT Entry Length from PFAT Script and PFAT Metadata match + if not (pfat_hdr.DataSize == pfat_entry_len == pfat_met.DataSize): + printer(f'Error: Detected sub-PFS PFAT Block {pfat_entry_idx_ord} Metadata & PFAT Length mismatch!', padding + 16) # Check that the PFAT Entry payload Size from PFAT Header matches the one from PFAT Metadata - if pfat_hdr.DataSize != pfat_met.BlockSize: - printer('Error: Detected sub-PFS PFAT Entry Metadata & PFAT Block Size mismatch!', padding + 16) + if pfat_hdr.DataSize != pfat_met.DataSize: + printer(f'Error: Detected sub-PFS PFAT Block {pfat_entry_idx_ord} Metadata & PFAT Block Size mismatch!', padding + 16) # Get sub-PFS Entry Raw Data by subtracting PFAT Header & Script from PFAT Entry Data pfat_entry_data_raw = pfat_entry_data[pfat_hdr_bgs_size:] # The sub-PFS Entry Raw Data (w/o PFAT Header & Script) should match with the PFAT Block payload if pfat_entry_data_raw != pfat_payload_data: - printer('Error: Detected sub-PFS PFAT Entry w/o PFAT & PFAT Block Data mismatch!', padding + 16) + printer(f'Error: Detected sub-PFS PFAT Block {pfat_entry_idx_ord} w/o PFAT & PFAT Block Data mismatch!', padding + 16) pfat_entry_data_raw = pfat_payload_data # Prefer Data from PFAT Block, in case PFAT Entry differs - # Store each sub-PFS PFAT Entry Order/Offset and Payload/Raw Data (w/o PFAT) - pfat_entries_all.append((pfat_entry_off, pfat_entry_data_raw)) + # Store each sub-PFS PFAT Entry/Block Offset, Address, Ordinal Index and Payload/Raw Data + # Goal is to sort these based on Offset first and Address second, in cases of same Offset + # For example, Precision 3430 has two PFAT Entries with the same Offset of 0x40000 at both + # BG Script and PFAT Metadata but their PFAT Metadata Address is 0xFF040000 and 0xFFA40000 + pfat_entries_all.append((pfat_entry_off, pfat_entry_adr, pfat_entry_idx_ord, pfat_entry_data_raw)) + + # Check if next sub-PFS PFAT Entry offset is valid + if pfat_next_entry <= 0: + printer(f'Error: Detected sub-PFS PFAT Block {pfat_entry_idx_ord} with invalid next PFAT Block offset!', padding + 16) + pfat_next_entry += pfs_entry_size # Avoid a potential infinite loop if next sub-PFS PFAT Entry offset is bad pfat_entry_start = pfat_next_entry # Next sub-PFS PFAT Entry starts after sub-PFS Entry Metadata Signature pfat_entry_index += 1 - pfat_entries_all.sort() # Sort all sub-PFS PFAT Entries payloads/data based on their Order/Offset + pfat_entries_all.sort() # Sort all sub-PFS PFAT Entries based on their Offset/Address - sorted_start_expected = pfat_entries_all[0][0] # Initialize sub-PFS PFAT Entry expected Offset - final_entry_data = b'' # Initialize final sub-PFS Entry Data from ordered PFAT Entries + block_start_exp = 0 # Initialize sub-PFS PFAT Entry expected Offset + total_pfat_data = b'' # Initialize final/ordered sub-PFS Entry Data # Parse all sorted sub-PFS PFAT Entries and merge their payload/data - for sorted_start,sorted_data in pfat_entries_all: + for block_start,_,block_index,block_data in pfat_entries_all: # Fill any data gaps between sorted sub-PFS PFAT Entries with padding - if sorted_start != sorted_start_expected: - # The sub-PFS PFAT Entry expected Start is the previous Offset + Size - final_entry_data += b'\xFF' * (sorted_start - sorted_start_expected) + # For example, Precision 7960 v0.16.68 has gap at 0x1190000-0x11A0000 + block_data_gap = block_start - block_start_exp + if block_data_gap > 0: + printer(f'Warning: Filled sub-PFS PFAT {block_index} data gap 0x{block_data_gap:X} [0x{block_start_exp:X}-0x{block_start:X}]!', padding + 8) + total_pfat_data += b'\xFF' * block_data_gap # The sub-PFS PFAT Entry expected Start is the previous Offset + Size - final_entry_data += sorted_data # Append sorted sub-PFS PFAT Entry payload/data + total_pfat_data += block_data # Append sorted sub-PFS PFAT Entry payload/data - sorted_start_expected = sorted_start + len(sorted_data) # Set next sub-PFS PFAT Entry expected Start + block_start_exp = len(total_pfat_data) # Set next sub-PFS PFAT Entry expected Start # Verify that the end offset of the last PFAT Entry matches the final sub-PFS Entry Data Size - if len(final_entry_data) != pfat_entries_all[-1][0] + len(pfat_entries_all[-1][1]): - printer('Error: Detected sub-PFS PFAT Entry Buffer & Last Offset Size mismatch!', padding + 8) + if len(total_pfat_data) != pfat_entries_all[-1][0] + len(pfat_entries_all[-1][3]): + printer('Error: Detected sub-PFS PFAT total buffer size and last block end mismatch!', padding + 8) # Analyze sub-PFS Footer Structure chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padding + 4, structure) - return final_entry_data + return total_pfat_data # Get Dell PFS Entry Structure & Size via its Version def get_pfs_entry(buffer, offset): diff --git a/README.md b/README.md index 6b5d67b..7446fa0 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ * [**Apple EFI Package Extractor**](#apple-efi-package-extractor) * [**Apple EFI PBZX Extractor**](#apple-efi-pbzx-extractor) * [**Award BIOS Module Extractor**](#award-bios-module-extractor) -* [**Dell PFS/PKG Update Extractor**](#dell-pfspkg-update-extractor) +* [**Dell PFS Update Extractor**](#dell-pfs-update-extractor) * [**Fujitsu SFX BIOS Extractor**](#fujitsu-sfx-bios-extractor) * [**Fujitsu UPC BIOS Extractor**](#fujitsu-upc-bios-extractor) * [**Insyde iFlash/iFdPacker Extractor**](#insyde-iflashifdpacker-extractor) @@ -252,17 +252,17 @@ To run the utility, you must have the following 3rd party tool at the "external" ![]() -## **Dell PFS/PKG Update Extractor** +## **Dell PFS Update Extractor** ![]() #### **Description** -Parses Dell PFS/PKG Update images and extracts their Firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and Utilities (e.g. Flasher etc) component sections. It supports all Dell PFS/PKG revisions and formats, including those which are originally LZMA compressed in ThinOS packages, ZLIB compressed or Intel BIOS Guard (PFAT) protected. The output comprises only final firmware components which are directly usable by end users. +Parses Dell PFS Update images and extracts their Firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and Utilities (e.g. Flasher etc) component sections. It supports all Dell PFS revisions and formats, including those which are originally LZMA compressed in ThinOS packages (PKG), ZLIB compressed or Intel BIOS Guard (PFAT) protected. The output comprises only final firmware components which are directly usable by end users. #### **Usage** -You can either Drag & Drop or manually enter Dell PFS/PKG Update images(s). Optional arguments: +You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional arguments: * -h or --help : show help message and exit * -v or --version : show utility name and version diff --git a/common/system.py b/common/system.py index 32056e0..9598e30 100644 --- a/common/system.py +++ b/common/system.py @@ -30,8 +30,8 @@ def is_auto_exit(): def check_sys_py(): sys_py = get_py_ver() - if sys_py < (3,8): - sys.stdout.write(f'\nError: Python >= 3.8 required, not {sys_py[0]}.{sys_py[1]}!') + if sys_py < (3,10): + sys.stdout.write(f'\nError: Python >= 3.10 required, not {sys_py[0]}.{sys_py[1]}!') if not is_auto_exit(): # noinspection PyUnresolvedReferences From 71cbfdaf8b2326509a041da5d58cb1d0d05afad7 Mon Sep 17 00:00:00 2001 From: Plato Mavropoulos Date: Sun, 16 Oct 2022 18:35:29 +0300 Subject: [PATCH 50/52] Replace contents of no longer relevant comment --- Dell_PFS_Extract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index 571a35d..b8cb686 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -904,7 +904,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding=0, structure=True): block_data_gap = block_start - block_start_exp if block_data_gap > 0: printer(f'Warning: Filled sub-PFS PFAT {block_index} data gap 0x{block_data_gap:X} [0x{block_start_exp:X}-0x{block_start:X}]!', padding + 8) - total_pfat_data += b'\xFF' * block_data_gap # The sub-PFS PFAT Entry expected Start is the previous Offset + Size + total_pfat_data += b'\xFF' * block_data_gap # Use 0xFF padding to fill in data gaps in PFAT UEFI firmware images total_pfat_data += block_data # Append sorted sub-PFS PFAT Entry payload/data From 65467da21c51744a59959eef547fd08e118a0709 Mon Sep 17 00:00:00 2001 From: Plato Mavropoulos Date: Sun, 23 Oct 2022 22:34:35 +0300 Subject: [PATCH 51/52] Auto-resolve extract directory name conflicts If the output (extracted) folder for a given input file already exists, a new name is generated automatically (i.e. _2nd, _3rd, _4th etc) --- README.md | 32 ++++++++++++++++---------------- common/templates.py | 26 ++++++++++++++++++-------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 7446fa0..0d8c29c 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s) * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** @@ -70,7 +70,7 @@ You can either Drag & Drop or manually enter AMI UCP Update executable file(s). * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts * -c or --checksum : verify AMI UCP Checksums (slow) #### **Compatibility** @@ -108,7 +108,7 @@ You can either Drag & Drop or manually enter Apple EFI IM4P file(s). Optional ar * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** @@ -138,7 +138,7 @@ You can either Drag & Drop or manually enter Apple EFI image file(s). Optional a * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts * -r or --rename : rename EFI image based on its tag #### **Compatibility** @@ -172,7 +172,7 @@ You can either Drag & Drop or manually enter Apple EFI PKG package file(s). Opti * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** @@ -204,7 +204,7 @@ You can either Drag & Drop or manually enter Apple EFI PBZX image file(s). Optio * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** @@ -236,7 +236,7 @@ You can either Drag & Drop or manually enter Award BIOS image file(s). Optional * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** @@ -268,7 +268,7 @@ You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts * -a or --advanced : extract signatures and metadata * -s or --structure : show PFS structure information @@ -302,7 +302,7 @@ You can either Drag & Drop or manually enter Fujitsu SFX BIOS image file(s). Opt * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** @@ -334,7 +334,7 @@ You can either Drag & Drop or manually enter Fujitsu UPC BIOS image file(s). Opt * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** @@ -366,7 +366,7 @@ You can either Drag & Drop or manually enter Insyde iFlash/iFdPacker Update imag * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** @@ -396,7 +396,7 @@ You can either Drag & Drop or manually enter Panasonic BIOS Package executable f * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** @@ -433,7 +433,7 @@ You can either Drag & Drop or manually enter Phoenix Tools Development Kit (TDK) * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** @@ -465,7 +465,7 @@ You can either Drag & Drop or manually enter Portwell UEFI Unpacker EFI executab * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** @@ -503,7 +503,7 @@ You can either Drag & Drop or manually enter Toshiba BIOS COM image file(s). Opt * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** @@ -535,7 +535,7 @@ You can either Drag & Drop or manually enter VAIO Packaging Manager executable f * -v or --version : show utility name and version * -i or --input-dir : extract from given input directory * -o or --output-dir : extract in given output directory -* -e or --auto-exit : skip press enter to exit prompts +* -e or --auto-exit : skip all user action prompts #### **Compatibility** diff --git a/common/templates.py b/common/templates.py index 6eeeff3..f69af33 100644 --- a/common/templates.py +++ b/common/templates.py @@ -11,11 +11,14 @@ import ctypes import argparse import traceback -from common.path_ops import runtime_root, is_path_absolute, safe_path, get_dequoted_path, get_path_files, path_parent, get_extract_path -from common.system import check_sys_py, check_sys_os, get_os_ver, printer, is_auto_exit +from common.num_ops import get_ordinal +from common.path_ops import get_dequoted_path, get_extract_path, get_path_files, is_path_absolute, path_parent, runtime_root, safe_path +from common.system import check_sys_os, check_sys_py, get_os_ver, is_auto_exit, printer class BIOSUtility: + MAX_FAT32_ITEMS = 65535 + def __init__(self, title, check, main, padding=0): self._title = title self._main = main @@ -27,7 +30,7 @@ class BIOSUtility: self._argparser = argparse.ArgumentParser() self._argparser.add_argument('files', type=argparse.FileType('r', encoding='utf-8'), nargs='*') - self._argparser.add_argument('-e', '--auto-exit', help='skip press enter to exit prompts', action='store_true') + self._argparser.add_argument('-e', '--auto-exit', help='skip all user action prompts', action='store_true') self._argparser.add_argument('-v', '--version', help='show utility name and version', action='store_true') self._argparser.add_argument('-o', '--output-dir', help='extract in given output directory') self._argparser.add_argument('-i', '--input-dir', help='extract from given input directory') @@ -58,7 +61,7 @@ class BIOSUtility: self._process_input_files() # Count input files for exit code - self.exit_code = len(self._input_files) + self._exit_code = len(self._input_files) def parse_argument(self, *args, **kwargs): _dest = self._argparser.add_argument(*args, **kwargs).dest @@ -78,14 +81,21 @@ class BIOSUtility: _extract_path = os.path.join(self._output_path, get_extract_path(_input_name)) + if os.path.isdir(_extract_path): + for _suffix in range(2, self.MAX_FAT32_ITEMS): + _renamed_path = f'{os.path.normpath(_extract_path)}_{get_ordinal(_suffix)}' + + if not os.path.isdir(_renamed_path): + _extract_path = _renamed_path + + break # Extract path is now unique + if self._main(_input_file, _extract_path, self._padding + 4, **self._arguments_kw) in [0, None]: - self.exit_code -= 1 - - #print(self.exit_code) + self._exit_code -= 1 printer('Done!', pause=True) - sys.exit(self.exit_code) + sys.exit(self._exit_code) # Process input files def _process_input_files(self): From 03ae0cf0706ede5a2a15da0986c19c776d0e6b26 Mon Sep 17 00:00:00 2001 From: Plato Mavropoulos Date: Mon, 31 Oct 2022 17:04:50 +0200 Subject: [PATCH 52/52] Fixed potential crash at files without extension(s) --- common/path_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/path_ops.py b/common/path_ops.py index 26eca72..bcff167 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -81,7 +81,7 @@ def path_stem(in_path): # Get list of path file extensions def path_suffixes(in_path): - return PurePath(in_path).suffixes + return PurePath(in_path).suffixes or [''] # Check if path is absolute def is_path_absolute(in_path):