1
0
Fork 0
mirror of https://github.com/platomav/BIOSUtilities.git synced 2025-05-21 02:35:26 -04:00

BIOSUtilities v24.10.01

Complete repository overhaul into python project
Re-designed BIOSUtility base template class flow
Re-structured utilities as BIOSUtility inherited
Re-structured project for 3rd-party compatibility
Unified project requirements and package version
Code overhaul with type hints and linting support
Switched external executable dependencies via PATH
BIOSUtility enforces simple check and parse methods
Utilities now work with both path and buffer inputs
Adjusted class, method, function names and parameters
Improved Dell PFS Update Extractor sub-PFAT processing
Improved Award BIOS Module Extractor corruption handling
Improved Apple EFI Image Identifier to expose the EFI ID
Improved Insyde iFlash/iFdPacker Extractor with ISH & PDT
Re-written Apple EFI Package Extractor to support all PKG
This commit is contained in:
Plato Mavropoulos 2024-10-02 00:09:14 +03:00
parent ef50b75ae1
commit cda2fbd0b1
65 changed files with 6239 additions and 5233 deletions

4
.gitignore vendored
View file

@ -1,6 +1,4 @@
/.idea/
/.mypy_cache/
/external/*
/external/
/venv/
!external/__init__.py

View file

@ -2,3 +2,23 @@
explicit_package_bases = True
mypy_path = $MYPY_CONFIG_FILE_DIR/
[mypy-apple_efi_id]
ignore_missing_imports = True
[mypy-apple_efi_im4p]
ignore_missing_imports = True
[mypy-apple_efi_pbzx]
ignore_missing_imports = True
[mypy-dissect.util.compression]
ignore_missing_imports = True
[mypy-pefile]
ignore_missing_imports = True

View file

@ -6,14 +6,12 @@ init-hook="import sys; sys.path.append('./')"
disable=
duplicate-code,
invalid-name,
line-too-long,
too-few-public-methods,
too-many-arguments,
too-many-branches,
too-many-instance-attributes,
too-many-lines,
too-many-locals,
too-many-nested-blocks,
too-many-return-statements,
too-many-statements
too-many-positional-arguments,
too-many-statements

View file

@ -1,460 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
AMI PFAT Extract
AMI BIOS Guard Extractor
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import ctypes
import os
import re
import struct
from common.externals import get_bgs_tool
from common.num_ops import get_ordinal
from common.path_ops import extract_suffix, get_extract_path, make_dirs, path_name, safe_name
from common.patterns import PAT_AMI_PFAT
from common.struct_ops import Char, get_struct, UInt8, UInt16, UInt32
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import bytes_to_hex, file_to_bytes
TITLE = 'AMI BIOS Guard Extractor v6.0'
class AmiBiosGuardHeader(ctypes.LittleEndianStructure):
""" AMI BIOS Guard Header """
_pack_ = 1
# noinspection PyTypeChecker
_fields_ = [
('Size', UInt32), # 0x00 Header + Entries
('Checksum', UInt32), # 0x04 ?
('Tag', Char * 8), # 0x04 _AMIPFAT
('Flags', UInt8), # 0x10 ?
# 0x11
]
def struct_print(self, padd: int) -> None:
""" Display structure information """
printer(['Size :', f'0x{self.Size:X}'], padd, False)
printer(['Checksum:', f'0x{self.Checksum:04X}'], padd, False)
printer(['Tag :', self.Tag.decode('utf-8')], padd, False)
printer(['Flags :', f'0x{self.Flags:02X}'], padd, False)
class IntelBiosGuardHeader(ctypes.LittleEndianStructure):
""" Intel BIOS Guard Header """
_pack_ = 1
# noinspection PyTypeChecker
_fields_ = [
('BGVerMajor', UInt16), # 0x00
('BGVerMinor', UInt16), # 0x02
('PlatformID', UInt8 * 16), # 0x04
('Attributes', UInt32), # 0x14
('ScriptVerMajor', UInt16), # 0x16
('ScriptVerMinor', UInt16), # 0x18
('ScriptSize', UInt32), # 0x1C
('DataSize', UInt32), # 0x20
('BIOSSVN', UInt32), # 0x24
('ECSVN', UInt32), # 0x28
('VendorInfo', UInt32), # 0x2C
# 0x30
]
def get_platform_id(self) -> str:
""" Get Intel BIOS Guard Platform ID """
id_byte: bytes = bytes(self.PlatformID)
id_text: str = re.sub(r'[\n\t\r\x00 ]', '', id_byte.decode('utf-8', 'ignore'))
id_hexs: str = f'{int.from_bytes(id_byte, "big"):0{0x10 * 2}X}'
id_guid: str = 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_hdr_marker(self) -> bytes:
""" Get Intel BIOS Guard Header Marker """
return struct.pack('<HH16B', self.BGVerMajor, self.BGVerMinor, *self.PlatformID)
def get_flags(self) -> tuple:
""" Get Intel BIOS Guard Header Attributes """
attr = IntelBiosGuardHeaderGetAttributes()
attr.asbytes = self.Attributes # pylint: disable=W0201
return attr.b.SFAM, attr.b.ProtectEC, attr.b.GFXMitDis, attr.b.FTU, attr.b.Reserved
def struct_print(self, padd: int) -> None:
""" Display structure information """
no_yes: dict[int, str] = {0: 'No', 1: 'Yes'}
sfam, ec_opc, gfx_dis, ft_upd, attr_res = self.get_flags()
printer(['BIOS Guard Version :', f'{self.BGVerMajor}.{self.BGVerMinor}'], padd, False)
printer(['Platform Identity :', self.get_platform_id()], padd, False)
printer(['Signed Flash Address Map :', no_yes[sfam]], padd, False)
printer(['Protected EC OpCodes :', no_yes[ec_opc]], padd, False)
printer(['Graphics Security Disable :', no_yes[gfx_dis]], padd, False)
printer(['Fault Tolerant Update :', no_yes[ft_upd]], padd, False)
printer(['Attributes Reserved :', f'0x{attr_res:X}'], padd, False)
printer(['Script Version :', f'{self.ScriptVerMajor}.{self.ScriptVerMinor}'], padd, False)
printer(['Script Size :', f'0x{self.ScriptSize:X}'], padd, False)
printer(['Data Size :', f'0x{self.DataSize:X}'], padd, False)
printer(['BIOS Security Version Number:', f'0x{self.BIOSSVN:X}'], padd, False)
printer(['EC Security Version Number :', f'0x{self.ECSVN:X}'], padd, False)
printer(['Vendor Information :', f'0x{self.VendorInfo:X}'], padd, False)
class IntelBiosGuardHeaderAttributes(ctypes.LittleEndianStructure):
""" Intel BIOS Guard Header Attributes """
_pack_ = 1
_fields_ = [
('SFAM', UInt32, 1), # Signed Flash Address Map
('ProtectEC', UInt32, 1), # Protected EC OpCodes
('GFXMitDis', UInt32, 1), # GFX Security Disable
('FTU', UInt32, 1), # Fault Tolerant Update
('Reserved', UInt32, 28) # Reserved/Unknown
]
class IntelBiosGuardHeaderGetAttributes(ctypes.Union):
""" Intel BIOS Guard Header Attributes Getter """
_pack_ = 1
_fields_ = [
('b', IntelBiosGuardHeaderAttributes),
('asbytes', UInt32)
]
class IntelBiosGuardSignatureHeader(ctypes.LittleEndianStructure):
""" Intel BIOS Guard Signature Header """
_pack_ = 1
_fields_ = [
('Unknown0', UInt32), # 0x000
('Unknown1', UInt32), # 0x004
# 0x8
]
def struct_print(self, padd: int) -> None:
""" Display structure information """
printer(['Unknown 0:', f'0x{self.Unknown0:X}'], padd, False)
printer(['Unknown 1:', f'0x{self.Unknown1:X}'], padd, False)
class IntelBiosGuardSignatureRsa2k(ctypes.LittleEndianStructure):
""" Intel BIOS Guard Signature Block 2048-bit """
_pack_ = 1
# noinspection PyTypeChecker
_fields_ = [
('Modulus', UInt8 * 256), # 0x000
('Exponent', UInt32), # 0x100
('Signature', UInt8 * 256), # 0x104
# 0x204
]
def struct_print(self, padd: int) -> None:
""" Display structure information """
printer(['Modulus :', f'{bytes_to_hex(self.Modulus, "little", 0x100, 32)} [...]'], padd, False)
printer(['Exponent :', f'0x{self.Exponent:X}'], padd, False)
printer(['Signature:', f'{bytes_to_hex(self.Signature, "little", 0x100, 32)} [...]'], padd, False)
class IntelBiosGuardSignatureRsa3k(ctypes.LittleEndianStructure):
""" Intel BIOS Guard Signature Block 3072-bit """
_pack_ = 1
# noinspection PyTypeChecker
_fields_ = [
('Modulus', UInt8 * 384), # 0x000
('Exponent', UInt32), # 0x180
('Signature', UInt8 * 384), # 0x184
# 0x304
]
def struct_print(self, padd: int) -> None:
""" Display structure information """
printer(['Modulus :', f'{int.from_bytes(self.Modulus, "little"):0{0x180 * 2}X}'[:64]], padd, False)
printer(['Exponent :', f'0x{self.Exponent:X}'], padd, False)
printer(['Signature:', f'{int.from_bytes(self.Signature, "little"):0{0x180 * 2}X}'[:64]], padd, False)
def is_ami_pfat(input_object: str | bytes | bytearray) -> bool:
""" Check if input is AMI BIOS Guard """
input_buffer: bytes = file_to_bytes(input_object)
return bool(get_ami_pfat(input_buffer))
def get_ami_pfat(input_object: str | bytes | bytearray) -> bytes:
""" Get actual AMI BIOS Guard buffer """
input_buffer: bytes = file_to_bytes(input_object)
match = PAT_AMI_PFAT.search(input_buffer)
return input_buffer[match.start() - 0x8:] if match else b''
def get_file_name(index: int, name: str) -> str:
""" Create AMI BIOS Guard output filename """
return safe_name(f'{index:02d} -- {name}')
def parse_bg_script(script_data: bytes, padding: int = 0) -> int:
""" Process Intel BIOS Guard Script """
is_opcode_div: bool = len(script_data) % 8 == 0
if not is_opcode_div:
printer('Error: BIOS Guard script is not divisible by OpCode length!', padding, False)
return 1
is_begin_end: bool = script_data[:8] + script_data[-8:] == b'\x01' + b'\x00' * 7 + b'\xFF' + b'\x00' * 7
if not is_begin_end:
printer('Error: BIOS Guard script lacks Begin and/or End OpCodes!', padding, False)
return 2
big_script = get_bgs_tool()
if not big_script:
printer('Note: BIOS Guard Script Tool optional dependency is missing!', padding, False)
return 3
script = big_script(code_bytes=script_data).to_string().replace('\t', ' ').split('\n')
for opcode in script:
if opcode.endswith(('begin', 'end')):
spacing: int = padding
elif opcode.endswith(':'):
spacing = padding + 4
else:
spacing = padding + 12
operands = [operand for operand in opcode.split(' ') if operand]
# Largest opcode length is 11 (erase64kblk) and largest operand length is 10 (0xAABBCCDD).
printer(f'{operands[0]:11s}{"".join((f" {o:10s}" for o in operands[1:]))}', spacing, False)
return 0
def parse_bg_sign(input_data: bytes, sign_offset: int, sign_length: int = 0,
print_info: bool = False, padding: int = 0) -> int:
""" Process Intel BIOS Guard Signature """
bg_sig_hdr = get_struct(input_data, sign_offset, IntelBiosGuardSignatureHeader)
if bg_sig_hdr.Unknown0 == 1:
bg_sig_rsa_struct = IntelBiosGuardSignatureRsa2k # Unknown0 = 1, Unknown1 = 1
elif bg_sig_hdr.Unknown0 == 2:
bg_sig_rsa_struct = IntelBiosGuardSignatureRsa3k # Unknown0 = 2, Unknown1 = 3
elif sign_length == PFAT_INT_SIG_HDR_LEN + PFAT_INT_SIG_R2K_LEN:
bg_sig_rsa_struct = IntelBiosGuardSignatureRsa2k
printer('Warning: Detected Intel BIOS Guard Signature 2K length via pattern!\n', padding, False)
elif sign_length == PFAT_INT_SIG_HDR_LEN + PFAT_INT_SIG_R3K_LEN:
bg_sig_rsa_struct = IntelBiosGuardSignatureRsa3k
printer('Warning: Detected Intel BIOS Guard Signature 3K length via pattern!\n', padding, False)
else:
bg_sig_rsa_struct = IntelBiosGuardSignatureRsa3k
printer('Error: Could not detect Intel BIOS Guard Signature length, assuming 3K!\n', padding, False, pause=True)
bg_sig_rsa = get_struct(input_data, sign_offset + PFAT_INT_SIG_HDR_LEN, bg_sig_rsa_struct)
if print_info:
bg_sig_hdr.struct_print(padding)
bg_sig_rsa.struct_print(padding)
# Total size of Signature Header and RSA Structure
return PFAT_INT_SIG_HDR_LEN + ctypes.sizeof(bg_sig_rsa_struct)
def parse_pfat_hdr(buffer: bytes | bytearray, padding: int = 0) -> tuple:
""" Parse AMI BIOS Guard Header """
block_all: list = []
pfat_hdr = get_struct(buffer, 0x0, AmiBiosGuardHeader)
hdr_size: int = pfat_hdr.Size
hdr_data: bytes = buffer[PFAT_AMI_HDR_LEN:hdr_size]
hdr_text: list[str] = 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: int = len(hdr_files)
hdr_tag, *hdr_indexes = hdr_title.split('II')
printer(hdr_tag + '\n', padding + 4)
bgt_indexes: list = [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: list = entry.split(';')
info: list = entry_parts[0].split()
name: str = entry_parts[1]
flags: int = int(info[0])
param: str = info[1]
count: int = int(info[2])
order: str = get_ordinal((bgt_indexes[index] if bgt_indexes else index) + 1)
desc = f'{name} (Index: {index + 1:02d}, Flash: {order}, ' \
f'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_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Process and store AMI BIOS Guard output file """
input_buffer: bytes = file_to_bytes(input_object)
pfat_buffer: bytes = get_ami_pfat(input_buffer)
file_path: str = ''
all_blocks_dict: dict = {}
bg_sign_len: int = 0
extract_name: str = path_name(extract_path).removesuffix(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: str = 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: int = block_off + PFAT_INT_HDR_LEN
bg_script_end: int = bg_script_bgn + bg_hdr.ScriptSize
bg_data_bgn: int = bg_script_end
bg_data_end: int = bg_data_bgn + bg_hdr.DataSize
bg_data_bin: bytes = 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:
printer(f'Intel BIOS Guard {block_status} Signature:\n', padding + 8)
if bg_sign_len == 0:
bg_sign_len = pfat_buffer.find(bg_hdr.get_hdr_marker(), bg_data_end,
bg_data_end + PFAT_INT_SIG_MAX_LEN) - bg_data_end
# Adjust next block to start after current block Data + Signature
block_off += parse_bg_sign(pfat_buffer, bg_data_end, bg_sign_len, True, padding + 12)
printer(f'Intel BIOS Guard {block_status} Script:\n', padding + 8)
_ = parse_bg_script(pfat_buffer[bg_script_bgn:bg_script_end], padding + 12)
with open(file_path, 'ab') as out_dat:
out_dat.write(bg_data_bin)
all_blocks_dict[file_index] += bg_data_bin
if block_index + 1 == block_count:
if is_ami_pfat(all_blocks_dict[file_index]):
parse_pfat_file(all_blocks_dict[file_index], get_extract_path(file_path), padding + 8)
pfat_oob_data: bytes = pfat_buffer[block_off:] # Store out-of-bounds data after the end of PFAT files
pfat_oob_name: str = get_file_name(file_count + 1, f'{extract_name}_OOB.bin')
pfat_oob_path: str = 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: bytes = b''.join([block[1] for block in sorted(all_blocks_dict.items())])
in_all_name: str = get_file_name(0, f'{extract_name}_ALL.bin')
in_all_path: str = 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: int = ctypes.sizeof(AmiBiosGuardHeader)
PFAT_INT_HDR_LEN: int = ctypes.sizeof(IntelBiosGuardHeader)
PFAT_INT_SIG_HDR_LEN: int = ctypes.sizeof(IntelBiosGuardSignatureHeader)
PFAT_INT_SIG_R2K_LEN: int = ctypes.sizeof(IntelBiosGuardSignatureRsa2k)
PFAT_INT_SIG_R3K_LEN: int = ctypes.sizeof(IntelBiosGuardSignatureRsa3k)
PFAT_INT_SIG_MAX_LEN: int = PFAT_INT_SIG_HDR_LEN + PFAT_INT_SIG_R3K_LEN
if __name__ == '__main__':
BIOSUtility(title=TITLE, check=is_ami_pfat, main=parse_pfat_file).run_utility()

View file

@ -1,570 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
AMI UCP Extract
AMI UCP Update Extractor
Copyright (C) 2021-2024 Plato Mavropoulos
"""
import contextlib
import ctypes
import os
import re
import struct
from common.checksums import get_chk_16
from common.comp_efi import efi_decompress, is_efi_compressed
from common.path_ops import agnostic_path, get_extract_path, make_dirs, safe_name, safe_path
from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG
from common.struct_ops import Char, get_struct, UInt8, UInt16, UInt32
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
TITLE = 'AMI UCP Update Extractor v3.0'
class UafHeader(ctypes.LittleEndianStructure):
""" UAF Header """
_pack_ = 1
_fields_ = [
('ModuleTag', Char * 4), # 0x00
('ModuleSize', UInt32), # 0x04
('Checksum', UInt16), # 0x08
('Unknown0', UInt8), # 0x0A
('Unknown1', UInt8), # 0x0A
('Reserved', UInt8 * 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, padd):
""" Display structure information """
printer(['Tag :', self.ModuleTag.decode('utf-8')], padd, False)
printer(['Size :', f'0x{self.ModuleSize:X}'], padd, False)
printer(['Checksum :', f'0x{self.Checksum:04X}'], padd, False)
printer(['Unknown 0 :', f'0x{self.Unknown0:02X}'], padd, False)
printer(['Unknown 1 :', f'0x{self.Unknown1:02X}'], padd, False)
printer(['Reserved :', self._get_reserved()], padd, False)
class UafModule(ctypes.LittleEndianStructure):
""" UAF Module """
_pack_ = 1
_fields_ = [
('CompressSize', UInt32), # 0x00
('OriginalSize', UInt32), # 0x04
# 0x08
]
def struct_print(self, padd, filename, description):
""" Display structure information """
printer(['Compress Size:', f'0x{self.CompressSize:X}'], padd, False)
printer(['Original Size:', f'0x{self.OriginalSize:X}'], padd, False)
printer(['Filename :', filename], padd, False)
printer(['Description :', description], padd, False)
class UiiHeader(ctypes.LittleEndianStructure):
""" UII Header """
_pack_ = 1
_fields_ = [
('UIISize', UInt16), # 0x00
('Checksum', UInt16), # 0x02
('UtilityVersion', UInt32), # 0x04 AFU|BGT (Unknown, Signed)
('InfoSize', UInt16), # 0x08
('SupportBIOS', UInt8), # 0x0A
('SupportOS', UInt8), # 0x0B
('DataBusWidth', UInt8), # 0x0C
('ProgramType', UInt8), # 0x0D
('ProgramMode', UInt8), # 0x0E
('SourceSafeRel', UInt8), # 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, padd, description):
""" Display structure information """
support_bios = self.SBI.get(self.SupportBIOS, f'Unknown ({self.SupportBIOS})')
support_os = self.SOS.get(self.SupportOS, f'Unknown ({self.SupportOS})')
data_bus_width = self.DBW.get(self.DataBusWidth, f'Unknown ({self.DataBusWidth})')
program_type = self.PTP.get(self.ProgramType, f'Unknown ({self.ProgramType})')
program_mode = self.PMD.get(self.ProgramMode, f'Unknown ({self.ProgramMode})')
printer(['UII Size :', f'0x{self.UIISize:X}'], padd, False)
printer(['Checksum :', f'0x{self.Checksum:04X}'], padd, False)
printer(['Tool Version :', f'0x{self.UtilityVersion:08X}'], padd, False)
printer(['Info Size :', f'0x{self.InfoSize:X}'], padd, False)
printer(['Supported BIOS:', support_bios], padd, False)
printer(['Supported OS :', support_os], padd, False)
printer(['Data Bus Width:', data_bus_width], padd, False)
printer(['Program Type :', program_type], padd, False)
printer(['Program Mode :', program_mode], padd, False)
printer(['SourceSafe Tag:', f'{self.SourceSafeRel:02d}'], padd, False)
printer(['Description :', description], padd, False)
class DisHeader(ctypes.LittleEndianStructure):
""" DIS Header """
_pack_ = 1
_fields_ = [
('PasswordSize', UInt16), # 0x00
('EntryCount', UInt16), # 0x02
('Password', Char * 12), # 0x04
# 0x10
]
def struct_print(self, padd):
""" Display structure information """
printer(['Password Size:', f'0x{self.PasswordSize:X}'], padd, False)
printer(['Entry Count :', self.EntryCount], padd, False)
printer(['Password :', self.Password.decode('utf-8')], padd, False)
class DisModule(ctypes.LittleEndianStructure):
""" DIS Module """
_pack_ = 1
_fields_ = [
('EnabledDisabled', UInt8), # 0x00
('ShownHidden', UInt8), # 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, padd):
""" Display structure information """
enabled_disabled = self.ENDIS.get(self.EnabledDisabled, f'Unknown ({self.EnabledDisabled})')
shown_hidden = self.SHOWN.get(self.ShownHidden, f'Unknown ({self.ShownHidden})')
printer(['State :', enabled_disabled], padd, False)
printer(['Display :', shown_hidden], padd, False)
printer(['Command :', self.Command.decode('utf-8').strip()], padd, False)
printer(['Description:', self.Description.decode('utf-8').strip()], padd, False)
def chk16_validate(data, tag, padd=0):
""" Validate UCP Module Checksum-16 """
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)
def is_ami_ucp(in_file):
""" Check if input is AMI UCP image """
buffer = file_to_bytes(in_file)
return bool(get_ami_ucp(buffer)[0] is not None)
def get_ami_ucp(in_file):
""" Get all input file AMI UCP patterns """
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
def get_uaf_mod(buffer, uaf_off=0x0):
""" Get list of @UAF|@HPU Modules """
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
def ucp_extract(in_file, extract_path, padding=0, checksum=False):
""" Parse & Extract AMI UCP structures """
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)
def uaf_extract(buffer, extract_path, mod_info, padding=0, checksum=False, nal_dict=None):
""" Parse & Extract AMI UCP > @UAF|@HPU Module/Section """
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))
# Add missing padding for decompression
uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw + comp_padd
else:
# Add the EFI/Tiano Compression info before Raw Data
uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw
else:
# No compression, extend to end of Original @UAF|@HPU Module size
uaf_data_raw = uaf_data_raw[:uaf_mod.OriginalSize]
# 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', ''],
'@D64': ['amifldrv64.sys', 'amifldrv64.sys', ''],
}
if __name__ == '__main__':
utility_args = [(['-c', '--checksum'], {'help': 'verify AMI UCP Checksums (slow)', 'action': 'store_true'})]
utility = BIOSUtility(title=TITLE, check=is_ami_ucp, main=ucp_extract, args=utility_args)
utility.run_utility()

View file

@ -1,181 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Apple EFI ID
Apple EFI Image Identifier
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import ctypes
import logging
import os
import struct
import subprocess
import zlib
from common.externals import get_uefiextract_path, get_uefifind_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
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
TITLE = 'Apple EFI Image Identifier v3.0'
class IntelBiosId(ctypes.LittleEndianStructure):
""" Intel BIOS ID Structure """
_pack_ = 1
_fields_ = [
('Signature', Char * 8), # 0x00
('BoardID', UInt8 * 16), # 0x08
('Dot1', UInt8 * 2), # 0x18
('BoardExt', UInt8 * 6), # 0x1A
('Dot2', UInt8 * 2), # 0x20
('VersionMajor', UInt8 * 8), # 0x22
('Dot3', UInt8 * 2), # 0x2A
('BuildType', UInt8 * 2), # 0x2C
('VersionMinor', UInt8 * 4), # 0x2E
('Dot4', UInt8 * 2), # 0x32
('Year', UInt8 * 4), # 0x34
('Month', UInt8 * 4), # 0x38
('Day', UInt8 * 4), # 0x3C
('Hour', UInt8 * 4), # 0x40
('Minute', UInt8 * 4), # 0x44
('NullTerminator', UInt8 * 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):
""" Create Apple EFI BIOS ID """
board_id = self._decode(self.BoardID)
board_ext = self._decode(self.BoardExt)
version_major = self._decode(self.VersionMajor)
build_type = self._decode(self.BuildType)
version_minor = self._decode(self.VersionMinor)
build_date = f'20{self._decode(self.Year)}-{self._decode(self.Month)}-{self._decode(self.Day)}'
build_time = f'{self._decode(self.Hour)}-{self._decode(self.Minute)}'
return board_id, board_ext, version_major, build_type, version_minor, build_date, build_time
def struct_print(self, padd):
""" Display structure information """
board_id, board_ext, version_major, build_type, version_minor, build_date, build_time = self.get_bios_id()
printer(['Intel Signature:', self.Signature.decode('utf-8')], padd, False)
printer(['Board Identity: ', board_id], padd, False)
printer(['Apple Identity: ', board_ext], padd, False)
printer(['Major Version: ', version_major], padd, False)
printer(['Minor Version: ', version_minor], padd, False)
printer(['Build Type: ', build_type], padd, False)
printer(['Build Date: ', build_date], padd, False)
printer(['Build Time: ', build_time.replace('-', ':')], padd, False)
def is_apple_efi(input_file):
""" Check if input is Apple EFI image """
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 as error: # pylint: disable=broad-except
logging.debug('Error: Could not check if input is Apple EFI image: %s', error)
return False
def apple_efi_identify(input_file, extract_path, padding=0, rename=False):
""" Parse & Identify (or Rename) Apple EFI image """
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 as error: # pylint: disable=broad-except
printer(f'Error: Failed to parse compressed $IBIOSI$ pattern: {error}!', 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)
fw_id, fw_ext, fw_major, fw_type, fw_minor, fw_date, fw_time = bios_id_hdr.get_bios_id()
output_name = f'{fw_id}_{fw_ext}_{fw_major}_{fw_type}{fw_minor}_{fw_date}_{fw_time}_' \
f'{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_args = [(['-r', '--rename'], {'help': 'rename EFI image based on its tag', 'action': 'store_true'})]
utility = BIOSUtility(title=TITLE, check=is_apple_efi, main=apple_efi_identify, args=utility_args)
utility.run_utility()

View file

@ -1,147 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Apple EFI IM4P
Apple EFI IM4P Splitter
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import os
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
TITLE = 'Apple EFI IM4P Splitter v4.0'
def is_apple_im4p(input_file):
""" Check if input is Apple EFI IM4P image """
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)
def apple_im4p_split(input_file, extract_path, padding=0):
""" Parse & Split Apple EFI IM4P image """
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=TITLE, check=is_apple_im4p, main=apple_im4p_split).run_utility()

View file

@ -1,128 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Apple PBZX Extract
Apple EFI PBZX Extractor
Copyright (C) 2021-2024 Plato Mavropoulos
"""
import ctypes
import logging
import lzma
import os
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
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
TITLE = 'Apple EFI PBZX Extractor v2.0'
class PbzxChunk(ctypes.BigEndianStructure):
""" PBZX Chunk Header """
_pack_ = 1
_fields_ = [
('Reserved0', UInt32), # 0x00
('InitSize', UInt32), # 0x04
('Reserved1', UInt32), # 0x08
('CompSize', UInt32), # 0x0C
# 0x10
]
def struct_print(self, padd):
""" Display structure information """
printer(['Reserved 0 :', f'0x{self.Reserved0:X}'], padd, False)
printer(['Initial Size :', f'0x{self.InitSize:X}'], padd, False)
printer(['Reserved 1 :', f'0x{self.Reserved1:X}'], padd, False)
printer(['Compressed Size:', f'0x{self.CompSize:X}'], padd, False)
def is_apple_pbzx(input_file):
""" Check if input is Apple PBZX image """
input_buffer = file_to_bytes(input_file)
return bool(PAT_APPLE_PBZX.search(input_buffer[:0x4]))
def apple_pbzx_extract(input_file, extract_path, padding=0):
""" Parse & Extract Apple PBZX image """
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 as error: # pylint: disable=broad-except
logging.debug('Error: Failed to LZMA decompress PBZX Chunk 0x%X: %s', chunk_off, error)
# 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=TITLE, check=is_apple_pbzx, main=apple_pbzx_extract).run_utility()

View file

@ -1,177 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Apple EFI PKG
Apple EFI Package Extractor
Copyright (C) 2019-2024 Plato Mavropoulos
"""
import os
from common.comp_szip import is_szip_supported, szip_decompress
from common.path_ops import copy_file, del_dirs, get_extract_path, get_path_files, make_dirs, path_name, path_parent
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
TITLE = 'Apple EFI Package Extractor v3.0'
def is_apple_pkg(input_file):
""" Check if input is Apple EFI PKG package """
input_buffer = file_to_bytes(input_file)
return bool(PAT_APPLE_PKG.search(input_buffer[:0x4]))
def efi_split_rename(in_file, out_path, padding=0):
""" Split Apple EFI image (if applicable) and Rename """
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)
def apple_pkg_extract(input_file, extract_path, padding=0):
""" Parse & Extract Apple EFI PKG packages """
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=TITLE, check=is_apple_pkg, main=apple_pkg_extract).run_utility()

View file

@ -1,84 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Award BIOS Extract
Award BIOS Module Extractor
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import os
import stat
from common.comp_szip import szip_decompress
from common.path_ops import get_extract_path, make_dirs, safe_name
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
TITLE = 'Award BIOS Module Extractor v3.0'
def is_award_bios(in_file):
""" Check if input is Award BIOS image """
in_buffer = file_to_bytes(in_file)
return bool(PAT_AWARD_LZH.search(in_buffer))
def award_bios_extract(input_file, extract_path, padding=0):
""" Parse & Extract Award BIOS image """
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]
if len(mod_bin) != 0x2 + hdr_len + mod_len:
printer(f'Error: Skipped incomplete LZH stream at 0x{mod_bgn:X}!', padding, False)
continue
tag_txt = safe_name(mod_bin[0x16:0x16 + mod_bin[0x15]].decode('utf-8', 'ignore').strip())
printer(f'{lzh_text} > {tag_txt} [0x{mod_bgn:06X}-0x{mod_end:06X}]', padding)
mod_path = os.path.join(extract_path, 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.chmod(lzh_path, stat.S_IWRITE)
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=TITLE, check=is_award_bios, main=award_bios_extract).run_utility()

478
CHANGELOG Normal file
View file

@ -0,0 +1,478 @@
24.10.01
Complete repository overhaul into python project
Re-designed BIOSUtility base template class flow
Re-structured utilities as BIOSUtility inherited
Re-structured project for 3rd-party compatibility
Unified project requirements and package version
Code overhaul with type hints and linting support
Switched external executable dependencies via PATH
BIOSUtility enforces simple check and parse methods
Utilities now work with both path and buffer inputs
Adjusted class, method, function names and parameters
Improved Dell PFS Update Extractor sub-PFAT processing
Improved Award BIOS Module Extractor corruption handling
Improved Apple EFI Image Identifier to expose the EFI ID
Improved Insyde iFlash/iFdPacker Extractor with ISH & PDT
Re-written Apple EFI Package Extractor to support all PKG
24.06.17
Toshiba BIOS COM Extractor v4.0
Improved Toshiba COM detection (pattern only, no file extension)
Improved input object handling to support both paths and bytes
Populated code type hints and applied few small improvements
24.05.26
Panasonic BIOS Package Extractor v4.0
Added ability to parse nested Panasonic BIOS update executable directly
Restructured logic to allow more flexibility on input executable parsing
Populated code type hints and applied multiple small improvements
24.05.16
Improved Intel BIOS Guard Signature parsing
24.04.24
Added AMI PFAT RSA 3K signed blocks support
Added AMI PFAT nested detection at each file
Added Award BIOS payload naming at each file
Switched Panasonic BIOS LZNT1 external library
Improved Panasonic LZNT1 detection and length
Improved Dell PFS code structure and fixed bugs
Improved code exception handling (raise, catch)
Improved code definitions (PEP8, docs, types)
Fixed some arguments missing from help screens
Additions, Improvements and Fixes from Q4 2022 to Q1 2024
Fixed .gitignore file exclusion
Various README cleanups
22.10.31
Fixed potential crash at files without extension(s)
22.10.23
Auto-resolve extract directory name conflicts
22.10.16
Updated Dell PFS Update Extractor v6.0_a16
Improved handling of sub-PFS PFAT Entries with offset conflicts and/or data gaps
Replace contents of no longer relevant comment
22.10.07
Updated Dell PFS/PKG Update Extractor v6.0_a15
Padding is now added between sub-PFS PFAT Entry gaps
Thanks for the help @NikolajSchlej
22.09.12
Created common template for executing all utilities
Unified extracted output directory naming logic
Multiple code fixes, refactors and improvements
22.09.02
Updated AMI UCP Update Extractor v2.0_a19
22.08.31
Updated Dell PFS/PKG Update Extractor v6.0_a12
Added support for Dell ThinOS PKG with multiple PFS
22.08.28
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
22.08.17
Added Apple EFI IM4P Splitter v3.0_a2
22.08.15
Added Apple EFI Image Identifier v2.0_a3
Fixed argparse lock of input files
22.07.14
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
22.07.07
Insyde iFlash/iFdPacker Extractor v2.0_a9
Support for hardcoded Insyde 7-Zip SFX password
Fixed 7-Zip hang on password protected files
22.07.06
Insyde iFlash/iFdPacker Extractor v2.0_a8
Improved PFAT, UCP, PFS, TDK format check methods
Cleanup/improvements to all utilities
Fixed README > Insyde iFlash/iFdPacker Extractor
Increased Python version to 3.10 and PEFile version to 2022.5.30 (performance)
22.06.30
Added Insyde iFlash Update Extractor v2.0_a2
Added Toshiba BIOS COM Extractor v2.0_a2
22.06.29
Added Fujitsu UPC BIOS Extractor v2.0_a2
22.06.26
Added Award BIOS Module Extractor v2.0_a3
Improved 7-Zip exit code handling
22.06.21
Added Panasonic BIOS Package Extractor v2.0_a7
New processes to better handle PE files
Various common package fixes and improvements
22.06.16
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!
22.06.02
Added VAIO Packaging Manager Extractor v3.0_a4
Sort README utilities based on name
22.06.01
Improved 7-Zip decompressor
Removed --static argument
Small fixes at variable names and f-strings
22.05.24
Phoenix TDK Packer Extractor v2.0_a6
Dramatically increase TDK Packer base offset detection speed
Applied regex pattern improvements
22.05.23
Phoenix TDK Packer Extractor v2.0_a5
Added detection of TDK Packer executable base offset
Improve TDK unpacking at weird images
22.05.22
Added Phoenix TDK Packer Extractor v2.0_a4
Added f-strings all the things!
22.05.15
Portwell EFI BIOS Extractor v2.0_a4
Replaced any assertions
Added pip requirements file
22.05.09
Improved README
22.05.07
Added --static parameter to README
22.05.06
Added relevant exit codes at utilities
Fixed missing output path value crash
Increased minimum Python version to 3.8
Added --static optional parameter
Allow usage of static-built external dependencies
22.05.01
Fix handling of quote-encased user input paths
22.04.21
Format detectors now accept input bytes or path
22.04.17
Revamped path-related operations
Fixed dependencies detecton
Fixed frozen state support
22.04.16
Fixed path symlink resolutions
22.04.15
Improved AMI UCP > NAL unpacking
Fix potential illegal path traversals
22.04.14
Fixes at title/version display
Add quick format check functions for PFAT, UCP, PFS
22.04.13
Dell PFS Update Extractor v6.0_a2
Added --version parameter
Structure fixes and improvements
22.04.09
AMI UCP BIOS Extractor v2.0_a3
Added support for HP-modded AMI UCP (HP Flash Utility v4)
Small AMI UCP pattern improvement
22.04.07
Added Dell PFS Update Extractor v6.0_a1
Adjusted dependencies
22.04.01
Added AMI UCP BIOS Extractor v2.0_a1
Added AMI BIOS Guard Extractor v4.0_a1
Added empty external directory
Initial refactor work
Cleanup old files
22.03.28
Apple EFI Package Grabber v2.0
22.03.14
Insyde iFlash Image Extractor v1.0
22.01.05
Dell PFS Update Extractor v5.1
Fixed BIOS Guard (PFAT) PFS Entry Signature parsing bug
Minor improvement in BIOS Guard (PFAT) PFS Entry detection
21.12.27
Dell PFS Update Extractor v5.0
Support for PFS Utilities section extraction, aside from the Firmware one
Support and proper extraction of Intel BIOS Guard protected Firmware
Support for parsing some newer PFS Information and Signature entries
Ability to show verbose output of extraction progress and PFS structures
Ability to better integrate the script to other projects via new parameters
Extensive code re-structuring in more modular form for future expansion
21.09.15
Fujitsu UPC BIOS Extractor v1.0
Fujitsu SFX BIOS Extractor v2.1
Small rebranding
21.08.21
AMI UCP BIOS Extractor v1.2
Fixed crash when parsing compressed text only UAF Modules
21.08.02
AMI UCP BIOS Extractor v1.1
Improved UAF Tag-File Dictionary
21.06.19
Portwell EFI BIOS Extractor v1.0
21.06.15
Phoenix SCT BIOS Extractor v1.0
21.06.04
AMI UCP BIOS Extractor v1.0
21.05.08
Dell PFS BIOS Extractor v4.9
Improved detection of PFS Text Modules
21.04.27
Dell PFS BIOS Extractor v4.8
Added support for PFS images within Dell ThinOS PKG packages
Applied various small performance & static analysis code fixes
21.02.27
Update README.md
Updated Donation button
21.01.19
AMI BIOS Guard Extractor v3.2
Each input file name is now shown at the top
Each output file now includes the input file name
Applied a few small static analysis code fixes
21.01.02
AMI BIOS Guard Extractor v3.1
File AMI_PFAT_X_DATA_ALL now includes AMI_PFAT_X_DATA_END
Applied various static analysis code fixes
20.12.06
AMI BIOS Guard Extractor v3.0
Added AMI PFAT Component new extraction method
Added AMI PFAT Nested PFAT Component extraction
Added Intel BIOS Guard Block Header detailed info
Added Intel BIOS Guard Block Script decompilation
Applied various code fixes & improvements
20.10.07
Dell PFS BIOS Extractor v4.6
Fixed crash when PFS filenames include Windows reserved characters
Fixed PFS Entry Version Type display typo, thanks to @vuquangtrong
20.07.04
Apple EFI IM4P Splitter v2.1
Improved Intel Flash Descriptor detection & parsing
20.06.26
VAIO Packaging Manager Extractor v2.0
Major de-obfuscation speed increase, up to x13 times
Improved VAIO executable unlocking procedure
Fujitsu SFX Packager Extractor v2.0
Major de-obfuscation speed increase, up to x13 times
20.06.18
Dell PFS BIOS Extractor v4.5
Added PFS section zlib data size & checksum checks
Added PFS section zlib footer detection & checks
Fixed null character ignoring at info text files
Applied various code fixes & improvements
20.05.23
Apple EFI IM4P Splitter v2.0
Added better Apple EFI extraction based on Intel Flash Descriptor Flash Component Size. Same output, robust method.
20.04.22
Dell PFS BIOS Extractor v4.2
Added PFS Revision 2 support
Added proper sub PFS Chunked Entries parsing
19.09.24
Dell PFS BIOS Extractor v3.6
Added another PFS Information Entry GUID
19.09.12
Dell PFS BIOS Extractor v3.5
Added support for Nested PFS without PFS Information Entry
Fixed error handling when PFS Entry names cannot be found
19.09.07
Update README.md
Added Anti-Virus False Positives note
19.08.09
Dell PFS BIOS Extractor v3.2
Fixed crash when checking for sub PFS of Zlib or Chunk types
19.08.07
Dell PFS BIOS Extractor v3.1
Fixed Dell PFS sub PFS Payload Chunk Order Number detection
New scripts, updated current scripts, new repo license
Added Dell PFS BIOS Extractor v3.0 (removed Dell HDR Module Extractor v2.0)
Added Apple EFI Package Extractor v1.1
Apple EFI File Renamer v1.3 supports calling from Apple EFI Package Extractor utility
Apple EFI IM4P Splitter v1.3 supports calling from Apple EFI Package Extractor utility
Apple EFI Sucatalog Link Grabber v1.2 stores output text file with unique name for easier comparisons
Repository is now licensed under BSD+Patent
All scripts now require Python 3.7 or newer
19.02.28
Fujitsu SFX Packager Extractor v1.0
19.02.03
VAIO Packaging Manager Extractor v1.0
19.01.15
AMI BIOS Guard Extractor v2.0
Any data after the end of PFAT, as dictated by the Main Header, are now appended to the final unpacked image.
19.01.10
Award BIOS Module Extractor v1.2
Added 7zip parameter to automatically rename extracted files with the same name
18.11.01
Apple EFI IM4P Splitter v1.2
Fixed IM4P payload start offset
18.10.13
Added AMI BIOS Guard Extractor
18.09.22
Dell HDR Module Extractor v2.0
Fixed issue which caused some HDR executables to extract less modules than expected
18.09.12
Added Panasonic BIOS Update Extractor
18.09.06
Updated README
18.09.05
Apple EFI File Renamer v1.2
Added official Intel $IBIOSI$ parsing
Added $IBIOSI$ output information
18.09.04
Updated all scripts to v1.1
Added "Working..." indicators while running at all scripts
Added dependency filename expectations when required
Apple EFI File Renamer now saves the checksum in hex
18.08.31
Added Utilities, Readme and Licence
Dell HDR Module Extractor v1.0
Apple EFI Sucatalog Link Grabber v1.0
Apple EFI File Renamer v1.0
Apple EFI IM4P Splitter v1.0
Award BIOS Module Extractor v1.0

File diff suppressed because it is too large Load diff

View file

@ -1,93 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Fujitsu SFX Extractor
Fujitsu SFX BIOS Extractor
Copyright (C) 2019-2024 Plato Mavropoulos
"""
import os
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
TITLE = 'Fujitsu SFX BIOS Extractor v4.0'
def is_fujitsu_sfx(in_file):
""" Check if input is Fujitsu SFX image """
buffer = file_to_bytes(in_file)
return bool(PAT_FUJITSU_SFX.search(buffer))
def fujitsu_cabinet(in_file, extract_path, padding=0):
""" Extract Fujitsu SFX image """
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 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- 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, 'FjSfxBinay CAB', padding + 8, check=True) == 0:
os.remove(cab_path) # Successful extraction, delete temporary CAB archive
else:
return 3
else:
return 2
return 0
def fujitsu_sfx_extract(in_file, extract_path, padding=0):
""" Parse & Extract Fujitsu SFX image """
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=TITLE, check=is_fujitsu_sfx, main=fujitsu_sfx_extract).run_utility()

View file

@ -1,47 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Fujitsu UPC Extract
Fujitsu UPC BIOS Extractor
Copyright (C) 2021-2024 Plato Mavropoulos
"""
import os
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
TITLE = 'Fujitsu UPC BIOS Extractor v3.0'
def is_fujitsu_upc(in_file):
""" Check if input is Fujitsu UPC image """
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
def fujitsu_upc_extract(input_file, extract_path, padding=0):
""" Parse & Extract Fujitsu UPC image """
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=TITLE, check=is_fujitsu_upc, main=fujitsu_upc_extract).run_utility()

View file

@ -1,234 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Insyde IFD Extract
Insyde iFlash/iFdPacker Extractor
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import ctypes
import os
from common.comp_szip import is_szip_supported, szip_decompress
from common.path_ops import get_extract_path, get_path_files, make_dirs, safe_name
from common.patterns import PAT_INSYDE_IFL, PAT_INSYDE_SFX
from common.struct_ops import Char, get_struct, UInt32
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
TITLE = 'Insyde iFlash/iFdPacker Extractor v3.0'
class IflashHeader(ctypes.LittleEndianStructure):
""" Insyde iFlash Header """
_pack_ = 1
# noinspection PyTypeChecker
_fields_ = [
('Signature', Char * 8), # 0x00 $_IFLASH
('ImageTag', Char * 8), # 0x08
('TotalSize', UInt32), # 0x10 from header end
('ImageSize', UInt32), # 0x14 from header end
# 0x18
]
def _get_padd_len(self) -> int:
return self.TotalSize - self.ImageSize
def get_image_tag(self) -> str:
""" Get Insyde iFlash image tag """
return self.ImageTag.decode('utf-8', 'ignore').strip('_')
def struct_print(self, padd: int) -> None:
""" Display structure information """
printer(['Signature :', self.Signature.decode('utf-8')], padd, False)
printer(['Image Name:', self.get_image_tag()], padd, False)
printer(['Image Size:', f'0x{self.ImageSize:X}'], padd, False)
printer(['Total Size:', f'0x{self.TotalSize:X}'], padd, False)
printer(['Padd Size :', f'0x{self._get_padd_len():X}'], padd, False)
def is_insyde_ifd(input_object: str | bytes | bytearray) -> bool:
""" Check if input is Insyde iFlash/iFdPacker Update image """
input_buffer: bytes = file_to_bytes(input_object)
is_ifl: bool = bool(insyde_iflash_detect(input_buffer))
is_sfx: bool = bool(PAT_INSYDE_SFX.search(input_buffer))
return is_ifl or is_sfx
def insyde_ifd_extract(input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Extract Insyde iFlash/iFdPacker Update images """
input_buffer: bytes = file_to_bytes(input_object)
iflash_code: int = insyde_iflash_extract(input_buffer, extract_path, padding)
ifdpack_path: str = os.path.join(extract_path, 'Insyde iFdPacker SFX')
ifdpack_code: int = insyde_packer_extract(input_buffer, ifdpack_path, padding)
return iflash_code and ifdpack_code
def insyde_iflash_detect(input_buffer: bytes) -> list:
""" Detect Insyde iFlash Update image """
iflash_match_all: list = []
iflash_match_nan: list = [0x0, 0xFFFFFFFF]
for iflash_match in PAT_INSYDE_IFL.finditer(input_buffer):
ifl_bgn: int = 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
def insyde_iflash_extract(input_buffer: bytes, extract_path: str, padding: int = 0) -> int:
""" Extract Insyde iFlash Update image """
insyde_iflash_all: list = 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: list = []
for insyde_iflash in insyde_iflash_all:
exit_code: int = 0
ifl_bgn, ifl_hdr = insyde_iflash
img_bgn: int = ifl_bgn + INS_IFL_LEN
img_end: int = img_bgn + ifl_hdr.ImageSize
img_bin: bytes = input_buffer[img_bgn:img_end]
if len(img_bin) != ifl_hdr.ImageSize:
exit_code = 1
img_val: list = [ifl_hdr.get_image_tag(), 'bin']
img_tag, img_ext = INS_IFL_IMG.get(img_val[0], img_val)
img_name: str = 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: str = f'{img_name}.{img_ext}'
out_path: str = 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)
def insyde_packer_extract(input_buffer: bytes, extract_path: str, padding: int = 0) -> int:
""" Extract Insyde iFdPacker 7-Zip SFX 7z Update image """
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 = 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: str = 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: int = 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: str = '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: dict = {
'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: int = ctypes.sizeof(IflashHeader)
if __name__ == '__main__':
BIOSUtility(title=TITLE, check=is_insyde_ifd, main=insyde_ifd_extract).run_utility()

View file

@ -1,229 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Panasonic BIOS Extract
Panasonic BIOS Package Extractor
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import io
import logging
import os
from re import Match
import pefile
from dissect.util.compression import lznt1
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_desc, get_pe_file, 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
TITLE = 'Panasonic BIOS Package Extractor v4.0'
def is_panasonic_pkg(input_object: str | bytes | bytearray) -> bool:
""" Check if input is Panasonic BIOS Package PE """
pe_file: pefile.PE | None = get_pe_file(input_object, silent=True)
if not pe_file:
return False
if get_pe_desc(pe_file, silent=True).decode('utf-8', 'ignore').upper() not in (PAN_PE_DESC_UNP, PAN_PE_DESC_UPD):
return False
return True
def panasonic_pkg_name(input_object: str | bytes | bytearray) -> str:
""" Get Panasonic BIOS Package file name, when applicable """
if isinstance(input_object, str) and os.path.isfile(input_object):
return safe_name(path_stem(input_object))
return ''
def panasonic_cab_extract(input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> str:
""" Search and Extract Panasonic BIOS Package PE CAB archive """
input_data: bytes = file_to_bytes(input_object)
cab_match: Match[bytes] | None = PAT_MICROSOFT_CAB.search(input_data)
if cab_match:
cab_bgn: int = cab_match.start()
cab_end: int = cab_bgn + int.from_bytes(input_data[cab_bgn + 0x8:cab_bgn + 0xC], 'little')
cab_tag: str = f'[0x{cab_bgn:06X}-0x{cab_end:06X}]'
cab_path: str = os.path.join(extract_path, f'CAB_{cab_tag}.cab')
with open(cab_path, 'wb') as cab_file:
cab_file.write(input_data[cab_bgn:cab_end]) # 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
for extracted_file_path in get_path_files(extract_path):
extracted_pe_file: pefile.PE | None = get_pe_file(extracted_file_path, padding, silent=True)
if extracted_pe_file:
extracted_pe_desc: bytes = get_pe_desc(extracted_pe_file, silent=True)
if extracted_pe_desc.decode('utf-8', 'ignore').upper() == PAN_PE_DESC_UPD:
return extracted_file_path
return ''
def panasonic_res_extract(pe_file: pefile.PE, extract_path: str, pe_name: str = '', padding: int = 0) -> bool:
""" Extract & Decompress Panasonic BIOS Update PE RCDATA (LZNT1) """
is_rcdata: bool = 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: int = resource.directory.entries[0].data.struct.OffsetToData
res_len: int = resource.directory.entries[0].data.struct.Size
res_end: int = res_bgn + res_len
res_bin: bytes = pe_file.get_data(res_bgn, res_len)
res_tag: str = f'{pe_name} [0x{res_bgn:06X}-0x{res_end:06X}]'.strip()
res_out: str = os.path.join(extract_path, f'{res_tag}')
printer(res_tag, padding)
try:
res_raw: bytes = lznt1.decompress(res_bin[0x8:])
if len(res_raw) != int.from_bytes(res_bin[0x4:0x8], 'little'):
raise ValueError('LZNT1_DECOMPRESS_BAD_SIZE')
printer('Succesfull LZNT1 decompression via Dissect!', padding + 4)
except Exception as error: # pylint: disable=broad-except
logging.debug('Error: LZNT1 decompression of %s failed: %s', res_tag, error)
res_raw = res_bin
printer('Succesfull PE Resource extraction!', padding + 4)
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
if is_ami_pfat(res_raw):
pfat_dir: str = os.path.join(extract_path, res_tag)
parse_pfat_file(res_raw, pfat_dir, padding + 8)
else:
if is_pe_file(res_raw):
res_ext: str = '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: str = line.decode('utf-8', 'ignore').rstrip()
printer(line_text, padding + 8, new_line=False)
with open(f'{res_out}.{res_ext}', 'wb') as out_file:
out_file.write(res_raw)
return is_rcdata
def panasonic_img_extract(pe_file: pefile.PE, extract_path: str, pe_name: str = '', padding: int = 0) -> bool:
""" Extract Panasonic BIOS Update PE Data when RCDATA is not available """
pe_data: bytes = bytes(pe_file.__data__)
sec_bgn: int = pe_file.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY[
'IMAGE_DIRECTORY_ENTRY_SECURITY']].VirtualAddress
img_bgn: int = pe_file.OPTIONAL_HEADER.BaseOfData + pe_file.OPTIONAL_HEADER.SizeOfInitializedData
img_end: int = sec_bgn or len(pe_data)
img_bin: bytes = pe_data[img_bgn:img_end]
img_tag: str = f'{pe_name} [0x{img_bgn:X}-0x{img_end:X}]'.strip()
img_out: str = os.path.join(extract_path, f'{img_tag}.bin')
printer(img_tag, padding)
with open(img_out, 'wb') as out_img:
out_img.write(img_bin)
printer('Succesfull PE Data extraction!', padding + 4)
return bool(img_bin)
def panasonic_pkg_extract(input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Extract Panasonic BIOS Package PE """
upd_pe_file: pefile.PE | None = get_pe_file(input_object, padding)
upd_pe_name: str = panasonic_pkg_name(input_object)
printer(f'Panasonic BIOS Package > PE ({upd_pe_name})\n'.replace(' ()', ''), padding)
show_pe_info(upd_pe_file, padding + 4)
make_dirs(extract_path, delete=True)
upd_pe_path: str = panasonic_cab_extract(input_object, extract_path, padding + 8)
upd_padding: int = padding
if upd_pe_path:
upd_padding = padding + 16
upd_pe_name = panasonic_pkg_name(upd_pe_path)
printer(f'Panasonic BIOS Update > PE ({upd_pe_name})\n'.replace(' ()', ''), upd_padding)
upd_pe_file = get_pe_file(upd_pe_path, upd_padding)
show_pe_info(upd_pe_file, upd_padding + 4)
os.remove(upd_pe_path)
is_upd_extracted: bool = panasonic_res_extract(upd_pe_file, extract_path, upd_pe_name, upd_padding + 8)
if not is_upd_extracted:
is_upd_extracted = panasonic_img_extract(upd_pe_file, extract_path, upd_pe_name, upd_padding + 8)
return 0 if is_upd_extracted else 1
PAN_PE_DESC_UNP: str = 'UNPACK UTILITY'
PAN_PE_DESC_UPD: str = 'BIOS UPDATE'
if __name__ == '__main__':
BIOSUtility(title=TITLE, check=is_panasonic_pkg, main=panasonic_pkg_extract).run_utility()

View file

@ -1,276 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Phoenix TDK Extract
Phoenix TDK Packer Extractor
Copyright (C) 2021-2024 Plato Mavropoulos
"""
import ctypes
import logging
import lzma
import os
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
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
TITLE = 'Phoenix TDK Packer Extractor v3.0'
class PhoenixTdkHeader(ctypes.LittleEndianStructure):
""" Phoenix TDK Header """
_pack_ = 1
_fields_ = [
('Tag', Char * 8), # 0x00
('Size', UInt32), # 0x08
('Count', UInt32), # 0x0C
# 0x10
]
def _get_tag(self):
return self.Tag.decode('utf-8', 'ignore').strip()
def struct_print(self, padd):
""" Display structure information """
printer(['Tag :', self._get_tag()], padd, False)
printer(['Size :', f'0x{self.Size:X}'], padd, False)
printer(['Entries:', self.Count], padd, False)
class PhoenixTdkEntry(ctypes.LittleEndianStructure):
""" Phoenix TDK Entry """
_pack_ = 1
_fields_ = [
('Name', Char * 256), # 0x000
('Offset', UInt32), # 0x100
('Size', UInt32), # 0x104
('Compressed', UInt32), # 0x108
('Reserved', UInt32), # 0x10C
# 0x110
]
COMP = {0: 'None', 1: 'LZMA'}
def __init__(self, mz_base, *args, **kwargs):
super().__init__(*args, **kwargs)
self.mz_base = mz_base
def get_name(self):
""" Get TDK Entry decoded name """
return self.Name.decode('utf-8', 'replace').strip()
def get_offset(self):
""" Get TDK Entry absolute offset """
return self.mz_base + self.Offset
def get_compression(self):
""" Get TDK Entry compression type """
return self.COMP.get(self.Compressed, f'Unknown ({self.Compressed})')
def struct_print(self, padd):
""" Display structure information """
printer(['Name :', self.get_name()], padd, False)
printer(['Offset :', f'0x{self.get_offset():X}'], padd, False)
printer(['Size :', f'0x{self.Size:X}'], padd, False)
printer(['Compression:', self.get_compression()], padd, False)
printer(['Reserved :', f'0x{self.Reserved:X}'], padd, False)
def get_tdk_base(in_buffer, pack_off):
""" Get Phoenix TDK Executable (MZ) Base Offset """
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_match in mz_ord:
mz_off = mz_match.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:], silent=True)
# Parse detected MZ > PE > Info
pe_info = get_pe_info(pe_file, silent=True)
# Parse detected MZ > PE > Info > Product Name
pe_name = pe_info.get(b'ProductName', b'')
except Exception as error: # pylint: disable=broad-except
# Any error means no MZ > PE > Info > Product Name
logging.debug('Error: Invalid potential MZ > PE match at 0x%X: %s', pe_off, error)
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
def get_phoenix_tdk(in_buffer):
""" Scan input buffer for valid Phoenix TDK image """
# 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
def is_phoenix_tdk(in_file):
""" Check if input contains valid Phoenix TDK image """
buffer = file_to_bytes(in_file)
return bool(get_phoenix_tdk(buffer)[1] is not None)
def phoenix_tdk_extract(input_file, extract_path, padding=0):
""" Parse & Extract Phoenix Tools Development Kit (TDK) Packer """
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 as error: # pylint: disable=broad-except
printer(f'Error: Phoenix TDK Entry > LZMA decompression failed: {error}!\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=TITLE, check=is_phoenix_tdk, main=phoenix_tdk_extract).run_utility()

View file

@ -1,150 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Portwell EFI Extract
Portwell EFI Update Extractor
Copyright (C) 2021-2024 Plato Mavropoulos
"""
import logging
import os
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
TITLE = 'Portwell EFI Update Extractor v3.0'
FILE_NAMES = {
0: 'Flash.efi',
1: 'Fparts.txt',
2: 'Update.nsh',
3: 'Temp.bin',
4: 'SaveDmiData.efi'
}
def is_portwell_efi(in_file):
""" Check if input is Portwell EFI executable """
in_buffer = file_to_bytes(in_file)
try:
pe_buffer = get_portwell_pe(in_buffer)[1]
except Exception as error: # pylint: disable=broad-except
logging.debug('Error: Could not check if input is Portwell EFI executable: %s', error)
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)
def get_portwell_pe(in_buffer):
""" Get PE of Portwell EFI executable """
pe_file = get_pe_file(in_buffer, silent=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
def portwell_efi_extract(input_file, extract_path, padding=0):
""" Parse & Extract Portwell UEFI Unpacker """
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)
def get_unpacker_tag(input_buffer, pe_file):
""" Get Portwell UEFI Unpacker tag """
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
def parse_efi_files(extract_path, efi_files, padding):
""" Process Portwell UEFI Unpacker payload files """
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)
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=TITLE, check=is_portwell_efi, main=portwell_efi_extract).run_utility()

355
README.md
View file

@ -1,7 +1,163 @@
# BIOSUtilities
**Various BIOS Utilities for Modding/Research**
[BIOS Utilities News Feed](https://twitter.com/platomaniac)
## About
Various BIOS/UEFI-related utilities which aid in modding and/or research
## Usage
### Main
The "main" script provides a simple way to check and parse each of the user provided files against all utilities, in succession. It is ideal for quick drag & drop operations but lacks the finer control of the BIOSUtility method. If needed, a few options can be set, by using the command line:
``` bash
usage: main.py [-h] [--output-folder OUTPUT_FOLDER] [--pause-exit] input_files [input_files ...]
positional arguments:
input_files
options:
-h, --help show help message and exit
--output-folder OUTPUT_FOLDER extraction folder
--pause-exit pause on exit
```
``` bash
python ./main.py "/path/to/input/file.bin" --output-folder "/path/to/file extractions"
```
### BIOSUtility
Each utility is derived from a base template: BIOSUtility. The base BIOSUtility offers the following options, applicable to all utilities:
``` bash
usage: [-h] [-e] [-o OUTPUT_DIR] [paths ...]
positional arguments:
paths
options:
-h, --help show help message and exit
-e, --auto-exit skip user action prompts
-o OUTPUT_DIR, --output-dir OUTPUT_DIR output extraction directory
```
``` bash
python -m biosutilities.ami_pfat_extract -e "/path/to/input/file1.bin" "/path/to/input/file2.bin" "/path/to/input/folder/with/files/" -o "/path/to/output_directory"
```
If no arguments are provided, the BIOSUtility.run_utility() method gets executed, which will request the input and output paths from the user. If no output path is provided, the utility will use the parent directory of the first input file or fallback to the runtime execution directory.
``` bash
Enter input file or directory path: "C:\P5405CSA.303"
Enter output directory path: "C:\P5405CSA.303_output"
```
### Package
All utilities form the "biosutilities" python package, which can be installed from PyPi:
``` bash
python -m pip install --upgrade biosutilities
```
Installing the python package is the recommended way to call one or more utilities programatically, while fully controlling arguments and options.
``` python
from biosutilities.ami_pfat_extract import AmiPfatExtract
ami_pfat_extractor = AmiPfatExtract()
ami_pfat_extractor.check_format(input_object='/path/to/input/file.bin')
ami_pfat_extractor.parse_format(input_object='/path/to/input/file.bin')
```
``` python
from biosutilities.dell_pfs_extract import DellPfsExtract
dell_pfs_extractor = DellPfsExtract()
with open(file='/path/to/input/file.bin', mode='rb') as pfs_file:
pfs_buffer = pfs_file.read()
dell_pfs_extractor.check_format(input_object=pfs_buffer)
dell_pfs_extractor.parse_format(input_object=pfs_buffer)
```
It also allows to use directly the four public methods which are inherited by every utility from the base BIOSUtility class.
#### run_utility
Run utility after checking for supported format
``` python
run_utility(padding: int = 0) -> int
```
#### check_format
Check if input object is of specific supported format
``` python
check_format(input_object: str | bytes | bytearray) -> bool
```
#### parse_format
Process input object as a specific supported format
``` python
parse_format(input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int | None
```
#### show_version
Show title and version of utility
``` python
show_version(is_boxed: bool = True, padding: int = 0) -> None
```
## Requirements
There are two main types of requirements (dependencies), depending on the utility.
### Python Packages
Python packages can be installed via Pypi (e.g. pip)
``` bash
python -m pip install --upgrade -r requirements.txt
```
``` bash
python -m pip install --upgrade pefile
```
### External Executables / Python Scripts
External executables (e.g. 7-Zip, TianoCompress, UEFIFind) and Python scripts (e.g. BIOS Guard Script Tool) need to be found via PATH.
#### Linux
[Linux Path](https://www.digitalocean.com/community/tutorials/how-to-view-and-update-the-linux-path-environment-variable)
or
``` bash
sudo install "/path/to/downloaded/external/executable/to/install"
```
#### Windows
[Windows Path](https://www.computerhope.com/issues/ch000549.htm)
#### MacOS
[Mac Path](https://mac.install.guide/terminal/path)
## Utilities
* [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor)
* [**AMI UCP Update Extractor**](#ami-ucp-update-extractor)
@ -20,23 +176,17 @@
* [**Toshiba BIOS COM Extractor**](#toshiba-bios-com-extractor)
* [**VAIO Packaging Manager Extractor**](#vaio-packaging-manager-extractor)
## **AMI BIOS Guard Extractor**
### **AMI BIOS Guard Extractor**
#### **Description**
Parses AMI BIOS Guard (a.k.a. PFAT, Platform Firmware Armoring Technology) images, extracts their SPI/BIOS/UEFI firmware components and decompiles the Intel BIOS Guard Scripts. It supports all AMI PFAT revisions and formats, including those with Index Information tables or nested AMI PFAT structures. The output comprises only final firmware components which are directly usable by end users.
Parses AMI BIOS Guard (a.k.a. PFAT, Platform Firmware Armoring Technology) images, extracts their SPI/BIOS/UEFI firmware components and optionally decompiles the Intel BIOS Guard Scripts. It supports all AMI PFAT revisions and formats, including those with Index Information tables or nested AMI PFAT structures. The output comprises only final firmware components which are directly usable by end users.
Note that the AMI PFAT structure may not have an explicit component order. AMI's BIOS Guard Firmware Update Tool (AFUBGT) updates components based on the user/OEM provided Parameters and Options or Index Information table, when applicable. That means that merging all the components together does not usually yield a proper SPI/BIOS/UEFI image. The utility does generate such a merged file with the name "00 -- \<filename\>\_ALL.bin" but it is up to the end user to determine its usefulness. Moreover, any custom OEM data after the AMI PFAT structure are additionally stored in the last file with the name "\<n+1\> -- \_OOB.bin" and it is once again up to the end user to determine its usefulness. In cases where the trailing custom OEM data include a nested AMI PFAT structure, the utility will process and extract it automatically as well.
Note that the AMI PFAT structure may not have an explicit component order. AMI's BIOS Guard Firmware Update Tool (AFUBGT) updates components based on the user/OEM provided Parameters and Options or Index Information table, when applicable. Thus, merging all the components together does not usually yield a proper SPI/BIOS/UEFI image. The utility does generate such a merged file with the name "00 -- \<filename\>\_ALL.bin" but it is up to the end user to determine its usefulness. Additionally, any custom OEM data, after the AMI PFAT structure, is stored in the last file with the name "\<n+1\> -- \_OOB.bin" and it is once again up to the end user to determine its usefulness. In cases where the trailing custom OEM data includes a nested AMI PFAT structure, the utility will process and extract it automatically as well.
#### **Usage**
You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -44,11 +194,11 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
#### **Prerequisites**
Optionally, to decompile the AMI PFAT \> Intel BIOS Guard Scripts, you must have the following 3rd party utility at the "external" project directory:
Optionally, to decompile the AMI PFAT \> Intel BIOS Guard Scripts, you must have the following 3rd party python script at PATH:
* [BIOS Guard Script Tool](https://github.com/platomav/BGScriptTool) (i.e. big_script_tool.py)
## **AMI UCP Update Extractor**
### **AMI UCP Update Extractor**
#### **Description**
@ -56,13 +206,8 @@ Parses AMI UCP (Utility Configuration Program) Update executables, extracts thei
#### **Usage**
You can either Drag & Drop or manually enter AMI UCP Update executable file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
Additional optional arguments are provided for this utility:
* -c or --checksum : verify AMI UCP Checksums (slow)
#### **Compatibility**
@ -71,16 +216,16 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
#### **Prerequisites**
To run the utility, you must have the following 3rd party tools at the "external" project directory:
To run the utility, you must have the following 3rd party tools at PATH:
* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (i.e. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux)
* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux)
Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (when applicable), you must have the following 3rd party utility at the "external" project directory:
Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (when applicable), you must have the following 3rd party python script at PATH:
* [BIOS Guard Script Tool](https://github.com/platomav/BGScriptTool) (i.e. big_script_tool.py)
## **Apple EFI IM4P Splitter**
### **Apple EFI IM4P Splitter**
#### **Description**
@ -88,13 +233,7 @@ Parses Apple IM4P multi-EFI files and splits all detected EFI firmware into sepa
#### **Usage**
You can either Drag & Drop or manually enter Apple EFI IM4P file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -104,7 +243,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
To run the utility, you do not need any prerequisites.
## **Apple EFI Image Identifier**
### **Apple EFI Image Identifier**
#### **Description**
@ -112,14 +251,7 @@ Parses Apple EFI images and identifies them based on Intel's official $IBIOSI$ t
#### **Usage**
You can either Drag & Drop or manually enter Apple EFI image file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
* -r or --rename : rename EFI image based on its tag
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -127,12 +259,12 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
#### **Prerequisites**
To run the utility, you must have the following 3rd party tools at the "external" project directory:
To run the utility, you must have the following 3rd party tools at PATH:
* [UEFIFind](https://github.com/LongSoft/UEFITool/) (i.e. [UEFIFind.exe for Windows or UEFIFind for Linux](https://github.com/LongSoft/UEFITool/releases))
* [UEFIExtract](https://github.com/LongSoft/UEFITool/) (i.e. [UEFIExtract.exe for Windows or UEFIExtract for Linux](https://github.com/LongSoft/UEFITool/releases))
## **Apple EFI Package Extractor**
### **Apple EFI Package Extractor**
#### **Description**
@ -140,13 +272,7 @@ Parses Apple EFI PKG firmware packages (i.e. FirmwareUpdate.pkg, BridgeOSUpdateC
#### **Usage**
You can either Drag & Drop or manually enter Apple EFI PKG package file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -154,11 +280,11 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
#### **Prerequisites**
To run the utility, you must have the following 3rd party tools at the "external" project directory:
To run the utility, you must have the following 3rd party tools at PATH:
* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux)
## **Apple EFI PBZX Extractor**
### **Apple EFI PBZX Extractor**
#### **Description**
@ -166,13 +292,7 @@ Parses Apple EFI PBZX images, re-assembles their CPIO payload and extracts its f
#### **Usage**
You can either Drag & Drop or manually enter Apple EFI PBZX image file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -180,11 +300,11 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
#### **Prerequisites**
To run the utility, you must have the following 3rd party tools at the "external" project directory:
To run the utility, you must have the following 3rd party tools at PATH:
* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux)
## **Award BIOS Module Extractor**
### **Award BIOS Module Extractor**
#### **Description**
@ -192,13 +312,7 @@ Parses Award BIOS images and extracts their modules (e.g. RAID, MEMINIT, \_EN_CO
#### **Usage**
You can either Drag & Drop or manually enter Award BIOS image file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -206,11 +320,11 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
#### **Prerequisites**
To run the utility, you must have the following 3rd party tool at the "external" project directory:
To run the utility, you must have the following 3rd party tool at PATH:
* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux)
## **Dell PFS Update Extractor**
### **Dell PFS Update Extractor**
#### **Description**
@ -218,13 +332,8 @@ Parses Dell PFS Update images and extracts their Firmware (e.g. SPI, BIOS/UEFI,
#### **Usage**
You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
Additional optional arguments are provided for this utility:
* -a or --advanced : extract signatures and metadata
* -s or --structure : show PFS structure information
@ -234,11 +343,11 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
#### **Prerequisites**
Optionally, to decompile the Intel BIOS Guard (PFAT) Scripts, you must have the following 3rd party utility at the "external" project directory:
Optionally, to decompile the Intel BIOS Guard (PFAT) Scripts, you must have the following 3rd party utility at PATH:
* [BIOS Guard Script Tool](https://github.com/platomav/BGScriptTool) (i.e. big_script_tool.py)
## **Fujitsu SFX BIOS Extractor**
### **Fujitsu SFX BIOS Extractor**
#### **Description**
@ -246,13 +355,7 @@ Parses Fujitsu SFX BIOS images and extracts their obfuscated Microsoft CAB archi
#### **Usage**
You can either Drag & Drop or manually enter Fujitsu SFX BIOS image file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -260,11 +363,11 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
#### **Prerequisites**
To run the utility, you must have the following 3rd party tool at the "external" project directory:
To run the utility, you must have the following 3rd party tool at PATH:
* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux)
## **Fujitsu UPC BIOS Extractor**
### **Fujitsu UPC BIOS Extractor**
#### **Description**
@ -272,13 +375,7 @@ Parses Fujitsu UPC BIOS images and extracts their EFI compressed SPI/BIOS/UEFI f
#### **Usage**
You can either Drag & Drop or manually enter Fujitsu UPC BIOS image file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -286,11 +383,11 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
#### **Prerequisites**
To run the utility, you must have the following 3rd party tool at the "external" project directory:
To run the utility, you must have the following 3rd party tool at PATH:
* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (i.e. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux)
## **Insyde iFlash/iFdPacker Extractor**
### **Insyde iFlash/iFdPacker Extractor**
#### **Description**
@ -298,13 +395,7 @@ Parses Insyde iFlash/iFdPacker Update images and extracts their firmware (e.g. S
#### **Usage**
You can either Drag & Drop or manually enter Insyde iFlash/iFdPacker Update image file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -314,7 +405,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
To run the utility, you do not need any prerequisites.
## **Panasonic BIOS Package Extractor**
### **Panasonic BIOS Package Extractor**
#### **Description**
@ -322,13 +413,7 @@ Parses Panasonic BIOS Package executables and extracts their firmware (e.g. SPI,
#### **Usage**
You can either Drag & Drop or manually enter Panasonic BIOS Package executable file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -341,11 +426,11 @@ To run the utility, you must have the following 3rd party Python modules install
* [pefile](https://pypi.org/project/pefile/)
* [dissect.util](https://pypi.org/project/dissect.util/)
Moreover, you must have the following 3rd party tool at the "external" project directory:
Moreover, you must have the following 3rd party tool at PATH:
* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux)
## **Phoenix TDK Packer Extractor**
### **Phoenix TDK Packer Extractor**
#### **Description**
@ -353,13 +438,7 @@ Parses Phoenix Tools Development Kit (TDK) Packer executables and extracts their
#### **Usage**
You can either Drag & Drop or manually enter Phoenix Tools Development Kit (TDK) Packer executable file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -371,7 +450,7 @@ To run the utility, you must have the following 3rd party Python module installe
* [pefile](https://pypi.org/project/pefile/)
## **Portwell EFI Update Extractor**
### **Portwell EFI Update Extractor**
#### **Description**
@ -379,13 +458,7 @@ Parses Portwell UEFI Unpacker EFI executables (usually named "Update.efi") and e
#### **Usage**
You can either Drag & Drop or manually enter Portwell UEFI Unpacker EFI executable file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -397,11 +470,11 @@ To run the utility, you must have the following 3rd party Python module installe
* [pefile](https://pypi.org/project/pefile/)
Moreover, you must have the following 3rd party tool at the "external" project directory:
Moreover, you must have the following 3rd party tool at PATH:
* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (i.e. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux)
## **Toshiba BIOS COM Extractor**
### **Toshiba BIOS COM Extractor**
#### **Description**
@ -409,13 +482,7 @@ Parses Toshiba BIOS COM images and extracts their raw or compressed SPI/BIOS/UEF
#### **Usage**
You can either Drag & Drop or manually enter Toshiba BIOS COM image file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -423,11 +490,11 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
#### **Prerequisites**
To run the utility, you must have the following 3rd party tool at the "external" project directory:
To run the utility, you must have the following 3rd party tool at PATH:
* [ToshibaComExtractor](https://github.com/LongSoft/ToshibaComExtractor) (i.e. [comextract.exe for Windows or comextract for Linux](https://github.com/LongSoft/ToshibaComExtractor/releases))
## **VAIO Packaging Manager Extractor**
### **VAIO Packaging Manager Extractor**
#### **Description**
@ -435,13 +502,7 @@ Parses VAIO Packaging Manager executables and extracts their firmware (e.g. SPI,
#### **Usage**
You can either Drag & Drop or manually enter VAIO Packaging Manager executable file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip all user action prompts
No additional optional arguments are provided for this utility.
#### **Compatibility**
@ -449,6 +510,6 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
#### **Prerequisites**
To run the utility, you must have the following 3rd party tool at the "external" project directory:
To run the utility, you must have the following 3rd party tool at PATH:
* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zzs for Linux)

View file

@ -1,60 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Toshiba COM Extract
Toshiba BIOS COM Extractor
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import os
import subprocess
from common.externals import get_comextract_path
from common.path_ops import make_dirs, path_stem, safe_name
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
TITLE = 'Toshiba BIOS COM Extractor v4.0'
def is_toshiba_com(input_object: str | bytes | bytearray) -> bool:
""" Check if input is Toshiba BIOS COM image """
return bool(PAT_TOSHIBA_COM.search(file_to_bytes(input_object)))
def toshiba_com_extract(input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Extract Toshiba BIOS COM image """
make_dirs(extract_path, delete=True)
if isinstance(input_object, str) and os.path.isfile(input_object):
input_path: str = input_object
else:
input_path = os.path.join(extract_path, 'toshiba_bios.com')
with open(input_path, 'wb') as input_buffer:
input_buffer.write(file_to_bytes(input_object))
output_path: str = os.path.join(extract_path, f'{safe_name(path_stem(input_path))}_extracted.bin')
try:
subprocess.run([get_comextract_path(), input_path, output_path], check=True, stdout=subprocess.DEVNULL)
if not os.path.isfile(output_path):
raise FileNotFoundError('EXTRACTED_FILE_MISSING')
except Exception as error: # pylint: disable=broad-except
printer(f'Error: ToshibaComExtractor could not extract {input_path}: {error}!', padding)
return 1
printer('Succesfull extraction via ToshibaComExtractor!', padding)
return 0
if __name__ == '__main__':
BIOSUtility(title=TITLE, check=is_toshiba_com, main=toshiba_com_extract).run_utility()

View file

@ -1,164 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
VAIO Package Extractor
VAIO Packaging Manager Extractor
Copyright (C) 2019-2024 Plato Mavropoulos
"""
import os
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
TITLE = 'VAIO Packaging Manager Extractor v4.0'
def is_vaio_pkg(in_file):
""" Check if input is VAIO Packaging Manager """
buffer = file_to_bytes(in_file)
return bool(PAT_VAIO_CFG.search(buffer))
def vaio_cabinet(name, buffer, extract_path, padding=0):
""" Extract VAIO Packaging Manager executable """
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 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 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, 'VAIO CAB', padding + 8, check=True) == 0:
os.remove(cab_path) # Successful extraction, delete temporary CAB archive
else:
return 3
else:
return 2
return 0
def vaio_unlock(name, buffer, extract_path, padding=0):
""" Unlock VAIO Packaging Manager executable """
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
def vaio_pkg_extract(input_file, extract_path, padding=0):
""" Parse & Extract or Unlock VAIO Packaging Manager """
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=TITLE, check=is_vaio_pkg, main=vaio_pkg_extract).run_utility()

View file

@ -1,6 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2019-2024 Plato Mavropoulos
"""

View file

@ -0,0 +1,8 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2018-2024 Plato Mavropoulos
"""
__version__ = '24.10.01'

View file

@ -0,0 +1,477 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
AMI PFAT Extract
AMI BIOS Guard Extractor
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import ctypes
import os
import re
import struct
from typing import Any, Type
from biosutilities.common.externals import big_script_tool
from biosutilities.common.paths import extract_suffix, extract_folder, make_dirs, path_name, safe_name
from biosutilities.common.patterns import PAT_AMI_PFAT
from biosutilities.common.structs import CHAR, ctypes_struct, UINT8, UINT16, UINT32
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import bytes_to_hex, file_to_bytes, to_ordinal
class AmiBiosGuardHeader(ctypes.LittleEndianStructure):
""" AMI BIOS Guard Header """
_pack_ = 1
_fields_ = [
('Size', UINT32), # 0x00 Header + Entries
('Checksum', UINT32), # 0x04 ?
('Tag', CHAR * 8), # 0x04 _AMIPFAT
('Flags', UINT8) # 0x10 ?
# 0x11
]
def struct_print(self, padding: int = 0) -> None:
""" Display structure information """
printer(message=['Size :', f'0x{self.Size:X}'], padding=padding, new_line=False)
printer(message=['Checksum:', f'0x{self.Checksum:04X}'], padding=padding, new_line=False)
printer(message=['Tag :', self.Tag.decode(encoding='utf-8')], padding=padding, new_line=False)
printer(message=['Flags :', f'0x{self.Flags:02X}'], padding=padding, new_line=False)
class IntelBiosGuardHeader(ctypes.LittleEndianStructure):
""" Intel BIOS Guard Header """
_pack_ = 1
_fields_ = [
('BGVerMajor', UINT16), # 0x00
('BGVerMinor', UINT16), # 0x02
('PlatformID', UINT8 * 16), # 0x04
('Attributes', UINT32), # 0x14
('ScriptVerMajor', UINT16), # 0x16
('ScriptVerMinor', UINT16), # 0x18
('ScriptSize', UINT32), # 0x1C
('DataSize', UINT32), # 0x20
('BIOSSVN', UINT32), # 0x24
('ECSVN', UINT32), # 0x28
('VendorInfo', UINT32) # 0x2C
# 0x30
]
def get_platform_id(self) -> str:
""" Get Intel BIOS Guard Platform ID """
id_byte: bytes = bytes(self.PlatformID)
id_text: str = re.sub(r'[\n\t\r\x00 ]', '', id_byte.decode(encoding='utf-8', errors='ignore'))
id_hexs: str = f'{int.from_bytes(bytes=id_byte, byteorder="big"):0{0x10 * 2}X}'
id_guid: str = 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_hdr_marker(self) -> bytes:
""" Get Intel BIOS Guard Header Marker """
return struct.pack('<HH16B', self.BGVerMajor, self.BGVerMinor, *self.PlatformID)
def get_flags(self) -> tuple:
""" Get Intel BIOS Guard Header Attributes """
attr: IntelBiosGuardHeaderGetAttributes = IntelBiosGuardHeaderGetAttributes()
attr.asbytes = self.Attributes # pylint: disable=attribute-defined-outside-init
return attr.b.SFAM, attr.b.ProtectEC, attr.b.GFXMitDis, attr.b.FTU, attr.b.Reserved
def struct_print(self, padding: int = 0) -> None:
""" Display structure information """
no_yes: dict[int, str] = {0: 'No', 1: 'Yes'}
sfam, ec_opc, gfx_dis, ft_upd, attr_res = self.get_flags()
bg_version: str = f'{self.BGVerMajor}.{self.BGVerMinor}'
script_version: str = f'{self.ScriptVerMajor}.{self.ScriptVerMinor}'
printer(message=['BIOS Guard Version :', bg_version], padding=padding, new_line=False)
printer(message=['Platform Identity :', self.get_platform_id()], padding=padding, new_line=False)
printer(message=['Signed Flash Address Map :', no_yes[sfam]], padding=padding, new_line=False)
printer(message=['Protected EC OpCodes :', no_yes[ec_opc]], padding=padding, new_line=False)
printer(message=['Graphics Security Disable :', no_yes[gfx_dis]], padding=padding, new_line=False)
printer(message=['Fault Tolerant Update :', no_yes[ft_upd]], padding=padding, new_line=False)
printer(message=['Attributes Reserved :', f'0x{attr_res:X}'], padding=padding, new_line=False)
printer(message=['Script Version :', script_version], padding=padding, new_line=False)
printer(message=['Script Size :', f'0x{self.ScriptSize:X}'], padding=padding, new_line=False)
printer(message=['Data Size :', f'0x{self.DataSize:X}'], padding=padding, new_line=False)
printer(message=['BIOS Security Version Number:', f'0x{self.BIOSSVN:X}'], padding=padding, new_line=False)
printer(message=['EC Security Version Number :', f'0x{self.ECSVN:X}'], padding=padding, new_line=False)
printer(message=['Vendor Information :', f'0x{self.VendorInfo:X}'], padding=padding, new_line=False)
class IntelBiosGuardHeaderAttributes(ctypes.LittleEndianStructure):
""" Intel BIOS Guard Header Attributes """
_pack_ = 1
_fields_ = [
('SFAM', UINT32, 1), # Signed Flash Address Map
('ProtectEC', UINT32, 1), # Protected EC OpCodes
('GFXMitDis', UINT32, 1), # GFX Security Disable
('FTU', UINT32, 1), # Fault Tolerant Update
('Reserved', UINT32, 28) # Reserved/Unknown
]
class IntelBiosGuardHeaderGetAttributes(ctypes.Union):
""" Intel BIOS Guard Header Attributes Getter """
_pack_ = 1
_fields_ = [
('b', IntelBiosGuardHeaderAttributes),
('asbytes', UINT32)
]
class IntelBiosGuardSignatureHeader(ctypes.LittleEndianStructure):
""" Intel BIOS Guard Signature Header """
_pack_ = 1
_fields_ = [
('Unknown0', UINT32), # 0x000
('Unknown1', UINT32) # 0x004
# 0x8
]
def struct_print(self, padding: int = 0) -> None:
""" Display structure information """
printer(message=['Unknown 0:', f'0x{self.Unknown0:X}'], padding=padding, new_line=False)
printer(message=['Unknown 1:', f'0x{self.Unknown1:X}'], padding=padding, new_line=False)
class IntelBiosGuardSignatureRsa2k(ctypes.LittleEndianStructure):
""" Intel BIOS Guard Signature Block 2048-bit """
_pack_ = 1
_fields_ = [
('Modulus', UINT8 * 256), # 0x000
('Exponent', UINT32), # 0x100
('Signature', UINT8 * 256) # 0x104
# 0x204
]
def struct_print(self, padding: int = 0) -> None:
""" Display structure information """
modulus: str = f'{bytes_to_hex(in_buffer=self.Modulus, order="little", data_len=0x100, slice_len=32)} [...]'
exponent: str = f'0x{self.Exponent:X}'
signature: str = f'{bytes_to_hex(in_buffer=self.Signature, order="little", data_len=0x100, slice_len=32)} [...]'
printer(message=['Modulus :', modulus], padding=padding, new_line=False)
printer(message=['Exponent :', exponent], padding=padding, new_line=False)
printer(message=['Signature:', signature], padding=padding, new_line=False)
class IntelBiosGuardSignatureRsa3k(ctypes.LittleEndianStructure):
""" Intel BIOS Guard Signature Block 3072-bit """
_pack_ = 1
_fields_ = [
('Modulus', UINT8 * 384), # 0x000
('Exponent', UINT32), # 0x180
('Signature', UINT8 * 384) # 0x184
# 0x304
]
def struct_print(self, padding: int = 0) -> None:
""" Display structure information """
modulus: str = f'{int.from_bytes(bytes=self.Modulus, byteorder="little"):0{0x180 * 2}X}'[:64]
exponent: str = f'0x{self.Exponent:X}'
signature: str = f'{int.from_bytes(bytes=self.Signature, byteorder="little"):0{0x180 * 2}X}'[:64]
printer(message=['Modulus :', modulus], padding=padding, new_line=False)
printer(message=['Exponent :', exponent], padding=padding, new_line=False)
printer(message=['Signature:', signature], padding=padding, new_line=False)
class AmiPfatExtract(BIOSUtility):
""" AMI BIOS Guard Extractor """
TITLE: str = 'AMI BIOS Guard Extractor'
PFAT_AMI_HDR_LEN: int = ctypes.sizeof(AmiBiosGuardHeader)
PFAT_INT_HDR_LEN: int = ctypes.sizeof(IntelBiosGuardHeader)
PFAT_INT_SIG_HDR_LEN: int = ctypes.sizeof(IntelBiosGuardSignatureHeader)
PFAT_INT_SIG_R2K_LEN: int = ctypes.sizeof(IntelBiosGuardSignatureRsa2k)
PFAT_INT_SIG_R3K_LEN: int = ctypes.sizeof(IntelBiosGuardSignatureRsa3k)
PFAT_INT_SIG_MAX_LEN: int = PFAT_INT_SIG_HDR_LEN + PFAT_INT_SIG_R3K_LEN
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is AMI BIOS Guard """
input_buffer: bytes = file_to_bytes(in_object=input_object)
return bool(self._get_ami_pfat(input_object=input_buffer))
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Process and store AMI BIOS Guard output file """
input_buffer: bytes = file_to_bytes(in_object=input_object)
pfat_buffer: bytes = self._get_ami_pfat(input_object=input_buffer)
file_path: str = ''
all_blocks_dict: dict = {}
bg_sign_len: int = 0
extract_name: str = path_name(in_path=extract_path).removesuffix(extract_suffix())
make_dirs(in_path=extract_path, delete=True)
block_all, block_off, file_count = self._parse_pfat_hdr(buffer=pfat_buffer, padding=padding)
for block in block_all:
file_desc, file_name, _, _, _, file_index, block_index, block_count = block
if block_index == 0:
printer(message=file_desc, padding=padding + 4)
file_path = os.path.join(extract_path, self._get_file_name(index=file_index + 1, name=file_name))
all_blocks_dict[file_index] = b''
block_status: str = f'{block_index + 1}/{block_count}'
bg_hdr: Any = ctypes_struct(buffer=pfat_buffer, start_offset=block_off, class_object=IntelBiosGuardHeader)
printer(message=f'Intel BIOS Guard {block_status} Header:\n', padding=padding + 8)
bg_hdr.struct_print(padding=padding + 12)
bg_script_bgn: int = block_off + self.PFAT_INT_HDR_LEN
bg_script_end: int = bg_script_bgn + bg_hdr.ScriptSize
bg_data_bgn: int = bg_script_end
bg_data_end: int = bg_data_bgn + bg_hdr.DataSize
bg_data_bin: bytes = 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:
printer(message=f'Intel BIOS Guard {block_status} Signature:\n', padding=padding + 8)
# Manual BIOS Guard Signature length detection from Header pattern (e.g. Panasonic)
if bg_sign_len == 0:
bg_sign_sig: bytes = bg_hdr.get_hdr_marker()
bg_sign_lim: int = bg_data_end + self.PFAT_INT_SIG_MAX_LEN + len(bg_sign_sig)
bg_sign_len = pfat_buffer.find(bg_sign_sig, bg_data_end, bg_sign_lim) - bg_data_end
# Adjust next block to start after current block Data + Signature
block_off += self.parse_bg_sign(input_data=pfat_buffer, sign_offset=bg_data_end,
sign_length=bg_sign_len, print_info=True, padding=padding + 12)
printer(message=f'Intel BIOS Guard {block_status} Script:\n', padding=padding + 8)
_ = self.parse_bg_script(script_data=pfat_buffer[bg_script_bgn:bg_script_end], padding=padding + 12)
with open(file=file_path, mode='ab') as out_dat:
out_dat.write(bg_data_bin)
all_blocks_dict[file_index] += bg_data_bin
if block_index + 1 == block_count:
if self.check_format(input_object=all_blocks_dict[file_index]):
self.parse_format(input_object=all_blocks_dict[file_index],
extract_path=extract_folder(file_path), padding=padding + 8)
pfat_oob_data: bytes = pfat_buffer[block_off:] # Store out-of-bounds data after the end of PFAT files
pfat_oob_name: str = self._get_file_name(index=file_count + 1, name=f'{extract_name}_OOB.bin')
pfat_oob_path: str = os.path.join(extract_path, pfat_oob_name)
with open(file=pfat_oob_path, mode='wb') as out_oob:
out_oob.write(pfat_oob_data)
if self.check_format(input_object=pfat_oob_data):
self.parse_format(input_object=pfat_oob_data, extract_path=extract_folder(pfat_oob_path),
padding=padding)
in_all_data: bytes = b''.join([block[1] for block in sorted(all_blocks_dict.items())])
in_all_name: str = self._get_file_name(index=0, name=f'{extract_name}_ALL.bin')
in_all_path: str = os.path.join(extract_path, in_all_name)
with open(file=in_all_path, mode='wb') as out_all:
out_all.write(in_all_data + pfat_oob_data)
return 0
def parse_bg_sign(self, input_data: bytes, sign_offset: int, sign_length: int = 0,
print_info: bool = False, padding: int = 0) -> int:
""" Process Intel BIOS Guard Signature """
bg_sig_hdr: Any = ctypes_struct(buffer=input_data, start_offset=sign_offset,
class_object=IntelBiosGuardSignatureHeader)
if bg_sig_hdr.Unknown0 == 1:
bg_sig_rsa_struct: Any = IntelBiosGuardSignatureRsa2k # Unknown0/Unknown1 = 1,1
elif bg_sig_hdr.Unknown0 in (2, 3):
bg_sig_rsa_struct = IntelBiosGuardSignatureRsa3k # Unknown0/Unknown1 = 2/3, 3/5, 3/6
elif sign_length == self.PFAT_INT_SIG_HDR_LEN + self.PFAT_INT_SIG_R2K_LEN:
bg_sig_rsa_struct = IntelBiosGuardSignatureRsa2k
printer(message='Warning: Detected Intel BIOS Guard Signature 2K length via pattern!\n',
padding=padding, new_line=False)
elif sign_length == self.PFAT_INT_SIG_HDR_LEN + self.PFAT_INT_SIG_R3K_LEN:
bg_sig_rsa_struct = IntelBiosGuardSignatureRsa3k
printer(message='Warning: Detected Intel BIOS Guard Signature 3K length via pattern!\n',
padding=padding, new_line=False)
else:
bg_sig_rsa_struct = IntelBiosGuardSignatureRsa3k
printer(message='Error: Could not detect Intel BIOS Guard Signature length, assuming 3K!\n',
padding=padding, new_line=False)
bg_sig_rsa: Any = ctypes_struct(buffer=input_data, start_offset=sign_offset + self.PFAT_INT_SIG_HDR_LEN,
class_object=bg_sig_rsa_struct)
if print_info:
bg_sig_hdr.struct_print(padding=padding)
bg_sig_rsa.struct_print(padding=padding)
# Total size of Signature Header and RSA Structure
return self.PFAT_INT_SIG_HDR_LEN + ctypes.sizeof(bg_sig_rsa_struct)
@staticmethod
def _get_ami_pfat(input_object: str | bytes | bytearray) -> bytes:
""" Get actual AMI BIOS Guard buffer """
input_buffer: bytes = file_to_bytes(in_object=input_object)
match: re.Match[bytes] | None = PAT_AMI_PFAT.search(string=input_buffer)
return input_buffer[match.start() - 0x8:] if match else b''
@staticmethod
def _get_file_name(index: int, name: str) -> str:
""" Create AMI BIOS Guard output filename """
return safe_name(in_name=f'{index:02d} -- {name}')
@staticmethod
def parse_bg_script(script_data: bytes, padding: int = 0) -> int:
""" Process Intel BIOS Guard Script """
is_opcode_div: bool = len(script_data) % 8 == 0
if not is_opcode_div:
printer(message='Error: BIOS Guard script is not divisible by OpCode length!',
padding=padding, new_line=False)
return 1
is_begin_end: bool = script_data[:8] + script_data[-8:] == b'\x01' + b'\x00' * 7 + b'\xFF' + b'\x00' * 7
if not is_begin_end:
printer(message='Error: BIOS Guard script lacks Begin and/or End OpCodes!',
padding=padding, new_line=False)
return 2
big_script: Type | None = big_script_tool()
if not big_script:
printer(message='Note: BIOS Guard Script Tool optional dependency is missing!',
padding=padding, new_line=False)
return 3
script: list[str] = big_script(code_bytes=script_data).to_string().replace('\t', ' ').split('\n')
for opcode in script:
if opcode.endswith(('begin', 'end')):
spacing: int = padding
elif opcode.endswith(':'):
spacing = padding + 4
else:
spacing = padding + 12
operands: list[str] = [operand for operand in opcode.split(' ') if operand]
# Largest opcode length is 11 (erase64kblk) and largest operand length is 10 (0xAABBCCDD).
printer(message=f'{operands[0]:11s}{"".join((f" {o:10s}" for o in operands[1:]))}',
padding=spacing, new_line=False)
return 0
def _parse_pfat_hdr(self, buffer: bytes | bytearray, padding: int = 0) -> tuple:
""" Parse AMI BIOS Guard Header """
block_all: list = []
pfat_hdr: Any = ctypes_struct(buffer=buffer, start_offset=0, class_object=AmiBiosGuardHeader)
hdr_size: int = pfat_hdr.Size
hdr_data: bytes = buffer[self.PFAT_AMI_HDR_LEN:hdr_size]
hdr_text: list[str] = hdr_data.decode(encoding='utf-8').splitlines()
printer(message='AMI BIOS Guard Header:\n', padding=padding)
pfat_hdr.struct_print(padding=padding + 4)
hdr_title, *hdr_files = hdr_text
files_count: int = len(hdr_files)
hdr_tag, *hdr_indexes = hdr_title.split('II')
printer(message=hdr_tag + '\n', padding=padding + 4)
bgt_indexes: list = [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: list = entry.split(';')
info: list = entry_parts[0].split()
name: str = entry_parts[1]
flags: int = int(info[0])
param: str = info[1]
count: int = int(info[2])
order: str = to_ordinal(in_number=(bgt_indexes[index] if bgt_indexes else index) + 1)
desc = f'{name} (Index: {index + 1:02d}, Flash: {order}, ' \
f'Parameter: {param}, Flags: 0x{flags:X}, Blocks: {count})'
block_all += [(desc, name, order, param, flags, index, i, count) for i in range(count)]
for block in block_all:
if block[6] == 0:
printer(message=block[0], padding=padding + 8, new_line=False)
return block_all, hdr_size, files_count
if __name__ == '__main__':
AmiPfatExtract().run_utility()

View file

@ -0,0 +1,599 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
AMI UCP Extract
AMI UCP Update Extractor
Copyright (C) 2021-2024 Plato Mavropoulos
"""
import contextlib
import ctypes
import os
import re
import struct
from typing import Any
from biosutilities.common.checksums import checksum_16
from biosutilities.common.compression import efi_decompress, is_efi_compressed
from biosutilities.common.paths import agnostic_path, extract_folder, make_dirs, safe_name, safe_path
from biosutilities.common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG
from biosutilities.common.structs import CHAR, ctypes_struct, UINT8, UINT16, UINT32
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes, to_string
from biosutilities.ami_pfat_extract import AmiPfatExtract
from biosutilities.insyde_ifd_extract import InsydeIfdExtract
class UafHeader(ctypes.LittleEndianStructure):
""" UAF Header """
_pack_ = 1
_fields_ = [
('ModuleTag', CHAR * 4), # 0x00
('ModuleSize', UINT32), # 0x04
('Checksum', UINT16), # 0x08
('Unknown0', UINT8), # 0x0A
('Unknown1', UINT8), # 0x0A
('Reserved', UINT8 * 4) # 0x0C
# 0x10
]
def _get_reserved(self) -> str:
res_bytes: bytes = bytes(self.Reserved)
res_hex: str = f'0x{int.from_bytes(bytes=res_bytes, byteorder="big"):0{0x4 * 2}X}'
res_str: str = re.sub(r'[\n\t\r\x00 ]', '', res_bytes.decode(encoding='utf-8', errors='ignore'))
res_txt: str = f' ({res_str})' if len(res_str) else ''
return f'{res_hex}{res_txt}'
def struct_print(self, padding: int = 0) -> None:
""" Display structure information """
printer(message=['Tag :', self.ModuleTag.decode(encoding='utf-8')], padding=padding, new_line=False)
printer(message=['Size :', f'0x{self.ModuleSize:X}'], padding=padding, new_line=False)
printer(message=['Checksum :', f'0x{self.Checksum:04X}'], padding=padding, new_line=False)
printer(message=['Unknown 0 :', f'0x{self.Unknown0:02X}'], padding=padding, new_line=False)
printer(message=['Unknown 1 :', f'0x{self.Unknown1:02X}'], padding=padding, new_line=False)
printer(message=['Reserved :', self._get_reserved()], padding=padding, new_line=False)
class UafModule(ctypes.LittleEndianStructure):
""" UAF Module """
_pack_ = 1
_fields_ = [
('CompressSize', UINT32), # 0x00
('OriginalSize', UINT32) # 0x04
# 0x08
]
def struct_print(self, filename: str, description: str, padding: int = 0) -> None:
""" Display structure information """
printer(message=['Compress Size:', f'0x{self.CompressSize:X}'], padding=padding, new_line=False)
printer(message=['Original Size:', f'0x{self.OriginalSize:X}'], padding=padding, new_line=False)
printer(message=['Filename :', filename], padding=padding, new_line=False)
printer(message=['Description :', description], padding=padding, new_line=False)
class UiiHeader(ctypes.LittleEndianStructure):
""" UII Header """
_pack_ = 1
_fields_ = [
('UIISize', UINT16), # 0x00
('Checksum', UINT16), # 0x02
('UtilityVersion', UINT32), # 0x04 AFU|BGT (Unknown, Signed)
('InfoSize', UINT16), # 0x08
('SupportBIOS', UINT8), # 0x0A
('SupportOS', UINT8), # 0x0B
('DataBusWidth', UINT8), # 0x0C
('ProgramType', UINT8), # 0x0D
('ProgramMode', UINT8), # 0x0E
('SourceSafeRel', UINT8) # 0x0F
# 0x10
]
SBI: dict[int, str] = {1: 'ALL', 2: 'AMIBIOS8', 3: 'UEFI', 4: 'AMIBIOS8/UEFI'}
SOS: dict[int, str] = {1: 'DOS', 2: 'EFI', 3: 'Windows', 4: 'Linux', 5: 'FreeBSD', 6: 'MacOS', 128: 'Multi-OS'}
DBW: dict[int, str] = {1: '16b', 2: '16/32b', 3: '32b', 4: '64b'}
PTP: dict[int, str] = {1: 'Executable', 2: 'Library', 3: 'Driver'}
PMD: dict[int, str] = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'}
def struct_print(self, description: str, padding: int = 0) -> None:
""" Display structure information """
support_bios: str = self.SBI.get(self.SupportBIOS, f'Unknown ({self.SupportBIOS})')
support_os: str = self.SOS.get(self.SupportOS, f'Unknown ({self.SupportOS})')
data_bus_width: str = self.DBW.get(self.DataBusWidth, f'Unknown ({self.DataBusWidth})')
program_type: str = self.PTP.get(self.ProgramType, f'Unknown ({self.ProgramType})')
program_mode: str = self.PMD.get(self.ProgramMode, f'Unknown ({self.ProgramMode})')
printer(message=['UII Size :', f'0x{self.UIISize:X}'], padding=padding, new_line=False)
printer(message=['Checksum :', f'0x{self.Checksum:04X}'], padding=padding, new_line=False)
printer(message=['Tool Version :', f'0x{self.UtilityVersion:08X}'], padding=padding, new_line=False)
printer(message=['Info Size :', f'0x{self.InfoSize:X}'], padding=padding, new_line=False)
printer(message=['Supported BIOS:', support_bios], padding=padding, new_line=False)
printer(message=['Supported OS :', support_os], padding=padding, new_line=False)
printer(message=['Data Bus Width:', data_bus_width], padding=padding, new_line=False)
printer(message=['Program Type :', program_type], padding=padding, new_line=False)
printer(message=['Program Mode :', program_mode], padding=padding, new_line=False)
printer(message=['SourceSafe Tag:', f'{self.SourceSafeRel:02d}'], padding=padding, new_line=False)
printer(message=['Description :', description], padding=padding, new_line=False)
class DisHeader(ctypes.LittleEndianStructure):
""" DIS Header """
_pack_ = 1
_fields_ = [
('PasswordSize', UINT16), # 0x00
('EntryCount', UINT16), # 0x02
('Password', CHAR * 12) # 0x04
# 0x10
]
def struct_print(self, padding: int = 0) -> None:
""" Display structure information """
printer(message=['Password Size:', f'0x{self.PasswordSize:X}'], padding=padding, new_line=False)
printer(message=['Entry Count :', self.EntryCount], padding=padding, new_line=False)
printer(message=['Password :', self.Password.decode(encoding='utf-8')], padding=padding, new_line=False)
class DisModule(ctypes.LittleEndianStructure):
""" DIS Module """
_pack_ = 1
_fields_ = [
('EnabledDisabled', UINT8), # 0x00
('ShownHidden', UINT8), # 0x01
('Command', CHAR * 32), # 0x02
('Description', CHAR * 256) # 0x22
# 0x122
]
ENDIS: dict[int, str] = {0: 'Disabled', 1: 'Enabled'}
SHOWN: dict[int, str] = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'}
def struct_print(self, padding: int = 0) -> None:
""" Display structure information """
enabled_disabled: str = self.ENDIS.get(self.EnabledDisabled, f'Unknown ({self.EnabledDisabled})')
shown_hidden: str = self.SHOWN.get(self.ShownHidden, f'Unknown ({self.ShownHidden})')
command: str = self.Command.decode(encoding='utf-8').strip()
description: str = self.Description.decode(encoding='utf-8').strip()
printer(message=['State :', enabled_disabled], padding=padding, new_line=False)
printer(message=['Display :', shown_hidden], padding=padding, new_line=False)
printer(message=['Command :', command], padding=padding, new_line=False)
printer(message=['Description:', description], padding=padding, new_line=False)
class AmiUcpExtract(BIOSUtility):
""" AMI UCP Update Extractor """
TITLE: str = 'AMI UCP Update Extractor'
ARGUMENTS: list[tuple[list[str], dict[str, str]]] = [
(['-c', '--checksum'], {'help': 'verify AMI UCP Checksums (slow)', 'action': 'store_true'})
]
# Get common ctypes Structure Sizes
UAF_HDR_LEN: int = ctypes.sizeof(UafHeader)
UAF_MOD_LEN: int = ctypes.sizeof(UafModule)
DIS_HDR_LEN: int = ctypes.sizeof(DisHeader)
DIS_MOD_LEN: int = ctypes.sizeof(DisModule)
UII_HDR_LEN: int = ctypes.sizeof(UiiHeader)
# AMI UCP Tag Dictionary
UAF_TAG_DICT: dict[str, list[str]] = {
'@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', ''],
'@D64': ['amifldrv64.sys', 'amifldrv64.sys', '']
}
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is AMI UCP image """
buffer: bytes = file_to_bytes(in_object=input_object)
return bool(self._get_ami_ucp(input_object=buffer)[0])
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> None:
""" Parse & Extract AMI UCP structures """
input_buffer: bytes = file_to_bytes(in_object=input_object)
nal_dict: dict[str, tuple[str, str]] = {} # Initialize @NAL Dictionary per UCP
printer(message='Utility Configuration Program', padding=padding)
make_dirs(in_path=extract_path, delete=True)
# Get best AMI UCP Pattern match based on @UAF|@HPU Size
ucp_buffer, ucp_tag = self._get_ami_ucp(input_object=input_buffer)
# Parse @UAF|@HPU Header Structure
uaf_hdr: Any = ctypes_struct(buffer=ucp_buffer, start_offset=0, class_object=UafHeader)
printer(message=f'Utility Auxiliary File > {ucp_tag}:\n', padding=padding + 4)
uaf_hdr.struct_print(padding=padding + 8)
fake = struct.pack('<II', len(ucp_buffer), len(ucp_buffer)) # Generate UafModule Structure
# Parse @UAF|@HPU Module EFI Structure
uaf_mod: Any = ctypes_struct(buffer=fake, start_offset=0, class_object=UafModule)
uaf_name = self.UAF_TAG_DICT[ucp_tag][0] # Get @UAF|@HPU Module Filename
uaf_desc = self.UAF_TAG_DICT[ucp_tag][1] # Get @UAF|@HPU Module Description
# Print @UAF|@HPU Module EFI Info
uaf_mod.struct_print(filename=uaf_name, description=uaf_desc, padding=padding + 8)
if self.arguments.checksum:
self._chk16_validate(data=ucp_buffer, tag=ucp_tag, padding=padding + 8)
uaf_all = self._get_uaf_mod(buffer=ucp_buffer, uaf_off=self.UAF_HDR_LEN)
for mod_info in uaf_all:
nal_dict = self._uaf_extract(buffer=ucp_buffer, extract_path=extract_path, mod_info=mod_info,
nal_dict=nal_dict, padding=padding + 8)
@staticmethod
def _chk16_validate(data: bytes | bytearray, tag: str, padding: int = 0) -> None:
""" Validate UCP Module Checksum-16 """
if checksum_16(data=data) != 0:
printer(message=f'Error: Invalid UCP Module {tag} Checksum!', padding=padding)
else:
printer(message=f'Checksum of UCP Module {tag} is valid!', padding=padding)
@staticmethod
def _get_ami_ucp(input_object: str | bytes | bytearray) -> tuple[bytes, str]:
""" Get all input file AMI UCP patterns """
buffer: bytes = file_to_bytes(in_object=input_object)
uaf_len_max: int = 0x0 # Length of largest detected @UAF|@HPU
uaf_buf_bin: bytes = b'' # Buffer of largest detected @UAF|@HPU
uaf_buf_tag: str = '@UAF' # Tag of largest detected @UAF|@HPU
for uaf in PAT_AMI_UCP.finditer(string=buffer):
uaf_len_cur: int = int.from_bytes(bytes=buffer[uaf.start() + 0x4:uaf.start() + 0x8], byteorder='little')
if uaf_len_cur > uaf_len_max:
uaf_len_max = uaf_len_cur
uaf_buf_bin = buffer[uaf.start():uaf.start() + uaf_len_max]
uaf_buf_tag = uaf.group(0)[:4].decode(encoding='utf-8', errors='ignore')
return uaf_buf_bin, uaf_buf_tag
@staticmethod
def _get_uaf_mod(buffer: bytes | bytearray, uaf_off: int = 0x0) -> list[list]:
""" Get list of @UAF|@HPU Modules """
uaf_all: list[list] = [] # Initialize list of all @UAF|@HPU Modules
while buffer[uaf_off] == 0x40: # ASCII of @ is 0x40
# Parse @UAF|@HPU Module Structure
uaf_hdr: Any = ctypes_struct(buffer=buffer, start_offset=uaf_off, class_object=UafHeader)
uaf_tag: str = uaf_hdr.ModuleTag.decode(encoding='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(iterable=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
def _uaf_extract(self, buffer: bytes | bytearray, extract_path: str, mod_info: list,
nal_dict: dict[str, tuple[str, str]], padding: int = 0) -> dict[str, tuple[str, str]]:
""" Parse & Extract AMI UCP > @UAF|@HPU Module/Section """
uaf_tag: str = mod_info[0]
uaf_off: int = mod_info[1]
uaf_hdr: Any = mod_info[2]
uaf_data_all: bytes = buffer[uaf_off:uaf_off + uaf_hdr.ModuleSize] # @UAF|@HPU Module Entire Data
uaf_data_mod: bytes = uaf_data_all[self.UAF_HDR_LEN:] # @UAF|@HPU Module EFI Data
uaf_data_raw: bytes = uaf_data_mod[self.UAF_MOD_LEN:] # @UAF|@HPU Module Raw Data
printer(message=f'Utility Auxiliary File > {uaf_tag}:\n', padding=padding)
uaf_hdr.struct_print(padding=padding + 4) # Print @UAF|@HPU Module Info
# Parse UAF Module EFI Structure
uaf_mod: Any = ctypes_struct(buffer=buffer, start_offset=uaf_off + self.UAF_HDR_LEN, class_object=UafModule)
is_comp: bool = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect @UAF|@HPU Module EFI Compression
if uaf_tag in nal_dict:
uaf_name: str = nal_dict[uaf_tag][1] # Always prefer @NAL naming first
elif uaf_tag in self.UAF_TAG_DICT:
uaf_name = self.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: str = '' if uaf_name != uaf_tag else '.bin'
uaf_fdesc: str = self.UAF_TAG_DICT[uaf_tag][1] if uaf_tag in self.UAF_TAG_DICT else uaf_name
# Print @UAF|@HPU Module EFI Info
uaf_mod.struct_print(filename=uaf_name + uaf_fext, description=uaf_fdesc, padding=padding + 4)
# 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 self.UAF_TAG_DICT and \
not uaf_tag.startswith(('@ROM', '@R0', '@S0', '@DR', '@DS')):
printer(message=f'Note: Detected new AMI UCP Module {uaf_tag} ({nal_dict[uaf_tag][1]}) in @NAL!',
padding=padding + 4, pause=True)
# Generate @UAF|@HPU Module File name, depending on whether decompression will be required
uaf_sname: str = safe_name(in_name=uaf_name + ('.temp' if is_comp else uaf_fext))
if uaf_tag in nal_dict:
uaf_npath: str = safe_path(base_path=extract_path, user_paths=nal_dict[uaf_tag][0])
make_dirs(in_path=uaf_npath, exist_ok=True)
uaf_fname: str = safe_path(base_path=uaf_npath, user_paths=uaf_sname)
else:
uaf_fname = safe_path(base_path=extract_path, user_paths=uaf_sname)
if self.arguments.checksum:
self._chk16_validate(data=uaf_data_all, tag=uaf_tag, padding=padding + 4)
# Parse Utility Identification Information @UAF|@HPU Module (@UII)
if uaf_tag == '@UII':
# Parse @UII Module Raw Structure
info_hdr: Any = ctypes_struct(buffer=uaf_data_raw, start_offset=0, class_object=UiiHeader)
# @UII Module Info Data
info_data: bytes = uaf_data_raw[max(self.UII_HDR_LEN, info_hdr.InfoSize):info_hdr.UIISize]
# Get @UII Module Info/Description text field
info_desc: str = info_data.decode(encoding='utf-8', errors='ignore').strip('\x00 ')
printer(message='Utility Identification Information:\n', padding=padding + 4)
info_hdr.struct_print(description=info_desc, padding=padding + 8) # Print @UII Module Info
if self.arguments.checksum:
self._chk16_validate(data=uaf_data_raw, tag='@UII > Info', padding=padding + 8)
# Store/Save @UII Module Info in file
with open(file=uaf_fname[:-4] + '.txt', mode='a', encoding='utf-8') as uii_out:
with contextlib.redirect_stdout(uii_out):
info_hdr.struct_print(description=info_desc, padding=0) # 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: bytes = b'\x00' * (uaf_mod.CompressSize - len(uaf_data_raw))
# Add missing padding for decompression
uaf_data_raw = uaf_data_mod[:self.UAF_MOD_LEN] + uaf_data_raw + comp_padd
else:
# Add the EFI/Tiano Compression info before Raw Data
uaf_data_raw = uaf_data_mod[:self.UAF_MOD_LEN] + uaf_data_raw
else:
# No compression, extend to end of Original @UAF|@HPU Module size
uaf_data_raw = uaf_data_raw[:uaf_mod.OriginalSize]
# Store/Save @UAF|@HPU Module file
if uaf_tag != '@UII': # Skip @UII binary, already parsed
with open(file=uaf_fname, mode='wb') as uaf_out:
uaf_out.write(uaf_data_raw)
# @UAF|@HPU Module EFI/Tiano Decompression
if is_comp and is_efi_compressed(data=uaf_data_raw, strict=False):
# Decompressed @UAF|@HPU Module file path
dec_fname: str = uaf_fname.replace('.temp', uaf_fext)
if efi_decompress(in_path=uaf_fname, out_path=dec_fname, padding=padding + 4) == 0:
with open(file=dec_fname, mode='rb') as dec:
uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data
os.remove(path=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 self.UAF_TAG_DICT and self.UAF_TAG_DICT[uaf_tag][2] == 'Text':
printer(message=f'{self.UAF_TAG_DICT[uaf_tag][1]}:', padding=padding + 4)
printer(message=uaf_data_raw.decode(encoding='utf-8', errors='ignore'), padding=padding + 8)
# Parse Default Command Status @UAF|@HPU Module (@DIS)
if len(uaf_data_raw) and uaf_tag == '@DIS':
# Parse @DIS Module Raw Header Structure
dis_hdr: Any = ctypes_struct(buffer=uaf_data_raw, start_offset=0, class_object=DisHeader)
printer(message='Default Command Status Header:\n', padding=padding + 4)
dis_hdr.struct_print(padding=padding + 8) # Print @DIS Module Raw Header Info
# Store/Save @DIS Module Header Info in file
with open(file=uaf_fname[:-3] + 'txt', mode='a', encoding='utf-8') as dis:
with contextlib.redirect_stdout(dis):
dis_hdr.struct_print(padding=0) # Store @DIS Module Header Info
dis_data: bytes = uaf_data_raw[self.DIS_HDR_LEN:] # @DIS Module Entries Data
# Parse all @DIS Module Entries
for mod_idx in range(dis_hdr.EntryCount):
# Parse @DIS Module Raw Entry Structure
dis_mod: Any = ctypes_struct(buffer=dis_data, start_offset=mod_idx * self.DIS_MOD_LEN,
class_object=DisModule)
printer(message=f'Default Command Status Entry {mod_idx + 1:02d}/{dis_hdr.EntryCount:02d}:\n',
padding=padding + 8)
dis_mod.struct_print(padding=padding + 12) # Print @DIS Module Raw Entry Info
# Store/Save @DIS Module Entry Info in file
with open(file=uaf_fname[:-3] + 'txt', mode='a', encoding='utf-8') as dis:
with contextlib.redirect_stdout(dis):
printer(message=None)
dis_mod.struct_print(padding=4) # Store @DIS Module Entry Info
os.remove(path=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: list[str] = uaf_data_raw.decode(encoding='utf-8',
errors='ignore').replace('\r', '').strip().split('\n')
printer(message='AMI UCP Module Name List:\n', padding=padding + 4)
# Parse all @NAL Module Entries
for info in nal_info:
info_tag, info_value = info.split(':', 1)
# Print @NAL Module Tag-Path Info
printer(message=f'{info_tag} : {info_value}', padding=padding + 8, new_line=False)
# Split OS-agnostic path in parts
info_part: Any = agnostic_path(in_path=info_value).parts
# Get path without drive/root or file
info_path: str = to_string(in_object=info_part[1:-1], sep_char=os.sep)
# Get file from last path part
info_name: str = info_part[-1]
# Assign a file path & name to each Tag
nal_dict[info_tag] = (info_path, info_name)
# Parse Insyde BIOS @UAF|@HPU Module (@INS)
if uaf_tag == '@INS' and InsydeIfdExtract().check_format(input_object=uaf_fname):
# Generate extraction directory
ins_dir: str = os.path.join(extract_path, safe_name(in_name=f'{uaf_tag}_nested-IFD'))
if InsydeIfdExtract().parse_format(input_object=uaf_fname, extract_path=extract_folder(ins_dir),
padding=padding + 4) == 0:
os.remove(path=uaf_fname) # Delete raw nested Insyde IFD image after successful extraction
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
if AmiPfatExtract().check_format(input_object=uaf_data_raw):
pfat_dir: str = os.path.join(extract_path, safe_name(in_name=uaf_name))
AmiPfatExtract().parse_format(input_object=uaf_data_raw, extract_path=extract_folder(pfat_dir),
padding=padding + 4)
os.remove(path=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(string=uaf_data_raw):
printer(message='Intel Management Engine (ME) Firmware:\n', padding=padding + 4)
printer(message='Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer',
padding=padding + 8, new_line=False)
# Parse Nested AMI UCP image
if self.check_format(input_object=uaf_data_raw):
# Generate extraction directory
uaf_dir: str = os.path.join(extract_path, safe_name(in_name=f'{uaf_tag}_nested-UCP'))
self.parse_format(input_object=uaf_data_raw, extract_path=extract_folder(uaf_dir),
padding=padding + 4) # Call recursively
os.remove(path=uaf_fname) # Delete raw nested AMI UCP image after successful extraction
return nal_dict
if __name__ == '__main__':
AmiUcpExtract().run_utility()

View file

@ -0,0 +1,208 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Apple EFI ID
Apple EFI Image Identifier
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import ctypes
import logging
import os
import struct
import subprocess
import zlib
from re import Match
from typing import Any, Final
from biosutilities.common.externals import uefiextract_path, uefifind_path
from biosutilities.common.paths import delete_dirs, delete_file, path_suffixes, runtime_root
from biosutilities.common.patterns import PAT_APPLE_EFI
from biosutilities.common.structs import CHAR, ctypes_struct, UINT8
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
class IntelBiosId(ctypes.LittleEndianStructure):
"""
Intel BIOS ID Structure
https://github.com/tianocore/edk2-platforms/blob/master/Platform/Intel/BoardModulePkg/Include/Guid/BiosId.h
"""
_pack_ = 1
_fields_ = [
('Signature', CHAR * 8), # 0x00
('BoardID', UINT8 * 16), # 0x08
('Dot1', UINT8 * 2), # 0x18
('BoardExt', UINT8 * 6), # 0x1A
('Dot2', UINT8 * 2), # 0x20
('VersionMajor', UINT8 * 8), # 0x22
('Dot3', UINT8 * 2), # 0x2A
('BuildType', UINT8 * 2), # 0x2C
('VersionMinor', UINT8 * 4), # 0x2E
('Dot4', UINT8 * 2), # 0x32
('Year', UINT8 * 4), # 0x34
('Month', UINT8 * 4), # 0x38
('Day', UINT8 * 4), # 0x3C
('Hour', UINT8 * 4), # 0x40
('Minute', UINT8 * 4), # 0x44
('NullTerminator', UINT8 * 2) # 0x48
# 0x4A
]
@staticmethod
def _decode(field: bytes) -> str:
return struct.pack('B' * len(field), *field).decode(encoding='utf-16', errors='ignore').strip('\x00 ')
def get_bios_id(self) -> tuple:
""" Create Apple EFI BIOS ID """
board_id: str = self._decode(field=self.BoardID)
board_ext: str = self._decode(field=self.BoardExt)
version_major: str = self._decode(field=self.VersionMajor)
build_type: str = self._decode(field=self.BuildType)
version_minor: str = self._decode(field=self.VersionMinor)
build_year: str = self._decode(field=self.Year)
build_month: str = self._decode(field=self.Month)
build_day: str = self._decode(field=self.Day)
build_hour: str = self._decode(field=self.Hour)
build_minute: str = self._decode(field=self.Minute)
build_date: str = f'20{build_year}-{build_month}-{build_day}'
build_time: str = f'{build_hour}-{build_minute}'
return board_id, board_ext, version_major, build_type, version_minor, build_date, build_time
def struct_print(self, padding: int = 0) -> None:
""" Display structure information """
board_id, board_ext, version_major, build_type, version_minor, build_date, build_time = self.get_bios_id()
intel_id: str = self.Signature.decode(encoding='utf-8')
printer(message=['Intel Signature:', intel_id], padding=padding, new_line=False)
printer(message=['Board Identity: ', board_id], padding=padding, new_line=False)
printer(message=['Apple Identity: ', board_ext], padding=padding, new_line=False)
printer(message=['Major Version: ', version_major], padding=padding, new_line=False)
printer(message=['Minor Version: ', version_minor], padding=padding, new_line=False)
printer(message=['Build Type: ', build_type], padding=padding, new_line=False)
printer(message=['Build Date: ', build_date], padding=padding, new_line=False)
printer(message=['Build Time: ', build_time.replace('-', ':')], padding=padding, new_line=False)
class AppleEfiIdentify(BIOSUtility):
""" Apple EFI Image Identifier """
TITLE: str = 'Apple EFI Image Identifier'
PAT_UEFIFIND: Final[str] = f'244942494F534924{"." * 32}2E00{"." * 12}2E00{"." * 16}2E00{"." * 12}2E00{"." * 40}0000'
def __init__(self, arguments: list[str] | None = None) -> None:
super().__init__(arguments=arguments)
self.efi_name_id: str = ''
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Apple EFI image """
input_buffer: bytes = file_to_bytes(in_object=input_object)
if PAT_APPLE_EFI.search(string=input_buffer):
return True
if isinstance(input_object, str) and os.path.isfile(path=input_object):
input_path: str = input_object
else:
input_path = os.path.join(runtime_root(), 'APPLE_EFI_ID_INPUT_BUFFER_CHECK.tmp')
with open(file=input_path, mode='wb') as check_out:
check_out.write(input_buffer)
try:
_ = subprocess.run([uefifind_path(), input_path, 'body', 'list', self.PAT_UEFIFIND],
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return True
except Exception as error: # pylint: disable=broad-except
logging.debug('Error: Could not check if input is Apple EFI image: %s', error)
return False
finally:
if input_path != input_object:
delete_file(in_path=input_path)
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Identify (or Rename) Apple EFI image """
input_buffer: bytes = file_to_bytes(in_object=input_object)
if isinstance(input_object, str) and os.path.isfile(path=input_object):
input_path: str = input_object
else:
input_path = os.path.join(runtime_root(), 'APPLE_EFI_ID_INPUT_BUFFER_PARSE.bin')
with open(file=input_path, mode='wb') as parse_out:
parse_out.write(input_buffer)
bios_id_match: Match[bytes] | None = PAT_APPLE_EFI.search(string=input_buffer)
if bios_id_match:
bios_id_res: str = f'0x{bios_id_match.start():X}'
bios_id_hdr: Any = ctypes_struct(buffer=input_buffer, start_offset=bios_id_match.start(),
class_object=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([uefifind_path(), input_path, 'body', 'list',
self.PAT_UEFIFIND], text=True)[:36]
# UEFIExtract must create its output folder itself
delete_dirs(in_path=extract_path)
_ = subprocess.run([uefiextract_path(), input_path, bios_id_res, '-o', extract_path, '-m', 'body'],
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
with open(file=os.path.join(extract_path, 'body.bin'), mode='rb') as raw_body:
body_buffer: bytes = raw_body.read()
# Detect decompressed $IBIOSI$ pattern
bios_id_match = PAT_APPLE_EFI.search(string=body_buffer)
if not bios_id_match:
raise RuntimeError('Failed to detect decompressed $IBIOSI$ pattern!')
bios_id_hdr = ctypes_struct(buffer=body_buffer, start_offset=bios_id_match.start(),
class_object=IntelBiosId)
delete_dirs(in_path=extract_path) # Successful UEFIExtract extraction, remove its output folder
except Exception as error: # pylint: disable=broad-except
printer(message=f'Error: Failed to parse compressed $IBIOSI$ pattern: {error}!', padding=padding)
return 1
printer(message=f'Detected $IBIOSI$ at {bios_id_res}\n', padding=padding)
bios_id_hdr.struct_print(padding=padding + 4)
input_suffix: str = path_suffixes(input_path)[-1]
input_adler32: int = zlib.adler32(input_buffer)
fw_id, fw_ext, fw_major, fw_type, fw_minor, fw_date, fw_time = bios_id_hdr.get_bios_id()
self.efi_name_id = (f'{fw_id}_{fw_ext}_{fw_major}_{fw_type}{fw_minor}_{fw_date}_{fw_time}_'
f'{input_adler32:08X}{input_suffix}')
if input_path != input_object:
delete_file(in_path=input_path)
return 0
if __name__ == '__main__':
AppleEfiIdentify().run_utility()

View file

@ -0,0 +1,157 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Apple EFI IM4P
Apple EFI IM4P Splitter
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import os
from re import Match
from typing import Final
from biosutilities.common.paths import make_dirs, path_stem
from biosutilities.common.patterns import PAT_APPLE_IM4P, PAT_INTEL_IFD
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
class AppleEfiIm4pSplit(BIOSUtility):
""" Apple EFI IM4P Splitter """
TITLE: str = 'Apple EFI IM4P Splitter'
# Intel Flash Descriptor Component Sizes (4MB, 8MB, 16MB and 32MB)
IFD_COMP_LEN: Final[dict[int, int]] = {3: 0x400000, 4: 0x800000, 5: 0x1000000, 6: 0x2000000}
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Apple EFI IM4P image """
input_buffer: bytes = file_to_bytes(in_object=input_object)
if PAT_APPLE_IM4P.search(string=input_buffer) and PAT_INTEL_IFD.search(string=input_buffer):
return True
return False
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Split Apple EFI IM4P image """
exit_codes: list[int] = []
input_buffer: bytes = file_to_bytes(in_object=input_object)
make_dirs(in_path=extract_path, delete=True)
# Detect IM4P EFI pattern
im4p_match: Match[bytes] | None = PAT_APPLE_IM4P.search(string=input_buffer)
if not im4p_match:
return 1
# 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: int = im4p_match.start() + input_buffer[im4p_match.start() - 0x1]
# IM4P mefi payload size
mefi_data_len: int = int.from_bytes(bytes=input_buffer[im4p_match.end() + 0x5:im4p_match.end() + 0x9],
byteorder='big')
# Check if mefi is followed by _MEFIBIN
mefibin_exist: bool = input_buffer[mefi_data_bgn:mefi_data_bgn + 0x8] == b'_MEFIBIN'
# Actual multi EFI payloads start after _MEFIBIN
efi_data_bgn: int = mefi_data_bgn + 0x100 if mefibin_exist else mefi_data_bgn
# Actual multi EFI payloads size without _MEFIBIN
efi_data_len: int = 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(string=input_buffer):
# Component Base Address from FD start (ICH8-ICH10 = 1, IBX = 2, CPT+ = 3)
ifd_flmap0_fcba: int = 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_subtruct: int = 0x0
# 0xBC for [0xAC] + 0xFF * 16 sanity check
ifd_end_subtruct: int = 0xBC
# Platform Controller Hub (PCH)
else:
# At PCH, Flash Descriptor starts at 0x10
ifd_bgn_subtruct = 0x10
# 0xBC for [0xAC] + 0xFF * 16 sanity check
ifd_end_subtruct = 0xBC
# Actual Flash Descriptor Start Offset
ifd_match_start: int = ifd.start() - ifd_bgn_subtruct
# Actual Flash Descriptor End Offset
ifd_match_end: int = ifd.end() - ifd_end_subtruct
# Calculate Intel Flash Descriptor Flash Component Total Size
# Component Count (00 = 1, 01 = 2)
ifd_flmap0_nc: int = ((int.from_bytes(bytes=input_buffer[ifd_match_end:ifd_match_end + 0x4],
byteorder='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: int = 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: int = 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: int = 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: int = 0x4 if ifd_flmap1_isl >= 0x13 else 0x3
# Component 1 Density (FCBA > C0DEN)
ifd_comp_all_size: int = self.IFD_COMP_LEN[ifd_comp_den & ifd_comp_1_bitwise]
# Component 2 Density (FCBA > C1DEN)
if ifd_flmap0_nc == 2:
ifd_comp_all_size += self.IFD_COMP_LEN[ifd_comp_den >> ifd_comp_2_bitwise]
ifd_data_bgn: int = ifd_match_start
ifd_data_end: int = ifd_data_bgn + ifd_comp_all_size
ifd_data_txt: str = f'0x{ifd_data_bgn:07X}-0x{ifd_data_end:07X}'
output_data: bytes = input_buffer[ifd_data_bgn:ifd_data_end]
output_size: int = len(output_data)
output_name: str = path_stem(in_path=input_object) if isinstance(input_object, str) else 'Part'
output_path: str = os.path.join(extract_path, f'{output_name}_[{ifd_data_txt}].fd')
with open(file=output_path, mode='wb') as output_image:
output_image.write(output_data)
printer(message=f'Split Apple EFI image at {ifd_data_txt}!', padding=padding)
if output_size != ifd_comp_all_size:
printer(message=f'Error: Bad image size 0x{output_size:07X}, expected 0x{ifd_comp_all_size:07X}!',
padding=padding + 4)
exit_codes.append(1)
return sum(exit_codes)
if __name__ == '__main__':
AppleEfiIm4pSplit().run_utility()

View file

@ -0,0 +1,133 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Apple PBZX Extract
Apple EFI PBZX Extractor
Copyright (C) 2021-2024 Plato Mavropoulos
"""
import ctypes
import logging
import lzma
import os
from typing import Any, Final
from biosutilities.common.compression import is_szip_supported, szip_decompress
from biosutilities.common.paths import make_dirs, path_stem
from biosutilities.common.patterns import PAT_APPLE_PBZX
from biosutilities.common.structs import ctypes_struct, UINT32
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
class PbzxChunk(ctypes.BigEndianStructure):
""" PBZX Chunk Header """
_pack_ = 1
_fields_ = [
('Reserved0', UINT32), # 0x00
('InitSize', UINT32), # 0x04
('Reserved1', UINT32), # 0x08
('CompSize', UINT32) # 0x0C
# 0x10
]
def struct_print(self, padding: int = 0) -> None:
""" Display structure information """
printer(message=['Reserved 0 :', f'0x{self.Reserved0:X}'], padding=padding, new_line=False)
printer(message=['Initial Size :', f'0x{self.InitSize:X}'], padding=padding, new_line=False)
printer(message=['Reserved 1 :', f'0x{self.Reserved1:X}'], padding=padding, new_line=False)
printer(message=['Compressed Size:', f'0x{self.CompSize:X}'], padding=padding, new_line=False)
class AppleEfiPbzxExtract(BIOSUtility):
""" Apple EFI PBZX Extractor """
TITLE: str = 'Apple EFI PBZX Extractor'
PBZX_CHUNK_HDR_LEN: Final[int] = ctypes.sizeof(PbzxChunk)
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Apple PBZX image """
input_buffer: bytes = file_to_bytes(in_object=input_object)
return bool(PAT_APPLE_PBZX.search(string=input_buffer[:0x4]))
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Extract Apple PBZX image """
input_buffer: bytes = file_to_bytes(in_object=input_object)
make_dirs(in_path=extract_path, delete=True)
cpio_bin: bytes = b'' # Initialize PBZX > CPIO Buffer
cpio_len: int = 0x0 # Initialize PBZX > CPIO Length
chunk_off: int = 0xC # First PBZX Chunk starts at 0xC
while chunk_off < len(input_buffer):
chunk_hdr: Any = ctypes_struct(buffer=input_buffer, start_offset=chunk_off, class_object=PbzxChunk)
printer(message=f'PBZX Chunk at 0x{chunk_off:08X}\n', padding=padding)
chunk_hdr.struct_print(padding=padding + 4)
# PBZX Chunk data starts after its Header
comp_bgn: int = chunk_off + self.PBZX_CHUNK_HDR_LEN
# To avoid a potential infinite loop, double-check Compressed Size
comp_end: int = comp_bgn + max(chunk_hdr.CompSize, self.PBZX_CHUNK_HDR_LEN)
comp_bin: bytes = input_buffer[comp_bgn:comp_end]
try:
# Attempt XZ decompression, if applicable to Chunk data
cpio_bin += lzma.LZMADecompressor().decompress(comp_bin)
printer(message='Successful LZMA decompression!', padding=padding + 8)
except Exception as error: # pylint: disable=broad-except
logging.debug('Error: Failed to LZMA decompress PBZX Chunk 0x%X: %s', chunk_off, error)
# 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(message='Error: Unexpected CPIO archive size!', padding=padding)
return 1
cpio_name: str = path_stem(in_path=input_object) if isinstance(input_object, str) else 'Payload'
cpio_path: str = os.path.join(extract_path, f'{cpio_name}.cpio')
with open(file=cpio_path, mode='wb') as cpio_object:
cpio_object.write(cpio_bin)
# Decompress PBZX > CPIO archive with 7-Zip
if is_szip_supported(in_path=cpio_path, padding=padding, args=['-tCPIO'], silent=False):
if szip_decompress(in_path=cpio_path, out_path=extract_path, in_name='CPIO',
padding=padding, args=['-tCPIO'], check=True) == 0:
os.remove(path=cpio_path) # Successful extraction, delete PBZX > CPIO archive
else:
return 3
else:
return 2
return 0
if __name__ == '__main__':
AppleEfiPbzxExtract().run_utility()

View file

@ -0,0 +1,189 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Apple EFI PKG
Apple EFI Package Extractor
Copyright (C) 2019-2024 Plato Mavropoulos
"""
import os
from biosutilities.common.compression import is_szip_supported, szip_decompress
from biosutilities.common.paths import (copy_file, delete_dirs, extract_folder, path_files,
make_dirs, path_name, path_parent, runtime_root)
from biosutilities.common.patterns import PAT_APPLE_PKG_TAR, PAT_APPLE_PKG_XAR
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
from biosutilities.apple_efi_id import AppleEfiIdentify
from biosutilities.apple_efi_im4p import AppleEfiIm4pSplit
from biosutilities.apple_efi_pbzx import AppleEfiPbzxExtract
class AppleEfiPkgExtract(BIOSUtility):
""" Apple EFI Package Extractor """
TITLE: str = 'Apple EFI Package Extractor'
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Apple EFI PKG package """
is_apple_efi_pkg: bool = False
input_buffer: bytes = file_to_bytes(in_object=input_object)
if isinstance(input_object, str) and os.path.isfile(path=input_object):
input_path: str = input_object
else:
input_path = os.path.join(runtime_root(), 'APPLE_EFI_PKG_INPUT_BUFFER_CHECK.bin')
with open(file=input_path, mode='wb') as input_path_object:
input_path_object.write(input_buffer)
if is_szip_supported(in_path=input_path, args=['-tXAR']):
if bool(PAT_APPLE_PKG_XAR.search(string=input_buffer, endpos=4)):
is_apple_efi_pkg = True
elif is_szip_supported(in_path=input_path, args=['-tTAR']):
if bool(PAT_APPLE_PKG_TAR.search(string=input_buffer)):
is_apple_efi_pkg = True
if input_path != input_object:
os.remove(path=input_path)
return is_apple_efi_pkg
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Extract Apple EFI PKG packages """
if isinstance(input_object, str) and os.path.isfile(path=input_object):
input_path: str = input_object
else:
input_path = os.path.join(runtime_root(), 'APPLE_EFI_PKG_INPUT_BUFFER_PARSE.bin')
with open(file=input_path, mode='wb') as input_path_object:
input_path_object.write(file_to_bytes(in_object=input_object))
make_dirs(in_path=extract_path, delete=True)
working_dir: str = os.path.join(extract_path, 'temp')
for pkg_type in ('XAR', 'TAR'):
if is_szip_supported(in_path=input_path, padding=padding, args=[f'-t{pkg_type}']):
if szip_decompress(in_path=input_path, out_path=working_dir, in_name=pkg_type, padding=padding,
args=[f'-t{pkg_type}'], check=True) == 0:
break
else:
return 1
if input_path != input_object:
os.remove(path=input_path)
for work_file in path_files(in_path=working_dir):
self._pbzx_zip(input_path=work_file, extract_path=extract_path, padding=padding + 4)
self._gzip_cpio(input_path=work_file, extract_path=extract_path, padding=padding + 4)
self._tar_gzip(input_path=work_file, extract_path=extract_path, padding=padding + 4)
delete_dirs(in_path=working_dir)
return 0
def _tar_gzip(self, input_path: str, extract_path: str, padding: int = 0) -> None:
""" TAR > GZIP """
if is_szip_supported(in_path=input_path, padding=padding, args=['-tTAR']):
tar_path: str = extract_folder(in_path=input_path)
if szip_decompress(in_path=input_path, out_path=tar_path, in_name='TAR', padding=padding,
args=['-tTAR'], check=False) == 0:
for tar_file in path_files(in_path=tar_path):
self._gzip_cpio(input_path=tar_file, extract_path=extract_path, padding=padding + 4)
def _pbzx_zip(self, input_path: str, extract_path: str, padding: int = 0) -> None:
""" PBZX > ZIP """
pbzx_module: AppleEfiPbzxExtract = AppleEfiPbzxExtract()
if pbzx_module.check_format(input_object=input_path):
printer(message=f'Extracting PBZX via {pbzx_module.title}', padding=padding)
pbzx_path: str = extract_folder(in_path=input_path)
if pbzx_module.parse_format(input_object=input_path, extract_path=pbzx_path, padding=padding + 4) == 0:
printer(message=f'Successful PBZX extraction via {pbzx_module.title}!', padding=padding)
for pbzx_file in path_files(in_path=pbzx_path):
if is_szip_supported(in_path=pbzx_file, padding=padding + 4, args=['-tZIP']):
zip_path: str = extract_folder(in_path=pbzx_file)
if szip_decompress(in_path=pbzx_file, out_path=zip_path, in_name='ZIP',
padding=padding + 4, args=['-tZIP'], check=False) == 0:
for zip_file in path_files(in_path=zip_path):
self._im4p_id(input_path=zip_file, output_path=extract_path, padding=padding + 8)
def _gzip_cpio(self, input_path: str, extract_path: str, padding: int = 0) -> None:
""" GZIP > CPIO """
if is_szip_supported(in_path=input_path, padding=padding, args=['-tGZIP']):
gzip_path: str = extract_folder(in_path=input_path)
if szip_decompress(in_path=input_path, out_path=gzip_path, in_name='GZIP', padding=padding,
args=['-tGZIP'], check=True) == 0:
for gzip_file in path_files(in_path=gzip_path):
if is_szip_supported(in_path=gzip_file, padding=padding + 4, args=['-tCPIO']):
cpio_path: str = extract_folder(in_path=gzip_file)
if szip_decompress(in_path=gzip_file, out_path=cpio_path, in_name='CPIO',
padding=padding + 4, args=['-tCPIO'], check=False) == 0:
for cpio_file in path_files(in_path=cpio_path):
self._im4p_id(input_path=cpio_file, output_path=extract_path, padding=padding + 8)
@staticmethod
def _im4p_id(input_path: str, output_path: str, padding: int = 0) -> None:
""" Split IM4P (if applicable), identify and rename EFI """
if not AppleEfiIdentify().check_format(input_object=input_path):
return None
input_name: str = path_name(in_path=input_path)
printer(message=input_name, padding=padding)
working_dir: str = extract_folder(in_path=input_path)
im4p_module: AppleEfiIm4pSplit = AppleEfiIm4pSplit()
if im4p_module.check_format(input_object=input_path):
printer(message=f'Splitting IM4P via {im4p_module.title}', padding=padding + 4)
im4p_module.parse_format(input_object=input_path, extract_path=working_dir, padding=padding + 8)
else:
make_dirs(in_path=working_dir, delete=True)
copy_file(in_path=input_path, out_path=working_dir, metadata=True)
for efi_source in path_files(in_path=working_dir):
efi_id_module: AppleEfiIdentify = AppleEfiIdentify()
if efi_id_module.check_format(input_object=efi_source):
printer(message=f'Identifying EFI via {efi_id_module.title}', padding=padding + 4)
efi_id_exit: int = efi_id_module.parse_format(
input_object=efi_source, extract_path=extract_folder(in_path=efi_source), padding=padding + 8)
if efi_id_exit == 0:
efi_dest: str = os.path.join(path_parent(in_path=efi_source), efi_id_module.efi_name_id)
os.rename(src=efi_source, dst=efi_dest)
for efi_final in path_files(in_path=working_dir):
copy_file(in_path=efi_final, out_path=output_path, metadata=True)
delete_dirs(in_path=working_dir)
return None
if __name__ == '__main__':
AppleEfiPkgExtract().run_utility()

View file

@ -0,0 +1,93 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Award BIOS Extract
Award BIOS Module Extractor
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import os
import stat
from biosutilities.common.compression import szip_decompress
from biosutilities.common.paths import extract_folder, make_dirs, safe_name
from biosutilities.common.patterns import PAT_AWARD_LZH
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
class AwardBiosExtract(BIOSUtility):
""" Award BIOS Module Extractor """
TITLE: str = 'Award BIOS Module Extractor'
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Award BIOS image """
in_buffer: bytes = file_to_bytes(in_object=input_object)
return bool(PAT_AWARD_LZH.search(string=in_buffer))
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> None:
""" Parse & Extract Award BIOS image """
input_buffer: bytes = file_to_bytes(in_object=input_object)
make_dirs(in_path=extract_path, delete=True)
for lzh_match in PAT_AWARD_LZH.finditer(string=input_buffer):
lzh_type: str = lzh_match.group(0).decode(encoding='utf-8')
lzh_text: str = f'LZH-{lzh_type.strip("-").upper()}'
lzh_bgn: int = lzh_match.start()
mod_bgn: int = lzh_bgn - 0x2
hdr_len: int = input_buffer[mod_bgn]
mod_len: int = int.from_bytes(bytes=input_buffer[mod_bgn + 0x7:mod_bgn + 0xB], byteorder='little')
mod_end: int = lzh_bgn + hdr_len + mod_len
mod_bin: bytes = input_buffer[mod_bgn:mod_end]
if len(mod_bin) != 0x2 + hdr_len + mod_len:
printer(message=f'Error: Skipped incomplete LZH stream at 0x{mod_bgn:X}!',
padding=padding, new_line=True)
continue
if len(mod_bin) >= 0x16:
tag_txt: str = safe_name(in_name=mod_bin[0x16:0x16 + mod_bin[0x15]].decode(
encoding='utf-8', errors='ignore').strip())
else:
tag_txt = f'{mod_bgn:X}_{mod_end:X}'
printer(message=f'{lzh_text} > {tag_txt} [0x{mod_bgn:06X}-0x{mod_end:06X}]', padding=padding)
mod_path: str = os.path.join(extract_path, tag_txt)
lzh_path: str = f'{mod_path}.lzh'
with open(file=lzh_path, mode='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(in_path=lzh_path, out_path=extract_path, in_name=lzh_text,
padding=padding + 4, check=False)
# Manually check if 7-Zip extracted LZH due to its CRC check issue
if os.path.isfile(path=mod_path):
os.chmod(path=lzh_path, mode=stat.S_IWRITE)
os.remove(path=lzh_path) # Successful extraction, delete LZH archive
# Extract any nested LZH archives
if self.check_format(input_object=mod_path):
# Recursively extract nested Award BIOS modules
self.parse_format(input_object=mod_path, extract_path=extract_folder(mod_path),
padding=padding + 8)
if __name__ == '__main__':
AwardBiosExtract().run_utility()

View file

@ -7,12 +7,12 @@ Copyright (C) 2022-2024 Plato Mavropoulos
# Get Checksum 16-bit
def get_chk_16(data, value=0, order='little'):
def checksum_16(data: bytes | bytearray, value: int = 0, order: str = 'little') -> int:
""" Calculate Checksum-16 of data, controlling IV and Endianess """
for idx in range(0, len(data), 2):
# noinspection PyTypeChecker
value += int.from_bytes(data[idx:idx + 2], byteorder=order)
value += int.from_bytes(bytes=data[idx:idx + 2], byteorder=order) # type: ignore
value &= 0xFFFF
@ -20,7 +20,7 @@ def get_chk_16(data, value=0, order='little'):
# Get Checksum 8-bit XOR
def get_chk_8_xor(data, value=0):
def checksum_8_xor(data: bytes | bytearray, value: int = 0) -> int:
""" Calculate Checksum-8 XOR of data, controlling IV """
for byte in data:

View file

@ -0,0 +1,122 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import os
import subprocess
from biosutilities.common.externals import szip_path, tiano_path
from biosutilities.common.system import printer
def szip_code_assert(exit_code: int) -> None:
""" Check 7-Zip bad exit codes (0 OK, 1 Warning) """
if exit_code not in (0, 1):
raise ValueError(f'Bad exit code: {exit_code}')
def is_szip_supported(in_path: str, padding: int = 0, args: list | None = None, silent: bool = True) -> bool:
""" Check if file is 7-Zip supported """
try:
if args is None:
args = []
szip_c: list[str] = [szip_path(), 't', in_path, *args, '-bso0', '-bse0', '-bsp0']
szip_t: subprocess.CompletedProcess[bytes] = subprocess.run(args=szip_c, check=False)
szip_code_assert(exit_code=szip_t.returncode)
except Exception as error: # pylint: disable=broad-except
if not silent:
printer(message=f'Error: 7-Zip could not check support for file {in_path}: {error}!', padding=padding)
return False
return True
def szip_decompress(in_path: str, out_path: str, in_name: str | None, padding: int = 0, args: list | None = None,
check: bool = False, silent: bool = False) -> int:
""" Archive decompression via 7-Zip """
if not in_name:
in_name = 'archive'
try:
if args is None:
args = []
szip_c: list[str] = [szip_path(), 'x', *args, '-aou', '-bso0', '-bse0', '-bsp0', f'-o{out_path}', in_path]
szip_x: subprocess.CompletedProcess[bytes] = subprocess.run(args=szip_c, check=False)
if check:
szip_code_assert(exit_code=szip_x.returncode)
if not os.path.isdir(out_path):
raise OSError(f'Extraction directory not found: {out_path}')
except Exception as error: # pylint: disable=broad-except
if not silent:
printer(message=f'Error: 7-Zip could not extract {in_name} file {in_path}: {error}!', padding=padding)
return 1
if not silent:
printer(message=f'Successful {in_name} decompression via 7-Zip!', padding=padding)
return 0
def efi_compress_sizes(data: bytes | bytearray) -> tuple[int, int]:
""" Get EFI compression sizes """
size_compress: int = int.from_bytes(bytes=data[0x0:0x4], byteorder='little')
size_original: int = int.from_bytes(bytes=data[0x4:0x8], byteorder='little')
return size_compress, size_original
def is_efi_compressed(data: bytes | bytearray, strict: bool = True) -> bool:
""" Check if data is EFI compressed, controlling EOF padding """
size_comp, size_orig = efi_compress_sizes(data=data)
check_diff: bool = size_comp < size_orig
if strict:
check_size: bool = size_comp + 0x8 == len(data)
else:
check_size = size_comp + 0x8 <= len(data)
return check_diff and check_size
def efi_decompress(in_path: str, out_path: str, padding: int = 0, silent: bool = False,
comp_type: str = '--uefi') -> int:
""" EFI/Tiano Decompression via TianoCompress """
try:
subprocess.run(args=[tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type],
check=True, stdout=subprocess.DEVNULL)
with open(file=in_path, mode='rb') as file:
_, size_orig = efi_compress_sizes(data=file.read())
if os.path.getsize(out_path) != size_orig:
raise OSError('EFI decompressed file & header size mismatch!')
except Exception as error: # pylint: disable=broad-except
if not silent:
printer(message=f'Error: TianoCompress could not extract file {in_path}: {error}!', padding=padding)
return 1
if not silent:
printer(message='Successful EFI decompression via TianoCompress!', padding=padding)
return 0

View file

@ -7,17 +7,17 @@ Copyright (C) 2022-2024 Plato Mavropoulos
import pefile
from common.system import printer
from common.text_ops import file_to_bytes
from biosutilities.common.system import printer
from biosutilities.common.texts import file_to_bytes
def is_pe_file(in_file: str | bytes) -> bool:
def is_ms_pe(in_file: str | bytes) -> bool:
""" Check if input is a PE file """
return bool(get_pe_file(in_file, silent=True))
return bool(ms_pe(in_file=in_file, silent=True))
def get_pe_file(in_file: str | bytes, padding: int = 0, fast: bool = True, silent: bool = False) -> pefile.PE | None:
def ms_pe(in_file: str | bytes, padding: int = 0, fast: bool = True, silent: bool = False) -> pefile.PE | None:
""" Get pefile object from PE file """
pe_file: pefile.PE | None = None
@ -29,18 +29,18 @@ def get_pe_file(in_file: str | bytes, padding: int = 0, fast: bool = True, silen
if not silent:
filename: str = in_file if isinstance(in_file, str) else 'buffer'
printer(f'Error: Could not get pefile object from {filename}: {error}!', padding)
printer(message=f'Error: Could not get pefile object from {filename}: {error}!', padding=padding)
return pe_file
def get_pe_desc(pe_file: pefile.PE, padding: int = 0, silent: bool = False) -> bytes:
def ms_pe_desc(pe_file: pefile.PE, padding: int = 0, silent: bool = False) -> bytes:
""" Get PE description from pefile object info """
return get_pe_info(pe_file, padding, silent).get(b'FileDescription', b'')
return ms_pe_info(pe_file=pe_file, padding=padding, silent=silent).get(b'FileDescription', b'')
def get_pe_info(pe_file: pefile.PE, padding: int = 0, silent: bool = False) -> dict:
def ms_pe_info(pe_file: pefile.PE, padding: int = 0, silent: bool = False) -> dict:
""" Get PE info from pefile object """
pe_info: dict = {}
@ -53,20 +53,20 @@ def get_pe_info(pe_file: pefile.PE, padding: int = 0, silent: bool = False) -> d
pe_info = pe_file.FileInfo[0][0].StringTable[0].entries
except Exception as error: # pylint: disable=broad-except
if not silent:
printer(f'Error: Could not get PE info from pefile object: {error}!', padding)
printer(message=f'Error: Could not get PE info from pefile object: {error}!', padding=padding)
return pe_info
def show_pe_info(pe_file: pefile.PE, padding: int = 0) -> None:
def ms_pe_info_show(pe_file: pefile.PE, padding: int = 0) -> None:
""" Print PE info from pefile StringTable """
pe_info: dict = get_pe_info(pe_file=pe_file, padding=padding)
pe_info: dict = ms_pe_info(pe_file=pe_file, padding=padding)
if isinstance(pe_info, dict):
for title, value in pe_info.items():
info_title: str = title.decode('utf-8', 'ignore').strip()
info_value: str = value.decode('utf-8', 'ignore').strip()
info_title: str = title.decode(encoding='utf-8', errors='ignore').strip()
info_value: str = value.decode(encoding='utf-8', errors='ignore').strip()
if info_title and info_value:
printer(f'{info_title}: {info_value}', padding, new_line=False)
printer(message=f'{info_title}: {info_value}', padding=padding, new_line=False)

View file

@ -0,0 +1,94 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import os
import re
import shutil
import sys
from importlib.abc import Loader
from importlib.machinery import ModuleSpec
from importlib.util import module_from_spec, spec_from_file_location
from types import ModuleType
from typing import Type
def big_script_tool() -> Type | None:
""" Get Intel BIOS Guard Script Tool class """
bgst: str | None = shutil.which(cmd='big_script_tool')
if bgst and os.path.isfile(path=bgst):
bgst_spec: ModuleSpec | None = spec_from_file_location(
name='big_script_tool', location=re.sub(r'\.PY$', '.py', bgst))
if bgst_spec and isinstance(bgst_spec.loader, Loader):
bgst_module: ModuleType | None = module_from_spec(spec=bgst_spec)
if bgst_module:
sys.modules['big_script_tool'] = bgst_module
bgst_spec.loader.exec_module(module=bgst_module)
return getattr(bgst_module, 'BigScript')
return None
def comextract_path() -> str:
""" Get ToshibaComExtractor path """
comextract: str | None = shutil.which(cmd='comextract')
if not (comextract and os.path.isfile(path=comextract)):
raise OSError('comextract executable not found!')
return comextract
def szip_path() -> str:
""" Get 7-Zip path """
szip: str | None = shutil.which(cmd='7zzs') or shutil.which(cmd='7z')
if not (szip and os.path.isfile(path=szip)):
raise OSError('7zzs or 7z executable not found!')
return szip
def tiano_path() -> str:
""" Get TianoCompress path """
tiano: str | None = shutil.which(cmd='TianoCompress')
if not (tiano and os.path.isfile(path=tiano)):
raise OSError('TianoCompress executable not found!')
return tiano
def uefifind_path() -> str:
""" Get UEFIFind path """
uefifind: str | None = shutil.which(cmd='UEFIFind')
if not (uefifind and os.path.isfile(path=uefifind)):
raise OSError('UEFIFind executable not found!')
return uefifind
def uefiextract_path() -> str:
""" Get UEFIExtract path """
uefiextract: str | None = shutil.which(cmd='UEFIExtract')
if not (uefiextract and os.path.isfile(path=uefiextract)):
raise OSError('UEFIExtract executable not found!')
return uefiextract

View file

@ -0,0 +1,218 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import os
import re
import shutil
import stat
import sys
from pathlib import Path, PurePath
from typing import Callable, Final
from biosutilities.common.system import system_platform
from biosutilities.common.texts import to_string
MAX_WIN_COMP_LEN: Final[int] = 255
def safe_name(in_name: str) -> str:
"""
Fix illegal/reserved Windows characters
Can also be used to nuke dangerous paths
"""
name_repr: str = repr(in_name).strip("'")
return re.sub(pattern=r'[\\/:"*?<>|]+', repl='_', string=name_repr)
def safe_path(base_path: str, user_paths: str | list | tuple) -> str:
""" Check and attempt to fix illegal/unsafe OS path traversals """
# Convert base path to absolute path
base_path = real_path(in_path=base_path)
# Merge user path(s) to string with OS separators
user_path: str = to_string(in_object=user_paths, sep_char=os.sep)
# Create target path from base + requested user path
target_path: str = norm_path(base_path=base_path, user_path=user_path)
# Check if target path is OS illegal/unsafe
if is_safe_path(base_path=base_path, target_path=target_path):
return target_path
# Re-create target path from base + leveled/safe illegal "path" (now file)
nuked_path: str = norm_path(base_path=base_path, user_path=safe_name(in_name=user_path))
# Check if illegal path leveling worked
if is_safe_path(base_path=base_path, target_path=nuked_path):
return nuked_path
# Still illegal, raise exception to halt execution
raise OSError(f'Encountered illegal path traversal: {user_path}')
def is_safe_path(base_path: str, target_path: str) -> bool:
""" Check for illegal/unsafe OS path traversal """
base_path = real_path(in_path=base_path)
target_path = real_path(in_path=target_path)
common_path: str = os.path.commonpath(paths=(base_path, target_path))
return base_path == common_path
def norm_path(base_path: str, user_path: str) -> str:
""" Create normalized base path + OS separator + user path """
return os.path.normpath(path=base_path + os.sep + user_path)
def real_path(in_path: str) -> str:
""" Get absolute path, resolving any symlinks """
return os.path.realpath(in_path)
def agnostic_path(in_path: str) -> PurePath:
""" Get Windows/Posix OS-agnostic path """
return PurePath(in_path.replace('\\', os.sep))
def path_parent(in_path: str) -> Path:
""" Get absolute parent of path """
return Path(in_path).parent.absolute()
def path_name(in_path: str, limit: bool = False) -> str:
""" Get final path component, with suffix """
comp_name: str = PurePath(in_path).name
is_win: bool = system_platform()[1]
if limit and is_win:
comp_name = comp_name[:MAX_WIN_COMP_LEN - len(extract_suffix())]
return comp_name
def path_stem(in_path: str) -> str:
""" Get final path component, w/o suffix """
return PurePath(in_path).stem
def path_suffixes(in_path: str) -> list[str]:
""" Get list of path file extensions """
return PurePath(in_path).suffixes or ['']
def make_dirs(in_path: str, parents: bool = True, exist_ok: bool = False, delete: bool = False):
""" Create folder(s), controlling parents, existence and prior deletion """
if delete:
delete_dirs(in_path=in_path)
Path.mkdir(Path(in_path), parents=parents, exist_ok=exist_ok)
def delete_dirs(in_path: str) -> None:
""" Delete folder(s), if present """
if Path(in_path).is_dir():
shutil.rmtree(path=in_path, onexc=clear_readonly_callback)
def delete_file(in_path: str) -> None:
""" Delete file, if present """
if Path(in_path).is_file():
clear_readonly(in_path=in_path)
os.remove(path=in_path)
def copy_file(in_path: str, out_path: str, metadata: bool = False) -> None:
""" Copy file to path with or w/o metadata """
if metadata:
shutil.copy2(src=in_path, dst=out_path)
else:
shutil.copy(src=in_path, dst=out_path)
def clear_readonly(in_path: str) -> None:
""" Clear read-only file attribute """
os.chmod(path=in_path, mode=stat.S_IWRITE)
def clear_readonly_callback(in_func: Callable, in_path: str, _) -> None:
""" Clear read-only file attribute (on shutil.rmtree error) """
clear_readonly(in_path=in_path)
in_func(in_path=in_path)
def path_files(in_path: str, follow_links: bool = False) -> list[str]:
""" Walk path to get all files """
file_paths: list[str] = []
for root, _, filenames in os.walk(top=in_path, followlinks=follow_links):
for filename in filenames:
file_paths.append(os.path.abspath(path=os.path.join(root, filename)))
return file_paths
def is_empty_dir(in_path: str, follow_links: bool = False) -> bool:
""" Check if directory is empty (file-wise) """
for _, _, filenames in os.walk(top=in_path, followlinks=follow_links):
if filenames:
return False
return True
def extract_suffix() -> str:
""" Set utility extraction stem """
return '_extracted'
def extract_folder(in_path: str, suffix: str = extract_suffix()) -> str:
""" Get utility extraction directory """
return f'{in_path}{suffix}'
def project_root() -> str:
""" Get project root directory """
return real_path(in_path=str(Path(__file__).parent.parent))
def runtime_root() -> str:
""" Get runtime root directory """
if getattr(sys, 'frozen', False):
root: str = str(Path(sys.executable).parent)
else:
root = project_root()
return real_path(in_path=root)

View file

@ -0,0 +1,125 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import re
from typing import Final
PAT_AMI_PFAT: Final[re.Pattern[bytes]] = re.compile(
pattern=br'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS',
flags=re.DOTALL
)
PAT_AMI_UCP: Final[re.Pattern[bytes]] = re.compile(
pattern=br'@(UAF|HPU).{12}@',
flags=re.DOTALL
)
PAT_APPLE_EFI: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\$IBIOSI\$.{16}\x2E\x00.{6}\x2E\x00.{8}\x2E\x00.{6}\x2E\x00.{20}\x00{2}',
flags=re.DOTALL
)
PAT_APPLE_IM4P: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\x16\x04IM4P\x16\x04mefi'
)
PAT_APPLE_PBZX: Final[re.Pattern[bytes]] = re.compile(
pattern=br'pbzx'
)
PAT_APPLE_PKG_XAR: Final[re.Pattern[bytes]] = re.compile(
pattern=br'xar!'
)
PAT_APPLE_PKG_TAR: Final[re.Pattern[bytes]] = re.compile(
pattern=br'<key>IFPkgDescriptionDescription</key>'
)
PAT_AWARD_LZH: Final[re.Pattern[bytes]] = re.compile(
pattern=br'-lh[04567]-'
)
PAT_DELL_FTR: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90'
)
PAT_DELL_HDR: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C',
flags=re.DOTALL
)
PAT_DELL_PKG: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\x72\x13\x55\x00.{45}7zXZ',
flags=re.DOTALL
)
PAT_FUJITSU_SFX: Final[re.Pattern[bytes]] = re.compile(
pattern=br'FjSfxBinay\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE',
flags=re.DOTALL
)
PAT_INSYDE_IFL: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\$_IFLASH'
)
PAT_INSYDE_SFX: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\x0D\x0A;!@InstallEnd@!\x0D\x0A(7z\xBC\xAF\x27|\x6E\xF4\x79\x5F\x4E)'
)
PAT_INTEL_ENG: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))',
flags=re.DOTALL
)
PAT_INTEL_IFD: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\x5A\xA5\xF0\x0F.{172}\xFF{16}',
flags=re.DOTALL
)
PAT_MICROSOFT_CAB: Final[re.Pattern[bytes]] = re.compile(
pattern=br'MSCF\x00{4}'
)
PAT_MICROSOFT_MZ: Final[re.Pattern[bytes]] = re.compile(
pattern=br'MZ'
)
PAT_MICROSOFT_PE: Final[re.Pattern[bytes]] = re.compile(
pattern=br'PE\x00{2}'
)
PAT_PHOENIX_TDK: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\$PACK\x00{3}..\x00{2}.\x00{3}',
flags=re.DOTALL
)
PAT_PORTWELL_EFI: Final[re.Pattern[bytes]] = re.compile(
pattern=br'<U{2}>'
)
PAT_TOSHIBA_COM: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\x00{2}[\x00-\x02]BIOS.{20}[\x00\x01]',
flags=re.DOTALL
)
PAT_VAIO_CAB: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE',
flags=re.DOTALL
)
PAT_VAIO_CFG: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\[Setting]\x0D\x0A'
)
PAT_VAIO_CHK: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\x0AUseVAIOCheck='
)
PAT_VAIO_EXT: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\x0AExtractPathByUser='
)

View file

@ -0,0 +1,38 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import ctypes
from typing import Any, Final
CHAR: Final[type[ctypes.c_char] | int] = ctypes.c_char
UINT8: Final[type[ctypes.c_ubyte] | int] = ctypes.c_ubyte
UINT16: Final[type[ctypes.c_ushort] | int] = ctypes.c_ushort
UINT32: Final[type[ctypes.c_uint] | int] = ctypes.c_uint
UINT64: Final[type[ctypes.c_uint64] | int] = ctypes.c_uint64
def ctypes_struct(buffer: bytes | bytearray, start_offset: int, class_object: Any,
param_list: list | None = None) -> Any:
"""
https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky
"""
if not param_list:
param_list = []
structure: Any = class_object(*param_list)
struct_len: int = ctypes.sizeof(structure)
struct_data: bytes | bytearray = buffer[start_offset:start_offset + struct_len]
least_len: int = min(len(struct_data), struct_len)
ctypes.memmove(ctypes.addressof(structure), struct_data, least_len)
return structure

View file

@ -0,0 +1,48 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import sys
import platform
from biosutilities.common.texts import to_string
def system_platform() -> tuple[str, bool, bool]:
""" Get OS platform """
sys_os: str = platform.system()
is_win: bool = sys_os == 'Windows'
is_lnx: bool = sys_os in ('Linux', 'Darwin')
return sys_os, is_win, is_lnx
def python_version() -> tuple:
""" Get Python version """
return sys.version_info
def printer(message: str | list | tuple | None = None, padding: int = 0, new_line: bool = True,
pause: bool = False, sep_char: str = ' ') -> None:
""" Show message(s), controlling padding, newline, pausing & separator """
message_string: str = to_string(in_object='' if message is None else message, sep_char=sep_char)
message_output: str = '\n' if new_line else ''
for line_index, line_text in enumerate(iterable=message_string.split('\n')):
line_newline: str = '' if line_index == 0 else '\n'
message_output += f'{line_newline}{" " * padding}{line_text}'
if pause:
input(message_output)
else:
print(message_output)

View file

@ -0,0 +1,157 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import os
import sys
from argparse import ArgumentParser, Namespace
from typing import Final
from biosutilities import __version__
from biosutilities.common.paths import (delete_dirs, extract_folder, is_empty_dir, path_files,
path_name, path_parent, real_path, runtime_root)
from biosutilities.common.system import system_platform, python_version, printer
from biosutilities.common.texts import remove_quotes, to_boxed, to_ordinal
class BIOSUtility:
""" Base utility class for BIOSUtilities """
TITLE: str = 'BIOS Utility'
ARGUMENTS: list[tuple[list[str], dict[str, str]]] = []
MAX_FAT32_ITEMS: Final[int] = 65535
MIN_PYTHON_VER: Final[tuple[int, int]] = (3, 10)
def __init__(self, arguments: list[str] | None = None) -> None:
self._check_sys_py()
self._check_sys_os()
self.title: str = f'{self.TITLE.strip()} v{__version__}'
argparser: ArgumentParser = ArgumentParser(allow_abbrev=False)
argparser.add_argument('paths', nargs='*')
argparser.add_argument('-e', '--auto-exit', help='skip user action prompts', action='store_true')
argparser.add_argument('-o', '--output-dir', help='output extraction directory')
for argument in self.ARGUMENTS:
argparser.add_argument(*argument[0], **argument[1]) # type: ignore
sys_argv: list[str] = arguments if isinstance(arguments, list) and arguments else sys.argv[1:]
self.arguments: Namespace = argparser.parse_known_args(sys_argv)[0]
self._input_files: list[str] = []
self._output_path: str = ''
def run_utility(self, padding: int = 0) -> int:
""" Run utility after checking for supported format """
self.show_version(padding=padding)
self._setup_input_files(padding=padding)
self._setup_output_dir(padding=padding)
exit_code: int = len(self._input_files)
for input_file in self._input_files:
input_name: str = path_name(in_path=input_file, limit=True)
printer(message=input_name, padding=padding + 4)
if not self.check_format(input_object=input_file):
printer(message='Error: This is not a supported format!', padding=padding + 8)
continue
extract_path: str = os.path.join(self._output_path, extract_folder(in_path=input_name))
if os.path.isdir(extract_path):
for suffix in range(2, self.MAX_FAT32_ITEMS):
renamed_path: str = f'{os.path.normpath(path=extract_path)}_{to_ordinal(in_number=suffix)}'
if not os.path.isdir(renamed_path):
extract_path = renamed_path
break
if self.parse_format(input_object=input_file, extract_path=extract_path,
padding=padding + 8) in [0, None]:
exit_code -= 1
if is_empty_dir(in_path=extract_path):
delete_dirs(in_path=extract_path)
printer(message='Done!\n' if not self.arguments.auto_exit else None, pause=not self.arguments.auto_exit)
return exit_code
def show_version(self, is_boxed: bool = True, padding: int = 0) -> None:
""" Show title and version of utility """
printer(message=to_boxed(in_text=self.title) if is_boxed else self.title, new_line=False, padding=padding)
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int | None:
""" Process input object as a specific supported format """
raise NotImplementedError(f'Method "parse_format" not implemented at {__name__}')
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input object is of specific supported format """
raise NotImplementedError(f'Method "check_format" not implemented at {__name__}')
def _setup_input_files(self, padding: int = 0) -> None:
input_paths: list[str] = self.arguments.paths
if not input_paths:
input_paths = [remove_quotes(in_text=input(f'\n{" " * padding}Enter input file or directory path: '))]
for input_path in [input_path for input_path in input_paths if input_path]:
input_path_real: str = real_path(in_path=input_path)
if os.path.isdir(input_path_real):
self._input_files.extend(path_files(input_path_real))
else:
self._input_files.append(input_path_real)
def _setup_output_dir(self, padding: int = 0) -> None:
output_path: str = self.arguments.output_dir
if not output_path:
output_path = remove_quotes(in_text=input(f'\n{" " * padding}Enter output directory path: '))
if not output_path and self._input_files:
output_path = str(path_parent(in_path=self._input_files[0]))
self._output_path = output_path or runtime_root()
def _check_sys_py(self) -> None:
""" Check Python Version """
sys_py: tuple = python_version()
if sys_py < self.MIN_PYTHON_VER:
min_py_str: str = '.'.join(map(str, self.MIN_PYTHON_VER))
sys_py_str: str = '.'.join(map(str, sys_py[:2]))
raise RuntimeError(f'Python >= {min_py_str} required, not {sys_py_str}')
@staticmethod
def _check_sys_os() -> None:
""" Check OS Platform """
os_tag, is_win, is_lnx = system_platform()
if not (is_win or is_lnx):
raise OSError(f'Unsupported operating system: {os_tag}')

View file

@ -0,0 +1,73 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
def to_string(in_object: str | list | tuple, sep_char: str = '') -> str:
""" Get string from given input object """
if isinstance(in_object, (list, tuple)):
out_string: str = sep_char.join(map(str, in_object))
else:
out_string = str(in_object)
return out_string
def to_ordinal(in_number: int) -> str:
"""
Get ordinal (textual) representation of input numerical value
https://leancrew.com/all-this/2020/06/ordinals-in-python/ by Dr. Drang
"""
ordinals: list[str] = ['th', 'st', 'nd', 'rd'] + ['th'] * 10
numerical: int = in_number % 100
if numerical > 13:
return f'{in_number}{ordinals[numerical % 10]}'
return f'{in_number}{ordinals[numerical]}'
def file_to_bytes(in_object: str | bytes | bytearray) -> bytes:
""" Get bytes from given buffer or file path """
if not isinstance(in_object, (bytes, bytearray)):
with open(file=to_string(in_object=in_object), mode='rb') as object_data:
object_bytes: bytes = object_data.read()
else:
object_bytes = in_object
return object_bytes
def bytes_to_hex(in_buffer: bytes, order: str, data_len: int, slice_len: int | None = None) -> str:
""" Converts bytes to hex string, controlling endianess, data size and string slicing """
# noinspection PyTypeChecker
return f'{int.from_bytes(bytes=in_buffer, byteorder=order):0{data_len * 2}X}'[:slice_len] # type: ignore
def remove_quotes(in_text: str) -> str:
""" Remove leading/trailing quotes from path """
out_text: str = to_string(in_object=in_text).strip()
if len(out_text) >= 2:
if (out_text[0] == '"' and out_text[-1] == '"') or (out_text[0] == "'" and out_text[-1] == "'"):
out_text = out_text[1:-1]
return out_text
def to_boxed(in_text: str) -> str:
""" Box string into two horizontal lines of same size """
box_line: str = '-' * len(to_string(in_object=in_text))
return f'{box_line}\n{in_text}\n{box_line}'

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,92 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Fujitsu SFX Extractor
Fujitsu SFX BIOS Extractor
Copyright (C) 2019-2024 Plato Mavropoulos
"""
import os
import re
from biosutilities.common.compression import is_szip_supported, szip_decompress
from biosutilities.common.paths import make_dirs
from biosutilities.common.patterns import PAT_FUJITSU_SFX
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
class FujitsuSfxExtract(BIOSUtility):
""" Fujitsu SFX BIOS Extractor """
TITLE: str = 'Fujitsu SFX BIOS Extractor'
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Fujitsu SFX image """
input_buffer: bytes = file_to_bytes(in_object=input_object)
return bool(PAT_FUJITSU_SFX.search(string=input_buffer))
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Extract Fujitsu SFX image """
input_buffer: bytes = file_to_bytes(in_object=input_object)
# Microsoft CAB Header XOR 0xFF
match_cab: re.Match[bytes] | None = PAT_FUJITSU_SFX.search(string=input_buffer)
if not match_cab:
return 1
printer(message='Detected obfuscated CAB archive!', padding=padding)
# Microsoft CAB Header XOR 0xFF starts after "FjSfxBinay" signature
cab_start: int = match_cab.start() + 0xA
# Get LE XOR-ed CAB size
cab_size: int = int.from_bytes(bytes=input_buffer[cab_start + 0x8:cab_start + 0xC], byteorder='little')
# Create CAB size XOR value
xor_size: int = int.from_bytes(bytes=b'\xFF' * 0x4, byteorder='little')
# Perform XOR 0xFF and get actual CAB size
cab_size ^= xor_size
printer(message='Removing obfuscation...', padding=padding + 4)
# Get BE XOR-ed CAB data
cab_data: int = int.from_bytes(bytes=input_buffer[cab_start:cab_start + cab_size], byteorder='big')
# Create CAB data XOR value
xor_data: int = int.from_bytes(bytes=b'\xFF' * cab_size, byteorder='big')
# Perform XOR 0xFF and get actual CAB data
raw_data: bytes = (cab_data ^ xor_data).to_bytes(cab_size, 'big')
printer(message='Extracting archive...', padding=padding + 4)
make_dirs(in_path=extract_path, delete=True)
cab_path: str = os.path.join(extract_path, 'FjSfxBinay.cab')
# Create temporary CAB archive
with open(file=cab_path, mode='wb') as cab_file_object:
cab_file_object.write(raw_data)
if is_szip_supported(in_path=cab_path, padding=padding + 8, silent=False):
if szip_decompress(in_path=cab_path, out_path=extract_path, in_name='FjSfxBinay CAB',
padding=padding + 8, check=True) == 0:
os.remove(path=cab_path)
else:
return 3
else:
return 2
return 0
if __name__ == '__main__':
FujitsuSfxExtract().run_utility()

View file

@ -0,0 +1,68 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Fujitsu UPC Extract
Fujitsu UPC BIOS Extractor
Copyright (C) 2021-2024 Plato Mavropoulos
"""
import os
from biosutilities.common.compression import efi_decompress, is_efi_compressed
from biosutilities.common.paths import make_dirs, path_name, path_suffixes
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
class FujitsuUpcExtract(BIOSUtility):
""" Fujitsu UPC BIOS Extractor """
TITLE: str = 'Fujitsu UPC BIOS Extractor'
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Fujitsu UPC image """
is_upc: bool = False
if isinstance(input_object, str) and os.path.isfile(path=input_object):
is_upc = path_suffixes(input_object)[-1].upper() == '.UPC'
elif isinstance(input_object, (bytes, bytearray)):
is_upc = True
if is_upc:
is_upc = is_efi_compressed(data=file_to_bytes(in_object=input_object))
return is_upc
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Extract Fujitsu UPC image """
make_dirs(in_path=extract_path, delete=True)
if isinstance(input_object, str) and os.path.isfile(path=input_object):
input_name: str = path_name(in_path=input_object)
input_path: str = input_object
if input_name.upper().endswith('.UPC'):
input_name = input_name[:-4]
else:
input_name = 'Fujitsu_UPC_Image'
input_path = os.path.join(extract_path, f'{input_name}.UPC')
with open(file=input_path, mode='wb') as input_path_object:
input_path_object.write(file_to_bytes(in_object=input_object))
output_path: str = os.path.join(extract_path, f'{input_name}.bin')
efi_code: int = efi_decompress(in_path=input_path, out_path=output_path, padding=padding)
if input_path != input_object:
os.remove(path=input_path)
return efi_code
if __name__ == '__main__':
FujitsuUpcExtract().run_utility()

View file

@ -0,0 +1,244 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Insyde IFD Extract
Insyde iFlash/iFdPacker Extractor
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import ctypes
import os
import re
from typing import Any
from biosutilities.common.compression import is_szip_supported, szip_decompress
from biosutilities.common.paths import extract_folder, path_files, make_dirs, path_name, safe_name
from biosutilities.common.patterns import PAT_INSYDE_IFL, PAT_INSYDE_SFX
from biosutilities.common.structs import CHAR, ctypes_struct, UINT32
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
class IflashHeader(ctypes.LittleEndianStructure):
""" Insyde iFlash Header """
_pack_ = 1
_fields_ = [
('Signature', CHAR * 8), # 0x00 $_IFLASH
('ImageTag', CHAR * 8), # 0x08
('TotalSize', UINT32), # 0x10 from header end
('ImageSize', UINT32) # 0x14 from header end
# 0x18
]
def _get_padd_len(self) -> int:
return self.TotalSize - self.ImageSize
def get_image_tag(self) -> str:
""" Get Insyde iFlash image tag """
return self.ImageTag.decode(encoding='utf-8', errors='ignore').strip('_')
def struct_print(self, padding: int = 0) -> None:
""" Display structure information """
printer(message=['Signature :', self.Signature.decode(encoding='utf-8')], padding=padding, new_line=False)
printer(message=['Image Name:', self.get_image_tag()], padding=padding, new_line=False)
printer(message=['Image Size:', f'0x{self.ImageSize:X}'], padding=padding, new_line=False)
printer(message=['Total Size:', f'0x{self.TotalSize:X}'], padding=padding, new_line=False)
printer(message=['Padd Size :', f'0x{self._get_padd_len():X}'], padding=padding, new_line=False)
class InsydeIfdExtract(BIOSUtility):
""" Insyde iFlash/iFdPacker Extractor """
TITLE: str = 'Insyde iFlash/iFdPacker Extractor'
# Insyde iFdPacker known 7-Zip SFX Password
INS_SFX_PWD: str = '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: dict = {
'BIOSCER': ['Certificate', 'bin'],
'BIOSCR2': ['Certificate 2nd', 'bin'],
'BIOSIMG': ['BIOS-UEFI', 'bin'],
'DRV_IMG': ['isflash', 'efi'],
'EC_IMG': ['Embedded Controller', 'bin'],
'INI_IMG': ['platform', 'ini'],
'IOM_IMG': ['IO Manageability', 'bin'],
'ISH_IMG': ['Integrated Sensor Hub', 'bin'],
'ME_IMG': ['Management Engine', 'bin'],
'OEM_ID': ['OEM Identifier', 'bin'],
'PDT_IMG': ['Platform Descriptor Table', 'bin'],
'TBT_IMG': ['Integrated Thunderbolt', 'bin']
}
# Get common ctypes Structure Sizes
INS_IFL_LEN: int = ctypes.sizeof(IflashHeader)
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Insyde iFlash/iFdPacker Update image """
input_buffer: bytes = file_to_bytes(in_object=input_object)
if bool(self._insyde_iflash_detect(input_buffer=input_buffer)):
return True
if bool(PAT_INSYDE_SFX.search(string=input_buffer)):
return True
return False
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Extract Insyde iFlash/iFdPacker Update images """
input_buffer: bytes = file_to_bytes(in_object=input_object)
iflash_code: int = self._insyde_iflash_extract(input_buffer=input_buffer, extract_path=extract_path,
padding=padding)
ifdpack_path: str = os.path.join(extract_path, 'Insyde iFdPacker SFX')
ifdpack_code: int = self._insyde_packer_extract(input_buffer=input_buffer, extract_path=ifdpack_path,
padding=padding)
return iflash_code and ifdpack_code
def _insyde_iflash_detect(self, input_buffer: bytes) -> list:
""" Detect Insyde iFlash Update image """
iflash_match_all: list = []
iflash_match_nan: list = [0x0, 0xFFFFFFFF]
for iflash_match in PAT_INSYDE_IFL.finditer(string=input_buffer):
ifl_bgn: int = iflash_match.start()
if len(input_buffer[ifl_bgn:]) <= self.INS_IFL_LEN:
continue
ifl_hdr: Any = ctypes_struct(buffer=input_buffer, start_offset=ifl_bgn, class_object=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 + self.INS_IFL_LEN + ifl_hdr.TotalSize > len(input_buffer):
continue
iflash_match_all.append([ifl_bgn, ifl_hdr])
return iflash_match_all
def _insyde_iflash_extract(self, input_buffer: bytes, extract_path: str, padding: int = 0) -> int:
""" Extract Insyde iFlash Update image """
insyde_iflash_all: list = self._insyde_iflash_detect(input_buffer=input_buffer)
if not insyde_iflash_all:
return 127
printer(message='Detected Insyde iFlash Update image!', padding=padding)
make_dirs(in_path=extract_path, delete=True)
exit_codes: list = []
for insyde_iflash in insyde_iflash_all:
exit_code: int = 0
ifl_bgn, ifl_hdr = insyde_iflash
img_bgn: int = ifl_bgn + self.INS_IFL_LEN
img_end: int = img_bgn + ifl_hdr.ImageSize
img_bin: bytes = input_buffer[img_bgn:img_end]
if len(img_bin) != ifl_hdr.ImageSize:
exit_code = 1
img_val: list = [ifl_hdr.get_image_tag(), 'bin']
img_tag, img_ext = self.INS_IFL_IMG.get(img_val[0], img_val)
img_name: str = f'{img_tag} [0x{img_bgn:08X}-0x{img_end:08X}]'
printer(message=f'{img_name}\n', padding=padding + 4)
ifl_hdr.struct_print(padding=padding + 8)
if img_val == [img_tag, img_ext]:
printer(message=f'Note: Detected new Insyde iFlash tag {img_tag}!',
padding=padding + 12, pause=True)
out_name: str = f'{img_name}.{img_ext}'
out_path: str = os.path.join(extract_path, safe_name(in_name=out_name))
with open(file=out_path, mode='wb') as out_image:
out_image.write(img_bin)
printer(message=f'Successful Insyde iFlash > {img_tag} extraction!', padding=padding + 12)
exit_codes.append(exit_code)
return sum(exit_codes)
def _insyde_packer_extract(self, input_buffer: bytes, extract_path: str, padding: int = 0) -> int:
""" Extract Insyde iFdPacker 7-Zip SFX 7z Update image """
match_sfx: re.Match[bytes] | None = PAT_INSYDE_SFX.search(string=input_buffer)
if not match_sfx:
return 127
printer(message='Detected Insyde iFdPacker Update image!', padding=padding)
make_dirs(in_path=extract_path, delete=True)
sfx_buffer: bytearray = bytearray(input_buffer[match_sfx.end() - 0x5:])
if sfx_buffer[:0x5] == b'\x6E\xF4\x79\x5F\x4E':
printer(message='Detected Insyde iFdPacker > 7-Zip SFX > Obfuscation!', padding=padding + 4)
for index, byte in enumerate(iterable=sfx_buffer):
sfx_buffer[index] = byte // 2 + (128 if byte % 2 else 0)
printer(message='Removed Insyde iFdPacker > 7-Zip SFX > Obfuscation!', padding=padding + 8)
printer(message='Extracting Insyde iFdPacker > 7-Zip SFX archive...', padding=padding + 4)
if bytes(self.INS_SFX_PWD, 'utf-16le') in input_buffer[:match_sfx.start()]:
printer(message='Detected Insyde iFdPacker > 7-Zip SFX > Password!', padding=padding + 8)
printer(message=self.INS_SFX_PWD, padding=padding + 12)
sfx_path: str = os.path.join(extract_path, 'Insyde_iFdPacker_SFX.7z')
with open(file=sfx_path, mode='wb') as sfx_file_object:
sfx_file_object.write(sfx_buffer)
if is_szip_supported(in_path=sfx_path, padding=padding + 8, args=[f'-p{self.INS_SFX_PWD}'], silent=False):
if szip_decompress(in_path=sfx_path, out_path=extract_path, in_name='Insyde iFdPacker > 7-Zip SFX',
padding=padding + 8, args=[f'-p{self.INS_SFX_PWD}'], check=True) == 0:
os.remove(path=sfx_path)
else:
return 125
else:
return 126
exit_codes: list[int] = []
for sfx_file in path_files(in_path=extract_path):
if self.check_format(input_object=sfx_file):
printer(message=path_name(in_path=sfx_file), padding=padding + 12)
ifd_code: int = self.parse_format(input_object=sfx_file, extract_path=extract_folder(sfx_file),
padding=padding + 16)
exit_codes.append(ifd_code)
return sum(exit_codes)
if __name__ == '__main__':
InsydeIfdExtract().run_utility()

View file

@ -0,0 +1,243 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Panasonic BIOS Extract
Panasonic BIOS Package Extractor
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import io
import logging
import os
import re
import pefile
# noinspection PyPackageRequirements
from dissect.util.compression import lznt1
from biosutilities.common.compression import is_szip_supported, szip_decompress
from biosutilities.common.paths import path_files, make_dirs, path_stem, safe_name
from biosutilities.common.executables import ms_pe_desc, ms_pe, is_ms_pe, ms_pe_info_show
from biosutilities.common.patterns import PAT_MICROSOFT_CAB
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
from biosutilities.ami_pfat_extract import AmiPfatExtract
class PanasonicBiosExtract(BIOSUtility):
""" Panasonic BIOS Package Extractor """
TITLE: str = 'Panasonic BIOS Package Extractor'
PAN_PE_DESC_UNP: str = 'UNPACK UTILITY'
PAN_PE_DESC_UPD: str = 'BIOS UPDATE'
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Panasonic BIOS Package PE """
pe_file: pefile.PE | None = ms_pe(in_file=input_object, silent=True)
if not pe_file:
return False
if ms_pe_desc(pe_file=pe_file, silent=True).decode(encoding='utf-8', errors='ignore').upper() not in (
self.PAN_PE_DESC_UNP, self.PAN_PE_DESC_UPD):
return False
return True
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Extract Panasonic BIOS Package PE """
upd_pe_file: pefile.PE = ms_pe(in_file=input_object, padding=padding) # type: ignore
upd_pe_name: str = self._panasonic_pkg_name(input_object=input_object)
printer(message=f'Panasonic BIOS Package > PE ({upd_pe_name})\n'.replace(' ()', ''), padding=padding)
ms_pe_info_show(pe_file=upd_pe_file, padding=padding + 4)
make_dirs(in_path=extract_path, delete=True)
upd_pe_path: str = self._panasonic_cab_extract(input_object=input_object,
extract_path=extract_path, padding=padding + 8)
upd_padding: int = padding
if upd_pe_path:
upd_padding = padding + 16
upd_pe_name = self._panasonic_pkg_name(input_object=upd_pe_path)
printer(message=f'Panasonic BIOS Update > PE ({upd_pe_name})\n'.replace(' ()', ''), padding=upd_padding)
upd_pe_file = ms_pe(in_file=upd_pe_path, padding=upd_padding) # type: ignore
ms_pe_info_show(pe_file=upd_pe_file, padding=upd_padding + 4)
os.remove(path=upd_pe_path)
is_upd_extracted: bool = self._panasonic_res_extract(pe_file=upd_pe_file, extract_path=extract_path,
pe_name=upd_pe_name, padding=upd_padding + 8)
if not is_upd_extracted:
is_upd_extracted = self._panasonic_img_extract(pe_file=upd_pe_file, extract_path=extract_path,
pe_name=upd_pe_name, padding=upd_padding + 8)
return 0 if is_upd_extracted else 1
@staticmethod
def _panasonic_pkg_name(input_object: str | bytes | bytearray) -> str:
""" Get Panasonic BIOS Package file name, when applicable """
if isinstance(input_object, str) and os.path.isfile(path=input_object):
return safe_name(in_name=path_stem(in_path=input_object))
return ''
def _panasonic_cab_extract(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> str:
""" Search and Extract Panasonic BIOS Package PE CAB archive """
input_data: bytes = file_to_bytes(in_object=input_object)
cab_match: re.Match[bytes] | None = PAT_MICROSOFT_CAB.search(string=input_data)
if cab_match:
cab_bgn: int = cab_match.start()
cab_end: int = cab_bgn + int.from_bytes(bytes=input_data[cab_bgn + 0x8:cab_bgn + 0xC], byteorder='little')
cab_tag: str = f'[0x{cab_bgn:06X}-0x{cab_end:06X}]'
cab_path: str = os.path.join(extract_path, f'CAB_{cab_tag}.cab')
with open(file=cab_path, mode='wb') as cab_file_object:
cab_file_object.write(input_data[cab_bgn:cab_end])
if is_szip_supported(in_path=cab_path, padding=padding, silent=False):
printer(message=f'Panasonic BIOS Package > PE > CAB {cab_tag}', padding=padding)
if szip_decompress(in_path=cab_path, out_path=extract_path, in_name='CAB',
padding=padding + 4, check=True) == 0:
os.remove(path=cab_path) # Successful extraction, delete CAB archive
for extracted_file_path in path_files(in_path=extract_path):
extracted_pe_file: pefile.PE | None = ms_pe(
in_file=extracted_file_path, padding=padding, silent=True)
if extracted_pe_file:
extracted_pe_desc: bytes = ms_pe_desc(pe_file=extracted_pe_file, silent=True)
if extracted_pe_desc.decode(encoding='utf-8', errors='ignore'
).upper() == self.PAN_PE_DESC_UPD:
return extracted_file_path
return ''
@staticmethod
def _panasonic_res_extract(pe_file: pefile.PE, extract_path: str, pe_name: str = '', padding: int = 0) -> bool:
""" Extract & Decompress Panasonic BIOS Update PE RCDATA (LZNT1) """
is_rcdata: bool = 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']])
# noinspection PyUnresolvedReferences
for entry in pe_file.DIRECTORY_ENTRY_RESOURCE.entries:
# Parse all Resource Data Directories > RCDATA (ID = 10)
if entry.struct.name == 'IMAGE_RESOURCE_DIRECTORY_ENTRY' and entry.struct.Id == 0xA:
is_rcdata = True
for resource in entry.directory.entries:
res_bgn: int = resource.directory.entries[0].data.struct.OffsetToData
res_len: int = resource.directory.entries[0].data.struct.Size
res_end: int = res_bgn + res_len
res_bin: bytes = pe_file.get_data(res_bgn, res_len)
res_tag: str = f'{pe_name} [0x{res_bgn:06X}-0x{res_end:06X}]'.strip()
res_out: str = os.path.join(extract_path, f'{res_tag}')
printer(message=res_tag, padding=padding)
try:
res_raw: bytes = lznt1.decompress(src=res_bin[0x8:])
if len(res_raw) != int.from_bytes(bytes=res_bin[0x4:0x8], byteorder='little'):
raise ValueError('LZNT1_DECOMPRESS_BAD_SIZE')
printer(message='Successful LZNT1 decompression via Dissect!', padding=padding + 4)
except Exception as error: # pylint: disable=broad-except
logging.debug('Error: LZNT1 decompression of %s failed: %s', res_tag, error)
res_raw = res_bin
printer(message='Successful PE Resource extraction!', padding=padding + 4)
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
if AmiPfatExtract().check_format(input_object=res_raw):
pfat_dir: str = os.path.join(extract_path, res_tag)
AmiPfatExtract().parse_format(input_object=res_raw, extract_path=pfat_dir,
padding=padding + 8)
else:
if is_ms_pe(in_file=res_raw):
res_ext: str = '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(message=None, new_line=False)
for line in io.BytesIO(res_raw).readlines():
line_text: str = line.decode(encoding='utf-8', errors='ignore').rstrip()
printer(message=line_text, padding=padding + 8, new_line=False)
with open(file=f'{res_out}.{res_ext}', mode='wb') as out_file_object:
out_file_object.write(res_raw)
return is_rcdata
@staticmethod
def _panasonic_img_extract(pe_file: pefile.PE, extract_path: str, pe_name: str = '',
padding: int = 0) -> bool:
""" Extract Panasonic BIOS Update PE Data when RCDATA is not available """
pe_data: bytes = bytes(pe_file.__data__)
sec_bgn: int = pe_file.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY[
'IMAGE_DIRECTORY_ENTRY_SECURITY']].VirtualAddress
img_bgn: int = (pe_file.OPTIONAL_HEADER.BaseOfData + # type: ignore
pe_file.OPTIONAL_HEADER.SizeOfInitializedData)
img_end: int = sec_bgn or len(pe_data)
img_bin: bytes = pe_data[img_bgn:img_end]
img_tag: str = f'{pe_name} [0x{img_bgn:X}-0x{img_end:X}]'.strip()
img_out: str = os.path.join(extract_path, f'{img_tag}.bin')
printer(message=img_tag, padding=padding)
with open(file=img_out, mode='wb') as out_img_object:
out_img_object.write(img_bin)
printer(message='Successful PE Data extraction!', padding=padding + 4)
return bool(img_bin)
if __name__ == '__main__':
PanasonicBiosExtract().run_utility()

View file

@ -0,0 +1,286 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Phoenix TDK Extract
Phoenix TDK Packer Extractor
Copyright (C) 2021-2024 Plato Mavropoulos
"""
import ctypes
import logging
import lzma
import os
from re import Match
from typing import Any, Final
from pefile import PE
from biosutilities.common.paths import make_dirs, safe_name
from biosutilities.common.executables import ms_pe, ms_pe_info
from biosutilities.common.patterns import PAT_MICROSOFT_MZ, PAT_MICROSOFT_PE, PAT_PHOENIX_TDK
from biosutilities.common.structs import CHAR, ctypes_struct, UINT32
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
class PhoenixTdkHeader(ctypes.LittleEndianStructure):
""" Phoenix TDK Header """
_pack_ = 1
_fields_ = [
('Tag', CHAR * 8), # 0x00
('Size', UINT32), # 0x08
('Count', UINT32) # 0x0C
# 0x10
]
def _get_tag(self) -> str:
return self.Tag.decode(encoding='utf-8', errors='ignore').strip()
def struct_print(self, padding: int = 0) -> None:
""" Display structure information """
printer(message=['Tag :', self._get_tag()], padding=padding, new_line=False)
printer(message=['Size :', f'0x{self.Size:X}'], padding=padding, new_line=False)
printer(message=['Entries:', self.Count], padding=padding, new_line=False)
class PhoenixTdkEntry(ctypes.LittleEndianStructure):
""" Phoenix TDK Entry """
_pack_ = 1
_fields_ = [
('Name', CHAR * 256), # 0x000
('Offset', UINT32), # 0x100
('Size', UINT32), # 0x104
('Compressed', UINT32), # 0x108
('Reserved', UINT32) # 0x10C
# 0x110
]
COMP: Final[dict[int, str]] = {0: 'None', 1: 'LZMA'}
def __init__(self, mz_base: int, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.mz_base: int = mz_base
def get_name(self) -> str:
""" Get TDK Entry decoded name """
return self.Name.decode(encoding='utf-8', errors='replace').strip()
def get_offset(self) -> int:
""" Get TDK Entry absolute offset """
return self.mz_base + self.Offset
def get_compression(self) -> str:
""" Get TDK Entry compression type """
return self.COMP.get(self.Compressed, f'Unknown ({self.Compressed})')
def struct_print(self, padding: int) -> None:
""" Display structure information """
printer(message=['Name :', self.get_name()], padding=padding, new_line=False)
printer(message=['Offset :', f'0x{self.get_offset():X}'], padding=padding, new_line=False)
printer(message=['Size :', f'0x{self.Size:X}'], padding=padding, new_line=False)
printer(message=['Compression:', self.get_compression()], padding=padding, new_line=False)
printer(message=['Reserved :', f'0x{self.Reserved:X}'], padding=padding, new_line=False)
class PhoenixTdkExtract(BIOSUtility):
""" Phoenix TDK Packer Extractor """
TITLE: str = 'Phoenix TDK Packer Extractor'
TDK_HDR_LEN: Final[int] = ctypes.sizeof(PhoenixTdkHeader)
TDK_MOD_LEN: Final[int] = ctypes.sizeof(PhoenixTdkEntry)
TDK_DUMMY_LEN: Final[int] = 0x200
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input contains valid Phoenix TDK image """
input_buffer: bytes = file_to_bytes(in_object=input_object)
return bool(self._get_phoenix_tdk(in_buffer=input_buffer)[1] is not None)
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Extract Phoenix Tools Development Kit (TDK) Packer """
exit_code: int = 0
input_buffer: bytes = file_to_bytes(in_object=input_object)
make_dirs(in_path=extract_path, delete=True)
printer(message='Phoenix Tools Development Kit Packer', padding=padding)
base_off, pack_off = self._get_phoenix_tdk(in_buffer=input_buffer)
# Parse TDK Header structure
tdk_hdr: Any = ctypes_struct(buffer=input_buffer, start_offset=pack_off, class_object=PhoenixTdkHeader)
# Print TDK Header structure info
printer(message='Phoenix TDK Header:\n', padding=padding + 4)
tdk_hdr.struct_print(padding=padding + 8)
# Check if reported TDK Header Size matches manual TDK Entry Count calculation
if tdk_hdr.Size != self.TDK_HDR_LEN + self.TDK_DUMMY_LEN + tdk_hdr.Count * self.TDK_MOD_LEN:
printer(message='Error: Phoenix TDK Header Size & Entry Count mismatch!\n', padding=padding + 8)
exit_code = 1
# Store TDK Entries offset after the placeholder data
entries_off: int = pack_off + self.TDK_HDR_LEN + self.TDK_DUMMY_LEN
# Parse and extract each TDK Header Entry
for entry_index in range(tdk_hdr.Count):
# Parse TDK Entry structure
tdk_mod: Any = ctypes_struct(buffer=input_buffer, start_offset=entries_off + entry_index * self.TDK_MOD_LEN,
class_object=PhoenixTdkEntry, param_list=[base_off])
# Print TDK Entry structure info
printer(message=f'Phoenix TDK Entry ({entry_index + 1}/{tdk_hdr.Count}):\n', padding=padding + 8)
tdk_mod.struct_print(padding=padding + 12)
# Get TDK Entry raw data Offset (TDK Base + Entry Offset)
mod_off: int = tdk_mod.get_offset()
# Check if TDK Entry raw data Offset is valid
if mod_off >= len(input_buffer):
printer(message='Error: Phoenix TDK Entry > Offset is out of bounds!\n', padding=padding + 12)
exit_code = 2
# Store TDK Entry raw data (relative to TDK Base, not TDK Header)
mod_data: bytes = 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(message='Error: Phoenix TDK Entry > Data is truncated!\n', padding=padding + 12)
exit_code = 3
# Check if TDK Entry Reserved is present
if tdk_mod.Reserved:
printer(message='Error: Phoenix TDK Entry > Reserved is not empty!\n', padding=padding + 12)
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(data=mod_data)
except Exception as error: # pylint: disable=broad-except
printer(message=f'Error: Phoenix TDK Entry > LZMA decompression failed: {error}!\n',
padding=padding + 12)
exit_code = 5
# Generate TDK Entry file name, avoid crash if Entry data is bad
mod_name: str = tdk_mod.get_name() or f'Unknown_{entry_index + 1:02d}.bin'
# Generate TDK Entry file data output path
mod_file: str = os.path.join(extract_path, safe_name(mod_name))
# Account for potential duplicate file names
if os.path.isfile(path=mod_file):
mod_file += f'_{entry_index + 1:02d}'
# Save TDK Entry data to output file
with open(file=mod_file, mode='wb') as out_file:
out_file.write(mod_data)
return exit_code
@staticmethod
def _get_tdk_base(in_buffer: bytes | bytearray, pack_off: int) -> int | None:
""" Get Phoenix TDK Executable (MZ) Base Offset """
# Initialize Phoenix TDK Base MZ Offset
tdk_base_off: int | None = None
# Scan input file for all Microsoft executable patterns (MZ) before TDK Header Offset
mz_all: list[Match[bytes]] = [mz for mz in PAT_MICROSOFT_MZ.finditer(string=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: list[Match[bytes]] = [mz_all[0]] + list(reversed(mz_all[1:]))
# Parse each detected MZ
for mz_match in mz_ord:
mz_off: int = mz_match.start()
# MZ (DOS) > PE (NT) image Offset is found at offset 0x3C-0x40 relative to MZ base
pe_off: int = mz_off + int.from_bytes(bytes=in_buffer[mz_off + 0x3C:mz_off + 0x40], byteorder='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(string=in_buffer[pe_off:pe_off + 0x4]):
try:
# Parse detected MZ > PE > Image, quickly (fast_load)
pe_file: PE | None = ms_pe(in_file=in_buffer[mz_off:], silent=True)
if not pe_file:
raise RuntimeError('Failed to parse detected MZ > PE > Image!')
# Parse detected MZ > PE > Info
pe_info: dict = ms_pe_info(pe_file=pe_file, silent=True)
# Parse detected MZ > PE > Info > Product Name
pe_name: bytes = pe_info.get(b'ProductName', b'')
except Exception as error: # pylint: disable=broad-except
# Any error means no MZ > PE > Info > Product Name
logging.debug('Error: Invalid potential MZ > PE match at 0x%X: %s', pe_off, error)
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
def _get_phoenix_tdk(self, in_buffer: bytes | bytearray) -> tuple:
""" Scan input buffer for valid Phoenix TDK image """
# Scan input buffer for Phoenix TDK pattern
tdk_match: Match[bytes] | None = PAT_PHOENIX_TDK.search(string=in_buffer)
if not tdk_match:
return None, None
# Set Phoenix TDK Header ($PACK) Offset
tdk_pack_off: int = tdk_match.start()
# Get Phoenix TDK Executable (MZ) Base Offset
tdk_base_off: int | None = self._get_tdk_base(in_buffer, tdk_pack_off)
return tdk_base_off, tdk_pack_off
if __name__ == '__main__':
PhoenixTdkExtract().run_utility()

View file

@ -0,0 +1,169 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Portwell EFI Extract
Portwell EFI Update Extractor
Copyright (C) 2021-2024 Plato Mavropoulos
"""
import logging
import os
from re import Match
from pefile import PE
from biosutilities.common.compression import efi_decompress, is_efi_compressed
from biosutilities.common.paths import make_dirs, safe_name
from biosutilities.common.executables import ms_pe
from biosutilities.common.patterns import PAT_MICROSOFT_MZ, PAT_PORTWELL_EFI
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
class PortwellEfiExtract(BIOSUtility):
""" Portwell EFI Update Extractor """
TITLE: str = 'Portwell EFI Update Extractor'
FILE_NAMES: dict[int, str] = {
0: 'Flash.efi',
1: 'Fparts.txt',
2: 'Update.nsh',
3: 'Temp.bin',
4: 'SaveDmiData.efi'
}
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Portwell EFI executable """
input_buffer: bytes = file_to_bytes(in_object=input_object)
try:
pe_buffer: bytes = self._get_portwell_pe(in_buffer=input_buffer)[1]
except Exception as error: # pylint: disable=broad-except
logging.debug('Error: Could not check if input is Portwell EFI executable: %s', error)
return False
# EFI images start with PE Header MZ
if PAT_MICROSOFT_MZ.search(string=input_buffer[:0x2]):
# Portwell EFI files start with <UU>
if PAT_PORTWELL_EFI.search(string=pe_buffer[:0x4]):
return True
return False
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> None:
""" Parse & Extract Portwell UEFI Unpacker """
# Initialize EFI Payload file chunks
efi_files: list[bytes] = []
input_buffer: bytes = file_to_bytes(in_object=input_object)
make_dirs(in_path=extract_path, delete=True)
pe_file, pe_data = self._get_portwell_pe(in_buffer=input_buffer)
efi_title: str = self._get_unpacker_tag(input_buffer=input_buffer, pe_file=pe_file)
printer(message=efi_title, padding=padding)
# Split EFI Payload into <UU> file chunks
efi_list: list[Match[bytes]] = list(PAT_PORTWELL_EFI.finditer(string=pe_data))
for idx, val in enumerate(iterable=efi_list):
efi_bgn: int = val.end()
efi_end: int = len(pe_data) if idx == len(efi_list) - 1 else efi_list[idx + 1].start()
efi_files.append(pe_data[efi_bgn:efi_end])
self._parse_efi_files(extract_path=extract_path, efi_files=efi_files, padding=padding)
@staticmethod
def _get_portwell_pe(in_buffer: bytes) -> tuple:
""" Get PE of Portwell EFI executable """
# Analyze EFI Portable Executable (PE)
pe_file: PE | None = ms_pe(in_buffer, silent=True)
# Skip EFI executable
# pylint: disable=no-member
pe_data: bytes = in_buffer[pe_file.OPTIONAL_HEADER.SizeOfImage:] # type: ignore
return pe_file, pe_data
@staticmethod
def _get_unpacker_tag(input_buffer: bytes | bytearray, pe_file: PE) -> str:
""" Get Portwell UEFI Unpacker tag """
unpacker_tag_txt: str = '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: int = pe_section.PointerToRawData
pe_data_end: int = pe_data_bgn + pe_section.SizeOfRawData
# Decode any valid UTF-16 .data PE section info to a parsable text buffer
pe_data_txt: str = input_buffer[pe_data_bgn:pe_data_end].decode(encoding='utf-16', errors='ignore')
# Search .data for UEFI Unpacker tag
unpacker_tag_bgn: int = pe_data_txt.find(unpacker_tag_txt)
if unpacker_tag_bgn != -1:
unpacker_tag_len: int = pe_data_txt[unpacker_tag_bgn:].find('=')
if unpacker_tag_len != -1:
unpacker_tag_end: int = unpacker_tag_bgn + unpacker_tag_len
unpacker_tag_raw: str = 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
def _parse_efi_files(self, extract_path: str, efi_files: list[bytes], padding: int) -> None:
""" Process Portwell UEFI Unpacker payload files """
for file_index, file_data in enumerate(efi_files):
if file_data in (b'', b'NULL'):
continue # Skip empty/unused files
# Assign Name to EFI file
file_name: str = self.FILE_NAMES.get(file_index, f'Unknown_{file_index}.bin')
# Print EFI file name, indicate progress
printer(message=f'[{file_index}] {file_name}', padding=padding + 4)
if file_name.startswith('Unknown_'):
printer(message=f'Note: Detected new Portwell EFI file ID {file_index}!',
padding=padding + 8, pause=True)
# Store EFI file output path
file_path: str = os.path.join(extract_path, safe_name(in_name=file_name))
# Store EFI file data to drive
with open(file=file_path, mode='wb') as out_file:
out_file.write(file_data)
# Attempt to detect EFI compression & decompress when applicable
if is_efi_compressed(data=file_data):
# Store temporary compressed file name
comp_fname: str = file_path + '.temp'
# Rename initial/compressed file
os.replace(src=file_path, dst=comp_fname)
# Successful decompression, delete compressed file
if efi_decompress(in_path=comp_fname, out_path=file_path, padding=padding + 8) == 0:
os.remove(path=comp_fname)
if __name__ == '__main__':
PortwellEfiExtract().run_utility()

View file

@ -0,0 +1,69 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Toshiba COM Extract
Toshiba BIOS COM Extractor
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import os
import subprocess
from biosutilities.common.externals import comextract_path
from biosutilities.common.paths import make_dirs, path_stem, safe_name
from biosutilities.common.patterns import PAT_TOSHIBA_COM
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
class ToshibaComExtract(BIOSUtility):
""" Toshiba BIOS COM Extractor """
TITLE: str = 'Toshiba BIOS COM Extractor'
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Toshiba BIOS COM image """
input_buffer: bytes = file_to_bytes(in_object=input_object)
return bool(PAT_TOSHIBA_COM.search(string=input_buffer, endpos=0x100))
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Extract Toshiba BIOS COM image """
make_dirs(in_path=extract_path, delete=True)
if isinstance(input_object, str) and os.path.isfile(path=input_object):
input_path: str = input_object
else:
input_path = os.path.join(extract_path, 'toshiba_bios.com')
with open(file=input_path, mode='wb') as input_buffer:
input_buffer.write(file_to_bytes(in_object=input_object))
output_name: str = f'{safe_name(in_name=path_stem(in_path=input_path))}_extracted.bin'
output_path: str = os.path.join(extract_path, output_name)
try:
subprocess.run([comextract_path(), input_path, output_path], check=True, stdout=subprocess.DEVNULL)
if not os.path.isfile(path=output_path):
raise FileNotFoundError('EXTRACTED_FILE_MISSING')
except Exception as error: # pylint: disable=broad-except
printer(message=f'Error: ToshibaComExtractor could not extract {input_path}: {error}!', padding=padding)
return 1
if input_path != input_object:
os.remove(path=input_path)
printer(message='Successful extraction via ToshibaComExtractor!', padding=padding)
return 0
if __name__ == '__main__':
ToshibaComExtract().run_utility()

View file

@ -0,0 +1,181 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
VAIO Package Extractor
VAIO Packaging Manager Extractor
Copyright (C) 2019-2024 Plato Mavropoulos
"""
import os
from re import Match
from biosutilities.common.compression import is_szip_supported, szip_decompress
from biosutilities.common.paths import make_dirs
from biosutilities.common.patterns import PAT_VAIO_CAB, PAT_VAIO_CFG, PAT_VAIO_CHK, PAT_VAIO_EXT
from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes
class VaioPackageExtract(BIOSUtility):
""" VAIO Packaging Manager Extractor """
TITLE: str = 'VAIO Packaging Manager Extractor'
def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is VAIO Packaging Manager """
input_buffer: bytes = file_to_bytes(in_object=input_object)
return bool(PAT_VAIO_CFG.search(string=input_buffer))
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Parse & Extract or Unlock VAIO Packaging Manager """
input_buffer: bytes = file_to_bytes(input_object)
input_name: str = os.path.basename(input_object) if isinstance(input_buffer, str) else 'VAIO_Package'
make_dirs(in_path=extract_path, delete=True)
if self._vaio_cabinet(name=input_name, buffer=input_buffer, extract_path=extract_path, padding=padding) == 0:
printer(message='Successfully Extracted!', padding=padding)
elif self._vaio_unlock(name=input_name, buffer=input_buffer, extract_path=extract_path, padding=padding) == 0:
printer(message='Successfully Unlocked!', padding=padding)
else:
printer(message='Error: Failed to Extract or Unlock executable!', padding=padding)
return 1
return 0
@staticmethod
def _vaio_cabinet(name: str, buffer: bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Extract VAIO Packaging Manager executable """
# Microsoft CAB Header XOR 0xFF
match_cab: Match[bytes] | None = PAT_VAIO_CAB.search(string=buffer)
if not match_cab:
return 1
printer(message='Detected obfuscated CAB archive!', padding=padding)
# Get LE XOR CAB size
cab_size: int = int.from_bytes(bytes=buffer[match_cab.start() + 0x8:match_cab.start() + 0xC],
byteorder='little')
# Create CAB size XOR value
xor_size: int = int.from_bytes(bytes=b'\xFF' * 0x4, byteorder='little')
# Perform XOR 0xFF and get actual CAB size
cab_size ^= xor_size
printer(message='Removing obfuscation...', padding=padding + 4)
# Get BE XOR CAB data
cab_data: int = int.from_bytes(bytes=buffer[match_cab.start():match_cab.start() + cab_size], byteorder='big')
# Create CAB data XOR value
xor_data: int = int.from_bytes(bytes=b'\xFF' * cab_size, byteorder='big')
# Perform XOR 0xFF and get actual CAB data
raw_data: bytes = (cab_data ^ xor_data).to_bytes(cab_size, 'big')
printer(message='Extracting archive...', padding=padding + 4)
cab_path: str = os.path.join(extract_path, f'{name}_Temporary.cab')
# Create temporary CAB archive
with open(file=cab_path, mode='wb') as cab_file:
cab_file.write(raw_data)
if is_szip_supported(in_path=cab_path, padding=padding + 8, silent=False):
if szip_decompress(in_path=cab_path, out_path=extract_path, in_name='VAIO CAB',
padding=padding + 8, check=True) == 0:
os.remove(path=cab_path)
else:
return 3
else:
return 2
return 0
@staticmethod
def _vaio_unlock(name: str, buffer: bytes | bytearray, extract_path: str, padding: int = 0) -> int:
""" Unlock VAIO Packaging Manager executable """
input_buffer: bytearray = bytearray(buffer) if isinstance(buffer, bytes) else buffer
match_cfg: Match[bytes] | None = PAT_VAIO_CFG.search(string=input_buffer)
if not match_cfg:
return 1
printer(message='Attempting to Unlock executable!', padding=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: list[bytearray] = input_buffer[cfg_bgn:cfg_end].split(
b'\x0D\x0A\x4D\x5A')[0].replace(b'\x0D', b'').split(b'\x0A')
printer(message='Retrieving True/False values...', padding=padding + 4)
# Determine VAIO Package Configuration file True & False values
for info in cfg_info:
if info.startswith(b'ExtractPathByUser='):
# Should be 0/No/False
cfg_false = bytearray(b'0' if info[18:] in (b'0', b'1') else info[18:])
if info.startswith(b'UseCompression='):
# Should be 1/Yes/True
cfg_true = bytearray(b'1' if info[15:] in (b'0', b'1') else info[15:])
# Check if valid True/False values have been retrieved
if cfg_false == cfg_true or not cfg_false or not cfg_true:
printer(message='Error: Could not retrieve True/False values!', padding=padding + 8)
return 2
printer(message='Adjusting UseVAIOCheck entry...', padding=padding + 4)
# Find and replace UseVAIOCheck entry from 1/Yes/True to 0/No/False
vaio_check: Match[bytes] | None = PAT_VAIO_CHK.search(string=input_buffer[cfg_bgn:])
if vaio_check:
input_buffer[cfg_bgn + vaio_check.end():cfg_bgn + vaio_check.end() + len(cfg_true)] = cfg_false
else:
printer(message='Error: Could not find entry UseVAIOCheck!', padding=padding + 8)
return 3
printer(message='Adjusting ExtractPathByUser entry...', padding=padding + 4)
# Find and replace ExtractPathByUser entry from 0/No/False to 1/Yes/True
user_path: Match[bytes] | None = PAT_VAIO_EXT.search(string=input_buffer[cfg_bgn:])
if user_path:
input_buffer[cfg_bgn + user_path.end():cfg_bgn + user_path.end() + len(cfg_false)] = cfg_true
else:
printer(message='Error: Could not find entry ExtractPathByUser!', padding=padding + 8)
return 4
printer(message='Storing unlocked executable...', padding=padding + 4)
# Store Unlocked VAIO Packaging Manager executable
if vaio_check and user_path:
unlock_path: str = os.path.join(extract_path, f'{name}_Unlocked.exe')
with open(file=unlock_path, mode='wb') as unl_file:
unl_file.write(input_buffer)
return 0
if __name__ == '__main__':
VaioPackageExtract().run_utility()

View file

@ -1,6 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2019-2024 Plato Mavropoulos
"""

View file

@ -1,60 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import os
import subprocess
from common.externals import get_tiano_path
from common.system import printer
def get_compress_sizes(data):
""" Get EFI compression sizes """
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):
""" Check if data is EFI compressed, controlling EOF padding """
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
def efi_decompress(in_path, out_path, padding=0, silent=False, comp_type='--uefi'):
""" EFI/Tiano Decompression via TianoCompress """
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 OSError('EFI decompressed file & header size mismatch!')
except Exception as error: # pylint: disable=broad-except
if not silent:
printer(f'Error: TianoCompress could not extract file {in_path}: {error}!', padding)
return 1
if not silent:
printer('Succesfull EFI decompression via TianoCompress!', padding)
return 0

View file

@ -1,72 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import os
import subprocess
from common.externals import get_szip_path
from common.system import printer
def check_bad_exit_code(exit_code):
""" Check 7-Zip bad exit codes (0 OK, 1 Warning) """
if exit_code not in (0, 1):
raise ValueError(f'Bad exit code: {exit_code}')
def is_szip_supported(in_path, padding=0, args=None, check=False, silent=False):
""" Check if file is 7-Zip supported """
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 as error: # pylint: disable=broad-except
if not silent:
printer(f'Error: 7-Zip could not check support for file {in_path}: {error}!', padding)
return False
return True
def szip_decompress(in_path, out_path, in_name, padding=0, args=None, check=False, silent=False):
""" Archive decompression via 7-Zip """
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 OSError(f'Extraction directory not found: {out_path}')
except Exception as error: # pylint: disable=broad-except
if not silent:
printer(f'Error: 7-Zip could not extract {in_name} file {in_path}: {error}!', padding)
return 1
if not silent:
printer(f'Succesfull {in_name} decompression via 7-Zip!', padding)
return 0

View file

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

View file

@ -1,19 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
def get_ordinal(number):
"""
Get ordinal (textual) representation of input numerical value
https://leancrew.com/all-this/2020/06/ordinals-in-python/ by Dr. Drang
"""
txt = ('th', 'st', 'nd', 'rd') + ('th',) * 10
val = number % 100
return f'{number}{txt[val % 10]}' if val > 13 else f'{number}{txt[val]}'

View file

@ -1,213 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import os
import re
import shutil
import stat
import sys
from pathlib import Path, PurePath
from common.system import get_os_ver
from common.text_ops import is_encased, to_string
MAX_WIN_COMP_LEN = 255
def safe_name(in_name):
"""
Fix illegal/reserved Windows characters
Can also be used to nuke dangerous paths
"""
name_repr = repr(in_name).strip("'")
return re.sub(r'[\\/:"*?<>|]+', '_', name_repr)
def safe_path(base_path, user_paths):
""" Check and attempt to fix illegal/unsafe OS path traversals """
# 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 OSError(f'Encountered illegal path traversal: {user_path}')
def is_safe_path(base_path, target_path):
""" Check for illegal/unsafe OS path traversal """
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
def norm_path(base_path, user_path):
""" Create normalized base path + OS separator + user path """
return os.path.normpath(base_path + os.sep + user_path)
def real_path(in_path):
""" Get absolute path, resolving any symlinks """
return os.path.realpath(in_path)
def agnostic_path(in_path):
""" Get Windows/Posix OS agnostic path """
return PurePath(in_path.replace('\\', os.sep))
def path_parent(in_path):
""" Get absolute parent of path """
return Path(in_path).parent.absolute()
def path_name(in_path, limit=False):
""" Get final path component, with suffix """
comp_name = PurePath(in_path).name
if limit and get_os_ver()[1]:
comp_name = comp_name[:MAX_WIN_COMP_LEN - len(extract_suffix())]
return comp_name
def path_stem(in_path):
""" Get final path component, w/o suffix """
return PurePath(in_path).stem
def path_suffixes(in_path):
""" Get list of path file extensions """
return PurePath(in_path).suffixes or ['']
def is_path_absolute(in_path):
""" Check if path is absolute """
return Path(in_path).is_absolute()
def make_dirs(in_path, parents=True, exist_ok=False, delete=False):
""" Create folder(s), controlling parents, existence and prior deletion """
if delete:
del_dirs(in_path)
Path.mkdir(Path(in_path), parents=parents, exist_ok=exist_ok)
def del_dirs(in_path):
""" Delete folder(s), if present """
if Path(in_path).is_dir():
shutil.rmtree(in_path, onerror=clear_readonly_callback)
def copy_file(in_path, out_path, meta=False):
""" Copy file to path with or w/o metadata """
if meta:
shutil.copy2(in_path, out_path)
else:
shutil.copy(in_path, out_path)
def clear_readonly(in_path):
""" Clear read-only file attribute """
os.chmod(in_path, stat.S_IWRITE)
def clear_readonly_callback(in_func, in_path, _):
""" Clear read-only file attribute (on shutil.rmtree error) """
clear_readonly(in_path)
in_func(in_path)
def get_path_files(in_path):
""" Walk path to get all files """
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
def get_dequoted_path(in_path):
""" Get path without leading/trailing quotes """
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
def extract_suffix():
""" Set utility extraction stem """
return '_extracted'
def get_extract_path(in_path, suffix=extract_suffix()):
""" Get utility extraction path """
return f'{in_path}{suffix}'
def project_root():
""" Get project's root directory """
return real_path(Path(__file__).parent.parent)
def runtime_root():
""" Get runtime's root directory """
if getattr(sys, 'frozen', False):
root = Path(sys.executable).parent
else:
root = project_root()
return real_path(root)

View file

@ -1,34 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 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=')

View file

@ -1,34 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import ctypes
Char: type[ctypes.c_char] | int = ctypes.c_char
UInt8: type[ctypes.c_ubyte] | int = ctypes.c_ubyte
UInt16: type[ctypes.c_ushort] | int = ctypes.c_ushort
UInt32: type[ctypes.c_uint] | int = ctypes.c_uint
UInt64: type[ctypes.c_uint64] | int = ctypes.c_uint64
def get_struct(buffer, start_offset, class_name, param_list=None):
"""
https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky
"""
parameters = [] if param_list is None else param_list
structure = class_name(*parameters) # 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

View file

@ -1,84 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import sys
from common.text_ops import padder, to_string
def get_py_ver():
""" Get Python Version (tuple) """
return sys.version_info
def get_os_ver():
""" Get OS Platform (string) """
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
def is_auto_exit():
""" Check for --auto-exit|-e """
return bool('--auto-exit' in sys.argv or '-e' in sys.argv)
def check_sys_py():
""" # Check Python Version """
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)
def check_sys_os():
""" Check OS Platform """
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:
# noinspection PyUnresolvedReferences
sys.stdout.reconfigure(encoding='utf-8')
def printer(message=None, padd=0, new_line=True, pause=False, sep_char=' '):
""" Show message(s), controlling padding, newline, pausing & separator """
message_input = '' if message is None else message
string = to_string(message_input, sep_char)
padding = padder(padd)
newline = '\n' if new_line else ''
message_output = newline + padding + string
(input if pause and not is_auto_exit() else print)(message_output)

View file

@ -1,173 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import argparse
import ctypes
import os
import sys
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_name, path_parent, real_path, runtime_root)
from common.system import check_sys_os, check_sys_py, get_os_ver, is_auto_exit, printer
class BIOSUtility:
""" Template utility class for BIOSUtilities """
MAX_FAT32_ITEMS = 65535
def __init__(self, title, check, main, args=None, padding=0):
self._title = title
self._main = main
self._check = check
self._arg_defs = args if args is not None else []
self._padding = padding
self._arguments_kw = {}
self._arguments_kw_dest = []
# 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')
for _arg_def in self._arg_defs:
_action = self._argparser.add_argument(*_arg_def[0], **_arg_def[1])
self._arguments_kw_dest.append(_action.dest)
self._arguments, _ = self._argparser.parse_known_args()
for _arg_dest in self._arguments_kw_dest:
self._arguments_kw.update({_arg_dest: self._arguments.__dict__[_arg_dest]})
# 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 run_utility(self):
""" Run utility after checking for supported format """
for _input_file in self._input_files:
_input_name = path_name(_input_file, limit=True)
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_user_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_user_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_user_path(_output_path)
# Get absolute user file path
@staticmethod
def _get_user_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 = real_path(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)

View file

@ -1,48 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022-2024 Plato Mavropoulos
"""
def padder(padd_count, tab=False):
""" Generate padding (spaces or tabs) """
return ('\t' if tab else ' ') * padd_count
def to_string(in_object, sep_char=''):
""" Get String from given input object """
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
def file_to_bytes(in_object):
""" Get Bytes from given buffer or file path """
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
def bytes_to_hex(buffer: bytes, order: str, data_len: int, slice_len: int | None = None) -> str:
""" Converts bytes to hex string, controlling endianess, data size and string slicing """
# noinspection PyTypeChecker
return f'{int.from_bytes(buffer, order):0{data_len * 2}X}'[:slice_len]
def is_encased(in_string, chars):
""" Check if string starts and ends with given character(s) """
return in_string.startswith(chars) and in_string.endswith(chars)

View file

@ -1,6 +0,0 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2019-2024 Plato Mavropoulos
"""

63
main.py Normal file
View file

@ -0,0 +1,63 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2018-2024 Plato Mavropoulos
"""
from argparse import ArgumentParser, Namespace
from pathlib import Path
from biosutilities.ami_pfat_extract import AmiPfatExtract
from biosutilities.ami_ucp_extract import AmiUcpExtract
from biosutilities.apple_efi_id import AppleEfiIdentify
from biosutilities.apple_efi_im4p import AppleEfiIm4pSplit
from biosutilities.apple_efi_pbzx import AppleEfiPbzxExtract
from biosutilities.apple_efi_pkg import AppleEfiPkgExtract
from biosutilities.award_bios_extract import AwardBiosExtract
from biosutilities.dell_pfs_extract import DellPfsExtract
from biosutilities.fujitsu_sfx_extract import FujitsuSfxExtract
from biosutilities.fujitsu_upc_extract import FujitsuUpcExtract
from biosutilities.insyde_ifd_extract import InsydeIfdExtract
from biosutilities.panasonic_bios_extract import PanasonicBiosExtract
from biosutilities.phoenix_tdk_extract import PhoenixTdkExtract
from biosutilities.portwell_efi_extract import PortwellEfiExtract
from biosutilities.toshiba_com_extract import ToshibaComExtract
from biosutilities.vaio_package_extract import VaioPackageExtract
if __name__ == '__main__':
main_argparser: ArgumentParser = ArgumentParser(allow_abbrev=False)
main_argparser.add_argument('input_files', nargs='+')
main_argparser.add_argument('--output-folder', help='extraction folder')
main_argparser.add_argument('--pause-exit', help='pause on exit', action='store_false')
main_arguments: Namespace = main_argparser.parse_args()
if main_arguments.output_folder:
output_folder: Path = Path(main_arguments.output_folder)
else:
output_folder = Path(main_arguments.input_files[0]).parent
util_arguments: list[str] = [*main_arguments.input_files, '-e', '-o', str(output_folder.absolute())]
AmiUcpExtract(arguments=util_arguments).run_utility()
AmiPfatExtract(arguments=util_arguments).run_utility()
InsydeIfdExtract(arguments=util_arguments).run_utility()
DellPfsExtract(arguments=util_arguments).run_utility()
PhoenixTdkExtract(arguments=util_arguments).run_utility()
PanasonicBiosExtract(arguments=util_arguments).run_utility()
VaioPackageExtract(arguments=util_arguments).run_utility()
PortwellEfiExtract(arguments=util_arguments).run_utility()
ToshibaComExtract(arguments=util_arguments).run_utility()
FujitsuSfxExtract(arguments=util_arguments).run_utility()
FujitsuUpcExtract(arguments=util_arguments).run_utility()
AwardBiosExtract(arguments=util_arguments).run_utility()
AppleEfiPkgExtract(arguments=util_arguments).run_utility()
AppleEfiPbzxExtract(arguments=util_arguments).run_utility()
AppleEfiIm4pSplit(arguments=util_arguments).run_utility()
AppleEfiIdentify(arguments=util_arguments).run_utility()
if main_arguments.pause_exit:
input('Press any key to exit...')

51
pyproject.toml Normal file
View file

@ -0,0 +1,51 @@
[build-system]
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "biosutilities"
license = {text = "BSD-2-Clause-Patent"}
authors = [
{name = "Plato Mavropoulos"}
]
maintainers = [
{name = "Plato Mavropoulos"}
]
description = "Various BIOS Utilities for Modding/Research"
keywords = [
"bios", "uefi", "firmware", "extract", "unpack", "package", "ami", "insyde", "phoenix", "award",
"apple", "dell", "fujitsu", "panasonic", "toshiba", "dynabook", "vaio", "portwell", "bios guard",
"pfat", "ucp", "im4p", "pbzx", "efi", "pfs", "sfx", "upc", "iflash", "ifdpacker", "ifd", "com"
]
readme = "README.md"
dynamic = ["version"]
requires-python = ">= 3.10"
classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"License :: OSI Approved :: BSD License",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX :: Linux",
"Operating System :: POSIX :: BSD",
"Operating System :: MacOS",
"Topic :: Security",
"Topic :: Scientific/Engineering :: Information Analysis"
]
[project.optional-dependencies]
lznt1 = ["dissect.util == 3.18"]
pefile = ["pefile == 2024.8.26"]
[project.urls]
Homepage = "https://github.com/platomav/BIOSUtilities"
Repository = "https://github.com/platomav/BIOSUtilities"
Issues = "https://github.com/platomav/BIOSUtilities/issues"
Readme = "https://github.com/platomav/BIOSUtilities/blob/main/README.md"
Changelog = "https://github.com/platomav/BIOSUtilities/blob/main/CHANGELOG"
[tool.setuptools.packages.find]
include = ["biosutilities*"]
[tool.setuptools.dynamic]
version = {attr = "biosutilities.__version__"}

2
requirements-dev.txt Normal file
View file

@ -0,0 +1,2 @@
mypy==1.11.2
pylint==3.3.1

View file

@ -1,2 +1,2 @@
dissect.util==3.16
pefile==2023.2.7
dissect.util==3.18
pefile==2024.8.26