Compare commits

...

52 commits

Author SHA1 Message Date
Plato Mavropoulos
03ae0cf070 Fixed potential crash at files without extension(s) 2022-10-31 17:04:50 +02:00
Plato Mavropoulos
65467da21c Auto-resolve extract directory name conflicts
If the output (extracted) folder for a given input file already exists, a new name is generated automatically (i.e. _2nd, _3rd, _4th etc)
2022-10-23 22:34:35 +03:00
Plato Mavropoulos
71cbfdaf8b Replace contents of no longer relevant comment 2022-10-16 18:35:29 +03:00
Plato Mavropoulos
5d5dc5828b Updated Dell PFS Update Extractor v6.0_a16
Improved handling of sub-PFS PFAT Entries with offset conflicts and/or data gaps
2022-10-16 18:21:23 +03:00
Plato Mavropoulos
c1f4ab9121 Updated Dell PFS/PKG Update Extractor v6.0_a15
Padding is now added between sub-PFS PFAT Entry gaps

Thanks for the help @NikolajSchlej
2022-10-07 01:32:30 +03:00
platomav
6de50c422f Created common template for executing all utilities
Unified extracted output directory naming logic

Multiple code fixes, refactors and improvements
2022-09-12 23:09:12 +03:00
platomav
5f364f4759 Updated AMI UCP Update Extractor v2.0_a19 2022-09-02 00:37:10 +03:00
platomav
0c6c35b354 Updated Dell PFS/PKG Update Extractor v6.0_a12
Added support for Dell ThinOS PKG with multiple PFS
2022-08-31 01:11:25 +03:00
platomav
0e170334c6 Added Apple EFI Package Extractor v2.0_a4
Added Apple EFI PBZX Extractor v1.0_a4

Updated Apple EFI Image Identifier v2.0_a4

Updated Apple EFI IM4P Splitter v3.0_a4

Updated Insyde iFlash/iFdPacker Extractor v2.0_a10

Improved 7-Zip parameter control
2022-08-28 20:02:55 +03:00
platomav
389c30bb65 Added Apple EFI IM4P Splitter v3.0_a2 2022-08-17 01:26:01 +03:00
platomav
c144ad804c Added Apple EFI Image Identifier v2.0_a3
Fixed argparse lock of input files
2022-08-15 18:29:58 +03:00
platomav
48562b0f68 Added Fujitsu SFX BIOS Extractor v3.0_a2
Fixed deletion of folders with read-only files

Fixed missing README > Requirement for VAIO Packaging Manager Extractor
2022-07-14 01:32:25 +03:00
platomav
df47293d01 Insyde iFlash/iFdPacker Extractor v2.0_a9
Support for hardcoded Insyde 7-Zip SFX password

Fixed 7-Zip hang on password protected files
2022-07-07 01:31:29 +03:00
platomav
69784889cb Fixed README > Insyde iFlash/iFdPacker Extractor
Increased Python version to 3.10 and PEFile version to 2022.5.30 (performance)
2022-07-06 18:10:11 +03:00
platomav
0317009e09 Fixed README index 2022-07-06 18:01:56 +03:00
platomav
4749414f81 Insyde iFlash/iFdPacker Extractor v2.0_a8
Improved PFAT, UCP, PFS, TDK format check methods

Cleanup/improvements to all utilities
2022-07-06 17:54:17 +03:00
platomav
cd2704f743 Added Insyde iFlash Update Extractor v2.0_a2
Added Toshiba BIOS COM Extractor v2.0_a2
2022-06-30 01:20:21 +03:00
platomav
be90f364d2 Added Fujitsu UPC BIOS Extractor v2.0_a2 2022-06-29 00:44:42 +03:00
platomav
82cd4336bd Added Award BIOS Module Extractor v2.0_a3
Improved 7-Zip exit code handling
2022-06-26 19:09:01 +03:00
platomav
fc73921967 Added Panasonic BIOS Package Extractor v2.0_a7
New processes to better handle PE files

Various common package fixes and improvements
2022-06-21 14:23:08 +03:00
platomav
f5905ec662 Fix main pattern detections, when found at offset 0x0
AMI UCP Update Extractor v2.0_a15

Phoenix TDK Packer Extractor v2.0_a7

Portwell EFI Update Extractor v2.0_a9

Fixes issue #13, thanks @PCRider for the report!
2022-06-16 01:13:41 +03:00
platomav
fddd33aafd Added VAIO Packaging Manager Extractor v3.0_a4
Sort README utilities based on name
2022-06-02 02:14:39 +03:00
platomav
7111757764 Improved 7-Zip decompressor
Removed --static argument

Small fixes at variable names and f-strings
2022-06-01 02:22:59 +03:00
platomav
aea54aeaad Phoenix TDK Packer Extractor v2.0_a6
Dramatically increase TDK Packer base offset detection speed

Applied regex pattern improvements
2022-05-24 14:36:46 +03:00
platomav
8b561640db Phoenix TDK Packer Extractor v2.0_a5
Added detection of TDK Packer executable base offset

Improve TDK unpacking at weird images
2022-05-23 21:04:05 +03:00
platomav
7bb0c5f9a9 Added Phoenix TDK Packer Extractor v2.0_a4
f-strings all the things!
2022-05-22 00:24:20 +03:00
platomav
b4a93513f7 Added pip requirements file 2022-05-15 19:56:33 +03:00
platomav
8d262318dd Portwell EFI BIOS Extractor v2.0_a4
Replaced any assertions
2022-05-15 19:40:54 +03:00
platomav
1480d663be Fix README MD v2 2022-05-09 01:02:52 +03:00
platomav
dd250a8595 Fix README MD 2022-05-09 00:56:14 +03:00
platomav
2fb8ff913b Improved README 2022-05-09 00:53:36 +03:00
platomav
ec73b9e950 Added --static parameter to README 2022-05-07 00:27:02 +03:00
platomav
7e96a62f42 Added --static optional parameter
Allows usage of static-built external dependencies
2022-05-06 14:58:00 +03:00
platomav
cf88fc7a5d Added relevant exit codes at utilities
Fixed missing output path value crash

Increased minimum Python version to 3.8
2022-05-06 13:54:48 +03:00
platomav
9b29c37c65 Fix handling of quote-encased user input paths 2022-05-01 01:33:43 +03:00
platomav
982e3f3fc9 Format detectors now accept input bytes or path 2022-04-21 13:59:40 +03:00
platomav
a2eca0aac6 Revamped path-related operations
Fixed dependencies detecton

Fixed frozen state support
2022-04-17 20:48:43 +03:00
platomav
44546a67c5 Fixed path symlink resolutions 2022-04-16 23:39:56 +03:00
platomav
40686d5edf Improved AMI UCP > NAL unpacking
Fix potential illegal path traversals
2022-04-15 18:17:58 +03:00
platomav
672b4b2321 Fixes at title/version display 2022-04-14 16:07:34 +03:00
platomav
d02de2ac57 Add quick format check functions for PFAT, UCP, PFS 2022-04-14 14:06:23 +03:00
platomav
d6e8d31391 Dell PFS Update Extractor v6.0_a2
Added --version parameter

Structure fixes and improvements
2022-04-13 14:04:38 +03:00
platomav
96e87455de Small AMI UCP pattern improvement 2022-04-09 22:27:47 +03:00
platomav
2a98460d71 AMI UCP BIOS Extractor v2.0_a3
Added support for HP-modded AMI UCP (HP Flash Utility v4)
2022-04-09 22:22:25 +03:00
platomav
f2be701423 Added Dell PFS Update Extractor v6.0_a1
Adjusted dependencies
2022-04-07 01:13:07 +03:00
platomav
46172a218b add empty external directory 2022-04-01 18:00:39 +03:00
platomav
7c00479a9e cleanup old files 2022-04-01 17:48:20 +03:00
platomav
132457afda Initial refactor commit
Added AMI UCP BIOS Extractor v2.0_a1

Added AMI BIOS Guard Extractor v4.0_a1
2022-04-01 17:43:22 +03:00
platomav
2029ffc8b7 Apple EFI Package Grabber v2.0
Parses user-provided (DB) list of Apple Software Update CatalogURL .sucatalog links and saves all newer (since last run) EFI firmware package links into a text file. It removes any xml formatting, ignores false positives, removes duplicate links and sorts them in alphabetical order for easy comparison afterwards.
2022-03-28 00:58:07 +03:00
platomav
0f05e9b0be Insyde iFlash Image Extractor v1.0
Parses Insyde iFlash images and extracts their raw components (e.g. SPI/BIOS/UEFI, EC, ME, Flasher, Configuration etc)
2022-03-14 00:57:18 +02:00
platomav
e56aa66895 Dell PFS Update Extractor v5.1
Fixed BIOS Guard (PFAT) PFS Entry Signature parsing bug
Minor improvement in BIOS Guard (PFAT) PFS Entry detection
2022-01-05 16:44:58 +02:00
platomav
ec14803065 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
2021-12-27 00:46:37 +02:00
47 changed files with 4841 additions and 3751 deletions

2
.gitattributes vendored
View file

@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# Skip all external files
external/*
# Keep external > requirements file
!external/requirements.txt

View file

@ -1,321 +0,0 @@
#!/usr/bin/env python3
#coding=utf-8
"""
AMI PFAT Extract
AMI BIOS Guard Extractor
Copyright (C) 2018-2021 Plato Mavropoulos
"""
print('AMI BIOS Guard Extractor v3.2')
import sys
# Detect Python version
sys_ver = sys.version_info
if sys_ver < (3,7) :
sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1]))
(raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602
sys.exit(1)
import os
import re
import ctypes
import shutil
import traceback
# 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: ABGE crashed, please report the following:\n')
traceback.print_exception(exc_type, exc_value, tb)
input('\nPress enter to exit')
sys.exit(1)
# Pause after any unexpected python exception
sys.excepthook = show_exception_and_exit
sys.dont_write_bytecode = True
# https://github.com/allowitsme/big-tool by Dmitry Frolov
try :
from big_script_tool import BigScript
is_bgst = True
except :
is_bgst = False
# 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 PFAT_Header(ctypes.LittleEndianStructure) :
_pack_ = 1
_fields_ = [
('Size', uint32_t), # 0x00
('Checksum', uint32_t), # 0x04 Unknown 16-bits
('Tag', char*8), # 0x04 _AMIPFAT
('Flags', uint8_t), # 0x10
# 0x11
]
def pfat_print(self) :
print('\n PFAT Main Header:\n')
print(' Size : 0x%X' % self.Size)
print(' Checksum : 0x%0.4X' % self.Checksum)
print(' Tag : %s' % self.Tag.decode('utf-8'))
print(' Flags : 0x%0.2X' % self.Flags)
class PFAT_Block_Header(ctypes.LittleEndianStructure) :
_pack_ = 1
_fields_ = [
('PFATVerMajor', uint16_t), # 0x00
('PFATVerMinor', uint16_t), # 0x02
('PlatformID', uint8_t*16), # 0x04
('Attributes', uint32_t), # 0x14
('ScriptVerMajor', uint16_t), # 0x16
('ScriptVerMinor', uint16_t), # 0x18
('ScriptSize', uint32_t), # 0x1C
('DataSize', uint32_t), # 0x20
('BIOSSVN', uint32_t), # 0x24
('ECSVN', uint32_t), # 0x28
('VendorInfo', uint32_t), # 0x2C
# 0x30
]
def __init__(self, count, *args, **kwargs):
super().__init__(*args, **kwargs)
self.count = count
def get_flags(self) :
attr = PFAT_Block_Header_GetAttributes()
attr.asbytes = self.Attributes
return attr.b.SFAM, attr.b.ProtectEC, attr.b.GFXMitDis, attr.b.FTU, attr.b.Reserved
def pfat_print(self) :
no_yes = ['No','Yes']
f1,f2,f3,f4,f5 = self.get_flags()
PlatformID = bytes(self.PlatformID).strip(b'\x00')
if PlatformID.isalpha() : # STRING
PlatformID = PlatformID.decode('utf-8', 'ignore')
else : # GUID
PlatformID = '%0.*X' % (0x10 * 2, int.from_bytes(self.PlatformID, 'big'))
PlatformID = '{%s-%s-%s-%s-%s}' % (PlatformID[:8], PlatformID[8:12], PlatformID[12:16], PlatformID[16:20], PlatformID[20:])
print('\n PFAT Block %s Header:\n' % self.count)
print(' PFAT Version : %d.%d' % (self.PFATVerMajor, self.PFATVerMinor))
print(' Platform ID : %s' % PlatformID)
print(' Signed Flash Address Map : %s' % no_yes[f1])
print(' Protected EC OpCodes : %s' % no_yes[f2])
print(' Graphics Security Disable : %s' % no_yes[f3])
print(' Fault Tolerant Update : %s' % no_yes[f4])
print(' Attributes Reserved : 0x%X' % f5)
print(' Script Version : %d.%d' % (self.ScriptVerMajor, self.ScriptVerMinor))
print(' Script Size : 0x%X' % self.ScriptSize)
print(' Data Size : 0x%X' % self.DataSize)
print(' BIOS SVN : 0x%X' % self.BIOSSVN)
print(' EC SVN : 0x%X' % self.ECSVN)
print(' Vendor Info : 0x%X' % self.VendorInfo)
class PFAT_Block_Header_Attributes(ctypes.LittleEndianStructure):
_fields_ = [
('SFAM', uint32_t, 1), # Signed Flash Address Map
('ProtectEC', uint32_t, 1), # Protected EC OpCodes
('GFXMitDis', uint32_t, 1), # GFX Security Disable
('FTU', uint32_t, 1), # Fault Tolerant Update
('Reserved', uint32_t, 28)
]
class PFAT_Block_Header_GetAttributes(ctypes.Union):
_fields_ = [
('b', PFAT_Block_Header_Attributes),
('asbytes', uint32_t)
]
class PFAT_Block_RSA(ctypes.LittleEndianStructure) :
_pack_ = 1
_fields_ = [
('Unknown0', uint32_t), # 0x00
('Unknown1', uint32_t), # 0x04
('PublicKey', uint32_t*64), # 0x08
('Exponent', uint32_t), # 0x108
('Signature', uint32_t*64), # 0x10C
# 0x20C
]
def __init__(self, count, *args, **kwargs):
super().__init__(*args, **kwargs)
self.count = count
def pfat_print(self) :
PublicKey = '%0.*X' % (0x100 * 2, int.from_bytes(self.PublicKey, 'little'))
Signature = '%0.*X' % (0x100 * 2, int.from_bytes(self.Signature, 'little'))
print('\n PFAT Block %s Signature:\n' % self.count)
print(' Unknown 0 : 0x%X' % self.Unknown0)
print(' Unknown 1 : 0x%X' % self.Unknown1)
print(' Public Key : %s [...]' % PublicKey[:8])
print(' Exponent : 0x%X' % self.Exponent)
print(' Signature : %s [...]' % Signature[:8])
# 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) :
input('\n Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name.__name__))
sys.exit(1)
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)
return structure
if len(sys.argv) >= 2 :
# Drag & Drop or CLI
ami_pfat = sys.argv[1:]
else :
# Folder path
ami_pfat = []
in_path = input('\nEnter the full folder path: ')
print('\nWorking...')
for root, _, files in os.walk(in_path):
for name in files :
ami_pfat.append(os.path.join(root, name))
pfat_index = 1
input_name = ''
input_extension = ''
output_path = ''
block_hdr_size = ctypes.sizeof(PFAT_Block_Header)
block_rsa_size = ctypes.sizeof(PFAT_Block_RSA)
pfat_pat = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL)
for input_file in ami_pfat :
file_data = b''
final_data = b''
block_name = ''
block_count = 0
file_index = 0
blocks = []
with open(input_file, 'rb') as in_file : buffer = in_file.read()
pfat_match = pfat_pat.search(buffer)
if pfat_index == 1 :
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))
if not pfat_match :
print('\n Error: This is not an AMI BIOS Guard (PFAT) image!')
continue
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
if not pfat_match : continue
buffer = buffer[pfat_match.start() - 0x8:]
pfat_hdr = get_struct(buffer, 0, PFAT_Header)
hdr_size = pfat_hdr.Size
hdr_data = buffer[0x11:hdr_size].decode('utf-8').splitlines()
pfat_hdr.pfat_print()
print(' Title : %s' % hdr_data[0])
file_path = os.path.join(output_path, '%s%s -- %d' % (input_name, input_extension, pfat_index))
for entry in hdr_data[1:] :
entry_data = entry.split(' ')
entry_data = [s for s in entry_data if s != '']
entry_flags = int(entry_data[0])
entry_param = entry_data[1]
entry_blocks = int(entry_data[2])
entry_name = entry_data[3][1:]
for i in range(entry_blocks) : blocks.append([entry_name, entry_param, entry_flags, i + 1, entry_blocks])
block_count += entry_blocks
block_start = hdr_size
for i in range(block_count) :
is_file_start = blocks[i][0] != block_name
if is_file_start : print('\n %s (Parameter: %s, Flags: 0x%X)' % (blocks[i][0], blocks[i][1], blocks[i][2]))
block_hdr = get_struct(buffer, block_start, PFAT_Block_Header, ['%d/%d' % (blocks[i][3], blocks[i][4])])
block_hdr.pfat_print()
block_script_size = block_hdr.ScriptSize
block_script_data = buffer[block_start + block_hdr_size:block_start + block_hdr_size + block_script_size]
block_data_start = block_start + block_hdr_size + block_script_size
block_data_end = block_data_start + block_hdr.DataSize
block_data = buffer[block_data_start:block_data_end]
block_rsa = get_struct(buffer, block_data_end, PFAT_Block_RSA, ['%d/%d' % (blocks[i][3], blocks[i][4])])
block_rsa.pfat_print()
print('\n PFAT Block %d/%d Script:\n' % (blocks[i][3], blocks[i][4]))
is_opcode_div = len(block_script_data) % 8 == 0
is_begin_end = block_script_data[:8] + block_script_data[-8:] == b'\x01' + b'\x00' * 7 + b'\xFF' + b'\x00' * 7
if is_opcode_div and is_begin_end and is_bgst :
block_script_decomp = BigScript(code_bytes=block_script_data)
block_script_lines = block_script_decomp.to_string().replace('\t',' ').split('\n')
for line in block_script_lines :
spacing = ' ' * 16 if line.endswith(('begin','end',':')) else ' ' * 24
operands = [op for op in line.split(' ') if op != '']
print(spacing + ('{:<12s}' + '{:<11s}' * (len(operands) - 1)).format(*operands))
elif not is_opcode_div :
print(' Error: Script not divisible by OpCode length!')
elif not is_begin_end :
print(' Error: Script lacks Begin and/or End OpCodes!')
elif not is_bgst :
print(' Error: BIOS Guard Script Tool dependency missing!')
file_data += block_data
final_data += block_data
if i and is_file_start and file_data :
file_index += 1
with open('%s_%0.2d -- %s' % (file_path, file_index, block_name), 'wb') as o : o.write(file_data)
file_data = b''
block_name = blocks[i][0]
block_start = block_data_end + block_rsa_size
with open('%s_%0.2d -- %s' % (file_path, file_index + 1, block_name), 'wb') as o : o.write(file_data) # Last File
eof_data = buffer[block_start:] # Store any data after the end of PFAT
with open('%s_00 -- AMI_PFAT_%d_DATA_ALL.bin' % (file_path, pfat_index), 'wb') as final : final.write(final_data + eof_data)
if eof_data[:-0x100] != b'\xFF' * (len(eof_data) - 0x100) :
eof_path = '%s_%0.2d -- AMI_PFAT_%d_DATA_END.bin' % (file_path, file_index + 2, pfat_index)
with open(eof_path, 'wb') as final : final.write(eof_data)
if pfat_pat.search(eof_data) :
pfat_index += 1
ami_pfat_index = ami_pfat.index(input_file) + 1
ami_pfat.insert(ami_pfat_index, eof_path)
else :
pfat_index = 1
input('\nDone!')

View file

@ -1,610 +0,0 @@
#!/usr/bin/env python3
#coding=utf-8
"""
AMI UCP Extract
AMI UCP BIOS Extractor
Copyright (C) 2021 Plato Mavropoulos
"""
title = 'AMI UCP BIOS Extractor v1.2'
print('\n' + title) # Print script title
import sys
# Detect Python version
sys_ver = sys.version_info
if sys_ver < (3,7) :
sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1]))
(raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602
sys.exit(1)
import os
import re
import shutil
import ctypes
import argparse
import traceback
import subprocess
import contextlib
# 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
# 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
ucp_parser = argparse.ArgumentParser()
ucp_parser.add_argument('executables', type=argparse.FileType('r'), nargs='*')
ucp_parser.add_argument('-p', '--path', help='parse files within given folder', type=str)
ucp_parser.add_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true')
ucp_params = ucp_parser.parse_args()
verify_chk16 = bool(ucp_params.checksum) # Get Checksum16 Verification optional argument
# Get all files within path
def get_files(path) :
inputs = []
for root, _, files in os.walk(path):
for name in files :
inputs.append(os.path.join(root, name))
return inputs
if len(sys.argv) >= 2 :
if bool(ucp_params.path) :
ucp_exec = get_files(ucp_params.path) # CLI with --path
else :
ucp_exec = []
for executable in ucp_params.executables :
ucp_exec.append(executable.name) # Drag & Drop
else :
in_path = input('\nEnter the full folder path: ')
ucp_exec = get_files(in_path) # Direct Run
# 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 UAF_HDR(ctypes.LittleEndianStructure) :
_pack_ = 1
_fields_ = [
('ModuleTag', char*4), # 0x00
('ModuleSize', uint32_t), # 0x04
('Checksum', uint16_t), # 0x08
('Unknown0', uint8_t), # 0x0A
('Unknown1', uint8_t), # 0x0A
('Reserved', uint32_t), # 0x0C
# 0x10
]
def __init__(self, padd, *args, **kwargs) :
super().__init__(*args, **kwargs)
self.p = padd
def ucp_print(self, chk16) :
print('\n%s Utility Auxiliary File:\n' % self.p)
print('%s Module Tag : %s' % (self.p, self.ModuleTag.decode('utf-8')))
print('%s Module Size : 0x%X' % (self.p, self.ModuleSize))
print('%s Checksum : 0x%0.4X (%s)' % (self.p, self.Checksum, chk16))
print('%s Unknown 0 : 0x%0.2X' % (self.p, self.Unknown0))
print('%s Unknown 1 : 0x%0.2X' % (self.p, self.Unknown1))
print('%s Reserved : 0x%0.8X' % (self.p, self.Reserved))
class UAF_MOD(ctypes.LittleEndianStructure) :
_pack_ = 1
_fields_ = [
('CompressSize', uint32_t), # 0x00
('OriginalSize', uint32_t), # 0x04
# 0x08
]
def __init__(self, padd, *args, **kwargs) :
super().__init__(*args, **kwargs)
self.p = padd
def ucp_print(self) :
print('%s Compress Size : 0x%X' % (self.p, self.CompressSize))
print('%s Original Size : 0x%X' % (self.p, self.OriginalSize))
class UII_HDR(ctypes.LittleEndianStructure) :
_pack_ = 1
_fields_ = [
('UIISize', uint16_t), # 0x00
('Checksum', uint16_t), # 0x02
('UtilityVersion', uint32_t), # 0x04 i.e. AFU (Unknown Encoding, Signed)
('InfoSize', uint16_t), # 0x08
('SupportBIOS', uint8_t), # 0x0A
('SupportOS', uint8_t), # 0x0B
('DataBusWidth', uint8_t), # 0x0C
('ProgramType', uint8_t), # 0x0D
('ProgramMode', uint8_t), # 0x0E
('SourceSafeRelease', uint8_t), # 0x0F
# 0x10
]
def __init__(self, padd, *args, **kwargs) :
super().__init__(*args, **kwargs)
self.p = padd
def ucp_print(self, chk16) :
sbios = {1: 'ALL', 2: 'AMIBIOS8', 3: 'UEFI', 4: 'AMIBIOS8/UEFI'}
sos = {1: 'DOS', 2: 'EFI', 3: 'Windows', 4: 'Linux', 5: 'FreeBSD', 6: 'MacOS', 128: 'Multi-Platform'}
dbwidth = {1: '16b', 2: '16/32b', 3: '32b', 4: '64b'}
ptype = {1: 'Executable', 2: 'Library', 3: 'Driver'}
pmode = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'}
SupportBIOS = sbios[self.SupportBIOS] if self.SupportBIOS in sbios else 'Unknown (%d)' % self.SupportBIOS
SupportOS = sos[self.SupportOS] if self.SupportOS in sos else 'Unknown (%d)' % self.SupportOS
DataBusWidth = dbwidth[self.DataBusWidth] if self.DataBusWidth in dbwidth else 'Unknown (%d)' % self.DataBusWidth
ProgramType = ptype[self.ProgramType] if self.ProgramType in ptype else 'Unknown (%d)' % self.ProgramType
ProgramMode = pmode[self.ProgramMode] if self.ProgramMode in pmode else 'Unknown (%d)' % self.ProgramMode
print('\n%s Utility Identification Information:\n' % self.p)
print('%s UII Size : 0x%X' % (self.p, self.UIISize))
print('%s Checksum : 0x%0.4X (%s)' % (self.p, self.Checksum, chk16))
print('%s Tool Version : 0x%0.8X (Unknown)' % (self.p, self.UtilityVersion))
print('%s Info Size : 0x%X' % (self.p, self.InfoSize))
print('%s Supported BIOS : %s' % (self.p, SupportBIOS))
print('%s Supported OS : %s' % (self.p, SupportOS))
print('%s Data Bus Width : %s' % (self.p, DataBusWidth))
print('%s Program Type : %s' % (self.p, ProgramType))
print('%s Program Mode : %s' % (self.p, ProgramMode))
print('%s SourceSafe Tag : %0.2d' % (self.p, self.SourceSafeRelease))
class DIS_HDR(ctypes.LittleEndianStructure) :
_pack_ = 1
_fields_ = [
('PasswordSize', uint16_t), # 0x00
('EntryCount', uint16_t), # 0x02
('Password', char*12), # 0x04
# 0x10
]
def __init__(self, padd, *args, **kwargs) :
super().__init__(*args, **kwargs)
self.p = padd
def ucp_print(self) :
print('\n%s Default Command Status Header:\n' % self.p)
print('%s Password Size : 0x%X' % (self.p, self.PasswordSize))
print('%s Entry Count : %d' % (self.p, self.EntryCount))
print('%s Password : %s' % (self.p, self.Password.decode('utf-8')))
class DIS_MOD(ctypes.LittleEndianStructure) :
_pack_ = 1
_fields_ = [
('EnabledDisabled', uint8_t), # 0x00
('ShownHidden', uint8_t), # 0x01
('Command', char*32), # 0x02
('Description', char*256), # 0x22
# 0x122
]
def __init__(self, padd, *args, **kwargs) :
super().__init__(*args, **kwargs)
self.p = padd
def ucp_print(self) :
enabled = {0: 'Disabled', 1: 'Enabled'}
shown = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'}
EnabledDisabled = enabled[self.EnabledDisabled] if self.EnabledDisabled in enabled else 'Unknown (%d)' % self.EnabledDisabled
ShownHidden = shown[self.ShownHidden] if self.ShownHidden in shown else 'Unknown (%d)' % self.ShownHidden
print('\n%s Default Command Status Entry:\n' % self.p)
print('%s State : %s' % (self.p, EnabledDisabled))
print('%s Display : %s' % (self.p, ShownHidden))
print('%s Command : %s' % (self.p, self.Command.decode('utf-8').strip()))
print('%s Description : %s' % (self.p, self.Description.decode('utf-8').strip()))
# 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('\n Press enter to exit')
sys.exit(1)
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)
return structure
# Get Checksum16 validity result
def checksum16(buffer, check) :
if not check : return 'Skipped'
chk16 = 0
for idx in range(0, len(buffer), 2) :
chk16 += int.from_bytes(buffer[idx:idx + 2], 'little')
chk16 &= 0xFFFF
return 'Good' if chk16 == 0 else 'Bad'
# Get all input file AMI UCP patterns
def get_matches(buffer) :
uaf_len_max = 0 # Length of largest detected @UAF
uaf_hdr_off = 0 # Offset of largest detected @UAF
for uaf in ami_ucp_pat.finditer(buffer) :
uaf_len_cur = int.from_bytes(buffer[uaf.start() + 0x4:uaf.start() + 0x8], 'little')
if uaf_len_cur > uaf_len_max :
uaf_len_max = uaf_len_cur
uaf_hdr_off = uaf.start()
return uaf_hdr_off, uaf_len_max
# Parse & Extract AMI UCP structures
def ucp_extract(buffer, out_dir, level, padd) :
nal_dict = {} # Initialize @NAL Dictionary per UCP
uaf_hdr = get_struct(buffer, 0, UAF_HDR, [padd]) # Parse @UAF Header Structure
uaf_chk = checksum16(buffer, verify_chk16) # Get @UAF Header Checksum16
# Print @UAF Header Info
uaf_hdr.ucp_print(uaf_chk)
print('%s Compress Size : 0x%X' % (padd, len(buffer)))
print('%s Original Size : 0x%X' % (padd, len(buffer)))
print('%s Module Name : %s' % (padd, tag_dict['UAF']))
if uaf_chk == 'Bad' :
input('\n%s Error: Invalid AMI UCP Module UAF Checksum!' % padd)
uaf_off = uaf_hdr_len # Parsed @UAF, next Modules
uaf_all = [] # Initialize list of all UAF Modules
is_pfat = False # Initialize PFAT BIOS detection
is_dual = False # Initialize AMI/Insyde detection
while buffer[uaf_off] == 0x40 : # ASCII of @ is 0x40
uaf_hdr = get_struct(buffer, uaf_off, UAF_HDR, [padd]) # Parse UAF Module Structure
uaf_tag = uaf_hdr.ModuleTag.decode('utf-8')[1:] # Get unique UAF Module Tag
if uaf_tag == 'PFC' : is_pfat = True # Detect if UAF Module has PFAT BIOS
if uaf_tag == 'AMI' : is_dual = True # Detect if UAF Module has dual AMI/Insyde BIOS
uaf_all.append([uaf_tag, uaf_off, uaf_hdr]) # Store UAF Module Info
uaf_off += uaf_hdr.ModuleSize # Adjust to next UAF Module offset
if uaf_off >= len(buffer) : break # Stop parsing at EOF
# Check if UAF Module NAL exists and place it first
# Parsing NAL first allows naming all UAF Modules
for i in range(len(uaf_all)) :
if uaf_all[i][0] == 'NAL' :
uaf_all.insert(1, uaf_all.pop(i)) # After UII for visual purposes
break # NAL found, skip the rest
# Parse all UAF Modules
for uaf in uaf_all :
uaf_tag = uaf[0] # Store UAF Module Tag
uaf_off = uaf[1] # Store UAF Module Offset
uaf_hdr = uaf[2] # Store UAF Module Struct
uaf_data_all = buffer[uaf_off:uaf_off + uaf_hdr.ModuleSize] # UAF Module Entire Data
uaf_data_mod = uaf_data_all[uaf_hdr_len:] # UAF Module EFI Data
uaf_data_raw = uaf_data_mod[uaf_mod_len:] # UAF Module Raw Data
uaf_chk = checksum16(uaf_data_all, verify_chk16) # Get UAF Module Checksum16
uaf_hdr.ucp_print(uaf_chk) # Print UAF Module Info
uaf_mod = get_struct(buffer, uaf_off + uaf_hdr_len, UAF_MOD, [padd]) # Parse UAF Module EFI Structure
uaf_mod.ucp_print() # Print UAF Module EFI Info
is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect UAF Module EFI Compression
rom_name = 'PFAT' if is_pfat else 'BIOS' # Set UAF Module BIOS/ROM name based on PFAT state
if uaf_tag in nal_dict : uaf_name = nal_dict[uaf_tag] # Always prefer NAL naming first
elif uaf_tag in tag_dict : uaf_name = tag_dict[uaf_tag] # Otherwise use built-in naming
elif uaf_tag == 'ROM' : uaf_name = '%s.bin' % rom_name # BIOS/PFAT Firmware
elif uaf_tag.startswith('R0') : uaf_name = '%s_0%s.bin' % (rom_name, uaf_tag[2:]) # BIOS/PFAT Firmware
elif uaf_tag.startswith('S0') : uaf_name = '%s_0%s.sig' % (rom_name, uaf_tag[2:]) # BIOS/PFAT Signature
elif uaf_tag.startswith('DR') : uaf_name = 'DROM_0%s.bin' % uaf_tag[2:] # Thunderbolt Retimer Firmware
elif uaf_tag.startswith('DS') : uaf_name = 'DROM_0%s.sig' % uaf_tag[2:] # Thunderbolt Retimer Signature
elif uaf_tag.startswith('EC') : uaf_name = 'EC_0%s.bin' % uaf_tag[2:] # Embedded Controller Firmware
elif uaf_tag.startswith('ME') : uaf_name = 'ME_0%s.bin' % uaf_tag[2:] # Management Engine Firmware
else : uaf_name = uaf_tag # Could not name the UAF Module, use Tag instead
if uaf_name != uaf_tag :
uaf_fext = '' # File extension included in name
print('%s Module Name : %s' % (padd, uaf_name))
elif uaf_tag in ['CMD','PFC','VER','MEC','NAL','CKV'] :
uaf_fext = '.txt' # Known Text files
print('%s Module Name : %s%s (Unknown)' % (padd, uaf_name, uaf_fext))
else :
uaf_fext = '.bin' # Unknown files, assume binary
print('%s Module Name : %s%s (Unknown)' % (padd, uaf_name, uaf_fext))
# Check if unknown UAF Module Tag is present in NAL but not in built-in dictionary
if uaf_tag in nal_dict and uaf_tag not in tag_dict and not uaf_tag.startswith(('ROM','R0','S0','DR','DS')) :
input('\n%s Note: Detected new AMI UCP Module %s (%s) in NAL!' % (padd, uaf_tag, nal_dict[uaf_tag]))
# Generate UAF Module File name, depending on whether decompression will be required
uaf_fname = os.path.join(out_dir, '%s%s' % (uaf_name, '.temp' if is_comp else uaf_fext))
if uaf_chk == 'Bad' :
input('\n%s Error: Invalid AMI UCP Module %s Checksum!' % (padd, uaf_tag))
# Parse Utility Identification Information UAF Module (UII)
if uaf_tag == 'UII' :
info_hdr = get_struct(uaf_data_raw, 0, UII_HDR, [padd]) # Parse UII Module Raw Structure
info_chk = checksum16(uaf_data_raw, verify_chk16) # Get UII Module Checksum16
info_hdr.ucp_print(info_chk) # Print UII Module Info
# Get UII Module Description text field
desc = uaf_data_raw[info_hdr.InfoSize:info_hdr.UIISize].strip(b'\x00').decode('utf-8')
print('%s Description : %s' % (padd, desc)) # Print UII Module Description
if info_chk == 'Bad' :
input('\n%s Error: Invalid AMI UCP Module %s > Info Checksum!' % (padd, uaf_tag))
# Store/Save UII Module Info in file
with open(uaf_fname[:-3] + 'txt', 'a') as uii :
with contextlib.redirect_stdout(uii) :
info_hdr.ucp_print(info_chk) # Store UII Module Info
print('%s Description : %s' % (padd, desc)) # Store UII Module Description
# Adjust UAF Module Raw Data for extraction
if is_comp :
# Some Compressed UAF Module EFI data lack necessary padding in the end
if uaf_mod.CompressSize > len(uaf_data_raw) :
comp_padd = b'\x00' * (uaf_mod.CompressSize - len(uaf_data_raw))
uaf_data_raw = uaf_data_mod[:uaf_mod_len] + uaf_data_raw + comp_padd # Add missing padding for decompression
else :
uaf_data_raw = uaf_data_mod[:uaf_mod_len] + uaf_data_raw # Add the EFI/Tiano Compression info before Raw Data
else :
uaf_data_raw = uaf_data_raw[:uaf_mod.OriginalSize] # No compression, extend to end of Original UAF Module size
# Store/Save UAF Module file
if uaf_tag != 'UII' : # Skip UII binary, already parsed
with open(uaf_fname, 'wb') as out : out.write(uaf_data_raw)
# UAF Module EFI/Tiano Decompression
if is_comp :
try :
dec_fname = uaf_fname[:-5] + uaf_fext # Decompressed UAF Module file path
subprocess.run(['TianoCompress', '-d', uaf_fname, '-o', dec_fname, '--uefi', '-q'], check = True, stdout = subprocess.DEVNULL)
with open(dec_fname, 'rb') as dec : uaf_data_raw = dec.read() # Read back the UAF Module decompressed Raw data
if len(uaf_data_raw) == 0 : raise Exception('DECOMP_OUT_EMPTY') # If decompressed file is empty, something went wrong
os.remove(uaf_fname) # Successful decompression, delete compressed UAF Module file
uaf_fname = dec_fname # Adjust UAF Module file path to the decompressed one
except :
print('\n%s Error: Could not extract AMI UCP Module %s via TianoCompress!' % (padd, uaf_tag))
input('%s Make sure that "TianoCompress" executable exists!' % padd)
# Process and Print known text only UAF Modules (after EFI/Tiano Decompression)
if uaf_tag in ['CMD','PFC','VER','MEC','CKV'] : # Always referenced in tag_desc
text_data = uaf_data_raw.decode('utf-8')
print('\n%s %s:\n\n%s %s' % (padd, tag_desc[uaf_tag], padd, text_data))
# Parse Default Command Status UAF Module (DIS)
if len(uaf_data_raw) and uaf_tag == 'DIS' :
dis_hdr = get_struct(uaf_data_raw, 0, DIS_HDR, [padd]) # Parse DIS Module Raw Header Structure
dis_hdr.ucp_print() # Print DIS Module Raw Header Info
# Store/Save DIS Module Header Info in file
with open(uaf_fname[:-3] + 'txt', 'a') as dis :
with contextlib.redirect_stdout(dis) :
dis_hdr.ucp_print() # Store DIS Module Header Info
dis_data = uaf_data_raw[uaf_hdr_len:] # DIS Module Entries Data
# Parse all DIS Module Entries
for e_idx in range(dis_hdr.EntryCount) :
dis_mod = get_struct(dis_data, e_idx * 0x122, DIS_MOD, [padd]) # Parse DIS Module Raw Entry Structure
dis_mod.ucp_print() # Print DIS Module Raw Entry Info
# Store/Save DIS Module Entry Info in file
with open(uaf_fname[:-3] + 'txt', 'a') as dis :
with contextlib.redirect_stdout(dis) :
dis_mod.ucp_print() # Store DIS Module Entry Info
os.remove(uaf_fname) # Delete DIS Module binary, info exported as text
# Parse Non-AMI List (?) UAF Module (NAL)
if len(uaf_data_raw) >= 5 and (uaf_tag,uaf_data_raw[0],uaf_data_raw[4]) == ('NAL',0x40,0x3A) :
nal_info = uaf_data_raw.decode('utf-8').strip().replace('\r','').split('\n')
print('\n%s UAF List:\n' % padd)
# Parse all NAL Module Entries
for info in nal_info :
print('%s %s : %s' % (padd, info[1:4], info[5:])) # Print NAL Module Tag-Path Info
nal_dict[info[1:4]] = os.path.basename(info[5:]) # Assign a file name (w/o path) to each Tag
# Parse Insyde BIOS UAF Module (INS)
if len(uaf_data_raw) >= 2 and (uaf_tag,is_dual,uaf_data_raw[:2]) == ('INS',True,b'\x4D\x5A') :
ins_dir = os.path.join(out_dir, '%s_extracted (SFX)' % uaf_tag) # Generate extraction directory
print('\n%s Insyde BIOS 7-Zip SFX Archive:\n\n%s 7-Zip will be used for extraction' % (padd, padd))
# INS Module extraction
try :
subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + ins_dir, uaf_fname], check = True, stdout = subprocess.DEVNULL)
if not os.path.isdir(ins_dir) : raise Exception('EXTR_DIR_MISSING') # If extraction folder is missing, something went wrong
os.remove(uaf_fname) # Successful extraction, delete archived INS Module file
except :
print('\n%s Error: Could not extract AMI UCP Module %s via 7-Zip!' % (padd, uaf_tag))
input('%s Make sure that "7z" executable exists!' % padd)
# Detect AMI BIOS Guard (PFAT) image and print extraction instructions/utility
if len(uaf_data_raw) >= 16 and (is_pfat,uaf_data_raw[0x8:0x10]) == (True,b'_AMIPFAT') :
print('\n%s AMI BIOS Guard (PFAT) Image:\n' % padd)
print('%s Use "AMI BIOS Guard Extractor" from https://github.com/platomav/BIOSUtilities' % padd)
# Detect Intel Management Engine (ME) image and print parsing instructions/utility
if len(uaf_data_raw) and uaf_name.startswith('ME_0') :
print('\n%s Intel Management Engine (ME) Firmware:\n' % padd)
print('%s Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer' % padd)
# Get best Nested AMI UCP Pattern match based on @UAF Size
uaf_hdr_off,uaf_len_max = get_matches(uaf_data_raw)
# Parse Nested AMI UCP Structure
if uaf_hdr_off :
level += 1 # Increase structure Level to control output padding
uaf_dir = os.path.join(out_dir, '%s_extracted (UCP)' % uaf_tag) # Generate extraction directory
os.mkdir(uaf_dir) # Create extraction directory
ucp_extract(uaf_data_raw[uaf_hdr_off:uaf_hdr_off + uaf_len_max], uaf_dir, level, ' ' * level) # Call recursively
os.remove(uaf_fname) # Delete raw nested AMI UCP Structure after successful recursion/extraction
# Utility Auxiliary File (@UAF) and Utility Identification Information (@UII)
ami_ucp_pat = re.compile(br'\x40\x55\x41\x46.{12}\x40\x55\x49\x49', re.DOTALL)
# Get common ctypes Structure Sizes
uaf_hdr_len = ctypes.sizeof(UAF_HDR)
uaf_mod_len = ctypes.sizeof(UAF_MOD)
# User friendly Tag Descriptions
tag_desc = {
'CMD' : 'AMI AFU Command',
'PFC' : 'AMI BGT Command',
'VER' : 'OEM Version',
'CKV' : 'Check Version',
'MEC' : 'ME FWUpdLcl Command',
}
# AMI UCP Tag-File Dictionary
tag_dict = {
'W32' : 'amifldrv32.sys',
'W64' : 'amifldrv64.sys',
'VXD' : 'amifldrv.vxd',
'DCT' : 'DevCon32.exe',
'DCX' : 'DevCon64.exe',
'CMD' : 'AFU_Command.txt',
'PFC' : 'BGT_Command.txt',
'VER' : 'OEM_Version.txt',
'CKV' : 'Check_Version.txt',
'OKM' : 'OK_Message.txt',
'CPM' : 'AC_Message.txt',
'DIS' : 'Command_Status.bin',
'UAF' : 'UCP_Main.bin',
'UII' : 'UCP_Info.txt',
'NAL' : 'UAF_List.txt',
'MEC' : 'FWUpdLcl.txt',
'MED' : 'FWUpdLcl_DOS.exe',
'MET' : 'FWUpdLcl_WIN.exe',
'AMI' : 'UCP_Nested.bin',
'INS' : 'Insyde_Nested.bin',
'RFI' : 'CryptRSA.efi',
'R3I' : 'CryptRSA32.efi',
'UFI' : 'HpBiosUpdate.efi',
'US9' : 'HpBiosUpdate.s09',
'US2' : 'HpBiosUpdate.s12',
'USG' : 'HpBiosUpdate.sig',
'3FI' : 'HpBiosUpdate32.efi',
'3S9' : 'HpBiosUpdate32.s09',
'3S2' : 'HpBiosUpdate32.s12',
'3SG' : 'HpBiosUpdate32.sig',
'MFI' : 'HpBiosMgmt.efi',
'MS9' : 'HpBiosMgmt.s09',
'MS2' : 'HpBiosMgmt.s12',
'US4' : 'HpBiosUpdate.s14',
'3S4' : 'HpBiosUpdate32.s14',
'MS4' : 'HpBiosMgmt.s14',
'M3I' : 'HpBiosMgmt32.efi',
'M39' : 'HpBiosMgmt32.s09',
'M32' : 'HpBiosMgmt32.s12',
'M34' : 'HpBiosMgmt32.s14',
'BME' : 'BiosMgmt.efi',
'BM9' : 'BiosMgmt.s09',
'B12' : 'BiosMgmt.s12',
'B14' : 'BiosMgmt.s14',
'B3E' : 'BiosMgmt32.efi',
'B39' : 'BiosMgmt32.s09',
'B32' : 'BiosMgmt32.s12',
'B34' : 'BiosMgmt32.s14',
'DFE' : 'HpDevFwUpdate.efi',
'DFS' : 'HpDevFwUpdate.s12',
'ENB' : 'ENBG64.exe',
}
# Process each input AMI UCP BIOS executable
for input_file in ucp_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()
# Get best AMI UCP Pattern match based on @UAF Size
uaf_hdr_off,uaf_len_max = get_matches(input_data)
# Check if AMI UCP Pattern was found on executable
if not uaf_hdr_off :
print('\n Error: This is not an AMI UCP BIOS executable!')
continue # Next input file
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
print('\n AMI Utility Configuration Program')
level = 0 # Set initial AMI UCP structure Level to control padding in nested ones
ucp_extract(input_data[uaf_hdr_off:uaf_hdr_off + uaf_len_max], output_path, level, '') # Call the AMI UCP Extractor function
print('\n Extracted AMI UCP BIOS executable!')
input('\nDone!')
sys.exit(0)

319
AMI_PFAT_Extract.py Normal file
View file

@ -0,0 +1,319 @@
#!/usr/bin/env python3
#coding=utf-8
"""
AMI PFAT Extract
AMI BIOS Guard Extractor
Copyright (C) 2018-2022 Plato Mavropoulos
"""
TITLE = 'AMI BIOS Guard Extractor v4.0_a12'
import os
import re
import sys
import ctypes
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.externals import get_bgs_tool
from common.num_ops import get_ordinal
from common.path_ops import make_dirs, safe_name, get_extract_path, extract_suffix
from common.patterns import PAT_AMI_PFAT
from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
class AmiBiosGuardHeader(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('Size', uint32_t), # 0x00 Header + Entries
('Checksum', uint32_t), # 0x04 ?
('Tag', char*8), # 0x04 _AMIPFAT
('Flags', uint8_t), # 0x10 ?
# 0x11
]
def struct_print(self, p):
printer(['Size :', f'0x{self.Size:X}'], p, False)
printer(['Checksum:', f'0x{self.Checksum:04X}'], p, False)
printer(['Tag :', self.Tag.decode('utf-8')], p, False)
printer(['Flags :', f'0x{self.Flags:02X}'], p, False)
class IntelBiosGuardHeader(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('BGVerMajor', uint16_t), # 0x00
('BGVerMinor', uint16_t), # 0x02
('PlatformID', uint8_t*16), # 0x04
('Attributes', uint32_t), # 0x14
('ScriptVerMajor', uint16_t), # 0x16
('ScriptVerMinor', uint16_t), # 0x18
('ScriptSize', uint32_t), # 0x1C
('DataSize', uint32_t), # 0x20
('BIOSSVN', uint32_t), # 0x24
('ECSVN', uint32_t), # 0x28
('VendorInfo', uint32_t), # 0x2C
# 0x30
]
def get_platform_id(self):
id_byte = bytes(self.PlatformID)
id_text = re.sub(r'[\n\t\r\x00 ]', '', id_byte.decode('utf-8','ignore'))
id_hexs = f'{int.from_bytes(id_byte, "big"):0{0x10 * 2}X}'
id_guid = f'{{{id_hexs[:8]}-{id_hexs[8:12]}-{id_hexs[12:16]}-{id_hexs[16:20]}-{id_hexs[20:]}}}'
return f'{id_text} {id_guid}'
def get_flags(self):
attr = IntelBiosGuardHeaderGetAttributes()
attr.asbytes = self.Attributes
return attr.b.SFAM, attr.b.ProtectEC, attr.b.GFXMitDis, attr.b.FTU, attr.b.Reserved
def struct_print(self, p):
no_yes = ['No','Yes']
f1,f2,f3,f4,f5 = self.get_flags()
printer(['BIOS Guard Version :', f'{self.BGVerMajor}.{self.BGVerMinor}'], p, False)
printer(['Platform Identity :', self.get_platform_id()], p, False)
printer(['Signed Flash Address Map :', no_yes[f1]], p, False)
printer(['Protected EC OpCodes :', no_yes[f2]], p, False)
printer(['Graphics Security Disable :', no_yes[f3]], p, False)
printer(['Fault Tolerant Update :', no_yes[f4]], p, False)
printer(['Attributes Reserved :', f'0x{f5:X}'], p, False)
printer(['Script Version :', f'{self.ScriptVerMajor}.{self.ScriptVerMinor}'], p, False)
printer(['Script Size :', f'0x{self.ScriptSize:X}'], p, False)
printer(['Data Size :', f'0x{self.DataSize:X}'], p, False)
printer(['BIOS Security Version Number:', f'0x{self.BIOSSVN:X}'], p, False)
printer(['EC Security Version Number :', f'0x{self.ECSVN:X}'], p, False)
printer(['Vendor Information :', f'0x{self.VendorInfo:X}'], p, False)
class IntelBiosGuardHeaderAttributes(ctypes.LittleEndianStructure):
_fields_ = [
('SFAM', uint32_t, 1), # Signed Flash Address Map
('ProtectEC', uint32_t, 1), # Protected EC OpCodes
('GFXMitDis', uint32_t, 1), # GFX Security Disable
('FTU', uint32_t, 1), # Fault Tolerant Update
('Reserved', uint32_t, 28) # Reserved/Unknown
]
class IntelBiosGuardHeaderGetAttributes(ctypes.Union):
_fields_ = [
('b', IntelBiosGuardHeaderAttributes),
('asbytes', uint32_t)
]
class IntelBiosGuardSignature2k(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('Unknown0', uint32_t), # 0x000
('Unknown1', uint32_t), # 0x004
('Modulus', uint32_t*64), # 0x008
('Exponent', uint32_t), # 0x108
('Signature', uint32_t*64), # 0x10C
# 0x20C
]
def struct_print(self, p):
Modulus = f'{int.from_bytes(self.Modulus, "little"):0{0x100 * 2}X}'
Signature = f'{int.from_bytes(self.Signature, "little"):0{0x100 * 2}X}'
printer(['Unknown 0:', f'0x{self.Unknown0:X}'], p, False)
printer(['Unknown 1:', f'0x{self.Unknown1:X}'], p, False)
printer(['Modulus :', f'{Modulus[:32]} [...]'], p, False)
printer(['Exponent :', f'0x{self.Exponent:X}'], p, False)
printer(['Signature:', f'{Signature[:32]} [...]'], p, False)
def is_ami_pfat(input_file):
input_buffer = file_to_bytes(input_file)
return bool(get_ami_pfat(input_buffer))
def get_ami_pfat(input_file):
input_buffer = file_to_bytes(input_file)
match = PAT_AMI_PFAT.search(input_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=0):
is_opcode_div = len(script_data) % 8 == 0
if not is_opcode_div:
printer('Error: Script is not divisible by OpCode length!', padding, False)
return 1
is_begin_end = script_data[:8] + script_data[-8:] == b'\x01' + b'\x00' * 7 + b'\xFF' + b'\x00' * 7
if not is_begin_end:
printer('Error: Script lacks Begin and/or End OpCodes!', padding, False)
return 2
BigScript = get_bgs_tool()
if not BigScript:
printer('Note: BIOS Guard Script Tool optional dependency is missing!', padding, False)
return 3
script = BigScript(code_bytes=script_data).to_string().replace('\t',' ').split('\n')
for opcode in script:
if opcode.endswith(('begin','end')): spacing = padding
elif opcode.endswith(':'): spacing = padding + 4
else: spacing = padding + 12
operands = [operand for operand in opcode.split(' ') if operand]
printer(('{:<12s}' + '{:<11s}' * (len(operands) - 1)).format(*operands), spacing, False)
return 0
def parse_pfat_hdr(buffer, padding=0):
block_all = []
pfat_hdr = get_struct(buffer, 0x0, AmiBiosGuardHeader)
hdr_size = pfat_hdr.Size
hdr_data = buffer[PFAT_AMI_HDR_LEN:hdr_size]
hdr_text = hdr_data.decode('utf-8').splitlines()
printer('AMI BIOS Guard Header:\n', padding)
pfat_hdr.struct_print(padding + 4)
hdr_title,*hdr_files = hdr_text
files_count = len(hdr_files)
hdr_tag,*hdr_indexes = hdr_title.split('II')
printer(hdr_tag + '\n', padding + 4)
bgt_indexes = [int(h, 16) for h in re.findall(r'.{1,4}', hdr_indexes[0])] if hdr_indexes else []
for index,entry in enumerate(hdr_files):
entry_parts = entry.split(';')
info = entry_parts[0].split()
name = entry_parts[1]
flags = int(info[0])
param = info[1]
count = int(info[2])
order = get_ordinal((bgt_indexes[index] if bgt_indexes else index) + 1)
desc = f'{name} (Index: {index + 1:02d}, Flash: {order}, Parameter: {param}, Flags: 0x{flags:X}, Blocks: {count})'
block_all += [(desc, name, order, param, flags, index, i, count) for i in range(count)]
_ = [printer(block[0], padding + 8, False) for block in block_all if block[6] == 0]
return block_all, hdr_size, files_count
def parse_pfat_file(input_file, extract_path, padding=0):
input_buffer = file_to_bytes(input_file)
pfat_buffer = get_ami_pfat(input_buffer)
file_path = ''
all_blocks_dict = {}
extract_name = os.path.basename(extract_path).rstrip(extract_suffix())
make_dirs(extract_path, delete=True)
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
if block_index == 0:
printer(file_desc, padding + 4)
file_path = os.path.join(extract_path, get_file_name(file_index + 1, file_name))
all_blocks_dict[file_index] = b''
block_status = f'{block_index + 1}/{block_count}'
bg_hdr = get_struct(pfat_buffer, block_off, IntelBiosGuardHeader)
printer(f'Intel BIOS Guard {block_status} Header:\n', padding + 8)
bg_hdr.struct_print(padding + 12)
bg_script_bgn = block_off + PFAT_BLK_HDR_LEN
bg_script_end = bg_script_bgn + bg_hdr.ScriptSize
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 = pfat_buffer[bg_data_bgn:bg_data_end]
block_off = bg_data_end # Assume next block starts at data end
is_sfam,_,_,_,_ = bg_hdr.get_flags() # SFAM, ProtectEC, GFXMitDis, FTU, Reserved
if is_sfam:
bg_sig_bgn = bg_data_end
bg_sig_end = bg_sig_bgn + PFAT_BLK_S2K_LEN
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)
printer(f'Intel BIOS Guard {block_status} Signature:\n', padding + 8)
bg_sig.struct_print(padding + 12)
block_off = bg_sig_end # Adjust next block to start at data + signature end
printer(f'Intel BIOS Guard {block_status} Script:\n', padding + 8)
_ = parse_bg_script(bg_script_bin, padding + 12)
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 = 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)
if is_ami_pfat(pfat_oob_data):
parse_pfat_file(pfat_oob_data, get_extract_path(pfat_oob_path), padding)
in_all_data = b''.join([block[1] for block in sorted(all_blocks_dict.items())])
in_all_name = get_file_name(0, f'{extract_name}_ALL.bin')
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)
return 0
PFAT_AMI_HDR_LEN = ctypes.sizeof(AmiBiosGuardHeader)
PFAT_BLK_HDR_LEN = ctypes.sizeof(IntelBiosGuardHeader)
PFAT_BLK_S2K_LEN = ctypes.sizeof(IntelBiosGuardSignature2k)
if __name__ == '__main__':
BIOSUtility(TITLE, is_ami_pfat, parse_pfat_file).run_utility()

515
AMI_UCP_Extract.py Normal file
View file

@ -0,0 +1,515 @@
#!/usr/bin/env python3
#coding=utf-8
"""
AMI UCP Extract
AMI UCP Update Extractor
Copyright (C) 2021-2022 Plato Mavropoulos
"""
TITLE = 'AMI UCP Update Extractor v2.0_a20'
import os
import re
import sys
import struct
import ctypes
import contextlib
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.checksums import get_chk_16
from common.comp_efi import efi_decompress, is_efi_compressed
from common.path_ops import agnostic_path, make_dirs, safe_name, safe_path, get_extract_path
from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG
from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes, to_string
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
_fields_ = [
('ModuleTag', char*4), # 0x00
('ModuleSize', uint32_t), # 0x04
('Checksum', uint16_t), # 0x08
('Unknown0', uint8_t), # 0x0A
('Unknown1', uint8_t), # 0x0A
('Reserved', uint8_t*4), # 0x0C
# 0x10
]
def _get_reserved(self):
res_bytes = bytes(self.Reserved)
res_hex = f'0x{int.from_bytes(res_bytes, "big"):0{0x4 * 2}X}'
res_str = re.sub(r'[\n\t\r\x00 ]', '', res_bytes.decode('utf-8','ignore'))
res_txt = f' ({res_str})' if len(res_str) else ''
return f'{res_hex}{res_txt}'
def struct_print(self, p):
printer(['Tag :', self.ModuleTag.decode('utf-8')], p, False)
printer(['Size :', f'0x{self.ModuleSize:X}'], p, False)
printer(['Checksum :', f'0x{self.Checksum:04X}'], p, False)
printer(['Unknown 0 :', f'0x{self.Unknown0:02X}'], p, False)
printer(['Unknown 1 :', f'0x{self.Unknown1:02X}'], p, False)
printer(['Reserved :', self._get_reserved()], p, False)
class UafModule(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('CompressSize', uint32_t), # 0x00
('OriginalSize', uint32_t), # 0x04
# 0x08
]
def struct_print(self, p, filename, description):
printer(['Compress Size:', f'0x{self.CompressSize:X}'], p, False)
printer(['Original Size:', f'0x{self.OriginalSize:X}'], p, False)
printer(['Filename :', filename], p, False)
printer(['Description :', description], p, False)
class UiiHeader(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('UIISize', uint16_t), # 0x00
('Checksum', uint16_t), # 0x02
('UtilityVersion', uint32_t), # 0x04 AFU|BGT (Unknown, Signed)
('InfoSize', uint16_t), # 0x08
('SupportBIOS', uint8_t), # 0x0A
('SupportOS', uint8_t), # 0x0B
('DataBusWidth', uint8_t), # 0x0C
('ProgramType', uint8_t), # 0x0D
('ProgramMode', uint8_t), # 0x0E
('SourceSafeRel', uint8_t), # 0x0F
# 0x10
]
SBI = {1: 'ALL', 2: 'AMIBIOS8', 3: 'UEFI', 4: 'AMIBIOS8/UEFI'}
SOS = {1: 'DOS', 2: 'EFI', 3: 'Windows', 4: 'Linux', 5: 'FreeBSD', 6: 'MacOS', 128: 'Multi-Platform'}
DBW = {1: '16b', 2: '16/32b', 3: '32b', 4: '64b'}
PTP = {1: 'Executable', 2: 'Library', 3: 'Driver'}
PMD = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'}
def struct_print(self, p, description):
SupportBIOS = self.SBI.get(self.SupportBIOS, f'Unknown ({self.SupportBIOS})')
SupportOS = self.SOS.get(self.SupportOS, f'Unknown ({self.SupportOS})')
DataBusWidth = self.DBW.get(self.DataBusWidth, f'Unknown ({self.DataBusWidth})')
ProgramType = self.PTP.get(self.ProgramType, f'Unknown ({self.ProgramType})')
ProgramMode = self.PMD.get(self.ProgramMode, f'Unknown ({self.ProgramMode})')
printer(['UII Size :', f'0x{self.UIISize:X}'], p, False)
printer(['Checksum :', f'0x{self.Checksum:04X}'], p, False)
printer(['Tool Version :', f'0x{self.UtilityVersion:08X}'], p, False)
printer(['Info Size :', f'0x{self.InfoSize:X}'], p, False)
printer(['Supported BIOS:', SupportBIOS], p, False)
printer(['Supported OS :', SupportOS], p, False)
printer(['Data Bus Width:', DataBusWidth], p, False)
printer(['Program Type :', ProgramType], p, False)
printer(['Program Mode :', ProgramMode], p, False)
printer(['SourceSafe Tag:', f'{self.SourceSafeRel:02d}'], p, False)
printer(['Description :', description], p, False)
class DisHeader(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('PasswordSize', uint16_t), # 0x00
('EntryCount', uint16_t), # 0x02
('Password', char*12), # 0x04
# 0x10
]
def struct_print(self, p):
printer(['Password Size:', f'0x{self.PasswordSize:X}'], p, False)
printer(['Entry Count :', self.EntryCount], p, False)
printer(['Password :', self.Password.decode('utf-8')], p, False)
class DisModule(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('EnabledDisabled', uint8_t), # 0x00
('ShownHidden', uint8_t), # 0x01
('Command', char*32), # 0x02
('Description', char*256), # 0x22
# 0x122
]
ENDIS = {0: 'Disabled', 1: 'Enabled'}
SHOWN = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'}
def struct_print(self, p):
EnabledDisabled = self.ENDIS.get(self.EnabledDisabled, f'Unknown ({self.EnabledDisabled})')
ShownHidden = self.SHOWN.get(self.ShownHidden, f'Unknown ({self.ShownHidden})')
printer(['State :', EnabledDisabled], p, False)
printer(['Display :', ShownHidden], p, False)
printer(['Command :', self.Command.decode('utf-8').strip()], p, False)
printer(['Description:', self.Description.decode('utf-8').strip()], p, False)
# Validate UCP Module Checksum-16
def chk16_validate(data, tag, padd=0):
if get_chk_16(data) != 0:
printer(f'Error: Invalid UCP Module {tag} Checksum!', padd, pause=True)
else:
printer(f'Checksum of UCP Module {tag} is valid!', padd)
# Check if input is AMI UCP image
def is_ami_ucp(in_file):
buffer = file_to_bytes(in_file)
return bool(get_ami_ucp(buffer)[0] is not None)
# Get all input file AMI UCP patterns
def get_ami_ucp(in_file):
buffer = file_to_bytes(in_file)
uaf_len_max = 0x0 # Length 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
for uaf in PAT_AMI_UCP.finditer(buffer):
uaf_len_cur = int.from_bytes(buffer[uaf.start() + 0x4:uaf.start() + 0x8], 'little')
if uaf_len_cur > uaf_len_max:
uaf_len_max = uaf_len_cur
uaf_hdr_off = uaf.start()
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_buf_bin, uaf_buf_tag
# Get list of @UAF|@HPU Modules
def get_uaf_mod(buffer, uaf_off=0x0):
uaf_all = [] # Initialize list of all @UAF|@HPU Modules
while buffer[uaf_off] == 0x40: # ASCII of @ is 0x40
uaf_hdr = get_struct(buffer, uaf_off, UafHeader) # Parse @UAF|@HPU Module Structure
uaf_tag = uaf_hdr.ModuleTag.decode('utf-8') # Get unique @UAF|@HPU Module Tag
uaf_all.append([uaf_tag, uaf_off, uaf_hdr]) # Store @UAF|@HPU Module Info
uaf_off += uaf_hdr.ModuleSize # Adjust to next @UAF|@HPU Module offset
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(in_file, extract_path, padding=0, checksum=False):
input_buffer = file_to_bytes(in_file)
nal_dict = {} # Initialize @NAL Dictionary per UCP
printer('Utility Configuration Program', padding)
make_dirs(extract_path, delete=True)
# 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(ucp_buffer), len(ucp_buffer)) # Generate UafModule Structure
uaf_mod = get_struct(fake, 0x0, UafModule) # Parse @UAF|@HPU Module EFI Structure
uaf_name = UAF_TAG_DICT[ucp_tag][0] # Get @UAF|@HPU Module Filename
uaf_desc = UAF_TAG_DICT[ucp_tag][1] # Get @UAF|@HPU Module Description
uaf_mod.struct_print(padding + 8, uaf_name, uaf_desc) # Print @UAF|@HPU Module EFI Info
if checksum:
chk16_validate(ucp_buffer, ucp_tag, padding + 8)
uaf_all = get_uaf_mod(ucp_buffer, UAF_HDR_LEN)
for mod_info in uaf_all:
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, checksum=False, nal_dict=None):
if nal_dict is None:
nal_dict = {}
uaf_tag,uaf_off,uaf_hdr = mod_info
uaf_data_all = buffer[uaf_off:uaf_off + uaf_hdr.ModuleSize] # @UAF|@HPU Module Entire Data
uaf_data_mod = uaf_data_all[UAF_HDR_LEN:] # @UAF|@HPU Module EFI Data
uaf_data_raw = uaf_data_mod[UAF_MOD_LEN:] # @UAF|@HPU Module Raw Data
printer(f'Utility Auxiliary File > {uaf_tag}:\n', padding)
uaf_hdr.struct_print(padding + 4) # Print @UAF|@HPU Module Info
uaf_mod = get_struct(buffer, uaf_off + UAF_HDR_LEN, UafModule) # Parse UAF Module EFI Structure
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
uaf_fext = '' if uaf_name != uaf_tag else '.bin'
uaf_fdesc = UAF_TAG_DICT[uaf_tag][1] if uaf_tag in UAF_TAG_DICT else uaf_name
uaf_mod.struct_print(padding + 4, uaf_name + uaf_fext, uaf_fdesc) # Print @UAF|@HPU Module EFI Info
# Check if unknown @UAF|@HPU Module Tag is present in @NAL but not in built-in dictionary
if uaf_tag in nal_dict and uaf_tag not in UAF_TAG_DICT and not uaf_tag.startswith(('@ROM','@R0','@S0','@DR','@DS')):
printer(f'Note: Detected new AMI UCP Module {uaf_tag} ({nal_dict[uaf_tag][1]}) in @NAL!', padding + 4, pause=True)
# Generate @UAF|@HPU Module File name, depending on whether decompression will be required
uaf_sname = safe_name(uaf_name + ('.temp' if is_comp else uaf_fext))
if uaf_tag in nal_dict:
uaf_npath = safe_path(extract_path, nal_dict[uaf_tag][0])
make_dirs(uaf_npath, exist_ok=True)
uaf_fname = safe_path(uaf_npath, uaf_sname)
else:
uaf_fname = safe_path(extract_path, uaf_sname)
if checksum:
chk16_validate(uaf_data_all, uaf_tag, padding + 4)
# Parse Utility Identification Information @UAF|@HPU Module (@UII)
if uaf_tag == '@UII':
info_hdr = get_struct(uaf_data_raw, 0, UiiHeader) # Parse @UII Module Raw Structure
info_data = uaf_data_raw[max(UII_HDR_LEN,info_hdr.InfoSize):info_hdr.UIISize] # @UII Module Info Data
# Get @UII Module Info/Description text field
info_desc = info_data.decode('utf-8','ignore').strip('\x00 ')
printer('Utility Identification Information:\n', padding + 4)
info_hdr.struct_print(padding + 8, info_desc) # Print @UII Module Info
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:
with contextlib.redirect_stdout(uii_out):
info_hdr.struct_print(0, info_desc) # Store @UII Module Info
# Adjust @UAF|@HPU Module Raw Data for extraction
if is_comp:
# Some Compressed @UAF|@HPU Module EFI data lack necessary EOF padding
if uaf_mod.CompressSize > len(uaf_data_raw):
comp_padd = b'\x00' * (uaf_mod.CompressSize - len(uaf_data_raw))
uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw + comp_padd # Add missing padding for decompression
else:
uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw # Add the EFI/Tiano Compression info before Raw Data
else:
uaf_data_raw = uaf_data_raw[:uaf_mod.OriginalSize] # No compression, extend to end of Original @UAF|@HPU Module size
# 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)
# @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
os.remove(uaf_fname) # Successful decompression, delete compressed @UAF|@HPU Module file
uaf_fname = dec_fname # Adjust @UAF|@HPU Module file path to the decompressed one
# Process and Print known text only @UAF|@HPU Modules (after EFI/Tiano Decompression)
if uaf_tag in UAF_TAG_DICT and UAF_TAG_DICT[uaf_tag][2] == 'Text':
printer(f'{UAF_TAG_DICT[uaf_tag][1]}:', padding + 4)
printer(uaf_data_raw.decode('utf-8','ignore'), padding + 8)
# Parse Default Command Status @UAF|@HPU Module (@DIS)
if len(uaf_data_raw) and uaf_tag == '@DIS':
dis_hdr = get_struct(uaf_data_raw, 0x0, DisHeader) # Parse @DIS Module Raw Header Structure
printer('Default Command Status Header:\n', padding + 4)
dis_hdr.struct_print(padding + 8) # Print @DIS Module Raw Header Info
# Store/Save @DIS Module Header Info in file
with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis:
with contextlib.redirect_stdout(dis):
dis_hdr.struct_print(0) # Store @DIS Module Header Info
dis_data = uaf_data_raw[DIS_HDR_LEN:] # @DIS Module Entries Data
# Parse all @DIS Module Entries
for mod_idx in range(dis_hdr.EntryCount):
dis_mod = get_struct(dis_data, mod_idx * DIS_MOD_LEN, DisModule) # Parse @DIS Module Raw Entry Structure
printer(f'Default Command Status Entry {mod_idx + 1:02d}/{dis_hdr.EntryCount:02d}:\n', padding + 8)
dis_mod.struct_print(padding + 12) # Print @DIS Module Raw Entry Info
# Store/Save @DIS Module Entry Info in file
with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis:
with contextlib.redirect_stdout(dis):
printer()
dis_mod.struct_print(4) # Store @DIS Module Entry Info
os.remove(uaf_fname) # Delete @DIS Module binary, info exported as text
# Parse Name List @UAF|@HPU Module (@NAL)
if len(uaf_data_raw) >= 5 and (uaf_tag,uaf_data_raw[0],uaf_data_raw[4]) == ('@NAL',0x40,0x3A):
nal_info = uaf_data_raw.decode('utf-8','ignore').replace('\r','').strip().split('\n')
printer('AMI UCP Module Name List:\n', padding + 4)
# Parse all @NAL Module Entries
for info in nal_info:
info_tag,info_value = info.split(':',1)
printer(f'{info_tag} : {info_value}', padding + 8, False) # Print @NAL Module Tag-Path Info
info_part = agnostic_path(info_value).parts # Split OS agnostic path in parts
info_path = to_string(info_part[1:-1], os.sep) # Get path without drive/root or file
info_name = info_part[-1] # Get file from last path part
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_insyde_ifd(uaf_fname):
ins_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-IFD')) # Generate extraction directory
if insyde_ifd_extract(uaf_fname, get_extract_path(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
if is_ami_pfat(uaf_data_raw):
pfat_dir = os.path.join(extract_path, safe_name(uaf_name))
parse_pfat_file(uaf_data_raw, get_extract_path(pfat_dir), padding + 4)
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)
# 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(uaf_data_raw, get_extract_path(uaf_dir), padding + 4, checksum) # Call recursively
os.remove(uaf_fname) # Delete raw nested AMI UCP image after successful extraction
return nal_dict
# Get common ctypes Structure Sizes
UAF_HDR_LEN = ctypes.sizeof(UafHeader)
UAF_MOD_LEN = ctypes.sizeof(UafModule)
DIS_HDR_LEN = ctypes.sizeof(DisHeader)
DIS_MOD_LEN = ctypes.sizeof(DisModule)
UII_HDR_LEN = ctypes.sizeof(UiiHeader)
# AMI UCP Tag Dictionary
UAF_TAG_DICT = {
'@3FI' : ['HpBiosUpdate32.efi', 'HpBiosUpdate32.efi', ''],
'@3S2' : ['HpBiosUpdate32.s12', 'HpBiosUpdate32.s12', ''],
'@3S4' : ['HpBiosUpdate32.s14', 'HpBiosUpdate32.s14', ''],
'@3S9' : ['HpBiosUpdate32.s09', 'HpBiosUpdate32.s09', ''],
'@3SG' : ['HpBiosUpdate32.sig', 'HpBiosUpdate32.sig', ''],
'@AMI' : ['UCP_Nested.bin', 'Nested AMI UCP', ''],
'@B12' : ['BiosMgmt.s12', 'BiosMgmt.s12', ''],
'@B14' : ['BiosMgmt.s14', 'BiosMgmt.s14', ''],
'@B32' : ['BiosMgmt32.s12', 'BiosMgmt32.s12', ''],
'@B34' : ['BiosMgmt32.s14', 'BiosMgmt32.s14', ''],
'@B39' : ['BiosMgmt32.s09', 'BiosMgmt32.s09', ''],
'@B3E' : ['BiosMgmt32.efi', 'BiosMgmt32.efi', ''],
'@BM9' : ['BiosMgmt.s09', 'BiosMgmt.s09', ''],
'@BME' : ['BiosMgmt.efi', 'BiosMgmt.efi', ''],
'@CKV' : ['Check_Version.txt', 'Check Version', 'Text'],
'@CMD' : ['AFU_Command.txt', 'AMI AFU Command', 'Text'],
'@CML' : ['CMOSD4.txt', 'CMOS Item Number-Value (MSI)', 'Text'],
'@CMS' : ['CMOSD4.exe', 'Get or Set CMOS Item (MSI)', ''],
'@CPM' : ['AC_Message.txt', 'Confirm Power Message', ''],
'@DCT' : ['DevCon32.exe', 'Device Console WIN32', ''],
'@DCX' : ['DevCon64.exe', 'Device Console WIN64', ''],
'@DFE' : ['HpDevFwUpdate.efi', 'HpDevFwUpdate.efi', ''],
'@DFS' : ['HpDevFwUpdate.s12', 'HpDevFwUpdate.s12', ''],
'@DIS' : ['Command_Status.bin', 'Default Command Status', ''],
'@ENB' : ['ENBG64.exe', 'ENBG64.exe', ''],
'@HPU' : ['UCP_Main.bin', 'Utility Auxiliary File (HP)', ''],
'@INS' : ['Insyde_Nested.bin', 'Nested Insyde SFX', ''],
'@M32' : ['HpBiosMgmt32.s12', 'HpBiosMgmt32.s12', ''],
'@M34' : ['HpBiosMgmt32.s14', 'HpBiosMgmt32.s14', ''],
'@M39' : ['HpBiosMgmt32.s09', 'HpBiosMgmt32.s09', ''],
'@M3I' : ['HpBiosMgmt32.efi', 'HpBiosMgmt32.efi', ''],
'@MEC' : ['FWUpdLcl.txt', 'Intel FWUpdLcl Command', 'Text'],
'@MED' : ['FWUpdLcl_DOS.exe', 'Intel FWUpdLcl DOS', ''],
'@MET' : ['FWUpdLcl_WIN32.exe', 'Intel FWUpdLcl WIN32', ''],
'@MFI' : ['HpBiosMgmt.efi', 'HpBiosMgmt.efi', ''],
'@MS2' : ['HpBiosMgmt.s12', 'HpBiosMgmt.s12', ''],
'@MS4' : ['HpBiosMgmt.s14', 'HpBiosMgmt.s14', ''],
'@MS9' : ['HpBiosMgmt.s09', 'HpBiosMgmt.s09', ''],
'@NAL' : ['UCP_List.txt', 'AMI UCP Module Name List', ''],
'@OKM' : ['OK_Message.txt', 'OK Message', ''],
'@PFC' : ['BGT_Command.txt', 'AMI BGT Command', 'Text'],
'@R3I' : ['CryptRSA32.efi', 'CryptRSA32.efi', ''],
'@RFI' : ['CryptRSA.efi', 'CryptRSA.efi', ''],
'@UAF' : ['UCP_Main.bin', 'Utility Auxiliary File (AMI)', ''],
'@UFI' : ['HpBiosUpdate.efi', 'HpBiosUpdate.efi', ''],
'@UII' : ['UCP_Info.txt', 'Utility Identification Information', ''],
'@US2' : ['HpBiosUpdate.s12', 'HpBiosUpdate.s12', ''],
'@US4' : ['HpBiosUpdate.s14', 'HpBiosUpdate.s14', ''],
'@US9' : ['HpBiosUpdate.s09', 'HpBiosUpdate.s09', ''],
'@USG' : ['HpBiosUpdate.sig', 'HpBiosUpdate.sig', ''],
'@VER' : ['OEM_Version.txt', 'OEM Version', 'Text'],
'@VXD' : ['amifldrv.vxd', 'amifldrv.vxd', ''],
'@W32' : ['amifldrv32.sys', 'amifldrv32.sys', ''],
'@W64' : ['amifldrv64.sys', 'amifldrv64.sys', ''],
}
if __name__ == '__main__':
utility = BIOSUtility(TITLE, is_ami_ucp, ucp_extract)
utility.parse_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true')
utility.run_utility()

View file

@ -1,104 +0,0 @@
#!/usr/bin/env python3
"""
Apple EFI Rename
Apple EFI File Renamer
Copyright (C) 2018-2019 Plato Mavropoulos
https://github.com/tianocore/edk2/blob/master/Vlv2TbltDevicePkg/Include/Library/BiosIdLib.h
"""
print('Apple EFI File Renamer v1.3\n')
import os
import re
import sys
import zlib
import shutil
import subprocess
pattern = re.compile(br'\x24\x49\x42\x49\x4F\x53\x49\x24') # Intel $IBIOSI$
if len(sys.argv) >= 3 and sys.argv[1] == '-skip' :
# Called via Apple_EFI_Package
apple_efi = sys.argv[2:]
skip_pause = True
elif len(sys.argv) >= 2 :
# Drag & Drop or CLI
apple_efi = sys.argv[1:]
skip_pause = False
else :
# Folder path
apple_efi = []
skip_pause = False
in_path = input('\nEnter the full folder path: ')
print('\nWorking...\n')
for root, dirs, files in os.walk(in_path):
for name in files :
apple_efi.append(os.path.join(root, name))
for input_file in apple_efi :
file_path = os.path.abspath(input_file)
file_name = os.path.basename(input_file)
file_dir = os.path.dirname(file_path)
file_ext = os.path.splitext(file_path)[1]
error = False
with open(input_file, 'rb') as in_file : buffer = in_file.read()
is_ibiosi = pattern.search(buffer) # Detect $IBIOSI$ pattern
if not is_ibiosi :
# On some Apple EFI, the $IBIOSI$ pattern is within compressed modules so we need to use UEFIFind and UEFIExtract
try :
uefifind = subprocess.check_output(['UEFIFind', file_path, 'body', 'list', '244942494F534924'], universal_newlines=True)
uefiextr = subprocess.run(['UEFIExtract', file_path, uefifind[0:36], '-o', '_$IBIOSI$_', '-m', 'body'], stdout=subprocess.DEVNULL)
with open(os.path.join('_$IBIOSI$_', 'body.bin'), 'rb') as in_file : buffer = in_file.read()
is_ibiosi = pattern.search(buffer) # Detect decompressed $IBIOSI$ pattern
shutil.rmtree('_$IBIOSI$_') # Remove temporary folder
except :
error = True
if not error :
bios_info = buffer[is_ibiosi.end():is_ibiosi.end() + 0x42].decode('utf-16')
BoardID = bios_info[:7].strip()
BoardRev = bios_info[7]
OEMID = bios_info[9:12] # 88Z
MajorVer = bios_info[13:17]
BuildType = bios_info[18] # B
MinorVer = bios_info[19:21]
Year = bios_info[22:24]
Month = bios_info[24:26]
Day = bios_info[26:28]
Hour = bios_info[28:30]
Minute = bios_info[30:32]
file_chk = zlib.adler32(buffer) # Checksum for EFI with same $IBIOSI$ but different PRD/PRE status
new_name = '%s%s_%s_%s%s_20%s-%s-%s_%s-%s_%0.8X%s' % (BoardID, BoardRev, MajorVer, BuildType, MinorVer, Year, Month, Day, Hour, Minute, file_chk, file_ext)
file_path_new = os.path.join(file_dir, new_name)
if not os.path.isfile(file_path_new) : os.replace(file_path, file_path_new) # Rename input EFI with proper name
print(new_name)
print('\nBoard Identity: %s%s' % (BoardID, BoardRev))
print('Apple Identity: %s' % OEMID)
print('Major Version: %s' % MajorVer)
print('Minor Version: %s' % MinorVer)
print('Build Type: %s' % BuildType)
print('Build Date: 20%s-%s-%s' % (Year, Month, Day))
print('Build Time: %s:%s\n' % (Hour, Minute))
else :
print('\nError: Could not find $IBIOSI$ pattern at %s!' % file_name)
print(' Make sure that "UEFIFind" and "UEFIExtract" executables exist!\n')
if not skip_pause : input('Done!')

View file

@ -1,146 +0,0 @@
#!/usr/bin/env python3
"""
Apple EFI Split
Apple EFI IM4P Splitter
Copyright (C) 2018-2020 Plato Mavropoulos
"""
title = 'Apple EFI IM4P Splitter v2.1'
import os
import re
import sys
im4p = re.compile(br'\x16\x04\x49\x4D\x34\x50\x16\x04') # Apple IM4P
ifd = re.compile(br'\x5A\xA5\xF0\x0F.{172}\xFF{16}', re.DOTALL) # Intel Flash Descriptor (Z¥π. + [0xAC] + 0xFF * 16)
# Flash Descriptor Component Sizes
comp_dict = {
0 : 0x80000, # 512 KB
1 : 0x100000, # 1 MB
2 : 0x200000, # 2 MB
3 : 0x400000, # 4 MB
4 : 0x800000, # 8 MB
5 : 0x1000000, # 16 MB
6 : 0x2000000, # 32 MB
7 : 0x4000000, # 64 MB
8 : 0x8000000, # 128 MB
9 : 0x10000000, # 256 MB
}
# Get input catalog file paths
if len(sys.argv) >= 3 and sys.argv[1] == '-skip' :
# Called via Apple_EFI_Package
apple_im4p = sys.argv[2:]
skip_pause = True
skip_space = ' '
print('\n%s%s' % (skip_space, title)) # Print Title
elif len(sys.argv) >= 2 :
# Drag & Drop or CLI
apple_im4p = sys.argv[1:]
skip_pause = False
skip_space = ''
print('\n%s%s' % (skip_space, title)) # Print Title
else :
# Folder path
apple_im4p = []
skip_pause = False
skip_space = ''
print('\n%s%s' % (skip_space, title)) # Print Title
in_path = input('\nEnter the full folder path: ')
print('\nWorking...')
for root, dirs, files in os.walk(in_path):
for name in files :
apple_im4p.append(os.path.join(root, name))
for input_file in apple_im4p :
file_path = os.path.abspath(input_file)
file_name = os.path.basename(input_file)
file_dir = os.path.dirname(file_path)
file_ext = os.path.splitext(file_path)[1]
print('\n%sFile: %s' % (skip_space, file_name)) # Print File Name
# Must be IM4P file because its size is 0x0 dependent
if file_ext not in ('.im4p','.IM4P') :
print('\n%s Error: Could not find IM4P file extension at %s!' % (skip_space, file_name))
continue # Critical error
with open(input_file, 'rb') as in_file : buffer = in_file.read()
is_im4p = im4p.search(buffer) # Detect IM4P pattern
if not is_im4p :
print('\n%s Error: Could not find IM4P pattern at %s!' % (skip_space, file_name))
continue # Critical error
im4p_size = int.from_bytes(buffer[2:is_im4p.start()], 'big') # Variable, from 0x2 - IM4P
im4p_type = buffer[is_im4p.end():is_im4p.end() + 0x4].decode('utf-8') # mefi
if im4p_type != 'mefi' :
print('\n%s Error: Could not find "mefi" IM4P Type at %s!' % (skip_space, file_name))
continue # Critical error
# After IM4P mefi (0x15), multi EFI payloads have _MEFIBIN (0x100) which is difficult to reverse without varying samples.
# However, _MEFIBIN is not required for splitting SPI/EFI images because Intel Flash Descriptor Component Density exists.
mefi_data_start = is_im4p.start() + buffer[is_im4p.start() - 0x1] # IM4P mefi payload start offset
mefi_data_size = int.from_bytes(buffer[is_im4p.end() + 0x9:is_im4p.end() + 0xD], 'big') # IM4P mefi payload size
mefibin_exist = buffer[mefi_data_start:mefi_data_start + 0x8] == b'_MEFIBIN' # Check if mefi is followed by _MEFIBIN
efi_data_start = mefi_data_start + 0x100 if mefibin_exist else mefi_data_start # Actual multi EFI payloads start after _MEFIBIN
efi_data_size = mefi_data_size - 0x100 if mefibin_exist else mefi_data_size # Actual multi EFI payloads size without _MEFIBIN
buffer = buffer[efi_data_start:efi_data_start + efi_data_size] # Adjust input file buffer to actual multi EFI payloads data
fd_matches = list(ifd.finditer(buffer)) # Find Intel Flash Descriptor pattern matches
fd_count = len(fd_matches) # Count found Intel Flash Descriptor pattern matches
fd_final = [] # Initialize final Intel Flash Descriptor info storage
# Parse Intel Flash Descriptor pattern matches
for fd_idx in range(fd_count) :
fd = fd_matches[fd_idx] # Get Intel Flash Descriptor match object
fd_flmap0_fcba = buffer[fd.start() + 0x4] * 0x10 # Component Base Address from FD start (ICH8-ICH10 = 1, IBX = 2, CPT+ = 3)
# I/O Controller Hub (ICH)
if fd_flmap0_fcba == 0x10 :
start_substruct = 0x0 # At ICH, Flash Descriptor starts at 0x0
end_substruct = 0xBC # 0xBC for [0xAC] + 0xFF * 16 sanity check
# Platform Controller Hub (PCH)
else :
start_substruct = 0x10 # At PCH, Flash Descriptor starts at 0x10
end_substruct = 0xBC # 0xBC for [0xAC] + 0xFF * 16 sanity check
fd_match_start = fd.start() - start_substruct # Actual Flash Descriptor Start Offset
fd_match_end = fd.end() - end_substruct # Actual Flash Descriptor End Offset
# Calculate Intel Flash Descriptor Flash Component Total Size
fd_flmap0_nc = ((int.from_bytes(buffer[fd_match_end:fd_match_end + 0x4], 'little') >> 8) & 3) + 1 # Component Count (00 = 1, 01 = 2)
fd_flmap1_isl = buffer[fd_match_end + 0x7] # PCH/ICH Strap Length (ME 2-8 & TXE 0-2 & SPS 1-2 <= 0x12, ME 9+ & TXE 3+ & SPS 3+ >= 0x13)
fd_comp_den = buffer[fd_match_start + fd_flmap0_fcba] # Component Density Byte (ME 2-8 & TXE 0-2 & SPS 1-2 = 0:5, ME 9+ & TXE 3+ & SPS 3+ = 0:7)
fd_comp_1_bitwise = 0xF if fd_flmap1_isl >= 0x13 else 0x7 # Component 1 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4)
fd_comp_2_bitwise = 0x4 if fd_flmap1_isl >= 0x13 else 0x3 # Component 2 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4)
fd_comp_all_size = comp_dict[fd_comp_den & fd_comp_1_bitwise] # Component 1 Density (FCBA > C0DEN)
if fd_flmap0_nc == 2 : fd_comp_all_size += comp_dict[fd_comp_den >> fd_comp_2_bitwise] # Component 2 Density (FCBA > C1DEN)
fd_final.append((fd_match_start,fd_comp_all_size)) # Store Intel Flash Descriptor final info
# Split IM4P via the final Intel Flash Descriptor matches
for fd_idx in range(fd_count) :
fd = fd_final[fd_idx] # Get Intel Flash Descriptor final info [FD Start, FD Component(s) Size]
# The Intel Flash Descriptor Flash Component Total Size should be enough to split the IM4P.
# However, for sanity, its Size can be compared to the Size different of Next - Current FD.
fd_diff_size = len(buffer) - fd[0] if fd_idx == fd_count - 1 else fd_final[fd_idx + 1][0] - fd[0] # Last FD ends at mefi payload end
if fd[1] != fd_diff_size : # FD Total Component Size should be equal to Next-Current FD Difference Size
print('\n%s Error: Intel FD %d/%d Component Size 0x%X != Next-Current FD Size Difference 0x%X!'
% (skip_space, fd_idx + 1, fd_count, fd[1], fd_diff_size))
file_data = buffer[fd[0]:fd[0] + max(fd[1], fd_diff_size)] # Split EFI image file data (use largest FD Size just in case)
file_path_new = os.path.join(file_dir, '%s_%d.fd' % (file_name[:-5], fd_idx + 1)) # Split EFI image file path
with open(file_path_new, 'wb') as spi_image : spi_image.write(file_data) # Store split EFI image file
print('\n%s Split IM4P into %d EFI image(s)!' % (skip_space, fd_count))
if not skip_pause : input('\nDone!')

View file

@ -1,101 +0,0 @@
#!/usr/bin/env python3
"""
Apple EFI Package
Apple EFI Package Extractor
Copyright (C) 2019 Plato Mavropoulos
"""
print('Apple EFI Package Extractor v1.1')
import os
import sys
import zlib
import shutil
import subprocess
if len(sys.argv) >= 2 :
pkg = sys.argv[1:]
else :
pkg = []
in_path = input('\nEnter the full folder path: ')
print('\nWorking...')
for root, dirs, files in os.walk(in_path):
for name in files :
pkg.append(os.path.join(root, name))
anytoiso_path = 'C:\\Program Files (x86)\\AnyToISO\\anytoiso.exe'
final_path = os.path.join(os.getcwd(), 'AppleEFI')
if os.path.exists(final_path) : shutil.rmtree(final_path)
for input_file in pkg :
file_path = os.path.abspath(input_file)
file_name = os.path.basename(input_file)
file_dir = os.path.dirname(file_path)
file_ext = os.path.splitext(file_path)[1]
print('\nFile: %s\n' % file_name)
with open(input_file, 'rb') as in_buff : file_adler = zlib.adler32(in_buff.read()) & 0xFFFFFFFF
pkg_payload = os.path.join(final_path, '%s_%0.8X' % (file_name, file_adler))
pkg_temp = os.path.join(final_path, '__TEMP_%s_%0.8X' % (file_name, file_adler))
os.makedirs(pkg_temp)
subprocess.run([anytoiso_path, '/extract', file_path, pkg_temp], check = True, stdout=subprocess.DEVNULL)
if os.path.isfile(os.path.join(pkg_temp, 'Scripts')) :
scripts_init = os.path.join(pkg_temp, 'Scripts')
scripts_cpgz = os.path.join(pkg_temp, 'Scripts.cpgz')
scripts_extr = os.path.join(pkg_temp, 'Scripts', '')
efi_path = os.path.join(scripts_extr, 'Tools', 'EFIPayloads', '')
os.replace(scripts_init, scripts_cpgz)
subprocess.run([anytoiso_path, '/extract', scripts_cpgz, scripts_extr], check = True, stdout=subprocess.DEVNULL)
shutil.copytree(efi_path, pkg_payload)
elif os.path.isfile(os.path.join(pkg_temp, 'Payload')) :
payload_init = os.path.join(pkg_temp, 'Payload')
payload_pbzx = os.path.join(pkg_temp, 'Payload.pbzx')
payload_extr = os.path.join(pkg_temp, 'Payload', '')
zip_path = os.path.join(payload_extr, 'usr', 'standalone', 'firmware', 'bridgeOSCustomer.bundle', 'Contents', 'Resources', 'UpdateBundle')
efi_path = os.path.join(zip_path, 'boot', 'Firmware', 'MacEFI', '')
os.replace(payload_init, payload_pbzx)
subprocess.run([anytoiso_path, '/extract', payload_pbzx, payload_extr], check = True, stdout=subprocess.DEVNULL)
subprocess.run([anytoiso_path, '/extract', zip_path + '.zip', zip_path], check = True, stdout=subprocess.DEVNULL)
if os.path.exists(efi_path) : shutil.copytree(efi_path, pkg_payload)
shutil.rmtree(pkg_temp)
im4p_files = []
for root, dirs, files in os.walk(pkg_payload):
for name in files :
if name.endswith('.im4p') :
im4p_files.append(os.path.join(root, name))
if im4p_files : subprocess.run(['python', 'Apple_EFI_Split.py', '-skip', *im4p_files], check = True, stdout=subprocess.DEVNULL)
for im4p in im4p_files : os.remove(im4p)
final_files = []
for root, dirs, files in os.walk(pkg_payload):
for name in files :
final_files.append(os.path.join(root, name))
if final_files : subprocess.run(['python', 'Apple_EFI_Rename.py', '-skip', *final_files], check = True, stdout=subprocess.DEVNULL)
for root, dirs, files in os.walk(pkg_payload):
for name in files :
if not os.path.isfile(os.path.join(final_path, name)) :
shutil.copy2(os.path.join(root, name), os.path.join(final_path, name))
shutil.rmtree(pkg_payload)
else :
input('\nDone!')

View file

@ -1,71 +0,0 @@
#!/usr/bin/env python3
"""
Apple EFI Links
Apple EFI Sucatalog Link Grabber
Copyright (C) 2018-2019 Plato Mavropoulos
"""
print('Apple EFI Sucatalog Link Grabber v1.2\n')
import os
import sys
import datetime
# Remove previous output files
if os.path.isfile('OUT.txt') : os.remove('OUT.txt')
# Get input catalog file paths
if len(sys.argv) >= 2 :
# Drag & Drop or CLI
catalogs = sys.argv[1:]
else :
# Working directory
catalogs = []
for root, dirs, files in os.walk(os.getcwd()) :
for name in files :
if name.endswith('.sucatalog') :
catalogs.append(os.path.join(root, name))
print('Working...')
# Parse each input xml file
for input_file in catalogs :
input_name,input_extension = os.path.splitext(os.path.basename(input_file))
print('\n%s%s' % (input_name, input_extension))
with open(input_file, 'r') as in_file :
for line in in_file :
# Find EFI Firmware package links
if ('.pkg' in line or '.tar' in line) and ('FirmwareUpd' in line or '/BridgeOSUpdateCustomer' in line or 'EFIUpd' in line) \
and 'Bluetooth' not in line and 'DPVGA' not in line and 'Thunderbolt' not in line and 'PMG5' not in line and 'HardDrive' not in line :
if '.pkg' in line : link = line[line.find('http'):line.find('.pkg') + 4] # Remove xml formatting
else : link = line[line.find('http'):line.find('.tar') + 4]
with open('OUT.txt', 'a') as out_file : out_file.write(link + '\n') # Store links in temporary output file
# Parse temporary output file
if os.path.isfile('OUT.txt') :
with open('OUT.txt', 'r+') as out_file :
parsed_lines = []
final_lines = []
for line in out_file :
if line not in parsed_lines : # Remove duplicate links
final_lines.append(line)
parsed_lines.append(line)
final_lines = ''.join(map(str, sorted(final_lines)))
current_datetime = datetime.datetime.utcnow().isoformat(timespec='seconds').replace('-','').replace('T','').replace(':','')
output_file = 'EFI %s.txt' % current_datetime
with open(output_file, 'w') as efi_file : efi_file.write(final_lines) # Save final output file
print('\nStored %s!' % output_file)
os.remove('OUT.txt') # Remove temporary output file
input('\nDone!')

167
Apple_EFI_ID.py Normal file
View file

@ -0,0 +1,167 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Apple EFI ID
Apple EFI Image Identifier
Copyright (C) 2018-2022 Plato Mavropoulos
"""
TITLE = 'Apple EFI Image Identifier v2.0_a5'
import os
import sys
import zlib
import struct
import ctypes
import subprocess
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.externals import get_uefifind_path, get_uefiextract_path
from common.path_ops import del_dirs, path_parent, path_suffixes
from common.patterns import PAT_APPLE_EFI
from common.struct_ops import char, get_struct, uint8_t
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
class IntelBiosId(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('Signature', char*8), # 0x00
('BoardID', uint8_t*16), # 0x08
('Dot1', uint8_t*2), # 0x18
('BoardExt', uint8_t*6), # 0x1A
('Dot2', uint8_t*2), # 0x20
('VersionMajor', uint8_t*8), # 0x22
('Dot3', uint8_t*2), # 0x2A
('BuildType', uint8_t*2), # 0x2C
('VersionMinor', uint8_t*4), # 0x2E
('Dot4', uint8_t*2), # 0x32
('Year', uint8_t*4), # 0x34
('Month', uint8_t*4), # 0x38
('Day', uint8_t*4), # 0x3C
('Hour', uint8_t*4), # 0x40
('Minute', uint8_t*4), # 0x44
('NullTerminator', uint8_t*2), # 0x48
# 0x4A
]
# https://github.com/tianocore/edk2-platforms/blob/master/Platform/Intel/BoardModulePkg/Include/Guid/BiosId.h
@staticmethod
def decode(field):
return struct.pack('B' * len(field), *field).decode('utf-16','ignore').strip('\x00 ')
def get_bios_id(self):
BoardID = self.decode(self.BoardID)
BoardExt = self.decode(self.BoardExt)
VersionMajor = self.decode(self.VersionMajor)
BuildType = self.decode(self.BuildType)
VersionMinor = self.decode(self.VersionMinor)
BuildDate = f'20{self.decode(self.Year)}-{self.decode(self.Month)}-{self.decode(self.Day)}'
BuildTime = f'{self.decode(self.Hour)}-{self.decode(self.Minute)}'
return BoardID, BoardExt, VersionMajor, BuildType, VersionMinor, BuildDate, BuildTime
def struct_print(self, p):
BoardID,BoardExt,VersionMajor,BuildType,VersionMinor,BuildDate,BuildTime = self.get_bios_id()
printer(['Intel Signature:', self.Signature.decode('utf-8')], p, False)
printer(['Board Identity: ', BoardID], p, False)
printer(['Apple Identity: ', BoardExt], p, False)
printer(['Major Version: ', VersionMajor], p, False)
printer(['Minor Version: ', VersionMinor], p, False)
printer(['Build Type: ', BuildType], p, False)
printer(['Build Date: ', BuildDate], p, False)
printer(['Build Time: ', BuildTime.replace('-',':')], p, False)
# Check if input is Apple EFI image
def is_apple_efi(input_file):
input_buffer = file_to_bytes(input_file)
if PAT_APPLE_EFI.search(input_buffer):
return True
if not os.path.isfile(input_file):
return False
try:
_ = subprocess.run([get_uefifind_path(), input_file, 'body', 'list', PAT_UEFIFIND],
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return True
except Exception:
return False
# Parse & Identify (or Rename) Apple EFI image
def apple_efi_identify(input_file, extract_path, padding=0, rename=False):
if not os.path.isfile(input_file):
printer('Error: Could not find input file path!', padding)
return 1
input_buffer = file_to_bytes(input_file)
bios_id_match = PAT_APPLE_EFI.search(input_buffer) # Detect $IBIOSI$ pattern
if bios_id_match:
bios_id_res = f'0x{bios_id_match.start():X}'
bios_id_hdr = get_struct(input_buffer, bios_id_match.start(), IntelBiosId)
else:
# The $IBIOSI$ pattern is within EFI compressed modules so we need to use UEFIFind and UEFIExtract
try:
bios_id_res = subprocess.check_output([get_uefifind_path(), input_file, 'body', 'list', PAT_UEFIFIND],
text=True)[:36]
del_dirs(extract_path) # UEFIExtract must create its output folder itself, make sure it is not present
_ = subprocess.run([get_uefiextract_path(), input_file, bios_id_res, '-o', extract_path, '-m', 'body'],
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
with open(os.path.join(extract_path, 'body.bin'), 'rb') as raw_body:
body_buffer = raw_body.read()
bios_id_match = PAT_APPLE_EFI.search(body_buffer) # Detect decompressed $IBIOSI$ pattern
bios_id_hdr = get_struct(body_buffer, bios_id_match.start(), IntelBiosId)
del_dirs(extract_path) # Successful UEFIExtract extraction, remove its output (temp) folder
except Exception:
printer('Error: Failed to parse compressed $IBIOSI$ pattern!', padding)
return 2
printer(f'Detected $IBIOSI$ at {bios_id_res}\n', padding)
bios_id_hdr.struct_print(padding + 4)
if rename:
input_parent = path_parent(input_file)
input_suffix = path_suffixes(input_file)[-1]
input_adler32 = zlib.adler32(input_buffer)
ID,Ext,Major,Type,Minor,Date,Time = bios_id_hdr.get_bios_id()
output_name = f'{ID}_{Ext}_{Major}_{Type}{Minor}_{Date}_{Time}_{input_adler32:08X}{input_suffix}'
output_file = os.path.join(input_parent, output_name)
if not os.path.isfile(output_file):
os.replace(input_file, output_file) # Rename input file based on its EFI tag
printer(f'Renamed to {output_name}', padding)
return 0
PAT_UEFIFIND = f'244942494F534924{"."*32}2E00{"."*12}2E00{"."*16}2E00{"."*12}2E00{"."*40}0000'
if __name__ == '__main__':
utility = BIOSUtility(TITLE, is_apple_efi, apple_efi_identify)
utility.parse_argument('-r', '--rename', help='rename EFI image based on its tag', action='store_true')
utility.run_utility()

145
Apple_EFI_IM4P.py Normal file
View file

@ -0,0 +1,145 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Apple EFI IM4P
Apple EFI IM4P Splitter
Copyright (C) 2018-2022 Plato Mavropoulos
"""
TITLE = 'Apple EFI IM4P Splitter v3.0_a5'
import os
import sys
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.path_ops import make_dirs, path_stem
from common.patterns import PAT_APPLE_IM4P, PAT_INTEL_IFD
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
# Check if input is Apple EFI IM4P image
def is_apple_im4p(input_file):
input_buffer = file_to_bytes(input_file)
is_im4p = PAT_APPLE_IM4P.search(input_buffer)
is_ifd = PAT_INTEL_IFD.search(input_buffer)
return bool(is_im4p and is_ifd)
# Parse & Split Apple EFI IM4P image
def apple_im4p_split(input_file, extract_path, padding=0):
exit_codes = []
input_buffer = file_to_bytes(input_file)
make_dirs(extract_path, delete=True)
# Detect IM4P EFI pattern
im4p_match = PAT_APPLE_IM4P.search(input_buffer)
# After IM4P mefi (0x15), multi EFI payloads have _MEFIBIN (0x100) but is difficult to RE w/o varying samples.
# However, _MEFIBIN is not required for splitting SPI images due to Intel Flash Descriptor Components Density.
# IM4P mefi payload start offset
mefi_data_bgn = im4p_match.start() + input_buffer[im4p_match.start() - 0x1]
# IM4P mefi payload size
mefi_data_len = int.from_bytes(input_buffer[im4p_match.end() + 0x5:im4p_match.end() + 0x9], 'big')
# Check if mefi is followed by _MEFIBIN
mefibin_exist = input_buffer[mefi_data_bgn:mefi_data_bgn + 0x8] == b'_MEFIBIN'
# Actual multi EFI payloads start after _MEFIBIN
efi_data_bgn = mefi_data_bgn + 0x100 if mefibin_exist else mefi_data_bgn
# Actual multi EFI payloads size without _MEFIBIN
efi_data_len = mefi_data_len - 0x100 if mefibin_exist else mefi_data_len
# Adjust input file buffer to actual multi EFI payloads data
input_buffer = input_buffer[efi_data_bgn:efi_data_bgn + efi_data_len]
# Parse Intel Flash Descriptor pattern matches
for ifd in PAT_INTEL_IFD.finditer(input_buffer):
# Component Base Address from FD start (ICH8-ICH10 = 1, IBX = 2, CPT+ = 3)
ifd_flmap0_fcba = input_buffer[ifd.start() + 0x4] * 0x10
# I/O Controller Hub (ICH)
if ifd_flmap0_fcba == 0x10:
# At ICH, Flash Descriptor starts at 0x0
ifd_bgn_substruct = 0x0
# 0xBC for [0xAC] + 0xFF * 16 sanity check
ifd_end_substruct = 0xBC
# Platform Controller Hub (PCH)
else:
# At PCH, Flash Descriptor starts at 0x10
ifd_bgn_substruct = 0x10
# 0xBC for [0xAC] + 0xFF * 16 sanity check
ifd_end_substruct = 0xBC
# Actual Flash Descriptor Start Offset
ifd_match_start = ifd.start() - ifd_bgn_substruct
# Actual Flash Descriptor End Offset
ifd_match_end = ifd.end() - ifd_end_substruct
# Calculate Intel Flash Descriptor Flash Component Total Size
# Component Count (00 = 1, 01 = 2)
ifd_flmap0_nc = ((int.from_bytes(input_buffer[ifd_match_end:ifd_match_end + 0x4], 'little') >> 8) & 3) + 1
# PCH/ICH Strap Length (ME 2-8 & TXE 0-2 & SPS 1-2 <= 0x12, ME 9+ & TXE 3+ & SPS 3+ >= 0x13)
ifd_flmap1_isl = input_buffer[ifd_match_end + 0x7]
# Component Density Byte (ME 2-8 & TXE 0-2 & SPS 1-2 = 0:5, ME 9+ & TXE 3+ & SPS 3+ = 0:7)
ifd_comp_den = input_buffer[ifd_match_start + ifd_flmap0_fcba]
# Component 1 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4)
ifd_comp_1_bitwise = 0xF if ifd_flmap1_isl >= 0x13 else 0x7
# Component 2 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4)
ifd_comp_2_bitwise = 0x4 if ifd_flmap1_isl >= 0x13 else 0x3
# Component 1 Density (FCBA > C0DEN)
ifd_comp_all_size = IFD_COMP_LEN[ifd_comp_den & ifd_comp_1_bitwise]
# Component 2 Density (FCBA > C1DEN)
if ifd_flmap0_nc == 2:
ifd_comp_all_size += IFD_COMP_LEN[ifd_comp_den >> ifd_comp_2_bitwise]
ifd_data_bgn = ifd_match_start
ifd_data_end = ifd_data_bgn + ifd_comp_all_size
ifd_data_txt = f'0x{ifd_data_bgn:07X}-0x{ifd_data_end:07X}'
output_data = input_buffer[ifd_data_bgn:ifd_data_end]
output_size = len(output_data)
output_name = path_stem(input_file) if os.path.isfile(input_file) else 'Part'
output_path = os.path.join(extract_path, f'{output_name}_[{ifd_data_txt}].fd')
with open(output_path, 'wb') as output_image:
output_image.write(output_data)
printer(f'Split Apple EFI image at {ifd_data_txt}!', padding)
if output_size != ifd_comp_all_size:
printer(f'Error: Bad image size 0x{output_size:07X}, expected 0x{ifd_comp_all_size:07X}!', padding + 4)
exit_codes.append(1)
return sum(exit_codes)
# Intel Flash Descriptor Component Sizes (4MB, 8MB, 16MB and 32MB)
IFD_COMP_LEN = {3: 0x400000, 4: 0x800000, 5: 0x1000000, 6: 0x2000000}
if __name__ == '__main__':
BIOSUtility(TITLE, is_apple_im4p, apple_im4p_split).run_utility()

118
Apple_EFI_PBZX.py Normal file
View file

@ -0,0 +1,118 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Apple PBZX Extract
Apple EFI PBZX Extractor
Copyright (C) 2021-2022 Plato Mavropoulos
"""
TITLE = 'Apple EFI PBZX Extractor v1.0_a5'
import os
import sys
import lzma
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 make_dirs, path_stem
from common.patterns import PAT_APPLE_PBZX
from common.struct_ops import get_struct, uint32_t
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
class PbzxChunk(ctypes.BigEndianStructure):
_pack_ = 1
_fields_ = [
('Reserved0', uint32_t), # 0x00
('InitSize', uint32_t), # 0x04
('Reserved1', uint32_t), # 0x08
('CompSize', uint32_t), # 0x0C
# 0x10
]
def struct_print(self, p):
printer(['Reserved 0 :', f'0x{self.Reserved0:X}'], p, False)
printer(['Initial Size :', f'0x{self.InitSize:X}'], p, False)
printer(['Reserved 1 :', f'0x{self.Reserved1:X}'], p, False)
printer(['Compressed Size:', f'0x{self.CompSize:X}'], p, False)
# Check if input is Apple PBZX image
def is_apple_pbzx(input_file):
input_buffer = file_to_bytes(input_file)
return bool(PAT_APPLE_PBZX.search(input_buffer[:0x4]))
# Parse & Extract Apple PBZX image
def apple_pbzx_extract(input_file, extract_path, padding=0):
input_buffer = file_to_bytes(input_file)
make_dirs(extract_path, delete=True)
cpio_bin = b'' # Initialize PBZX > CPIO Buffer
cpio_len = 0x0 # Initialize PBZX > CPIO Length
chunk_off = 0xC # First PBZX Chunk starts at 0xC
while chunk_off < len(input_buffer):
chunk_hdr = get_struct(input_buffer, chunk_off, PbzxChunk)
printer(f'PBZX Chunk at 0x{chunk_off:08X}\n', padding)
chunk_hdr.struct_print(padding + 4)
# PBZX Chunk data starts after its Header
comp_bgn = chunk_off + PBZX_CHUNK_HDR_LEN
# To avoid a potential infinite loop, double-check Compressed Size
comp_end = comp_bgn + max(chunk_hdr.CompSize, PBZX_CHUNK_HDR_LEN)
comp_bin = input_buffer[comp_bgn:comp_end]
try:
# Attempt XZ decompression, if applicable to Chunk data
cpio_bin += lzma.LZMADecompressor().decompress(comp_bin)
printer('Successful LZMA decompression!', padding + 8)
except Exception:
# Otherwise, Chunk data is not compressed
cpio_bin += comp_bin
# Final CPIO size should match the sum of all Chunks > Initial Size
cpio_len += chunk_hdr.InitSize
# Next Chunk starts at the end of current Chunk's data
chunk_off = comp_end
# Check that CPIO size is valid based on all Chunks > Initial Size
if cpio_len != len(cpio_bin):
printer('Error: Unexpected CPIO archive size!', padding)
return 1
cpio_name = path_stem(input_file) if os.path.isfile(input_file) else 'Payload'
cpio_path = os.path.join(extract_path, f'{cpio_name}.cpio')
with open(cpio_path, 'wb') as cpio_object:
cpio_object.write(cpio_bin)
# Decompress PBZX > CPIO archive with 7-Zip
if is_szip_supported(cpio_path, padding, args=['-tCPIO'], check=True):
if szip_decompress(cpio_path, extract_path, 'CPIO', padding, args=['-tCPIO'], check=True) == 0:
os.remove(cpio_path) # Successful extraction, delete PBZX > CPIO archive
else:
return 3
else:
return 2
return 0
# Get common ctypes Structure Sizes
PBZX_CHUNK_HDR_LEN = ctypes.sizeof(PbzxChunk)
if __name__ == '__main__':
BIOSUtility(TITLE, is_apple_pbzx, apple_pbzx_extract).run_utility()

148
Apple_EFI_PKG.py Normal file
View file

@ -0,0 +1,148 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Apple EFI PKG
Apple EFI Package Extractor
Copyright (C) 2019-2022 Plato Mavropoulos
"""
TITLE = 'Apple EFI Package Extractor v2.0_a5'
import os
import sys
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.comp_szip import is_szip_supported, szip_decompress
from common.path_ops import copy_file, del_dirs, get_path_files, make_dirs, path_name, path_parent, get_extract_path
from common.patterns import PAT_APPLE_PKG
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
from Apple_EFI_ID import apple_efi_identify, is_apple_efi
from Apple_EFI_IM4P import apple_im4p_split, is_apple_im4p
from Apple_EFI_PBZX import apple_pbzx_extract, is_apple_pbzx
# Check if input is Apple EFI PKG package
def is_apple_pkg(input_file):
input_buffer = file_to_bytes(input_file)
return bool(PAT_APPLE_PKG.search(input_buffer[:0x4]))
# Split Apple EFI image (if applicable) and Rename
def efi_split_rename(in_file, out_path, padding=0):
exit_codes = []
working_dir = get_extract_path(in_file)
if is_apple_im4p(in_file):
printer(f'Splitting IM4P via {is_apple_im4p.__module__}...', padding)
im4p_exit = apple_im4p_split(in_file, working_dir, padding + 4)
exit_codes.append(im4p_exit)
else:
make_dirs(working_dir, delete=True)
copy_file(in_file, working_dir, True)
for efi_file in get_path_files(working_dir):
if is_apple_efi(efi_file):
printer(f'Renaming EFI via {is_apple_efi.__module__}...', padding)
name_exit = apple_efi_identify(efi_file, efi_file, padding + 4, True)
exit_codes.append(name_exit)
for named_file in get_path_files(working_dir):
copy_file(named_file, out_path, True)
del_dirs(working_dir)
return sum(exit_codes)
# Parse & Extract Apple EFI PKG packages
def apple_pkg_extract(input_file, extract_path, padding=0):
if not os.path.isfile(input_file):
printer('Error: Could not find input file path!', padding)
return 1
make_dirs(extract_path, delete=True)
xar_path = os.path.join(extract_path, 'xar')
# Decompress PKG > XAR archive with 7-Zip
if is_szip_supported(input_file, padding, args=['-tXAR'], check=True):
if szip_decompress(input_file, xar_path, 'XAR', padding, args=['-tXAR'], check=True) != 0:
return 3
else:
return 2
for xar_file in get_path_files(xar_path):
if path_name(xar_file) == 'Payload':
pbzx_module = is_apple_pbzx.__module__
if is_apple_pbzx(xar_file):
printer(f'Extracting PBZX via {pbzx_module}...', padding + 4)
pbzx_path = get_extract_path(xar_file)
if apple_pbzx_extract(xar_file, pbzx_path, padding + 8) == 0:
printer(f'Succesfull PBZX extraction via {pbzx_module}!', padding + 4)
for pbzx_file in get_path_files(pbzx_path):
if path_name(pbzx_file) == 'UpdateBundle.zip':
if is_szip_supported(pbzx_file, padding + 8, args=['-tZIP'], check=True):
zip_path = get_extract_path(pbzx_file)
if szip_decompress(pbzx_file, zip_path, 'ZIP', padding + 8, args=['-tZIP'], check=True) == 0:
for zip_file in get_path_files(zip_path):
if path_name(path_parent(zip_file)) == 'MacEFI':
printer(path_name(zip_file), padding + 12)
if efi_split_rename(zip_file, extract_path, padding + 16) != 0:
printer(f'Error: Could not split and rename {path_name(zip_file)}!', padding)
return 10
else:
return 9
else:
return 8
break # ZIP found, stop
else:
printer('Error: Could not find "UpdateBundle.zip" file!', padding)
return 7
else:
printer(f'Error: Failed to extract PBZX file via {pbzx_module}!', padding)
return 6
else:
printer(f'Error: Failed to detect file as PBZX via {pbzx_module}!', padding)
return 5
break # Payload found, stop searching
if path_name(xar_file) == 'Scripts':
if is_szip_supported(xar_file, padding + 4, args=['-tGZIP'], check=True):
gzip_path = get_extract_path(xar_file)
if szip_decompress(xar_file, gzip_path, 'GZIP', padding + 4, args=['-tGZIP'], check=True) == 0:
for gzip_file in get_path_files(gzip_path):
if is_szip_supported(gzip_file, padding + 8, args=['-tCPIO'], check=True):
cpio_path = get_extract_path(gzip_file)
if szip_decompress(gzip_file, cpio_path, 'CPIO', padding + 8, args=['-tCPIO'], check=True) == 0:
for cpio_file in get_path_files(cpio_path):
if path_name(path_parent(cpio_file)) == 'EFIPayloads':
printer(path_name(cpio_file), padding + 12)
if efi_split_rename(cpio_file, extract_path, padding + 16) != 0:
printer(f'Error: Could not split and rename {path_name(cpio_file)}!', padding)
return 15
else:
return 14
else:
return 13
else:
return 12
else:
return 11
break # Scripts found, stop searching
else:
printer('Error: Could not find "Payload" or "Scripts" file!', padding)
return 4
del_dirs(xar_path) # Delete temporary/working XAR folder
return 0
if __name__ == '__main__':
BIOSUtility(TITLE, is_apple_pkg, apple_pkg_extract).run_utility()

View file

@ -1,65 +0,0 @@
#!/usr/bin/env python3
"""
Award BIOS Extract
Award BIOS Module Extractor
Copyright (C) 2018-2019 Plato Mavropoulos
http://www.onicos.com/staff/iz/formats/lzh.html
https://ist.uwaterloo.ca/~schepers/formats/LHA.TXT
https://sites.google.com/site/pinczakko/pinczakko-s-guide-to-award-bios-reverse-engineering
"""
print('Award BIOS Module Extractor v1.2\n')
import os
import re
import sys
import subprocess
if len(sys.argv) >= 2 :
# Drag & Drop or CLI
awd_images = sys.argv[1:]
else :
# Folder path
awd_images = []
in_path = input('\nEnter the full folder path: ')
print('\nWorking...')
for root, dirs, files in os.walk(in_path):
for name in files :
awd_images.append(os.path.join(root, name))
pat_lzh = re.compile(br'\x2D\x6C((\x68(([\x30-\x37])|(\x64)))|(\x7A([\x34\x73])))\x2D') # All 11 LZH Method IDs (Award probably used LH0 and LH5 only)
# Create output folder
extr_path = os.path.join(os.getcwd(), 'AWD_Extracted')
if not os.path.exists(extr_path) : os.makedirs(extr_path)
for in_file in awd_images :
file_path = os.path.abspath(in_file)
file_name = os.path.basename(in_file)
file_dir = os.path.dirname(file_path)
file_ext = os.path.splitext(file_path)[1]
match_lzh_list = []
print('\nFile: %s%s' % (file_name, file_ext))
with open(in_file, 'rb') as awd_img : buffer = awd_img.read()
match_lzh_list += pat_lzh.finditer(buffer) # Detect LZH patterns
for match_lzh in match_lzh_list :
hdr_size = buffer[match_lzh.start() - 0x2] # From LZH Tag (0x2+)
comp_size = int.from_bytes(buffer[match_lzh.end():match_lzh.end() + 0x4], 'little') # From LZH Header end
mod_data = buffer[match_lzh.start() - 0x2:match_lzh.start() + hdr_size + comp_size]
with open('mod_temp.bin', 'wb') as lzh_img : lzh_img.write(mod_data)
try :
decomp = subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o%s' % os.path.join(extr_path, file_name), 'mod_temp.bin']) # 7-Zip
except :
print('Error: Could not decompress LZH image at %s!' % file_name)
print(' Make sure that "7z" executable exists!\n')
os.remove('mod_temp.bin') # Remove temporary LZH image
input('\nDone!')

74
Award_BIOS_Extract.py Normal file
View file

@ -0,0 +1,74 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Award BIOS Extract
Award BIOS Module Extractor
Copyright (C) 2018-2022 Plato Mavropoulos
"""
TITLE = 'Award BIOS Module Extractor v2.0_a5'
import os
import sys
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.comp_szip import szip_decompress
from common.path_ops import make_dirs, safe_name, get_extract_path
from common.patterns import PAT_AWARD_LZH
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
# Check if input is Award BIOS image
def is_award_bios(in_file):
in_buffer = file_to_bytes(in_file)
return bool(PAT_AWARD_LZH.search(in_buffer))
# Parse & Extract Award BIOS image
def award_bios_extract(input_file, extract_path, padding=0):
input_buffer = file_to_bytes(input_file)
make_dirs(extract_path, delete=True)
for lzh_match in PAT_AWARD_LZH.finditer(input_buffer):
lzh_type = lzh_match.group(0).decode('utf-8')
lzh_text = f'LZH-{lzh_type.strip("-").upper()}'
lzh_bgn = lzh_match.start()
mod_bgn = lzh_bgn - 0x2
hdr_len = input_buffer[mod_bgn]
mod_len = int.from_bytes(input_buffer[mod_bgn + 0x7:mod_bgn + 0xB], 'little')
mod_end = lzh_bgn + hdr_len + mod_len
mod_bin = input_buffer[mod_bgn:mod_end]
tag_bgn = mod_bgn + 0x16
tag_end = tag_bgn + input_buffer[mod_bgn + 0x15]
tag_txt = input_buffer[tag_bgn:tag_end].decode('utf-8','ignore')
printer(f'{lzh_text} > {tag_txt} [0x{mod_bgn:06X}-0x{mod_end:06X}]', padding)
mod_path = os.path.join(extract_path, safe_name(tag_txt))
lzh_path = f'{mod_path}.lzh'
with open(lzh_path, 'wb') as lzh_file:
lzh_file.write(mod_bin) # Store LZH archive
# 7-Zip returns critical exit code (i.e. 2) if LZH CRC is wrong, do not check result
szip_decompress(lzh_path, extract_path, lzh_text, padding + 4, check=False)
# Manually check if 7-Zip extracted LZH due to its CRC check issue
if os.path.isfile(mod_path):
os.remove(lzh_path) # Successful extraction, delete LZH archive
# Extract any nested LZH archives
if is_award_bios(mod_path):
# Recursively extract nested Award BIOS modules
award_bios_extract(mod_path, get_extract_path(mod_path), padding + 8)
if __name__ == '__main__':
BIOSUtility(TITLE, is_award_bios, award_bios_extract).run_utility()

View file

@ -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)

1067
Dell_PFS_Extract.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,82 +0,0 @@
#!/usr/bin/env python3
"""
Fujitsu SFX Extractor
Fujitsu SFX BIOS Extractor
Copyright (C) 2019-2021 Plato Mavropoulos
"""
print('Fujitsu SFX BIOS Extractor v2.1')
import os
import re
import sys
import subprocess
if len(sys.argv) >= 2 :
# Drag & Drop or CLI
fjsfx_exec = sys.argv[1:]
else :
# Folder path
fjsfx_exec = []
in_path = input('\nEnter the full folder path: ')
print('\nWorking...')
for root, dirs, files in os.walk(in_path):
for name in files :
fjsfx_exec.append(os.path.join(root, name))
# "FjSfxBinay" + Microsoft CAB Header XOR 0xFF (Tag[4] + Res[4] + Size[4] + Res[4] + Offset[4] + Res[4] + Ver[2]) pattern
mscf_pattern = re.compile(br'\x46\x6A\x53\x66\x78\x42\x69\x6E\x61\x79\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL)
for input_file in fjsfx_exec :
file_path = os.path.abspath(input_file)
file_dir = os.path.dirname(file_path)
file_name = os.path.basename(file_path)
print('\nFile: ' + file_name)
# Open Fujitsu SFX Binary Packager executable as mutable bytearray
with open(input_file, 'rb') as in_file : FjSfx = bytearray(in_file.read())
match_mscf = mscf_pattern.search(FjSfx) # Search for Fujitsu Microsoft CAB Header XOR 0xFF pattern
# Check if Microsoft CAB Header XOR 0xFF pattern exists
if match_mscf :
print('\n Detected Obfuscation!')
mscf_start = match_mscf.start() + 0xA # Microsoft CAB Header XOR 0xFF starts after "FjSfxBinay" signature
# Determine the Microsoft CAB image Size
cab_size = int.from_bytes(FjSfx[mscf_start + 0x8:mscf_start + 0xC], 'little') # Get LE XOR-ed CAB Size
xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB Size XOR value
cab_size = cab_size ^ xor_size # Perform XOR 0xFF and get actual CAB Size
print('\n Removing Obfuscation...')
# Determine the Microsoft CAB image Data
cab_data = int.from_bytes(FjSfx[mscf_start:mscf_start + cab_size], 'big') # Get BE XOR-ed CAB Data
xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB Data XOR value
cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB Data
print('\n Extracting...')
with open('fjsfx_temp.cab', 'wb') as cab_file : cab_file.write(cab_data) # Create temporary CAB image
extr_path = os.path.join(file_dir, file_name[:-4], '') # Create CAB image extraction path
try :
decomp = subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + extr_path, 'fjsfx_temp.cab']) # 7-Zip
print('\n Extracted!')
except :
print('\n Error: Could not decompress Microsoft CAB image!')
print(' Make sure that "7z" executable exists!')
os.remove('fjsfx_temp.cab') # Remove temporary CAB image
else :
print('\n Error: This is not a Fujitsu SFX BIOS image!')
continue # Next input file
else :
input('\nDone!')

View file

@ -1,108 +0,0 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Fujitsu UPC Extract
Fujitsu UPC BIOS Extractor
Copyright (C) 2021 Plato Mavropoulos
"""
title = 'Fujitsu UPC BIOS Extractor v1.0'
print('\n' + title) # Print script title
import sys
# Detect Python version
sys_ver = sys.version_info
if sys_ver < (3,7) :
sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1]))
(raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602
sys.exit(1)
import os
import ctypes
import argparse
import traceback
import subprocess
# 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
# 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
upc_parser = argparse.ArgumentParser()
upc_parser.add_argument('upc', type=argparse.FileType('r'), nargs='*')
upc_parser.add_argument('-p', '--path', help='parse files within given folder', type=str)
upc_params = upc_parser.parse_args()
# Get all files within path
def get_files(path) :
inputs = []
for root, _, files in os.walk(path):
for name in files :
inputs.append(os.path.join(root, name))
return inputs
if len(sys.argv) >= 2 :
if bool(upc_params.path) :
upc_exec = get_files(upc_params.path) # CLI with --path
else :
upc_exec = []
for executable in upc_params.upc :
upc_exec.append(executable.name) # Drag & Drop
else :
in_path = input('\nEnter the full folder path: ')
upc_exec = get_files(in_path) # Direct Run
# Process each input Fujitsu UPC BIOS image
for input_file in upc_exec :
input_name,input_extension = os.path.splitext(os.path.basename(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 : upc_data = in_file.read()
if input_extension.upper() != '.UPC' or int.from_bytes(upc_data[0x0:0x4], 'little') + 0x8 != len(upc_data) :
print('\n Error: This is not a Fujitsu UPC BIOS image!')
continue # Next input file
output_file = input_file[:-4] + '.bin' # Decompressed filename
# EFI/Tiano Decompression
try :
subprocess.run(['TianoCompress', '-d', input_file, '-o', output_file, '--uefi', '-q'], check = True, stdout = subprocess.DEVNULL)
if os.path.getsize(output_file) != int.from_bytes(upc_data[0x4:0x8], 'little') : raise Exception('EFI_DECOMP_ERROR')
except :
print('\n Error: Could not extract input file via TianoCompress!')
input(' Make sure that "TianoCompress" executable exists!')
print('\n Extracted Fujitsu UPC BIOS image!')
input('\nDone!')
sys.exit(0)

89
Fujitsu_SFX_Extract.py Normal file
View file

@ -0,0 +1,89 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Fujitsu SFX Extractor
Fujitsu SFX BIOS Extractor
Copyright (C) 2019-2022 Plato Mavropoulos
"""
TITLE = 'Fujitsu SFX BIOS Extractor v3.0_a3'
import os
import sys
# Stop __pycache__ generation
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_FUJITSU_SFX
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
# Check if input is Fujitsu SFX image
def is_fujitsu_sfx(in_file):
buffer = file_to_bytes(in_file)
return bool(PAT_FUJITSU_SFX.search(buffer))
# Extract Fujitsu SFX image
def fujitsu_cabinet(in_file, extract_path, padding=0):
buffer = file_to_bytes(in_file)
match_cab = PAT_FUJITSU_SFX.search(buffer) # Microsoft CAB Header XOR 0xFF
if not match_cab:
return 1
printer('Detected obfuscated CAB archive!', padding)
# Microsoft CAB Header XOR 0xFF starts after "FjSfxBinay" signature
cab_start = match_cab.start() + 0xA
# Determine the Microsoft CAB image size
cab_size = int.from_bytes(buffer[cab_start + 0x8:cab_start + 0xC], 'little') # Get LE XOR-ed CAB size
xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB size XOR value
cab_size ^= xor_size # Perform XOR 0xFF and get actual CAB size
printer('Removing obfuscation...', padding + 4)
# Determine the Microsoft CAB image Data
cab_data = int.from_bytes(buffer[cab_start:cab_start + cab_size], 'big') # Get BE XOR-ed CAB data
xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB data XOR value
cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB data
printer('Extracting archive...', padding + 4)
cab_path = os.path.join(extract_path, 'FjSfxBinay.cab')
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:
os.remove(cab_path) # Successful extraction, delete temporary CAB archive
else:
return 3
else:
return 2
return 0
# Parse & Extract Fujitsu SFX image
def fujitsu_sfx_extract(in_file, extract_path, padding=0):
buffer = file_to_bytes(in_file)
make_dirs(extract_path, delete=True)
if fujitsu_cabinet(buffer, extract_path, padding) == 0:
printer('Successfully Extracted!', padding)
else:
printer('Error: Failed to Extract image!', padding)
return 1
return 0
if __name__ == '__main__':
BIOSUtility(TITLE, is_fujitsu_sfx, fujitsu_sfx_extract).run_utility()

44
Fujitsu_UPC_Extract.py Normal file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Fujitsu UPC Extract
Fujitsu UPC BIOS Extractor
Copyright (C) 2021-2022 Plato Mavropoulos
"""
TITLE = 'Fujitsu UPC BIOS Extractor v2.0_a5'
import os
import sys
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.comp_efi import efi_decompress, is_efi_compressed
from common.path_ops import make_dirs, path_suffixes
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
# Check if input is Fujitsu UPC image
def is_fujitsu_upc(in_file):
in_buffer = file_to_bytes(in_file)
is_ext = path_suffixes(in_file)[-1].upper() == '.UPC' if os.path.isfile(in_file) else True
is_efi = is_efi_compressed(in_buffer)
return is_ext and is_efi
# Parse & Extract Fujitsu UPC image
def fujitsu_upc_extract(input_file, extract_path, padding=0):
make_dirs(extract_path, delete=True)
image_base = os.path.basename(input_file)
image_name = image_base[:-4] if image_base.upper().endswith('.UPC') else image_base
image_path = os.path.join(extract_path, f'{image_name}.bin')
return efi_decompress(input_file, image_path, padding)
if __name__ == '__main__':
BIOSUtility(TITLE, is_fujitsu_upc, fujitsu_upc_extract).run_utility()

217
Insyde_IFD_Extract.py Normal file
View file

@ -0,0 +1,217 @@
#!/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_a11'
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, get_extract_path
from common.patterns import PAT_INSYDE_IFL, PAT_INSYDE_SFX
from common.struct_ops import char, get_struct, uint32_t
from common.system import printer
from common.templates import BIOSUtility
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_padd_len(self):
return self.TotalSize - self.ImageSize
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)
printer(['Padd Size :', f'0x{self._get_padd_len():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, extract_path, padding=0):
input_buffer = file_to_bytes(input_file)
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:]) <= INS_IFL_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 \
or ifl_bgn + INS_IFL_LEN + ifl_hdr.TotalSize > len(input_buffer):
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 127
printer('Detected Insyde iFlash Update image!', padding)
make_dirs(extract_path, delete=True)
exit_codes = []
for insyde_iflash in insyde_iflash_all:
exit_code = 0
ifl_bgn,ifl_hdr = insyde_iflash
img_bgn = ifl_bgn + INS_IFL_LEN
img_end = img_bgn + ifl_hdr.ImageSize
img_bin = input_buffer[img_bgn:img_end]
if len(img_bin) != ifl_hdr.ImageSize:
exit_code = 1
img_val = [ifl_hdr.get_image_tag(), 'bin']
img_tag,img_ext = INS_IFL_IMG.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)
exit_codes.append(exit_code)
return sum(exit_codes)
# 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)
if bytes(INS_SFX_PWD, 'utf-16le') in input_buffer[:match_sfx.start()]:
printer('Detected Insyde iFdPacker > 7-Zip SFX > Password!', padding + 8)
printer(INS_SFX_PWD, padding + 12)
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, args=[f'-p{INS_SFX_PWD}'], check=True):
if szip_decompress(sfx_path, extract_path, 'Insyde iFdPacker > 7-Zip SFX',
padding + 8, args=[f'-p{INS_SFX_PWD}'], 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, get_extract_path(sfx_file), padding + 16)
exit_codes.append(ifd_code)
return sum(exit_codes)
# Insyde iFdPacker known 7-Zip SFX Password
INS_SFX_PWD = 'Y`t~i!L@i#t$U%h^s7A*l(f)E-d=y+S_n?i'
# Insyde iFlash known Image Names
INS_IFL_IMG = {
'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
INS_IFL_LEN = ctypes.sizeof(IflashHeader)
if __name__ == '__main__':
BIOSUtility(TITLE, is_insyde_ifd, insyde_ifd_extract).run_utility()

View file

@ -1,4 +1,4 @@
Copyright (c) 2019-2021 Plato Mavropoulos
Copyright (c) 2019-2022 Plato Mavropoulos
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

View file

@ -1,74 +0,0 @@
#!/usr/bin/env python3
"""
Panasonic BIOS Extract
Panasonic BIOS Update Extractor
Copyright (C) 2018 Plato Mavropoulos
"""
print('Panasonic BIOS Update Extractor v1.0')
import os
import sys
import shutil
import pefile
import subprocess
if len(sys.argv) >= 2 :
# Drag & Drop or CLI
panasonic = sys.argv[1:]
else :
# Folder path
panasonic = []
in_path = input('\nEnter the full folder path: ')
print('\nWorking...')
for root, dirs, files in os.walk(in_path):
for name in files :
panasonic.append(os.path.join(root, name))
for input_file in panasonic :
file_path = os.path.abspath(input_file)
file_name = os.path.basename(input_file)
file_dir = os.path.dirname(file_path)
file_ext = os.path.splitext(file_path)[1]
# Create output folder
extr_path = os.path.join(os.getcwd(), 'RCDATA')
if os.path.exists(extr_path) : shutil.rmtree(extr_path)
os.makedirs(extr_path)
max_size = 0
max_file = None
pe = pefile.PE(input_file) # Analyze Portable Executable (PE)
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries :
# Parse Resource Data directories only
if entry.struct.name == 'IMAGE_RESOURCE_DIRECTORY_ENTRY' and entry.struct.Id == 10 : # RCDATA ID = 10
for resource in entry.directory.entries :
offset = resource.directory.entries[0].data.struct.OffsetToData
size = resource.directory.entries[0].data.struct.Size
data = pe.get_data(offset, size)
file = os.path.join(extr_path, '%X_%X.bin' % (offset, size))
with open(file, 'wb') as out_file : out_file.write(data)
# Remember largest resource (SPI/BIOS)
if size > max_size :
max_size = size
max_file = file
if not max_file :
print('\nError: No Panasonic BIOS Update at %s!' % file_name)
shutil.rmtree(extr_path) # Remove temporary folder
continue # Next input file
# Call Rustam Abdullaev's unpack_lznt1 to extract the LZNT1-compressed SPI/BIOS resource at 0x8 onwards
try :
subprocess.run(['unpack_lznt1', max_file, os.path.join(file_dir, file_name[:-4] + '.bin'), '8'], check = True, stdout = subprocess.DEVNULL)
print('\nExtracted %s via unpack_lznt1' % (file_name[:-4] + '.bin'))
except :
print('\nError: Could not extract %s via unpack_lznt1!' % (file_name[:-4] + '.bin'))
print(' Make sure that "unpack_lznt1.exe" executable exists!')
shutil.rmtree(extr_path) # Remove temporary folder
else :
input('\nDone!')

209
Panasonic_BIOS_Extract.py Normal file
View file

@ -0,0 +1,209 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Panasonic BIOS Extract
Panasonic BIOS Package Extractor
Copyright (C) 2018-2022 Plato Mavropoulos
"""
TITLE = 'Panasonic BIOS Package Extractor v2.0_a10'
import os
import io
import sys
import lznt1
import pefile
# 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, 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 printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
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):
in_buffer = file_to_bytes(in_file)
pe_file = get_pe_file(in_buffer, fast=True)
if not pe_file:
return False
pe_info = get_pe_info(pe_file)
if not pe_info:
return False
if pe_info.get(b'FileDescription',b'').upper() != b'UNPACK UTILITY':
return False
if not PAT_MICROSOFT_CAB.search(in_buffer):
return False
return True
# Search and Extract Panasonic BIOS Package PE CAB archive
def panasonic_cab_extract(buffer, extract_path, padding=0):
pe_path,pe_file,pe_info = [None] * 3
cab_bgn = PAT_MICROSOFT_CAB.search(buffer).start()
cab_len = int.from_bytes(buffer[cab_bgn + 0x8:cab_bgn + 0xC], 'little')
cab_end = cab_bgn + cab_len
cab_bin = buffer[cab_bgn:cab_end]
cab_tag = f'[0x{cab_bgn:06X}-0x{cab_end:06X}]'
cab_path = os.path.join(extract_path, f'CAB_{cab_tag}.cab')
with open(cab_path, 'wb') as cab_file:
cab_file.write(cab_bin) # Store CAB archive
if is_szip_supported(cab_path, padding, check=True):
printer(f'Panasonic BIOS Package > PE > CAB {cab_tag}', padding)
if szip_decompress(cab_path, extract_path, 'CAB', padding + 4, check=True) == 0:
os.remove(cab_path) # Successful extraction, delete CAB archive
else:
return pe_path, pe_file, pe_info
else:
return pe_path, pe_file, pe_info
for file_path in get_path_files(extract_path):
pe_file = get_pe_file(file_path, fast=True)
if pe_file:
pe_info = get_pe_info(pe_file)
if pe_info.get(b'FileDescription',b'').upper() == b'BIOS UPDATE':
pe_path = file_path
break
else:
return pe_path, pe_file, pe_info
return pe_path, pe_file, pe_info
# Extract & Decompress Panasonic BIOS Update PE RCDATA (LZNT1)
def panasonic_res_extract(pe_name, pe_file, extract_path, padding=0):
is_rcdata = False
# When fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed prior to RCDATA Directories
pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']])
# Parse all Resource Data Directories > RCDATA (ID = 10)
for entry in pe_file.DIRECTORY_ENTRY_RESOURCE.entries:
if entry.struct.name == 'IMAGE_RESOURCE_DIRECTORY_ENTRY' and entry.struct.Id == 0xA:
is_rcdata = True
for resource in entry.directory.entries:
res_bgn = resource.directory.entries[0].data.struct.OffsetToData
res_len = resource.directory.entries[0].data.struct.Size
res_end = res_bgn + res_len
res_bin = pe_file.get_data(res_bgn, res_len)
res_tag = f'{pe_name} [0x{res_bgn:06X}-0x{res_end:06X}]'
res_out = os.path.join(extract_path, f'{res_tag}')
printer(res_tag, padding + 4)
try:
res_raw = lznt1.decompress(res_bin[0x8:])
printer('Succesfull LZNT1 decompression via lznt1!', padding + 8)
except Exception:
res_raw = res_bin
printer('Succesfull PE Resource extraction!', padding + 8)
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
if is_ami_pfat(res_raw):
pfat_dir = os.path.join(extract_path, res_tag)
parse_pfat_file(res_raw, pfat_dir, padding + 12)
else:
if is_pe_file(res_raw):
res_ext = 'exe'
elif res_raw.startswith(b'[') and res_raw.endswith((b'\x0D\x0A',b'\x0A')):
res_ext = 'txt'
else:
res_ext = 'bin'
if res_ext == 'txt':
printer(new_line=False)
for line in io.BytesIO(res_raw).readlines():
line_text = line.decode('utf-8','ignore').rstrip()
printer(line_text, padding + 12, new_line=False)
with open(f'{res_out}.{res_ext}', 'wb') as out_file:
out_file.write(res_raw)
return is_rcdata
# Extract Panasonic BIOS Update PE Data when RCDATA is not available
def panasonic_img_extract(pe_name, pe_path, pe_file, extract_path, padding=0):
pe_data = file_to_bytes(pe_path)
sec_bgn = pe_file.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']].VirtualAddress
img_bgn = pe_file.OPTIONAL_HEADER.BaseOfData + pe_file.OPTIONAL_HEADER.SizeOfInitializedData
img_end = sec_bgn or len(pe_data)
img_bin = pe_data[img_bgn:img_end]
img_tag = f'{pe_name} [0x{img_bgn:X}-0x{img_end:X}]'
img_out = os.path.join(extract_path, f'{img_tag}.bin')
printer(img_tag, padding + 4)
with open(img_out, 'wb') as out_img:
out_img.write(img_bin)
printer('Succesfull PE Data extraction!', padding + 8)
return bool(img_bin)
# Parse & Extract Panasonic BIOS Package PE
def panasonic_pkg_extract(input_file, extract_path, padding=0):
input_buffer = file_to_bytes(input_file)
make_dirs(extract_path, delete=True)
pkg_pe_file = get_pe_file(input_buffer, fast=True)
if not pkg_pe_file:
return 2
pkg_pe_info = get_pe_info(pkg_pe_file)
if not pkg_pe_info:
return 3
pkg_pe_name = path_stem(input_file)
printer(f'Panasonic BIOS Package > PE ({pkg_pe_name})\n', padding)
show_pe_info(pkg_pe_info, padding + 4)
upd_pe_path,upd_pe_file,upd_pe_info = panasonic_cab_extract(input_buffer, extract_path, padding + 4)
if not (upd_pe_path and upd_pe_file and upd_pe_info):
return 4
upd_pe_name = safe_name(path_stem(upd_pe_path))
printer(f'Panasonic BIOS Update > PE ({upd_pe_name})\n', padding + 12)
show_pe_info(upd_pe_info, padding + 16)
is_upd_res, is_upd_img = False, False
is_upd_res = panasonic_res_extract(upd_pe_name, upd_pe_file, extract_path, padding + 16)
if not is_upd_res:
is_upd_img = panasonic_img_extract(upd_pe_name, upd_pe_path, upd_pe_file, extract_path, padding + 16)
os.remove(upd_pe_path)
return 0 if is_upd_res or is_upd_img else 1
if __name__ == '__main__':
BIOSUtility(TITLE, is_panasonic_pkg, panasonic_pkg_extract).run_utility()

View file

@ -1,213 +0,0 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Phoenix SCT Extract
Phoenix SCT BIOS Extractor
Copyright (C) 2021 Plato Mavropoulos
"""
title = 'Phoenix SCT BIOS Extractor v1.0'
print('\n' + title) # Print script title
import sys
# Detect Python version
sys_ver = sys.version_info
if sys_ver < (3,7) :
sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1]))
(raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602
sys.exit(1)
import os
import re
import lzma
import shutil
import ctypes
import argparse
import traceback
# 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
# 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
sct_parser = argparse.ArgumentParser()
sct_parser.add_argument('executables', type=argparse.FileType('r'), nargs='*')
sct_parser.add_argument('-p', '--path', help='parse files within given folder', type=str)
sct_params = sct_parser.parse_args()
# Get all files within path
def get_files(path) :
inputs = []
for root, _, files in os.walk(path):
for name in files :
inputs.append(os.path.join(root, name))
return inputs
if len(sys.argv) >= 2 :
if bool(sct_params.path) :
sct_exec = get_files(sct_params.path) # CLI with --path
else :
sct_exec = []
for executable in sct_params.executables :
sct_exec.append(executable.name) # Drag & Drop
else :
in_path = input('\nEnter the full folder path: ')
sct_exec = get_files(in_path) # Direct Run
# 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 SCT_HDR(ctypes.LittleEndianStructure) :
_pack_ = 1
_fields_ = [
('Tag', char*8), # 0x00
('Size', uint32_t), # 0x08
('Count', uint32_t), # 0x0C
# 0x10
]
def sct_print(self) :
print('\n Phoenix SCT Header:\n')
print(' Tag : %s' % self.Tag.decode('utf-8','replace').strip())
print(' Size : 0x%X' % self.Size)
print(' Count : %d' % self.Count)
class SCT_MOD(ctypes.LittleEndianStructure) :
_pack_ = 1
_fields_ = [
('Name', char*256), # 0x000
('Offset', uint32_t), # 0x100
('Size', uint32_t), # 0x104
('Compressed', uint32_t), # 0x108
('Reserved', uint32_t), # 0x10C
# 0x110
]
def sct_print(self) :
print('\n Phoenix SCT Entry:\n')
print(' Name : %s' % self.Name.decode('utf-8','replace').strip())
print(' Offset : 0x%X' % self.Offset)
print(' Size : 0x%X' % self.Size)
print(' Compressed : %s' % ['No','Yes'][self.Compressed])
print(' Reserved : 0x%X' % self.Reserved)
# 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('\n Press enter to exit')
sys.exit(1)
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)
return structure
# Phoenix SCT BIOS Package Pattern ($PACK + Size + Count)
sct_pat = re.compile(br'\x24\x50\x41\x43\x4B\x00{3}..\x00{2}.\x00{3}', re.DOTALL)
# Get common ctypes Structure Sizes
sct_hdr_len = ctypes.sizeof(SCT_HDR)
sct_mod_len = ctypes.sizeof(SCT_MOD)
# Size of dummy/placeholder SCT Entries
sct_dummy_len = 0x200 # Top 2, Names only
# Process each input Phoenix SCT BIOS executable
for input_file in sct_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()
sct_match = sct_pat.search(input_data) # Search for Phoenix SCT BIOS Pattern
# Check if Phoenix SCT BIOS Pattern was found on executable
if not sct_match :
print('\n Error: This is not a Phoenix SCT BIOS executable!')
continue # Next input file
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
print('\n Phoenix SecureCore Technology')
sct_hdr = get_struct(input_data, sct_match.start(), SCT_HDR) # Parse SCT Header Structure
sct_hdr.sct_print() # Print SCT Header Info
# Check if reported SCT Header Size matches manual SCT Entry Count calculation
if sct_hdr.Size != sct_hdr_len + sct_dummy_len + sct_hdr.Count * sct_mod_len :
input('\n Error: This Phoenix SCT BIOS image is corrupted!')
continue # Next input file
# Store all SCT $PACK Data w/o initial dummy/placeholder Entries
pack_data = input_data[sct_match.end() + sct_dummy_len:sct_match.start() + sct_hdr.Size]
# Parse each SCT Entry
for e_idx in range(sct_hdr.Count) :
mod_hdr = get_struct(pack_data, e_idx * sct_mod_len, SCT_MOD) # Parse SCT Entry Structure
mod_hdr.sct_print() # Print SCT Entry Info
mod_data = input_data[mod_hdr.Offset:mod_hdr.Offset + mod_hdr.Size] # Store SCT Entry Raw Data
# Check if SCT Entry Raw Data is complete
if len(mod_data) != mod_hdr.Size :
input('\n Error: This Phoenix SCT BIOS image is incomplete!')
# Store SCT Entry LZMA Decompressed Data, when applicable
if mod_hdr.Compressed : mod_data = lzma.LZMADecompressor().decompress(mod_data)
# Replace common Windows reserved/illegal filename characters
mod_fname = re.sub(r'[\\/*?:"<>|]', '_', mod_hdr.Name.decode('utf-8','replace').strip())
with open(os.path.join(output_path, mod_fname), 'wb') as out : out.write(mod_data) # Store SCT Entry Data/File
print('\n Extracted Phoenix SCT BIOS executable!')
input('\nDone!')
sys.exit(0)

243
Phoenix_TDK_Extract.py Normal file
View file

@ -0,0 +1,243 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Phoenix TDK Extract
Phoenix TDK Packer Extractor
Copyright (C) 2021-2022 Plato Mavropoulos
"""
TITLE = 'Phoenix TDK Packer Extractor v2.0_a10'
import os
import sys
import lzma
import ctypes
# Stop __pycache__ generation
sys.dont_write_bytecode = True
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_MICROSOFT_MZ, PAT_MICROSOFT_PE, PAT_PHOENIX_TDK
from common.struct_ops import char, get_struct, uint32_t
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
class PhoenixTdkHeader(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('Tag', char*8), # 0x00
('Size', uint32_t), # 0x08
('Count', uint32_t), # 0x0C
# 0x10
]
def _get_tag(self):
return self.Tag.decode('utf-8','ignore').strip()
def struct_print(self, p):
printer(['Tag :', self._get_tag()], p, False)
printer(['Size :', f'0x{self.Size:X}'], p, False)
printer(['Entries:', self.Count], p, False)
class PhoenixTdkEntry(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('Name', char*256), # 0x000
('Offset', uint32_t), # 0x100
('Size', uint32_t), # 0x104
('Compressed', uint32_t), # 0x108
('Reserved', uint32_t), # 0x10C
# 0x110
]
COMP = {0: 'None', 1: 'LZMA'}
def __init__(self, mz_base, *args, **kwargs):
super().__init__(*args, **kwargs)
self.Base = mz_base
def get_name(self):
return self.Name.decode('utf-8','replace').strip()
def get_offset(self):
return self.Base + self.Offset
def get_compression(self):
return self.COMP.get(self.Compressed, f'Unknown ({self.Compressed})')
def struct_print(self, p):
printer(['Name :', self.get_name()], p, False)
printer(['Offset :', f'0x{self.get_offset():X}'], p, False)
printer(['Size :', f'0x{self.Size:X}'], p, False)
printer(['Compression:', self.get_compression()], p, False)
printer(['Reserved :', f'0x{self.Reserved:X}'], p, False)
# Get Phoenix TDK Executable (MZ) Base Offset
def get_tdk_base(in_buffer, pack_off):
tdk_base_off = None # Initialize Phoenix TDK Base MZ Offset
# Scan input file for all Microsoft executable patterns (MZ) before TDK Header Offset
mz_all = [mz for mz in PAT_MICROSOFT_MZ.finditer(in_buffer) if mz.start() < pack_off]
# Phoenix TDK Header structure is an index table for all TDK files
# Each TDK file is referenced from the TDK Packer executable base
# The TDK Header is always at the end of the TDK Packer executable
# Thus, prefer the TDK Packer executable (MZ) closest to TDK Header
# For speed, check MZ closest to (or at) 0x0 first (expected input)
mz_ord = [mz_all[0]] + list(reversed(mz_all[1:]))
# Parse each detected MZ
for mz in mz_ord:
mz_off = mz.start()
# MZ (DOS) > PE (NT) image Offset is found at offset 0x3C-0x40 relative to MZ base
pe_off = mz_off + int.from_bytes(in_buffer[mz_off + 0x3C:mz_off + 0x40], 'little')
# Skip MZ (DOS) with bad PE (NT) image Offset
if pe_off == mz_off or pe_off >= pack_off:
continue
# Check if potential MZ > PE image magic value is valid
if PAT_MICROSOFT_PE.search(in_buffer[pe_off:pe_off + 0x4]):
try:
# Parse detected MZ > PE > Image, quickly (fast_load)
pe_file = get_pe_file(in_buffer[mz_off:], fast=True)
# Parse detected MZ > PE > Info
pe_info = get_pe_info(pe_file)
# Parse detected MZ > PE > Info > Product Name
pe_name = pe_info.get(b'ProductName',b'')
except Exception:
# Any error means no MZ > PE > Info > Product Name
pe_name = b''
# Check for valid Phoenix TDK Packer PE > Product Name
# Expected value is "TDK Packer (Extractor for Windows)"
if pe_name.upper().startswith(b'TDK PACKER'):
# Set TDK Base Offset to valid TDK Packer MZ offset
tdk_base_off = mz_off
# Stop parsing detected MZ once TDK Base Offset is found
if tdk_base_off is not None:
break
else:
# No TDK Base Offset could be found, assume 0x0
tdk_base_off = 0x0
return tdk_base_off
# Scan input buffer for valid Phoenix TDK image
def get_phoenix_tdk(in_buffer):
# Scan input buffer for Phoenix TDK pattern
tdk_match = PAT_PHOENIX_TDK.search(in_buffer)
if not tdk_match:
return None, None
# Set Phoenix TDK Header ($PACK) Offset
tdk_pack_off = tdk_match.start()
# Get Phoenix TDK Executable (MZ) Base Offset
tdk_base_off = get_tdk_base(in_buffer, tdk_pack_off)
return tdk_base_off, tdk_pack_off
# Check if input contains valid Phoenix TDK image
def is_phoenix_tdk(in_file):
buffer = file_to_bytes(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_file, extract_path, padding=0):
exit_code = 0
input_buffer = file_to_bytes(input_file)
make_dirs(extract_path, delete=True)
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)
# Print TDK Header structure info
printer('Phoenix TDK Header:\n', padding + 4)
tdk_hdr.struct_print(padding + 8)
# Check if reported TDK Header Size matches manual TDK Entry Count calculation
if tdk_hdr.Size != TDK_HDR_LEN + TDK_DUMMY_LEN + tdk_hdr.Count * TDK_MOD_LEN:
printer('Error: Phoenix TDK Header Size & Entry Count mismatch!\n', padding + 8, pause=True)
exit_code = 1
# Store TDK Entries offset after the placeholder data
entries_off = pack_off + TDK_HDR_LEN + TDK_DUMMY_LEN
# Parse and extract each TDK Header Entry
for entry_index in range(tdk_hdr.Count):
# Parse TDK Entry structure
tdk_mod = get_struct(input_buffer, entries_off + entry_index * TDK_MOD_LEN, PhoenixTdkEntry, [base_off])
# Print TDK Entry structure info
printer(f'Phoenix TDK Entry ({entry_index + 1}/{tdk_hdr.Count}):\n', padding + 8)
tdk_mod.struct_print(padding + 12)
# Get TDK Entry raw data Offset (TDK Base + Entry Offset)
mod_off = tdk_mod.get_offset()
# Check if TDK Entry raw data Offset is valid
if mod_off >= len(input_buffer):
printer('Error: Phoenix TDK Entry > Offset is out of bounds!\n', padding + 12, pause=True)
exit_code = 2
# Store TDK Entry raw data (relative to TDK Base, not TDK Header)
mod_data = input_buffer[mod_off:mod_off + tdk_mod.Size]
# Check if TDK Entry raw data is complete
if len(mod_data) != tdk_mod.Size:
printer('Error: Phoenix TDK Entry > Data is truncated!\n', padding + 12, pause=True)
exit_code = 3
# Check if TDK Entry Reserved is present
if tdk_mod.Reserved:
printer('Error: Phoenix TDK Entry > Reserved is not empty!\n', padding + 12, pause=True)
exit_code = 4
# Decompress TDK Entry raw data, when applicable (i.e. LZMA)
if tdk_mod.get_compression() == 'LZMA':
try:
mod_data = lzma.LZMADecompressor().decompress(mod_data)
except Exception:
printer('Error: Phoenix TDK Entry > LZMA decompression failed!\n', padding + 12, pause=True)
exit_code = 5
# Generate TDK Entry file name, avoid crash if Entry data is bad
mod_name = tdk_mod.get_name() or f'Unknown_{entry_index + 1:02d}.bin'
# Generate TDK Entry file data output path
mod_file = os.path.join(extract_path, safe_name(mod_name))
# Account for potential duplicate file names
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)
return exit_code
# Get ctypes Structure Sizes
TDK_HDR_LEN = ctypes.sizeof(PhoenixTdkHeader)
TDK_MOD_LEN = ctypes.sizeof(PhoenixTdkEntry)
# Set placeholder TDK Entries Size
TDK_DUMMY_LEN = 0x200
if __name__ == '__main__':
BIOSUtility(TITLE, is_phoenix_tdk, phoenix_tdk_extract).run_utility()

View file

@ -1,174 +0,0 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Portwell EFI Extract
Portwell EFI BIOS Extractor
Copyright (C) 2021 Plato Mavropoulos
"""
title = 'Portwell EFI BIOS Extractor v1.0'
print('\n' + title) # Print script title
import sys
# Detect Python version
sys_ver = sys.version_info
if sys_ver < (3,7) :
sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1]))
(raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602
sys.exit(1)
import os
import pefile
import shutil
import ctypes
import argparse
import traceback
import subprocess
# 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
# 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
efi_parser = argparse.ArgumentParser()
efi_parser.add_argument('efi', type=argparse.FileType('r'), nargs='*')
efi_parser.add_argument('-p', '--path', help='parse files within given folder', type=str)
efi_params = efi_parser.parse_args()
# Get all files within path
def get_files(path) :
inputs = []
for root, _, files in os.walk(path):
for name in files :
inputs.append(os.path.join(root, name))
return inputs
if len(sys.argv) >= 2 :
if bool(efi_params.path) :
efi_exec = get_files(efi_params.path) # CLI with --path
else :
efi_exec = []
for executable in efi_params.efi :
efi_exec.append(executable.name) # Drag & Drop
else :
in_path = input('\nEnter the full folder path: ')
efi_exec = get_files(in_path) # Direct Run
# Portwell UEFI Unpacker File Names (v1.1 - v1.2)
file_names = {0 : 'Flash.efi', 1 : 'Fparts.txt', 2 : 'Update.nsh', 3 : 'Temp.bin', 4 : 'SaveDmiData.efi'}
# Process each input Portwell EFI Update Package
for input_file in efi_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()
try :
assert input_data[0x0:0x2] == b'\x4D\x5A' # EFI images start with DOS Header MZ
pe = pefile.PE(input_file, fast_load=True) # Analyze EFI Portable Executable (PE)
payload_data = input_data[pe.OPTIONAL_HEADER.SizeOfImage:] # Skip EFI executable (pylint: disable=E1101)
assert payload_data[0x0:0x4] == b'\x3C\x55\x55\x3E' # Portwell EFI files start with <UU>
except :
print('\n Error: This is not a Portwell EFI Update Package!')
continue # Next input file
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
pack_tag = 'UEFI Unpacker' # Initialize Portwell UEFI Unpacker tag
# Get Portwell UEFI Unpacker tag
for s in pe.sections :
if s.Name.startswith(b'.data') : # Unpacker Tag, Version, Strings etc are found in .data PE section
# Decode any valid UTF-16 .data PE section info to a parsable text buffer
info = input_data[s.PointerToRawData:s.PointerToRawData + s.SizeOfRawData].decode('utf-16','ignore')
# Search .data for UEFI Unpacker tag
pack_tag_off = info.find('UEFI Unpacker')
if pack_tag_off != -1 :
pack_tag_len = info[pack_tag_off:].find('=')
if pack_tag_len != -1 :
# Found full UEFI Unpacker tag, store and slightly beautify the resulting text
pack_tag = info[pack_tag_off:pack_tag_off + pack_tag_len].strip().replace(' ',' ').replace('<',' <')
break # Found PE .data section, skip the rest
print('\n Portwell %s' % pack_tag) # Print Portwell UEFI Unpacker tag
efi_files = payload_data.split(b'\x3C\x55\x55\x3E')[1:] # Split EFI Payload into <UU> file chunks
# Parse each EFI Payload File
for i in range(len(efi_files)) :
file_data = efi_files[i] # Store EFI File data
if len(file_data) == 0 or file_data == b'NULL' : continue # Skip empty/unused files
is_known = i in file_names # Check if EFI file is known & Store result
file_name = file_names[i] if is_known else 'Unknown_%d.bin' % i # Assign Name to EFI file
print('\n %s' % file_name) # Print EFI file name, indicate progress
if not is_known : input('\n Note: Detected unknown Portwell EFI file with ID %d!' % i) # Report new EFI files
file_path = os.path.join(output_path, file_name) # Store EFI file output path
with open(file_path, 'wb') as o : o.write(file_data) # Store EFI file data to drive
# Attempt to detect EFI/Tiano Compression & Decompress when applicable
if int.from_bytes(file_data[0x0:0x4], 'little') + 0x8 == len(file_data) :
try :
comp_fname = file_path + '.temp' # Store temporary compressed file name
os.replace(file_path, comp_fname) # Rename initial/compressed file
subprocess.run(['TianoCompress', '-d', comp_fname, '-o', file_path, '--uefi', '-q'], check = True, stdout = subprocess.DEVNULL)
if os.path.getsize(file_path) != int.from_bytes(file_data[0x4:0x8], 'little') : raise Exception('EFI_DECOMP_ERROR')
os.remove(comp_fname) # Successful decompression, delete initial/compressed file
except :
print('\n Error: Could not extract file %s via TianoCompress!' % file_name)
input(' Make sure that "TianoCompress" executable exists!')
print('\n Extracted Portwell EFI Update Package!')
input('\nDone!')
sys.exit(0)

136
Portwell_EFI_Extract.py Normal file
View file

@ -0,0 +1,136 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Portwell EFI Extract
Portwell EFI Update Extractor
Copyright (C) 2021-2022 Plato Mavropoulos
"""
TITLE = 'Portwell EFI Update Extractor v2.0_a12'
import os
import sys
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.comp_efi import efi_decompress, is_efi_compressed
from common.path_ops import make_dirs, safe_name
from common.pe_ops import get_pe_file
from common.patterns import PAT_MICROSOFT_MZ, PAT_PORTWELL_EFI
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
FILE_NAMES = {
0 : 'Flash.efi',
1 : 'Fparts.txt',
2 : 'Update.nsh',
3 : 'Temp.bin',
4 : 'SaveDmiData.efi'
}
# Check if input is Portwell EFI executable
def is_portwell_efi(in_file):
in_buffer = file_to_bytes(in_file)
try:
pe_buffer = get_portwell_pe(in_buffer)[1]
except Exception:
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)
# Get PE of Portwell EFI executable
def get_portwell_pe(in_buffer):
pe_file = get_pe_file(in_buffer, fast=True) # Analyze EFI Portable Executable (PE)
pe_data = in_buffer[pe_file.OPTIONAL_HEADER.SizeOfImage:] # Skip EFI executable (pylint: disable=E1101)
return pe_file, pe_data
# Parse & Extract Portwell UEFI Unpacker
def portwell_efi_extract(input_file, extract_path, padding=0):
efi_files = [] # Initialize EFI Payload file chunks
input_buffer = file_to_bytes(input_file)
make_dirs(extract_path, delete=True)
pe_file,pe_data = get_portwell_pe(input_buffer)
efi_title = get_unpacker_tag(input_buffer, pe_file)
printer(efi_title, padding)
# Split EFI Payload into <UU> file chunks
efi_list = list(PAT_PORTWELL_EFI.finditer(pe_data))
for idx,val in enumerate(efi_list):
efi_bgn = val.end()
efi_end = len(pe_data) if idx == len(efi_list) - 1 else efi_list[idx + 1].start()
efi_files.append(pe_data[efi_bgn:efi_end])
parse_efi_files(extract_path, efi_files, padding)
# Get Portwell UEFI Unpacker tag
def get_unpacker_tag(input_buffer, pe_file):
unpacker_tag_txt = 'UEFI Unpacker'
for pe_section in pe_file.sections:
# Unpacker Tag, Version, Strings etc are found in .data PE section
if pe_section.Name.startswith(b'.data'):
pe_data_bgn = pe_section.PointerToRawData
pe_data_end = pe_data_bgn + pe_section.SizeOfRawData
# Decode any valid UTF-16 .data PE section info to a parsable text buffer
pe_data_txt = input_buffer[pe_data_bgn:pe_data_end].decode('utf-16','ignore')
# Search .data for UEFI Unpacker tag
unpacker_tag_bgn = pe_data_txt.find(unpacker_tag_txt)
if unpacker_tag_bgn != -1:
unpacker_tag_len = pe_data_txt[unpacker_tag_bgn:].find('=')
if unpacker_tag_len != -1:
unpacker_tag_end = unpacker_tag_bgn + unpacker_tag_len
unpacker_tag_raw = pe_data_txt[unpacker_tag_bgn:unpacker_tag_end]
# Found full UEFI Unpacker tag, store and slightly beautify the resulting text
unpacker_tag_txt = unpacker_tag_raw.strip().replace(' ',' ').replace('<',' <')
break # Found PE .data section, skip the rest
return unpacker_tag_txt
# 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
file_name = FILE_NAMES.get(file_index, f'Unknown_{file_index}.bin') # Assign Name to EFI file
printer(f'[{file_index}] {file_name}', padding + 4) # Print EFI file name, indicate progress
if file_name.startswith('Unknown_'):
printer(f'Note: Detected new Portwell EFI file ID {file_index}!', padding + 8, pause=True) # Report new EFI files
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
# Attempt to detect EFI compression & decompress when applicable
if is_efi_compressed(file_data):
comp_fname = file_path + '.temp' # Store temporary compressed file name
os.replace(file_path, comp_fname) # Rename initial/compressed file
if efi_decompress(comp_fname, file_path, padding + 8) == 0:
os.remove(comp_fname) # Successful decompression, delete compressed file
if __name__ == '__main__':
BIOSUtility(TITLE, is_portwell_efi, portwell_efi_extract).run_utility()

1008
README.md

File diff suppressed because it is too large Load diff

63
Toshiba_COM_Extract.py Normal file
View file

@ -0,0 +1,63 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Toshiba COM Extract
Toshiba BIOS COM Extractor
Copyright (C) 2018-2022 Plato Mavropoulos
"""
TITLE = 'Toshiba BIOS COM Extractor v2.0_a4'
import os
import sys
import subprocess
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.externals import get_comextract_path
from common.path_ops import make_dirs, path_stem, path_suffixes
from common.patterns import PAT_TOSHIBA_COM
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
# Check if input is Toshiba BIOS COM image
def is_toshiba_com(in_file):
buffer = file_to_bytes(in_file)
is_ext = path_suffixes(in_file)[-1].upper() == '.COM' if os.path.isfile(in_file) else True
is_com = PAT_TOSHIBA_COM.search(buffer)
return is_ext and is_com
# Parse & Extract Toshiba BIOS COM image
def toshiba_com_extract(input_file, extract_path, padding=0):
if not os.path.isfile(input_file):
printer('Error: Could not find input file path!', padding)
return 1
make_dirs(extract_path, delete=True)
output_name = path_stem(input_file)
output_file = os.path.join(extract_path, f'{output_name}.bin')
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')
except Exception:
printer(f'Error: ToshibaComExtractor could not extract file {input_file}!', padding)
return 2
printer(f'Succesfull {output_name} extraction via ToshibaComExtractor!', padding)
return 0
if __name__ == '__main__':
BIOSUtility(TITLE, is_toshiba_com, toshiba_com_extract).run_utility()

View file

@ -1,126 +0,0 @@
#!/usr/bin/env python3
"""
VAIO Package Extractor
VAIO Packaging Manager Extractor
Copyright (C) 2019-2020 Plato Mavropoulos
"""
print('VAIO Packaging Manager Extractor v2.0')
import os
import re
import sys
import subprocess
if len(sys.argv) >= 2 :
# Drag & Drop or CLI
vaio_exec = sys.argv[1:]
else :
# Folder path
vaio_exec = []
in_path = input('\nEnter the full folder path: ')
print('\nWorking...')
for root, dirs, files in os.walk(in_path):
for name in files :
vaio_exec.append(os.path.join(root, name))
# Microsoft CAB Header XOR 0xFF (Tag[4] + Res[4] + Size[4] + Res[4] + Offset[4] + Res[4] + Ver[2]) pattern
mscf_pattern = re.compile(br'\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL)
# VAIO Packaging Manager Configuration file ("[Setting]" + Windows_new_line) pattern
vaio_pattern = re.compile(br'\x5B\x53\x65\x74\x74\x69\x6E\x67\x5D\x0D\x0A')
# VAIO Packaging Manager Configuration file entry "UseVAIOCheck" pattern
check_pattern = re.compile(br'\x0A\x55\x73\x65\x56\x41\x49\x4F\x43\x68\x65\x63\x6B\x3D')
# VAIO Packaging Manager Configuration file entry "ExtractPathByUser" pattern
path_pattern = re.compile(br'\x0A\x45\x78\x74\x72\x61\x63\x74\x50\x61\x74\x68\x42\x79\x55\x73\x65\x72\x3D')
for input_file in vaio_exec :
file_path = os.path.abspath(input_file)
file_dir = os.path.dirname(file_path)
file_name = os.path.basename(file_path)
print('\nFile: ' + file_name)
# Open Locked VAIO Packaging Manager executable as mutable bytearray
with open(input_file, 'rb') as in_file : vaio_data = bytearray(in_file.read())
match_mscf = mscf_pattern.search(vaio_data) # Search for Microsoft CAB Header XOR 0xFF pattern
match_vaio = vaio_pattern.search(vaio_data) if not match_mscf else None # Search for VAIO Packaging Manager Configuration file
# Check if Microsoft CAB Header XOR 0xFF pattern exists
if match_mscf :
print('\n Detected Obfuscation!')
# Determine the Microsoft CAB image Size
cab_size = int.from_bytes(vaio_data[match_mscf.start() + 0x8:match_mscf.start() + 0xC], 'little') # Get LE XOR-ed CAB Size
xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB Size XOR value
cab_size = cab_size ^ xor_size # Perform XOR 0xFF and get actual CAB Size
print('\n Removing Obfuscation...')
# Determine the Microsoft CAB image Data
cab_data = int.from_bytes(vaio_data[match_mscf.start():match_mscf.start() + cab_size], 'big') # Get BE XOR-ed CAB Data
xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB Data XOR value
cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB Data
print('\n Extracting...')
with open('vaio_temp.cab', 'wb') as cab_file : cab_file.write(cab_data) # Create temporary CAB image
extr_path = os.path.join(file_dir, file_name[:-4], '') # Create CAB image extraction path
try :
decomp = subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + extr_path, 'vaio_temp.cab']) # 7-Zip
print('\n Extracted!')
except :
print('\n Error: Could not decompress Microsoft CAB image!')
print(' Make sure that "7z" executable exists!')
os.remove('vaio_temp.cab') # Remove temporary CAB image
# Check if VAIO Packaging Manager Configuration file pattern exists
elif match_vaio :
print('\n Error: Failed to Extract, attempting to Unlock instead...')
# Initialize VAIO Package Configuration file variables (assume overkill size of 0x500)
info_start, info_end, val_false, val_true = [match_vaio.start(), match_vaio.start() + 0x500, b'', b'']
# Get VAIO Package Configuration file info, split at new_line and stop at payload DOS header (EOF)
vaio_info = vaio_data[info_start:info_end].split(b'\x0D\x0A\x4D\x5A')[0].replace(b'\x0D',b'').split(b'\x0A')
# Determine VAIO Package Configuration file True & False values
for info in vaio_info :
if info.startswith(b'ExtractPathByUser=') : val_false = bytearray(b'0' if info[18:] in (b'0',b'1') else info[18:]) # Should be 0/No/False
if info.startswith(b'UseCompression=') : val_true = bytearray(b'1' if info[15:] in (b'0',b'1') else info[15:]) # Should be 1/Yes/True
else :
if val_false == val_true or not val_false or not val_true :
print('\n Error: Could not determine True/False values!')
print(' Please report this VAIO Packaging Manager!')
continue # Next input file
# Find and replace UseVAIOCheck entry from 1/Yes/True to 0/No/False
UseVAIOCheck = check_pattern.search(vaio_data[info_start:])
if UseVAIOCheck : vaio_data[info_start + UseVAIOCheck.end():info_start + UseVAIOCheck.end() + len(val_false)] = val_false
else : print('\n Error: Could not find UseVAIOCheck entry!')
# Find and replace ExtractPathByUser entry from 0/No/False to 1/Yes/True
ExtractPathByUser = path_pattern.search(vaio_data[info_start:])
if ExtractPathByUser : vaio_data[info_start + ExtractPathByUser.end():info_start + ExtractPathByUser.end() + len(val_false)] = val_true
else : print('\n Error: Could not find ExtractPathByUser entry!')
# Store Unlocked VAIO Packaging Manager executable
if UseVAIOCheck and ExtractPathByUser :
with open(os.path.join(file_dir, file_name + '_Unlocked.exe'), 'wb') as unl_file : unl_file.write(vaio_data)
print('\n Unlocked!')
else :
print('\n Error: No VAIO Packaging Manager found!')
continue # Next input file
else :
input('\nDone!')

147
VAIO_Package_Extract.py Normal file
View file

@ -0,0 +1,147 @@
#!/usr/bin/env python3
#coding=utf-8
"""
VAIO Package Extractor
VAIO Packaging Manager Extractor
Copyright (C) 2019-2022 Plato Mavropoulos
"""
TITLE = 'VAIO Packaging Manager Extractor v3.0_a8'
import os
import sys
# Stop __pycache__ generation
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_CAB, PAT_VAIO_CFG, PAT_VAIO_CHK, PAT_VAIO_EXT
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
# Check if input is VAIO Packaging Manager
def is_vaio_pkg(in_file):
buffer = file_to_bytes(in_file)
return bool(PAT_VAIO_CFG.search(buffer))
# Extract VAIO Packaging Manager executable
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
printer('Detected obfuscated CAB archive!', padding)
# Determine the Microsoft CAB image size
cab_size = int.from_bytes(buffer[match_cab.start() + 0x8:match_cab.start() + 0xC], 'little') # Get LE XOR-ed CAB size
xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB size XOR value
cab_size ^= xor_size # Perform XOR 0xFF and get actual CAB size
printer('Removing obfuscation...', padding + 4)
# Determine the Microsoft CAB image Data
cab_data = int.from_bytes(buffer[match_cab.start():match_cab.start() + cab_size], 'big') # Get BE XOR-ed CAB data
xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB data XOR value
cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB data
printer('Extracting archive...', padding + 4)
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
if is_szip_supported(cab_path, padding + 8, check=True):
if szip_decompress(cab_path, extract_path, 'CAB', padding + 8, check=True) == 0:
os.remove(cab_path) # Successful extraction, delete temporary CAB archive
else:
return 3
else:
return 2
return 0
# Unlock VAIO Packaging Manager executable
def vaio_unlock(name, buffer, extract_path, padding=0):
match_cfg = PAT_VAIO_CFG.search(buffer)
if not match_cfg:
return 1
printer('Attempting to Unlock executable!', padding)
# Initialize VAIO Package Configuration file variables (assume overkill size of 0x500)
cfg_bgn,cfg_end,cfg_false,cfg_true = [match_cfg.start(), match_cfg.start() + 0x500, b'', b'']
# Get VAIO Package Configuration file info, split at new_line and stop at payload DOS header (EOF)
cfg_info = buffer[cfg_bgn:cfg_end].split(b'\x0D\x0A\x4D\x5A')[0].replace(b'\x0D',b'').split(b'\x0A')
printer('Retrieving True/False values...', padding + 4)
# Determine VAIO Package Configuration file True & False values
for info in cfg_info:
if info.startswith(b'ExtractPathByUser='):
cfg_false = bytearray(b'0' if info[18:] in (b'0',b'1') else info[18:]) # Should be 0/No/False
if info.startswith(b'UseCompression='):
cfg_true = bytearray(b'1' if info[15:] in (b'0',b'1') else info[15:]) # Should be 1/Yes/True
# Check if valid True/False values have been retrieved
if cfg_false == cfg_true or not cfg_false or not cfg_true:
printer('Error: Could not retrieve True/False values!', padding + 8)
return 2
printer('Adjusting UseVAIOCheck entry...', padding + 4)
# Find and replace UseVAIOCheck entry from 1/Yes/True to 0/No/False
vaio_check = PAT_VAIO_CHK.search(buffer[cfg_bgn:])
if vaio_check:
buffer[cfg_bgn + vaio_check.end():cfg_bgn + vaio_check.end() + len(cfg_true)] = cfg_false
else:
printer('Error: Could not find entry UseVAIOCheck!', padding + 8)
return 3
printer('Adjusting ExtractPathByUser entry...', padding + 4)
# Find and replace ExtractPathByUser entry from 0/No/False to 1/Yes/True
user_path = PAT_VAIO_EXT.search(buffer[cfg_bgn:])
if user_path:
buffer[cfg_bgn + user_path.end():cfg_bgn + user_path.end() + len(cfg_false)] = cfg_true
else:
printer('Error: Could not find entry ExtractPathByUser!', padding + 8)
return 4
printer('Storing unlocked executable...', padding + 4)
# 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)
return 0
# Parse & Extract or Unlock VAIO Packaging Manager
def vaio_pkg_extract(input_file, extract_path, padding=0):
input_buffer = file_to_bytes(input_file)
input_name = os.path.basename(input_file)
make_dirs(extract_path, delete=True)
if vaio_cabinet(input_name, input_buffer, extract_path, padding) == 0:
printer('Successfully Extracted!', padding)
elif vaio_unlock(input_name, bytearray(input_buffer), extract_path, padding) == 0:
printer('Successfully Unlocked!', padding)
else:
printer('Error: Failed to Extract or Unlock executable!', padding)
return 1
return 0
if __name__ == '__main__':
BIOSUtility(TITLE, is_vaio_pkg, vaio_pkg_extract).run_utility()

25
common/checksums.py Normal file
View file

@ -0,0 +1,25 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
"""
# Get Checksum 16-bit
def get_chk_16(data, value=0, order='little'):
for idx in range(0, len(data), 2):
# noinspection PyTypeChecker
value += int.from_bytes(data[idx:idx + 2], order)
value &= 0xFFFF
return value
# Get Checksum 8-bit XOR
def get_chk_8_xor(data, value=0):
for byte in data:
value ^= byte
value ^= 0x0
return value

57
common/comp_efi.py Normal file
View file

@ -0,0 +1,57 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
"""
import os
import subprocess
from common.path_ops import project_root, safe_path
from common.system import get_os_ver, printer
def get_compress_sizes(data):
size_compress = int.from_bytes(data[0x0:0x4], 'little')
size_original = int.from_bytes(data[0x4:0x8], 'little')
return size_compress, size_original
def is_efi_compressed(data, strict=True):
size_comp,size_orig = get_compress_sizes(data)
check_diff = size_comp < size_orig
if strict:
check_size = size_comp + 0x8 == len(data)
else:
check_size = size_comp + 0x8 <= len(data)
return check_diff and check_size
# Get TianoCompress path
def get_tiano_path():
exec_name = f'TianoCompress{".exe" if get_os_ver()[1] else ""}'
return safe_path(project_root(), ['external',exec_name])
# EFI/Tiano Decompression via TianoCompress
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())
if os.path.getsize(out_path) != size_orig:
raise Exception('EFI_DECOMPRESS_ERROR')
except Exception:
if not silent:
printer(f'Error: TianoCompress could not extract file {in_path}!', padding)
return 1
if not silent:
printer('Succesfull EFI decompression via TianoCompress!', padding)
return 0

72
common/comp_szip.py Normal file
View file

@ -0,0 +1,72 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
"""
import os
import subprocess
from common.path_ops import project_root, safe_path
from common.system import get_os_ver, printer
# Get 7-Zip path
def get_szip_path():
exec_name = '7z.exe' if get_os_ver()[1] else '7zzs'
return safe_path(project_root(), ['external',exec_name])
# Check 7-Zip bad exit codes (0 OK, 1 Warning)
def check_bad_exit_code(exit_code):
if exit_code not in (0,1):
raise Exception(f'BAD_EXIT_CODE_{exit_code}')
# Check if file is 7-Zip supported
def is_szip_supported(in_path, padding=0, args=None, check=False, silent=False):
try:
if args is None:
args = []
szip_c = [get_szip_path(), 't', in_path, *args, '-bso0', '-bse0', '-bsp0']
szip_t = subprocess.run(szip_c, check=False)
if check:
check_bad_exit_code(szip_t.returncode)
except Exception:
if not silent:
printer(f'Error: 7-Zip could not check support for file {in_path}!', padding)
return False
return True
# Archive decompression via 7-Zip
def szip_decompress(in_path, out_path, in_name, padding=0, args=None, check=False, silent=False):
if not in_name:
in_name = 'archive'
try:
if args is None:
args = []
szip_c = [get_szip_path(), 'x', *args, '-aou', '-bso0', '-bse0', '-bsp0', f'-o{out_path}', in_path]
szip_x = subprocess.run(szip_c, check=False)
if check:
check_bad_exit_code(szip_x.returncode)
if not os.path.isdir(out_path):
raise Exception('EXTRACT_DIR_MISSING')
except Exception:
if not silent:
printer(f'Error: 7-Zip could not extract {in_name} file {in_path}!', padding)
return 1
if not silent:
printer(f'Succesfull {in_name} decompression via 7-Zip!', padding)
return 0

38
common/externals.py Normal file
View file

@ -0,0 +1,38 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
"""
from common.path_ops import project_root, safe_path
from common.system import get_os_ver
# https://github.com/allowitsme/big-tool by Dmitry Frolov
# https://github.com/platomav/BGScriptTool by Plato Mavropoulos
def get_bgs_tool():
try:
# noinspection PyUnresolvedReferences
from external.big_script_tool import BigScript # pylint: disable=E0401,E0611
except Exception:
BigScript = None
return BigScript
# Get UEFIFind path
def get_uefifind_path():
exec_name = f'UEFIFind{".exe" if get_os_ver()[1] else ""}'
return safe_path(project_root(), ['external', exec_name])
# Get UEFIExtract path
def get_uefiextract_path():
exec_name = f'UEFIExtract{".exe" if get_os_ver()[1] else ""}'
return safe_path(project_root(), ['external', exec_name])
# Get ToshibaComExtractor path
def get_comextract_path():
exec_name = f'comextract{".exe" if get_os_ver()[1] else ""}'
return safe_path(project_root(), ['external', exec_name])

14
common/num_ops.py Normal file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
"""
# https://leancrew.com/all-this/2020/06/ordinals-in-python/ by Dr. Drang
def get_ordinal(number):
s = ('th', 'st', 'nd', 'rd') + ('th',) * 10
v = number % 100
return f'{number}{s[v % 10]}' if v > 13 else f'{number}{s[v]}'

154
common/path_ops.py Normal file
View file

@ -0,0 +1,154 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
"""
import os
import re
import sys
import stat
import shutil
from pathlib import Path, PurePath
from common.text_ops import is_encased, to_string
# Fix illegal/reserved Windows characters
def safe_name(in_name):
name_repr = repr(in_name).strip("'")
return re.sub(r'[\\/:"*?<>|]+', '_', name_repr)
# Check and attempt to fix illegal/unsafe OS path traversals
def safe_path(base_path, user_paths):
# Convert base path to absolute path
base_path = real_path(base_path)
# Merge user path(s) to string with OS separators
user_path = to_string(user_paths, os.sep)
# Create target path from base + requested user path
target_path = norm_path(base_path, user_path)
# Check if target path is OS illegal/unsafe
if is_safe_path(base_path, target_path):
return target_path
# Re-create target path from base + leveled/safe illegal "path" (now file)
nuked_path = norm_path(base_path, safe_name(user_path))
# Check if illegal path leveling worked
if is_safe_path(base_path, nuked_path):
return nuked_path
# Still illegal, raise exception to halt execution
raise Exception(f'ILLEGAL_PATH_TRAVERSAL: {user_path}')
# Check for illegal/unsafe OS path traversal
def is_safe_path(base_path, target_path):
base_path = real_path(base_path)
target_path = real_path(target_path)
common_path = os.path.commonpath((base_path, target_path))
return base_path == common_path
# Create normalized base path + OS separator + user path
def norm_path(base_path, user_path):
return os.path.normpath(base_path + os.sep + user_path)
# Get absolute path, resolving any symlinks
def real_path(in_path):
return os.path.realpath(in_path)
# Get Windows/Posix OS agnostic path
def agnostic_path(in_path):
return PurePath(in_path.replace('\\', os.sep))
# Get absolute parent of path
def path_parent(in_path):
return Path(in_path).parent.absolute()
# Get final path component, with suffix
def path_name(in_path):
return PurePath(in_path).name
# Get final path component, w/o suffix
def path_stem(in_path):
return PurePath(in_path).stem
# Get list of path file extensions
def path_suffixes(in_path):
return PurePath(in_path).suffixes or ['']
# Check if path is absolute
def is_path_absolute(in_path):
return Path(in_path).is_absolute()
# 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)
Path.mkdir(Path(in_path), parents=parents, exist_ok=exist_ok)
# Delete folder(s), if present
def del_dirs(in_path):
if Path(in_path).is_dir():
shutil.rmtree(in_path, onerror=clear_readonly)
# Copy file to path with or w/o metadata
def copy_file(in_path, out_path, meta=False):
if meta:
shutil.copy2(in_path, out_path)
else:
shutil.copy(in_path, out_path)
# Clear read-only file attribute (on shutil.rmtree error)
def clear_readonly(in_func, in_path, _):
os.chmod(in_path, stat.S_IWRITE)
in_func(in_path)
# Walk path to get all files
def get_path_files(in_path):
path_files = []
for root, _, files in os.walk(in_path):
for name in files:
path_files.append(os.path.join(root, name))
return path_files
# Get path without leading/trailing quotes
def get_dequoted_path(in_path):
out_path = to_string(in_path).strip()
if len(out_path) >= 2 and is_encased(out_path, ("'",'"')):
out_path = out_path[1:-1]
return out_path
# Set utility extraction stem
def extract_suffix():
return '_extracted'
# Get utility extraction path
def get_extract_path(in_path, suffix=extract_suffix()):
return f'{in_path}{suffix}'
# Get project's root directory
def project_root():
root = Path(__file__).parent.parent
return real_path(root)
# Get runtime's root directory
def runtime_root():
if getattr(sys, 'frozen', False):
root = Path(sys.executable).parent
else:
root = project_root()
return real_path(root)

34
common/patterns.py Normal file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
"""
import re
PAT_AMI_PFAT = re.compile(br'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL)
PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}@', re.DOTALL)
PAT_APPLE_EFI = re.compile(br'\$IBIOSI\$.{16}\x2E\x00.{6}\x2E\x00.{8}\x2E\x00.{6}\x2E\x00.{20}\x00{2}', re.DOTALL)
PAT_APPLE_IM4P = re.compile(br'\x16\x04IM4P\x16\x04mefi')
PAT_APPLE_PBZX = re.compile(br'pbzx')
PAT_APPLE_PKG = re.compile(br'xar!')
PAT_AWARD_LZH = re.compile(br'-lh[04567]-')
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_FUJITSU_SFX = re.compile(br'FjSfxBinay\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', 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_INTEL_IFD = re.compile(br'\x5A\xA5\xF0\x0F.{172}\xFF{16}', re.DOTALL)
PAT_MICROSOFT_CAB = re.compile(br'MSCF\x00{4}')
PAT_MICROSOFT_MZ = re.compile(br'MZ')
PAT_MICROSOFT_PE = re.compile(br'PE\x00{2}')
PAT_PHOENIX_TDK = re.compile(br'\$PACK\x00{3}..\x00{2}.\x00{3}', re.DOTALL)
PAT_PORTWELL_EFI = re.compile(br'<U{2}>')
PAT_TOSHIBA_COM = re.compile(br'\x00{2}[\x00-\x02]BIOS.{20}[\x00\x01]', re.DOTALL)
PAT_VAIO_CAB = re.compile(br'\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL)
PAT_VAIO_CFG = re.compile(br'\[Setting]\x0D\x0A')
PAT_VAIO_CHK = re.compile(br'\x0AUseVAIOCheck=')
PAT_VAIO_EXT = re.compile(br'\x0AExtractPathByUser=')

49
common/pe_ops.py Normal file
View file

@ -0,0 +1,49 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
"""
import pefile
from common.system import printer
from common.text_ops import file_to_bytes
# Check if input is a PE file
def is_pe_file(in_file):
return bool(get_pe_file(in_file))
# Get pefile object from PE file
def get_pe_file(in_file, fast=True):
in_buffer = file_to_bytes(in_file)
try:
# Analyze detected MZ > PE image buffer
pe_file = pefile.PE(data=in_buffer, fast_load=fast)
except Exception:
pe_file = None
return pe_file
# Get PE info from pefile object
def get_pe_info(pe_file):
try:
# When fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed prior to FileInfo > StringTable
pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']])
# Retrieve MZ > PE > FileInfo > StringTable information
pe_info = pe_file.FileInfo[0][0].StringTable[0].entries
except Exception:
pe_info = {}
return pe_info
# Print PE info from pefile StringTable
def show_pe_info(pe_info, padding=0):
if type(pe_info).__name__ == 'dict':
for title,value in pe_info.items():
info_title = title.decode('utf-8','ignore').strip()
info_value = value.decode('utf-8','ignore').strip()
if info_title and info_value:
printer(f'{info_title}: {info_value}', padding, new_line=False)

28
common/struct_ops.py Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
"""
import ctypes
char = ctypes.c_char
uint8_t = ctypes.c_ubyte
uint16_t = ctypes.c_ushort
uint32_t = ctypes.c_uint
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 = []
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)
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)
return structure

68
common/system.py Normal file
View file

@ -0,0 +1,68 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
"""
import sys
from common.text_ops import padder, to_string
# Get Python Version (tuple)
def get_py_ver():
return sys.version_info
# Get OS Platform (string)
def get_os_ver():
sys_os = sys.platform
is_win = sys_os == 'win32'
is_lnx = sys_os.startswith('linux') or sys_os == 'darwin' or sys_os.find('bsd') != -1
return sys_os, is_win, is_win or is_lnx
# Check for --auto-exit|-e
def is_auto_exit():
return bool('--auto-exit' in sys.argv or '-e' in sys.argv)
# Check Python Version
def check_sys_py():
sys_py = get_py_ver()
if sys_py < (3,10):
sys.stdout.write(f'\nError: Python >= 3.10 required, not {sys_py[0]}.{sys_py[1]}!')
if not is_auto_exit():
# noinspection PyUnresolvedReferences
(raw_input if sys_py[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602
sys.exit(125)
# Check OS Platform
def check_sys_os():
os_tag,os_win,os_sup = get_os_ver()
if not os_sup:
printer(f'Error: Unsupported platform "{os_tag}"!')
if not is_auto_exit():
input('\nPress enter to exit')
sys.exit(126)
# Fix Windows Unicode console redirection
if os_win:
sys.stdout.reconfigure(encoding='utf-8')
# Show message(s) while controlling padding, newline, pausing & separator
def printer(in_message='', padd_count=0, new_line=True, pause=False, sep_char=' '):
message = to_string(in_message, sep_char)
padding = padder(padd_count)
newline = '\n' if new_line else ''
output = newline + padding + message
(input if pause and not is_auto_exit() else print)(output)

162
common/templates.py Normal file
View file

@ -0,0 +1,162 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
"""
import os
import sys
import ctypes
import argparse
import traceback
from common.num_ops import get_ordinal
from common.path_ops import get_dequoted_path, get_extract_path, get_path_files, is_path_absolute, path_parent, runtime_root, safe_path
from common.system import check_sys_os, check_sys_py, get_os_ver, is_auto_exit, printer
class BIOSUtility:
MAX_FAT32_ITEMS = 65535
def __init__(self, title, check, main, padding=0):
self._title = title
self._main = main
self._check = check
self._padding = padding
self._arguments_kw = {}
# Initialize argparse argument parser
self._argparser = argparse.ArgumentParser()
self._argparser.add_argument('files', type=argparse.FileType('r', encoding='utf-8'), nargs='*')
self._argparser.add_argument('-e', '--auto-exit', help='skip all user action prompts', action='store_true')
self._argparser.add_argument('-v', '--version', help='show utility name and version', action='store_true')
self._argparser.add_argument('-o', '--output-dir', help='extract in given output directory')
self._argparser.add_argument('-i', '--input-dir', help='extract from given input directory')
self._arguments,self._arguments_unk = self._argparser.parse_known_args()
# Managed Python exception handler
sys.excepthook = self._exception_handler
# Check Python Version
check_sys_py()
# Check OS Platform
check_sys_os()
# Show Script Title
printer(self._title, new_line=False)
# Show Utility Version on demand
if self._arguments.version:
sys.exit(0)
# Set console/terminal window title (Windows only)
if get_os_ver()[1]:
ctypes.windll.kernel32.SetConsoleTitleW(self._title)
# Process input files and generate output path
self._process_input_files()
# Count input files for exit code
self._exit_code = len(self._input_files)
def parse_argument(self, *args, **kwargs):
_dest = self._argparser.add_argument(*args, **kwargs).dest
self._arguments = self._argparser.parse_known_args(self._arguments_unk)[0]
self._arguments_kw.update({_dest: self._arguments.__dict__[_dest]})
def run_utility(self):
for _input_file in self._input_files:
_input_name = os.path.basename(_input_file)
printer(['***', _input_name], self._padding)
if not self._check(_input_file):
printer('Error: This is not a supported input!', self._padding + 4)
continue # Next input file
_extract_path = os.path.join(self._output_path, get_extract_path(_input_name))
if os.path.isdir(_extract_path):
for _suffix in range(2, self.MAX_FAT32_ITEMS):
_renamed_path = f'{os.path.normpath(_extract_path)}_{get_ordinal(_suffix)}'
if not os.path.isdir(_renamed_path):
_extract_path = _renamed_path
break # Extract path is now unique
if self._main(_input_file, _extract_path, self._padding + 4, **self._arguments_kw) in [0, None]:
self._exit_code -= 1
printer('Done!', pause=True)
sys.exit(self._exit_code)
# Process input files
def _process_input_files(self):
self._input_files = []
if len(sys.argv) >= 2:
# Drag & Drop or CLI
if self._arguments.input_dir:
_input_path_user = self._arguments.input_dir
_input_path_full = self._get_input_path(_input_path_user) if _input_path_user else ''
self._input_files = get_path_files(_input_path_full)
else:
# Parse list of input files (i.e. argparse FileType objects)
for _file_object in self._arguments.files:
# Store each argparse FileType object's name (i.e. path)
self._input_files.append(_file_object.name)
# Close each argparse FileType object (i.e. allow input file changes)
_file_object.close()
# Set output fallback value for missing argparse Output and Input Path
_output_fallback = path_parent(self._input_files[0]) if self._input_files else None
# Set output path via argparse Output path or argparse Input path or first input file path
_output_path = self._arguments.output_dir or self._arguments.input_dir or _output_fallback
else:
# Script w/o parameters
_input_path_user = get_dequoted_path(input('\nEnter input directory path: '))
_input_path_full = self._get_input_path(_input_path_user) if _input_path_user else ''
self._input_files = get_path_files(_input_path_full)
_output_path = get_dequoted_path(input('\nEnter output directory path: '))
self._output_path = self._get_input_path(_output_path)
# Get absolute input file path
@staticmethod
def _get_input_path(input_path):
if not input_path:
# Use runtime directory if no user path is specified
absolute_path = runtime_root()
else:
# Check if user specified path is absolute
if is_path_absolute(input_path):
absolute_path = input_path
# Otherwise, make it runtime directory relative
else:
absolute_path = safe_path(runtime_root(), input_path)
return absolute_path
# https://stackoverflow.com/a/781074 by Torsten Marek
@staticmethod
def _exception_handler(exc_type, exc_value, exc_traceback):
if exc_type is KeyboardInterrupt:
printer('')
else:
printer('Error: Utility crashed, please report the following:\n')
traceback.print_exception(exc_type, exc_value, exc_traceback)
if not is_auto_exit():
input('\nPress enter to exit')
sys.exit(127)

33
common/text_ops.py Normal file
View file

@ -0,0 +1,33 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
"""
# Generate padding (spaces or tabs)
def padder(padd_count, tab=False):
return ('\t' if tab else ' ') * padd_count
# Get String from given input object
def to_string(in_object, sep_char=''):
if type(in_object).__name__ in ('list','tuple'):
out_string = sep_char.join(map(str, in_object))
else:
out_string = str(in_object)
return out_string
# Get Bytes from given buffer or file path
def file_to_bytes(in_object):
object_bytes = in_object
if type(in_object).__name__ not in ('bytes','bytearray'):
with open(to_string(in_object), 'rb') as object_data:
object_bytes = object_data.read()
return object_bytes
# Check if string starts and ends with given character(s)
def is_encased(in_string, chars):
return in_string.startswith(chars) and in_string.endswith(chars)

2
external/requirements.txt vendored Normal file
View file

@ -0,0 +1,2 @@
lznt1 >= 0.2
pefile >= 2022.5.30