Insyde iFlash/iFdPacker Extractor v2.0_a8

Improved PFAT, UCP, PFS, TDK format check methods

Cleanup/improvements to all utilities
This commit is contained in:
platomav 2022-07-06 17:54:17 +03:00
parent cd2704f743
commit 4749414f81
21 changed files with 603 additions and 435 deletions

View file

@ -7,7 +7,7 @@ AMI BIOS Guard Extractor
Copyright (C) 2018-2022 Plato Mavropoulos
"""
TITLE = 'AMI BIOS Guard Extractor v4.0_a10'
TITLE = 'AMI BIOS Guard Extractor v4.0_a11'
import os
import re
@ -19,10 +19,10 @@ sys.dont_write_bytecode = True
from common.externals import get_bgs_tool
from common.num_ops import get_ordinal
from common.path_ops import safe_name, make_dirs
from common.path_ops import make_dirs, safe_name
from common.patterns import PAT_AMI_PFAT
from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t
from common.system import script_init, argparse_init, printer
from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t
from common.system import argparse_init, printer, script_init
from common.text_ops import file_to_bytes
class AmiBiosGuardHeader(ctypes.LittleEndianStructure):
@ -128,22 +128,22 @@ class IntelBiosGuardSignature2k(ctypes.LittleEndianStructure):
printer(['Exponent :', f'0x{self.Exponent:X}'], p, False)
printer(['Signature:', f'{Signature[:32]} [...]'], p, False)
def is_ami_pfat(in_file):
input_buffer = file_to_bytes(in_file)
def is_ami_pfat(input_file):
input_buffer = file_to_bytes(input_file)
return bool(get_ami_pfat(input_buffer)[0])
return bool(get_ami_pfat(input_buffer))
def get_ami_pfat(input_file):
input_buffer = file_to_bytes(input_file)
def get_ami_pfat(input_buffer):
match = PAT_AMI_PFAT.search(input_buffer)
buffer = input_buffer[match.start() - 0x8:] if match else b''
return match, buffer
return input_buffer[match.start() - 0x8:] if match else b''
def get_file_name(index, name):
return safe_name(f'{index:02d} -- {name}')
def parse_bg_script(script_data, padding):
def parse_bg_script(script_data, padding=0):
is_opcode_div = len(script_data) % 8 == 0
if not is_opcode_div:
@ -177,7 +177,7 @@ def parse_bg_script(script_data, padding):
return 0
def parse_pfat_hdr(buffer, padding):
def parse_pfat_hdr(buffer, padding=0):
block_all = []
pfat_hdr = get_struct(buffer, 0x0, AmiBiosGuardHeader)
@ -220,7 +220,11 @@ def parse_pfat_hdr(buffer, padding):
return block_all, hdr_size, files_count
def parse_pfat_file(buffer, output_path, padding):
def parse_pfat_file(input_file, output_path, padding=0):
input_buffer = file_to_bytes(input_file)
pfat_buffer = get_ami_pfat(input_buffer)
file_path = ''
all_blocks_dict = {}
@ -230,7 +234,7 @@ def parse_pfat_file(buffer, output_path, padding):
make_dirs(extract_path, delete=True)
block_all,block_off,file_count = parse_pfat_hdr(buffer, padding)
block_all,block_off,file_count = parse_pfat_hdr(pfat_buffer, padding)
for block in block_all:
file_desc,file_name,_,_,_,file_index,block_index,block_count = block
@ -244,7 +248,7 @@ def parse_pfat_file(buffer, output_path, padding):
block_status = f'{block_index + 1}/{block_count}'
bg_hdr = get_struct(buffer, block_off, IntelBiosGuardHeader)
bg_hdr = get_struct(pfat_buffer, block_off, IntelBiosGuardHeader)
printer(f'Intel BIOS Guard {block_status} Header:\n', padding + 8)
@ -252,11 +256,11 @@ def parse_pfat_file(buffer, output_path, padding):
bg_script_bgn = block_off + PFAT_BLK_HDR_LEN
bg_script_end = bg_script_bgn + bg_hdr.ScriptSize
bg_script_bin = buffer[bg_script_bgn:bg_script_end]
bg_script_bin = pfat_buffer[bg_script_bgn:bg_script_end]
bg_data_bgn = bg_script_end
bg_data_end = bg_data_bgn + bg_hdr.DataSize
bg_data_bin = buffer[bg_data_bgn:bg_data_end]
bg_data_bin = pfat_buffer[bg_data_bgn:bg_data_end]
block_off = bg_data_end # Assume next block starts at data end
@ -265,7 +269,7 @@ def parse_pfat_file(buffer, output_path, padding):
if is_sfam:
bg_sig_bgn = bg_data_end
bg_sig_end = bg_sig_bgn + PFAT_BLK_S2K_LEN
bg_sig_bin = buffer[bg_sig_bgn:bg_sig_end]
bg_sig_bin = pfat_buffer[bg_sig_bgn:bg_sig_end]
if len(bg_sig_bin) == PFAT_BLK_S2K_LEN:
bg_sig = get_struct(bg_sig_bin, 0x0, IntelBiosGuardSignature2k)
@ -280,21 +284,22 @@ def parse_pfat_file(buffer, output_path, padding):
_ = parse_bg_script(bg_script_bin, padding + 12)
with open(file_path, 'ab') as out_dat: out_dat.write(bg_data_bin)
with open(file_path, 'ab') as out_dat:
out_dat.write(bg_data_bin)
all_blocks_dict[file_index] += bg_data_bin
pfat_oob_data = buffer[block_off:] # Store out-of-bounds data after the end of PFAT files
pfat_oob_data = pfat_buffer[block_off:] # Store out-of-bounds data after the end of PFAT files
pfat_oob_name = get_file_name(file_count + 1, f'{extract_name}_OOB.bin')
pfat_oob_path = os.path.join(extract_path, pfat_oob_name)
with open(pfat_oob_path, 'wb') as out_oob: out_oob.write(pfat_oob_data)
with open(pfat_oob_path, 'wb') as out_oob:
out_oob.write(pfat_oob_data)
oob_pfat_match,pfat_oob_buffer = get_ami_pfat(pfat_oob_data)
if oob_pfat_match: parse_pfat_file(pfat_oob_buffer, pfat_oob_path, padding)
if is_ami_pfat(pfat_oob_data):
parse_pfat_file(pfat_oob_data, pfat_oob_path, padding)
in_all_data = b''.join([block[1] for block in sorted(all_blocks_dict.items())])
@ -302,7 +307,10 @@ def parse_pfat_file(buffer, output_path, padding):
in_all_path = os.path.join(extract_path, in_all_name)
with open(in_all_path, 'wb') as out_all: out_all.write(in_all_data + pfat_oob_data)
with open(in_all_path, 'wb') as out_all:
out_all.write(in_all_data + pfat_oob_data)
return 0
PFAT_AMI_HDR_LEN = ctypes.sizeof(AmiBiosGuardHeader)
PFAT_BLK_HDR_LEN = ctypes.sizeof(IntelBiosGuardHeader)
@ -321,18 +329,17 @@ if __name__ == '__main__':
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
with open(input_file, 'rb') as in_file:
input_buffer = in_file.read()
pfat_match,pfat_buffer = get_ami_pfat(input_buffer)
if not pfat_match:
if not is_ami_pfat(input_buffer):
printer('Error: This is not an AMI BIOS Guard (PFAT) image!', padding)
continue # Next input file
extract_path = os.path.join(output_path, input_name)
parse_pfat_file(pfat_buffer, extract_path, padding)
parse_pfat_file(input_buffer, extract_path, padding)
exit_code -= 1

View file

@ -7,7 +7,7 @@ AMI UCP Update Extractor
Copyright (C) 2021-2022 Plato Mavropoulos
"""
TITLE = 'AMI UCP Update Extractor v2.0_a17'
TITLE = 'AMI UCP Update Extractor v2.0_a18'
import os
import re
@ -21,14 +21,14 @@ sys.dont_write_bytecode = True
from common.checksums import get_chk_16
from common.comp_efi import efi_decompress, is_efi_compressed
from common.comp_szip import is_szip_supported, szip_decompress
from common.path_ops import agnostic_path, safe_name, safe_path, make_dirs
from common.path_ops import agnostic_path, make_dirs, safe_name, safe_path
from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG
from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t
from common.system import script_init, argparse_init, printer
from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t
from common.system import argparse_init, printer, script_init
from common.text_ops import file_to_bytes, to_string
from AMI_PFAT_Extract import get_ami_pfat, parse_pfat_file
from AMI_PFAT_Extract import is_ami_pfat, parse_pfat_file
from Insyde_IFD_Extract import insyde_ifd_extract, is_insyde_ifd
class UafHeader(ctypes.LittleEndianStructure):
_pack_ = 1
@ -166,9 +166,10 @@ def is_ami_ucp(in_file):
return bool(get_ami_ucp(buffer)[0] is not None)
# Get all input file AMI UCP patterns
def get_ami_ucp(buffer):
def get_ami_ucp(in_file):
buffer = file_to_bytes(in_file)
uaf_len_max = 0x0 # Length of largest detected @UAF|@HPU
uaf_hdr_off = None # Offset of largest detected @UAF|@HPU
uaf_buf_bin = None # Buffer of largest detected @UAF|@HPU
uaf_buf_tag = '@UAF' # Tag of largest detected @UAF|@HPU
@ -181,7 +182,7 @@ def get_ami_ucp(buffer):
uaf_buf_bin = buffer[uaf_hdr_off:uaf_hdr_off + uaf_len_max]
uaf_buf_tag = uaf.group(0)[:4].decode('utf-8','ignore')
return uaf_hdr_off, uaf_buf_bin, uaf_buf_tag
return uaf_buf_bin, uaf_buf_tag
# Get list of @UAF|@HPU Modules
def get_uaf_mod(buffer, uaf_off=0x0):
@ -196,19 +197,23 @@ def get_uaf_mod(buffer, uaf_off=0x0):
uaf_off += uaf_hdr.ModuleSize # Adjust to next @UAF|@HPU Module offset
if uaf_off >= len(buffer): break # Stop parsing at EOF
if uaf_off >= len(buffer):
break # Stop parsing at EOF
# Check if @UAF|@HPU Module @NAL exists and place it first
# Parsing @NAL first allows naming all @UAF|@HPU Modules
for mod_idx,mod_val in enumerate(uaf_all):
if mod_val[0] == '@NAL':
uaf_all.insert(1, uaf_all.pop(mod_idx)) # After UII for visual purposes
break # @NAL found, skip the rest
return uaf_all
# Parse & Extract AMI UCP structures
def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False):
def ucp_extract(in_file, out_path, padding=0, checksum=False):
input_buffer = file_to_bytes(in_file)
nal_dict = {} # Initialize @NAL Dictionary per UCP
printer('Utility Configuration Program', padding)
@ -217,13 +222,16 @@ def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False):
make_dirs(extract_path, delete=True)
uaf_hdr = get_struct(buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure
# Get best AMI UCP Pattern match based on @UAF|@HPU Size
ucp_buffer,ucp_tag = get_ami_ucp(input_buffer)
uaf_hdr = get_struct(ucp_buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure
printer(f'Utility Auxiliary File > {ucp_tag}:\n', padding + 4)
uaf_hdr.struct_print(padding + 8)
fake = struct.pack('<II', len(buffer), len(buffer)) # Generate UafModule Structure
fake = struct.pack('<II', len(ucp_buffer), len(ucp_buffer)) # Generate UafModule Structure
uaf_mod = get_struct(fake, 0x0, UafModule) # Parse @UAF|@HPU Module EFI Structure
@ -232,16 +240,18 @@ def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False):
uaf_mod.struct_print(padding + 8, uaf_name, uaf_desc) # Print @UAF|@HPU Module EFI Info
if is_checksum: chk16_validate(buffer, ucp_tag, padding + 8)
if checksum:
chk16_validate(ucp_buffer, ucp_tag, padding + 8)
uaf_all = get_uaf_mod(buffer, UAF_HDR_LEN)
uaf_all = get_uaf_mod(ucp_buffer, UAF_HDR_LEN)
for mod_info in uaf_all:
nal_dict = uaf_extract(buffer, extract_path, mod_info, padding + 8, is_checksum, nal_dict)
nal_dict = uaf_extract(ucp_buffer, extract_path, mod_info, padding + 8, checksum, nal_dict)
# Parse & Extract AMI UCP > @UAF|@HPU Module/Section
def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, nal_dict=None):
if nal_dict is None: nal_dict = {}
def uaf_extract(buffer, extract_path, mod_info, padding=0, checksum=False, nal_dict=None):
if nal_dict is None:
nal_dict = {}
uaf_tag,uaf_off,uaf_hdr = mod_info
@ -259,16 +269,26 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na
is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect @UAF|@HPU Module EFI Compression
if uaf_tag in nal_dict: uaf_name = nal_dict[uaf_tag][1] # Always prefer @NAL naming first
elif uaf_tag in UAF_TAG_DICT: uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming
elif uaf_tag == '@ROM': uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature)
elif uaf_tag.startswith('@R0'): uaf_name = f'BIOS_0{uaf_tag[3:]}.bin' # BIOS/PFAT Firmware
elif uaf_tag.startswith('@S0'): uaf_name = f'BIOS_0{uaf_tag[3:]}.sig' # BIOS/PFAT Signature
elif uaf_tag.startswith('@DR'): uaf_name = f'DROM_0{uaf_tag[3:]}.bin' # Thunderbolt Retimer Firmware
elif uaf_tag.startswith('@DS'): uaf_name = f'DROM_0{uaf_tag[3:]}.sig' # Thunderbolt Retimer Signature
elif uaf_tag.startswith('@EC'): uaf_name = f'EC_0{uaf_tag[3:]}.bin' # Embedded Controller Firmware
elif uaf_tag.startswith('@ME'): uaf_name = f'ME_0{uaf_tag[3:]}.bin' # Management Engine Firmware
else: uaf_name = uaf_tag # Could not name the @UAF|@HPU Module, use Tag instead
if uaf_tag in nal_dict:
uaf_name = nal_dict[uaf_tag][1] # Always prefer @NAL naming first
elif uaf_tag in UAF_TAG_DICT:
uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming
elif uaf_tag == '@ROM':
uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature)
elif uaf_tag.startswith('@R0'):
uaf_name = f'BIOS_0{uaf_tag[3:]}.bin' # BIOS/PFAT Firmware
elif uaf_tag.startswith('@S0'):
uaf_name = f'BIOS_0{uaf_tag[3:]}.sig' # BIOS/PFAT Signature
elif uaf_tag.startswith('@DR'):
uaf_name = f'DROM_0{uaf_tag[3:]}.bin' # Thunderbolt Retimer Firmware
elif uaf_tag.startswith('@DS'):
uaf_name = f'DROM_0{uaf_tag[3:]}.sig' # Thunderbolt Retimer Signature
elif uaf_tag.startswith('@EC'):
uaf_name = f'EC_0{uaf_tag[3:]}.bin' # Embedded Controller Firmware
elif uaf_tag.startswith('@ME'):
uaf_name = f'ME_0{uaf_tag[3:]}.bin' # Management Engine Firmware
else:
uaf_name = uaf_tag # Could not name the @UAF|@HPU Module, use Tag instead
uaf_fext = '' if uaf_name != uaf_tag else '.bin'
@ -289,7 +309,8 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na
else:
uaf_fname = safe_path(extract_path, uaf_sname)
if is_checksum: chk16_validate(uaf_data_all, uaf_tag, padding + 4)
if checksum:
chk16_validate(uaf_data_all, uaf_tag, padding + 4)
# Parse Utility Identification Information @UAF|@HPU Module (@UII)
if uaf_tag == '@UII':
@ -304,7 +325,8 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na
info_hdr.struct_print(padding + 8, info_desc) # Print @UII Module Info
if is_checksum: chk16_validate(uaf_data_raw, '@UII > Info', padding + 8)
if checksum:
chk16_validate(uaf_data_raw, '@UII > Info', padding + 8)
# Store/Save @UII Module Info in file
with open(uaf_fname[:-4] + '.txt', 'a', encoding='utf-8') as uii_out:
@ -324,14 +346,16 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na
# Store/Save @UAF|@HPU Module file
if uaf_tag != '@UII': # Skip @UII binary, already parsed
with open(uaf_fname, 'wb') as uaf_out: uaf_out.write(uaf_data_raw)
with open(uaf_fname, 'wb') as uaf_out:
uaf_out.write(uaf_data_raw)
# @UAF|@HPU Module EFI/Tiano Decompression
if is_comp and is_efi_compressed(uaf_data_raw, False):
dec_fname = uaf_fname.replace('.temp', uaf_fext) # Decompressed @UAF|@HPU Module file path
if efi_decompress(uaf_fname, dec_fname, padding + 4) == 0:
with open(dec_fname, 'rb') as dec: uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data
with open(dec_fname, 'rb') as dec:
uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data
os.remove(uaf_fname) # Successful decompression, delete compressed @UAF|@HPU Module file
@ -392,39 +416,32 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na
nal_dict[info_tag] = (info_path,info_name) # Assign a file path & name to each Tag
# Parse Insyde BIOS @UAF|@HPU Module (@INS)
if uaf_tag == '@INS' and is_szip_supported(uaf_fname, padding + 4, check=True):
ins_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-SFX')) # Generate extraction directory
if uaf_tag == '@INS' and is_insyde_ifd(uaf_fname):
ins_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-IFD')) # Generate extraction directory
printer('Insyde BIOS 7z SFX Archive:', padding + 4)
if szip_decompress(uaf_fname, ins_dir, '7z SFX', padding + 8, check=True) == 0:
os.remove(uaf_fname) # Successful extraction, delete @INS Module file/archive
if insyde_ifd_extract(uaf_fname, ins_dir, padding + 4) == 0:
os.remove(uaf_fname) # Delete raw nested Insyde IFD image after successful extraction
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
pfat_match,pfat_buffer = get_ami_pfat(uaf_data_raw)
if pfat_match:
if is_ami_pfat(uaf_data_raw):
pfat_dir = os.path.join(extract_path, safe_name(uaf_name))
parse_pfat_file(pfat_buffer, pfat_dir, padding + 4)
parse_pfat_file(uaf_data_raw, pfat_dir, padding + 4)
os.remove(uaf_fname) # Delete PFAT Module file after extraction
os.remove(uaf_fname) # Delete raw PFAT BIOS image after successful extraction
# Detect Intel Engine firmware image and show ME Analyzer advice
if uaf_tag.startswith('@ME') and PAT_INTEL_ENG.search(uaf_data_raw):
printer('Intel Management Engine (ME) Firmware:\n', padding + 4)
printer('Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer', padding + 8, False)
# Get best Nested AMI UCP Pattern match based on @UAF|@HPU Size
nested_uaf_off,nested_uaf_bin,nested_uaf_tag = get_ami_ucp(uaf_data_raw)
# Parse Nested AMI UCP Structure
if nested_uaf_off is not None:
# Parse Nested AMI UCP image
if is_ami_ucp(uaf_data_raw):
uaf_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-UCP')) # Generate extraction directory
ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum) # Call recursively
ucp_extract(uaf_data_raw, uaf_dir, padding + 4, checksum) # Call recursively
os.remove(uaf_fname) # Delete raw nested AMI UCP Structure after successful recursion/extraction
os.remove(uaf_fname) # Delete raw nested AMI UCP image after successful extraction
return nal_dict
@ -497,7 +514,7 @@ if __name__ == '__main__':
argparser.add_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true')
arguments = argparser.parse_args()
is_checksum = arguments.checksum # Set Checksum verification optional argument
checksum = arguments.checksum # Set Checksum verification optional argument
# Initialize script (must be after argparse)
exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4)
@ -507,19 +524,17 @@ if __name__ == '__main__':
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
with open(input_file, 'rb') as in_file:
input_buffer = in_file.read()
# Get best AMI UCP Pattern match based on @UAF|@HPU Size
main_uaf_off,main_uaf_bin,main_uaf_tag = get_ami_ucp(input_buffer)
if main_uaf_off is None:
if not is_ami_ucp(input_buffer):
printer('Error: This is not an AMI UCP Update executable!', padding)
continue # Next input file
extract_path = os.path.join(output_path, input_name)
ucp_extract(main_uaf_bin, extract_path, main_uaf_tag, padding, is_checksum)
ucp_extract(input_buffer, extract_path, padding, checksum)
exit_code -= 1

View file

@ -7,7 +7,7 @@ Award BIOS Module Extractor
Copyright (C) 2018-2022 Plato Mavropoulos
"""
TITLE = 'Award BIOS Module Extractor v2.0_a3'
TITLE = 'Award BIOS Module Extractor v2.0_a4'
import os
import sys
@ -18,7 +18,7 @@ sys.dont_write_bytecode = True
from common.comp_szip import szip_decompress
from common.path_ops import make_dirs, safe_name
from common.patterns import PAT_AWARD_LZH
from common.system import script_init, argparse_init, printer
from common.system import argparse_init, printer, script_init
from common.text_ops import file_to_bytes
# Check if input is Award BIOS image
@ -84,7 +84,8 @@ if __name__ == '__main__':
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
with open(input_file, 'rb') as in_file:
input_buffer = in_file.read()
if not is_award_bios(input_buffer):
printer('Error: This is not an Award BIOS image!', padding)

View file

@ -3,11 +3,11 @@
"""
Dell PFS Extract
Dell PFS Update Extractor
Dell PFS/PKG Update Extractor
Copyright (C) 2018-2022 Plato Mavropoulos
"""
TITLE = 'Dell PFS Update Extractor v6.0_a10'
TITLE = 'Dell PFS/PKG Update Extractor v6.0_a11'
import os
import io
@ -21,10 +21,10 @@ import contextlib
sys.dont_write_bytecode = True
from common.checksums import get_chk_8_xor
from common.path_ops import safe_name, make_dirs
from common.patterns import PAT_DELL_HDR, PAT_DELL_FTR, PAT_DELL_PKG
from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t, uint64_t
from common.system import script_init, argparse_init, printer
from common.path_ops import make_dirs, path_stem, safe_name
from common.patterns import PAT_DELL_FTR, PAT_DELL_HDR, PAT_DELL_PKG
from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t, uint64_t
from common.system import argparse_init, printer, script_init
from common.text_ops import file_to_bytes
from AMI_PFAT_Extract import IntelBiosGuardHeader, IntelBiosGuardSignature2k, parse_bg_script
@ -190,7 +190,9 @@ class DellPfsPfatMetadata(ctypes.LittleEndianStructure):
# Each section starts with a 0x30 header, which begins with pattern 72135500.
# The section length is found at 0x10-0x14 and its (optional) MD5 hash at 0x20-0x30.
# Section data can be raw or LZMA2 (7zXZ) compressed. The latter contains the PFS update image.
def is_pfs_pkg(in_buffer):
def is_pfs_pkg(in_file):
in_buffer = file_to_bytes(in_file)
return PAT_DELL_PKG.search(in_buffer)
# The Dell PFS update images usually contain multiple sections.
@ -198,28 +200,57 @@ def is_pfs_pkg(in_buffer):
# where ******** is the zlib stream size, ++ is the section type and -- the header Checksum XOR 8.
# The "Firmware" section has type AA and its files are stored in PFS format.
# The "Utility" section has type BB and its files are stored in PFS, BIN or 7z formats.
def is_pfs_hdr(in_buffer):
return list(PAT_DELL_HDR.finditer(in_buffer))
def is_pfs_hdr(in_file):
in_buffer = file_to_bytes(in_file)
return bool(PAT_DELL_HDR.search(in_buffer))
# Each section is followed by the footer pattern ********EEAAEE8F491BE8AE143790--,
# where ******** is the zlib stream size and ++ the footer Checksum XOR 8.
def is_pfs_ftr(in_buffer):
return PAT_DELL_FTR.search(in_buffer)
def is_pfs_ftr(in_file):
in_buffer = file_to_bytes(in_file)
return bool(PAT_DELL_FTR.search(in_buffer))
# Check if input is Dell PFS/PKG image
def is_dell_pfs(in_file):
in_buffer = file_to_bytes(in_file)
return bool(is_pfs_hdr(in_buffer) or is_pfs_pkg(in_buffer))
is_pkg = is_pfs_pkg(in_buffer)
is_hdr = is_pfs_hdr(in_buffer)
is_ftr = is_pfs_ftr(in_buffer)
return bool(is_pkg or is_hdr and is_ftr)
# Extract Dell ThinOS PKG 7zXZ
def thinos_pkg_extract(in_file):
in_buffer = file_to_bytes(in_file)
# Search input image for ThinOS PKG 7zXZ header
thinos_pkg_match = PAT_DELL_PKG.search(in_buffer)
lzma_len_off = thinos_pkg_match.start() + 0x10
lzma_len_int = int.from_bytes(in_buffer[lzma_len_off:lzma_len_off + 0x4], 'little')
lzma_bin_off = thinos_pkg_match.end() - 0x5
lzma_bin_dat = in_buffer[lzma_bin_off:lzma_bin_off + lzma_len_int]
# Check if the compressed 7zXZ stream is complete
if len(lzma_bin_dat) != lzma_len_int:
return in_buffer
return lzma.decompress(lzma_bin_dat)
# Get PFS ZLIB Section Offsets
def get_section_offsets(buffer):
pfs_zlib_init = is_pfs_hdr(buffer)
if not pfs_zlib_init: return [] # No PFS ZLIB detected
pfs_zlib_list = [] # Initialize PFS ZLIB offset list
pfs_zlib_init = list(PAT_DELL_HDR.finditer(buffer))
if not pfs_zlib_init:
return pfs_zlib_list # No PFS ZLIB detected
# Remove duplicate/nested PFS ZLIB offsets
for zlib_c in pfs_zlib_init:
is_duplicate = False # Initialize duplicate/nested PFS ZLIB offset
@ -228,14 +259,16 @@ def get_section_offsets(buffer):
zlib_o_size = int.from_bytes(buffer[zlib_o.start() - 0x5:zlib_o.start() - 0x1], 'little')
# If current PFS ZLIB offset is within another PFS ZLIB range (start-end), set as duplicate
if zlib_o.start() < zlib_c.start() < zlib_o.start() + zlib_o_size: is_duplicate = True
if zlib_o.start() < zlib_c.start() < zlib_o.start() + zlib_o_size:
is_duplicate = True
if not is_duplicate: pfs_zlib_list.append(zlib_c.start())
if not is_duplicate:
pfs_zlib_list.append(zlib_c.start())
return pfs_zlib_list
# Dell PFS ZLIB Section Parser
def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padding, is_structure=True, is_advanced=True):
def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padding, structure=True, advanced=True):
is_zlib_error = False # Initialize PFS ZLIB-related error state
section_type = zlib_data[zlib_start - 0x1] # Byte before PFS ZLIB Section pattern is Section Type (e.g. AA, BB)
@ -281,11 +314,8 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p
# Store the PFS ZLIB section footer contents (16 bytes)
footer_data = zlib_data[compressed_end:compressed_end + 0x10]
# Search input section for PFS ZLIB section footer
pfs_zlib_footer_match = is_pfs_ftr(footer_data)
# Check if PFS ZLIB section footer was found in the section
if not pfs_zlib_footer_match:
if not is_pfs_ftr(footer_data):
printer('Error: This Dell PFS ZLIB section is corrupted!', padding)
is_zlib_error = True
@ -304,18 +334,19 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p
# Decompress PFS ZLIB section payload
try:
if is_zlib_error: raise Exception('ZLIB_ERROR') # ZLIB errors are critical
if is_zlib_error:
raise Exception('ZLIB_ERROR') # ZLIB errors are critical
section_data = zlib.decompress(compressed_data) # ZLIB decompression
except:
section_data = zlib_data # Fallback to raw ZLIB data upon critical error
# Call the PFS Extract function on the decompressed PFS ZLIB Section
pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padding, is_structure, is_advanced)
pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padding, structure, advanced)
# Parse & Extract Dell PFS Volume
def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, is_structure=True, is_advanced=True):
def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, structure=True, advanced=True):
# Show PFS Volume indicator
if is_structure:
if structure:
printer('PFS Volume:', pfs_padd)
# Get PFS Header Structure values
@ -328,7 +359,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
return # Critical error, abort
# Show PFS Header Structure info
if is_structure:
if structure:
printer('PFS Header:\n', pfs_padd + 4)
pfs_hdr.struct_print(pfs_padd + 8)
@ -348,7 +379,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
while len(pfs_payload[entry_start:entry_start + pfs_entry_size]) == pfs_entry_size:
# Analyze PFS Entry Structure and get relevant info
_,entry_version,entry_guid,entry_data,entry_data_sig,entry_met,entry_met_sig,next_entry = \
parse_pfs_entry(pfs_payload, entry_start, pfs_entry_size, pfs_entry_struct, 'PFS Entry', pfs_padd, is_structure)
parse_pfs_entry(pfs_payload, entry_start, pfs_entry_size, pfs_entry_struct, 'PFS Entry', pfs_padd, structure)
entry_type = 'OTHER' # Adjusted later if PFS Entry is Zlib, PFAT, PFS Info, Model Info
@ -384,7 +415,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
entry_info_hdr = get_struct(filename_info, info_start, DellPfsInfo)
# Show PFS Information Header Structure info
if is_structure:
if structure:
printer('PFS Information Header:\n', pfs_padd + 4)
entry_info_hdr.struct_print(pfs_padd + 8)
@ -408,7 +439,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
entry_name = safe_name(name_data.decode('utf-16').strip()) # PFS Entry's FileName value
# Show PFS FileName Structure info
if is_structure:
if structure:
printer('PFS FileName Entry:\n', pfs_padd + 8)
entry_info_mod.struct_print(pfs_padd + 12, entry_name)
@ -436,7 +467,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
entry_info = get_struct(entry_metadata, 0, DellPfsMetadata)
# Show Nested PFS Metadata Structure info
if is_structure:
if structure:
printer('PFS Metadata Information:\n', pfs_padd + 4)
entry_info.struct_print(pfs_padd + 8)
@ -460,7 +491,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
entry_info_hdr = get_struct(signature_info, sign_start, DellPfsInfo)
# Show PFS Information Header Structure info
if is_structure:
if structure:
printer('PFS Information Header:\n', pfs_padd + 4)
entry_info_hdr.struct_print(pfs_padd + 8)
@ -476,7 +507,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
entry_hdr = get_struct(signature_info, sign_start + PFS_INFO_LEN, pfs_entry_struct)
# Show PFS Information Header Structure info
if is_structure:
if structure:
printer('PFS Information Entry:\n', pfs_padd + 8)
entry_hdr.struct_print(pfs_padd + 12)
@ -486,7 +517,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
sign_data_raw = signature_info[sign_info_start + 0x2:sign_info_start + 0x2 + sign_size]
sign_data_txt = f'{int.from_bytes(sign_data_raw, "little"):0{sign_size * 2}X}'
if is_structure:
if structure:
printer('Signature Information:\n', pfs_padd + 8)
printer(f'Signature Size: 0x{sign_size:X}', pfs_padd + 12, False)
printer(f'Signature Data: {sign_data_txt[:32]} [...]', pfs_padd + 12, False)
@ -500,7 +531,8 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
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_HEAD_LEN: continue
if len(entry_data) < PFS_HEAD_LEN:
continue
# Check if PFS Entry contains zlib-compressed sub-PFS Volume
pfs_zlib_offsets = get_section_offsets(entry_data)
@ -518,7 +550,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
if pfat_entry_hdr.Tag == b'PFS.HDR.' and is_pfat:
entry_type = 'PFAT' # Re-set PFS Entry Type from OTHER to PFAT, to use such info afterwards
entry_data = parse_pfat_pfs(pfat_entry_hdr, entry_data, pfs_padd, is_structure) # Parse sub-PFS PFAT Volume
entry_data = parse_pfat_pfs(pfat_entry_hdr, entry_data, pfs_padd, structure) # Parse sub-PFS PFAT Volume
# Parse PFS Entry which contains zlib-compressed sub-PFS Volume
elif pfs_zlib_offsets:
@ -539,7 +571,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
sub_pfs_path = os.path.join(output_path, str(pfs_count) + safe_name(sub_pfs_name))
# Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count)
pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4, is_structure, is_advanced)
pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4, structure, advanced)
entries_all[index][4] = entry_data # Adjust PFS Entry Data after parsing PFAT (same ZLIB raw data, not stored afterwards)
entries_all[index][3] = entry_type # Adjust PFS Entry Type from OTHER to PFAT or ZLIB (ZLIB is ignored at file extraction)
@ -560,10 +592,12 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
file_name = 'Model Information'
elif file_type == 'NAME_INFO':
file_name = 'Filename Information'
if not is_advanced: continue # Don't store Filename Information in non-advanced user mode
if not advanced:
continue # Don't store Filename Information in non-advanced user mode
elif file_type == 'SIG_INFO':
file_name = 'Signature Information'
if not is_advanced: continue # Don't store Signature Information in non-advanced user mode
if not advanced:
continue # Don't store Signature Information in non-advanced user mode
else:
file_name = ''
@ -580,6 +614,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
file_version = info_version
info_all[info_index][0] = 'USED' # PFS with zlib-compressed sub-PFS use the same GUID
break # Break at 1st Name match to not rename again from next zlib-compressed sub-PFS with the same GUID
# For both advanced & non-advanced users, the goal is to store final/usable files only
@ -590,29 +625,36 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i
is_zlib = bool(file_type == 'ZLIB') # Determine if PFS Entry Data was zlib-compressed
if file_data and not is_zlib: write_files.append([file_data, 'data']) # PFS Entry Data Payload
if file_data_sig and is_advanced: write_files.append([file_data_sig, 'sign_data']) # PFS Entry Data Signature
if file_meta and (is_zlib or is_advanced): write_files.append([file_meta, 'meta']) # PFS Entry Metadata Payload
if file_meta_sig and is_advanced: write_files.append([file_meta_sig, 'sign_meta']) # PFS Entry Metadata Signature
if file_data and not is_zlib:
write_files.append([file_data, 'data']) # PFS Entry Data Payload
if file_data_sig and advanced:
write_files.append([file_data_sig, 'sign_data']) # PFS Entry Data Signature
if file_meta and (is_zlib or advanced):
write_files.append([file_meta, 'meta']) # PFS Entry Metadata Payload
if file_meta_sig and advanced:
write_files.append([file_meta_sig, 'sign_meta']) # PFS Entry Metadata Signature
# Write/Extract PFS Entry files
for file in write_files:
full_name = f'{pfs_index}{pfs_name} -- {file_index} {file_name} v{file_version}' # Full PFS Entry Name
pfs_file_write(file[0], file[1], file_type, full_name, output_path, pfs_padd, is_structure, is_advanced)
pfs_file_write(file[0], file[1], file_type, full_name, output_path, pfs_padd, structure, advanced)
# Get PFS Footer Data after PFS Header Payload
pfs_footer = buffer[PFS_HEAD_LEN + pfs_hdr.PayloadSize:PFS_HEAD_LEN + pfs_hdr.PayloadSize + PFS_FOOT_LEN]
# Analyze PFS Footer Structure
chk_pfs_ftr(pfs_footer, pfs_payload, pfs_hdr.PayloadSize, 'PFS', pfs_padd, is_structure)
chk_pfs_ftr(pfs_footer, pfs_payload, pfs_hdr.PayloadSize, 'PFS', pfs_padd, structure)
# Analyze Dell PFS Entry Structure
def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, padding, is_structure=True):
def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, padding, structure=True):
# Get PFS Entry Structure values
pfs_entry = get_struct(entry_buffer, entry_start, entry_struct)
# Show PFS Entry Structure info
if is_structure:
if structure:
printer('PFS Entry:\n', padding + 4)
pfs_entry.struct_print(padding + 8)
@ -653,13 +695,13 @@ def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, p
return pfs_entry, entry_version, entry_guid, entry_data, entry_data_sig, entry_met, entry_met_sig, entry_met_sig_end
# Parse Dell PFS Volume with PFAT Payload
def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True):
def parse_pfat_pfs(entry_hdr, entry_data, padding, structure=True):
# Show PFS Volume indicator
if is_structure:
if structure:
printer('PFS Volume:', padding + 4)
# Show sub-PFS Header Structure Info
if is_structure:
if structure:
printer('PFS Header:\n', padding + 8)
entry_hdr.struct_print(padding + 12)
@ -679,11 +721,11 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True):
_, pfs_entry_size = get_pfs_entry(pfat_payload, 0) # Get initial PFS PFAT Entry Size for loop
while len(pfat_payload[pfat_entry_start:pfat_entry_start + pfs_entry_size]) == pfs_entry_size:
# Get sub-PFS PFAT Entry Structure & Size info
pfat_entry_struct, pfat_entry_size = get_pfs_entry(pfat_payload, pfat_entry_start)
pfat_entry_struct,pfat_entry_size = get_pfs_entry(pfat_payload, pfat_entry_start)
# Analyze sub-PFS PFAT Entry Structure and get relevant info
pfat_entry,_,_,pfat_entry_data,_,pfat_entry_met,_,pfat_next_entry = parse_pfs_entry(pfat_payload,
pfat_entry_start, pfat_entry_size, pfat_entry_struct, 'sub-PFS PFAT Entry', padding + 4, is_structure)
pfat_entry_start, pfat_entry_size, pfat_entry_struct, 'sub-PFS PFAT Entry', padding + 4, structure)
# Each sub-PFS PFAT Entry includes an AMI BIOS Guard (a.k.a. PFAT) block at the beginning
# We need to parse the PFAT block and remove its contents from the final Payload/Raw Data
@ -693,7 +735,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True):
pfat_hdr = get_struct(pfat_payload, pfat_hdr_off, IntelBiosGuardHeader)
# Show sub-PFS PFAT Header Structure info
if is_structure:
if structure:
printer(f'PFAT Block {pfat_entry_index} Header:\n', padding + 12)
pfat_hdr.struct_print(padding + 16)
@ -718,15 +760,14 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True):
pfat_sig = get_struct(pfat_payload, pfat_payload_end, IntelBiosGuardSignature2k)
# Show sub-PFS PFAT Signature Structure info
if is_structure:
if structure:
printer(f'PFAT Block {pfat_entry_index} Signature:\n', padding + 12)
pfat_sig.struct_print(padding + 16)
# Show PFAT Script via BIOS Guard Script Tool
if is_structure:
if structure:
printer(f'PFAT Block {pfat_entry_index} Script:\n', padding + 12)
# https://github.com/allowitsme/big-tool by Dmitry Frolov
_ = parse_bg_script(pfat_script_data, padding + 16)
# The payload of sub-PFS PFAT Entries is not in proper order by default
@ -740,7 +781,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True):
pfat_met = get_struct(pfat_entry_met, 0, DellPfsPfatMetadata)
# Show sub-PFS PFAT Metadata Structure info
if is_structure:
if structure:
printer(f'PFAT Block {pfat_entry_index} Metadata:\n', padding + 12)
pfat_met.struct_print(padding + 16)
@ -772,14 +813,15 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True):
pfat_data_all.sort() # Sort all sub-PFS PFAT Entries payloads/data based on their Order/Offset
entry_data = b'' # Initialize new sub-PFS Entry Data
for pfat_data in pfat_data_all: entry_data += pfat_data[1] # Merge all sub-PFS PFAT Entry Payload/Raw into the final sub-PFS Entry Data
for pfat_data in pfat_data_all:
entry_data += pfat_data[1] # Merge all sub-PFS PFAT Entry Payload/Raw into the final sub-PFS Entry Data
# Verify that the Order/Offset of the last PFAT Entry w/ its Size matches the final sub-PFS Entry Data Size
if len(entry_data) != pfat_data_all[-1][0] + len(pfat_data_all[-1][1]):
printer('Error: Detected sub-PFS PFAT Entry Buffer & Last Offset Size mismatch!', padding + 8)
# Analyze sub-PFS Footer Structure
chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padding + 4, is_structure)
chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padding + 4, structure)
return entry_data
@ -787,8 +829,11 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding, is_structure=True):
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 DellPfsEntryR1, ctypes.sizeof(DellPfsEntryR1)
if pfs_entry_ver == 2: return DellPfsEntryR2, ctypes.sizeof(DellPfsEntryR2)
if pfs_entry_ver == 1:
return DellPfsEntryR1, ctypes.sizeof(DellPfsEntryR1)
if pfs_entry_ver == 2:
return DellPfsEntryR2, ctypes.sizeof(DellPfsEntryR2)
return DellPfsEntryR2, ctypes.sizeof(DellPfsEntryR2)
@ -801,28 +846,35 @@ def get_entry_ver(version_fields, version_types):
for index,field in enumerate(version_fields):
eol = '' if index == len(version_fields) - 1 else '.'
if version_types[index] == 65: version += f'{field:X}{eol}' # 0x41 = ASCII
elif version_types[index] == 78: version += f'{field:d}{eol}' # 0x4E = Number
elif version_types[index] in (0, 32): version = version.strip('.') # 0x00 or 0x20 = Unused
else: version += f'{field:X}{eol}' # Unknown
if version_types[index] == 65:
version += f'{field:X}{eol}' # 0x41 = ASCII
elif version_types[index] == 78:
version += f'{field:d}{eol}' # 0x4E = Number
elif version_types[index] in (0, 32):
version = version.strip('.') # 0x00 or 0x20 = Unused
else:
version += f'{field:X}{eol}' # Unknown
return version
# Check if Dell PFS Header Version is known
def chk_hdr_ver(version, text, padding):
if version in (1,2): return
if version in (1,2):
return
printer(f'Error: Unknown {text} Header Version {version}!', padding)
return
# Analyze Dell PFS Footer Structure
def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_structure=True):
def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, structure=True):
# Get PFS Footer Structure values
pfs_ftr = get_struct(footer_buffer, 0, DellPfsFooter)
# Validate that a PFS Footer was parsed
if pfs_ftr.Tag == b'PFS.FTR.':
# Show PFS Footer Structure info
if is_structure:
if structure:
printer('PFS Footer:\n', padding + 4)
pfs_ftr.struct_print(padding + 8)
else:
@ -840,29 +892,31 @@ def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_structu
printer(f'Error: Invalid {text} Footer Payload Checksum!', padding + 4)
# Write/Extract Dell PFS Entry Files (Data, Metadata, Signature)
def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, is_structure=True, is_advanced=True):
def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, structure=True, advanced=True):
# Store Data/Metadata Signature (advanced users only)
if bin_name.startswith('sign'):
final_name = f'{safe_name(full_name)}.{bin_name.split("_")[1]}.sig'
final_path = os.path.join(out_path, final_name)
with open(final_path, 'wb') as pfs_out: pfs_out.write(bin_buff) # Write final Data/Metadata Signature
with open(final_path, 'wb') as pfs_out:
pfs_out.write(bin_buff) # Write final Data/Metadata Signature
return # Skip further processing for Signatures
# Store Data/Metadata Payload
bin_ext = f'.{bin_name}.bin' if is_advanced else '.bin' # Simpler Data/Metadata Extension for non-advanced users
bin_ext = f'.{bin_name}.bin' if advanced else '.bin' # Simpler Data/Metadata Extension for 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(bin_buff, bin_type, bin_name == 'meta', padding, is_structure, is_advanced)
is_text,final_data,file_ext,write_mode = bin_is_text(bin_buff, bin_type, bin_name == 'meta', padding, structure, advanced)
final_name = f'{safe_name(full_name)}{bin_ext[:-4] + file_ext if is_text else bin_ext}'
final_path = os.path.join(out_path, final_name)
with open(final_path, write_mode) as pfs_out: pfs_out.write(final_data) # Write final Data/Metadata Payload
with open(final_path, write_mode) as pfs_out:
pfs_out.write(final_data) # Write final Data/Metadata Payload
# Check if Dell PFS Entry file/data is Text/XML and Convert
def bin_is_text(buffer, file_type, is_metadata, pfs_padd, is_structure=True, is_advanced=True):
def bin_is_text(buffer, file_type, is_metadata, pfs_padd, structure=True, advanced=True):
is_text = False
write_mode = 'wb'
extension = '.bin'
@ -892,12 +946,13 @@ def bin_is_text(buffer, file_type, is_metadata, pfs_padd, is_structure=True, is_
buffer = text_buffer.getvalue()
# Show Model/PCR XML Information, if applicable
if is_structure and is_text and not is_metadata: # Metadata is shown at initial DellPfsMetadata analysis
if structure and is_text and not is_metadata: # Metadata is shown at initial DellPfsMetadata analysis
printer(f'PFS { {".txt": "Model", ".xml": "PCR XML"}[extension] } Information:\n', pfs_padd + 8)
_ = [printer(line.strip('\r'), pfs_padd + 12, False) for line in buffer.split('\n') if line]
# Only for non-advanced users due to signature (.sig) invalidation
if is_advanced: return False, buffer_in, '.bin', 'wb'
if advanced:
return False, buffer_in, '.bin', 'wb'
return is_text, buffer, extension, write_mode
@ -918,8 +973,8 @@ if __name__ == '__main__':
argparser.add_argument('-s', '--structure', help='show PFS structure information', action='store_true')
arguments = argparser.parse_args()
is_advanced = arguments.advanced # Set Advanced user mode optional argument
is_structure = arguments.structure # Set Structure output mode optional argument
advanced = arguments.advanced # Set Advanced user mode optional argument
structure = arguments.structure # Set Structure output mode optional argument
# Initialize script (must be after argparse)
exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4)
@ -929,42 +984,25 @@ if __name__ == '__main__':
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
with open(input_file, 'rb') as in_file:
input_buffer = in_file.read()
# Search input image for ThinOS PKG 7zXZ section header
lzma_pkg_hdr_match = is_pfs_pkg(input_buffer)
# 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_buffer[lzma_len_off:lzma_len_off + 0x4], 'little')
lzma_bin_off = lzma_pkg_hdr_match.end() - 0x5
lzma_bin_dat = input_buffer[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:
printer('Error: This Dell ThinOS PKG update image is corrupted!', padding)
continue # Next input file
input_buffer = lzma.decompress(lzma_bin_dat)
# Search input image for PFS ZLIB Sections
pfs_zlib_offsets = get_section_offsets(input_buffer)
if not pfs_zlib_offsets:
printer('Error: This is not a Dell PFS update image!', padding)
if not is_dell_pfs(input_buffer):
printer('Error: This is not a Dell PFS/PKG Update image!', padding)
continue # Next input file
extract_path = os.path.join(output_path, f'{input_name}_extracted')
extract_name = ' ' + os.path.splitext(input_name)[0]
extract_name = ' ' + path_stem(input_file)
if is_pfs_pkg(input_buffer):
input_buffer = thinos_pkg_extract(input_buffer)
# Parse each PFS ZLIB Section
for offset in pfs_zlib_offsets:
for zlib_offset in get_section_offsets(input_buffer):
# Call the PFS ZLIB Section Parser function
pfs_section_parse(input_buffer, offset, extract_path, extract_name, 1, 1, False, padding, is_structure, is_advanced)
pfs_section_parse(input_buffer, zlib_offset, extract_path, extract_name, 1, 1, False, padding, structure, advanced)
exit_code -= 1

View file

@ -7,7 +7,7 @@ Fujitsu UPC BIOS Extractor
Copyright (C) 2021-2022 Plato Mavropoulos
"""
TITLE = 'Fujitsu UPC BIOS Extractor v2.0_a3'
TITLE = 'Fujitsu UPC BIOS Extractor v2.0_a4'
import os
import sys
@ -55,7 +55,8 @@ if __name__ == '__main__':
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
with open(input_file, 'rb') as in_file:
input_buffer = in_file.read()
if not is_fujitsu_upc(input_buffer):
printer('Error: This is not a Fujitsu UPC BIOS image!', padding)

222
Insyde_IFD_Extract.py Normal file
View file

@ -0,0 +1,222 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Insyde IFD Extract
Insyde iFlash/iFdPacker Extractor
Copyright (C) 2022 Plato Mavropoulos
"""
TITLE = 'Insyde iFlash/iFdPacker Extractor v2.0_a8'
import os
import sys
import ctypes
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.comp_szip import is_szip_supported, szip_decompress
from common.path_ops import get_path_files, make_dirs, safe_name
from common.patterns import PAT_INSYDE_IFL, PAT_INSYDE_SFX
from common.struct_ops import char, get_struct, uint32_t
from common.system import argparse_init, printer, script_init
from common.text_ops import file_to_bytes
class IflashHeader(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('Signature', char*8), # 0x00 $_IFLASH
('ImageTag', char*8), # 0x08
('TotalSize', uint32_t), # 0x10 from header end
('ImageSize', uint32_t), # 0x14 from header end
# 0x18
]
def get_image_tag(self):
return self.ImageTag.decode('utf-8','ignore').strip('_')
def struct_print(self, p):
printer(['Signature :', self.Signature.decode('utf-8')], p, False)
printer(['Image Name:', self.get_image_tag()], p, False)
printer(['Image Size:', f'0x{self.ImageSize:X}'], p, False)
printer(['Total Size:', f'0x{self.TotalSize:X}'], p, False)
# Check if input is Insyde iFlash/iFdPacker Update image
def is_insyde_ifd(input_file):
input_buffer = file_to_bytes(input_file)
is_ifl = bool(insyde_iflash_detect(input_buffer))
is_sfx = bool(PAT_INSYDE_SFX.search(input_buffer))
return is_ifl or is_sfx
# Parse & Extract Insyde iFlash/iFdPacker Update images
def insyde_ifd_extract(input_file, output_path, padding=0):
input_buffer = file_to_bytes(input_file)
extract_path = os.path.join(f'{output_path}_extracted')
iflash_code = insyde_iflash_extract(input_buffer, extract_path, padding)
ifdpack_path = os.path.join(extract_path, 'Insyde iFdPacker SFX')
ifdpack_code = insyde_packer_extract(input_buffer, ifdpack_path, padding)
return iflash_code and ifdpack_code
# Detect Insyde iFlash Update image
def insyde_iflash_detect(input_buffer):
iflash_match_all = []
iflash_match_nan = [0x0,0xFFFFFFFF]
for iflash_match in PAT_INSYDE_IFL.finditer(input_buffer):
ifl_bgn = iflash_match.start()
if len(input_buffer[ifl_bgn:]) <= IFL_HDR_LEN:
continue
ifl_hdr = get_struct(input_buffer, ifl_bgn, IflashHeader)
if ifl_hdr.TotalSize in iflash_match_nan \
or ifl_hdr.ImageSize in iflash_match_nan \
or ifl_hdr.TotalSize <= ifl_hdr.ImageSize:
continue
iflash_match_all.append([ifl_bgn, ifl_hdr])
return iflash_match_all
# Extract Insyde iFlash Update image
def insyde_iflash_extract(input_buffer, extract_path, padding=0):
insyde_iflash_all = insyde_iflash_detect(input_buffer)
if not insyde_iflash_all:
return 1
printer('Detected Insyde iFlash Update image!', padding)
make_dirs(extract_path, delete=True)
for insyde_iflash in insyde_iflash_all:
ifl_bgn,ifl_hdr = insyde_iflash
img_bgn = ifl_bgn + IFL_HDR_LEN
img_end = img_bgn + ifl_hdr.ImageSize
img_bin = input_buffer[img_bgn:img_end]
img_val = [ifl_hdr.get_image_tag(), 'bin']
img_tag,img_ext = IFL_IMG_NAMES.get(img_val[0], img_val)
img_name = f'{img_tag} [0x{img_bgn:08X}-0x{img_end:08X}]'
printer(f'{img_name}\n', padding + 4)
ifl_hdr.struct_print(padding + 8)
if img_val == [img_tag,img_ext]:
printer(f'Note: Detected new Insyde iFlash tag {img_tag}!', padding + 12, pause=True)
out_name = f'{img_name}.{img_ext}'
out_path = os.path.join(extract_path, safe_name(out_name))
with open(out_path, 'wb') as out_image:
out_image.write(img_bin)
printer(f'Succesfull Insyde iFlash > {img_tag} extraction!', padding + 12)
return 0
# Extract Insyde iFdPacker 7-Zip SFX 7z Update image
def insyde_packer_extract(input_buffer, extract_path, padding=0):
match_sfx = PAT_INSYDE_SFX.search(input_buffer)
if not match_sfx:
return 127
printer('Detected Insyde iFdPacker Update image!', padding)
make_dirs(extract_path, delete=True)
sfx_buffer = bytearray(input_buffer[match_sfx.end() - 0x5:])
if sfx_buffer[:0x5] == b'\x6E\xF4\x79\x5F\x4E':
printer('Detected Insyde iFdPacker > 7-Zip SFX obfuscation!', padding + 4)
for index,byte in enumerate(sfx_buffer):
sfx_buffer[index] = byte // 2 + (128 if byte % 2 else 0)
printer('Removed Insyde iFdPacker > 7-Zip SFX obfuscation!', padding + 8)
printer('Extracting Insyde iFdPacker > 7-Zip SFX archive...', padding + 4)
sfx_path = os.path.join(extract_path, 'Insyde_iFdPacker_SFX.7z')
with open(sfx_path, 'wb') as sfx_file:
sfx_file.write(sfx_buffer)
if is_szip_supported(sfx_path, padding + 8, check=True):
if szip_decompress(sfx_path, extract_path, 'Insyde iFdPacker > 7-Zip SFX', padding + 8, check=True) == 0:
os.remove(sfx_path)
else:
return 125
else:
return 126
exit_codes = []
for sfx_file in get_path_files(extract_path):
if is_insyde_ifd(sfx_file):
printer(f'{os.path.basename(sfx_file)}', padding + 12)
ifd_code = insyde_ifd_extract(sfx_file, sfx_file, padding + 16)
exit_codes.append(ifd_code)
return sum(exit_codes)
# Insyde iFlash Image Names
IFL_IMG_NAMES = {
'BIOSCER' : ['Certificate', 'bin'],
'BIOSCR2' : ['Certificate 2nd', 'bin'],
'BIOSIMG' : ['BIOS-UEFI', 'bin'],
'DRV_IMG' : ['isflash', 'efi'],
'EC_IMG' : ['Embedded Controller', 'bin'],
'INI_IMG' : ['platform', 'ini'],
'ME_IMG' : ['Management Engine', 'bin'],
'OEM_ID' : ['OEM Identifier', 'bin'],
}
# Get common ctypes Structure Sizes
IFL_HDR_LEN = ctypes.sizeof(IflashHeader)
if __name__ == '__main__':
# Set argparse Arguments
argparser = argparse_init()
arguments = argparser.parse_args()
# Initialize script (must be after argparse)
exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4)
for input_file in input_files:
input_name = os.path.basename(input_file)
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file:
input_buffer = in_file.read()
if not is_insyde_ifd(input_buffer):
printer('Error: This is not an Insyde iFlash/iFdPacker Update image!', padding)
continue # Next input file
extract_path = os.path.join(output_path, input_name)
insyde_ifd_extract(input_buffer, extract_path, padding)
exit_code -= 1
printer('Done!', pause=True)
sys.exit(exit_code)

View file

@ -1,148 +0,0 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Insyde iFlash Extract
Insyde iFlash Update Extractor
Copyright (C) 2022 Plato Mavropoulos
"""
TITLE = 'Insyde iFlash Update Extractor v2.0_a2'
import os
import sys
import ctypes
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.path_ops import make_dirs, safe_name
from common.patterns import PAT_INSYDE_IFL
from common.struct_ops import get_struct, char, uint32_t
from common.system import script_init, argparse_init, printer
from common.text_ops import file_to_bytes
class IflashHeader(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('Signature', char*9), # 0x00 $_IFLASH_
('ImageTag', char*7), # 0x08
('TotalSize', uint32_t), # 0x10 from header end
('ImageSize', uint32_t), # 0x14 from header end
# 0x18
]
def struct_print(self, p):
printer(['Signature :', self.Signature.decode('utf-8','ignore')], p, False)
printer(['Image Name:', self.ImageTag.decode('utf-8','ignore')], p, False)
printer(['Image Size:', f'0x{self.ImageSize:X}'], p, False)
printer(['Total Size:', f'0x{self.TotalSize:X}'], p, False)
# Parse & Extract Insyde iFlash Update image
def insyde_iflash_extract(input_buffer, ins_ifl_all, output_path, padding=0):
extract_path = os.path.join(f'{output_path}_extracted')
make_dirs(extract_path, delete=True)
for ins_ifl_val in ins_ifl_all:
ins_ifl_off,ins_ifl_hdr = ins_ifl_val
mod_bgn = ins_ifl_off + IFL_HDR_LEN
mod_end = mod_bgn + ins_ifl_hdr.ImageSize
mod_bin = input_buffer[mod_bgn:mod_end]
mod_val = [ins_ifl_hdr.ImageTag.decode('utf-8','ignore'), 'bin']
mod_tag,mod_ext = IFL_MOD_NAMES.get(mod_val[0], mod_val)
mod_name = f'{mod_tag} [0x{mod_bgn:08X}-0x{mod_end:08X}]'
printer(f'{mod_name}\n', padding)
ins_ifl_hdr.struct_print(padding + 4)
if mod_val == [mod_tag,mod_ext]:
printer(f'Note: Detected new Insyde iFlash image tag {mod_tag}!', padding + 8, pause=True)
out_name = f'{mod_name}.{mod_ext}'
out_path = os.path.join(extract_path, safe_name(out_name))
with open(out_path, 'wb') as out: out.write(mod_bin)
printer('Succesfull Insyde iFlash image extraction!', padding + 8)
# Get Insyde iFlash Update image matches
def get_insyde_iflash(in_file):
ins_ifl_all = []
ins_ifl_nan = [0x0,0xFFFFFFFF]
buffer = file_to_bytes(in_file)
for ins_ifl_match in PAT_INSYDE_IFL.finditer(buffer):
ins_ifl_off = ins_ifl_match.start()
if len(buffer[ins_ifl_off:]) <= IFL_HDR_LEN:
continue
ins_ifl_hdr = get_struct(buffer, ins_ifl_off, IflashHeader)
if ins_ifl_hdr.TotalSize in ins_ifl_nan \
or ins_ifl_hdr.ImageSize in ins_ifl_nan \
or ins_ifl_hdr.TotalSize <= ins_ifl_hdr.ImageSize:
continue
ins_ifl_all.append([ins_ifl_off, ins_ifl_hdr])
return ins_ifl_all
# Check if input is Insyde iFlash Update image
def is_insyde_iflash(in_file):
buffer = file_to_bytes(in_file)
return bool(get_insyde_iflash(buffer))
IFL_MOD_NAMES = {
'DRV_IMG' : ['isflash', 'efi'],
'INI_IMG' : ['platform', 'ini'],
'BIOSIMG' : ['BIOS-UEFI', 'bin'],
'ME_IMG_' : ['Management Engine', 'bin'],
'EC_IMG_' : ['Embedded Controller', 'bin'],
'OEM_ID_' : ['OEM Identifier', 'bin'],
'BIOSCER' : ['Certificate', 'bin'],
'BIOSCR2' : ['Certificate 2nd', 'bin'],
}
# Get common ctypes Structure Sizes
IFL_HDR_LEN = ctypes.sizeof(IflashHeader)
if __name__ == '__main__':
# Set argparse Arguments
argparser = argparse_init()
arguments = argparser.parse_args()
# Initialize script (must be after argparse)
exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4)
for input_file in input_files:
input_name = os.path.basename(input_file)
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
ins_ifl_all = get_insyde_iflash(input_buffer)
if not ins_ifl_all:
printer('Error: This is not an Insyde iFlash Update image!', padding)
continue # Next input file
extract_path = os.path.join(output_path, input_name)
insyde_iflash_extract(input_buffer, ins_ifl_all, extract_path, padding)
exit_code -= 1
printer('Done!', pause=True)
sys.exit(exit_code)

View file

@ -7,7 +7,7 @@ Panasonic BIOS Package Extractor
Copyright (C) 2018-2022 Plato Mavropoulos
"""
TITLE = 'Panasonic BIOS Package Extractor v2.0_a8'
TITLE = 'Panasonic BIOS Package Extractor v2.0_a9'
import os
import io
@ -22,10 +22,10 @@ from common.comp_szip import is_szip_supported, szip_decompress
from common.path_ops import get_path_files, make_dirs, path_stem, safe_name
from common.pe_ops import get_pe_file, get_pe_info, is_pe_file, show_pe_info
from common.patterns import PAT_MICROSOFT_CAB
from common.system import script_init, argparse_init, printer
from common.system import argparse_init, printer, script_init
from common.text_ops import file_to_bytes
from AMI_PFAT_Extract import get_ami_pfat, parse_pfat_file
from AMI_PFAT_Extract import is_ami_pfat, parse_pfat_file
# Check if input is Panasonic BIOS Package PE
def is_panasonic_pkg(in_file):
@ -117,12 +117,10 @@ def panasonic_res_extract(pe_name, pe_file, extract_path, padding=0):
printer('Succesfull PE Resource extraction!', padding + 8)
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
pfat_match,pfat_buffer = get_ami_pfat(res_raw)
if pfat_match:
if is_ami_pfat(res_raw):
pfat_dir = os.path.join(extract_path, res_tag)
parse_pfat_file(pfat_buffer, pfat_dir, padding + 12)
parse_pfat_file(res_raw, pfat_dir, padding + 12)
else:
if is_pe_file(res_raw):
res_ext = 'exe'
@ -219,7 +217,8 @@ if __name__ == '__main__':
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
with open(input_file, 'rb') as in_file:
input_buffer = in_file.read()
# Check if Panasonic BIOS Package pattern was found on executable
if not is_panasonic_pkg(input_buffer):

View file

@ -7,7 +7,7 @@ Phoenix TDK Packer Extractor
Copyright (C) 2021-2022 Plato Mavropoulos
"""
TITLE = 'Phoenix TDK Packer Extractor v2.0_a8'
TITLE = 'Phoenix TDK Packer Extractor v2.0_a9'
import os
import sys
@ -17,11 +17,11 @@ import ctypes
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.path_ops import safe_name, make_dirs
from common.path_ops import make_dirs, safe_name
from common.pe_ops import get_pe_file, get_pe_info
from common.patterns import PAT_PHOENIX_TDK, PAT_MICROSOFT_MZ, PAT_MICROSOFT_PE
from common.struct_ops import get_struct, char, uint32_t
from common.system import script_init, argparse_init, printer
from common.patterns import PAT_MICROSOFT_MZ, PAT_MICROSOFT_PE, PAT_PHOENIX_TDK
from common.struct_ops import char, get_struct, uint32_t
from common.system import argparse_init, printer, script_init
from common.text_ops import file_to_bytes
class PhoenixTdkHeader(ctypes.LittleEndianStructure):
@ -152,7 +152,9 @@ def is_phoenix_tdk(in_file):
return bool(get_phoenix_tdk(buffer)[1] is not None)
# Parse & Extract Phoenix Tools Development Kit (TDK) Packer
def phoenix_tdk_extract(input_buffer, output_path, pack_off, base_off=0, padding=0):
def phoenix_tdk_extract(input_file, output_path, padding=0):
input_buffer = file_to_bytes(input_file)
exit_code = 0
extract_path = os.path.join(f'{output_path}_extracted')
@ -161,6 +163,8 @@ def phoenix_tdk_extract(input_buffer, output_path, pack_off, base_off=0, padding
printer('Phoenix Tools Development Kit Packer', padding)
base_off,pack_off = get_phoenix_tdk(input_buffer)
# Parse TDK Header structure
tdk_hdr = get_struct(input_buffer, pack_off, PhoenixTdkHeader)
@ -224,7 +228,8 @@ def phoenix_tdk_extract(input_buffer, output_path, pack_off, base_off=0, padding
if os.path.isfile(mod_file): mod_file += f'_{entry_index + 1:02d}'
# Save TDK Entry data to output file
with open(mod_file, 'wb') as out_file: out_file.write(mod_data)
with open(mod_file, 'wb') as out_file:
out_file.write(mod_data)
return exit_code
@ -248,19 +253,18 @@ if __name__ == '__main__':
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
tdk_base_off,tdk_pack_off = get_phoenix_tdk(input_buffer)
with open(input_file, 'rb') as in_file:
input_buffer = in_file.read()
# Check if Phoenix TDK Packer pattern was found on executable
if tdk_pack_off is None:
if not is_phoenix_tdk(input_buffer):
printer('Error: This is not a Phoenix TDK Packer executable!', padding)
continue # Next input file
extract_path = os.path.join(output_path, input_name)
if phoenix_tdk_extract(input_buffer, extract_path, tdk_pack_off, tdk_base_off, padding) == 0:
if phoenix_tdk_extract(input_buffer, extract_path, padding) == 0:
exit_code -= 1
printer('Done!', pause=True)

View file

@ -7,7 +7,7 @@ Portwell EFI Update Extractor
Copyright (C) 2021-2022 Plato Mavropoulos
"""
TITLE = 'Portwell EFI Update Extractor v2.0_a10'
TITLE = 'Portwell EFI Update Extractor v2.0_a11'
import os
import sys
@ -16,10 +16,10 @@ import sys
sys.dont_write_bytecode = True
from common.comp_efi import efi_decompress, is_efi_compressed
from common.path_ops import safe_name, make_dirs
from common.path_ops import make_dirs, safe_name
from common.pe_ops import get_pe_file
from common.patterns import PAT_PORTWELL_EFI, PAT_MICROSOFT_MZ
from common.system import script_init, argparse_init, printer
from common.patterns import PAT_MICROSOFT_MZ, PAT_PORTWELL_EFI
from common.system import argparse_init, printer, script_init
from common.text_ops import file_to_bytes
FILE_NAMES = {
@ -34,10 +34,13 @@ FILE_NAMES = {
def is_portwell_efi(in_file):
in_buffer = file_to_bytes(in_file)
try: pe_buffer = get_portwell_pe(in_buffer)[1]
except: pe_buffer = b''
try:
pe_buffer = get_portwell_pe(in_buffer)[1]
except:
pe_buffer = b''
is_mz = PAT_MICROSOFT_MZ.search(in_buffer[:0x2]) # EFI images start with PE Header MZ
is_uu = PAT_PORTWELL_EFI.search(pe_buffer[:0x4]) # Portwell EFI files start with <UU>
return bool(is_mz and is_uu)
@ -104,7 +107,8 @@ def get_unpacker_tag(input_buffer, pe_file):
# Process Portwell UEFI Unpacker payload files
def parse_efi_files(extract_path, efi_files, padding):
for file_index,file_data in enumerate(efi_files):
if file_data in (b'', b'NULL'): continue # Skip empty/unused files
if file_data in (b'', b'NULL'):
continue # Skip empty/unused files
file_name = FILE_NAMES.get(file_index, f'Unknown_{file_index}.bin') # Assign Name to EFI file
@ -115,7 +119,8 @@ def parse_efi_files(extract_path, efi_files, padding):
file_path = os.path.join(extract_path, safe_name(file_name)) # Store EFI file output path
with open(file_path, 'wb') as out_file: out_file.write(file_data) # Store EFI file data to drive
with open(file_path, 'wb') as out_file:
out_file.write(file_data) # Store EFI file data to drive
# Attempt to detect EFI compression & decompress when applicable
if is_efi_compressed(file_data):
@ -139,7 +144,8 @@ if __name__ == '__main__':
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
with open(input_file, 'rb') as in_file:
input_buffer = in_file.read()
if not is_portwell_efi(input_buffer):
printer('Error: This is not a Portwell EFI Update Package!', padding)

View file

@ -6,9 +6,9 @@
* [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor)
* [**AMI UCP Update Extractor**](#ami-ucp-update-extractor)
* [**Award BIOS Module Extractor**](#award-bios-module-extractor)
* [**Dell PFS Update Extractor**](#dell-pfs-update-extractor)
* [**Dell PFS/PKG Update Extractor**](#dell-pfs-pkg-update-extractor)
* [**Fujitsu UPC BIOS Extractor**](#fujitsu-upc-bios-extractor)
* [**Insyde iFlash Update Extractor**](#insyde-iflash-update-extractor)
* [**Insyde iFlash/iFdPacker Extractor**](#insyde-iflash-ifdpacker-update-extractor)
* [**Panasonic BIOS Package Extractor**](#panasonic-bios-package-extractor)
* [**Phoenix TDK Packer Extractor**](#phoenix-tdk-packer-extractor)
* [**Portwell EFI Update Extractor**](#portwell-efi-update-extractor)
@ -37,7 +37,7 @@ You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s)
#### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support.
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
#### **Prerequisites**
@ -49,7 +49,7 @@ Optionally, to decompile the AMI PFAT \> Intel BIOS Guard Scripts, you must have
PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often.
1. Make sure Python 3.8.0 or newer is installed:
1. Make sure Python 3.10.0 or newer is installed:
> python --version
@ -81,7 +81,7 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con
#### **Description**
Parses AMI UCP (Utility Configuration Program) Update executables, extracts their firmware components (e.g. SPI/BIOS/UEFI, EC, ME etc) and shows all relevant info. It supports all AMI UCP revisions and formats, including those with nested AMI PFAT, AMI UCP or Insyde SFX structures. The output comprises only final firmware components and utilities which are directly usable by end users.
Parses AMI UCP (Utility Configuration Program) Update executables, extracts their firmware components (e.g. SPI/BIOS/UEFI, EC, ME etc) and shows all relevant info. It supports all AMI UCP revisions and formats, including those with nested AMI PFAT, AMI UCP or Insyde iFlash/iFdPacker structures. The output comprises only final firmware components and utilities which are directly usable by end users.
#### **Usage**
@ -96,7 +96,7 @@ You can either Drag & Drop or manually enter AMI UCP Update executable file(s).
#### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support.
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
#### **Prerequisites**
@ -113,7 +113,7 @@ Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (wh
PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often.
1. Make sure Python 3.8.0 or newer is installed:
1. Make sure Python 3.10.0 or newer is installed:
> python --version
@ -161,7 +161,7 @@ You can either Drag & Drop or manually enter Award BIOS image file(s). Optional
#### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support.
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
#### **Prerequisites**
@ -173,7 +173,7 @@ To run the utility, you must have the following 3rd party tool at the "external"
PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often.
1. Make sure Python 3.8.0 or newer is installed:
1. Make sure Python 3.10.0 or newer is installed:
> python --version
@ -199,17 +199,17 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con
![]()
## **Dell PFS Update Extractor**
## **Dell PFS/PKG Update Extractor**
![]()
#### **Description**
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.
Parses Dell PFS/PKG 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/PKG 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.
#### **Usage**
You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional arguments:
You can either Drag & Drop or manually enter Dell PFS/PKG Update images(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
@ -221,7 +221,7 @@ You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional
#### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support.
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
#### **Prerequisites**
@ -233,7 +233,7 @@ Optionally, to decompile the Intel BIOS Guard (PFAT) Scripts, you must have the
PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often.
1. Make sure Python 3.8.0 or newer is installed:
1. Make sure Python 3.10.0 or newer is installed:
> python --version
@ -279,7 +279,7 @@ You can either Drag & Drop or manually enter Fujitsu UPC BIOS image file(s). Opt
#### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support.
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
#### **Prerequisites**
@ -291,7 +291,7 @@ To run the utility, you must have the following 3rd party tool at the "external"
PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often.
1. Make sure Python 3.8.0 or newer is installed:
1. Make sure Python 3.10.0 or newer is installed:
> python --version
@ -317,17 +317,17 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con
![]()
## **Insyde iFlash Update Extractor**
## **Insyde iFlash/iFdPacker Extractor**
![]()
#### **Description**
Parses Insyde iFlash Update images and extracts their firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and utilities (e.g. Flasher, Configuration etc) components. The output comprises only final firmware components which are directly usable by end users.
Parses Insyde iFlash/iFdPacker Update images and extracts their firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and utilities (e.g. Flasher, Configuration etc) components. It supports all Insyde iFlash/iFdPacker revisions and formats, including those which are \7-Zip SFX 7z compressed in raw or obfuscated form. The output comprises only final firmware components which are directly usable by end users.
#### **Usage**
You can either Drag & Drop or manually enter Insyde iFlash Update image file(s). Optional arguments:
You can either Drag & Drop or manually enter Insyde iFlash/iFdPacker Update image file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
@ -337,7 +337,7 @@ You can either Drag & Drop or manually enter Insyde iFlash Update image file(s).
#### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support.
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
#### **Prerequisites**
@ -347,7 +347,7 @@ To run the utility, you do not need any prerequisites.
PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often.
1. Make sure Python 3.8.0 or newer is installed:
1. Make sure Python 3.10.0 or newer is installed:
> python --version
@ -357,7 +357,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform
3. Build/Freeze/Compile:
> pyinstaller --noupx --onefile \<path-to-project\>\/Insyde_iFlash_Extract.py
> pyinstaller --noupx --onefile \<path-to-project\>\/Insyde_IFD_Extract.py
At dist folder you should find the final utility executable
@ -389,7 +389,7 @@ You can either Drag & Drop or manually enter Panasonic BIOS Package executable f
#### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support.
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
#### **Prerequisites**
@ -406,7 +406,7 @@ Moreover, you must have the following 3rd party tool at the "external" project d
PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often.
1. Make sure Python 3.8.0 or newer is installed:
1. Make sure Python 3.10.0 or newer is installed:
> python --version
@ -456,7 +456,7 @@ You can either Drag & Drop or manually enter Phoenix Tools Development Kit (TDK)
#### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support.
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
#### **Prerequisites**
@ -468,7 +468,7 @@ To run the utility, you must have the following 3rd party Python module installe
PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often.
1. Make sure Python 3.8.0 or newer is installed:
1. Make sure Python 3.10.0 or newer is installed:
> python --version
@ -514,7 +514,7 @@ You can either Drag & Drop or manually enter Portwell UEFI Unpacker EFI executab
#### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support.
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
#### **Prerequisites**
@ -532,7 +532,7 @@ Moreover, you must have the following 3rd party tool at the "external" project d
PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often.
1. Make sure Python 3.8.0 or newer is installed:
1. Make sure Python 3.10.0 or newer is installed:
> python --version
@ -582,7 +582,7 @@ You can either Drag & Drop or manually enter Toshiba BIOS COM image file(s). Opt
#### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support.
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
#### **Prerequisites**
@ -594,7 +594,7 @@ To run the utility, you must have the following 3rd party tool at the "external"
PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often.
1. Make sure Python 3.8.0 or newer is installed:
1. Make sure Python 3.10.0 or newer is installed:
> python --version
@ -640,7 +640,7 @@ You can either Drag & Drop or manually enter VAIO Packaging Manager executable f
#### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support.
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
#### **Prerequisites**
@ -650,7 +650,7 @@ To run the utility, you do not need any prerequisites.
PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often.
1. Make sure Python 3.8.0 or newer is installed:
1. Make sure Python 3.10.0 or newer is installed:
> python --version

View file

@ -7,7 +7,7 @@ Toshiba BIOS COM Extractor
Copyright (C) 2018-2022 Plato Mavropoulos
"""
TITLE = 'Toshiba BIOS COM Extractor v2.0_a2'
TITLE = 'Toshiba BIOS COM Extractor v2.0_a3'
import os
import sys
@ -49,7 +49,8 @@ def toshiba_com_extract(input_file, output_path, padding=0):
try:
subprocess.run([get_comextract_path(), input_file, output_file], check=True, stdout=subprocess.DEVNULL)
if not os.path.isfile(output_file): raise Exception('EXTRACT_FILE_MISSING')
if not os.path.isfile(output_file):
raise Exception('EXTRACT_FILE_MISSING')
except:
printer(f'Error: ToshibaComExtractor could not extract file {input_file}!', padding)
@ -72,7 +73,8 @@ if __name__ == '__main__':
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
with open(input_file, 'rb') as in_file:
input_buffer = in_file.read()
if not is_toshiba_com(input_file):
printer('Error: This is not a Toshiba BIOS COM image!', padding)

View file

@ -7,7 +7,7 @@ VAIO Packaging Manager Extractor
Copyright (C) 2019-2022 Plato Mavropoulos
"""
TITLE = 'VAIO Packaging Manager Extractor v3.0_a6'
TITLE = 'VAIO Packaging Manager Extractor v3.0_a7'
import os
import sys
@ -17,8 +17,8 @@ sys.dont_write_bytecode = True
from common.comp_szip import is_szip_supported, szip_decompress
from common.path_ops import make_dirs
from common.patterns import PAT_VAIO_CFG, PAT_VAIO_CHK, PAT_VAIO_EXT, PAT_VAIO_CAB
from common.system import script_init, argparse_init, printer
from common.patterns import PAT_VAIO_CAB, PAT_VAIO_CFG, PAT_VAIO_CHK, PAT_VAIO_EXT
from common.system import argparse_init, printer, script_init
from common.text_ops import file_to_bytes
# Check if input is VAIO Packaging Manager
@ -31,7 +31,8 @@ def is_vaio_pkg(in_file):
def vaio_cabinet(name, buffer, extract_path, padding=0):
match_cab = PAT_VAIO_CAB.search(buffer) # Microsoft CAB Header XOR 0xFF
if not match_cab: return 1
if not match_cab:
return 1
printer('Detected obfuscated CAB archive!', padding)
@ -51,7 +52,8 @@ def vaio_cabinet(name, buffer, extract_path, padding=0):
cab_path = os.path.join(extract_path, f'{name}_Temporary.cab')
with open(cab_path, 'wb') as cab_file: cab_file.write(cab_data) # Create temporary CAB archive
with open(cab_path, 'wb') as cab_file:
cab_file.write(cab_data) # Create temporary CAB archive
if is_szip_supported(cab_path, padding + 8, check=True):
if szip_decompress(cab_path, extract_path, 'CAB', padding + 8, check=True) == 0:
@ -67,7 +69,8 @@ def vaio_cabinet(name, buffer, extract_path, padding=0):
def vaio_unlock(name, buffer, extract_path, padding=0):
match_cfg = PAT_VAIO_CFG.search(buffer)
if not match_cfg: return 1
if not match_cfg:
return 1
printer('Attempting to Unlock executable!', padding)
@ -116,7 +119,8 @@ def vaio_unlock(name, buffer, extract_path, padding=0):
# Store Unlocked VAIO Packaging Manager executable
if vaio_check and user_path:
unlock_path = os.path.join(extract_path, f'{name}_Unlocked.exe')
with open(unlock_path, 'wb') as unl_file: unl_file.write(buffer)
with open(unlock_path, 'wb') as unl_file:
unl_file.write(buffer)
return 0
@ -149,7 +153,8 @@ if __name__ == '__main__':
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
with open(input_file, 'rb') as in_file:
input_buffer = in_file.read()
# Check if VAIO Packaging Manager pattern was found on executable
if not is_vaio_pkg(input_buffer):

View file

@ -22,8 +22,10 @@ def is_efi_compressed(data, strict=True):
check_diff = size_comp < size_orig
if strict: check_size = size_comp + 0x8 == len(data)
else: check_size = size_comp + 0x8 <= len(data)
if strict:
check_size = size_comp + 0x8 == len(data)
else:
check_size = size_comp + 0x8 <= len(data)
return check_diff and check_size
@ -38,9 +40,11 @@ def efi_decompress(in_path, out_path, padding=0, silent=False, comp_type='--uefi
try:
subprocess.run([get_tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type], check=True, stdout=subprocess.DEVNULL)
with open(in_path, 'rb') as file: _,size_orig = get_compress_sizes(file.read())
with open(in_path, 'rb') as file:
_,size_orig = get_compress_sizes(file.read())
if os.path.getsize(out_path) != size_orig: raise Exception('EFI_DECOMPRESS_ERROR')
if os.path.getsize(out_path) != size_orig:
raise Exception('EFI_DECOMPRESS_ERROR')
except:
if not silent:
printer(f'Error: TianoCompress could not extract file {in_path}!', padding)

View file

@ -27,7 +27,8 @@ def is_szip_supported(in_path, padding=0, check=False, silent=False):
try:
szip_t = subprocess.run([get_szip_path(), 't', in_path, '-bso0', '-bse0', '-bsp0'], check=False)
if check: check_bad_exit_code(szip_t.returncode)
if check:
check_bad_exit_code(szip_t.returncode)
except:
if not silent:
printer(f'Error: 7-Zip could not check support for file {in_path}!', padding)
@ -38,14 +39,17 @@ def is_szip_supported(in_path, padding=0, check=False, silent=False):
# Archive decompression via 7-Zip
def szip_decompress(in_path, out_path, in_name, padding=0, check=False, silent=False):
if not in_name: in_name = 'archive'
if not in_name:
in_name = 'archive'
try:
szip_x = subprocess.run([get_szip_path(), 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + out_path, in_path], check=False)
if check: check_bad_exit_code(szip_x.returncode)
if check:
check_bad_exit_code(szip_x.returncode)
if not os.path.isdir(out_path): raise Exception('EXTRACT_DIR_MISSING')
if not os.path.isdir(out_path):
raise Exception('EXTRACT_DIR_MISSING')
except:
if not silent:
printer(f'Error: 7-Zip could not extract {in_name} file {in_path}!', padding)

View file

@ -6,6 +6,7 @@ Copyright (C) 2022 Plato Mavropoulos
"""
# https://github.com/allowitsme/big-tool by Dmitry Frolov
# https://github.com/platomav/BGScriptTool by Plato Mavropoulos
def get_bgs_tool():
try:
from external.big_script_tool import BigScript

View file

@ -84,7 +84,8 @@ def is_path_absolute(in_path):
# Create folder(s), controlling parents, existence and prior deletion
def make_dirs(in_path, parents=True, exist_ok=False, delete=False):
if delete: del_dirs(in_path)
if delete:
del_dirs(in_path)
Path.mkdir(Path(in_path), parents=parents, exist_ok=exist_ok)
@ -129,7 +130,8 @@ def get_argparse_path(argparse_path):
# Process input files (argparse object)
def process_input_files(argparse_args, sys_argv=None):
if sys_argv is None: sys_argv = []
if sys_argv is None:
sys_argv = []
if len(sys_argv) >= 2:
# Drag & Drop or CLI

View file

@ -14,6 +14,7 @@ PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90')
PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL)
PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL)
PAT_INSYDE_IFL = re.compile(br'\$_IFLASH_')
PAT_INSYDE_SFX = re.compile(br'\x0D\x0A;!@InstallEnd@!\x0D\x0A(7z\xBC\xAF\x27|\x6E\xF4\x79\x5F\x4E)')
PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL)
PAT_MICROSOFT_CAB = re.compile(br'MSCF\x00{4}')
PAT_MICROSOFT_MZ = re.compile(br'MZ')

View file

@ -15,7 +15,8 @@ uint64_t = ctypes.c_uint64
# 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 = []
if param_list is None:
param_list = []
structure = class_name(*param_list) # Unpack parameter list
struct_len = ctypes.sizeof(structure)

View file

@ -56,7 +56,8 @@ def check_sys_os():
sys.exit(126)
# Fix Windows Unicode console redirection
if os_win: sys.stdout.reconfigure(encoding='utf-8')
if os_win:
sys.stdout.reconfigure(encoding='utf-8')
# Initialize common argparse arguments
def argparse_init():
@ -85,10 +86,12 @@ def script_init(title, arguments, padding=0):
printer(title, new_line=False)
# Show Utility Version on demand
if arguments.version: sys.exit(0)
if arguments.version:
sys.exit(0)
# Set console/terminal window title (Windows only)
if get_os_ver()[1]: ctypes.windll.kernel32.SetConsoleTitleW(title)
if get_os_ver()[1]:
ctypes.windll.kernel32.SetConsoleTitleW(title)
# Process input files and generate output path
input_files,output_path = process_input_files(arguments, sys.argv)

View file

@ -1,2 +1,2 @@
lznt1==0.2
pefile==2021.9.3
pefile==2022.5.30