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:
parent
ef50b75ae1
commit
cda2fbd0b1
65 changed files with 6239 additions and 5233 deletions
.gitignore.mypy.ini.pylintrcAMI_PFAT_Extract.pyAMI_UCP_Extract.pyApple_EFI_ID.pyApple_EFI_IM4P.pyApple_EFI_PBZX.pyApple_EFI_PKG.pyAward_BIOS_Extract.pyCHANGELOGDell_PFS_Extract.pyFujitsu_SFX_Extract.pyFujitsu_UPC_Extract.pyInsyde_IFD_Extract.pyPanasonic_BIOS_Extract.pyPhoenix_TDK_Extract.pyPortwell_EFI_Extract.pyREADME.mdToshiba_COM_Extract.pyVAIO_Package_Extract.py__init__.py
biosutilities
__init__.pyami_pfat_extract.pyami_ucp_extract.pyapple_efi_id.pyapple_efi_im4p.pyapple_efi_pbzx.pyapple_efi_pkg.pyaward_bios_extract.py
common
checksums.pycompression.pyexecutables.pyexternals.pypaths.pypatterns.pystructs.pysystem.pytemplates.pytexts.py
dell_pfs_extract.pyfujitsu_sfx_extract.pyfujitsu_upc_extract.pyinsyde_ifd_extract.pypanasonic_bios_extract.pyphoenix_tdk_extract.pyportwell_efi_extract.pytoshiba_com_extract.pyvaio_package_extract.pycommon
__init__.pycomp_efi.pycomp_szip.pyexternals.pynum_ops.pypath_ops.pypatterns.pystruct_ops.pysystem.pytemplates.pytext_ops.py
external
main.pypyproject.tomlrequirements-dev.txtrequirements.txt
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,6 +1,4 @@
|
|||
/.idea/
|
||||
/.mypy_cache/
|
||||
/external/*
|
||||
/external/
|
||||
/venv/
|
||||
|
||||
!external/__init__.py
|
||||
|
|
20
.mypy.ini
20
.mypy.ini
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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()
|
181
Apple_EFI_ID.py
181
Apple_EFI_ID.py
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
177
Apple_EFI_PKG.py
177
Apple_EFI_PKG.py
|
@ -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()
|
|
@ -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
478
CHANGELOG
Normal 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
|
1238
Dell_PFS_Extract.py
1238
Dell_PFS_Extract.py
File diff suppressed because it is too large
Load diff
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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
355
README.md
|
@ -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)
|
||||
|
|
|
@ -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()
|
|
@ -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()
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2019-2024 Plato Mavropoulos
|
||||
"""
|
8
biosutilities/__init__.py
Normal file
8
biosutilities/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2018-2024 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
__version__ = '24.10.01'
|
477
biosutilities/ami_pfat_extract.py
Normal file
477
biosutilities/ami_pfat_extract.py
Normal 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()
|
599
biosutilities/ami_ucp_extract.py
Normal file
599
biosutilities/ami_ucp_extract.py
Normal 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()
|
208
biosutilities/apple_efi_id.py
Normal file
208
biosutilities/apple_efi_id.py
Normal 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()
|
157
biosutilities/apple_efi_im4p.py
Normal file
157
biosutilities/apple_efi_im4p.py
Normal 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()
|
133
biosutilities/apple_efi_pbzx.py
Normal file
133
biosutilities/apple_efi_pbzx.py
Normal 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()
|
189
biosutilities/apple_efi_pkg.py
Normal file
189
biosutilities/apple_efi_pkg.py
Normal 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()
|
93
biosutilities/award_bios_extract.py
Normal file
93
biosutilities/award_bios_extract.py
Normal 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()
|
|
@ -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:
|
122
biosutilities/common/compression.py
Normal file
122
biosutilities/common/compression.py
Normal 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
|
|
@ -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)
|
94
biosutilities/common/externals.py
Normal file
94
biosutilities/common/externals.py
Normal 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
|
218
biosutilities/common/paths.py
Normal file
218
biosutilities/common/paths.py
Normal 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)
|
125
biosutilities/common/patterns.py
Normal file
125
biosutilities/common/patterns.py
Normal 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='
|
||||
)
|
38
biosutilities/common/structs.py
Normal file
38
biosutilities/common/structs.py
Normal 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
|
48
biosutilities/common/system.py
Normal file
48
biosutilities/common/system.py
Normal 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)
|
157
biosutilities/common/templates.py
Normal file
157
biosutilities/common/templates.py
Normal 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}')
|
73
biosutilities/common/texts.py
Normal file
73
biosutilities/common/texts.py
Normal 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}'
|
1303
biosutilities/dell_pfs_extract.py
Normal file
1303
biosutilities/dell_pfs_extract.py
Normal file
File diff suppressed because it is too large
Load diff
92
biosutilities/fujitsu_sfx_extract.py
Normal file
92
biosutilities/fujitsu_sfx_extract.py
Normal 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()
|
68
biosutilities/fujitsu_upc_extract.py
Normal file
68
biosutilities/fujitsu_upc_extract.py
Normal 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()
|
244
biosutilities/insyde_ifd_extract.py
Normal file
244
biosutilities/insyde_ifd_extract.py
Normal 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()
|
243
biosutilities/panasonic_bios_extract.py
Normal file
243
biosutilities/panasonic_bios_extract.py
Normal 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()
|
286
biosutilities/phoenix_tdk_extract.py
Normal file
286
biosutilities/phoenix_tdk_extract.py
Normal 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()
|
169
biosutilities/portwell_efi_extract.py
Normal file
169
biosutilities/portwell_efi_extract.py
Normal 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()
|
69
biosutilities/toshiba_com_extract.py
Normal file
69
biosutilities/toshiba_com_extract.py
Normal 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()
|
181
biosutilities/vaio_package_extract.py
Normal file
181
biosutilities/vaio_package_extract.py
Normal 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()
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2019-2024 Plato Mavropoulos
|
||||
"""
|
|
@ -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
|
|
@ -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
|
|
@ -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])
|
|
@ -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]}'
|
|
@ -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)
|
|
@ -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=')
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
6
external/__init__.py
vendored
6
external/__init__.py
vendored
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2019-2024 Plato Mavropoulos
|
||||
"""
|
63
main.py
Normal file
63
main.py
Normal 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
51
pyproject.toml
Normal 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
2
requirements-dev.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
mypy==1.11.2
|
||||
pylint==3.3.1
|
|
@ -1,2 +1,2 @@
|
|||
dissect.util==3.16
|
||||
pefile==2023.2.7
|
||||
dissect.util==3.18
|
||||
pefile==2024.8.26
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue