#!/usr/bin/env python3 """ Dell PFS Extract Dell PFS BIOS Extractor Copyright (C) 2019 Plato Mavropoulos Inspired from https://github.com/LongSoft/PFSExtractor-RS by Nikolaj Schlej """ title = 'Dell PFS BIOS Extractor v3.0' import os import re import sys import zlib 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 # noinspection PyTypeChecker 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) # noinspection PyTypeChecker 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')) # noinspection PyTypeChecker class PFS_ENTRY(ctypes.LittleEndianStructure) : _pack_ = 1 _fields_ = [ ('GUID', uint32_t*4), # 0x00 Little Endian ('HeaderVersion', uint32_t), # 0x10 ('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('= len(buffer)) or (fit_len < struct_len) : print(' Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name)) sys.exit(1) ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len) return structure # Pause after any unexpected Python exception 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 hander 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('executables', 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_entry_size = ctypes.sizeof(PFS_ENTRY) pfs_info_size = ctypes.sizeof(PFS_INFO) if len(sys.argv) >= 2 : # Drag & Drop or CLI pfs_exec = [] for executable in args.executables : pfs_exec.append(executable.name) else : # Folder path pfs_exec = [] in_path = input('\nEnter the full folder path: ') print('\nWorking...') for root, dirs, files in os.walk(in_path): for name in files : pfs_exec.append(os.path.join(root, name)) # Process each input Dell icon-less PFS BIOS executable 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('\nFile: %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() # The Dell icon-less 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) 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 icon-less PFS BIOS executable!') 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') # 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]) 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 = True if args.advanced else False # 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 icon-less PFS BIOS executable!') else : input('\nDone!') sys.exit(0)