diff --git a/Dell PFS BIOS Extractor/Dell_PFS_Extract.py b/Dell PFS BIOS Extractor/Dell_PFS_Extract.py index cd57c17..84b66ba 100644 --- a/Dell PFS BIOS Extractor/Dell_PFS_Extract.py +++ b/Dell PFS BIOS Extractor/Dell_PFS_Extract.py @@ -7,7 +7,7 @@ Copyright (C) 2019-2020 Plato Mavropoulos Inspired from https://github.com/LongSoft/PFSExtractor-RS by Nikolaj Schlej """ -title = 'Dell PFS BIOS Extractor v4.2' +title = 'Dell PFS BIOS Extractor v4.5' import os import re @@ -385,7 +385,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) : # 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_match = zlib_bios_pattern.search(entry_data) + 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 @@ -526,11 +526,58 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) : 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 (0xAA type). A zlib-compressed - # PFS Entry Data contains a full PFS structure, like the original Dell PFS BIOS executable - elif zlib_bios_match : - compressed_size = int.from_bytes(entry_data[zlib_bios_match.start() - 0x4:zlib_bios_match.start()], 'little') - entry_data = zlib.decompress(entry_data[zlib_bios_match.start() + 0xC:zlib_bios_match.start() + 0xC + compressed_size]) + # 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 @@ -642,8 +689,7 @@ def bin_is_text(buffer, file_type, is_metadata, is_advanced) : is_text = True write_mode = 'w' extension = '.txt' - if buffer.endswith(b'\x00\x00') : buffer = buffer[:-2] - buffer = buffer.decode('utf-8').replace(';','\n') + buffer = buffer.split(b'\x00')[0].decode('utf-8').replace(';','\n') elif b'= 2 : # Drag & Drop or CLI pfs_exec = [] - for executable in args.executables : - pfs_exec.append(executable.name) + for image in args.images : + pfs_exec.append(image.name) else : # Folder path pfs_exec = [] @@ -753,7 +819,7 @@ else : for name in files : pfs_exec.append(os.path.join(root, name)) -# Process each input Dell PFS BIOS executable +# 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)) @@ -767,25 +833,68 @@ for input_file in pfs_exec : with open(input_file, 'rb') as in_file : input_data = in_file.read() - # The Dell PFS BIOS executables may contain more than one section. Each section is zlib-compressed - # with header pattern ++EEAA761BECBB20F1E651--789C where ++ is the section type and -- a random number - # 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 7-Zip formats. There could be more section types - # but for the purposes of this utility, we are only interested in extracting the "BIOS" section files - zlib_bios_pattern = re.compile(br'\xAA\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) + # Search input image for zlib "BIOS" section header + zlib_bios_hdr_match = zlib_bios_header.search(input_data) - zlib_bios_match = zlib_bios_pattern.search(input_data) # Search input executable for zlib "BIOS" section - - # Check if zlib-compressed "BIOS" section with type 0xAA was found in the executable - if not zlib_bios_match : - print('\n Error: This is not a Dell PFS BIOS executable!') + # 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 data size from the preceding 4 bytes of the "BIOS" section header pattern - compressed_size = int.from_bytes(input_data[zlib_bios_match.start() - 0x4:zlib_bios_match.start()], 'little') + # 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(input_data[zlib_bios_match.start() + 0xC:zlib_bios_match.start() + 0xC + compressed_size]) + input_data = zlib.decompress(compressed_data) output_path = os.path.join(input_dir, '%s%s' % (input_name, input_extension) + '_extracted') # Set extraction directory @@ -800,7 +909,7 @@ for input_file in pfs_exec : pfs_extract(input_data, pfs_index, pfs_name, pfs_count) # Call the Dell PFS.HDR. Extractor function - print('\n Extracted Dell PFS BIOS executable!') + print('\n Extracted Dell PFS BIOS image!') else : input('\nDone!') diff --git a/README.md b/README.md index c13fe82..450293a 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ Various BIOS Utilities for Modding/Research #### **Description** -Parses modern icon-less Dell PFS BIOS executables and extracts their SPI/BIOS/UEFI firmware components. It supports all Dell PFS revisions and formats, including those which are originally 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 BIOS images and extracts their SPI/BIOS/UEFI firmware components. It supports all Dell PFS revisions and formats, including those which are originally 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. #### **Usage** -You can either Drag & Drop or manually enter the full path of a folder containing icon-less Dell PFS BIOS executables. Optional arguments: +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