mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-09 13:52:00 -04:00
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
This commit is contained in:
parent
c05a1da05f
commit
ec14803065
3 changed files with 1267 additions and 949 deletions
|
@ -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('<I', val), 'little') for val in reversed(self.GUID))
|
||||
VersionType = ''.join('%0.2X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.VersionType))
|
||||
Version = ''.join('%0.4X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.Version))
|
||||
Unknown = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.Unknown))
|
||||
|
||||
print('\nPFS Entry:\n')
|
||||
print('GUID : %s' % GUID)
|
||||
print('HeaderVersion : %d' % self.HeaderVersion)
|
||||
print('VersionType : %s' % VersionType)
|
||||
print('Version : %s' % Version)
|
||||
print('Reserved : 0x%X' % self.Reserved)
|
||||
print('DataSize : 0x%X' % self.DataSize)
|
||||
print('DataSigSize : 0x%X' % self.DataSigSize)
|
||||
print('DataMetSize : 0x%X' % self.DataMetSize)
|
||||
print('DataMetSigSize : 0x%X' % self.DataMetSigSize)
|
||||
print('Unknown : %s' % Unknown)
|
||||
|
||||
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) :
|
||||
GUID = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.GUID))
|
||||
VersionType = ''.join('%0.2X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.VersionType))
|
||||
Version = ''.join('%0.4X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.Version))
|
||||
Unknown = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.Unknown))
|
||||
|
||||
print('\nPFS Entry:\n')
|
||||
print('GUID : %s' % GUID)
|
||||
print('HeaderVersion : %d' % self.HeaderVersion)
|
||||
print('VersionType : %s' % VersionType)
|
||||
print('Version : %s' % Version)
|
||||
print('Reserved : 0x%X' % self.Reserved)
|
||||
print('DataSize : 0x%X' % self.DataSize)
|
||||
print('DataSigSize : 0x%X' % self.DataSigSize)
|
||||
print('DataMetSize : 0x%X' % self.DataMetSize)
|
||||
print('DataMetSigSize : 0x%X' % self.DataMetSigSize)
|
||||
print('Unknown : %s' % Unknown)
|
||||
|
||||
class PFS_INFO(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('HeaderVersion', uint32_t), # 0x00
|
||||
('GUID', uint32_t*4), # 0x04 Little Endian
|
||||
('Version', uint16_t*4), # 0x14
|
||||
('VersionType', uint8_t*4), # 0x1C
|
||||
('CharacterCount', uint16_t), # 0x20 UTF-16 2-byte Characters
|
||||
# 0x22
|
||||
]
|
||||
|
||||
def pfs_print(self) :
|
||||
GUID = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.GUID))
|
||||
Version = ''.join('%0.4X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.Version))
|
||||
VersionType = ''.join('%0.2X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.VersionType))
|
||||
|
||||
print('\nPFS Information:\n')
|
||||
print('HeaderVersion : %d' % self.HeaderVersion)
|
||||
print('GUID : %s' % GUID)
|
||||
print('Version : %s' % Version)
|
||||
print('VersionType : %s' % VersionType)
|
||||
print('CharacterCount : %d' % (self.CharacterCount * 2))
|
||||
|
||||
class METADATA_INFO(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) :
|
||||
print('\nMetadata Information:\n')
|
||||
print('Model IDs : %s' % self.ModelIDs.decode('utf-8').strip(',END'))
|
||||
print('File Name : %s' % self.FileName.decode('utf-8'))
|
||||
print('File Version : %s' % self.FileVersion.decode('utf-8'))
|
||||
print('Date : %s' % self.Date.decode('utf-8'))
|
||||
print('Brand : %s' % self.Brand.decode('utf-8'))
|
||||
print('Model File : %s' % self.ModelFile.decode('utf-8'))
|
||||
print('Model Name : %s' % self.ModelName.decode('utf-8'))
|
||||
print('Model Version : %s' % 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'))
|
||||
|
||||
class CHUNK_INFO_HDR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Unknown0', uint32_t), # 0x00
|
||||
('DellTag', char*16), # 0x04
|
||||
('Unknown1', uint32_t), # 0x14
|
||||
('Unknown2', uint32_t), # 0x18
|
||||
('FlagsSize', uint32_t), # 0x1C
|
||||
('ChunkSize', uint32_t), # 0x20
|
||||
('Unknown3', uint16_t), # 0x24
|
||||
('EndTag', char*2), # 0x26
|
||||
# 0x28
|
||||
]
|
||||
|
||||
def pfs_print(self) :
|
||||
print('\nChunk Header:\n')
|
||||
print('Unknown 0 : 0x%X' % self.Unknown0)
|
||||
print('Dell Tag : %s' % self.DellTag.replace(b'\x00',b'\x20').decode('utf-8','ignore').strip())
|
||||
print('Unknown 1 : 0x%X' % self.Unknown1)
|
||||
print('Unknown 2 : 0x%X' % self.Unknown2)
|
||||
print('Flags Size : 0x%X' % self.FlagsSize)
|
||||
print('Chunk Size : 0x%X' % self.ChunkSize)
|
||||
print('Unknown 3 : 0x%X' % self.Unknown3)
|
||||
print('End Tag : %s' % self.EndTag.decode('utf-8','ignore'))
|
||||
|
||||
class CHUNK_INFO_FTR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('EndMarker', uint64_t), # 0x00
|
||||
# 0x08
|
||||
]
|
||||
|
||||
def pfs_print(self) :
|
||||
print('\nChunk Footer:\n')
|
||||
print('End Marker : 0x%X' % self.EndMarker)
|
||||
|
||||
# Dell PFS.HDR. Extractor
|
||||
def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
||||
# Get PFS Header Structure values
|
||||
pfs_hdr = get_struct(buffer, 0, PFS_HDR)
|
||||
|
||||
# Validate that a PFS Header was parsed
|
||||
if pfs_hdr.Tag != b'PFS.HDR.' :
|
||||
print('\n Error: PFS Header could not be found!')
|
||||
return # Critical error, abort
|
||||
|
||||
# Validate that a known PFS Header Version was encountered
|
||||
if pfs_hdr.HeaderVersion not in (1,2) :
|
||||
print('\n Error: Unknown PFS Header Version %d!' % pfs_hdr.HeaderVersion)
|
||||
|
||||
# Get PFS Footer Data after PFS Header Payload
|
||||
footer = buffer[pfs_header_size + pfs_hdr.PayloadSize:pfs_header_size + pfs_hdr.PayloadSize + pfs_footer_size]
|
||||
|
||||
# Get PFS Footer Structure values
|
||||
pfs_ftr = pfs_hdr = get_struct(footer, 0, PFS_FTR)
|
||||
|
||||
# Validate that a PFS Footer was parsed
|
||||
if pfs_ftr.Tag != b'PFS.FTR.' :
|
||||
print('\n Error: PFS Footer could not be found!')
|
||||
|
||||
# Validate that the PFS Header Payload Size matches the one at the PFS Footer
|
||||
if pfs_hdr.PayloadSize != pfs_ftr.PayloadSize :
|
||||
print('\n Error: PFS Header & Footer Payload Size mismatch!')
|
||||
|
||||
# Get PFS Payload Data
|
||||
payload = buffer[pfs_header_size:pfs_header_size + pfs_hdr.PayloadSize]
|
||||
|
||||
# Calculate the PFS Payload Data CRC-32 w/ Vector 0 Checksum
|
||||
footer_checksum = ~zlib.crc32(payload, 0) & 0xFFFFFFFF
|
||||
|
||||
# Validate PFS Payload Data Checksum via the PFS Footer
|
||||
if pfs_ftr.Checksum != footer_checksum :
|
||||
print('\n Error: Invalid PFS Footer Payload Checksum!')
|
||||
|
||||
# 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
|
||||
pfs_info = [] # Buffer for PFS Information Entry Data
|
||||
pfs_entry_struct, pfs_entry_size = get_pfs_entry(payload, entry_start) # Get PFS Entry Info
|
||||
while len(payload[entry_start:entry_start + pfs_entry_size]) == pfs_entry_size :
|
||||
# Get PFS Entry Structure values
|
||||
pfs_entry = get_struct(payload, entry_start, pfs_entry_struct)
|
||||
|
||||
# Validate that a known PFS Entry Header Version was encountered
|
||||
if pfs_entry.HeaderVersion not in (1,2) :
|
||||
print('\n Error: Unknown PFS Entry Header Version %d!' % pfs_entry.HeaderVersion)
|
||||
|
||||
# Validate that the PFS Entry Reserved field is empty
|
||||
if pfs_entry.Reserved != 0 :
|
||||
print('\n Error: Detected non-empty PFS Entry Reserved field!')
|
||||
|
||||
# Get PFS Entry Version string via "Version" and "VersionType" fields
|
||||
entry_version = get_version(pfs_entry.Version, pfs_entry.VersionType)
|
||||
|
||||
# Get PFS Entry GUID in Big Endian format
|
||||
entry_guid = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(pfs_entry.GUID))
|
||||
|
||||
# PFS Entry Data starts after the PFS Entry Structure
|
||||
entry_data_start = entry_start + pfs_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 = payload[entry_data_start:entry_data_end] # Store PFS Entry Data
|
||||
entry_data_sig = payload[entry_data_sig_start:entry_data_sig_end] # Store PFS Entry Data Signature
|
||||
entry_met = payload[entry_met_start:entry_met_end] # Store PFS Entry Metadata
|
||||
entry_met_sig = payload[entry_met_sig_start:entry_met_sig_end] # Store PFS Entry Metadata Signature
|
||||
|
||||
entry_type = 'OTHER' # Adjusted later if PFS Entry is Zlib, Chunks, PFS Info, Model Info
|
||||
|
||||
# Get PFS Information from the PFS Entry with GUID E0717CE3A9BB25824B9F0DC8FD041960 or B033CB16EC9B45A14055F80E4D583FD3
|
||||
if entry_guid in ['E0717CE3A9BB25824B9F0DC8FD041960','B033CB16EC9B45A14055F80E4D583FD3'] :
|
||||
pfs_info = entry_data
|
||||
entry_type = 'PFS_INFO'
|
||||
|
||||
# Get Model Information from the PFS Entry with GUID 6F1D619A22A6CB924FD4DA68233AE3FB
|
||||
elif entry_guid == '6F1D619A22A6CB924FD4DA68233AE3FB' :
|
||||
entry_type = 'MODEL_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 = entry_met_sig_end # 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(pfs_info[info_start:info_start + pfs_info_size]) == pfs_info_size :
|
||||
# Get PFS Information Structure values
|
||||
entry_info = get_struct(pfs_info, info_start, PFS_INFO)
|
||||
|
||||
# Validate that a known PFS Information Header Version was encountered
|
||||
if entry_info.HeaderVersion != 1 :
|
||||
print('\n Error: Unknown PFS Information Header Version %d!' % entry_info.HeaderVersion)
|
||||
break # Skip PFS Information Entries/Descriptors in case of assertion error
|
||||
|
||||
# Get PFS Information GUID in Big Endian format to match each Info to the equivalent stored PFS Entry details
|
||||
entry_guid = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(entry_info.GUID))
|
||||
|
||||
# The PFS Information 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
|
||||
entry_name = pfs_info[info_start + pfs_info_size:info_start + pfs_info_size + entry_info.CharacterCount * 2].decode('utf-16').strip()
|
||||
|
||||
# Get PFS Information Version string via "Version" and "VersionType" fields
|
||||
# PFS Information Version string must be preferred over PFS Entry's Version
|
||||
entry_version = get_version(entry_info.Version, entry_info.VersionType)
|
||||
|
||||
# Store all relevant PFS Information details
|
||||
info_all.append([entry_guid, entry_name, entry_version])
|
||||
|
||||
# The next PFS Information starts after the calculated Entry Name size
|
||||
# Two space/null characters seem to always exist after the Entry Name
|
||||
info_start += (pfs_info_size + entry_info.CharacterCount * 2 + 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 pfs_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) >= 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'<Rimm x-schema="' in buffer[:0x50] : # XML Type
|
||||
is_text = True
|
||||
write_mode = 'w'
|
||||
extension = '.xml'
|
||||
buffer = buffer.decode('utf-8')
|
||||
elif file_type in ('NESTED_PFS','ZLIB') and is_metadata and len(buffer) == met_info_size : # Text Type 3
|
||||
is_text = True
|
||||
write_mode = 'w'
|
||||
extension = '.txt'
|
||||
buffer = get_struct(buffer, 0, METADATA_INFO).pfs_write()
|
||||
|
||||
return is_text, buffer, extension, write_mode
|
||||
|
||||
# Determine PFS Entry Version string via "Version" and "VersionType" fields
|
||||
def get_version(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 '.'
|
||||
|
||||
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
|
||||
print('\n Error: Unknown PFS Entry Version Type 0x%0.2X!' % version_types[idx])
|
||||
|
||||
return version
|
||||
|
||||
# Get 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, ctypes.sizeof(PFS_ENTRY)
|
||||
if pfs_entry_ver == 2 : return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2)
|
||||
|
||||
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 ^= 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 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('\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)
|
1245
Dell PFS Update Extractor/Dell_PFS_Extract.py
Normal file
1245
Dell PFS Update Extractor/Dell_PFS_Extract.py
Normal file
File diff suppressed because it is too large
Load diff
28
README.md
28
README.md
|
@ -5,7 +5,7 @@
|
|||
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DJDZD3PRGCSCL"><img border="0" title="BIOS Utilities Donation via Paypal or Debit/Credit Card" alt="BIOS Utilities Donation via Paypal or Debit/Credit Card" src="https://user-images.githubusercontent.com/11527726/109392268-e0f68280-7923-11eb-83d8-0a63f0d20783.png"></a>
|
||||
|
||||
* [**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**
|
||||
|
||||

|
||||

|
||||
|
||||
#### **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
|
|||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## **AMI UCP BIOS Extractor**
|
||||
|
||||

|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue