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
This commit is contained in:
Plato Mavropoulos 2024-04-24 01:22:53 +03:00
parent 03ae0cf070
commit d85a7f82dc
37 changed files with 2897 additions and 2174 deletions

9
.gitignore vendored
View file

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

4
.mypy.ini Normal file
View file

@ -0,0 +1,4 @@
[mypy]
explicit_package_bases = True
mypy_path = $MYPY_CONFIG_FILE_DIR/

19
.pylintrc Normal file
View file

@ -0,0 +1,19 @@
[MAIN]
init-hook="import sys; sys.path.append('./')"
[MESSAGES CONTROL]
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

View file

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

View file

@ -1,29 +1,23 @@
#!/usr/bin/env python3 #!/usr/bin/env python3 -B
#coding=utf-8 # coding=utf-8
""" """
AMI UCP Extract AMI UCP Extract
AMI UCP Update Extractor AMI UCP Update Extractor
Copyright (C) 2021-2022 Plato Mavropoulos Copyright (C) 2021-2024 Plato Mavropoulos
""" """
TITLE = 'AMI UCP Update Extractor v2.0_a20' import contextlib
import ctypes
import os import os
import re import re
import sys
import struct import struct
import ctypes
import contextlib
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.checksums import get_chk_16 from common.checksums import get_chk_16
from common.comp_efi import efi_decompress, is_efi_compressed from common.comp_efi import efi_decompress, is_efi_compressed
from common.path_ops import agnostic_path, make_dirs, safe_name, safe_path, get_extract_path from common.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.patterns import PAT_AMI_UCP, PAT_INTEL_ENG
from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t from common.struct_ops import Char, get_struct, UInt8, UInt16, UInt32
from common.system import printer from common.system import printer
from common.templates import BIOSUtility from common.templates import BIOSUtility
from common.text_ops import file_to_bytes, to_string from common.text_ops import file_to_bytes, to_string
@ -31,419 +25,477 @@ from common.text_ops import file_to_bytes, to_string
from AMI_PFAT_Extract import is_ami_pfat, parse_pfat_file from AMI_PFAT_Extract import is_ami_pfat, parse_pfat_file
from Insyde_IFD_Extract import insyde_ifd_extract, is_insyde_ifd from Insyde_IFD_Extract import insyde_ifd_extract, is_insyde_ifd
TITLE = 'AMI UCP Update Extractor v3.0'
class UafHeader(ctypes.LittleEndianStructure): class UafHeader(ctypes.LittleEndianStructure):
""" UAF Header """
_pack_ = 1 _pack_ = 1
_fields_ = [ _fields_ = [
('ModuleTag', char*4), # 0x00 ('ModuleTag', Char * 4), # 0x00
('ModuleSize', uint32_t), # 0x04 ('ModuleSize', UInt32), # 0x04
('Checksum', uint16_t), # 0x08 ('Checksum', UInt16), # 0x08
('Unknown0', uint8_t), # 0x0A ('Unknown0', UInt8), # 0x0A
('Unknown1', uint8_t), # 0x0A ('Unknown1', UInt8), # 0x0A
('Reserved', uint8_t*4), # 0x0C ('Reserved', UInt8 * 4), # 0x0C
# 0x10 # 0x10
] ]
def _get_reserved(self): def _get_reserved(self):
res_bytes = bytes(self.Reserved) res_bytes = bytes(self.Reserved)
res_hex = f'0x{int.from_bytes(res_bytes, "big"):0{0x4 * 2}X}' 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_str = re.sub(r'[\n\t\r\x00 ]', '', res_bytes.decode('utf-8', 'ignore'))
res_txt = f' ({res_str})' if len(res_str) else '' res_txt = f' ({res_str})' if len(res_str) else ''
return f'{res_hex}{res_txt}' return f'{res_hex}{res_txt}'
def struct_print(self, p): def struct_print(self, padd):
printer(['Tag :', self.ModuleTag.decode('utf-8')], p, False) """ Display structure information """
printer(['Size :', f'0x{self.ModuleSize:X}'], p, False)
printer(['Checksum :', f'0x{self.Checksum:04X}'], p, False) printer(['Tag :', self.ModuleTag.decode('utf-8')], padd, False)
printer(['Unknown 0 :', f'0x{self.Unknown0:02X}'], p, False) printer(['Size :', f'0x{self.ModuleSize:X}'], padd, False)
printer(['Unknown 1 :', f'0x{self.Unknown1:02X}'], p, False) printer(['Checksum :', f'0x{self.Checksum:04X}'], padd, False)
printer(['Reserved :', self._get_reserved()], p, 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): class UafModule(ctypes.LittleEndianStructure):
""" UAF Module """
_pack_ = 1 _pack_ = 1
_fields_ = [ _fields_ = [
('CompressSize', uint32_t), # 0x00 ('CompressSize', UInt32), # 0x00
('OriginalSize', uint32_t), # 0x04 ('OriginalSize', UInt32), # 0x04
# 0x08 # 0x08
] ]
def struct_print(self, p, filename, description): def struct_print(self, padd, filename, description):
printer(['Compress Size:', f'0x{self.CompressSize:X}'], p, False) """ Display structure information """
printer(['Original Size:', f'0x{self.OriginalSize:X}'], p, False)
printer(['Filename :', filename], p, False) printer(['Compress Size:', f'0x{self.CompressSize:X}'], padd, False)
printer(['Description :', description], p, 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): class UiiHeader(ctypes.LittleEndianStructure):
""" UII Header """
_pack_ = 1 _pack_ = 1
_fields_ = [ _fields_ = [
('UIISize', uint16_t), # 0x00 ('UIISize', UInt16), # 0x00
('Checksum', uint16_t), # 0x02 ('Checksum', UInt16), # 0x02
('UtilityVersion', uint32_t), # 0x04 AFU|BGT (Unknown, Signed) ('UtilityVersion', UInt32), # 0x04 AFU|BGT (Unknown, Signed)
('InfoSize', uint16_t), # 0x08 ('InfoSize', UInt16), # 0x08
('SupportBIOS', uint8_t), # 0x0A ('SupportBIOS', UInt8), # 0x0A
('SupportOS', uint8_t), # 0x0B ('SupportOS', UInt8), # 0x0B
('DataBusWidth', uint8_t), # 0x0C ('DataBusWidth', UInt8), # 0x0C
('ProgramType', uint8_t), # 0x0D ('ProgramType', UInt8), # 0x0D
('ProgramMode', uint8_t), # 0x0E ('ProgramMode', UInt8), # 0x0E
('SourceSafeRel', uint8_t), # 0x0F ('SourceSafeRel', UInt8), # 0x0F
# 0x10 # 0x10
] ]
SBI = {1: 'ALL', 2: 'AMIBIOS8', 3: 'UEFI', 4: 'AMIBIOS8/UEFI'} 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'} 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'} DBW = {1: '16b', 2: '16/32b', 3: '32b', 4: '64b'}
PTP = {1: 'Executable', 2: 'Library', 3: 'Driver'} PTP = {1: 'Executable', 2: 'Library', 3: 'Driver'}
PMD = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'} PMD = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'}
def struct_print(self, p, description): def struct_print(self, padd, description):
SupportBIOS = self.SBI.get(self.SupportBIOS, f'Unknown ({self.SupportBIOS})') """ Display structure information """
SupportOS = self.SOS.get(self.SupportOS, f'Unknown ({self.SupportOS})')
DataBusWidth = self.DBW.get(self.DataBusWidth, f'Unknown ({self.DataBusWidth})') support_bios = self.SBI.get(self.SupportBIOS, f'Unknown ({self.SupportBIOS})')
ProgramType = self.PTP.get(self.ProgramType, f'Unknown ({self.ProgramType})') support_os = self.SOS.get(self.SupportOS, f'Unknown ({self.SupportOS})')
ProgramMode = self.PMD.get(self.ProgramMode, f'Unknown ({self.ProgramMode})') data_bus_width = self.DBW.get(self.DataBusWidth, f'Unknown ({self.DataBusWidth})')
program_type = self.PTP.get(self.ProgramType, f'Unknown ({self.ProgramType})')
printer(['UII Size :', f'0x{self.UIISize:X}'], p, False) program_mode = self.PMD.get(self.ProgramMode, f'Unknown ({self.ProgramMode})')
printer(['Checksum :', f'0x{self.Checksum:04X}'], p, False)
printer(['Tool Version :', f'0x{self.UtilityVersion:08X}'], p, False) printer(['UII Size :', f'0x{self.UIISize:X}'], padd, False)
printer(['Info Size :', f'0x{self.InfoSize:X}'], p, False) printer(['Checksum :', f'0x{self.Checksum:04X}'], padd, False)
printer(['Supported BIOS:', SupportBIOS], p, False) printer(['Tool Version :', f'0x{self.UtilityVersion:08X}'], padd, False)
printer(['Supported OS :', SupportOS], p, False) printer(['Info Size :', f'0x{self.InfoSize:X}'], padd, False)
printer(['Data Bus Width:', DataBusWidth], p, False) printer(['Supported BIOS:', support_bios], padd, False)
printer(['Program Type :', ProgramType], p, False) printer(['Supported OS :', support_os], padd, False)
printer(['Program Mode :', ProgramMode], p, False) printer(['Data Bus Width:', data_bus_width], padd, False)
printer(['SourceSafe Tag:', f'{self.SourceSafeRel:02d}'], p, False) printer(['Program Type :', program_type], padd, False)
printer(['Description :', description], p, 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): class DisHeader(ctypes.LittleEndianStructure):
""" DIS Header """
_pack_ = 1 _pack_ = 1
_fields_ = [ _fields_ = [
('PasswordSize', uint16_t), # 0x00 ('PasswordSize', UInt16), # 0x00
('EntryCount', uint16_t), # 0x02 ('EntryCount', UInt16), # 0x02
('Password', char*12), # 0x04 ('Password', Char * 12), # 0x04
# 0x10 # 0x10
] ]
def struct_print(self, p): def struct_print(self, padd):
printer(['Password Size:', f'0x{self.PasswordSize:X}'], p, False) """ Display structure information """
printer(['Entry Count :', self.EntryCount], p, False)
printer(['Password :', self.Password.decode('utf-8')], p, False) 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): class DisModule(ctypes.LittleEndianStructure):
""" DIS Module """
_pack_ = 1 _pack_ = 1
_fields_ = [ _fields_ = [
('EnabledDisabled', uint8_t), # 0x00 ('EnabledDisabled', UInt8), # 0x00
('ShownHidden', uint8_t), # 0x01 ('ShownHidden', UInt8), # 0x01
('Command', char*32), # 0x02 ('Command', Char * 32), # 0x02
('Description', char*256), # 0x22 ('Description', Char * 256), # 0x22
# 0x122 # 0x122
] ]
ENDIS = {0: 'Disabled', 1: 'Enabled'} ENDIS = {0: 'Disabled', 1: 'Enabled'}
SHOWN = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'} SHOWN = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'}
def struct_print(self, p):
EnabledDisabled = self.ENDIS.get(self.EnabledDisabled, f'Unknown ({self.EnabledDisabled})')
ShownHidden = self.SHOWN.get(self.ShownHidden, f'Unknown ({self.ShownHidden})')
printer(['State :', EnabledDisabled], p, False)
printer(['Display :', ShownHidden], p, False)
printer(['Command :', self.Command.decode('utf-8').strip()], p, False)
printer(['Description:', self.Description.decode('utf-8').strip()], p, False)
# Validate UCP Module Checksum-16 def 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): def chk16_validate(data, tag, padd=0):
""" Validate UCP Module Checksum-16 """
if get_chk_16(data) != 0: if get_chk_16(data) != 0:
printer(f'Error: Invalid UCP Module {tag} Checksum!', padd, pause=True) printer(f'Error: Invalid UCP Module {tag} Checksum!', padd, pause=True)
else: else:
printer(f'Checksum of UCP Module {tag} is valid!', padd) printer(f'Checksum of UCP Module {tag} is valid!', padd)
# Check if input is AMI UCP image
def is_ami_ucp(in_file): def is_ami_ucp(in_file):
""" Check if input is AMI UCP image """
buffer = file_to_bytes(in_file) buffer = file_to_bytes(in_file)
return bool(get_ami_ucp(buffer)[0] is not None) return bool(get_ami_ucp(buffer)[0] is not None)
# Get all input file AMI UCP patterns
def get_ami_ucp(in_file): def get_ami_ucp(in_file):
""" Get all input file AMI UCP patterns """
buffer = file_to_bytes(in_file) buffer = file_to_bytes(in_file)
uaf_len_max = 0x0 # Length of largest detected @UAF|@HPU uaf_len_max = 0x0 # Length of largest detected @UAF|@HPU
uaf_buf_bin = None # Buffer 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 uaf_buf_tag = '@UAF' # Tag of largest detected @UAF|@HPU
for uaf in PAT_AMI_UCP.finditer(buffer): for uaf in PAT_AMI_UCP.finditer(buffer):
uaf_len_cur = int.from_bytes(buffer[uaf.start() + 0x4:uaf.start() + 0x8], 'little') uaf_len_cur = int.from_bytes(buffer[uaf.start() + 0x4:uaf.start() + 0x8], 'little')
if uaf_len_cur > uaf_len_max: if uaf_len_cur > uaf_len_max:
uaf_len_max = uaf_len_cur uaf_len_max = uaf_len_cur
uaf_hdr_off = uaf.start() uaf_hdr_off = uaf.start()
uaf_buf_bin = buffer[uaf_hdr_off:uaf_hdr_off + uaf_len_max] uaf_buf_bin = buffer[uaf_hdr_off:uaf_hdr_off + uaf_len_max]
uaf_buf_tag = uaf.group(0)[:4].decode('utf-8','ignore')
uaf_buf_tag = uaf.group(0)[:4].decode('utf-8', 'ignore')
return uaf_buf_bin, uaf_buf_tag return uaf_buf_bin, uaf_buf_tag
# Get list of @UAF|@HPU Modules
def get_uaf_mod(buffer, uaf_off=0x0): def get_uaf_mod(buffer, uaf_off=0x0):
uaf_all = [] # Initialize list of all @UAF|@HPU Modules """ Get list of @UAF|@HPU Modules """
while buffer[uaf_off] == 0x40: # ASCII of @ is 0x40 uaf_all = [] # Initialize list of all @UAF|@HPU Modules
uaf_hdr = get_struct(buffer, uaf_off, UafHeader) # Parse @UAF|@HPU Module Structure
while buffer[uaf_off] == 0x40: # ASCII of @ is 0x40
uaf_tag = uaf_hdr.ModuleTag.decode('utf-8') # Get unique @UAF|@HPU Module Tag uaf_hdr = get_struct(buffer, uaf_off, UafHeader) # Parse @UAF|@HPU Module Structure
uaf_all.append([uaf_tag, uaf_off, uaf_hdr]) # Store @UAF|@HPU Module Info uaf_tag = uaf_hdr.ModuleTag.decode('utf-8') # Get unique @UAF|@HPU Module Tag
uaf_off += uaf_hdr.ModuleSize # Adjust to next @UAF|@HPU Module offset 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): if uaf_off >= len(buffer):
break # Stop parsing at EOF break # Stop parsing at EOF
# Check if @UAF|@HPU Module @NAL exists and place it first # Check if @UAF|@HPU Module @NAL exists and place it first
# Parsing @NAL first allows naming all @UAF|@HPU Modules # Parsing @NAL first allows naming all @UAF|@HPU Modules
for mod_idx,mod_val in enumerate(uaf_all): for mod_idx, mod_val in enumerate(uaf_all):
if mod_val[0] == '@NAL': if mod_val[0] == '@NAL':
uaf_all.insert(1, uaf_all.pop(mod_idx)) # After UII for visual purposes uaf_all.insert(1, uaf_all.pop(mod_idx)) # After UII for visual purposes
break # @NAL found, skip the rest break # @NAL found, skip the rest
return uaf_all return uaf_all
# Parse & Extract AMI UCP structures
def ucp_extract(in_file, extract_path, padding=0, checksum=False): def ucp_extract(in_file, extract_path, padding=0, checksum=False):
""" Parse & Extract AMI UCP structures """
input_buffer = file_to_bytes(in_file) input_buffer = file_to_bytes(in_file)
nal_dict = {} # Initialize @NAL Dictionary per UCP nal_dict = {} # Initialize @NAL Dictionary per UCP
printer('Utility Configuration Program', padding) printer('Utility Configuration Program', padding)
make_dirs(extract_path, delete=True) make_dirs(extract_path, delete=True)
# Get best AMI UCP Pattern match based on @UAF|@HPU Size # Get best AMI UCP Pattern match based on @UAF|@HPU Size
ucp_buffer,ucp_tag = get_ami_ucp(input_buffer) ucp_buffer, ucp_tag = get_ami_ucp(input_buffer)
uaf_hdr = get_struct(ucp_buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure uaf_hdr = get_struct(ucp_buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure
printer(f'Utility Auxiliary File > {ucp_tag}:\n', padding + 4) printer(f'Utility Auxiliary File > {ucp_tag}:\n', padding + 4)
uaf_hdr.struct_print(padding + 8) uaf_hdr.struct_print(padding + 8)
fake = struct.pack('<II', len(ucp_buffer), len(ucp_buffer)) # Generate UafModule Structure fake = struct.pack('<II', len(ucp_buffer), len(ucp_buffer)) # Generate UafModule Structure
uaf_mod = get_struct(fake, 0x0, UafModule) # Parse @UAF|@HPU Module EFI Structure 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_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_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
uaf_mod.struct_print(padding + 8, uaf_name, uaf_desc) # Print @UAF|@HPU Module EFI Info
if checksum: if checksum:
chk16_validate(ucp_buffer, ucp_tag, padding + 8) chk16_validate(ucp_buffer, ucp_tag, padding + 8)
uaf_all = get_uaf_mod(ucp_buffer, UAF_HDR_LEN) uaf_all = get_uaf_mod(ucp_buffer, UAF_HDR_LEN)
for mod_info in uaf_all: for mod_info in uaf_all:
nal_dict = uaf_extract(ucp_buffer, extract_path, mod_info, padding + 8, checksum, nal_dict) nal_dict = uaf_extract(ucp_buffer, extract_path, mod_info, padding + 8, checksum, nal_dict)
# Parse & Extract AMI UCP > @UAF|@HPU Module/Section
def uaf_extract(buffer, extract_path, mod_info, padding=0, checksum=False, nal_dict=None): 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: if nal_dict is None:
nal_dict = {} nal_dict = {}
uaf_tag,uaf_off,uaf_hdr = mod_info 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_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_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 uaf_data_raw = uaf_data_mod[UAF_MOD_LEN:] # @UAF|@HPU Module Raw Data
printer(f'Utility Auxiliary File > {uaf_tag}:\n', padding) printer(f'Utility Auxiliary File > {uaf_tag}:\n', padding)
uaf_hdr.struct_print(padding + 4) # Print @UAF|@HPU Module Info 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 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 is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect @UAF|@HPU Module EFI Compression
if uaf_tag in nal_dict: if uaf_tag in nal_dict:
uaf_name = nal_dict[uaf_tag][1] # Always prefer @NAL naming first uaf_name = nal_dict[uaf_tag][1] # Always prefer @NAL naming first
elif uaf_tag in UAF_TAG_DICT: elif uaf_tag in UAF_TAG_DICT:
uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming
elif uaf_tag == '@ROM': elif uaf_tag == '@ROM':
uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature) uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature)
elif uaf_tag.startswith('@R0'): elif uaf_tag.startswith('@R0'):
uaf_name = f'BIOS_0{uaf_tag[3:]}.bin' # BIOS/PFAT Firmware uaf_name = f'BIOS_0{uaf_tag[3:]}.bin' # BIOS/PFAT Firmware
elif uaf_tag.startswith('@S0'): elif uaf_tag.startswith('@S0'):
uaf_name = f'BIOS_0{uaf_tag[3:]}.sig' # BIOS/PFAT Signature uaf_name = f'BIOS_0{uaf_tag[3:]}.sig' # BIOS/PFAT Signature
elif uaf_tag.startswith('@DR'): elif uaf_tag.startswith('@DR'):
uaf_name = f'DROM_0{uaf_tag[3:]}.bin' # Thunderbolt Retimer Firmware uaf_name = f'DROM_0{uaf_tag[3:]}.bin' # Thunderbolt Retimer Firmware
elif uaf_tag.startswith('@DS'): elif uaf_tag.startswith('@DS'):
uaf_name = f'DROM_0{uaf_tag[3:]}.sig' # Thunderbolt Retimer Signature uaf_name = f'DROM_0{uaf_tag[3:]}.sig' # Thunderbolt Retimer Signature
elif uaf_tag.startswith('@EC'): elif uaf_tag.startswith('@EC'):
uaf_name = f'EC_0{uaf_tag[3:]}.bin' # Embedded Controller Firmware uaf_name = f'EC_0{uaf_tag[3:]}.bin' # Embedded Controller Firmware
elif uaf_tag.startswith('@ME'): elif uaf_tag.startswith('@ME'):
uaf_name = f'ME_0{uaf_tag[3:]}.bin' # Management Engine Firmware uaf_name = f'ME_0{uaf_tag[3:]}.bin' # Management Engine Firmware
else: else:
uaf_name = uaf_tag # Could not name the @UAF|@HPU Module, use Tag instead uaf_name = uaf_tag # Could not name the @UAF|@HPU Module, use Tag instead
uaf_fext = '' if uaf_name != uaf_tag else '.bin' 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_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 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 # 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')): if uaf_tag in nal_dict and uaf_tag not in UAF_TAG_DICT and \
printer(f'Note: Detected new AMI UCP Module {uaf_tag} ({nal_dict[uaf_tag][1]}) in @NAL!', padding + 4, pause=True) 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 # 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)) uaf_sname = safe_name(uaf_name + ('.temp' if is_comp else uaf_fext))
if uaf_tag in nal_dict: if uaf_tag in nal_dict:
uaf_npath = safe_path(extract_path, nal_dict[uaf_tag][0]) uaf_npath = safe_path(extract_path, nal_dict[uaf_tag][0])
make_dirs(uaf_npath, exist_ok=True) make_dirs(uaf_npath, exist_ok=True)
uaf_fname = safe_path(uaf_npath, uaf_sname) uaf_fname = safe_path(uaf_npath, uaf_sname)
else: else:
uaf_fname = safe_path(extract_path, uaf_sname) uaf_fname = safe_path(extract_path, uaf_sname)
if checksum: if checksum:
chk16_validate(uaf_data_all, uaf_tag, padding + 4) chk16_validate(uaf_data_all, uaf_tag, padding + 4)
# Parse Utility Identification Information @UAF|@HPU Module (@UII) # Parse Utility Identification Information @UAF|@HPU Module (@UII)
if uaf_tag == '@UII': if uaf_tag == '@UII':
info_hdr = get_struct(uaf_data_raw, 0, UiiHeader) # Parse @UII Module Raw Structure 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 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 # Get @UII Module Info/Description text field
info_desc = info_data.decode('utf-8','ignore').strip('\x00 ') info_desc = info_data.decode('utf-8', 'ignore').strip('\x00 ')
printer('Utility Identification Information:\n', padding + 4) printer('Utility Identification Information:\n', padding + 4)
info_hdr.struct_print(padding + 8, info_desc) # Print @UII Module Info info_hdr.struct_print(padding + 8, info_desc) # Print @UII Module Info
if checksum: if checksum:
chk16_validate(uaf_data_raw, '@UII > Info', padding + 8) chk16_validate(uaf_data_raw, '@UII > Info', padding + 8)
# Store/Save @UII Module Info in file # Store/Save @UII Module Info in file
with open(uaf_fname[:-4] + '.txt', 'a', encoding='utf-8') as uii_out: with open(uaf_fname[:-4] + '.txt', 'a', encoding='utf-8') as uii_out:
with contextlib.redirect_stdout(uii_out): with contextlib.redirect_stdout(uii_out):
info_hdr.struct_print(0, info_desc) # Store @UII Module Info info_hdr.struct_print(0, info_desc) # Store @UII Module Info
# Adjust @UAF|@HPU Module Raw Data for extraction # Adjust @UAF|@HPU Module Raw Data for extraction
if is_comp: if is_comp:
# Some Compressed @UAF|@HPU Module EFI data lack necessary EOF padding # Some Compressed @UAF|@HPU Module EFI data lack necessary EOF padding
if uaf_mod.CompressSize > len(uaf_data_raw): if uaf_mod.CompressSize > len(uaf_data_raw):
comp_padd = b'\x00' * (uaf_mod.CompressSize - len(uaf_data_raw)) comp_padd = b'\x00' * (uaf_mod.CompressSize - len(uaf_data_raw))
uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw + comp_padd # Add missing padding for decompression
# Add missing padding for decompression
uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw + comp_padd
else: else:
uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw # Add the EFI/Tiano Compression info before Raw Data # Add the EFI/Tiano Compression info before Raw Data
uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw
else: else:
uaf_data_raw = uaf_data_raw[:uaf_mod.OriginalSize] # No compression, extend to end of Original @UAF|@HPU Module size # 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 # Store/Save @UAF|@HPU Module file
if uaf_tag != '@UII': # Skip @UII binary, already parsed if uaf_tag != '@UII': # Skip @UII binary, already parsed
with open(uaf_fname, 'wb') as uaf_out: with open(uaf_fname, 'wb') as uaf_out:
uaf_out.write(uaf_data_raw) uaf_out.write(uaf_data_raw)
# @UAF|@HPU Module EFI/Tiano Decompression # @UAF|@HPU Module EFI/Tiano Decompression
if is_comp and is_efi_compressed(uaf_data_raw, False): 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 dec_fname = uaf_fname.replace('.temp', uaf_fext) # Decompressed @UAF|@HPU Module file path
if efi_decompress(uaf_fname, dec_fname, padding + 4) == 0: if efi_decompress(uaf_fname, dec_fname, padding + 4) == 0:
with open(dec_fname, 'rb') as dec: with open(dec_fname, 'rb') as dec:
uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data 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 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 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) # 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': 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(f'{UAF_TAG_DICT[uaf_tag][1]}:', padding + 4)
printer(uaf_data_raw.decode('utf-8','ignore'), padding + 8)
printer(uaf_data_raw.decode('utf-8', 'ignore'), padding + 8)
# Parse Default Command Status @UAF|@HPU Module (@DIS) # Parse Default Command Status @UAF|@HPU Module (@DIS)
if len(uaf_data_raw) and uaf_tag == '@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 dis_hdr = get_struct(uaf_data_raw, 0x0, DisHeader) # Parse @DIS Module Raw Header Structure
printer('Default Command Status Header:\n', padding + 4) printer('Default Command Status Header:\n', padding + 4)
dis_hdr.struct_print(padding + 8) # Print @DIS Module Raw Header Info dis_hdr.struct_print(padding + 8) # Print @DIS Module Raw Header Info
# Store/Save @DIS Module Header Info in file # Store/Save @DIS Module Header Info in file
with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis: with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis:
with contextlib.redirect_stdout(dis): with contextlib.redirect_stdout(dis):
dis_hdr.struct_print(0) # Store @DIS Module Header Info dis_hdr.struct_print(0) # Store @DIS Module Header Info
dis_data = uaf_data_raw[DIS_HDR_LEN:] # @DIS Module Entries Data dis_data = uaf_data_raw[DIS_HDR_LEN:] # @DIS Module Entries Data
# Parse all @DIS Module Entries # Parse all @DIS Module Entries
for mod_idx in range(dis_hdr.EntryCount): 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 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) 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 dis_mod.struct_print(padding + 12) # Print @DIS Module Raw Entry Info
# Store/Save @DIS Module Entry Info in file # Store/Save @DIS Module Entry Info in file
with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis: with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis:
with contextlib.redirect_stdout(dis): with contextlib.redirect_stdout(dis):
printer() printer()
dis_mod.struct_print(4) # Store @DIS Module Entry Info
dis_mod.struct_print(4) # Store @DIS Module Entry Info
os.remove(uaf_fname) # Delete @DIS Module binary, info exported as text
os.remove(uaf_fname) # Delete @DIS Module binary, info exported as text
# Parse Name List @UAF|@HPU Module (@NAL) # 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): 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') nal_info = uaf_data_raw.decode('utf-8', 'ignore').replace('\r', '').strip().split('\n')
printer('AMI UCP Module Name List:\n', padding + 4) printer('AMI UCP Module Name List:\n', padding + 4)
# Parse all @NAL Module Entries # Parse all @NAL Module Entries
for info in nal_info: for info in nal_info:
info_tag,info_value = info.split(':',1) info_tag, info_value = info.split(':', 1)
printer(f'{info_tag} : {info_value}', padding + 8, False) # Print @NAL Module Tag-Path Info 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_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 info_path = to_string(info_part[1:-1], os.sep) # Get path without drive/root or file
nal_dict[info_tag] = (info_path,info_name) # Assign a file path & name to each Tag 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) # Parse Insyde BIOS @UAF|@HPU Module (@INS)
if uaf_tag == '@INS' and is_insyde_ifd(uaf_fname): 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 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: 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 os.remove(uaf_fname) # Delete raw nested Insyde IFD image after successful extraction
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image # Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
if is_ami_pfat(uaf_data_raw): if is_ami_pfat(uaf_data_raw):
pfat_dir = os.path.join(extract_path, safe_name(uaf_name)) pfat_dir = os.path.join(extract_path, safe_name(uaf_name))
parse_pfat_file(uaf_data_raw, get_extract_path(pfat_dir), padding + 4) 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 os.remove(uaf_fname) # Delete raw PFAT BIOS image after successful extraction
# Detect Intel Engine firmware image and show ME Analyzer advice # Detect Intel Engine firmware image and show ME Analyzer advice
if uaf_tag.startswith('@ME') and PAT_INTEL_ENG.search(uaf_data_raw): if uaf_tag.startswith('@ME') and PAT_INTEL_ENG.search(uaf_data_raw):
printer('Intel Management Engine (ME) Firmware:\n', padding + 4) printer('Intel Management Engine (ME) Firmware:\n', padding + 4)
printer('Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer', padding + 8, False) printer('Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer', padding + 8, False)
# Parse Nested AMI UCP image # Parse Nested AMI UCP image
if is_ami_ucp(uaf_data_raw): if is_ami_ucp(uaf_data_raw):
uaf_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-UCP')) # Generate extraction directory 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 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 os.remove(uaf_fname) # Delete raw nested AMI UCP image after successful extraction
return nal_dict return nal_dict
# Get common ctypes Structure Sizes # Get common ctypes Structure Sizes
UAF_HDR_LEN = ctypes.sizeof(UafHeader) UAF_HDR_LEN = ctypes.sizeof(UafHeader)
UAF_MOD_LEN = ctypes.sizeof(UafModule) UAF_MOD_LEN = ctypes.sizeof(UafModule)
@ -453,63 +505,66 @@ UII_HDR_LEN = ctypes.sizeof(UiiHeader)
# AMI UCP Tag Dictionary # AMI UCP Tag Dictionary
UAF_TAG_DICT = { UAF_TAG_DICT = {
'@3FI' : ['HpBiosUpdate32.efi', 'HpBiosUpdate32.efi', ''], '@3FI': ['HpBiosUpdate32.efi', 'HpBiosUpdate32.efi', ''],
'@3S2' : ['HpBiosUpdate32.s12', 'HpBiosUpdate32.s12', ''], '@3S2': ['HpBiosUpdate32.s12', 'HpBiosUpdate32.s12', ''],
'@3S4' : ['HpBiosUpdate32.s14', 'HpBiosUpdate32.s14', ''], '@3S4': ['HpBiosUpdate32.s14', 'HpBiosUpdate32.s14', ''],
'@3S9' : ['HpBiosUpdate32.s09', 'HpBiosUpdate32.s09', ''], '@3S9': ['HpBiosUpdate32.s09', 'HpBiosUpdate32.s09', ''],
'@3SG' : ['HpBiosUpdate32.sig', 'HpBiosUpdate32.sig', ''], '@3SG': ['HpBiosUpdate32.sig', 'HpBiosUpdate32.sig', ''],
'@AMI' : ['UCP_Nested.bin', 'Nested AMI UCP', ''], '@AMI': ['UCP_Nested.bin', 'Nested AMI UCP', ''],
'@B12' : ['BiosMgmt.s12', 'BiosMgmt.s12', ''], '@B12': ['BiosMgmt.s12', 'BiosMgmt.s12', ''],
'@B14' : ['BiosMgmt.s14', 'BiosMgmt.s14', ''], '@B14': ['BiosMgmt.s14', 'BiosMgmt.s14', ''],
'@B32' : ['BiosMgmt32.s12', 'BiosMgmt32.s12', ''], '@B32': ['BiosMgmt32.s12', 'BiosMgmt32.s12', ''],
'@B34' : ['BiosMgmt32.s14', 'BiosMgmt32.s14', ''], '@B34': ['BiosMgmt32.s14', 'BiosMgmt32.s14', ''],
'@B39' : ['BiosMgmt32.s09', 'BiosMgmt32.s09', ''], '@B39': ['BiosMgmt32.s09', 'BiosMgmt32.s09', ''],
'@B3E' : ['BiosMgmt32.efi', 'BiosMgmt32.efi', ''], '@B3E': ['BiosMgmt32.efi', 'BiosMgmt32.efi', ''],
'@BM9' : ['BiosMgmt.s09', 'BiosMgmt.s09', ''], '@BM9': ['BiosMgmt.s09', 'BiosMgmt.s09', ''],
'@BME' : ['BiosMgmt.efi', 'BiosMgmt.efi', ''], '@BME': ['BiosMgmt.efi', 'BiosMgmt.efi', ''],
'@CKV' : ['Check_Version.txt', 'Check Version', 'Text'], '@CKV': ['Check_Version.txt', 'Check Version', 'Text'],
'@CMD' : ['AFU_Command.txt', 'AMI AFU Command', 'Text'], '@CMD': ['AFU_Command.txt', 'AMI AFU Command', 'Text'],
'@CML' : ['CMOSD4.txt', 'CMOS Item Number-Value (MSI)', 'Text'], '@CML': ['CMOSD4.txt', 'CMOS Item Number-Value (MSI)', 'Text'],
'@CMS' : ['CMOSD4.exe', 'Get or Set CMOS Item (MSI)', ''], '@CMS': ['CMOSD4.exe', 'Get or Set CMOS Item (MSI)', ''],
'@CPM' : ['AC_Message.txt', 'Confirm Power Message', ''], '@CPM': ['AC_Message.txt', 'Confirm Power Message', ''],
'@DCT' : ['DevCon32.exe', 'Device Console WIN32', ''], '@DCT': ['DevCon32.exe', 'Device Console WIN32', ''],
'@DCX' : ['DevCon64.exe', 'Device Console WIN64', ''], '@DCX': ['DevCon64.exe', 'Device Console WIN64', ''],
'@DFE' : ['HpDevFwUpdate.efi', 'HpDevFwUpdate.efi', ''], '@DFE': ['HpDevFwUpdate.efi', 'HpDevFwUpdate.efi', ''],
'@DFS' : ['HpDevFwUpdate.s12', 'HpDevFwUpdate.s12', ''], '@DFS': ['HpDevFwUpdate.s12', 'HpDevFwUpdate.s12', ''],
'@DIS' : ['Command_Status.bin', 'Default Command Status', ''], '@DIS': ['Command_Status.bin', 'Default Command Status', ''],
'@ENB' : ['ENBG64.exe', 'ENBG64.exe', ''], '@ENB': ['ENBG64.exe', 'ENBG64.exe', ''],
'@HPU' : ['UCP_Main.bin', 'Utility Auxiliary File (HP)', ''], '@HPU': ['UCP_Main.bin', 'Utility Auxiliary File (HP)', ''],
'@INS' : ['Insyde_Nested.bin', 'Nested Insyde SFX', ''], '@INS': ['Insyde_Nested.bin', 'Nested Insyde SFX', ''],
'@M32' : ['HpBiosMgmt32.s12', 'HpBiosMgmt32.s12', ''], '@M32': ['HpBiosMgmt32.s12', 'HpBiosMgmt32.s12', ''],
'@M34' : ['HpBiosMgmt32.s14', 'HpBiosMgmt32.s14', ''], '@M34': ['HpBiosMgmt32.s14', 'HpBiosMgmt32.s14', ''],
'@M39' : ['HpBiosMgmt32.s09', 'HpBiosMgmt32.s09', ''], '@M39': ['HpBiosMgmt32.s09', 'HpBiosMgmt32.s09', ''],
'@M3I' : ['HpBiosMgmt32.efi', 'HpBiosMgmt32.efi', ''], '@M3I': ['HpBiosMgmt32.efi', 'HpBiosMgmt32.efi', ''],
'@MEC' : ['FWUpdLcl.txt', 'Intel FWUpdLcl Command', 'Text'], '@MEC': ['FWUpdLcl.txt', 'Intel FWUpdLcl Command', 'Text'],
'@MED' : ['FWUpdLcl_DOS.exe', 'Intel FWUpdLcl DOS', ''], '@MED': ['FWUpdLcl_DOS.exe', 'Intel FWUpdLcl DOS', ''],
'@MET' : ['FWUpdLcl_WIN32.exe', 'Intel FWUpdLcl WIN32', ''], '@MET': ['FWUpdLcl_WIN32.exe', 'Intel FWUpdLcl WIN32', ''],
'@MFI' : ['HpBiosMgmt.efi', 'HpBiosMgmt.efi', ''], '@MFI': ['HpBiosMgmt.efi', 'HpBiosMgmt.efi', ''],
'@MS2' : ['HpBiosMgmt.s12', 'HpBiosMgmt.s12', ''], '@MS2': ['HpBiosMgmt.s12', 'HpBiosMgmt.s12', ''],
'@MS4' : ['HpBiosMgmt.s14', 'HpBiosMgmt.s14', ''], '@MS4': ['HpBiosMgmt.s14', 'HpBiosMgmt.s14', ''],
'@MS9' : ['HpBiosMgmt.s09', 'HpBiosMgmt.s09', ''], '@MS9': ['HpBiosMgmt.s09', 'HpBiosMgmt.s09', ''],
'@NAL' : ['UCP_List.txt', 'AMI UCP Module Name List', ''], '@NAL': ['UCP_List.txt', 'AMI UCP Module Name List', ''],
'@OKM' : ['OK_Message.txt', 'OK Message', ''], '@OKM': ['OK_Message.txt', 'OK Message', ''],
'@PFC' : ['BGT_Command.txt', 'AMI BGT Command', 'Text'], '@PFC': ['BGT_Command.txt', 'AMI BGT Command', 'Text'],
'@R3I' : ['CryptRSA32.efi', 'CryptRSA32.efi', ''], '@R3I': ['CryptRSA32.efi', 'CryptRSA32.efi', ''],
'@RFI' : ['CryptRSA.efi', 'CryptRSA.efi', ''], '@RFI': ['CryptRSA.efi', 'CryptRSA.efi', ''],
'@UAF' : ['UCP_Main.bin', 'Utility Auxiliary File (AMI)', ''], '@UAF': ['UCP_Main.bin', 'Utility Auxiliary File (AMI)', ''],
'@UFI' : ['HpBiosUpdate.efi', 'HpBiosUpdate.efi', ''], '@UFI': ['HpBiosUpdate.efi', 'HpBiosUpdate.efi', ''],
'@UII' : ['UCP_Info.txt', 'Utility Identification Information', ''], '@UII': ['UCP_Info.txt', 'Utility Identification Information', ''],
'@US2' : ['HpBiosUpdate.s12', 'HpBiosUpdate.s12', ''], '@US2': ['HpBiosUpdate.s12', 'HpBiosUpdate.s12', ''],
'@US4' : ['HpBiosUpdate.s14', 'HpBiosUpdate.s14', ''], '@US4': ['HpBiosUpdate.s14', 'HpBiosUpdate.s14', ''],
'@US9' : ['HpBiosUpdate.s09', 'HpBiosUpdate.s09', ''], '@US9': ['HpBiosUpdate.s09', 'HpBiosUpdate.s09', ''],
'@USG' : ['HpBiosUpdate.sig', 'HpBiosUpdate.sig', ''], '@USG': ['HpBiosUpdate.sig', 'HpBiosUpdate.sig', ''],
'@VER' : ['OEM_Version.txt', 'OEM Version', 'Text'], '@VER': ['OEM_Version.txt', 'OEM Version', 'Text'],
'@VXD' : ['amifldrv.vxd', 'amifldrv.vxd', ''], '@VXD': ['amifldrv.vxd', 'amifldrv.vxd', ''],
'@W32' : ['amifldrv32.sys', 'amifldrv32.sys', ''], '@W32': ['amifldrv32.sys', 'amifldrv32.sys', ''],
'@W64' : ['amifldrv64.sys', 'amifldrv64.sys', ''], '@W64': ['amifldrv64.sys', 'amifldrv64.sys', ''],
'@D64': ['amifldrv64.sys', 'amifldrv64.sys', ''],
} }
if __name__ == '__main__': if __name__ == '__main__':
utility = BIOSUtility(TITLE, is_ami_ucp, ucp_extract) utility_args = [(['-c', '--checksum'], {'help': 'verify AMI UCP Checksums (slow)', 'action': 'store_true'})]
utility.parse_argument('-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() utility.run_utility()

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -1,217 +1,234 @@
#!/usr/bin/env python3 #!/usr/bin/env python3 -B
#coding=utf-8 # coding=utf-8
""" """
Insyde IFD Extract Insyde IFD Extract
Insyde iFlash/iFdPacker Extractor Insyde iFlash/iFdPacker Extractor
Copyright (C) 2022 Plato Mavropoulos Copyright (C) 2022-2024 Plato Mavropoulos
""" """
TITLE = 'Insyde iFlash/iFdPacker Extractor v2.0_a11'
import os
import sys
import ctypes import ctypes
import os
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.comp_szip import is_szip_supported, szip_decompress from common.comp_szip import is_szip_supported, szip_decompress
from common.path_ops import get_path_files, make_dirs, safe_name, get_extract_path from common.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.patterns import PAT_INSYDE_IFL, PAT_INSYDE_SFX
from common.struct_ops import char, get_struct, uint32_t from common.struct_ops import Char, get_struct, UInt32
from common.system import printer from common.system import printer
from common.templates import BIOSUtility from common.templates import BIOSUtility
from common.text_ops import file_to_bytes from common.text_ops import file_to_bytes
TITLE = 'Insyde iFlash/iFdPacker Extractor v3.0'
class IflashHeader(ctypes.LittleEndianStructure): class IflashHeader(ctypes.LittleEndianStructure):
""" Insyde iFlash Header """
_pack_ = 1 _pack_ = 1
# noinspection PyTypeChecker
_fields_ = [ _fields_ = [
('Signature', char*8), # 0x00 $_IFLASH ('Signature', Char * 8), # 0x00 $_IFLASH
('ImageTag', char*8), # 0x08 ('ImageTag', Char * 8), # 0x08
('TotalSize', uint32_t), # 0x10 from header end ('TotalSize', UInt32), # 0x10 from header end
('ImageSize', uint32_t), # 0x14 from header end ('ImageSize', UInt32), # 0x14 from header end
# 0x18 # 0x18
] ]
def _get_padd_len(self):
return self.TotalSize - self.ImageSize
def get_image_tag(self):
return self.ImageTag.decode('utf-8','ignore').strip('_')
def struct_print(self, p):
printer(['Signature :', self.Signature.decode('utf-8')], p, False)
printer(['Image Name:', self.get_image_tag()], p, False)
printer(['Image Size:', f'0x{self.ImageSize:X}'], p, False)
printer(['Total Size:', f'0x{self.TotalSize:X}'], p, False)
printer(['Padd Size :', f'0x{self._get_padd_len():X}'], p, False)
# Check if input is Insyde iFlash/iFdPacker Update image def _get_padd_len(self) -> int:
def is_insyde_ifd(input_file): return self.TotalSize - self.ImageSize
input_buffer = file_to_bytes(input_file)
def get_image_tag(self) -> str:
is_ifl = bool(insyde_iflash_detect(input_buffer)) """ Get Insyde iFlash image tag """
is_sfx = bool(PAT_INSYDE_SFX.search(input_buffer)) 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 return is_ifl or is_sfx
# Parse & Extract Insyde iFlash/iFdPacker Update images
def insyde_ifd_extract(input_file, extract_path, padding=0): def insyde_ifd_extract(input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
input_buffer = file_to_bytes(input_file) """ Parse & Extract Insyde iFlash/iFdPacker Update images """
iflash_code = insyde_iflash_extract(input_buffer, extract_path, padding) input_buffer: bytes = file_to_bytes(input_object)
ifdpack_path = os.path.join(extract_path, 'Insyde iFdPacker SFX') iflash_code: int = insyde_iflash_extract(input_buffer, extract_path, padding)
ifdpack_code = insyde_packer_extract(input_buffer, ifdpack_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 return iflash_code and ifdpack_code
# Detect Insyde iFlash Update image
def insyde_iflash_detect(input_buffer): def insyde_iflash_detect(input_buffer: bytes) -> list:
iflash_match_all = [] """ Detect Insyde iFlash Update image """
iflash_match_nan = [0x0,0xFFFFFFFF]
iflash_match_all: list = []
iflash_match_nan: list = [0x0, 0xFFFFFFFF]
for iflash_match in PAT_INSYDE_IFL.finditer(input_buffer): for iflash_match in PAT_INSYDE_IFL.finditer(input_buffer):
ifl_bgn = iflash_match.start() ifl_bgn: int = iflash_match.start()
if len(input_buffer[ifl_bgn:]) <= INS_IFL_LEN: if len(input_buffer[ifl_bgn:]) <= INS_IFL_LEN:
continue continue
ifl_hdr = get_struct(input_buffer, ifl_bgn, IflashHeader) ifl_hdr = get_struct(input_buffer, ifl_bgn, IflashHeader)
if ifl_hdr.TotalSize in iflash_match_nan \ if ifl_hdr.TotalSize in iflash_match_nan \
or ifl_hdr.ImageSize in iflash_match_nan \ or ifl_hdr.ImageSize in iflash_match_nan \
or ifl_hdr.TotalSize < ifl_hdr.ImageSize \ or ifl_hdr.TotalSize < ifl_hdr.ImageSize \
or ifl_bgn + INS_IFL_LEN + ifl_hdr.TotalSize > len(input_buffer): or ifl_bgn + INS_IFL_LEN + ifl_hdr.TotalSize > len(input_buffer):
continue continue
iflash_match_all.append([ifl_bgn, ifl_hdr]) iflash_match_all.append([ifl_bgn, ifl_hdr])
return iflash_match_all return iflash_match_all
# Extract Insyde iFlash Update image
def insyde_iflash_extract(input_buffer, extract_path, padding=0): def insyde_iflash_extract(input_buffer: bytes, extract_path: str, padding: int = 0) -> int:
insyde_iflash_all = insyde_iflash_detect(input_buffer) """ Extract Insyde iFlash Update image """
insyde_iflash_all: list = insyde_iflash_detect(input_buffer)
if not insyde_iflash_all: if not insyde_iflash_all:
return 127 return 127
printer('Detected Insyde iFlash Update image!', padding) printer('Detected Insyde iFlash Update image!', padding)
make_dirs(extract_path, delete=True) make_dirs(extract_path, delete=True)
exit_codes = [] exit_codes: list = []
for insyde_iflash in insyde_iflash_all: for insyde_iflash in insyde_iflash_all:
exit_code = 0 exit_code: int = 0
ifl_bgn,ifl_hdr = insyde_iflash ifl_bgn, ifl_hdr = insyde_iflash
img_bgn = ifl_bgn + INS_IFL_LEN img_bgn: int = ifl_bgn + INS_IFL_LEN
img_end = img_bgn + ifl_hdr.ImageSize img_end: int = img_bgn + ifl_hdr.ImageSize
img_bin = input_buffer[img_bgn:img_end] img_bin: bytes = input_buffer[img_bgn:img_end]
if len(img_bin) != ifl_hdr.ImageSize: if len(img_bin) != ifl_hdr.ImageSize:
exit_code = 1 exit_code = 1
img_val = [ifl_hdr.get_image_tag(), 'bin'] img_val: list = [ifl_hdr.get_image_tag(), 'bin']
img_tag,img_ext = INS_IFL_IMG.get(img_val[0], img_val) img_tag, img_ext = INS_IFL_IMG.get(img_val[0], img_val)
img_name = f'{img_tag} [0x{img_bgn:08X}-0x{img_end:08X}]' img_name: str = f'{img_tag} [0x{img_bgn:08X}-0x{img_end:08X}]'
printer(f'{img_name}\n', padding + 4) printer(f'{img_name}\n', padding + 4)
ifl_hdr.struct_print(padding + 8) ifl_hdr.struct_print(padding + 8)
if img_val == [img_tag,img_ext]: if img_val == [img_tag, img_ext]:
printer(f'Note: Detected new Insyde iFlash tag {img_tag}!', padding + 12, pause=True) printer(f'Note: Detected new Insyde iFlash tag {img_tag}!', padding + 12, pause=True)
out_name = f'{img_name}.{img_ext}' out_name: str = f'{img_name}.{img_ext}'
out_path = os.path.join(extract_path, safe_name(out_name)) out_path: str = os.path.join(extract_path, safe_name(out_name))
with open(out_path, 'wb') as out_image: with open(out_path, 'wb') as out_image:
out_image.write(img_bin) out_image.write(img_bin)
printer(f'Succesfull Insyde iFlash > {img_tag} extraction!', padding + 12) printer(f'Succesfull Insyde iFlash > {img_tag} extraction!', padding + 12)
exit_codes.append(exit_code) exit_codes.append(exit_code)
return sum(exit_codes) return sum(exit_codes)
# Extract Insyde iFdPacker 7-Zip SFX 7z Update image
def insyde_packer_extract(input_buffer, extract_path, padding=0): 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) match_sfx = PAT_INSYDE_SFX.search(input_buffer)
if not match_sfx: if not match_sfx:
return 127 return 127
printer('Detected Insyde iFdPacker Update image!', padding) printer('Detected Insyde iFdPacker Update image!', padding)
make_dirs(extract_path, delete=True) make_dirs(extract_path, delete=True)
sfx_buffer = bytearray(input_buffer[match_sfx.end() - 0x5:]) sfx_buffer: bytearray = bytearray(input_buffer[match_sfx.end() - 0x5:])
if sfx_buffer[:0x5] == b'\x6E\xF4\x79\x5F\x4E': if sfx_buffer[:0x5] == b'\x6E\xF4\x79\x5F\x4E':
printer('Detected Insyde iFdPacker > 7-Zip SFX > Obfuscation!', padding + 4) printer('Detected Insyde iFdPacker > 7-Zip SFX > Obfuscation!', padding + 4)
for index,byte in enumerate(sfx_buffer): for index, byte in enumerate(sfx_buffer):
sfx_buffer[index] = byte // 2 + (128 if byte % 2 else 0) sfx_buffer[index] = byte // 2 + (128 if byte % 2 else 0)
printer('Removed Insyde iFdPacker > 7-Zip SFX > Obfuscation!', padding + 8) printer('Removed Insyde iFdPacker > 7-Zip SFX > Obfuscation!', padding + 8)
printer('Extracting Insyde iFdPacker > 7-Zip SFX archive...', padding + 4) printer('Extracting Insyde iFdPacker > 7-Zip SFX archive...', padding + 4)
if bytes(INS_SFX_PWD, 'utf-16le') in input_buffer[:match_sfx.start()]: if bytes(INS_SFX_PWD, 'utf-16le') in input_buffer[:match_sfx.start()]:
printer('Detected Insyde iFdPacker > 7-Zip SFX > Password!', padding + 8) printer('Detected Insyde iFdPacker > 7-Zip SFX > Password!', padding + 8)
printer(INS_SFX_PWD, padding + 12) printer(INS_SFX_PWD, padding + 12)
sfx_path = os.path.join(extract_path, 'Insyde_iFdPacker_SFX.7z') sfx_path: str = os.path.join(extract_path, 'Insyde_iFdPacker_SFX.7z')
with open(sfx_path, 'wb') as sfx_file: with open(sfx_path, 'wb') as sfx_file:
sfx_file.write(sfx_buffer) sfx_file.write(sfx_buffer)
if is_szip_supported(sfx_path, padding + 8, args=[f'-p{INS_SFX_PWD}'], check=True): 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', if szip_decompress(sfx_path, extract_path, 'Insyde iFdPacker > 7-Zip SFX',
padding + 8, args=[f'-p{INS_SFX_PWD}'], check=True) == 0: padding + 8, args=[f'-p{INS_SFX_PWD}'], check=True) == 0:
os.remove(sfx_path) os.remove(sfx_path)
else: else:
return 125 return 125
else: else:
return 126 return 126
exit_codes = [] exit_codes = []
for sfx_file in get_path_files(extract_path): for sfx_file in get_path_files(extract_path):
if is_insyde_ifd(sfx_file): if is_insyde_ifd(sfx_file):
printer(f'{os.path.basename(sfx_file)}', padding + 12) printer(f'{os.path.basename(sfx_file)}', padding + 12)
ifd_code = insyde_ifd_extract(sfx_file, get_extract_path(sfx_file), padding + 16) ifd_code: int = insyde_ifd_extract(sfx_file, get_extract_path(sfx_file), padding + 16)
exit_codes.append(ifd_code) exit_codes.append(ifd_code)
return sum(exit_codes) return sum(exit_codes)
# Insyde iFdPacker known 7-Zip SFX Password # Insyde iFdPacker known 7-Zip SFX Password
INS_SFX_PWD = 'Y`t~i!L@i#t$U%h^s7A*l(f)E-d=y+S_n?i' 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 # Insyde iFlash known Image Names
INS_IFL_IMG = { INS_IFL_IMG: dict = {
'BIOSCER' : ['Certificate', 'bin'], 'BIOSCER': ['Certificate', 'bin'],
'BIOSCR2' : ['Certificate 2nd', 'bin'], 'BIOSCR2': ['Certificate 2nd', 'bin'],
'BIOSIMG' : ['BIOS-UEFI', 'bin'], 'BIOSIMG': ['BIOS-UEFI', 'bin'],
'DRV_IMG' : ['isflash', 'efi'], 'DRV_IMG': ['isflash', 'efi'],
'EC_IMG' : ['Embedded Controller', 'bin'], 'EC_IMG': ['Embedded Controller', 'bin'],
'INI_IMG' : ['platform', 'ini'], 'INI_IMG': ['platform', 'ini'],
'ME_IMG' : ['Management Engine', 'bin'], 'ME_IMG': ['Management Engine', 'bin'],
'OEM_ID' : ['OEM Identifier', 'bin'], 'OEM_ID': ['OEM Identifier', 'bin'],
} }
# Get common ctypes Structure Sizes # Get common ctypes Structure Sizes
INS_IFL_LEN = ctypes.sizeof(IflashHeader) INS_IFL_LEN: int = ctypes.sizeof(IflashHeader)
if __name__ == '__main__': if __name__ == '__main__':
BIOSUtility(TITLE, is_insyde_ifd, insyde_ifd_extract).run_utility() BIOSUtility(title=TITLE, check=is_insyde_ifd, main=insyde_ifd_extract).run_utility()

View file

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

View file

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

View file

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

View file

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

View file

@ -42,7 +42,7 @@ You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s)
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -75,7 +75,7 @@ You can either Drag & Drop or manually enter AMI UCP Update executable file(s).
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -112,7 +112,7 @@ You can either Drag & Drop or manually enter Apple EFI IM4P file(s). Optional ar
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -143,7 +143,7 @@ You can either Drag & Drop or manually enter Apple EFI image file(s). Optional a
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -176,7 +176,7 @@ You can either Drag & Drop or manually enter Apple EFI PKG package file(s). Opti
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -208,7 +208,7 @@ You can either Drag & Drop or manually enter Apple EFI PBZX image file(s). Optio
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -240,7 +240,7 @@ You can either Drag & Drop or manually enter Award BIOS image file(s). Optional
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -274,7 +274,7 @@ You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -306,7 +306,7 @@ You can either Drag & Drop or manually enter Fujitsu SFX BIOS image file(s). Opt
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -338,7 +338,7 @@ You can either Drag & Drop or manually enter Fujitsu UPC BIOS image file(s). Opt
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -370,7 +370,7 @@ You can either Drag & Drop or manually enter Insyde iFlash/iFdPacker Update imag
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -400,14 +400,14 @@ You can either Drag & Drop or manually enter Panasonic BIOS Package executable f
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
To run the utility, you must have the following 3rd party Python modules installed: To run the utility, you must have the following 3rd party Python modules installed:
* [pefile](https://pypi.org/project/pefile/) * [pefile](https://pypi.org/project/pefile/)
* [lznt1](https://pypi.org/project/lznt1/) * [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 the "external" project directory:
@ -437,7 +437,7 @@ You can either Drag & Drop or manually enter Phoenix Tools Development Kit (TDK)
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -469,7 +469,7 @@ You can either Drag & Drop or manually enter Portwell UEFI Unpacker EFI executab
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -507,7 +507,7 @@ You can either Drag & Drop or manually enter Toshiba BIOS COM image file(s). Opt
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**
@ -539,7 +539,7 @@ You can either Drag & Drop or manually enter VAIO Packaging Manager executable f
#### **Compatibility** #### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
#### **Prerequisites** #### **Prerequisites**

View file

@ -1,20 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3 -B
#coding=utf-8 # coding=utf-8
""" """
Toshiba COM Extract Toshiba COM Extract
Toshiba BIOS COM Extractor Toshiba BIOS COM Extractor
Copyright (C) 2018-2022 Plato Mavropoulos Copyright (C) 2018-2024 Plato Mavropoulos
""" """
TITLE = 'Toshiba BIOS COM Extractor v2.0_a4'
import os import os
import sys
import subprocess import subprocess
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.externals import get_comextract_path from common.externals import get_comextract_path
from common.path_ops import make_dirs, path_stem, path_suffixes from common.path_ops import make_dirs, path_stem, path_suffixes
@ -23,41 +17,49 @@ from common.system import printer
from common.templates import BIOSUtility from common.templates import BIOSUtility
from common.text_ops import file_to_bytes from common.text_ops import file_to_bytes
# Check if input is Toshiba BIOS COM image TITLE = 'Toshiba BIOS COM Extractor v3.0'
def is_toshiba_com(in_file): def is_toshiba_com(in_file):
""" Check if input is Toshiba BIOS COM image """
buffer = file_to_bytes(in_file) buffer = file_to_bytes(in_file)
is_ext = path_suffixes(in_file)[-1].upper() == '.COM' if os.path.isfile(in_file) else True is_ext = path_suffixes(in_file)[-1].upper() == '.COM' if os.path.isfile(in_file) else True
is_com = PAT_TOSHIBA_COM.search(buffer) is_com = PAT_TOSHIBA_COM.search(buffer)
return is_ext and is_com return is_ext and is_com
# Parse & Extract Toshiba BIOS COM image
def toshiba_com_extract(input_file, extract_path, padding=0): def toshiba_com_extract(input_file, extract_path, padding=0):
""" Parse & Extract Toshiba BIOS COM image """
if not os.path.isfile(input_file): if not os.path.isfile(input_file):
printer('Error: Could not find input file path!', padding) printer('Error: Could not find input file path!', padding)
return 1 return 1
make_dirs(extract_path, delete=True) make_dirs(extract_path, delete=True)
output_name = path_stem(input_file) output_name = path_stem(input_file)
output_file = os.path.join(extract_path, f'{output_name}.bin') output_file = os.path.join(extract_path, f'{output_name}.bin')
try: try:
subprocess.run([get_comextract_path(), input_file, output_file], check=True, stdout=subprocess.DEVNULL) subprocess.run([get_comextract_path(), input_file, output_file], check=True, stdout=subprocess.DEVNULL)
if not os.path.isfile(output_file): if not os.path.isfile(output_file):
raise Exception('EXTRACT_FILE_MISSING') raise ValueError('EXTRACT_FILE_MISSING')
except Exception: except Exception as error: # pylint: disable=broad-except
printer(f'Error: ToshibaComExtractor could not extract file {input_file}!', padding) printer(f'Error: ToshibaComExtractor could not extract file {input_file}: {error}!', padding)
return 2 return 2
printer(f'Succesfull {output_name} extraction via ToshibaComExtractor!', padding) printer(f'Succesfull {output_name} extraction via ToshibaComExtractor!', padding)
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':
BIOSUtility(TITLE, is_toshiba_com, toshiba_com_extract).run_utility() BIOSUtility(title=TITLE, check=is_toshiba_com, main=toshiba_com_extract).run_utility()

View file

@ -1,19 +1,13 @@
#!/usr/bin/env python3 #!/usr/bin/env python3 -B
#coding=utf-8 # coding=utf-8
""" """
VAIO Package Extractor VAIO Package Extractor
VAIO Packaging Manager Extractor VAIO Packaging Manager Extractor
Copyright (C) 2019-2022 Plato Mavropoulos Copyright (C) 2019-2024 Plato Mavropoulos
""" """
TITLE = 'VAIO Packaging Manager Extractor v3.0_a8'
import os import os
import sys
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.comp_szip import is_szip_supported, szip_decompress from common.comp_szip import is_szip_supported, szip_decompress
from common.path_ops import make_dirs from common.path_ops import make_dirs
@ -22,126 +16,149 @@ from common.system import printer
from common.templates import BIOSUtility from common.templates import BIOSUtility
from common.text_ops import file_to_bytes from common.text_ops import file_to_bytes
# Check if input is VAIO Packaging Manager TITLE = 'VAIO Packaging Manager Extractor v4.0'
def is_vaio_pkg(in_file): def is_vaio_pkg(in_file):
""" Check if input is VAIO Packaging Manager """
buffer = file_to_bytes(in_file) buffer = file_to_bytes(in_file)
return bool(PAT_VAIO_CFG.search(buffer)) return bool(PAT_VAIO_CFG.search(buffer))
# Extract VAIO Packaging Manager executable
def vaio_cabinet(name, buffer, extract_path, padding=0): def vaio_cabinet(name, buffer, extract_path, padding=0):
match_cab = PAT_VAIO_CAB.search(buffer) # Microsoft CAB Header XOR 0xFF """ Extract VAIO Packaging Manager executable """
match_cab = PAT_VAIO_CAB.search(buffer) # Microsoft CAB Header XOR 0xFF
if not match_cab: if not match_cab:
return 1 return 1
printer('Detected obfuscated CAB archive!', padding) printer('Detected obfuscated CAB archive!', padding)
# Determine the Microsoft CAB image size # Determine the Microsoft CAB image size
cab_size = int.from_bytes(buffer[match_cab.start() + 0x8:match_cab.start() + 0xC], 'little') # Get LE XOR-ed CAB size 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 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) printer('Removing obfuscation...', padding + 4)
# Determine the Microsoft CAB image Data # Determine the Microsoft CAB image Data
cab_data = int.from_bytes(buffer[match_cab.start():match_cab.start() + cab_size], 'big') # Get BE XOR-ed CAB data 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 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) printer('Extracting archive...', padding + 4)
cab_path = os.path.join(extract_path, f'{name}_Temporary.cab') cab_path = os.path.join(extract_path, f'{name}_Temporary.cab')
with open(cab_path, 'wb') as cab_file: with open(cab_path, 'wb') as cab_file:
cab_file.write(cab_data) # Create temporary CAB archive cab_file.write(cab_data) # Create temporary CAB archive
if is_szip_supported(cab_path, padding + 8, check=True): if is_szip_supported(cab_path, padding + 8, check=True):
if szip_decompress(cab_path, extract_path, 'CAB', padding + 8, check=True) == 0: if szip_decompress(cab_path, extract_path, 'VAIO CAB', padding + 8, check=True) == 0:
os.remove(cab_path) # Successful extraction, delete temporary CAB archive os.remove(cab_path) # Successful extraction, delete temporary CAB archive
else: else:
return 3 return 3
else: else:
return 2 return 2
return 0 return 0
# Unlock VAIO Packaging Manager executable
def vaio_unlock(name, buffer, extract_path, padding=0): def vaio_unlock(name, buffer, extract_path, padding=0):
""" Unlock VAIO Packaging Manager executable """
match_cfg = PAT_VAIO_CFG.search(buffer) match_cfg = PAT_VAIO_CFG.search(buffer)
if not match_cfg: if not match_cfg:
return 1 return 1
printer('Attempting to Unlock executable!', padding) printer('Attempting to Unlock executable!', padding)
# Initialize VAIO Package Configuration file variables (assume overkill size of 0x500) # 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''] 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) # 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') 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) printer('Retrieving True/False values...', padding + 4)
# Determine VAIO Package Configuration file True & False values # Determine VAIO Package Configuration file True & False values
for info in cfg_info: for info in cfg_info:
if info.startswith(b'ExtractPathByUser='): 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 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='): 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 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 # Check if valid True/False values have been retrieved
if cfg_false == cfg_true or not cfg_false or not cfg_true: if cfg_false == cfg_true or not cfg_false or not cfg_true:
printer('Error: Could not retrieve True/False values!', padding + 8) printer('Error: Could not retrieve True/False values!', padding + 8)
return 2 return 2
printer('Adjusting UseVAIOCheck entry...', padding + 4) printer('Adjusting UseVAIOCheck entry...', padding + 4)
# Find and replace UseVAIOCheck entry from 1/Yes/True to 0/No/False # Find and replace UseVAIOCheck entry from 1/Yes/True to 0/No/False
vaio_check = PAT_VAIO_CHK.search(buffer[cfg_bgn:]) vaio_check = PAT_VAIO_CHK.search(buffer[cfg_bgn:])
if vaio_check: if vaio_check:
buffer[cfg_bgn + vaio_check.end():cfg_bgn + vaio_check.end() + len(cfg_true)] = cfg_false buffer[cfg_bgn + vaio_check.end():cfg_bgn + vaio_check.end() + len(cfg_true)] = cfg_false
else: else:
printer('Error: Could not find entry UseVAIOCheck!', padding + 8) printer('Error: Could not find entry UseVAIOCheck!', padding + 8)
return 3 return 3
printer('Adjusting ExtractPathByUser entry...', padding + 4) printer('Adjusting ExtractPathByUser entry...', padding + 4)
# Find and replace ExtractPathByUser entry from 0/No/False to 1/Yes/True # Find and replace ExtractPathByUser entry from 0/No/False to 1/Yes/True
user_path = PAT_VAIO_EXT.search(buffer[cfg_bgn:]) user_path = PAT_VAIO_EXT.search(buffer[cfg_bgn:])
if user_path: if user_path:
buffer[cfg_bgn + user_path.end():cfg_bgn + user_path.end() + len(cfg_false)] = cfg_true buffer[cfg_bgn + user_path.end():cfg_bgn + user_path.end() + len(cfg_false)] = cfg_true
else: else:
printer('Error: Could not find entry ExtractPathByUser!', padding + 8) printer('Error: Could not find entry ExtractPathByUser!', padding + 8)
return 4 return 4
printer('Storing unlocked executable...', padding + 4) printer('Storing unlocked executable...', padding + 4)
# Store Unlocked VAIO Packaging Manager executable # Store Unlocked VAIO Packaging Manager executable
if vaio_check and user_path: if vaio_check and user_path:
unlock_path = os.path.join(extract_path, f'{name}_Unlocked.exe') unlock_path = os.path.join(extract_path, f'{name}_Unlocked.exe')
with open(unlock_path, 'wb') as unl_file: with open(unlock_path, 'wb') as unl_file:
unl_file.write(buffer) unl_file.write(buffer)
return 0 return 0
# Parse & Extract or Unlock VAIO Packaging Manager
def vaio_pkg_extract(input_file, extract_path, padding=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_buffer = file_to_bytes(input_file)
input_name = os.path.basename(input_file) input_name = os.path.basename(input_file)
make_dirs(extract_path, delete=True) make_dirs(extract_path, delete=True)
if vaio_cabinet(input_name, input_buffer, extract_path, padding) == 0: if vaio_cabinet(input_name, input_buffer, extract_path, padding) == 0:
printer('Successfully Extracted!', padding) printer('Successfully Extracted!', padding)
elif vaio_unlock(input_name, bytearray(input_buffer), extract_path, padding) == 0: elif vaio_unlock(input_name, bytearray(input_buffer), extract_path, padding) == 0:
printer('Successfully Unlocked!', padding) printer('Successfully Unlocked!', padding)
else: else:
printer('Error: Failed to Extract or Unlock executable!', padding) printer('Error: Failed to Extract or Unlock executable!', padding)
return 1 return 1
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':
BIOSUtility(TITLE, is_vaio_pkg, vaio_pkg_extract).run_utility() BIOSUtility(title=TITLE, check=is_vaio_pkg, main=vaio_pkg_extract).run_utility()

6
__init__.py Normal file
View file

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

6
common/__init__.py Normal file
View file

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

View file

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

View file

@ -1,57 +1,60 @@
#!/usr/bin/env python3 #!/usr/bin/env python3 -B
#coding=utf-8 # coding=utf-8
""" """
Copyright (C) 2022 Plato Mavropoulos Copyright (C) 2022-2024 Plato Mavropoulos
""" """
import os import os
import subprocess import subprocess
from common.path_ops import project_root, safe_path from common.externals import get_tiano_path
from common.system import get_os_ver, printer from common.system import printer
def get_compress_sizes(data):
""" Get EFI compression sizes """
def get_compress_sizes(data):
size_compress = int.from_bytes(data[0x0:0x4], 'little') size_compress = int.from_bytes(data[0x0:0x4], 'little')
size_original = int.from_bytes(data[0x4:0x8], 'little') size_original = int.from_bytes(data[0x4:0x8], 'little')
return size_compress, size_original return size_compress, size_original
def is_efi_compressed(data, strict=True): def is_efi_compressed(data, strict=True):
size_comp,size_orig = get_compress_sizes(data) """ Check if data is EFI compressed, controlling EOF padding """
size_comp, size_orig = get_compress_sizes(data)
check_diff = size_comp < size_orig check_diff = size_comp < size_orig
if strict: if strict:
check_size = size_comp + 0x8 == len(data) check_size = size_comp + 0x8 == len(data)
else: else:
check_size = size_comp + 0x8 <= len(data) check_size = size_comp + 0x8 <= len(data)
return check_diff and check_size return check_diff and check_size
# Get TianoCompress path
def get_tiano_path():
exec_name = f'TianoCompress{".exe" if get_os_ver()[1] else ""}'
return safe_path(project_root(), ['external',exec_name])
# EFI/Tiano Decompression via TianoCompress
def efi_decompress(in_path, out_path, padding=0, silent=False, comp_type='--uefi'): def efi_decompress(in_path, out_path, padding=0, silent=False, comp_type='--uefi'):
""" EFI/Tiano Decompression via TianoCompress """
try: try:
subprocess.run([get_tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type], check=True, stdout=subprocess.DEVNULL) 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: with open(in_path, 'rb') as file:
_,size_orig = get_compress_sizes(file.read()) _, size_orig = get_compress_sizes(file.read())
if os.path.getsize(out_path) != size_orig: if os.path.getsize(out_path) != size_orig:
raise Exception('EFI_DECOMPRESS_ERROR') raise OSError('EFI decompressed file & header size mismatch!')
except Exception: except Exception as error: # pylint: disable=broad-except
if not silent: if not silent:
printer(f'Error: TianoCompress could not extract file {in_path}!', padding) printer(f'Error: TianoCompress could not extract file {in_path}: {error}!', padding)
return 1 return 1
if not silent: if not silent:
printer('Succesfull EFI decompression via TianoCompress!', padding) printer('Succesfull EFI decompression via TianoCompress!', padding)
return 0 return 0

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3 -B
#coding=utf-8 # coding=utf-8
""" """
Copyright (C) 2022 Plato Mavropoulos Copyright (C) 2022-2024 Plato Mavropoulos
""" """
import pefile import pefile
@ -10,40 +10,57 @@ import pefile
from common.system import printer from common.system import printer
from common.text_ops import file_to_bytes from common.text_ops import file_to_bytes
# Check if input is a PE file
def is_pe_file(in_file):
return bool(get_pe_file(in_file))
# Get pefile object from PE file def is_pe_file(in_file: str | bytes) -> bool:
def get_pe_file(in_file, fast=True): """ Check if input is a PE file """
return bool(get_pe_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:
""" Get pefile object from PE file """
in_buffer = file_to_bytes(in_file) in_buffer = file_to_bytes(in_file)
pe_file = None
try: try:
# Analyze detected MZ > PE image buffer # Analyze detected MZ > PE image buffer
pe_file = pefile.PE(data=in_buffer, fast_load=fast) pe_file = pefile.PE(data=in_buffer, fast_load=fast)
except Exception: except Exception as error: # pylint: disable=broad-except
pe_file = None if not silent:
_filename = in_file if type(in_file).__name__ == 'string' else 'buffer'
printer(f'Error: Could not get pefile object from {_filename}: {error}!', padding)
return pe_file return pe_file
# Get PE info from pefile object
def get_pe_info(pe_file): def get_pe_info(pe_file: pefile.PE, padding: int = 0, silent: bool = False) -> dict:
""" Get PE info from pefile object """
pe_info = {}
try: try:
# When fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed prior to FileInfo > StringTable # When fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed prior to FileInfo > StringTable
pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']]) pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']])
# Retrieve MZ > PE > FileInfo > StringTable information # Retrieve MZ > PE > FileInfo > StringTable information
pe_info = pe_file.FileInfo[0][0].StringTable[0].entries pe_info = pe_file.FileInfo[0][0].StringTable[0].entries
except Exception: except Exception as error: # pylint: disable=broad-except
pe_info = {} if not silent:
printer(f'Error: Could not get PE info from pefile object: {error}!', padding)
return pe_info return pe_info
# Print PE info from pefile StringTable
def show_pe_info(pe_info, padding=0): def show_pe_info(pe_info: dict, padding: int = 0) -> None:
if type(pe_info).__name__ == 'dict': """ Print PE info from pefile StringTable """
for title,value in pe_info.items():
info_title = title.decode('utf-8','ignore').strip() if isinstance(pe_info, dict):
info_value = value.decode('utf-8','ignore').strip() for title, value in pe_info.items():
info_title = title.decode('utf-8', 'ignore').strip()
info_value = value.decode('utf-8', 'ignore').strip()
if info_title and info_value: if info_title and info_value:
printer(f'{info_title}: {info_value}', padding, new_line=False) printer(f'{info_title}: {info_value}', padding, new_line=False)

View file

@ -1,26 +1,32 @@
#!/usr/bin/env python3 #!/usr/bin/env python3 -B
#coding=utf-8 # coding=utf-8
""" """
Copyright (C) 2022 Plato Mavropoulos Copyright (C) 2022-2024 Plato Mavropoulos
""" """
import ctypes import ctypes
char = ctypes.c_char Char: type[ctypes.c_char] | int = ctypes.c_char
uint8_t = ctypes.c_ubyte UInt8: type[ctypes.c_ubyte] | int = ctypes.c_ubyte
uint16_t = ctypes.c_ushort UInt16: type[ctypes.c_ushort] | int = ctypes.c_ushort
uint32_t = ctypes.c_uint UInt32: type[ctypes.c_uint] | int = ctypes.c_uint
uint64_t = ctypes.c_uint64 UInt64: type[ctypes.c_uint64] | int = ctypes.c_uint64
# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky
def get_struct(buffer, start_offset, class_name, param_list=None): def get_struct(buffer, start_offset, class_name, param_list=None):
if param_list is None: """
param_list = [] 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
structure = class_name(*param_list) # Unpack parameter list
struct_len = ctypes.sizeof(structure) struct_len = ctypes.sizeof(structure)
struct_data = buffer[start_offset:start_offset + struct_len] struct_data = buffer[start_offset:start_offset + struct_len]
fit_len = min(len(struct_data), struct_len) fit_len = min(len(struct_data), struct_len)
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len) ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)

View file

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

View file

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

View file

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

View file

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

2
requirements.txt Normal file
View file

@ -0,0 +1,2 @@
dissect.util == 3.15
pefile == 2023.2.7