mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-13 06:34:42 -04:00
Dell PFS BIOS Extractor v4.8
Added support for PFS images within Dell ThinOS PKG packages Applied various small performance & static analysis code fixes
This commit is contained in:
parent
8e4b2276fa
commit
f4d00ce419
2 changed files with 44 additions and 19 deletions
|
@ -1,18 +1,20 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
#coding=utf-8
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Dell PFS Extract
|
Dell PFS Extract
|
||||||
Dell PFS BIOS Extractor
|
Dell PFS BIOS Extractor
|
||||||
Copyright (C) 2019-2020 Plato Mavropoulos
|
Copyright (C) 2019-2021 Plato Mavropoulos
|
||||||
Inspired from https://github.com/LongSoft/PFSExtractor-RS by Nikolaj Schlej
|
Inspired from https://github.com/LongSoft/PFSExtractor-RS by Nikolaj Schlej
|
||||||
"""
|
"""
|
||||||
|
|
||||||
title = 'Dell PFS BIOS Extractor v4.6'
|
title = 'Dell PFS BIOS Extractor v4.8'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import zlib
|
import zlib
|
||||||
|
import lzma
|
||||||
import shutil
|
import shutil
|
||||||
import struct
|
import struct
|
||||||
import ctypes
|
import ctypes
|
||||||
|
@ -214,7 +216,6 @@ class CHUNK_INFO_FTR(ctypes.LittleEndianStructure) :
|
||||||
print('End Marker : 0x%X' % self.EndMarker)
|
print('End Marker : 0x%X' % self.EndMarker)
|
||||||
|
|
||||||
# Dell PFS.HDR. Extractor
|
# Dell PFS.HDR. Extractor
|
||||||
# noinspection PyUnusedLocal
|
|
||||||
def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
||||||
# Get PFS Header Structure values
|
# Get PFS Header Structure values
|
||||||
pfs_hdr = get_struct(buffer, 0, PFS_HDR)
|
pfs_hdr = get_struct(buffer, 0, PFS_HDR)
|
||||||
|
@ -490,7 +491,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
||||||
|
|
||||||
# Get sub PFS Entry Version string via "Version" and "VersionType" fields
|
# 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
|
# 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)
|
#chunk_entry_version = get_version(pfs_chunk_entry.Version, pfs_chunk_entry.VersionType)
|
||||||
|
|
||||||
# Sub PFS Entry Data starts after the sub PFS Entry Structure
|
# Sub PFS Entry Data starts after the sub PFS Entry Structure
|
||||||
chunk_entry_data_start = chunk_entry_start + pfs_entry_size
|
chunk_entry_data_start = chunk_entry_start + pfs_entry_size
|
||||||
|
@ -509,9 +510,9 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
||||||
chunk_entry_met_sig_end = chunk_entry_met_sig_start + pfs_chunk_entry.DataMetSigSize
|
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 = 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_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 = 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
|
#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
|
# 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_data_all.append((chunk_entry_number, chunk_entry_data, chunk_info_size))
|
||||||
|
@ -637,7 +638,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
||||||
full_name = '%d%s -- %d %s v%s' % (pfs_index, pfs_name, file_index, file_name, file_version) # Full Entry Name
|
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
|
safe_name = re.sub(r'[\\/*?:"<>|]', '_', full_name) # Replace common Windows reserved/illegal filename characters
|
||||||
|
|
||||||
is_zlib = True if file_type == 'ZLIB' else False # Determine if PFS Entry Data was zlib-compressed
|
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
|
# 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
|
# so empty or intermediate files such as sub-PFS, PFS w/ Chunks or zlib-PFS are skipped
|
||||||
|
@ -727,14 +728,15 @@ def get_pfs_entry(buffer, offset) :
|
||||||
pfs_entry_ver = int.from_bytes(buffer[offset + 0x10:offset + 0x14], 'little') # PFS Entry Version
|
pfs_entry_ver = int.from_bytes(buffer[offset + 0x10:offset + 0x14], 'little') # PFS Entry Version
|
||||||
|
|
||||||
if pfs_entry_ver == 1 : return PFS_ENTRY, ctypes.sizeof(PFS_ENTRY)
|
if pfs_entry_ver == 1 : return PFS_ENTRY, ctypes.sizeof(PFS_ENTRY)
|
||||||
elif pfs_entry_ver == 2 : return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2)
|
if pfs_entry_ver == 2 : return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2)
|
||||||
else : return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2)
|
|
||||||
|
return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2)
|
||||||
|
|
||||||
# Calculate Checksum XOR 8 of data
|
# Calculate Checksum XOR 8 of data
|
||||||
def chk_xor_8(data, init_value) :
|
def chk_xor_8(data, init_value) :
|
||||||
value = init_value
|
value = init_value
|
||||||
for byte in data : value = value ^ byte
|
for byte in data : value = value ^ byte
|
||||||
value = value ^ 0x0
|
value ^= 0x0
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -796,6 +798,13 @@ met_info_size = ctypes.sizeof(METADATA_INFO)
|
||||||
chunk_info_header_size = ctypes.sizeof(CHUNK_INFO_HDR)
|
chunk_info_header_size = ctypes.sizeof(CHUNK_INFO_HDR)
|
||||||
chunk_info_footer_size = ctypes.sizeof(CHUNK_INFO_FTR)
|
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
|
# 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,
|
# 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
|
# ++ is the section type and -- the header Checksum XOR 8. The "BIOS" section has type 0xAA and its
|
||||||
|
@ -816,7 +825,7 @@ else :
|
||||||
pfs_exec = []
|
pfs_exec = []
|
||||||
in_path = input('\nEnter the full folder path: ')
|
in_path = input('\nEnter the full folder path: ')
|
||||||
print('\nWorking...')
|
print('\nWorking...')
|
||||||
for root, dirs, files in os.walk(in_path):
|
for root, _, files in os.walk(in_path):
|
||||||
for name in files :
|
for name in files :
|
||||||
pfs_exec.append(os.path.join(root, name))
|
pfs_exec.append(os.path.join(root, name))
|
||||||
|
|
||||||
|
@ -834,6 +843,23 @@ for input_file in pfs_exec :
|
||||||
|
|
||||||
with open(input_file, 'rb') as in_file : input_data = in_file.read()
|
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
|
# Search input image for zlib "BIOS" section header
|
||||||
zlib_bios_hdr_match = zlib_bios_header.search(input_data)
|
zlib_bios_hdr_match = zlib_bios_header.search(input_data)
|
||||||
|
|
||||||
|
@ -906,13 +932,12 @@ for input_file in pfs_exec :
|
||||||
pfs_name = '' # N/A for Main/First/Initial full PFS, used for sub-PFS recursions
|
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_index = 1 # Main/First/Initial full PFS Index is 1
|
||||||
pfs_count = 1 # Main/First/Initial full PFS Count 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
|
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
|
pfs_extract(input_data, pfs_index, pfs_name, pfs_count) # Call the Dell PFS.HDR. Extractor function
|
||||||
|
|
||||||
print('\n Extracted Dell PFS BIOS image!')
|
print('\n Extracted Dell PFS BIOS image!')
|
||||||
|
|
||||||
else :
|
input('\nDone!')
|
||||||
input('\nDone!')
|
|
||||||
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
|
@ -7,11 +7,11 @@
|
||||||
|
|
||||||
## **Dell PFS BIOS Extractor**
|
## **Dell PFS BIOS Extractor**
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### **Description**
|
#### **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 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 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.
|
||||||
|
|
||||||
#### **Usage**
|
#### **Usage**
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue