mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-09 13:52:00 -04:00
Dell PFS BIOS Extractor v4.5
Added PFS section zlib data size & checksum checks Added PFS section zlib footer detection & checks Fixed null character ignoring at info text files Applied various code fixes & improvements
This commit is contained in:
parent
9be7d2b53e
commit
ac1c3580d3
2 changed files with 142 additions and 33 deletions
|
@ -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'<Rimm x-schema="' in buffer[:0x50] : # XML Type
|
||||
is_text = True
|
||||
write_mode = 'w'
|
||||
|
@ -683,17 +729,26 @@ def get_pfs_entry(buffer, offset) :
|
|||
elif pfs_entry_ver == 2 : return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2)
|
||||
else : return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2)
|
||||
|
||||
# Calculate Checksum XOR 8 of data
|
||||
def chk_xor_8(data, init_value) :
|
||||
value = init_value
|
||||
for byte in data : value = value ^ byte
|
||||
value = value ^ 0x0
|
||||
|
||||
return value
|
||||
|
||||
# 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 optional parameter 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))
|
||||
print('\n Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name.__name__))
|
||||
|
||||
input('\nPress enter to exit')
|
||||
|
||||
|
@ -704,6 +759,7 @@ def get_struct(buffer, start_offset, class_name, param_list = None) :
|
|||
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')
|
||||
|
@ -727,7 +783,7 @@ elif user_os.startswith('linux') or user_os == 'darwin' or user_os.find('bsd') !
|
|||
|
||||
# Set argparse Arguments
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('executables', type=argparse.FileType('r'), nargs='*')
|
||||
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()
|
||||
|
||||
|
@ -739,11 +795,21 @@ 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 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 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!')
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue