mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-09 13:52:00 -04:00
Merge pull request #38 from platomav/improvements_2022.4-2024.1
Additions, Improvements and Fixes from Q4 2022 to Q1 2024
This commit is contained in:
commit
6ae1587ec9
37 changed files with 2897 additions and 2174 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
|||
# Skip all external files
|
||||
external/*
|
||||
|
||||
# Keep external > requirements file
|
||||
!external/requirements.txt
|
||||
/.idea/
|
||||
/.mypy_cache/
|
||||
/external/
|
||||
/venv/
|
||||
|
|
4
.mypy.ini
Normal file
4
.mypy.ini
Normal file
|
@ -0,0 +1,4 @@
|
|||
[mypy]
|
||||
|
||||
explicit_package_bases = True
|
||||
mypy_path = $MYPY_CONFIG_FILE_DIR/
|
19
.pylintrc
Normal file
19
.pylintrc
Normal 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
|
|
@ -1,319 +1,434 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
AMI PFAT Extract
|
||||
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 re
|
||||
import sys
|
||||
import ctypes
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.externals import get_bgs_tool
|
||||
from common.num_ops import get_ordinal
|
||||
from common.path_ops import make_dirs, safe_name, get_extract_path, extract_suffix
|
||||
from common.path_ops import extract_suffix, get_extract_path, make_dirs, path_name, safe_name
|
||||
from common.patterns import PAT_AMI_PFAT
|
||||
from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t
|
||||
from common.struct_ops import Char, get_struct, UInt8, UInt16, UInt32
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
from common.text_ops import bytes_to_hex, file_to_bytes
|
||||
|
||||
TITLE = 'AMI BIOS Guard Extractor v5.0'
|
||||
|
||||
|
||||
class AmiBiosGuardHeader(ctypes.LittleEndianStructure):
|
||||
""" AMI BIOS Guard Header """
|
||||
|
||||
_pack_ = 1
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
_fields_ = [
|
||||
('Size', uint32_t), # 0x00 Header + Entries
|
||||
('Checksum', uint32_t), # 0x04 ?
|
||||
('Tag', char*8), # 0x04 _AMIPFAT
|
||||
('Flags', uint8_t), # 0x10 ?
|
||||
('Size', UInt32), # 0x00 Header + Entries
|
||||
('Checksum', UInt32), # 0x04 ?
|
||||
('Tag', Char * 8), # 0x04 _AMIPFAT
|
||||
('Flags', UInt8), # 0x10 ?
|
||||
# 0x11
|
||||
]
|
||||
|
||||
def struct_print(self, p):
|
||||
printer(['Size :', f'0x{self.Size:X}'], p, False)
|
||||
printer(['Checksum:', f'0x{self.Checksum:04X}'], p, False)
|
||||
printer(['Tag :', self.Tag.decode('utf-8')], p, False)
|
||||
printer(['Flags :', f'0x{self.Flags:02X}'], p, False)
|
||||
|
||||
def struct_print(self, padd: int) -> None:
|
||||
""" Display structure information """
|
||||
|
||||
printer(['Size :', f'0x{self.Size:X}'], padd, False)
|
||||
printer(['Checksum:', f'0x{self.Checksum:04X}'], padd, False)
|
||||
printer(['Tag :', self.Tag.decode('utf-8')], padd, False)
|
||||
printer(['Flags :', f'0x{self.Flags:02X}'], padd, False)
|
||||
|
||||
|
||||
class IntelBiosGuardHeader(ctypes.LittleEndianStructure):
|
||||
""" Intel BIOS Guard Header """
|
||||
|
||||
_pack_ = 1
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
_fields_ = [
|
||||
('BGVerMajor', uint16_t), # 0x00
|
||||
('BGVerMinor', uint16_t), # 0x02
|
||||
('PlatformID', uint8_t*16), # 0x04
|
||||
('Attributes', uint32_t), # 0x14
|
||||
('ScriptVerMajor', uint16_t), # 0x16
|
||||
('ScriptVerMinor', uint16_t), # 0x18
|
||||
('ScriptSize', uint32_t), # 0x1C
|
||||
('DataSize', uint32_t), # 0x20
|
||||
('BIOSSVN', uint32_t), # 0x24
|
||||
('ECSVN', uint32_t), # 0x28
|
||||
('VendorInfo', uint32_t), # 0x2C
|
||||
('BGVerMajor', UInt16), # 0x00
|
||||
('BGVerMinor', UInt16), # 0x02
|
||||
('PlatformID', UInt8 * 16), # 0x04
|
||||
('Attributes', UInt32), # 0x14
|
||||
('ScriptVerMajor', UInt16), # 0x16
|
||||
('ScriptVerMinor', UInt16), # 0x18
|
||||
('ScriptSize', UInt32), # 0x1C
|
||||
('DataSize', UInt32), # 0x20
|
||||
('BIOSSVN', UInt32), # 0x24
|
||||
('ECSVN', UInt32), # 0x28
|
||||
('VendorInfo', UInt32), # 0x2C
|
||||
# 0x30
|
||||
]
|
||||
|
||||
def get_platform_id(self):
|
||||
id_byte = bytes(self.PlatformID)
|
||||
|
||||
id_text = re.sub(r'[\n\t\r\x00 ]', '', id_byte.decode('utf-8','ignore'))
|
||||
|
||||
id_hexs = f'{int.from_bytes(id_byte, "big"):0{0x10 * 2}X}'
|
||||
id_guid = f'{{{id_hexs[:8]}-{id_hexs[8:12]}-{id_hexs[12:16]}-{id_hexs[16:20]}-{id_hexs[20:]}}}'
|
||||
|
||||
|
||||
def get_platform_id(self) -> str:
|
||||
""" Get Intel BIOS Guard Platform ID """
|
||||
|
||||
id_byte: bytes = bytes(self.PlatformID)
|
||||
|
||||
id_text: str = re.sub(r'[\n\t\r\x00 ]', '', id_byte.decode('utf-8', 'ignore'))
|
||||
|
||||
id_hexs: str = f'{int.from_bytes(id_byte, "big"):0{0x10 * 2}X}'
|
||||
id_guid: str = f'{{{id_hexs[:8]}-{id_hexs[8:12]}-{id_hexs[12:16]}-{id_hexs[16:20]}-{id_hexs[20:]}}}'
|
||||
|
||||
return f'{id_text} {id_guid}'
|
||||
|
||||
def get_flags(self):
|
||||
|
||||
def get_flags(self) -> tuple:
|
||||
""" Get Intel BIOS Guard Header Attributes """
|
||||
|
||||
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
|
||||
|
||||
def struct_print(self, p):
|
||||
no_yes = ['No','Yes']
|
||||
f1,f2,f3,f4,f5 = self.get_flags()
|
||||
|
||||
printer(['BIOS Guard Version :', f'{self.BGVerMajor}.{self.BGVerMinor}'], p, False)
|
||||
printer(['Platform Identity :', self.get_platform_id()], p, False)
|
||||
printer(['Signed Flash Address Map :', no_yes[f1]], p, False)
|
||||
printer(['Protected EC OpCodes :', no_yes[f2]], p, False)
|
||||
printer(['Graphics Security Disable :', no_yes[f3]], p, False)
|
||||
printer(['Fault Tolerant Update :', no_yes[f4]], p, False)
|
||||
printer(['Attributes Reserved :', f'0x{f5:X}'], p, False)
|
||||
printer(['Script Version :', f'{self.ScriptVerMajor}.{self.ScriptVerMinor}'], p, False)
|
||||
printer(['Script Size :', f'0x{self.ScriptSize:X}'], p, False)
|
||||
printer(['Data Size :', f'0x{self.DataSize:X}'], p, False)
|
||||
printer(['BIOS Security Version Number:', f'0x{self.BIOSSVN:X}'], p, False)
|
||||
printer(['EC Security Version Number :', f'0x{self.ECSVN:X}'], p, False)
|
||||
printer(['Vendor Information :', f'0x{self.VendorInfo:X}'], p, False)
|
||||
|
||||
|
||||
def struct_print(self, padd: int) -> None:
|
||||
""" Display structure information """
|
||||
|
||||
no_yes: dict[int, str] = {0: 'No', 1: 'Yes'}
|
||||
|
||||
sfam, ec_opc, gfx_dis, ft_upd, attr_res = self.get_flags()
|
||||
|
||||
printer(['BIOS Guard Version :', f'{self.BGVerMajor}.{self.BGVerMinor}'], padd, False)
|
||||
printer(['Platform Identity :', self.get_platform_id()], padd, False)
|
||||
printer(['Signed Flash Address Map :', no_yes[sfam]], padd, False)
|
||||
printer(['Protected EC OpCodes :', no_yes[ec_opc]], padd, False)
|
||||
printer(['Graphics Security Disable :', no_yes[gfx_dis]], padd, False)
|
||||
printer(['Fault Tolerant Update :', no_yes[ft_upd]], padd, False)
|
||||
printer(['Attributes Reserved :', f'0x{attr_res:X}'], padd, False)
|
||||
printer(['Script Version :', f'{self.ScriptVerMajor}.{self.ScriptVerMinor}'], padd, False)
|
||||
printer(['Script Size :', f'0x{self.ScriptSize:X}'], padd, False)
|
||||
printer(['Data Size :', f'0x{self.DataSize:X}'], padd, False)
|
||||
printer(['BIOS Security Version Number:', f'0x{self.BIOSSVN:X}'], padd, False)
|
||||
printer(['EC Security Version Number :', f'0x{self.ECSVN:X}'], padd, False)
|
||||
printer(['Vendor Information :', f'0x{self.VendorInfo:X}'], padd, False)
|
||||
|
||||
|
||||
class IntelBiosGuardHeaderAttributes(ctypes.LittleEndianStructure):
|
||||
""" Intel BIOS Guard Header Attributes """
|
||||
|
||||
_pack_ = 1
|
||||
|
||||
_fields_ = [
|
||||
('SFAM', uint32_t, 1), # Signed Flash Address Map
|
||||
('ProtectEC', uint32_t, 1), # Protected EC OpCodes
|
||||
('GFXMitDis', uint32_t, 1), # GFX Security Disable
|
||||
('FTU', uint32_t, 1), # Fault Tolerant Update
|
||||
('Reserved', uint32_t, 28) # Reserved/Unknown
|
||||
('SFAM', UInt32, 1), # Signed Flash Address Map
|
||||
('ProtectEC', UInt32, 1), # Protected EC OpCodes
|
||||
('GFXMitDis', UInt32, 1), # GFX Security Disable
|
||||
('FTU', UInt32, 1), # Fault Tolerant Update
|
||||
('Reserved', UInt32, 28) # Reserved/Unknown
|
||||
]
|
||||
|
||||
|
||||
class IntelBiosGuardHeaderGetAttributes(ctypes.Union):
|
||||
""" Intel BIOS Guard Header Attributes Getter """
|
||||
|
||||
_pack_ = 1
|
||||
|
||||
_fields_ = [
|
||||
('b', IntelBiosGuardHeaderAttributes),
|
||||
('asbytes', uint32_t)
|
||||
('asbytes', UInt32)
|
||||
]
|
||||
|
||||
class IntelBiosGuardSignature2k(ctypes.LittleEndianStructure):
|
||||
|
||||
class IntelBiosGuardSignatureHeader(ctypes.LittleEndianStructure):
|
||||
""" Intel BIOS Guard Signature Header """
|
||||
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Unknown0', uint32_t), # 0x000
|
||||
('Unknown1', uint32_t), # 0x004
|
||||
('Modulus', uint32_t*64), # 0x008
|
||||
('Exponent', uint32_t), # 0x108
|
||||
('Signature', uint32_t*64), # 0x10C
|
||||
# 0x20C
|
||||
]
|
||||
|
||||
def struct_print(self, p):
|
||||
Modulus = f'{int.from_bytes(self.Modulus, "little"):0{0x100 * 2}X}'
|
||||
Signature = f'{int.from_bytes(self.Signature, "little"):0{0x100 * 2}X}'
|
||||
|
||||
printer(['Unknown 0:', f'0x{self.Unknown0:X}'], p, False)
|
||||
printer(['Unknown 1:', f'0x{self.Unknown1:X}'], p, False)
|
||||
printer(['Modulus :', f'{Modulus[:32]} [...]'], p, False)
|
||||
printer(['Exponent :', f'0x{self.Exponent:X}'], p, False)
|
||||
printer(['Signature:', f'{Signature[:32]} [...]'], p, False)
|
||||
|
||||
def is_ami_pfat(input_file):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
_fields_ = [
|
||||
('Unknown0', UInt32), # 0x000
|
||||
('Unknown1', UInt32), # 0x004
|
||||
# 0x8
|
||||
]
|
||||
|
||||
def struct_print(self, padd: int) -> None:
|
||||
""" Display structure information """
|
||||
|
||||
printer(['Unknown 0:', f'0x{self.Unknown0:X}'], padd, False)
|
||||
printer(['Unknown 1:', f'0x{self.Unknown1:X}'], padd, False)
|
||||
|
||||
|
||||
class IntelBiosGuardSignatureRsa2k(ctypes.LittleEndianStructure):
|
||||
""" Intel BIOS Guard Signature Block 2048-bit """
|
||||
|
||||
_pack_ = 1
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
_fields_ = [
|
||||
('Modulus', UInt8 * 256), # 0x000
|
||||
('Exponent', UInt32), # 0x100
|
||||
('Signature', UInt8 * 256), # 0x104
|
||||
# 0x204
|
||||
]
|
||||
|
||||
def struct_print(self, padd: int) -> None:
|
||||
""" Display structure information """
|
||||
|
||||
printer(['Modulus :', f'{bytes_to_hex(self.Modulus, "little", 0x100, 32)} [...]'], padd, False)
|
||||
printer(['Exponent :', f'0x{self.Exponent:X}'], padd, False)
|
||||
printer(['Signature:', f'{bytes_to_hex(self.Signature, "little", 0x100, 32)} [...]'], padd, False)
|
||||
|
||||
|
||||
class IntelBiosGuardSignatureRsa3k(ctypes.LittleEndianStructure):
|
||||
""" Intel BIOS Guard Signature Block 3072-bit """
|
||||
|
||||
_pack_ = 1
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
_fields_ = [
|
||||
('Modulus', UInt8 * 384), # 0x000
|
||||
('Exponent', UInt32), # 0x180
|
||||
('Signature', UInt8 * 384), # 0x184
|
||||
# 0x304
|
||||
]
|
||||
|
||||
def struct_print(self, padd: int) -> None:
|
||||
""" Display structure information """
|
||||
|
||||
printer(['Modulus :', f'{int.from_bytes(self.Modulus, "little"):0{0x180 * 2}X}'[:64]], padd, False)
|
||||
printer(['Exponent :', f'0x{self.Exponent:X}'], padd, False)
|
||||
printer(['Signature:', f'{int.from_bytes(self.Signature, "little"):0{0x180 * 2}X}'[:64]], padd, False)
|
||||
|
||||
|
||||
def is_ami_pfat(input_object: str | bytes | bytearray) -> bool:
|
||||
""" Check if input is AMI BIOS Guard """
|
||||
|
||||
input_buffer: bytes = file_to_bytes(input_object)
|
||||
|
||||
return bool(get_ami_pfat(input_buffer))
|
||||
|
||||
def get_ami_pfat(input_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)
|
||||
|
||||
|
||||
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}')
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
BigScript = get_bgs_tool()
|
||||
|
||||
if not BigScript:
|
||||
|
||||
big_script = get_bgs_tool()
|
||||
|
||||
if not big_script:
|
||||
printer('Note: BIOS Guard Script Tool optional dependency is missing!', padding, False)
|
||||
|
||||
|
||||
return 3
|
||||
|
||||
script = BigScript(code_bytes=script_data).to_string().replace('\t',' ').split('\n')
|
||||
|
||||
|
||||
script = big_script(code_bytes=script_data).to_string().replace('\t', ' ').split('\n')
|
||||
|
||||
for opcode in script:
|
||||
if opcode.endswith(('begin','end')): spacing = padding
|
||||
elif opcode.endswith(':'): spacing = padding + 4
|
||||
else: spacing = padding + 12
|
||||
|
||||
if opcode.endswith(('begin', 'end')):
|
||||
spacing: int = padding
|
||||
elif opcode.endswith(':'):
|
||||
spacing = padding + 4
|
||||
else:
|
||||
spacing = padding + 12
|
||||
|
||||
operands = [operand for operand in opcode.split(' ') if operand]
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
hdr_size = pfat_hdr.Size
|
||||
hdr_data = buffer[PFAT_AMI_HDR_LEN:hdr_size]
|
||||
hdr_text = hdr_data.decode('utf-8').splitlines()
|
||||
|
||||
|
||||
hdr_size: int = pfat_hdr.Size
|
||||
|
||||
hdr_data: bytes = buffer[PFAT_AMI_HDR_LEN:hdr_size]
|
||||
|
||||
hdr_text: list[str] = hdr_data.decode('utf-8').splitlines()
|
||||
|
||||
printer('AMI BIOS Guard Header:\n', padding)
|
||||
|
||||
|
||||
pfat_hdr.struct_print(padding + 4)
|
||||
|
||||
hdr_title,*hdr_files = hdr_text
|
||||
|
||||
files_count = len(hdr_files)
|
||||
|
||||
hdr_tag,*hdr_indexes = hdr_title.split('II')
|
||||
|
||||
|
||||
hdr_title, *hdr_files = hdr_text
|
||||
|
||||
files_count: int = len(hdr_files)
|
||||
|
||||
hdr_tag, *hdr_indexes = hdr_title.split('II')
|
||||
|
||||
printer(hdr_tag + '\n', padding + 4)
|
||||
|
||||
bgt_indexes = [int(h, 16) for h in re.findall(r'.{1,4}', hdr_indexes[0])] if hdr_indexes else []
|
||||
|
||||
for index,entry in enumerate(hdr_files):
|
||||
entry_parts = entry.split(';')
|
||||
|
||||
info = entry_parts[0].split()
|
||||
name = entry_parts[1]
|
||||
|
||||
flags = int(info[0])
|
||||
param = info[1]
|
||||
count = int(info[2])
|
||||
|
||||
order = get_ordinal((bgt_indexes[index] if bgt_indexes else index) + 1)
|
||||
|
||||
desc = f'{name} (Index: {index + 1:02d}, Flash: {order}, Parameter: {param}, Flags: 0x{flags:X}, Blocks: {count})'
|
||||
|
||||
|
||||
bgt_indexes: list = [int(h, 16) for h in re.findall(r'.{1,4}', hdr_indexes[0])] if hdr_indexes else []
|
||||
|
||||
for index, entry in enumerate(hdr_files):
|
||||
entry_parts: list = entry.split(';')
|
||||
|
||||
info: list = entry_parts[0].split()
|
||||
|
||||
name: str = entry_parts[1]
|
||||
|
||||
flags: int = int(info[0])
|
||||
|
||||
param: str = info[1]
|
||||
|
||||
count: int = int(info[2])
|
||||
|
||||
order: str = get_ordinal((bgt_indexes[index] if bgt_indexes else index) + 1)
|
||||
|
||||
desc = f'{name} (Index: {index + 1:02d}, Flash: {order}, ' \
|
||||
f'Parameter: {param}, Flags: 0x{flags:X}, Blocks: {count})'
|
||||
|
||||
block_all += [(desc, name, order, param, flags, index, i, count) for i in range(count)]
|
||||
|
||||
|
||||
_ = [printer(block[0], padding + 8, False) for block in block_all if block[6] == 0]
|
||||
|
||||
|
||||
return block_all, hdr_size, files_count
|
||||
|
||||
def parse_pfat_file(input_file, extract_path, padding=0):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
pfat_buffer = get_ami_pfat(input_buffer)
|
||||
|
||||
file_path = ''
|
||||
all_blocks_dict = {}
|
||||
|
||||
extract_name = os.path.basename(extract_path).rstrip(extract_suffix())
|
||||
|
||||
|
||||
def parse_pfat_file(input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
|
||||
""" Process and store AMI BIOS Guard output file """
|
||||
|
||||
input_buffer: bytes = file_to_bytes(input_object)
|
||||
|
||||
pfat_buffer: bytes = get_ami_pfat(input_buffer)
|
||||
|
||||
file_path: str = ''
|
||||
|
||||
all_blocks_dict: dict = {}
|
||||
|
||||
extract_name: str = path_name(extract_path).removesuffix(extract_suffix())
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
block_all,block_off,file_count = parse_pfat_hdr(pfat_buffer, padding)
|
||||
|
||||
block_all, block_off, file_count = parse_pfat_hdr(pfat_buffer, padding)
|
||||
|
||||
for block in block_all:
|
||||
file_desc,file_name,_,_,_,file_index,block_index,block_count = block
|
||||
|
||||
file_desc, file_name, _, _, _, file_index, block_index, block_count = block
|
||||
|
||||
if block_index == 0:
|
||||
printer(file_desc, padding + 4)
|
||||
|
||||
|
||||
file_path = os.path.join(extract_path, get_file_name(file_index + 1, file_name))
|
||||
|
||||
|
||||
all_blocks_dict[file_index] = b''
|
||||
|
||||
block_status = f'{block_index + 1}/{block_count}'
|
||||
|
||||
|
||||
block_status: str = f'{block_index + 1}/{block_count}'
|
||||
|
||||
bg_hdr = get_struct(pfat_buffer, block_off, IntelBiosGuardHeader)
|
||||
|
||||
|
||||
printer(f'Intel BIOS Guard {block_status} Header:\n', padding + 8)
|
||||
|
||||
|
||||
bg_hdr.struct_print(padding + 12)
|
||||
|
||||
bg_script_bgn = 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:
|
||||
bg_sig_bgn = bg_data_end
|
||||
bg_sig_end = bg_sig_bgn + PFAT_BLK_S2K_LEN
|
||||
bg_sig_bin = pfat_buffer[bg_sig_bgn:bg_sig_end]
|
||||
|
||||
if len(bg_sig_bin) == PFAT_BLK_S2K_LEN:
|
||||
bg_sig = get_struct(bg_sig_bin, 0x0, IntelBiosGuardSignature2k)
|
||||
|
||||
printer(f'Intel BIOS Guard {block_status} Signature:\n', padding + 8)
|
||||
|
||||
bg_sig.struct_print(padding + 12)
|
||||
printer(f'Intel BIOS Guard {block_status} Signature:\n', padding + 8)
|
||||
|
||||
# Adjust next block to start after current block Data + Signature
|
||||
block_off += parse_bg_sign(pfat_buffer, bg_data_end, True, padding + 12)
|
||||
|
||||
block_off = bg_sig_end # Adjust next block to start at data + signature end
|
||||
|
||||
printer(f'Intel BIOS Guard {block_status} Script:\n', padding + 8)
|
||||
|
||||
_ = parse_bg_script(bg_script_bin, padding + 12)
|
||||
|
||||
|
||||
_ = parse_bg_script(pfat_buffer[bg_script_bgn:bg_script_end], padding + 12)
|
||||
|
||||
with open(file_path, 'ab') as out_dat:
|
||||
out_dat.write(bg_data_bin)
|
||||
|
||||
|
||||
all_blocks_dict[file_index] += bg_data_bin
|
||||
|
||||
pfat_oob_data = pfat_buffer[block_off:] # Store out-of-bounds data after the end of PFAT files
|
||||
|
||||
pfat_oob_name = get_file_name(file_count + 1, f'{extract_name}_OOB.bin')
|
||||
|
||||
pfat_oob_path = os.path.join(extract_path, pfat_oob_name)
|
||||
|
||||
|
||||
if block_index + 1 == block_count:
|
||||
if is_ami_pfat(all_blocks_dict[file_index]):
|
||||
parse_pfat_file(all_blocks_dict[file_index], get_extract_path(file_path), padding + 8)
|
||||
|
||||
pfat_oob_data: bytes = pfat_buffer[block_off:] # Store out-of-bounds data after the end of PFAT files
|
||||
|
||||
pfat_oob_name: str = get_file_name(file_count + 1, f'{extract_name}_OOB.bin')
|
||||
|
||||
pfat_oob_path: str = os.path.join(extract_path, pfat_oob_name)
|
||||
|
||||
with open(pfat_oob_path, 'wb') as out_oob:
|
||||
out_oob.write(pfat_oob_data)
|
||||
|
||||
|
||||
if is_ami_pfat(pfat_oob_data):
|
||||
parse_pfat_file(pfat_oob_data, get_extract_path(pfat_oob_path), padding)
|
||||
|
||||
in_all_data = b''.join([block[1] for block in sorted(all_blocks_dict.items())])
|
||||
|
||||
in_all_name = get_file_name(0, f'{extract_name}_ALL.bin')
|
||||
|
||||
in_all_path = os.path.join(extract_path, in_all_name)
|
||||
|
||||
|
||||
in_all_data: bytes = b''.join([block[1] for block in sorted(all_blocks_dict.items())])
|
||||
|
||||
in_all_name: str = get_file_name(0, f'{extract_name}_ALL.bin')
|
||||
|
||||
in_all_path: str = os.path.join(extract_path, in_all_name)
|
||||
|
||||
with open(in_all_path, 'wb') as out_all:
|
||||
out_all.write(in_all_data + pfat_oob_data)
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
PFAT_AMI_HDR_LEN = ctypes.sizeof(AmiBiosGuardHeader)
|
||||
PFAT_BLK_HDR_LEN = ctypes.sizeof(IntelBiosGuardHeader)
|
||||
PFAT_BLK_S2K_LEN = ctypes.sizeof(IntelBiosGuardSignature2k)
|
||||
|
||||
PFAT_AMI_HDR_LEN: int = ctypes.sizeof(AmiBiosGuardHeader)
|
||||
PFAT_BLK_HDR_LEN: int = ctypes.sizeof(IntelBiosGuardHeader)
|
||||
PFAT_BLK_SIG_LEN: int = ctypes.sizeof(IntelBiosGuardSignatureHeader)
|
||||
|
||||
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()
|
||||
|
|
|
@ -1,29 +1,23 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
AMI UCP Extract
|
||||
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 re
|
||||
import sys
|
||||
import struct
|
||||
import ctypes
|
||||
import contextlib
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.checksums import get_chk_16
|
||||
from common.comp_efi import efi_decompress, is_efi_compressed
|
||||
from common.path_ops import agnostic_path, make_dirs, safe_name, safe_path, get_extract_path
|
||||
from common.path_ops import agnostic_path, get_extract_path, make_dirs, safe_name, safe_path
|
||||
from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG
|
||||
from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t
|
||||
from common.struct_ops import Char, get_struct, UInt8, UInt16, UInt32
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes, to_string
|
||||
|
@ -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 Insyde_IFD_Extract import insyde_ifd_extract, is_insyde_ifd
|
||||
|
||||
TITLE = 'AMI UCP Update Extractor v3.0'
|
||||
|
||||
|
||||
class UafHeader(ctypes.LittleEndianStructure):
|
||||
""" UAF Header """
|
||||
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('ModuleTag', char*4), # 0x00
|
||||
('ModuleSize', uint32_t), # 0x04
|
||||
('Checksum', uint16_t), # 0x08
|
||||
('Unknown0', uint8_t), # 0x0A
|
||||
('Unknown1', uint8_t), # 0x0A
|
||||
('Reserved', uint8_t*4), # 0x0C
|
||||
('ModuleTag', Char * 4), # 0x00
|
||||
('ModuleSize', UInt32), # 0x04
|
||||
('Checksum', UInt16), # 0x08
|
||||
('Unknown0', UInt8), # 0x0A
|
||||
('Unknown1', UInt8), # 0x0A
|
||||
('Reserved', UInt8 * 4), # 0x0C
|
||||
# 0x10
|
||||
]
|
||||
|
||||
|
||||
def _get_reserved(self):
|
||||
res_bytes = bytes(self.Reserved)
|
||||
|
||||
|
||||
res_hex = f'0x{int.from_bytes(res_bytes, "big"):0{0x4 * 2}X}'
|
||||
|
||||
res_str = re.sub(r'[\n\t\r\x00 ]', '', res_bytes.decode('utf-8','ignore'))
|
||||
|
||||
|
||||
res_str = re.sub(r'[\n\t\r\x00 ]', '', res_bytes.decode('utf-8', 'ignore'))
|
||||
|
||||
res_txt = f' ({res_str})' if len(res_str) else ''
|
||||
|
||||
|
||||
return f'{res_hex}{res_txt}'
|
||||
|
||||
def struct_print(self, p):
|
||||
printer(['Tag :', self.ModuleTag.decode('utf-8')], p, False)
|
||||
printer(['Size :', f'0x{self.ModuleSize:X}'], p, False)
|
||||
printer(['Checksum :', f'0x{self.Checksum:04X}'], p, False)
|
||||
printer(['Unknown 0 :', f'0x{self.Unknown0:02X}'], p, False)
|
||||
printer(['Unknown 1 :', f'0x{self.Unknown1:02X}'], p, False)
|
||||
printer(['Reserved :', self._get_reserved()], p, False)
|
||||
|
||||
def struct_print(self, padd):
|
||||
""" Display structure information """
|
||||
|
||||
printer(['Tag :', self.ModuleTag.decode('utf-8')], padd, False)
|
||||
printer(['Size :', f'0x{self.ModuleSize:X}'], padd, False)
|
||||
printer(['Checksum :', f'0x{self.Checksum:04X}'], padd, False)
|
||||
printer(['Unknown 0 :', f'0x{self.Unknown0:02X}'], padd, False)
|
||||
printer(['Unknown 1 :', f'0x{self.Unknown1:02X}'], padd, False)
|
||||
printer(['Reserved :', self._get_reserved()], padd, False)
|
||||
|
||||
|
||||
class UafModule(ctypes.LittleEndianStructure):
|
||||
""" UAF Module """
|
||||
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('CompressSize', uint32_t), # 0x00
|
||||
('OriginalSize', uint32_t), # 0x04
|
||||
('CompressSize', UInt32), # 0x00
|
||||
('OriginalSize', UInt32), # 0x04
|
||||
# 0x08
|
||||
]
|
||||
|
||||
def struct_print(self, p, filename, description):
|
||||
printer(['Compress Size:', f'0x{self.CompressSize:X}'], p, False)
|
||||
printer(['Original Size:', f'0x{self.OriginalSize:X}'], p, False)
|
||||
printer(['Filename :', filename], p, False)
|
||||
printer(['Description :', description], p, False)
|
||||
|
||||
def struct_print(self, padd, filename, description):
|
||||
""" Display structure information """
|
||||
|
||||
printer(['Compress Size:', f'0x{self.CompressSize:X}'], padd, False)
|
||||
printer(['Original Size:', f'0x{self.OriginalSize:X}'], padd, False)
|
||||
printer(['Filename :', filename], padd, False)
|
||||
printer(['Description :', description], padd, False)
|
||||
|
||||
|
||||
class UiiHeader(ctypes.LittleEndianStructure):
|
||||
""" UII Header """
|
||||
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('UIISize', uint16_t), # 0x00
|
||||
('Checksum', uint16_t), # 0x02
|
||||
('UtilityVersion', uint32_t), # 0x04 AFU|BGT (Unknown, Signed)
|
||||
('InfoSize', uint16_t), # 0x08
|
||||
('SupportBIOS', uint8_t), # 0x0A
|
||||
('SupportOS', uint8_t), # 0x0B
|
||||
('DataBusWidth', uint8_t), # 0x0C
|
||||
('ProgramType', uint8_t), # 0x0D
|
||||
('ProgramMode', uint8_t), # 0x0E
|
||||
('SourceSafeRel', uint8_t), # 0x0F
|
||||
('UIISize', UInt16), # 0x00
|
||||
('Checksum', UInt16), # 0x02
|
||||
('UtilityVersion', UInt32), # 0x04 AFU|BGT (Unknown, Signed)
|
||||
('InfoSize', UInt16), # 0x08
|
||||
('SupportBIOS', UInt8), # 0x0A
|
||||
('SupportOS', UInt8), # 0x0B
|
||||
('DataBusWidth', UInt8), # 0x0C
|
||||
('ProgramType', UInt8), # 0x0D
|
||||
('ProgramMode', UInt8), # 0x0E
|
||||
('SourceSafeRel', UInt8), # 0x0F
|
||||
# 0x10
|
||||
]
|
||||
|
||||
|
||||
SBI = {1: 'ALL', 2: 'AMIBIOS8', 3: 'UEFI', 4: 'AMIBIOS8/UEFI'}
|
||||
SOS = {1: 'DOS', 2: 'EFI', 3: 'Windows', 4: 'Linux', 5: 'FreeBSD', 6: 'MacOS', 128: 'Multi-Platform'}
|
||||
DBW = {1: '16b', 2: '16/32b', 3: '32b', 4: '64b'}
|
||||
PTP = {1: 'Executable', 2: 'Library', 3: 'Driver'}
|
||||
PMD = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'}
|
||||
|
||||
def struct_print(self, p, description):
|
||||
SupportBIOS = self.SBI.get(self.SupportBIOS, f'Unknown ({self.SupportBIOS})')
|
||||
SupportOS = self.SOS.get(self.SupportOS, f'Unknown ({self.SupportOS})')
|
||||
DataBusWidth = self.DBW.get(self.DataBusWidth, f'Unknown ({self.DataBusWidth})')
|
||||
ProgramType = self.PTP.get(self.ProgramType, f'Unknown ({self.ProgramType})')
|
||||
ProgramMode = self.PMD.get(self.ProgramMode, f'Unknown ({self.ProgramMode})')
|
||||
|
||||
printer(['UII Size :', f'0x{self.UIISize:X}'], p, False)
|
||||
printer(['Checksum :', f'0x{self.Checksum:04X}'], p, False)
|
||||
printer(['Tool Version :', f'0x{self.UtilityVersion:08X}'], p, False)
|
||||
printer(['Info Size :', f'0x{self.InfoSize:X}'], p, False)
|
||||
printer(['Supported BIOS:', SupportBIOS], p, False)
|
||||
printer(['Supported OS :', SupportOS], p, False)
|
||||
printer(['Data Bus Width:', DataBusWidth], p, False)
|
||||
printer(['Program Type :', ProgramType], p, False)
|
||||
printer(['Program Mode :', ProgramMode], p, False)
|
||||
printer(['SourceSafe Tag:', f'{self.SourceSafeRel:02d}'], p, False)
|
||||
printer(['Description :', description], p, False)
|
||||
|
||||
def struct_print(self, padd, description):
|
||||
""" Display structure information """
|
||||
|
||||
support_bios = self.SBI.get(self.SupportBIOS, f'Unknown ({self.SupportBIOS})')
|
||||
support_os = self.SOS.get(self.SupportOS, f'Unknown ({self.SupportOS})')
|
||||
data_bus_width = self.DBW.get(self.DataBusWidth, f'Unknown ({self.DataBusWidth})')
|
||||
program_type = self.PTP.get(self.ProgramType, f'Unknown ({self.ProgramType})')
|
||||
program_mode = self.PMD.get(self.ProgramMode, f'Unknown ({self.ProgramMode})')
|
||||
|
||||
printer(['UII Size :', f'0x{self.UIISize:X}'], padd, False)
|
||||
printer(['Checksum :', f'0x{self.Checksum:04X}'], padd, False)
|
||||
printer(['Tool Version :', f'0x{self.UtilityVersion:08X}'], padd, False)
|
||||
printer(['Info Size :', f'0x{self.InfoSize:X}'], padd, False)
|
||||
printer(['Supported BIOS:', support_bios], padd, False)
|
||||
printer(['Supported OS :', support_os], padd, False)
|
||||
printer(['Data Bus Width:', data_bus_width], padd, False)
|
||||
printer(['Program Type :', program_type], padd, False)
|
||||
printer(['Program Mode :', program_mode], padd, False)
|
||||
printer(['SourceSafe Tag:', f'{self.SourceSafeRel:02d}'], padd, False)
|
||||
printer(['Description :', description], padd, False)
|
||||
|
||||
|
||||
class DisHeader(ctypes.LittleEndianStructure):
|
||||
""" DIS Header """
|
||||
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('PasswordSize', uint16_t), # 0x00
|
||||
('EntryCount', uint16_t), # 0x02
|
||||
('Password', char*12), # 0x04
|
||||
('PasswordSize', UInt16), # 0x00
|
||||
('EntryCount', UInt16), # 0x02
|
||||
('Password', Char * 12), # 0x04
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def struct_print(self, p):
|
||||
printer(['Password Size:', f'0x{self.PasswordSize:X}'], p, False)
|
||||
printer(['Entry Count :', self.EntryCount], p, False)
|
||||
printer(['Password :', self.Password.decode('utf-8')], p, False)
|
||||
|
||||
def struct_print(self, padd):
|
||||
""" Display structure information """
|
||||
|
||||
printer(['Password Size:', f'0x{self.PasswordSize:X}'], padd, False)
|
||||
printer(['Entry Count :', self.EntryCount], padd, False)
|
||||
printer(['Password :', self.Password.decode('utf-8')], padd, False)
|
||||
|
||||
|
||||
class DisModule(ctypes.LittleEndianStructure):
|
||||
""" DIS Module """
|
||||
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('EnabledDisabled', uint8_t), # 0x00
|
||||
('ShownHidden', uint8_t), # 0x01
|
||||
('Command', char*32), # 0x02
|
||||
('Description', char*256), # 0x22
|
||||
('EnabledDisabled', UInt8), # 0x00
|
||||
('ShownHidden', UInt8), # 0x01
|
||||
('Command', Char * 32), # 0x02
|
||||
('Description', Char * 256), # 0x22
|
||||
# 0x122
|
||||
]
|
||||
|
||||
|
||||
ENDIS = {0: 'Disabled', 1: 'Enabled'}
|
||||
SHOWN = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'}
|
||||
|
||||
def struct_print(self, 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):
|
||||
""" Validate UCP Module Checksum-16 """
|
||||
|
||||
if get_chk_16(data) != 0:
|
||||
printer(f'Error: Invalid UCP Module {tag} Checksum!', padd, pause=True)
|
||||
else:
|
||||
printer(f'Checksum of UCP Module {tag} is valid!', padd)
|
||||
|
||||
# Check if input is AMI UCP image
|
||||
|
||||
def is_ami_ucp(in_file):
|
||||
""" Check if input is AMI UCP image """
|
||||
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
|
||||
return bool(get_ami_ucp(buffer)[0] is not None)
|
||||
|
||||
# Get all input file AMI UCP patterns
|
||||
|
||||
def get_ami_ucp(in_file):
|
||||
""" Get all input file AMI UCP patterns """
|
||||
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
uaf_len_max = 0x0 # Length of largest detected @UAF|@HPU
|
||||
uaf_buf_bin = None # Buffer of largest detected @UAF|@HPU
|
||||
uaf_buf_tag = '@UAF' # Tag of largest detected @UAF|@HPU
|
||||
|
||||
|
||||
uaf_len_max = 0x0 # Length of largest detected @UAF|@HPU
|
||||
uaf_buf_bin = None # Buffer of largest detected @UAF|@HPU
|
||||
uaf_buf_tag = '@UAF' # Tag of largest detected @UAF|@HPU
|
||||
|
||||
for uaf in PAT_AMI_UCP.finditer(buffer):
|
||||
uaf_len_cur = int.from_bytes(buffer[uaf.start() + 0x4:uaf.start() + 0x8], 'little')
|
||||
|
||||
|
||||
if uaf_len_cur > uaf_len_max:
|
||||
uaf_len_max = uaf_len_cur
|
||||
|
||||
uaf_hdr_off = uaf.start()
|
||||
|
||||
uaf_buf_bin = buffer[uaf_hdr_off:uaf_hdr_off + uaf_len_max]
|
||||
uaf_buf_tag = uaf.group(0)[:4].decode('utf-8','ignore')
|
||||
|
||||
|
||||
uaf_buf_tag = uaf.group(0)[:4].decode('utf-8', 'ignore')
|
||||
|
||||
return uaf_buf_bin, uaf_buf_tag
|
||||
|
||||
# Get list of @UAF|@HPU Modules
|
||||
|
||||
def get_uaf_mod(buffer, uaf_off=0x0):
|
||||
uaf_all = [] # Initialize list of all @UAF|@HPU Modules
|
||||
|
||||
while buffer[uaf_off] == 0x40: # ASCII of @ is 0x40
|
||||
uaf_hdr = get_struct(buffer, uaf_off, UafHeader) # Parse @UAF|@HPU Module Structure
|
||||
|
||||
uaf_tag = uaf_hdr.ModuleTag.decode('utf-8') # Get unique @UAF|@HPU Module Tag
|
||||
|
||||
uaf_all.append([uaf_tag, uaf_off, uaf_hdr]) # Store @UAF|@HPU Module Info
|
||||
|
||||
uaf_off += uaf_hdr.ModuleSize # Adjust to next @UAF|@HPU Module offset
|
||||
|
||||
""" Get list of @UAF|@HPU Modules """
|
||||
|
||||
uaf_all = [] # Initialize list of all @UAF|@HPU Modules
|
||||
|
||||
while buffer[uaf_off] == 0x40: # ASCII of @ is 0x40
|
||||
uaf_hdr = get_struct(buffer, uaf_off, UafHeader) # Parse @UAF|@HPU Module Structure
|
||||
|
||||
uaf_tag = uaf_hdr.ModuleTag.decode('utf-8') # Get unique @UAF|@HPU Module Tag
|
||||
|
||||
uaf_all.append([uaf_tag, uaf_off, uaf_hdr]) # Store @UAF|@HPU Module Info
|
||||
|
||||
uaf_off += uaf_hdr.ModuleSize # Adjust to next @UAF|@HPU Module offset
|
||||
|
||||
if uaf_off >= len(buffer):
|
||||
break # Stop parsing at EOF
|
||||
|
||||
break # Stop parsing at EOF
|
||||
|
||||
# Check if @UAF|@HPU Module @NAL exists and place it first
|
||||
# Parsing @NAL first allows naming all @UAF|@HPU Modules
|
||||
for mod_idx,mod_val in enumerate(uaf_all):
|
||||
for mod_idx, mod_val in enumerate(uaf_all):
|
||||
if mod_val[0] == '@NAL':
|
||||
uaf_all.insert(1, uaf_all.pop(mod_idx)) # After UII for visual purposes
|
||||
|
||||
break # @NAL found, skip the rest
|
||||
|
||||
uaf_all.insert(1, uaf_all.pop(mod_idx)) # After UII for visual purposes
|
||||
|
||||
break # @NAL found, skip the rest
|
||||
|
||||
return uaf_all
|
||||
|
||||
# Parse & Extract AMI UCP structures
|
||||
|
||||
def ucp_extract(in_file, extract_path, padding=0, checksum=False):
|
||||
""" Parse & Extract AMI UCP structures """
|
||||
|
||||
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)
|
||||
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
|
||||
# Get best AMI UCP Pattern match based on @UAF|@HPU Size
|
||||
ucp_buffer,ucp_tag = get_ami_ucp(input_buffer)
|
||||
|
||||
uaf_hdr = get_struct(ucp_buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure
|
||||
|
||||
ucp_buffer, ucp_tag = get_ami_ucp(input_buffer)
|
||||
|
||||
uaf_hdr = get_struct(ucp_buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure
|
||||
|
||||
printer(f'Utility Auxiliary File > {ucp_tag}:\n', padding + 4)
|
||||
|
||||
|
||||
uaf_hdr.struct_print(padding + 8)
|
||||
|
||||
fake = struct.pack('<II', len(ucp_buffer), len(ucp_buffer)) # Generate UafModule Structure
|
||||
|
||||
uaf_mod = get_struct(fake, 0x0, UafModule) # Parse @UAF|@HPU Module EFI Structure
|
||||
|
||||
uaf_name = UAF_TAG_DICT[ucp_tag][0] # Get @UAF|@HPU Module Filename
|
||||
uaf_desc = UAF_TAG_DICT[ucp_tag][1] # Get @UAF|@HPU Module Description
|
||||
|
||||
uaf_mod.struct_print(padding + 8, uaf_name, uaf_desc) # Print @UAF|@HPU Module EFI Info
|
||||
|
||||
|
||||
fake = struct.pack('<II', len(ucp_buffer), len(ucp_buffer)) # Generate UafModule Structure
|
||||
|
||||
uaf_mod = get_struct(fake, 0x0, UafModule) # Parse @UAF|@HPU Module EFI Structure
|
||||
|
||||
uaf_name = UAF_TAG_DICT[ucp_tag][0] # Get @UAF|@HPU Module Filename
|
||||
|
||||
uaf_desc = UAF_TAG_DICT[ucp_tag][1] # Get @UAF|@HPU Module Description
|
||||
|
||||
uaf_mod.struct_print(padding + 8, uaf_name, uaf_desc) # Print @UAF|@HPU Module EFI Info
|
||||
|
||||
if checksum:
|
||||
chk16_validate(ucp_buffer, ucp_tag, padding + 8)
|
||||
|
||||
|
||||
uaf_all = get_uaf_mod(ucp_buffer, UAF_HDR_LEN)
|
||||
|
||||
|
||||
for mod_info in uaf_all:
|
||||
nal_dict = uaf_extract(ucp_buffer, extract_path, mod_info, padding + 8, checksum, nal_dict)
|
||||
|
||||
# Parse & Extract AMI UCP > @UAF|@HPU Module/Section
|
||||
|
||||
def uaf_extract(buffer, extract_path, mod_info, padding=0, checksum=False, nal_dict=None):
|
||||
""" Parse & Extract AMI UCP > @UAF|@HPU Module/Section """
|
||||
|
||||
if nal_dict is None:
|
||||
nal_dict = {}
|
||||
|
||||
uaf_tag,uaf_off,uaf_hdr = mod_info
|
||||
|
||||
uaf_data_all = buffer[uaf_off:uaf_off + uaf_hdr.ModuleSize] # @UAF|@HPU Module Entire Data
|
||||
|
||||
uaf_data_mod = uaf_data_all[UAF_HDR_LEN:] # @UAF|@HPU Module EFI Data
|
||||
|
||||
uaf_data_raw = uaf_data_mod[UAF_MOD_LEN:] # @UAF|@HPU Module Raw Data
|
||||
|
||||
|
||||
uaf_tag, uaf_off, uaf_hdr = mod_info
|
||||
|
||||
uaf_data_all = buffer[uaf_off:uaf_off + uaf_hdr.ModuleSize] # @UAF|@HPU Module Entire Data
|
||||
|
||||
uaf_data_mod = uaf_data_all[UAF_HDR_LEN:] # @UAF|@HPU Module EFI Data
|
||||
|
||||
uaf_data_raw = uaf_data_mod[UAF_MOD_LEN:] # @UAF|@HPU Module Raw Data
|
||||
|
||||
printer(f'Utility Auxiliary File > {uaf_tag}:\n', padding)
|
||||
|
||||
uaf_hdr.struct_print(padding + 4) # Print @UAF|@HPU Module Info
|
||||
|
||||
uaf_mod = get_struct(buffer, uaf_off + UAF_HDR_LEN, UafModule) # Parse UAF Module EFI Structure
|
||||
|
||||
is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect @UAF|@HPU Module EFI Compression
|
||||
|
||||
|
||||
uaf_hdr.struct_print(padding + 4) # Print @UAF|@HPU Module Info
|
||||
|
||||
uaf_mod = get_struct(buffer, uaf_off + UAF_HDR_LEN, UafModule) # Parse UAF Module EFI Structure
|
||||
|
||||
is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect @UAF|@HPU Module EFI Compression
|
||||
|
||||
if uaf_tag in nal_dict:
|
||||
uaf_name = nal_dict[uaf_tag][1] # Always prefer @NAL naming first
|
||||
uaf_name = nal_dict[uaf_tag][1] # Always prefer @NAL naming first
|
||||
elif uaf_tag in UAF_TAG_DICT:
|
||||
uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming
|
||||
uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming
|
||||
elif uaf_tag == '@ROM':
|
||||
uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature)
|
||||
uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature)
|
||||
elif uaf_tag.startswith('@R0'):
|
||||
uaf_name = f'BIOS_0{uaf_tag[3:]}.bin' # BIOS/PFAT Firmware
|
||||
uaf_name = f'BIOS_0{uaf_tag[3:]}.bin' # BIOS/PFAT Firmware
|
||||
elif uaf_tag.startswith('@S0'):
|
||||
uaf_name = f'BIOS_0{uaf_tag[3:]}.sig' # BIOS/PFAT Signature
|
||||
uaf_name = f'BIOS_0{uaf_tag[3:]}.sig' # BIOS/PFAT Signature
|
||||
elif uaf_tag.startswith('@DR'):
|
||||
uaf_name = f'DROM_0{uaf_tag[3:]}.bin' # Thunderbolt Retimer Firmware
|
||||
uaf_name = f'DROM_0{uaf_tag[3:]}.bin' # Thunderbolt Retimer Firmware
|
||||
elif uaf_tag.startswith('@DS'):
|
||||
uaf_name = f'DROM_0{uaf_tag[3:]}.sig' # Thunderbolt Retimer Signature
|
||||
uaf_name = f'DROM_0{uaf_tag[3:]}.sig' # Thunderbolt Retimer Signature
|
||||
elif uaf_tag.startswith('@EC'):
|
||||
uaf_name = f'EC_0{uaf_tag[3:]}.bin' # Embedded Controller Firmware
|
||||
uaf_name = f'EC_0{uaf_tag[3:]}.bin' # Embedded Controller Firmware
|
||||
elif uaf_tag.startswith('@ME'):
|
||||
uaf_name = f'ME_0{uaf_tag[3:]}.bin' # Management Engine Firmware
|
||||
uaf_name = f'ME_0{uaf_tag[3:]}.bin' # Management Engine Firmware
|
||||
else:
|
||||
uaf_name = uaf_tag # Could not name the @UAF|@HPU Module, use Tag instead
|
||||
|
||||
uaf_name = uaf_tag # Could not name the @UAF|@HPU Module, use Tag instead
|
||||
|
||||
uaf_fext = '' if uaf_name != uaf_tag else '.bin'
|
||||
|
||||
|
||||
uaf_fdesc = UAF_TAG_DICT[uaf_tag][1] if uaf_tag in UAF_TAG_DICT else uaf_name
|
||||
|
||||
uaf_mod.struct_print(padding + 4, uaf_name + uaf_fext, uaf_fdesc) # Print @UAF|@HPU Module EFI Info
|
||||
|
||||
|
||||
uaf_mod.struct_print(padding + 4, uaf_name + uaf_fext, uaf_fdesc) # Print @UAF|@HPU Module EFI Info
|
||||
|
||||
# Check if unknown @UAF|@HPU Module Tag is present in @NAL but not in built-in dictionary
|
||||
if uaf_tag in nal_dict and uaf_tag not in UAF_TAG_DICT and not uaf_tag.startswith(('@ROM','@R0','@S0','@DR','@DS')):
|
||||
printer(f'Note: Detected new AMI UCP Module {uaf_tag} ({nal_dict[uaf_tag][1]}) in @NAL!', padding + 4, pause=True)
|
||||
|
||||
if uaf_tag in nal_dict and uaf_tag not in UAF_TAG_DICT and \
|
||||
not uaf_tag.startswith(('@ROM', '@R0', '@S0', '@DR', '@DS')):
|
||||
|
||||
printer(f'Note: Detected new AMI UCP Module {uaf_tag} ({nal_dict[uaf_tag][1]}) in @NAL!',
|
||||
padding + 4, pause=True)
|
||||
|
||||
# Generate @UAF|@HPU Module File name, depending on whether decompression will be required
|
||||
uaf_sname = safe_name(uaf_name + ('.temp' if is_comp else uaf_fext))
|
||||
|
||||
if uaf_tag in nal_dict:
|
||||
uaf_npath = safe_path(extract_path, nal_dict[uaf_tag][0])
|
||||
|
||||
make_dirs(uaf_npath, exist_ok=True)
|
||||
|
||||
uaf_fname = safe_path(uaf_npath, uaf_sname)
|
||||
else:
|
||||
uaf_fname = safe_path(extract_path, uaf_sname)
|
||||
|
||||
|
||||
if checksum:
|
||||
chk16_validate(uaf_data_all, uaf_tag, padding + 4)
|
||||
|
||||
|
||||
# Parse Utility Identification Information @UAF|@HPU Module (@UII)
|
||||
if uaf_tag == '@UII':
|
||||
info_hdr = get_struct(uaf_data_raw, 0, UiiHeader) # Parse @UII Module Raw Structure
|
||||
|
||||
info_data = uaf_data_raw[max(UII_HDR_LEN,info_hdr.InfoSize):info_hdr.UIISize] # @UII Module Info Data
|
||||
|
||||
info_hdr = get_struct(uaf_data_raw, 0, UiiHeader) # Parse @UII Module Raw Structure
|
||||
|
||||
info_data = uaf_data_raw[max(UII_HDR_LEN, info_hdr.InfoSize):info_hdr.UIISize] # @UII Module Info Data
|
||||
|
||||
# Get @UII Module Info/Description text field
|
||||
info_desc = info_data.decode('utf-8','ignore').strip('\x00 ')
|
||||
|
||||
info_desc = info_data.decode('utf-8', 'ignore').strip('\x00 ')
|
||||
|
||||
printer('Utility Identification Information:\n', padding + 4)
|
||||
|
||||
info_hdr.struct_print(padding + 8, info_desc) # Print @UII Module Info
|
||||
|
||||
|
||||
info_hdr.struct_print(padding + 8, info_desc) # Print @UII Module Info
|
||||
|
||||
if checksum:
|
||||
chk16_validate(uaf_data_raw, '@UII > Info', padding + 8)
|
||||
|
||||
|
||||
# Store/Save @UII Module Info in file
|
||||
with open(uaf_fname[:-4] + '.txt', 'a', encoding='utf-8') as uii_out:
|
||||
with contextlib.redirect_stdout(uii_out):
|
||||
info_hdr.struct_print(0, info_desc) # Store @UII Module Info
|
||||
|
||||
info_hdr.struct_print(0, info_desc) # Store @UII Module Info
|
||||
|
||||
# Adjust @UAF|@HPU Module Raw Data for extraction
|
||||
if is_comp:
|
||||
# Some Compressed @UAF|@HPU Module EFI data lack necessary EOF padding
|
||||
if uaf_mod.CompressSize > len(uaf_data_raw):
|
||||
comp_padd = b'\x00' * (uaf_mod.CompressSize - len(uaf_data_raw))
|
||||
uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw + comp_padd # Add missing padding for decompression
|
||||
|
||||
# Add missing padding for decompression
|
||||
uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw + comp_padd
|
||||
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:
|
||||
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
|
||||
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:
|
||||
uaf_out.write(uaf_data_raw)
|
||||
|
||||
|
||||
# @UAF|@HPU Module EFI/Tiano Decompression
|
||||
if is_comp and is_efi_compressed(uaf_data_raw, False):
|
||||
dec_fname = uaf_fname.replace('.temp', uaf_fext) # Decompressed @UAF|@HPU Module file path
|
||||
|
||||
dec_fname = uaf_fname.replace('.temp', uaf_fext) # Decompressed @UAF|@HPU Module file path
|
||||
|
||||
if efi_decompress(uaf_fname, dec_fname, padding + 4) == 0:
|
||||
with open(dec_fname, 'rb') as dec:
|
||||
uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data
|
||||
|
||||
os.remove(uaf_fname) # Successful decompression, delete compressed @UAF|@HPU Module file
|
||||
|
||||
uaf_fname = dec_fname # Adjust @UAF|@HPU Module file path to the decompressed one
|
||||
|
||||
uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data
|
||||
|
||||
os.remove(uaf_fname) # Successful decompression, delete compressed @UAF|@HPU Module file
|
||||
|
||||
uaf_fname = dec_fname # Adjust @UAF|@HPU Module file path to the decompressed one
|
||||
|
||||
# Process and Print known text only @UAF|@HPU Modules (after EFI/Tiano Decompression)
|
||||
if uaf_tag in UAF_TAG_DICT and UAF_TAG_DICT[uaf_tag][2] == 'Text':
|
||||
printer(f'{UAF_TAG_DICT[uaf_tag][1]}:', padding + 4)
|
||||
printer(uaf_data_raw.decode('utf-8','ignore'), padding + 8)
|
||||
|
||||
|
||||
printer(uaf_data_raw.decode('utf-8', 'ignore'), padding + 8)
|
||||
|
||||
# Parse Default Command Status @UAF|@HPU Module (@DIS)
|
||||
if len(uaf_data_raw) and uaf_tag == '@DIS':
|
||||
dis_hdr = get_struct(uaf_data_raw, 0x0, DisHeader) # Parse @DIS Module Raw Header Structure
|
||||
|
||||
dis_hdr = get_struct(uaf_data_raw, 0x0, DisHeader) # Parse @DIS Module Raw Header Structure
|
||||
|
||||
printer('Default Command Status Header:\n', padding + 4)
|
||||
|
||||
dis_hdr.struct_print(padding + 8) # Print @DIS Module Raw Header Info
|
||||
|
||||
|
||||
dis_hdr.struct_print(padding + 8) # Print @DIS Module Raw Header Info
|
||||
|
||||
# Store/Save @DIS Module Header Info in file
|
||||
with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis:
|
||||
with contextlib.redirect_stdout(dis):
|
||||
dis_hdr.struct_print(0) # Store @DIS Module Header Info
|
||||
|
||||
dis_data = uaf_data_raw[DIS_HDR_LEN:] # @DIS Module Entries Data
|
||||
|
||||
dis_hdr.struct_print(0) # Store @DIS Module Header Info
|
||||
|
||||
dis_data = uaf_data_raw[DIS_HDR_LEN:] # @DIS Module Entries Data
|
||||
|
||||
# Parse all @DIS Module Entries
|
||||
for mod_idx in range(dis_hdr.EntryCount):
|
||||
dis_mod = get_struct(dis_data, mod_idx * DIS_MOD_LEN, DisModule) # Parse @DIS Module Raw Entry Structure
|
||||
|
||||
dis_mod = get_struct(dis_data, mod_idx * DIS_MOD_LEN, DisModule) # Parse @DIS Module Raw Entry Structure
|
||||
|
||||
printer(f'Default Command Status Entry {mod_idx + 1:02d}/{dis_hdr.EntryCount:02d}:\n', padding + 8)
|
||||
|
||||
dis_mod.struct_print(padding + 12) # Print @DIS Module Raw Entry Info
|
||||
|
||||
|
||||
dis_mod.struct_print(padding + 12) # Print @DIS Module Raw Entry Info
|
||||
|
||||
# Store/Save @DIS Module Entry Info in file
|
||||
with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis:
|
||||
with contextlib.redirect_stdout(dis):
|
||||
printer()
|
||||
dis_mod.struct_print(4) # Store @DIS Module Entry Info
|
||||
|
||||
os.remove(uaf_fname) # Delete @DIS Module binary, info exported as text
|
||||
|
||||
|
||||
dis_mod.struct_print(4) # Store @DIS Module Entry Info
|
||||
|
||||
os.remove(uaf_fname) # Delete @DIS Module binary, info exported as text
|
||||
|
||||
# Parse Name List @UAF|@HPU Module (@NAL)
|
||||
if len(uaf_data_raw) >= 5 and (uaf_tag,uaf_data_raw[0],uaf_data_raw[4]) == ('@NAL',0x40,0x3A):
|
||||
nal_info = uaf_data_raw.decode('utf-8','ignore').replace('\r','').strip().split('\n')
|
||||
|
||||
if len(uaf_data_raw) >= 5 and (uaf_tag, uaf_data_raw[0], uaf_data_raw[4]) == ('@NAL', 0x40, 0x3A):
|
||||
nal_info = uaf_data_raw.decode('utf-8', 'ignore').replace('\r', '').strip().split('\n')
|
||||
|
||||
printer('AMI UCP Module Name List:\n', padding + 4)
|
||||
|
||||
|
||||
# Parse all @NAL Module Entries
|
||||
for info in nal_info:
|
||||
info_tag,info_value = info.split(':',1)
|
||||
|
||||
printer(f'{info_tag} : {info_value}', padding + 8, False) # Print @NAL Module Tag-Path Info
|
||||
|
||||
info_part = agnostic_path(info_value).parts # Split OS agnostic path in parts
|
||||
info_path = to_string(info_part[1:-1], os.sep) # Get path without drive/root or file
|
||||
info_name = info_part[-1] # Get file from last path part
|
||||
|
||||
nal_dict[info_tag] = (info_path,info_name) # Assign a file path & name to each Tag
|
||||
|
||||
info_tag, info_value = info.split(':', 1)
|
||||
|
||||
printer(f'{info_tag} : {info_value}', padding + 8, False) # Print @NAL Module Tag-Path Info
|
||||
|
||||
info_part = agnostic_path(info_value).parts # Split OS agnostic path in parts
|
||||
|
||||
info_path = to_string(info_part[1:-1], os.sep) # Get path without drive/root or file
|
||||
|
||||
info_name = info_part[-1] # Get file from last path part
|
||||
|
||||
nal_dict[info_tag] = (info_path, info_name) # Assign a file path & name to each Tag
|
||||
|
||||
# Parse Insyde BIOS @UAF|@HPU Module (@INS)
|
||||
if uaf_tag == '@INS' and is_insyde_ifd(uaf_fname):
|
||||
ins_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-IFD')) # Generate extraction directory
|
||||
|
||||
ins_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-IFD')) # Generate extraction directory
|
||||
|
||||
if insyde_ifd_extract(uaf_fname, get_extract_path(ins_dir), padding + 4) == 0:
|
||||
os.remove(uaf_fname) # Delete raw nested Insyde IFD image after successful extraction
|
||||
|
||||
os.remove(uaf_fname) # Delete raw nested Insyde IFD image after successful extraction
|
||||
|
||||
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
|
||||
if is_ami_pfat(uaf_data_raw):
|
||||
pfat_dir = os.path.join(extract_path, safe_name(uaf_name))
|
||||
|
||||
|
||||
parse_pfat_file(uaf_data_raw, get_extract_path(pfat_dir), padding + 4)
|
||||
|
||||
os.remove(uaf_fname) # Delete raw PFAT BIOS image after successful extraction
|
||||
|
||||
|
||||
os.remove(uaf_fname) # Delete raw PFAT BIOS image after successful extraction
|
||||
|
||||
# Detect Intel Engine firmware image and show ME Analyzer advice
|
||||
if uaf_tag.startswith('@ME') and PAT_INTEL_ENG.search(uaf_data_raw):
|
||||
printer('Intel Management Engine (ME) Firmware:\n', padding + 4)
|
||||
printer('Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer', padding + 8, False)
|
||||
|
||||
|
||||
# Parse Nested AMI UCP image
|
||||
if is_ami_ucp(uaf_data_raw):
|
||||
uaf_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-UCP')) # Generate extraction directory
|
||||
|
||||
ucp_extract(uaf_data_raw, get_extract_path(uaf_dir), padding + 4, checksum) # Call recursively
|
||||
|
||||
os.remove(uaf_fname) # Delete raw nested AMI UCP image after successful extraction
|
||||
|
||||
uaf_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-UCP')) # Generate extraction directory
|
||||
|
||||
ucp_extract(uaf_data_raw, get_extract_path(uaf_dir), padding + 4, checksum) # Call recursively
|
||||
|
||||
os.remove(uaf_fname) # Delete raw nested AMI UCP image after successful extraction
|
||||
|
||||
return nal_dict
|
||||
|
||||
|
||||
# Get common ctypes Structure Sizes
|
||||
UAF_HDR_LEN = ctypes.sizeof(UafHeader)
|
||||
UAF_MOD_LEN = ctypes.sizeof(UafModule)
|
||||
|
@ -453,63 +505,66 @@ UII_HDR_LEN = ctypes.sizeof(UiiHeader)
|
|||
|
||||
# AMI UCP Tag Dictionary
|
||||
UAF_TAG_DICT = {
|
||||
'@3FI' : ['HpBiosUpdate32.efi', 'HpBiosUpdate32.efi', ''],
|
||||
'@3S2' : ['HpBiosUpdate32.s12', 'HpBiosUpdate32.s12', ''],
|
||||
'@3S4' : ['HpBiosUpdate32.s14', 'HpBiosUpdate32.s14', ''],
|
||||
'@3S9' : ['HpBiosUpdate32.s09', 'HpBiosUpdate32.s09', ''],
|
||||
'@3SG' : ['HpBiosUpdate32.sig', 'HpBiosUpdate32.sig', ''],
|
||||
'@AMI' : ['UCP_Nested.bin', 'Nested AMI UCP', ''],
|
||||
'@B12' : ['BiosMgmt.s12', 'BiosMgmt.s12', ''],
|
||||
'@B14' : ['BiosMgmt.s14', 'BiosMgmt.s14', ''],
|
||||
'@B32' : ['BiosMgmt32.s12', 'BiosMgmt32.s12', ''],
|
||||
'@B34' : ['BiosMgmt32.s14', 'BiosMgmt32.s14', ''],
|
||||
'@B39' : ['BiosMgmt32.s09', 'BiosMgmt32.s09', ''],
|
||||
'@B3E' : ['BiosMgmt32.efi', 'BiosMgmt32.efi', ''],
|
||||
'@BM9' : ['BiosMgmt.s09', 'BiosMgmt.s09', ''],
|
||||
'@BME' : ['BiosMgmt.efi', 'BiosMgmt.efi', ''],
|
||||
'@CKV' : ['Check_Version.txt', 'Check Version', 'Text'],
|
||||
'@CMD' : ['AFU_Command.txt', 'AMI AFU Command', 'Text'],
|
||||
'@CML' : ['CMOSD4.txt', 'CMOS Item Number-Value (MSI)', 'Text'],
|
||||
'@CMS' : ['CMOSD4.exe', 'Get or Set CMOS Item (MSI)', ''],
|
||||
'@CPM' : ['AC_Message.txt', 'Confirm Power Message', ''],
|
||||
'@DCT' : ['DevCon32.exe', 'Device Console WIN32', ''],
|
||||
'@DCX' : ['DevCon64.exe', 'Device Console WIN64', ''],
|
||||
'@DFE' : ['HpDevFwUpdate.efi', 'HpDevFwUpdate.efi', ''],
|
||||
'@DFS' : ['HpDevFwUpdate.s12', 'HpDevFwUpdate.s12', ''],
|
||||
'@DIS' : ['Command_Status.bin', 'Default Command Status', ''],
|
||||
'@ENB' : ['ENBG64.exe', 'ENBG64.exe', ''],
|
||||
'@HPU' : ['UCP_Main.bin', 'Utility Auxiliary File (HP)', ''],
|
||||
'@INS' : ['Insyde_Nested.bin', 'Nested Insyde SFX', ''],
|
||||
'@M32' : ['HpBiosMgmt32.s12', 'HpBiosMgmt32.s12', ''],
|
||||
'@M34' : ['HpBiosMgmt32.s14', 'HpBiosMgmt32.s14', ''],
|
||||
'@M39' : ['HpBiosMgmt32.s09', 'HpBiosMgmt32.s09', ''],
|
||||
'@M3I' : ['HpBiosMgmt32.efi', 'HpBiosMgmt32.efi', ''],
|
||||
'@MEC' : ['FWUpdLcl.txt', 'Intel FWUpdLcl Command', 'Text'],
|
||||
'@MED' : ['FWUpdLcl_DOS.exe', 'Intel FWUpdLcl DOS', ''],
|
||||
'@MET' : ['FWUpdLcl_WIN32.exe', 'Intel FWUpdLcl WIN32', ''],
|
||||
'@MFI' : ['HpBiosMgmt.efi', 'HpBiosMgmt.efi', ''],
|
||||
'@MS2' : ['HpBiosMgmt.s12', 'HpBiosMgmt.s12', ''],
|
||||
'@MS4' : ['HpBiosMgmt.s14', 'HpBiosMgmt.s14', ''],
|
||||
'@MS9' : ['HpBiosMgmt.s09', 'HpBiosMgmt.s09', ''],
|
||||
'@NAL' : ['UCP_List.txt', 'AMI UCP Module Name List', ''],
|
||||
'@OKM' : ['OK_Message.txt', 'OK Message', ''],
|
||||
'@PFC' : ['BGT_Command.txt', 'AMI BGT Command', 'Text'],
|
||||
'@R3I' : ['CryptRSA32.efi', 'CryptRSA32.efi', ''],
|
||||
'@RFI' : ['CryptRSA.efi', 'CryptRSA.efi', ''],
|
||||
'@UAF' : ['UCP_Main.bin', 'Utility Auxiliary File (AMI)', ''],
|
||||
'@UFI' : ['HpBiosUpdate.efi', 'HpBiosUpdate.efi', ''],
|
||||
'@UII' : ['UCP_Info.txt', 'Utility Identification Information', ''],
|
||||
'@US2' : ['HpBiosUpdate.s12', 'HpBiosUpdate.s12', ''],
|
||||
'@US4' : ['HpBiosUpdate.s14', 'HpBiosUpdate.s14', ''],
|
||||
'@US9' : ['HpBiosUpdate.s09', 'HpBiosUpdate.s09', ''],
|
||||
'@USG' : ['HpBiosUpdate.sig', 'HpBiosUpdate.sig', ''],
|
||||
'@VER' : ['OEM_Version.txt', 'OEM Version', 'Text'],
|
||||
'@VXD' : ['amifldrv.vxd', 'amifldrv.vxd', ''],
|
||||
'@W32' : ['amifldrv32.sys', 'amifldrv32.sys', ''],
|
||||
'@W64' : ['amifldrv64.sys', 'amifldrv64.sys', ''],
|
||||
'@3FI': ['HpBiosUpdate32.efi', 'HpBiosUpdate32.efi', ''],
|
||||
'@3S2': ['HpBiosUpdate32.s12', 'HpBiosUpdate32.s12', ''],
|
||||
'@3S4': ['HpBiosUpdate32.s14', 'HpBiosUpdate32.s14', ''],
|
||||
'@3S9': ['HpBiosUpdate32.s09', 'HpBiosUpdate32.s09', ''],
|
||||
'@3SG': ['HpBiosUpdate32.sig', 'HpBiosUpdate32.sig', ''],
|
||||
'@AMI': ['UCP_Nested.bin', 'Nested AMI UCP', ''],
|
||||
'@B12': ['BiosMgmt.s12', 'BiosMgmt.s12', ''],
|
||||
'@B14': ['BiosMgmt.s14', 'BiosMgmt.s14', ''],
|
||||
'@B32': ['BiosMgmt32.s12', 'BiosMgmt32.s12', ''],
|
||||
'@B34': ['BiosMgmt32.s14', 'BiosMgmt32.s14', ''],
|
||||
'@B39': ['BiosMgmt32.s09', 'BiosMgmt32.s09', ''],
|
||||
'@B3E': ['BiosMgmt32.efi', 'BiosMgmt32.efi', ''],
|
||||
'@BM9': ['BiosMgmt.s09', 'BiosMgmt.s09', ''],
|
||||
'@BME': ['BiosMgmt.efi', 'BiosMgmt.efi', ''],
|
||||
'@CKV': ['Check_Version.txt', 'Check Version', 'Text'],
|
||||
'@CMD': ['AFU_Command.txt', 'AMI AFU Command', 'Text'],
|
||||
'@CML': ['CMOSD4.txt', 'CMOS Item Number-Value (MSI)', 'Text'],
|
||||
'@CMS': ['CMOSD4.exe', 'Get or Set CMOS Item (MSI)', ''],
|
||||
'@CPM': ['AC_Message.txt', 'Confirm Power Message', ''],
|
||||
'@DCT': ['DevCon32.exe', 'Device Console WIN32', ''],
|
||||
'@DCX': ['DevCon64.exe', 'Device Console WIN64', ''],
|
||||
'@DFE': ['HpDevFwUpdate.efi', 'HpDevFwUpdate.efi', ''],
|
||||
'@DFS': ['HpDevFwUpdate.s12', 'HpDevFwUpdate.s12', ''],
|
||||
'@DIS': ['Command_Status.bin', 'Default Command Status', ''],
|
||||
'@ENB': ['ENBG64.exe', 'ENBG64.exe', ''],
|
||||
'@HPU': ['UCP_Main.bin', 'Utility Auxiliary File (HP)', ''],
|
||||
'@INS': ['Insyde_Nested.bin', 'Nested Insyde SFX', ''],
|
||||
'@M32': ['HpBiosMgmt32.s12', 'HpBiosMgmt32.s12', ''],
|
||||
'@M34': ['HpBiosMgmt32.s14', 'HpBiosMgmt32.s14', ''],
|
||||
'@M39': ['HpBiosMgmt32.s09', 'HpBiosMgmt32.s09', ''],
|
||||
'@M3I': ['HpBiosMgmt32.efi', 'HpBiosMgmt32.efi', ''],
|
||||
'@MEC': ['FWUpdLcl.txt', 'Intel FWUpdLcl Command', 'Text'],
|
||||
'@MED': ['FWUpdLcl_DOS.exe', 'Intel FWUpdLcl DOS', ''],
|
||||
'@MET': ['FWUpdLcl_WIN32.exe', 'Intel FWUpdLcl WIN32', ''],
|
||||
'@MFI': ['HpBiosMgmt.efi', 'HpBiosMgmt.efi', ''],
|
||||
'@MS2': ['HpBiosMgmt.s12', 'HpBiosMgmt.s12', ''],
|
||||
'@MS4': ['HpBiosMgmt.s14', 'HpBiosMgmt.s14', ''],
|
||||
'@MS9': ['HpBiosMgmt.s09', 'HpBiosMgmt.s09', ''],
|
||||
'@NAL': ['UCP_List.txt', 'AMI UCP Module Name List', ''],
|
||||
'@OKM': ['OK_Message.txt', 'OK Message', ''],
|
||||
'@PFC': ['BGT_Command.txt', 'AMI BGT Command', 'Text'],
|
||||
'@R3I': ['CryptRSA32.efi', 'CryptRSA32.efi', ''],
|
||||
'@RFI': ['CryptRSA.efi', 'CryptRSA.efi', ''],
|
||||
'@UAF': ['UCP_Main.bin', 'Utility Auxiliary File (AMI)', ''],
|
||||
'@UFI': ['HpBiosUpdate.efi', 'HpBiosUpdate.efi', ''],
|
||||
'@UII': ['UCP_Info.txt', 'Utility Identification Information', ''],
|
||||
'@US2': ['HpBiosUpdate.s12', 'HpBiosUpdate.s12', ''],
|
||||
'@US4': ['HpBiosUpdate.s14', 'HpBiosUpdate.s14', ''],
|
||||
'@US9': ['HpBiosUpdate.s09', 'HpBiosUpdate.s09', ''],
|
||||
'@USG': ['HpBiosUpdate.sig', 'HpBiosUpdate.sig', ''],
|
||||
'@VER': ['OEM_Version.txt', 'OEM Version', 'Text'],
|
||||
'@VXD': ['amifldrv.vxd', 'amifldrv.vxd', ''],
|
||||
'@W32': ['amifldrv32.sys', 'amifldrv32.sys', ''],
|
||||
'@W64': ['amifldrv64.sys', 'amifldrv64.sys', ''],
|
||||
'@D64': ['amifldrv64.sys', 'amifldrv64.sys', ''],
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
utility = BIOSUtility(TITLE, is_ami_ucp, ucp_extract)
|
||||
utility.parse_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true')
|
||||
utility_args = [(['-c', '--checksum'], {'help': 'verify AMI UCP Checksums (slow)', 'action': 'store_true'})]
|
||||
|
||||
utility = BIOSUtility(title=TITLE, check=is_ami_ucp, main=ucp_extract, args=utility_args)
|
||||
|
||||
utility.run_utility()
|
||||
|
|
230
Apple_EFI_ID.py
230
Apple_EFI_ID.py
|
@ -1,167 +1,181 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Apple EFI ID
|
||||
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 logging
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
import zlib
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.externals import get_uefifind_path, get_uefiextract_path
|
||||
from common.externals import get_uefiextract_path, get_uefifind_path
|
||||
from common.path_ops import del_dirs, path_parent, path_suffixes
|
||||
from common.patterns import PAT_APPLE_EFI
|
||||
from common.struct_ops import char, get_struct, uint8_t
|
||||
from common.struct_ops import Char, get_struct, UInt8
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
TITLE = 'Apple EFI Image Identifier v3.0'
|
||||
|
||||
|
||||
class IntelBiosId(ctypes.LittleEndianStructure):
|
||||
""" Intel BIOS ID Structure """
|
||||
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Signature', char*8), # 0x00
|
||||
('BoardID', uint8_t*16), # 0x08
|
||||
('Dot1', uint8_t*2), # 0x18
|
||||
('BoardExt', uint8_t*6), # 0x1A
|
||||
('Dot2', uint8_t*2), # 0x20
|
||||
('VersionMajor', uint8_t*8), # 0x22
|
||||
('Dot3', uint8_t*2), # 0x2A
|
||||
('BuildType', uint8_t*2), # 0x2C
|
||||
('VersionMinor', uint8_t*4), # 0x2E
|
||||
('Dot4', uint8_t*2), # 0x32
|
||||
('Year', uint8_t*4), # 0x34
|
||||
('Month', uint8_t*4), # 0x38
|
||||
('Day', uint8_t*4), # 0x3C
|
||||
('Hour', uint8_t*4), # 0x40
|
||||
('Minute', uint8_t*4), # 0x44
|
||||
('NullTerminator', uint8_t*2), # 0x48
|
||||
('Signature', Char * 8), # 0x00
|
||||
('BoardID', UInt8 * 16), # 0x08
|
||||
('Dot1', UInt8 * 2), # 0x18
|
||||
('BoardExt', UInt8 * 6), # 0x1A
|
||||
('Dot2', UInt8 * 2), # 0x20
|
||||
('VersionMajor', UInt8 * 8), # 0x22
|
||||
('Dot3', UInt8 * 2), # 0x2A
|
||||
('BuildType', UInt8 * 2), # 0x2C
|
||||
('VersionMinor', UInt8 * 4), # 0x2E
|
||||
('Dot4', UInt8 * 2), # 0x32
|
||||
('Year', UInt8 * 4), # 0x34
|
||||
('Month', UInt8 * 4), # 0x38
|
||||
('Day', UInt8 * 4), # 0x3C
|
||||
('Hour', UInt8 * 4), # 0x40
|
||||
('Minute', UInt8 * 4), # 0x44
|
||||
('NullTerminator', UInt8 * 2), # 0x48
|
||||
# 0x4A
|
||||
]
|
||||
|
||||
# https://github.com/tianocore/edk2-platforms/blob/master/Platform/Intel/BoardModulePkg/Include/Guid/BiosId.h
|
||||
|
||||
@staticmethod
|
||||
def decode(field):
|
||||
return struct.pack('B' * len(field), *field).decode('utf-16','ignore').strip('\x00 ')
|
||||
|
||||
def get_bios_id(self):
|
||||
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):
|
||||
""" Check if input is Apple EFI image """
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
|
||||
if PAT_APPLE_EFI.search(input_buffer):
|
||||
return True
|
||||
|
||||
|
||||
if not os.path.isfile(input_file):
|
||||
return False
|
||||
|
||||
try:
|
||||
_ = subprocess.run([get_uefifind_path(), input_file, 'body', 'list', PAT_UEFIFIND],
|
||||
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
return True
|
||||
except Exception:
|
||||
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):
|
||||
""" Parse & Identify (or Rename) Apple EFI image """
|
||||
|
||||
if not os.path.isfile(input_file):
|
||||
printer('Error: Could not find input file path!', padding)
|
||||
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
bios_id_match = PAT_APPLE_EFI.search(input_buffer) # Detect $IBIOSI$ pattern
|
||||
|
||||
|
||||
bios_id_match = PAT_APPLE_EFI.search(input_buffer) # Detect $IBIOSI$ pattern
|
||||
|
||||
if bios_id_match:
|
||||
bios_id_res = f'0x{bios_id_match.start():X}'
|
||||
|
||||
|
||||
bios_id_hdr = get_struct(input_buffer, bios_id_match.start(), IntelBiosId)
|
||||
else:
|
||||
# The $IBIOSI$ pattern is within EFI compressed modules so we need to use UEFIFind and UEFIExtract
|
||||
try:
|
||||
bios_id_res = subprocess.check_output([get_uefifind_path(), input_file, 'body', 'list', PAT_UEFIFIND],
|
||||
text=True)[:36]
|
||||
|
||||
del_dirs(extract_path) # UEFIExtract must create its output folder itself, make sure it is not present
|
||||
|
||||
text=True)[:36]
|
||||
|
||||
del_dirs(extract_path) # UEFIExtract must create its output folder itself, make sure it is not present
|
||||
|
||||
_ = subprocess.run([get_uefiextract_path(), input_file, bios_id_res, '-o', extract_path, '-m', 'body'],
|
||||
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
with open(os.path.join(extract_path, 'body.bin'), 'rb') as raw_body:
|
||||
body_buffer = raw_body.read()
|
||||
|
||||
bios_id_match = PAT_APPLE_EFI.search(body_buffer) # Detect decompressed $IBIOSI$ pattern
|
||||
|
||||
|
||||
bios_id_match = PAT_APPLE_EFI.search(body_buffer) # Detect decompressed $IBIOSI$ pattern
|
||||
|
||||
bios_id_hdr = get_struct(body_buffer, bios_id_match.start(), IntelBiosId)
|
||||
|
||||
del_dirs(extract_path) # Successful UEFIExtract extraction, remove its output (temp) folder
|
||||
except Exception:
|
||||
printer('Error: Failed to parse compressed $IBIOSI$ pattern!', padding)
|
||||
|
||||
|
||||
del_dirs(extract_path) # Successful UEFIExtract extraction, remove its output (temp) folder
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
printer(f'Error: Failed to parse compressed $IBIOSI$ pattern: {error}!', padding)
|
||||
|
||||
return 2
|
||||
|
||||
|
||||
printer(f'Detected $IBIOSI$ at {bios_id_res}\n', padding)
|
||||
|
||||
|
||||
bios_id_hdr.struct_print(padding + 4)
|
||||
|
||||
|
||||
if rename:
|
||||
input_parent = path_parent(input_file)
|
||||
|
||||
|
||||
input_suffix = path_suffixes(input_file)[-1]
|
||||
|
||||
|
||||
input_adler32 = zlib.adler32(input_buffer)
|
||||
|
||||
ID,Ext,Major,Type,Minor,Date,Time = bios_id_hdr.get_bios_id()
|
||||
|
||||
output_name = f'{ID}_{Ext}_{Major}_{Type}{Minor}_{Date}_{Time}_{input_adler32:08X}{input_suffix}'
|
||||
|
||||
|
||||
fw_id, fw_ext, fw_major, fw_type, fw_minor, fw_date, fw_time = bios_id_hdr.get_bios_id()
|
||||
|
||||
output_name = f'{fw_id}_{fw_ext}_{fw_major}_{fw_type}{fw_minor}_{fw_date}_{fw_time}_' \
|
||||
f'{input_adler32:08X}{input_suffix}'
|
||||
|
||||
output_file = os.path.join(input_parent, output_name)
|
||||
|
||||
|
||||
if not os.path.isfile(output_file):
|
||||
os.replace(input_file, output_file) # Rename input file based on its EFI tag
|
||||
|
||||
os.replace(input_file, output_file) # Rename input file based on its EFI tag
|
||||
|
||||
printer(f'Renamed to {output_name}', padding)
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
PAT_UEFIFIND = f'244942494F534924{"."*32}2E00{"."*12}2E00{"."*16}2E00{"."*12}2E00{"."*40}0000'
|
||||
|
||||
PAT_UEFIFIND = f'244942494F534924{"." * 32}2E00{"." * 12}2E00{"." * 16}2E00{"." * 12}2E00{"." * 40}0000'
|
||||
|
||||
if __name__ == '__main__':
|
||||
utility = BIOSUtility(TITLE, is_apple_efi, apple_efi_identify)
|
||||
utility.parse_argument('-r', '--rename', help='rename EFI image based on its tag', action='store_true')
|
||||
utility_args = [(['-r', '--rename'], {'help': 'rename EFI image based on its tag', 'action': 'store_true'})]
|
||||
|
||||
utility = BIOSUtility(title=TITLE, check=is_apple_efi, main=apple_efi_identify, args=utility_args)
|
||||
|
||||
utility.run_utility()
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Apple EFI IM4P
|
||||
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 sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.path_ops import make_dirs, path_stem
|
||||
from common.patterns import PAT_APPLE_IM4P, PAT_INTEL_IFD
|
||||
|
@ -21,125 +15,133 @@ from common.system import printer
|
|||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is Apple EFI IM4P image
|
||||
TITLE = 'Apple EFI IM4P Splitter v4.0'
|
||||
|
||||
|
||||
def is_apple_im4p(input_file):
|
||||
""" Check if input is Apple EFI IM4P image """
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
|
||||
is_im4p = PAT_APPLE_IM4P.search(input_buffer)
|
||||
|
||||
|
||||
is_ifd = PAT_INTEL_IFD.search(input_buffer)
|
||||
|
||||
|
||||
return bool(is_im4p and is_ifd)
|
||||
|
||||
# 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 = []
|
||||
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
|
||||
# Detect IM4P EFI pattern
|
||||
im4p_match = PAT_APPLE_IM4P.search(input_buffer)
|
||||
|
||||
|
||||
# After IM4P mefi (0x15), multi EFI payloads have _MEFIBIN (0x100) but is difficult to RE w/o varying samples.
|
||||
# However, _MEFIBIN is not required for splitting SPI images due to Intel Flash Descriptor Components Density.
|
||||
|
||||
|
||||
# IM4P mefi payload start offset
|
||||
mefi_data_bgn = im4p_match.start() + input_buffer[im4p_match.start() - 0x1]
|
||||
|
||||
|
||||
# IM4P mefi payload size
|
||||
mefi_data_len = int.from_bytes(input_buffer[im4p_match.end() + 0x5:im4p_match.end() + 0x9], 'big')
|
||||
|
||||
|
||||
# Check if mefi is followed by _MEFIBIN
|
||||
mefibin_exist = input_buffer[mefi_data_bgn:mefi_data_bgn + 0x8] == b'_MEFIBIN'
|
||||
|
||||
|
||||
# Actual multi EFI payloads start after _MEFIBIN
|
||||
efi_data_bgn = mefi_data_bgn + 0x100 if mefibin_exist else mefi_data_bgn
|
||||
|
||||
|
||||
# Actual multi EFI payloads size without _MEFIBIN
|
||||
efi_data_len = mefi_data_len - 0x100 if mefibin_exist else mefi_data_len
|
||||
|
||||
|
||||
# Adjust input file buffer to actual multi EFI payloads data
|
||||
input_buffer = input_buffer[efi_data_bgn:efi_data_bgn + efi_data_len]
|
||||
|
||||
|
||||
# Parse Intel Flash Descriptor pattern matches
|
||||
for ifd in PAT_INTEL_IFD.finditer(input_buffer):
|
||||
# Component Base Address from FD start (ICH8-ICH10 = 1, IBX = 2, CPT+ = 3)
|
||||
ifd_flmap0_fcba = input_buffer[ifd.start() + 0x4] * 0x10
|
||||
|
||||
|
||||
# I/O Controller Hub (ICH)
|
||||
if ifd_flmap0_fcba == 0x10:
|
||||
# At ICH, Flash Descriptor starts at 0x0
|
||||
ifd_bgn_substruct = 0x0
|
||||
|
||||
|
||||
# 0xBC for [0xAC] + 0xFF * 16 sanity check
|
||||
ifd_end_substruct = 0xBC
|
||||
|
||||
|
||||
# Platform Controller Hub (PCH)
|
||||
else:
|
||||
# At PCH, Flash Descriptor starts at 0x10
|
||||
ifd_bgn_substruct = 0x10
|
||||
|
||||
|
||||
# 0xBC for [0xAC] + 0xFF * 16 sanity check
|
||||
ifd_end_substruct = 0xBC
|
||||
|
||||
|
||||
# Actual Flash Descriptor Start Offset
|
||||
ifd_match_start = ifd.start() - ifd_bgn_substruct
|
||||
|
||||
|
||||
# Actual Flash Descriptor End Offset
|
||||
ifd_match_end = ifd.end() - ifd_end_substruct
|
||||
|
||||
|
||||
# Calculate Intel Flash Descriptor Flash Component Total Size
|
||||
|
||||
|
||||
# Component Count (00 = 1, 01 = 2)
|
||||
ifd_flmap0_nc = ((int.from_bytes(input_buffer[ifd_match_end:ifd_match_end + 0x4], 'little') >> 8) & 3) + 1
|
||||
|
||||
|
||||
# PCH/ICH Strap Length (ME 2-8 & TXE 0-2 & SPS 1-2 <= 0x12, ME 9+ & TXE 3+ & SPS 3+ >= 0x13)
|
||||
ifd_flmap1_isl = input_buffer[ifd_match_end + 0x7]
|
||||
|
||||
|
||||
# Component Density Byte (ME 2-8 & TXE 0-2 & SPS 1-2 = 0:5, ME 9+ & TXE 3+ & SPS 3+ = 0:7)
|
||||
ifd_comp_den = input_buffer[ifd_match_start + ifd_flmap0_fcba]
|
||||
|
||||
|
||||
# Component 1 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4)
|
||||
ifd_comp_1_bitwise = 0xF if ifd_flmap1_isl >= 0x13 else 0x7
|
||||
|
||||
|
||||
# Component 2 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4)
|
||||
ifd_comp_2_bitwise = 0x4 if ifd_flmap1_isl >= 0x13 else 0x3
|
||||
|
||||
|
||||
# Component 1 Density (FCBA > C0DEN)
|
||||
ifd_comp_all_size = IFD_COMP_LEN[ifd_comp_den & ifd_comp_1_bitwise]
|
||||
|
||||
|
||||
# Component 2 Density (FCBA > C1DEN)
|
||||
if ifd_flmap0_nc == 2:
|
||||
ifd_comp_all_size += IFD_COMP_LEN[ifd_comp_den >> ifd_comp_2_bitwise]
|
||||
|
||||
|
||||
ifd_data_bgn = ifd_match_start
|
||||
ifd_data_end = ifd_data_bgn + ifd_comp_all_size
|
||||
|
||||
ifd_data_txt = f'0x{ifd_data_bgn:07X}-0x{ifd_data_end:07X}'
|
||||
|
||||
|
||||
output_data = input_buffer[ifd_data_bgn:ifd_data_end]
|
||||
|
||||
|
||||
output_size = len(output_data)
|
||||
|
||||
|
||||
output_name = path_stem(input_file) if os.path.isfile(input_file) else 'Part'
|
||||
|
||||
|
||||
output_path = os.path.join(extract_path, f'{output_name}_[{ifd_data_txt}].fd')
|
||||
|
||||
|
||||
with open(output_path, 'wb') as output_image:
|
||||
output_image.write(output_data)
|
||||
|
||||
|
||||
printer(f'Split Apple EFI image at {ifd_data_txt}!', padding)
|
||||
|
||||
|
||||
if output_size != ifd_comp_all_size:
|
||||
printer(f'Error: Bad image size 0x{output_size:07X}, expected 0x{ifd_comp_all_size:07X}!', padding + 4)
|
||||
|
||||
|
||||
exit_codes.append(1)
|
||||
|
||||
|
||||
return sum(exit_codes)
|
||||
|
||||
|
||||
# Intel Flash Descriptor Component Sizes (4MB, 8MB, 16MB and 32MB)
|
||||
IFD_COMP_LEN = {3: 0x400000, 4: 0x800000, 5: 0x1000000, 6: 0x2000000}
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_apple_im4p, apple_im4p_split).run_utility()
|
||||
BIOSUtility(title=TITLE, check=is_apple_im4p, main=apple_im4p_split).run_utility()
|
||||
|
|
|
@ -1,118 +1,128 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Apple PBZX Extract
|
||||
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
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
import logging
|
||||
import lzma
|
||||
import os
|
||||
|
||||
from common.comp_szip import is_szip_supported, szip_decompress
|
||||
from common.path_ops import make_dirs, path_stem
|
||||
from common.patterns import PAT_APPLE_PBZX
|
||||
from common.struct_ops import get_struct, uint32_t
|
||||
from common.struct_ops import get_struct, UInt32
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
TITLE = 'Apple EFI PBZX Extractor v2.0'
|
||||
|
||||
|
||||
class PbzxChunk(ctypes.BigEndianStructure):
|
||||
""" PBZX Chunk Header """
|
||||
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Reserved0', uint32_t), # 0x00
|
||||
('InitSize', uint32_t), # 0x04
|
||||
('Reserved1', uint32_t), # 0x08
|
||||
('CompSize', uint32_t), # 0x0C
|
||||
('Reserved0', UInt32), # 0x00
|
||||
('InitSize', UInt32), # 0x04
|
||||
('Reserved1', UInt32), # 0x08
|
||||
('CompSize', UInt32), # 0x0C
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def struct_print(self, p):
|
||||
printer(['Reserved 0 :', f'0x{self.Reserved0:X}'], p, False)
|
||||
printer(['Initial Size :', f'0x{self.InitSize:X}'], p, False)
|
||||
printer(['Reserved 1 :', f'0x{self.Reserved1:X}'], p, False)
|
||||
printer(['Compressed Size:', f'0x{self.CompSize:X}'], p, False)
|
||||
|
||||
# Check if input is Apple PBZX image
|
||||
def struct_print(self, padd):
|
||||
""" Display structure information """
|
||||
|
||||
printer(['Reserved 0 :', f'0x{self.Reserved0:X}'], padd, False)
|
||||
printer(['Initial Size :', f'0x{self.InitSize:X}'], padd, False)
|
||||
printer(['Reserved 1 :', f'0x{self.Reserved1:X}'], padd, False)
|
||||
printer(['Compressed Size:', f'0x{self.CompSize:X}'], padd, False)
|
||||
|
||||
|
||||
def is_apple_pbzx(input_file):
|
||||
""" Check if input is Apple PBZX image """
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
|
||||
return bool(PAT_APPLE_PBZX.search(input_buffer[:0x4]))
|
||||
|
||||
# Parse & Extract Apple PBZX image
|
||||
|
||||
def apple_pbzx_extract(input_file, extract_path, padding=0):
|
||||
""" Parse & Extract Apple PBZX image """
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
cpio_bin = b'' # Initialize PBZX > CPIO Buffer
|
||||
cpio_len = 0x0 # Initialize PBZX > CPIO Length
|
||||
|
||||
chunk_off = 0xC # First PBZX Chunk starts at 0xC
|
||||
|
||||
cpio_bin = b'' # Initialize PBZX > CPIO Buffer
|
||||
|
||||
cpio_len = 0x0 # Initialize PBZX > CPIO Length
|
||||
|
||||
chunk_off = 0xC # First PBZX Chunk starts at 0xC
|
||||
while chunk_off < len(input_buffer):
|
||||
chunk_hdr = get_struct(input_buffer, chunk_off, PbzxChunk)
|
||||
|
||||
|
||||
printer(f'PBZX Chunk at 0x{chunk_off:08X}\n', padding)
|
||||
|
||||
|
||||
chunk_hdr.struct_print(padding + 4)
|
||||
|
||||
|
||||
# PBZX Chunk data starts after its Header
|
||||
comp_bgn = chunk_off + PBZX_CHUNK_HDR_LEN
|
||||
|
||||
|
||||
# To avoid a potential infinite loop, double-check Compressed Size
|
||||
comp_end = comp_bgn + max(chunk_hdr.CompSize, PBZX_CHUNK_HDR_LEN)
|
||||
|
||||
|
||||
comp_bin = input_buffer[comp_bgn:comp_end]
|
||||
|
||||
|
||||
try:
|
||||
# Attempt XZ decompression, if applicable to Chunk data
|
||||
cpio_bin += lzma.LZMADecompressor().decompress(comp_bin)
|
||||
|
||||
|
||||
printer('Successful LZMA decompression!', padding + 8)
|
||||
except Exception:
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
logging.debug('Error: Failed to LZMA decompress PBZX Chunk 0x%X: %s', chunk_off, error)
|
||||
|
||||
# Otherwise, Chunk data is not compressed
|
||||
cpio_bin += comp_bin
|
||||
|
||||
|
||||
# Final CPIO size should match the sum of all Chunks > Initial Size
|
||||
cpio_len += chunk_hdr.InitSize
|
||||
|
||||
|
||||
# Next Chunk starts at the end of current Chunk's data
|
||||
chunk_off = comp_end
|
||||
|
||||
|
||||
# Check that CPIO size is valid based on all Chunks > Initial Size
|
||||
if cpio_len != len(cpio_bin):
|
||||
printer('Error: Unexpected CPIO archive size!', padding)
|
||||
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
cpio_name = path_stem(input_file) if os.path.isfile(input_file) else 'Payload'
|
||||
|
||||
|
||||
cpio_path = os.path.join(extract_path, f'{cpio_name}.cpio')
|
||||
|
||||
|
||||
with open(cpio_path, 'wb') as cpio_object:
|
||||
cpio_object.write(cpio_bin)
|
||||
|
||||
|
||||
# Decompress PBZX > CPIO archive with 7-Zip
|
||||
if is_szip_supported(cpio_path, padding, args=['-tCPIO'], check=True):
|
||||
if szip_decompress(cpio_path, extract_path, 'CPIO', padding, args=['-tCPIO'], check=True) == 0:
|
||||
os.remove(cpio_path) # Successful extraction, delete PBZX > CPIO archive
|
||||
os.remove(cpio_path) # Successful extraction, delete PBZX > CPIO archive
|
||||
else:
|
||||
return 3
|
||||
else:
|
||||
return 2
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
# Get common ctypes Structure Sizes
|
||||
PBZX_CHUNK_HDR_LEN = ctypes.sizeof(PbzxChunk)
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_apple_pbzx, apple_pbzx_extract).run_utility()
|
||||
BIOSUtility(title=TITLE, check=is_apple_pbzx, main=apple_pbzx_extract).run_utility()
|
||||
|
|
105
Apple_EFI_PKG.py
105
Apple_EFI_PKG.py
|
@ -1,22 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Apple EFI PKG
|
||||
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 sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_szip import is_szip_supported, szip_decompress
|
||||
from common.path_ops import copy_file, del_dirs, get_path_files, make_dirs, path_name, path_parent, get_extract_path
|
||||
from common.path_ops import copy_file, del_dirs, get_extract_path, get_path_files, make_dirs, path_name, path_parent
|
||||
from common.patterns import PAT_APPLE_PKG
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
|
@ -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_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):
|
||||
""" Check if input is Apple EFI PKG package """
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
|
||||
return bool(PAT_APPLE_PKG.search(input_buffer[:0x4]))
|
||||
|
||||
# Split Apple EFI image (if applicable) and Rename
|
||||
|
||||
def efi_split_rename(in_file, out_path, padding=0):
|
||||
""" Split Apple EFI image (if applicable) and Rename """
|
||||
|
||||
exit_codes = []
|
||||
|
||||
|
||||
working_dir = get_extract_path(in_file)
|
||||
|
||||
|
||||
if is_apple_im4p(in_file):
|
||||
printer(f'Splitting IM4P via {is_apple_im4p.__module__}...', padding)
|
||||
|
||||
im4p_exit = apple_im4p_split(in_file, working_dir, padding + 4)
|
||||
|
||||
exit_codes.append(im4p_exit)
|
||||
else:
|
||||
make_dirs(working_dir, delete=True)
|
||||
|
||||
copy_file(in_file, working_dir, True)
|
||||
|
||||
|
||||
for efi_file in get_path_files(working_dir):
|
||||
if is_apple_efi(efi_file):
|
||||
printer(f'Renaming EFI via {is_apple_efi.__module__}...', padding)
|
||||
|
||||
name_exit = apple_efi_identify(efi_file, efi_file, padding + 4, True)
|
||||
|
||||
exit_codes.append(name_exit)
|
||||
|
||||
|
||||
for named_file in get_path_files(working_dir):
|
||||
copy_file(named_file, out_path, True)
|
||||
|
||||
|
||||
del_dirs(working_dir)
|
||||
|
||||
|
||||
return sum(exit_codes)
|
||||
|
||||
# Parse & Extract Apple EFI PKG packages
|
||||
|
||||
def apple_pkg_extract(input_file, extract_path, padding=0):
|
||||
""" Parse & Extract Apple EFI PKG packages """
|
||||
|
||||
if not os.path.isfile(input_file):
|
||||
printer('Error: Could not find input file path!', padding)
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
|
||||
xar_path = os.path.join(extract_path, 'xar')
|
||||
|
||||
|
||||
# Decompress PKG > XAR archive with 7-Zip
|
||||
if is_szip_supported(input_file, padding, args=['-tXAR'], check=True):
|
||||
if szip_decompress(input_file, xar_path, 'XAR', padding, args=['-tXAR'], check=True) != 0:
|
||||
return 3
|
||||
else:
|
||||
return 2
|
||||
|
||||
|
||||
for xar_file in get_path_files(xar_path):
|
||||
if path_name(xar_file) == 'Payload':
|
||||
pbzx_module = is_apple_pbzx.__module__
|
||||
|
||||
if is_apple_pbzx(xar_file):
|
||||
printer(f'Extracting PBZX via {pbzx_module}...', padding + 4)
|
||||
|
||||
pbzx_path = get_extract_path(xar_file)
|
||||
|
||||
if apple_pbzx_extract(xar_file, pbzx_path, padding + 8) == 0:
|
||||
printer(f'Succesfull PBZX extraction via {pbzx_module}!', padding + 4)
|
||||
|
||||
for pbzx_file in get_path_files(pbzx_path):
|
||||
if path_name(pbzx_file) == 'UpdateBundle.zip':
|
||||
if is_szip_supported(pbzx_file, padding + 8, args=['-tZIP'], check=True):
|
||||
zip_path = get_extract_path(pbzx_file)
|
||||
if szip_decompress(pbzx_file, zip_path, 'ZIP', padding + 8, args=['-tZIP'], check=True) == 0:
|
||||
|
||||
if szip_decompress(pbzx_file, zip_path, 'ZIP', padding + 8, args=['-tZIP'],
|
||||
check=True) == 0:
|
||||
for zip_file in get_path_files(zip_path):
|
||||
if path_name(path_parent(zip_file)) == 'MacEFI':
|
||||
printer(path_name(zip_file), padding + 12)
|
||||
|
||||
if efi_split_rename(zip_file, extract_path, padding + 16) != 0:
|
||||
printer(f'Error: Could not split and rename {path_name(zip_file)}!', padding)
|
||||
printer(f'Error: Could not split and rename {path_name(zip_file)}!',
|
||||
padding)
|
||||
|
||||
return 10
|
||||
else:
|
||||
return 9
|
||||
else:
|
||||
return 8
|
||||
break # ZIP found, stop
|
||||
|
||||
break # ZIP found, stop
|
||||
else:
|
||||
printer('Error: Could not find "UpdateBundle.zip" file!', padding)
|
||||
|
||||
return 7
|
||||
else:
|
||||
printer(f'Error: Failed to extract PBZX file via {pbzx_module}!', padding)
|
||||
|
||||
return 6
|
||||
else:
|
||||
printer(f'Error: Failed to detect file as PBZX via {pbzx_module}!', padding)
|
||||
|
||||
return 5
|
||||
|
||||
break # Payload found, stop searching
|
||||
|
||||
|
||||
break # Payload found, stop searching
|
||||
|
||||
if path_name(xar_file) == 'Scripts':
|
||||
if is_szip_supported(xar_file, padding + 4, args=['-tGZIP'], check=True):
|
||||
gzip_path = get_extract_path(xar_file)
|
||||
|
||||
if szip_decompress(xar_file, gzip_path, 'GZIP', padding + 4, args=['-tGZIP'], check=True) == 0:
|
||||
for gzip_file in get_path_files(gzip_path):
|
||||
if is_szip_supported(gzip_file, padding + 8, args=['-tCPIO'], check=True):
|
||||
cpio_path = get_extract_path(gzip_file)
|
||||
if szip_decompress(gzip_file, cpio_path, 'CPIO', padding + 8, args=['-tCPIO'], check=True) == 0:
|
||||
|
||||
if szip_decompress(gzip_file, cpio_path, 'CPIO', padding + 8, args=['-tCPIO'],
|
||||
check=True) == 0:
|
||||
for cpio_file in get_path_files(cpio_path):
|
||||
if path_name(path_parent(cpio_file)) == 'EFIPayloads':
|
||||
printer(path_name(cpio_file), padding + 12)
|
||||
|
||||
if efi_split_rename(cpio_file, extract_path, padding + 16) != 0:
|
||||
printer(f'Error: Could not split and rename {path_name(cpio_file)}!', padding)
|
||||
printer(f'Error: Could not split and rename {path_name(cpio_file)}!',
|
||||
padding)
|
||||
|
||||
return 15
|
||||
else:
|
||||
return 14
|
||||
|
@ -134,15 +161,17 @@ def apple_pkg_extract(input_file, extract_path, padding=0):
|
|||
return 12
|
||||
else:
|
||||
return 11
|
||||
|
||||
break # Scripts found, stop searching
|
||||
|
||||
break # Scripts found, stop searching
|
||||
else:
|
||||
printer('Error: Could not find "Payload" or "Scripts" file!', padding)
|
||||
|
||||
return 4
|
||||
|
||||
del_dirs(xar_path) # Delete temporary/working XAR folder
|
||||
|
||||
|
||||
del_dirs(xar_path) # Delete temporary/working XAR folder
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
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()
|
||||
|
|
|
@ -1,74 +1,84 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Award BIOS Extract
|
||||
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 sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
import stat
|
||||
|
||||
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.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
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):
|
||||
""" Check if input is Award BIOS image """
|
||||
|
||||
in_buffer = file_to_bytes(in_file)
|
||||
|
||||
|
||||
return bool(PAT_AWARD_LZH.search(in_buffer))
|
||||
|
||||
# Parse & Extract Award BIOS image
|
||||
|
||||
def award_bios_extract(input_file, extract_path, padding=0):
|
||||
""" Parse & Extract Award BIOS image """
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
|
||||
for lzh_match in PAT_AWARD_LZH.finditer(input_buffer):
|
||||
lzh_type = lzh_match.group(0).decode('utf-8')
|
||||
|
||||
lzh_text = f'LZH-{lzh_type.strip("-").upper()}'
|
||||
|
||||
|
||||
lzh_bgn = lzh_match.start()
|
||||
|
||||
|
||||
mod_bgn = lzh_bgn - 0x2
|
||||
hdr_len = input_buffer[mod_bgn]
|
||||
mod_len = int.from_bytes(input_buffer[mod_bgn + 0x7:mod_bgn + 0xB], 'little')
|
||||
mod_end = lzh_bgn + hdr_len + mod_len
|
||||
|
||||
mod_bin = input_buffer[mod_bgn:mod_end]
|
||||
|
||||
tag_bgn = mod_bgn + 0x16
|
||||
tag_end = tag_bgn + input_buffer[mod_bgn + 0x15]
|
||||
tag_txt = input_buffer[tag_bgn:tag_end].decode('utf-8','ignore')
|
||||
|
||||
|
||||
if len(mod_bin) != 0x2 + hdr_len + mod_len:
|
||||
printer(f'Error: Skipped incomplete LZH stream at 0x{mod_bgn:X}!', padding, False)
|
||||
|
||||
continue
|
||||
|
||||
tag_txt = safe_name(mod_bin[0x16:0x16 + mod_bin[0x15]].decode('utf-8', 'ignore').strip())
|
||||
|
||||
printer(f'{lzh_text} > {tag_txt} [0x{mod_bgn:06X}-0x{mod_end:06X}]', padding)
|
||||
|
||||
mod_path = os.path.join(extract_path, safe_name(tag_txt))
|
||||
|
||||
mod_path = os.path.join(extract_path, tag_txt)
|
||||
|
||||
lzh_path = f'{mod_path}.lzh'
|
||||
|
||||
|
||||
with open(lzh_path, 'wb') as lzh_file:
|
||||
lzh_file.write(mod_bin) # Store LZH archive
|
||||
|
||||
lzh_file.write(mod_bin) # Store LZH archive
|
||||
|
||||
# 7-Zip returns critical exit code (i.e. 2) if LZH CRC is wrong, do not check result
|
||||
szip_decompress(lzh_path, extract_path, lzh_text, padding + 4, check=False)
|
||||
|
||||
|
||||
# Manually check if 7-Zip extracted LZH due to its CRC check issue
|
||||
if os.path.isfile(mod_path):
|
||||
os.remove(lzh_path) # Successful extraction, delete LZH archive
|
||||
|
||||
os.chmod(lzh_path, stat.S_IWRITE)
|
||||
|
||||
os.remove(lzh_path) # Successful extraction, delete LZH archive
|
||||
|
||||
# Extract any nested LZH archives
|
||||
if is_award_bios(mod_path):
|
||||
# Recursively extract nested Award BIOS modules
|
||||
award_bios_extract(mod_path, get_extract_path(mod_path), padding + 8)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_award_bios, award_bios_extract).run_utility()
|
||||
BIOSUtility(title=TITLE, check=is_award_bios, main=award_bios_extract).run_utility()
|
||||
|
|
1207
Dell_PFS_Extract.py
1207
Dell_PFS_Extract.py
File diff suppressed because it is too large
Load diff
|
@ -1,19 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Fujitsu SFX 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 sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_szip import is_szip_supported, szip_decompress
|
||||
from common.path_ops import make_dirs
|
||||
|
@ -22,68 +16,78 @@ from common.system import printer
|
|||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is Fujitsu SFX image
|
||||
TITLE = 'Fujitsu SFX BIOS Extractor v4.0'
|
||||
|
||||
|
||||
def is_fujitsu_sfx(in_file):
|
||||
""" Check if input is Fujitsu SFX image """
|
||||
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
|
||||
return bool(PAT_FUJITSU_SFX.search(buffer))
|
||||
|
||||
# Extract Fujitsu SFX image
|
||||
|
||||
def fujitsu_cabinet(in_file, extract_path, padding=0):
|
||||
""" Extract Fujitsu SFX image """
|
||||
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
match_cab = PAT_FUJITSU_SFX.search(buffer) # Microsoft CAB Header XOR 0xFF
|
||||
|
||||
|
||||
match_cab = PAT_FUJITSU_SFX.search(buffer) # Microsoft CAB Header XOR 0xFF
|
||||
|
||||
if not match_cab:
|
||||
return 1
|
||||
|
||||
|
||||
printer('Detected obfuscated CAB archive!', padding)
|
||||
|
||||
|
||||
# Microsoft CAB Header XOR 0xFF starts after "FjSfxBinay" signature
|
||||
cab_start = match_cab.start() + 0xA
|
||||
|
||||
|
||||
# Determine the Microsoft CAB image size
|
||||
cab_size = int.from_bytes(buffer[cab_start + 0x8:cab_start + 0xC], 'little') # Get LE XOR-ed CAB size
|
||||
xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB size XOR value
|
||||
cab_size ^= xor_size # Perform XOR 0xFF and get actual CAB size
|
||||
cab_size = int.from_bytes(buffer[cab_start + 0x8:cab_start + 0xC], 'little') # Get LE XOR CAB size
|
||||
xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB size XOR value
|
||||
cab_size ^= xor_size # Perform XOR 0xFF and get actual CAB size
|
||||
|
||||
printer('Removing obfuscation...', padding + 4)
|
||||
|
||||
|
||||
# Determine the Microsoft CAB image Data
|
||||
cab_data = int.from_bytes(buffer[cab_start:cab_start + cab_size], 'big') # Get BE XOR-ed CAB data
|
||||
xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB data XOR value
|
||||
cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB data
|
||||
|
||||
cab_data = int.from_bytes(buffer[cab_start:cab_start + cab_size], 'big') # Get BE XOR- CAB data
|
||||
xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB data XOR value
|
||||
cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB data
|
||||
|
||||
printer('Extracting archive...', padding + 4)
|
||||
|
||||
|
||||
cab_path = os.path.join(extract_path, 'FjSfxBinay.cab')
|
||||
|
||||
|
||||
with open(cab_path, 'wb') as cab_file:
|
||||
cab_file.write(cab_data) # Create temporary CAB archive
|
||||
|
||||
cab_file.write(cab_data) # Create temporary CAB archive
|
||||
|
||||
if is_szip_supported(cab_path, padding + 8, check=True):
|
||||
if szip_decompress(cab_path, extract_path, 'CAB', padding + 8, check=True) == 0:
|
||||
os.remove(cab_path) # Successful extraction, delete temporary CAB archive
|
||||
if szip_decompress(cab_path, extract_path, 'FjSfxBinay CAB', padding + 8, check=True) == 0:
|
||||
os.remove(cab_path) # Successful extraction, delete temporary CAB archive
|
||||
else:
|
||||
return 3
|
||||
else:
|
||||
return 2
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
# Parse & Extract Fujitsu SFX image
|
||||
|
||||
def fujitsu_sfx_extract(in_file, extract_path, padding=0):
|
||||
""" Parse & Extract Fujitsu SFX image """
|
||||
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
|
||||
if fujitsu_cabinet(buffer, extract_path, padding) == 0:
|
||||
printer('Successfully Extracted!', padding)
|
||||
else:
|
||||
printer('Error: Failed to Extract image!', padding)
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_fujitsu_sfx, fujitsu_sfx_extract).run_utility()
|
||||
BIOSUtility(title=TITLE, check=is_fujitsu_sfx, main=fujitsu_sfx_extract).run_utility()
|
||||
|
|
|
@ -1,44 +1,47 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Fujitsu UPC Extract
|
||||
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 sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_efi import efi_decompress, is_efi_compressed
|
||||
from common.path_ops import make_dirs, path_suffixes
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is Fujitsu UPC image
|
||||
TITLE = 'Fujitsu UPC BIOS Extractor v3.0'
|
||||
|
||||
|
||||
def is_fujitsu_upc(in_file):
|
||||
""" Check if input is Fujitsu UPC image """
|
||||
|
||||
in_buffer = file_to_bytes(in_file)
|
||||
|
||||
|
||||
is_ext = path_suffixes(in_file)[-1].upper() == '.UPC' if os.path.isfile(in_file) else True
|
||||
|
||||
|
||||
is_efi = is_efi_compressed(in_buffer)
|
||||
|
||||
|
||||
return is_ext and is_efi
|
||||
|
||||
# Parse & Extract Fujitsu UPC image
|
||||
|
||||
def fujitsu_upc_extract(input_file, extract_path, padding=0):
|
||||
""" Parse & Extract Fujitsu UPC image """
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
|
||||
image_base = os.path.basename(input_file)
|
||||
|
||||
image_name = image_base[:-4] if image_base.upper().endswith('.UPC') else image_base
|
||||
|
||||
image_path = os.path.join(extract_path, f'{image_name}.bin')
|
||||
|
||||
|
||||
return efi_decompress(input_file, image_path, padding)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_fujitsu_upc, fujitsu_upc_extract).run_utility()
|
||||
BIOSUtility(title=TITLE, check=is_fujitsu_upc, main=fujitsu_upc_extract).run_utility()
|
||||
|
|
|
@ -1,217 +1,234 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Insyde IFD Extract
|
||||
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
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
import os
|
||||
|
||||
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.struct_ops import char, get_struct, uint32_t
|
||||
from common.struct_ops import Char, get_struct, UInt32
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
TITLE = 'Insyde iFlash/iFdPacker Extractor v3.0'
|
||||
|
||||
|
||||
class IflashHeader(ctypes.LittleEndianStructure):
|
||||
""" Insyde iFlash Header """
|
||||
|
||||
_pack_ = 1
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
_fields_ = [
|
||||
('Signature', char*8), # 0x00 $_IFLASH
|
||||
('ImageTag', char*8), # 0x08
|
||||
('TotalSize', uint32_t), # 0x10 from header end
|
||||
('ImageSize', uint32_t), # 0x14 from header end
|
||||
('Signature', Char * 8), # 0x00 $_IFLASH
|
||||
('ImageTag', Char * 8), # 0x08
|
||||
('TotalSize', UInt32), # 0x10 from header end
|
||||
('ImageSize', UInt32), # 0x14 from header end
|
||||
# 0x18
|
||||
]
|
||||
|
||||
def _get_padd_len(self):
|
||||
return self.TotalSize - self.ImageSize
|
||||
|
||||
def get_image_tag(self):
|
||||
return self.ImageTag.decode('utf-8','ignore').strip('_')
|
||||
|
||||
def struct_print(self, p):
|
||||
printer(['Signature :', self.Signature.decode('utf-8')], p, False)
|
||||
printer(['Image Name:', self.get_image_tag()], p, False)
|
||||
printer(['Image Size:', f'0x{self.ImageSize:X}'], p, False)
|
||||
printer(['Total Size:', f'0x{self.TotalSize:X}'], p, False)
|
||||
printer(['Padd Size :', f'0x{self._get_padd_len():X}'], p, False)
|
||||
|
||||
# Check if input is Insyde iFlash/iFdPacker Update image
|
||||
def is_insyde_ifd(input_file):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
is_ifl = bool(insyde_iflash_detect(input_buffer))
|
||||
|
||||
is_sfx = bool(PAT_INSYDE_SFX.search(input_buffer))
|
||||
|
||||
def _get_padd_len(self) -> int:
|
||||
return self.TotalSize - self.ImageSize
|
||||
|
||||
def get_image_tag(self) -> str:
|
||||
""" Get Insyde iFlash image tag """
|
||||
|
||||
return self.ImageTag.decode('utf-8', 'ignore').strip('_')
|
||||
|
||||
def struct_print(self, padd: int) -> None:
|
||||
""" Display structure information """
|
||||
|
||||
printer(['Signature :', self.Signature.decode('utf-8')], padd, False)
|
||||
printer(['Image Name:', self.get_image_tag()], padd, False)
|
||||
printer(['Image Size:', f'0x{self.ImageSize:X}'], padd, False)
|
||||
printer(['Total Size:', f'0x{self.TotalSize:X}'], padd, False)
|
||||
printer(['Padd Size :', f'0x{self._get_padd_len():X}'], padd, False)
|
||||
|
||||
|
||||
def is_insyde_ifd(input_object: str | bytes | bytearray) -> bool:
|
||||
""" Check if input is Insyde iFlash/iFdPacker Update image """
|
||||
|
||||
input_buffer: bytes = file_to_bytes(input_object)
|
||||
|
||||
is_ifl: bool = bool(insyde_iflash_detect(input_buffer))
|
||||
|
||||
is_sfx: bool = bool(PAT_INSYDE_SFX.search(input_buffer))
|
||||
|
||||
return is_ifl or is_sfx
|
||||
|
||||
# Parse & Extract Insyde iFlash/iFdPacker Update images
|
||||
def insyde_ifd_extract(input_file, extract_path, padding=0):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
iflash_code = insyde_iflash_extract(input_buffer, extract_path, padding)
|
||||
|
||||
ifdpack_path = os.path.join(extract_path, 'Insyde iFdPacker SFX')
|
||||
|
||||
ifdpack_code = insyde_packer_extract(input_buffer, ifdpack_path, padding)
|
||||
|
||||
|
||||
def insyde_ifd_extract(input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int:
|
||||
""" Parse & Extract Insyde iFlash/iFdPacker Update images """
|
||||
|
||||
input_buffer: bytes = file_to_bytes(input_object)
|
||||
|
||||
iflash_code: int = insyde_iflash_extract(input_buffer, extract_path, padding)
|
||||
|
||||
ifdpack_path: str = os.path.join(extract_path, 'Insyde iFdPacker SFX')
|
||||
|
||||
ifdpack_code: int = insyde_packer_extract(input_buffer, ifdpack_path, padding)
|
||||
|
||||
return iflash_code and ifdpack_code
|
||||
|
||||
# Detect Insyde iFlash Update image
|
||||
def insyde_iflash_detect(input_buffer):
|
||||
iflash_match_all = []
|
||||
iflash_match_nan = [0x0,0xFFFFFFFF]
|
||||
|
||||
|
||||
def insyde_iflash_detect(input_buffer: bytes) -> list:
|
||||
""" Detect Insyde iFlash Update image """
|
||||
|
||||
iflash_match_all: list = []
|
||||
iflash_match_nan: list = [0x0, 0xFFFFFFFF]
|
||||
|
||||
for iflash_match in PAT_INSYDE_IFL.finditer(input_buffer):
|
||||
ifl_bgn = iflash_match.start()
|
||||
|
||||
ifl_bgn: int = iflash_match.start()
|
||||
|
||||
if len(input_buffer[ifl_bgn:]) <= INS_IFL_LEN:
|
||||
continue
|
||||
|
||||
|
||||
ifl_hdr = get_struct(input_buffer, ifl_bgn, IflashHeader)
|
||||
|
||||
|
||||
if ifl_hdr.TotalSize in iflash_match_nan \
|
||||
or ifl_hdr.ImageSize in iflash_match_nan \
|
||||
or ifl_hdr.TotalSize < ifl_hdr.ImageSize \
|
||||
or ifl_bgn + INS_IFL_LEN + ifl_hdr.TotalSize > len(input_buffer):
|
||||
or ifl_hdr.ImageSize in iflash_match_nan \
|
||||
or ifl_hdr.TotalSize < ifl_hdr.ImageSize \
|
||||
or ifl_bgn + INS_IFL_LEN + ifl_hdr.TotalSize > len(input_buffer):
|
||||
continue
|
||||
|
||||
|
||||
iflash_match_all.append([ifl_bgn, ifl_hdr])
|
||||
|
||||
|
||||
return iflash_match_all
|
||||
|
||||
# Extract Insyde iFlash Update image
|
||||
def insyde_iflash_extract(input_buffer, extract_path, padding=0):
|
||||
insyde_iflash_all = insyde_iflash_detect(input_buffer)
|
||||
|
||||
|
||||
def insyde_iflash_extract(input_buffer: bytes, extract_path: str, padding: int = 0) -> int:
|
||||
""" Extract Insyde iFlash Update image """
|
||||
|
||||
insyde_iflash_all: list = insyde_iflash_detect(input_buffer)
|
||||
|
||||
if not insyde_iflash_all:
|
||||
return 127
|
||||
|
||||
|
||||
printer('Detected Insyde iFlash Update image!', padding)
|
||||
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
exit_codes = []
|
||||
|
||||
|
||||
exit_codes: list = []
|
||||
|
||||
for insyde_iflash in insyde_iflash_all:
|
||||
exit_code = 0
|
||||
|
||||
ifl_bgn,ifl_hdr = insyde_iflash
|
||||
|
||||
img_bgn = ifl_bgn + INS_IFL_LEN
|
||||
img_end = img_bgn + ifl_hdr.ImageSize
|
||||
img_bin = input_buffer[img_bgn:img_end]
|
||||
|
||||
exit_code: int = 0
|
||||
|
||||
ifl_bgn, ifl_hdr = insyde_iflash
|
||||
|
||||
img_bgn: int = ifl_bgn + INS_IFL_LEN
|
||||
img_end: int = img_bgn + ifl_hdr.ImageSize
|
||||
img_bin: bytes = input_buffer[img_bgn:img_end]
|
||||
|
||||
if len(img_bin) != ifl_hdr.ImageSize:
|
||||
exit_code = 1
|
||||
|
||||
img_val = [ifl_hdr.get_image_tag(), 'bin']
|
||||
img_tag,img_ext = INS_IFL_IMG.get(img_val[0], img_val)
|
||||
|
||||
img_name = f'{img_tag} [0x{img_bgn:08X}-0x{img_end:08X}]'
|
||||
|
||||
|
||||
img_val: list = [ifl_hdr.get_image_tag(), 'bin']
|
||||
img_tag, img_ext = INS_IFL_IMG.get(img_val[0], img_val)
|
||||
|
||||
img_name: str = f'{img_tag} [0x{img_bgn:08X}-0x{img_end:08X}]'
|
||||
|
||||
printer(f'{img_name}\n', padding + 4)
|
||||
|
||||
|
||||
ifl_hdr.struct_print(padding + 8)
|
||||
|
||||
if img_val == [img_tag,img_ext]:
|
||||
|
||||
if img_val == [img_tag, img_ext]:
|
||||
printer(f'Note: Detected new Insyde iFlash tag {img_tag}!', padding + 12, pause=True)
|
||||
|
||||
out_name = f'{img_name}.{img_ext}'
|
||||
|
||||
out_path = os.path.join(extract_path, safe_name(out_name))
|
||||
|
||||
|
||||
out_name: str = f'{img_name}.{img_ext}'
|
||||
|
||||
out_path: str = os.path.join(extract_path, safe_name(out_name))
|
||||
|
||||
with open(out_path, 'wb') as out_image:
|
||||
out_image.write(img_bin)
|
||||
|
||||
|
||||
printer(f'Succesfull Insyde iFlash > {img_tag} extraction!', padding + 12)
|
||||
|
||||
|
||||
exit_codes.append(exit_code)
|
||||
|
||||
|
||||
return sum(exit_codes)
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
if not match_sfx:
|
||||
return 127
|
||||
|
||||
|
||||
printer('Detected Insyde iFdPacker Update image!', padding)
|
||||
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
sfx_buffer = bytearray(input_buffer[match_sfx.end() - 0x5:])
|
||||
|
||||
|
||||
sfx_buffer: bytearray = bytearray(input_buffer[match_sfx.end() - 0x5:])
|
||||
|
||||
if sfx_buffer[:0x5] == b'\x6E\xF4\x79\x5F\x4E':
|
||||
printer('Detected Insyde iFdPacker > 7-Zip SFX > Obfuscation!', padding + 4)
|
||||
|
||||
for index,byte in enumerate(sfx_buffer):
|
||||
|
||||
for index, byte in enumerate(sfx_buffer):
|
||||
sfx_buffer[index] = byte // 2 + (128 if byte % 2 else 0)
|
||||
|
||||
|
||||
printer('Removed Insyde iFdPacker > 7-Zip SFX > Obfuscation!', padding + 8)
|
||||
|
||||
|
||||
printer('Extracting Insyde iFdPacker > 7-Zip SFX archive...', padding + 4)
|
||||
|
||||
|
||||
if bytes(INS_SFX_PWD, 'utf-16le') in input_buffer[:match_sfx.start()]:
|
||||
printer('Detected Insyde iFdPacker > 7-Zip SFX > Password!', padding + 8)
|
||||
|
||||
printer(INS_SFX_PWD, padding + 12)
|
||||
|
||||
sfx_path = os.path.join(extract_path, 'Insyde_iFdPacker_SFX.7z')
|
||||
|
||||
|
||||
sfx_path: str = os.path.join(extract_path, 'Insyde_iFdPacker_SFX.7z')
|
||||
|
||||
with open(sfx_path, 'wb') as sfx_file:
|
||||
sfx_file.write(sfx_buffer)
|
||||
|
||||
|
||||
if is_szip_supported(sfx_path, padding + 8, args=[f'-p{INS_SFX_PWD}'], check=True):
|
||||
if szip_decompress(sfx_path, extract_path, 'Insyde iFdPacker > 7-Zip SFX',
|
||||
padding + 8, args=[f'-p{INS_SFX_PWD}'], check=True) == 0:
|
||||
padding + 8, args=[f'-p{INS_SFX_PWD}'], check=True) == 0:
|
||||
os.remove(sfx_path)
|
||||
else:
|
||||
return 125
|
||||
else:
|
||||
return 126
|
||||
|
||||
|
||||
exit_codes = []
|
||||
|
||||
|
||||
for sfx_file in get_path_files(extract_path):
|
||||
if is_insyde_ifd(sfx_file):
|
||||
printer(f'{os.path.basename(sfx_file)}', padding + 12)
|
||||
|
||||
ifd_code = insyde_ifd_extract(sfx_file, get_extract_path(sfx_file), padding + 16)
|
||||
|
||||
|
||||
ifd_code: int = insyde_ifd_extract(sfx_file, get_extract_path(sfx_file), padding + 16)
|
||||
|
||||
exit_codes.append(ifd_code)
|
||||
|
||||
|
||||
return sum(exit_codes)
|
||||
|
||||
|
||||
# Insyde iFdPacker known 7-Zip SFX Password
|
||||
INS_SFX_PWD = '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
|
||||
INS_IFL_IMG = {
|
||||
'BIOSCER' : ['Certificate', 'bin'],
|
||||
'BIOSCR2' : ['Certificate 2nd', 'bin'],
|
||||
'BIOSIMG' : ['BIOS-UEFI', 'bin'],
|
||||
'DRV_IMG' : ['isflash', 'efi'],
|
||||
'EC_IMG' : ['Embedded Controller', 'bin'],
|
||||
'INI_IMG' : ['platform', 'ini'],
|
||||
'ME_IMG' : ['Management Engine', 'bin'],
|
||||
'OEM_ID' : ['OEM Identifier', 'bin'],
|
||||
}
|
||||
INS_IFL_IMG: dict = {
|
||||
'BIOSCER': ['Certificate', 'bin'],
|
||||
'BIOSCR2': ['Certificate 2nd', 'bin'],
|
||||
'BIOSIMG': ['BIOS-UEFI', 'bin'],
|
||||
'DRV_IMG': ['isflash', 'efi'],
|
||||
'EC_IMG': ['Embedded Controller', 'bin'],
|
||||
'INI_IMG': ['platform', 'ini'],
|
||||
'ME_IMG': ['Management Engine', 'bin'],
|
||||
'OEM_ID': ['OEM Identifier', 'bin'],
|
||||
}
|
||||
|
||||
# Get common ctypes Structure Sizes
|
||||
INS_IFL_LEN = ctypes.sizeof(IflashHeader)
|
||||
INS_IFL_LEN: int = ctypes.sizeof(IflashHeader)
|
||||
|
||||
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()
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -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:
|
||||
|
||||
|
|
|
@ -1,22 +1,19 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Panasonic BIOS Extract
|
||||
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 sys
|
||||
import lznt1
|
||||
import logging
|
||||
import os
|
||||
|
||||
import pefile
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
from dissect.util.compression import lznt1
|
||||
|
||||
from common.comp_szip import is_szip_supported, szip_decompress
|
||||
from common.path_ops import get_path_files, make_dirs, path_stem, safe_name
|
||||
|
@ -28,182 +25,216 @@ from common.text_ops import file_to_bytes
|
|||
|
||||
from AMI_PFAT_Extract import is_ami_pfat, parse_pfat_file
|
||||
|
||||
# Check if input is Panasonic BIOS Package PE
|
||||
TITLE = 'Panasonic BIOS Package Extractor v3.0'
|
||||
|
||||
|
||||
def is_panasonic_pkg(in_file):
|
||||
""" Check if input is Panasonic BIOS Package PE """
|
||||
|
||||
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:
|
||||
return False
|
||||
|
||||
pe_info = get_pe_info(pe_file)
|
||||
|
||||
|
||||
pe_info = get_pe_info(pe_file, silent=True)
|
||||
|
||||
if not pe_info:
|
||||
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
|
||||
|
||||
|
||||
if not PAT_MICROSOFT_CAB.search(in_buffer):
|
||||
return False
|
||||
|
||||
|
||||
return True
|
||||
|
||||
# Search and Extract Panasonic BIOS Package PE CAB archive
|
||||
|
||||
def panasonic_cab_extract(buffer, extract_path, padding=0):
|
||||
pe_path,pe_file,pe_info = [None] * 3
|
||||
|
||||
""" 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_len = int.from_bytes(buffer[cab_bgn + 0x8:cab_bgn + 0xC], 'little')
|
||||
cab_end = cab_bgn + cab_len
|
||||
|
||||
cab_bin = buffer[cab_bgn:cab_end]
|
||||
|
||||
cab_tag = f'[0x{cab_bgn:06X}-0x{cab_end:06X}]'
|
||||
|
||||
|
||||
cab_path = os.path.join(extract_path, f'CAB_{cab_tag}.cab')
|
||||
|
||||
|
||||
with open(cab_path, 'wb') as cab_file:
|
||||
cab_file.write(cab_bin) # Store CAB archive
|
||||
|
||||
cab_file.write(cab_bin) # Store CAB archive
|
||||
|
||||
if is_szip_supported(cab_path, padding, check=True):
|
||||
printer(f'Panasonic BIOS Package > PE > CAB {cab_tag}', padding)
|
||||
|
||||
|
||||
if szip_decompress(cab_path, extract_path, 'CAB', padding + 4, check=True) == 0:
|
||||
os.remove(cab_path) # Successful extraction, delete CAB archive
|
||||
os.remove(cab_path) # Successful extraction, delete CAB archive
|
||||
else:
|
||||
return pe_path, pe_file, pe_info
|
||||
else:
|
||||
return pe_path, pe_file, pe_info
|
||||
|
||||
|
||||
for file_path in get_path_files(extract_path):
|
||||
pe_file = get_pe_file(file_path, fast=True)
|
||||
pe_file = get_pe_file(file_path, padding, silent=True)
|
||||
|
||||
if pe_file:
|
||||
pe_info = get_pe_info(pe_file)
|
||||
if pe_info.get(b'FileDescription',b'').upper() == b'BIOS UPDATE':
|
||||
pe_info = get_pe_info(pe_file, padding, silent=True)
|
||||
|
||||
if pe_info.get(b'FileDescription', b'').upper() == b'BIOS UPDATE':
|
||||
pe_path = file_path
|
||||
|
||||
break
|
||||
else:
|
||||
return pe_path, pe_file, pe_info
|
||||
|
||||
|
||||
return pe_path, pe_file, pe_info
|
||||
|
||||
# Extract & Decompress Panasonic BIOS Update PE RCDATA (LZNT1)
|
||||
|
||||
def panasonic_res_extract(pe_name, pe_file, extract_path, padding=0):
|
||||
""" Extract & Decompress Panasonic BIOS Update PE RCDATA (LZNT1) """
|
||||
|
||||
is_rcdata = False
|
||||
|
||||
|
||||
# When fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed prior to RCDATA Directories
|
||||
pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']])
|
||||
|
||||
|
||||
# Parse all Resource Data Directories > RCDATA (ID = 10)
|
||||
for entry in pe_file.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if entry.struct.name == 'IMAGE_RESOURCE_DIRECTORY_ENTRY' and entry.struct.Id == 0xA:
|
||||
is_rcdata = True
|
||||
|
||||
for resource in entry.directory.entries:
|
||||
res_bgn = resource.directory.entries[0].data.struct.OffsetToData
|
||||
res_len = resource.directory.entries[0].data.struct.Size
|
||||
res_end = res_bgn + res_len
|
||||
|
||||
res_bin = pe_file.get_data(res_bgn, res_len)
|
||||
|
||||
res_tag = f'{pe_name} [0x{res_bgn:06X}-0x{res_end:06X}]'
|
||||
|
||||
res_out = os.path.join(extract_path, f'{res_tag}')
|
||||
|
||||
|
||||
printer(res_tag, padding + 4)
|
||||
|
||||
|
||||
try:
|
||||
res_raw = lznt1.decompress(res_bin[0x8:])
|
||||
|
||||
printer('Succesfull LZNT1 decompression via lznt1!', padding + 8)
|
||||
except Exception:
|
||||
|
||||
if len(res_raw) != int.from_bytes(res_bin[0x4:0x8], 'little'):
|
||||
raise ValueError('LZNT1_DECOMPRESS_BAD_SIZE')
|
||||
|
||||
printer('Succesfull LZNT1 decompression via Dissect!', padding + 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
|
||||
|
||||
|
||||
printer('Succesfull PE Resource extraction!', padding + 8)
|
||||
|
||||
|
||||
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
|
||||
if is_ami_pfat(res_raw):
|
||||
pfat_dir = os.path.join(extract_path, res_tag)
|
||||
|
||||
|
||||
parse_pfat_file(res_raw, pfat_dir, padding + 12)
|
||||
else:
|
||||
if is_pe_file(res_raw):
|
||||
res_ext = 'exe'
|
||||
elif res_raw.startswith(b'[') and res_raw.endswith((b'\x0D\x0A',b'\x0A')):
|
||||
elif res_raw.startswith(b'[') and res_raw.endswith((b'\x0D\x0A', b'\x0A')):
|
||||
res_ext = 'txt'
|
||||
else:
|
||||
res_ext = 'bin'
|
||||
|
||||
|
||||
if res_ext == 'txt':
|
||||
printer(new_line=False)
|
||||
|
||||
for line in io.BytesIO(res_raw).readlines():
|
||||
line_text = line.decode('utf-8','ignore').rstrip()
|
||||
line_text = line.decode('utf-8', 'ignore').rstrip()
|
||||
|
||||
printer(line_text, padding + 12, new_line=False)
|
||||
|
||||
|
||||
with open(f'{res_out}.{res_ext}', 'wb') as out_file:
|
||||
out_file.write(res_raw)
|
||||
|
||||
|
||||
return is_rcdata
|
||||
|
||||
# Extract Panasonic BIOS Update PE Data when RCDATA is not available
|
||||
|
||||
def panasonic_img_extract(pe_name, pe_path, pe_file, extract_path, padding=0):
|
||||
""" Extract Panasonic BIOS Update PE Data when RCDATA is not available """
|
||||
|
||||
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_end = sec_bgn or len(pe_data)
|
||||
|
||||
img_bin = pe_data[img_bgn:img_end]
|
||||
|
||||
img_tag = f'{pe_name} [0x{img_bgn:X}-0x{img_end:X}]'
|
||||
|
||||
img_out = os.path.join(extract_path, f'{img_tag}.bin')
|
||||
|
||||
|
||||
printer(img_tag, padding + 4)
|
||||
|
||||
|
||||
with open(img_out, 'wb') as out_img:
|
||||
out_img.write(img_bin)
|
||||
|
||||
|
||||
printer('Succesfull PE Data extraction!', padding + 8)
|
||||
|
||||
|
||||
return bool(img_bin)
|
||||
|
||||
# Parse & Extract Panasonic BIOS Package PE
|
||||
|
||||
def panasonic_pkg_extract(input_file, extract_path, padding=0):
|
||||
""" Parse & Extract Panasonic BIOS Package PE """
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
|
||||
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:
|
||||
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:
|
||||
return 3
|
||||
|
||||
|
||||
pkg_pe_name = path_stem(input_file)
|
||||
|
||||
|
||||
printer(f'Panasonic BIOS Package > PE ({pkg_pe_name})\n', padding)
|
||||
|
||||
|
||||
show_pe_info(pkg_pe_info, padding + 4)
|
||||
|
||||
upd_pe_path,upd_pe_file,upd_pe_info = panasonic_cab_extract(input_buffer, extract_path, padding + 4)
|
||||
|
||||
|
||||
upd_pe_path, upd_pe_file, upd_pe_info = panasonic_cab_extract(input_buffer, extract_path, padding + 4)
|
||||
|
||||
if not (upd_pe_path and upd_pe_file and upd_pe_info):
|
||||
return 4
|
||||
|
||||
|
||||
upd_pe_name = safe_name(path_stem(upd_pe_path))
|
||||
|
||||
|
||||
printer(f'Panasonic BIOS Update > PE ({upd_pe_name})\n', padding + 12)
|
||||
|
||||
|
||||
show_pe_info(upd_pe_info, padding + 16)
|
||||
|
||||
|
||||
is_upd_res, is_upd_img = False, False
|
||||
|
||||
|
||||
is_upd_res = panasonic_res_extract(upd_pe_name, upd_pe_file, extract_path, padding + 16)
|
||||
|
||||
|
||||
if not is_upd_res:
|
||||
is_upd_img = panasonic_img_extract(upd_pe_name, upd_pe_path, upd_pe_file, extract_path, padding + 16)
|
||||
|
||||
os.remove(upd_pe_path)
|
||||
|
||||
|
||||
return 0 if is_upd_res or is_upd_img else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_panasonic_pkg, panasonic_pkg_extract).run_utility()
|
||||
BIOSUtility(title=TITLE, check=is_panasonic_pkg, main=panasonic_pkg_extract).run_utility()
|
||||
|
|
|
@ -1,237 +1,270 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Phoenix TDK Extract
|
||||
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
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
import logging
|
||||
import lzma
|
||||
import os
|
||||
|
||||
from common.path_ops import make_dirs, safe_name
|
||||
from common.pe_ops import get_pe_file, get_pe_info
|
||||
from common.patterns import PAT_MICROSOFT_MZ, PAT_MICROSOFT_PE, PAT_PHOENIX_TDK
|
||||
from common.struct_ops import char, get_struct, uint32_t
|
||||
from common.struct_ops import Char, get_struct, UInt32
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
TITLE = 'Phoenix TDK Packer Extractor v3.0'
|
||||
|
||||
|
||||
class PhoenixTdkHeader(ctypes.LittleEndianStructure):
|
||||
""" Phoenix TDK Header """
|
||||
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Tag', char*8), # 0x00
|
||||
('Size', uint32_t), # 0x08
|
||||
('Count', uint32_t), # 0x0C
|
||||
('Tag', Char * 8), # 0x00
|
||||
('Size', UInt32), # 0x08
|
||||
('Count', UInt32), # 0x0C
|
||||
# 0x10
|
||||
]
|
||||
|
||||
|
||||
def _get_tag(self):
|
||||
return self.Tag.decode('utf-8','ignore').strip()
|
||||
|
||||
def struct_print(self, p):
|
||||
printer(['Tag :', self._get_tag()], p, False)
|
||||
printer(['Size :', f'0x{self.Size:X}'], p, False)
|
||||
printer(['Entries:', self.Count], p, False)
|
||||
return self.Tag.decode('utf-8', 'ignore').strip()
|
||||
|
||||
def struct_print(self, padd):
|
||||
""" Display structure information """
|
||||
|
||||
printer(['Tag :', self._get_tag()], padd, False)
|
||||
printer(['Size :', f'0x{self.Size:X}'], padd, False)
|
||||
printer(['Entries:', self.Count], padd, False)
|
||||
|
||||
|
||||
class PhoenixTdkEntry(ctypes.LittleEndianStructure):
|
||||
""" Phoenix TDK Entry """
|
||||
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Name', char*256), # 0x000
|
||||
('Offset', uint32_t), # 0x100
|
||||
('Size', uint32_t), # 0x104
|
||||
('Compressed', uint32_t), # 0x108
|
||||
('Reserved', uint32_t), # 0x10C
|
||||
('Name', Char * 256), # 0x000
|
||||
('Offset', UInt32), # 0x100
|
||||
('Size', UInt32), # 0x104
|
||||
('Compressed', UInt32), # 0x108
|
||||
('Reserved', UInt32), # 0x10C
|
||||
# 0x110
|
||||
]
|
||||
|
||||
|
||||
COMP = {0: 'None', 1: 'LZMA'}
|
||||
|
||||
|
||||
def __init__(self, mz_base, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.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):
|
||||
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
|
||||
mz_all = [mz for mz in PAT_MICROSOFT_MZ.finditer(in_buffer) if mz.start() < pack_off]
|
||||
|
||||
|
||||
# Phoenix TDK Header structure is an index table for all TDK files
|
||||
# Each TDK file is referenced from the TDK Packer executable base
|
||||
# The TDK Header is always at the end of the TDK Packer executable
|
||||
# Thus, prefer the TDK Packer executable (MZ) closest to TDK Header
|
||||
# For speed, check MZ closest to (or at) 0x0 first (expected input)
|
||||
mz_ord = [mz_all[0]] + list(reversed(mz_all[1:]))
|
||||
|
||||
|
||||
# Parse each detected MZ
|
||||
for mz in mz_ord:
|
||||
mz_off = mz.start()
|
||||
|
||||
# MZ (DOS) > PE (NT) image Offset is found at offset 0x3C-0x40 relative to MZ base
|
||||
for mz_match in mz_ord:
|
||||
mz_off = mz_match.start()
|
||||
|
||||
# MZ (DOS) > PE (NT) image Offset is found at offset 0x3C-0x40 relative to MZ base
|
||||
pe_off = mz_off + int.from_bytes(in_buffer[mz_off + 0x3C:mz_off + 0x40], 'little')
|
||||
|
||||
|
||||
# Skip MZ (DOS) with bad PE (NT) image Offset
|
||||
if pe_off == mz_off or pe_off >= pack_off:
|
||||
continue
|
||||
|
||||
|
||||
# Check if potential MZ > PE image magic value is valid
|
||||
if PAT_MICROSOFT_PE.search(in_buffer[pe_off:pe_off + 0x4]):
|
||||
try:
|
||||
# Parse detected MZ > PE > Image, quickly (fast_load)
|
||||
pe_file = get_pe_file(in_buffer[mz_off:], fast=True)
|
||||
|
||||
pe_file = get_pe_file(in_buffer[mz_off:], silent=True)
|
||||
|
||||
# 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
|
||||
pe_name = pe_info.get(b'ProductName',b'')
|
||||
except Exception:
|
||||
pe_name = pe_info.get(b'ProductName', b'')
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
# Any error means no MZ > PE > Info > Product Name
|
||||
logging.debug('Error: Invalid potential MZ > PE match at 0x%X: %s', pe_off, error)
|
||||
|
||||
pe_name = b''
|
||||
|
||||
|
||||
# Check for valid Phoenix TDK Packer PE > Product Name
|
||||
# Expected value is "TDK Packer (Extractor for Windows)"
|
||||
if pe_name.upper().startswith(b'TDK PACKER'):
|
||||
# Set TDK Base Offset to valid TDK Packer MZ offset
|
||||
tdk_base_off = mz_off
|
||||
|
||||
|
||||
# Stop parsing detected MZ once TDK Base Offset is found
|
||||
if tdk_base_off is not None:
|
||||
break
|
||||
else:
|
||||
# No TDK Base Offset could be found, assume 0x0
|
||||
tdk_base_off = 0x0
|
||||
|
||||
|
||||
return tdk_base_off
|
||||
|
||||
# Scan input buffer for valid Phoenix TDK image
|
||||
|
||||
def get_phoenix_tdk(in_buffer):
|
||||
""" Scan input buffer for valid Phoenix TDK image """
|
||||
|
||||
# Scan input buffer for Phoenix TDK pattern
|
||||
tdk_match = PAT_PHOENIX_TDK.search(in_buffer)
|
||||
|
||||
|
||||
if not tdk_match:
|
||||
return None, None
|
||||
|
||||
|
||||
# Set Phoenix TDK Header ($PACK) Offset
|
||||
tdk_pack_off = tdk_match.start()
|
||||
|
||||
|
||||
# Get Phoenix TDK Executable (MZ) Base Offset
|
||||
tdk_base_off = get_tdk_base(in_buffer, tdk_pack_off)
|
||||
|
||||
|
||||
return tdk_base_off, tdk_pack_off
|
||||
|
||||
# Check if input contains valid Phoenix TDK image
|
||||
|
||||
def is_phoenix_tdk(in_file):
|
||||
""" Check if input contains valid Phoenix TDK image """
|
||||
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
|
||||
return bool(get_phoenix_tdk(buffer)[1] is not None)
|
||||
|
||||
# Parse & Extract Phoenix Tools Development Kit (TDK) Packer
|
||||
|
||||
def phoenix_tdk_extract(input_file, extract_path, padding=0):
|
||||
""" Parse & Extract Phoenix Tools Development Kit (TDK) Packer """
|
||||
|
||||
exit_code = 0
|
||||
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
|
||||
printer('Phoenix Tools Development Kit Packer', padding)
|
||||
|
||||
base_off,pack_off = get_phoenix_tdk(input_buffer)
|
||||
|
||||
|
||||
base_off, pack_off = get_phoenix_tdk(input_buffer)
|
||||
|
||||
# Parse TDK Header structure
|
||||
tdk_hdr = get_struct(input_buffer, pack_off, PhoenixTdkHeader)
|
||||
|
||||
|
||||
# Print TDK Header structure info
|
||||
printer('Phoenix TDK Header:\n', padding + 4)
|
||||
|
||||
tdk_hdr.struct_print(padding + 8)
|
||||
|
||||
|
||||
# Check if reported TDK Header Size matches manual TDK Entry Count calculation
|
||||
if tdk_hdr.Size != TDK_HDR_LEN + TDK_DUMMY_LEN + tdk_hdr.Count * TDK_MOD_LEN:
|
||||
printer('Error: Phoenix TDK Header Size & Entry Count mismatch!\n', padding + 8, pause=True)
|
||||
|
||||
exit_code = 1
|
||||
|
||||
|
||||
# Store TDK Entries offset after the placeholder data
|
||||
entries_off = pack_off + TDK_HDR_LEN + TDK_DUMMY_LEN
|
||||
|
||||
|
||||
# Parse and extract each TDK Header Entry
|
||||
for entry_index in range(tdk_hdr.Count):
|
||||
# Parse TDK Entry structure
|
||||
tdk_mod = get_struct(input_buffer, entries_off + entry_index * TDK_MOD_LEN, PhoenixTdkEntry, [base_off])
|
||||
|
||||
|
||||
# Print TDK Entry structure info
|
||||
printer(f'Phoenix TDK Entry ({entry_index + 1}/{tdk_hdr.Count}):\n', padding + 8)
|
||||
|
||||
tdk_mod.struct_print(padding + 12)
|
||||
|
||||
|
||||
# Get TDK Entry raw data Offset (TDK Base + Entry Offset)
|
||||
mod_off = tdk_mod.get_offset()
|
||||
|
||||
|
||||
# Check if TDK Entry raw data Offset is valid
|
||||
if mod_off >= len(input_buffer):
|
||||
printer('Error: Phoenix TDK Entry > Offset is out of bounds!\n', padding + 12, pause=True)
|
||||
|
||||
exit_code = 2
|
||||
|
||||
|
||||
# Store TDK Entry raw data (relative to TDK Base, not TDK Header)
|
||||
mod_data = input_buffer[mod_off:mod_off + tdk_mod.Size]
|
||||
|
||||
|
||||
# Check if TDK Entry raw data is complete
|
||||
if len(mod_data) != tdk_mod.Size:
|
||||
printer('Error: Phoenix TDK Entry > Data is truncated!\n', padding + 12, pause=True)
|
||||
|
||||
exit_code = 3
|
||||
|
||||
|
||||
# Check if TDK Entry Reserved is present
|
||||
if tdk_mod.Reserved:
|
||||
printer('Error: Phoenix TDK Entry > Reserved is not empty!\n', padding + 12, pause=True)
|
||||
|
||||
exit_code = 4
|
||||
|
||||
|
||||
# Decompress TDK Entry raw data, when applicable (i.e. LZMA)
|
||||
if tdk_mod.get_compression() == 'LZMA':
|
||||
try:
|
||||
mod_data = lzma.LZMADecompressor().decompress(mod_data)
|
||||
except Exception:
|
||||
printer('Error: Phoenix TDK Entry > LZMA decompression failed!\n', padding + 12, pause=True)
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
printer(f'Error: Phoenix TDK Entry > LZMA decompression failed: {error}!\n', padding + 12, pause=True)
|
||||
|
||||
exit_code = 5
|
||||
|
||||
|
||||
# Generate TDK Entry file name, avoid crash if Entry data is bad
|
||||
mod_name = tdk_mod.get_name() or f'Unknown_{entry_index + 1:02d}.bin'
|
||||
|
||||
|
||||
# Generate TDK Entry file data output path
|
||||
mod_file = os.path.join(extract_path, safe_name(mod_name))
|
||||
|
||||
|
||||
# Account for potential duplicate file names
|
||||
if os.path.isfile(mod_file): mod_file += f'_{entry_index + 1:02d}'
|
||||
|
||||
if os.path.isfile(mod_file):
|
||||
mod_file += f'_{entry_index + 1:02d}'
|
||||
|
||||
# Save TDK Entry data to output file
|
||||
with open(mod_file, 'wb') as out_file:
|
||||
out_file.write(mod_data)
|
||||
|
||||
|
||||
return exit_code
|
||||
|
||||
|
||||
# Get ctypes Structure Sizes
|
||||
TDK_HDR_LEN = ctypes.sizeof(PhoenixTdkHeader)
|
||||
TDK_MOD_LEN = ctypes.sizeof(PhoenixTdkEntry)
|
||||
|
@ -240,4 +273,4 @@ TDK_MOD_LEN = ctypes.sizeof(PhoenixTdkEntry)
|
|||
TDK_DUMMY_LEN = 0x200
|
||||
|
||||
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()
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Portwell EFI Extract
|
||||
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 sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_efi import efi_decompress, is_efi_compressed
|
||||
from common.path_ops import make_dirs, safe_name
|
||||
|
@ -23,114 +18,133 @@ from common.system import printer
|
|||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
TITLE = 'Portwell EFI Update Extractor v3.0'
|
||||
|
||||
FILE_NAMES = {
|
||||
0 : 'Flash.efi',
|
||||
1 : 'Fparts.txt',
|
||||
2 : 'Update.nsh',
|
||||
3 : 'Temp.bin',
|
||||
4 : 'SaveDmiData.efi'
|
||||
0: 'Flash.efi',
|
||||
1: 'Fparts.txt',
|
||||
2: 'Update.nsh',
|
||||
3: 'Temp.bin',
|
||||
4: 'SaveDmiData.efi'
|
||||
}
|
||||
|
||||
# Check if input is Portwell EFI executable
|
||||
|
||||
def is_portwell_efi(in_file):
|
||||
""" Check if input is Portwell EFI executable """
|
||||
|
||||
in_buffer = file_to_bytes(in_file)
|
||||
|
||||
|
||||
try:
|
||||
pe_buffer = get_portwell_pe(in_buffer)[1]
|
||||
except Exception:
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
logging.debug('Error: Could not check if input is Portwell EFI executable: %s', error)
|
||||
|
||||
pe_buffer = b''
|
||||
|
||||
is_mz = PAT_MICROSOFT_MZ.search(in_buffer[:0x2]) # EFI images start with PE Header MZ
|
||||
|
||||
is_uu = PAT_PORTWELL_EFI.search(pe_buffer[:0x4]) # Portwell EFI files start with <UU>
|
||||
|
||||
|
||||
is_mz = PAT_MICROSOFT_MZ.search(in_buffer[:0x2]) # EFI images start with PE Header MZ
|
||||
|
||||
is_uu = PAT_PORTWELL_EFI.search(pe_buffer[:0x4]) # Portwell EFI files start with <UU>
|
||||
|
||||
return bool(is_mz and is_uu)
|
||||
|
||||
# Get PE of Portwell EFI executable
|
||||
def get_portwell_pe(in_buffer):
|
||||
pe_file = get_pe_file(in_buffer, fast=True) # Analyze EFI Portable Executable (PE)
|
||||
|
||||
pe_data = in_buffer[pe_file.OPTIONAL_HEADER.SizeOfImage:] # Skip EFI executable (pylint: disable=E1101)
|
||||
|
||||
|
||||
def get_portwell_pe(in_buffer):
|
||||
""" Get PE of Portwell EFI executable """
|
||||
|
||||
pe_file = get_pe_file(in_buffer, silent=True) # Analyze EFI Portable Executable (PE)
|
||||
|
||||
pe_data = in_buffer[pe_file.OPTIONAL_HEADER.SizeOfImage:] # Skip EFI executable (pylint: disable=E1101)
|
||||
|
||||
return pe_file, pe_data
|
||||
|
||||
# Parse & Extract Portwell UEFI Unpacker
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
printer(efi_title, padding)
|
||||
|
||||
|
||||
# Split EFI Payload into <UU> file chunks
|
||||
efi_list = list(PAT_PORTWELL_EFI.finditer(pe_data))
|
||||
for idx,val in enumerate(efi_list):
|
||||
|
||||
for idx, val in enumerate(efi_list):
|
||||
efi_bgn = val.end()
|
||||
efi_end = len(pe_data) if idx == len(efi_list) - 1 else efi_list[idx + 1].start()
|
||||
|
||||
efi_files.append(pe_data[efi_bgn:efi_end])
|
||||
|
||||
|
||||
parse_efi_files(extract_path, efi_files, padding)
|
||||
|
||||
# Get Portwell UEFI Unpacker tag
|
||||
|
||||
|
||||
def get_unpacker_tag(input_buffer, pe_file):
|
||||
""" Get Portwell UEFI Unpacker tag """
|
||||
|
||||
unpacker_tag_txt = 'UEFI Unpacker'
|
||||
|
||||
|
||||
for pe_section in pe_file.sections:
|
||||
# Unpacker Tag, Version, Strings etc are found in .data PE section
|
||||
if pe_section.Name.startswith(b'.data'):
|
||||
pe_data_bgn = pe_section.PointerToRawData
|
||||
pe_data_end = pe_data_bgn + pe_section.SizeOfRawData
|
||||
|
||||
|
||||
# Decode any valid UTF-16 .data PE section info to a parsable text buffer
|
||||
pe_data_txt = input_buffer[pe_data_bgn:pe_data_end].decode('utf-16','ignore')
|
||||
|
||||
pe_data_txt = input_buffer[pe_data_bgn:pe_data_end].decode('utf-16', 'ignore')
|
||||
|
||||
# Search .data for UEFI Unpacker tag
|
||||
unpacker_tag_bgn = pe_data_txt.find(unpacker_tag_txt)
|
||||
|
||||
if unpacker_tag_bgn != -1:
|
||||
unpacker_tag_len = pe_data_txt[unpacker_tag_bgn:].find('=')
|
||||
|
||||
if unpacker_tag_len != -1:
|
||||
unpacker_tag_end = unpacker_tag_bgn + unpacker_tag_len
|
||||
unpacker_tag_raw = pe_data_txt[unpacker_tag_bgn:unpacker_tag_end]
|
||||
|
||||
|
||||
# Found full UEFI Unpacker tag, store and slightly beautify the resulting text
|
||||
unpacker_tag_txt = unpacker_tag_raw.strip().replace(' ',' ').replace('<',' <')
|
||||
|
||||
break # Found PE .data section, skip the rest
|
||||
|
||||
unpacker_tag_txt = unpacker_tag_raw.strip().replace(' ', ' ').replace('<', ' <')
|
||||
|
||||
break # Found PE .data section, skip the rest
|
||||
|
||||
return unpacker_tag_txt
|
||||
|
||||
# Process Portwell UEFI Unpacker payload files
|
||||
|
||||
def parse_efi_files(extract_path, efi_files, padding):
|
||||
for file_index,file_data in enumerate(efi_files):
|
||||
""" Process Portwell UEFI Unpacker payload files """
|
||||
|
||||
for file_index, file_data in enumerate(efi_files):
|
||||
if file_data in (b'', b'NULL'):
|
||||
continue # Skip empty/unused files
|
||||
|
||||
file_name = FILE_NAMES.get(file_index, f'Unknown_{file_index}.bin') # Assign Name to EFI file
|
||||
|
||||
printer(f'[{file_index}] {file_name}', padding + 4) # Print EFI file name, indicate progress
|
||||
|
||||
continue # Skip empty/unused files
|
||||
|
||||
file_name = FILE_NAMES.get(file_index, f'Unknown_{file_index}.bin') # Assign Name to EFI file
|
||||
|
||||
printer(f'[{file_index}] {file_name}', padding + 4) # Print EFI file name, indicate progress
|
||||
|
||||
if file_name.startswith('Unknown_'):
|
||||
printer(f'Note: Detected new Portwell EFI file ID {file_index}!', padding + 8, pause=True) # Report new EFI files
|
||||
|
||||
file_path = os.path.join(extract_path, safe_name(file_name)) # Store EFI file output path
|
||||
|
||||
printer(f'Note: Detected new Portwell EFI file ID {file_index}!', padding + 8, pause=True)
|
||||
|
||||
file_path = os.path.join(extract_path, safe_name(file_name)) # Store EFI file output path
|
||||
|
||||
with open(file_path, 'wb') as out_file:
|
||||
out_file.write(file_data) # Store EFI file data to drive
|
||||
|
||||
out_file.write(file_data) # Store EFI file data to drive
|
||||
|
||||
# Attempt to detect EFI compression & decompress when applicable
|
||||
if is_efi_compressed(file_data):
|
||||
comp_fname = file_path + '.temp' # Store temporary compressed file name
|
||||
|
||||
os.replace(file_path, comp_fname) # Rename initial/compressed file
|
||||
|
||||
comp_fname = file_path + '.temp' # Store temporary compressed file name
|
||||
|
||||
os.replace(file_path, comp_fname) # Rename initial/compressed file
|
||||
|
||||
if efi_decompress(comp_fname, file_path, padding + 8) == 0:
|
||||
os.remove(comp_fname) # Successful decompression, delete compressed file
|
||||
os.remove(comp_fname) # Successful decompression, delete compressed file
|
||||
|
||||
|
||||
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()
|
||||
|
|
34
README.md
34
README.md
|
@ -42,7 +42,7 @@ You can either Drag & Drop or manually enter AMI BIOS Guard (PFAT) image file(s)
|
|||
|
||||
#### **Compatibility**
|
||||
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
|
||||
|
||||
#### **Prerequisites**
|
||||
|
||||
|
@ -75,7 +75,7 @@ You can either Drag & Drop or manually enter AMI UCP Update executable file(s).
|
|||
|
||||
#### **Compatibility**
|
||||
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
|
||||
|
||||
#### **Prerequisites**
|
||||
|
||||
|
@ -112,7 +112,7 @@ You can either Drag & Drop or manually enter Apple EFI IM4P file(s). Optional ar
|
|||
|
||||
#### **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**
|
||||
|
||||
|
@ -143,7 +143,7 @@ You can either Drag & Drop or manually enter Apple EFI image file(s). Optional a
|
|||
|
||||
#### **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**
|
||||
|
||||
|
@ -176,7 +176,7 @@ You can either Drag & Drop or manually enter Apple EFI PKG package file(s). Opti
|
|||
|
||||
#### **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**
|
||||
|
||||
|
@ -208,7 +208,7 @@ You can either Drag & Drop or manually enter Apple EFI PBZX image file(s). Optio
|
|||
|
||||
#### **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**
|
||||
|
||||
|
@ -240,7 +240,7 @@ You can either Drag & Drop or manually enter Award BIOS image file(s). Optional
|
|||
|
||||
#### **Compatibility**
|
||||
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
|
||||
|
||||
#### **Prerequisites**
|
||||
|
||||
|
@ -274,7 +274,7 @@ You can either Drag & Drop or manually enter Dell PFS Update images(s). Optional
|
|||
|
||||
#### **Compatibility**
|
||||
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
|
||||
|
||||
#### **Prerequisites**
|
||||
|
||||
|
@ -306,7 +306,7 @@ You can either Drag & Drop or manually enter Fujitsu SFX BIOS image file(s). Opt
|
|||
|
||||
#### **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**
|
||||
|
||||
|
@ -338,7 +338,7 @@ You can either Drag & Drop or manually enter Fujitsu UPC BIOS image file(s). Opt
|
|||
|
||||
#### **Compatibility**
|
||||
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
|
||||
|
||||
#### **Prerequisites**
|
||||
|
||||
|
@ -370,7 +370,7 @@ You can either Drag & Drop or manually enter Insyde iFlash/iFdPacker Update imag
|
|||
|
||||
#### **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**
|
||||
|
||||
|
@ -400,14 +400,14 @@ You can either Drag & Drop or manually enter Panasonic BIOS Package executable f
|
|||
|
||||
#### **Compatibility**
|
||||
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
|
||||
|
||||
#### **Prerequisites**
|
||||
|
||||
To run the utility, you must have the following 3rd party Python modules installed:
|
||||
|
||||
* [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:
|
||||
|
||||
|
@ -437,7 +437,7 @@ You can either Drag & Drop or manually enter Phoenix Tools Development Kit (TDK)
|
|||
|
||||
#### **Compatibility**
|
||||
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
|
||||
|
||||
#### **Prerequisites**
|
||||
|
||||
|
@ -469,7 +469,7 @@ You can either Drag & Drop or manually enter Portwell UEFI Unpacker EFI executab
|
|||
|
||||
#### **Compatibility**
|
||||
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
|
||||
|
||||
#### **Prerequisites**
|
||||
|
||||
|
@ -507,7 +507,7 @@ You can either Drag & Drop or manually enter Toshiba BIOS COM image file(s). Opt
|
|||
|
||||
#### **Compatibility**
|
||||
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
|
||||
|
||||
#### **Prerequisites**
|
||||
|
||||
|
@ -539,7 +539,7 @@ You can either Drag & Drop or manually enter VAIO Packaging Manager executable f
|
|||
|
||||
#### **Compatibility**
|
||||
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support.
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.10 or newer support.
|
||||
|
||||
#### **Prerequisites**
|
||||
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Toshiba COM Extract
|
||||
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 sys
|
||||
import subprocess
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.externals import get_comextract_path
|
||||
from common.path_ops import make_dirs, path_stem, path_suffixes
|
||||
|
@ -23,41 +17,49 @@ from common.system import printer
|
|||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is Toshiba BIOS COM image
|
||||
TITLE = 'Toshiba BIOS COM Extractor v3.0'
|
||||
|
||||
|
||||
def is_toshiba_com(in_file):
|
||||
""" Check if input is Toshiba BIOS COM image """
|
||||
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
|
||||
is_ext = path_suffixes(in_file)[-1].upper() == '.COM' if os.path.isfile(in_file) else True
|
||||
|
||||
|
||||
is_com = PAT_TOSHIBA_COM.search(buffer)
|
||||
|
||||
|
||||
return is_ext and is_com
|
||||
|
||||
# Parse & Extract Toshiba BIOS COM image
|
||||
|
||||
def toshiba_com_extract(input_file, extract_path, padding=0):
|
||||
""" Parse & Extract Toshiba BIOS COM image """
|
||||
|
||||
if not os.path.isfile(input_file):
|
||||
printer('Error: Could not find input file path!', padding)
|
||||
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
|
||||
output_name = path_stem(input_file)
|
||||
|
||||
output_file = os.path.join(extract_path, f'{output_name}.bin')
|
||||
|
||||
|
||||
try:
|
||||
subprocess.run([get_comextract_path(), input_file, output_file], check=True, stdout=subprocess.DEVNULL)
|
||||
|
||||
|
||||
if not os.path.isfile(output_file):
|
||||
raise Exception('EXTRACT_FILE_MISSING')
|
||||
except Exception:
|
||||
printer(f'Error: ToshibaComExtractor could not extract file {input_file}!', padding)
|
||||
|
||||
raise ValueError('EXTRACT_FILE_MISSING')
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
printer(f'Error: ToshibaComExtractor could not extract file {input_file}: {error}!', padding)
|
||||
|
||||
return 2
|
||||
|
||||
|
||||
printer(f'Succesfull {output_name} extraction via ToshibaComExtractor!', padding)
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_toshiba_com, toshiba_com_extract).run_utility()
|
||||
BIOSUtility(title=TITLE, check=is_toshiba_com, main=toshiba_com_extract).run_utility()
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
VAIO Package 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 sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_szip import is_szip_supported, szip_decompress
|
||||
from common.path_ops import make_dirs
|
||||
|
@ -22,126 +16,149 @@ from common.system import printer
|
|||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is VAIO Packaging Manager
|
||||
TITLE = 'VAIO Packaging Manager Extractor v4.0'
|
||||
|
||||
|
||||
def is_vaio_pkg(in_file):
|
||||
""" Check if input is VAIO Packaging Manager """
|
||||
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
|
||||
return bool(PAT_VAIO_CFG.search(buffer))
|
||||
|
||||
# Extract VAIO Packaging Manager executable
|
||||
|
||||
def vaio_cabinet(name, buffer, extract_path, padding=0):
|
||||
match_cab = PAT_VAIO_CAB.search(buffer) # Microsoft CAB Header XOR 0xFF
|
||||
|
||||
""" Extract VAIO Packaging Manager executable """
|
||||
|
||||
match_cab = PAT_VAIO_CAB.search(buffer) # Microsoft CAB Header XOR 0xFF
|
||||
|
||||
if not match_cab:
|
||||
return 1
|
||||
|
||||
|
||||
printer('Detected obfuscated CAB archive!', padding)
|
||||
|
||||
|
||||
# Determine the Microsoft CAB image size
|
||||
cab_size = int.from_bytes(buffer[match_cab.start() + 0x8:match_cab.start() + 0xC], 'little') # Get LE XOR-ed CAB size
|
||||
xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB size XOR value
|
||||
cab_size ^= xor_size # Perform XOR 0xFF and get actual CAB size
|
||||
cab_size = int.from_bytes(buffer[match_cab.start() + 0x8:match_cab.start() + 0xC], 'little') # Get LE XOR CAB size
|
||||
|
||||
xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB size XOR value
|
||||
|
||||
cab_size ^= xor_size # Perform XOR 0xFF and get actual CAB size
|
||||
|
||||
printer('Removing obfuscation...', padding + 4)
|
||||
|
||||
|
||||
# Determine the Microsoft CAB image Data
|
||||
cab_data = int.from_bytes(buffer[match_cab.start():match_cab.start() + cab_size], 'big') # Get BE XOR-ed CAB data
|
||||
xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB data XOR value
|
||||
cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB data
|
||||
|
||||
cab_data = int.from_bytes(buffer[match_cab.start():match_cab.start() + cab_size], 'big') # Get BE XOR CAB data
|
||||
|
||||
xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB data XOR value
|
||||
|
||||
cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB data
|
||||
|
||||
printer('Extracting archive...', padding + 4)
|
||||
|
||||
|
||||
cab_path = os.path.join(extract_path, f'{name}_Temporary.cab')
|
||||
|
||||
|
||||
with open(cab_path, 'wb') as cab_file:
|
||||
cab_file.write(cab_data) # Create temporary CAB archive
|
||||
|
||||
cab_file.write(cab_data) # Create temporary CAB archive
|
||||
|
||||
if is_szip_supported(cab_path, padding + 8, check=True):
|
||||
if szip_decompress(cab_path, extract_path, 'CAB', padding + 8, check=True) == 0:
|
||||
os.remove(cab_path) # Successful extraction, delete temporary CAB archive
|
||||
if szip_decompress(cab_path, extract_path, 'VAIO CAB', padding + 8, check=True) == 0:
|
||||
os.remove(cab_path) # Successful extraction, delete temporary CAB archive
|
||||
else:
|
||||
return 3
|
||||
else:
|
||||
return 2
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
# Unlock VAIO Packaging Manager executable
|
||||
|
||||
def vaio_unlock(name, buffer, extract_path, padding=0):
|
||||
""" Unlock VAIO Packaging Manager executable """
|
||||
|
||||
match_cfg = PAT_VAIO_CFG.search(buffer)
|
||||
|
||||
|
||||
if not match_cfg:
|
||||
return 1
|
||||
|
||||
|
||||
printer('Attempting to Unlock executable!', padding)
|
||||
|
||||
|
||||
# Initialize VAIO Package Configuration file variables (assume overkill size of 0x500)
|
||||
cfg_bgn,cfg_end,cfg_false,cfg_true = [match_cfg.start(), match_cfg.start() + 0x500, b'', b'']
|
||||
|
||||
cfg_bgn, cfg_end, cfg_false, cfg_true = [match_cfg.start(), match_cfg.start() + 0x500, b'', b'']
|
||||
|
||||
# Get VAIO Package Configuration file info, split at new_line and stop at payload DOS header (EOF)
|
||||
cfg_info = buffer[cfg_bgn:cfg_end].split(b'\x0D\x0A\x4D\x5A')[0].replace(b'\x0D',b'').split(b'\x0A')
|
||||
|
||||
cfg_info = buffer[cfg_bgn:cfg_end].split(b'\x0D\x0A\x4D\x5A')[0].replace(b'\x0D', b'').split(b'\x0A')
|
||||
|
||||
printer('Retrieving True/False values...', padding + 4)
|
||||
|
||||
|
||||
# Determine VAIO Package Configuration file True & False values
|
||||
for info in cfg_info:
|
||||
if info.startswith(b'ExtractPathByUser='):
|
||||
cfg_false = bytearray(b'0' if info[18:] in (b'0',b'1') else info[18:]) # Should be 0/No/False
|
||||
cfg_false = bytearray(b'0' if info[18:] in (b'0', b'1') else info[18:]) # Should be 0/No/False
|
||||
|
||||
if info.startswith(b'UseCompression='):
|
||||
cfg_true = bytearray(b'1' if info[15:] in (b'0',b'1') else info[15:]) # Should be 1/Yes/True
|
||||
|
||||
cfg_true = bytearray(b'1' if info[15:] in (b'0', b'1') else info[15:]) # Should be 1/Yes/True
|
||||
|
||||
# Check if valid True/False values have been retrieved
|
||||
if cfg_false == cfg_true or not cfg_false or not cfg_true:
|
||||
printer('Error: Could not retrieve True/False values!', padding + 8)
|
||||
|
||||
return 2
|
||||
|
||||
|
||||
printer('Adjusting UseVAIOCheck entry...', padding + 4)
|
||||
|
||||
|
||||
# Find and replace UseVAIOCheck entry from 1/Yes/True to 0/No/False
|
||||
vaio_check = PAT_VAIO_CHK.search(buffer[cfg_bgn:])
|
||||
|
||||
if vaio_check:
|
||||
buffer[cfg_bgn + vaio_check.end():cfg_bgn + vaio_check.end() + len(cfg_true)] = cfg_false
|
||||
else:
|
||||
printer('Error: Could not find entry UseVAIOCheck!', padding + 8)
|
||||
|
||||
return 3
|
||||
|
||||
|
||||
printer('Adjusting ExtractPathByUser entry...', padding + 4)
|
||||
|
||||
|
||||
# Find and replace ExtractPathByUser entry from 0/No/False to 1/Yes/True
|
||||
user_path = PAT_VAIO_EXT.search(buffer[cfg_bgn:])
|
||||
|
||||
if user_path:
|
||||
buffer[cfg_bgn + user_path.end():cfg_bgn + user_path.end() + len(cfg_false)] = cfg_true
|
||||
else:
|
||||
printer('Error: Could not find entry ExtractPathByUser!', padding + 8)
|
||||
|
||||
return 4
|
||||
|
||||
|
||||
printer('Storing unlocked executable...', padding + 4)
|
||||
|
||||
|
||||
# Store Unlocked VAIO Packaging Manager executable
|
||||
if vaio_check and user_path:
|
||||
unlock_path = os.path.join(extract_path, f'{name}_Unlocked.exe')
|
||||
|
||||
with open(unlock_path, 'wb') as unl_file:
|
||||
unl_file.write(buffer)
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
# Parse & Extract or Unlock VAIO Packaging Manager
|
||||
|
||||
def vaio_pkg_extract(input_file, extract_path, padding=0):
|
||||
""" Parse & Extract or Unlock VAIO Packaging Manager """
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
|
||||
input_name = os.path.basename(input_file)
|
||||
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
|
||||
if vaio_cabinet(input_name, input_buffer, extract_path, padding) == 0:
|
||||
printer('Successfully Extracted!', padding)
|
||||
elif vaio_unlock(input_name, bytearray(input_buffer), extract_path, padding) == 0:
|
||||
printer('Successfully Unlocked!', padding)
|
||||
else:
|
||||
printer('Error: Failed to Extract or Unlock executable!', padding)
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, 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
6
__init__.py
Normal 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
6
common/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2019-2024 Plato Mavropoulos
|
||||
"""
|
|
@ -1,25 +1,31 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
Copyright (C) 2022-2024 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
|
||||
# Get Checksum 16-bit
|
||||
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):
|
||||
# noinspection PyTypeChecker
|
||||
value += int.from_bytes(data[idx:idx + 2], order)
|
||||
|
||||
value += int.from_bytes(data[idx:idx + 2], byteorder=order)
|
||||
|
||||
value &= 0xFFFF
|
||||
|
||||
|
||||
return value
|
||||
|
||||
|
||||
# Get Checksum 8-bit XOR
|
||||
def get_chk_8_xor(data, value=0):
|
||||
""" Calculate Checksum-8 XOR of data, controlling IV """
|
||||
|
||||
for byte in data:
|
||||
value ^= byte
|
||||
|
||||
|
||||
value ^= 0x0
|
||||
|
||||
|
||||
return value
|
||||
|
|
|
@ -1,57 +1,60 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
Copyright (C) 2022-2024 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from common.path_ops import project_root, safe_path
|
||||
from common.system import get_os_ver, printer
|
||||
from common.externals import get_tiano_path
|
||||
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_original = int.from_bytes(data[0x4:0x8], 'little')
|
||||
|
||||
|
||||
return size_compress, size_original
|
||||
|
||||
|
||||
def is_efi_compressed(data, strict=True):
|
||||
size_comp,size_orig = get_compress_sizes(data)
|
||||
|
||||
""" Check if data is EFI compressed, controlling EOF padding """
|
||||
|
||||
size_comp, size_orig = get_compress_sizes(data)
|
||||
|
||||
check_diff = size_comp < size_orig
|
||||
|
||||
|
||||
if strict:
|
||||
check_size = size_comp + 0x8 == len(data)
|
||||
else:
|
||||
check_size = size_comp + 0x8 <= len(data)
|
||||
|
||||
|
||||
return check_diff and check_size
|
||||
|
||||
# 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'):
|
||||
""" EFI/Tiano Decompression via TianoCompress """
|
||||
|
||||
try:
|
||||
subprocess.run([get_tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type], check=True, stdout=subprocess.DEVNULL)
|
||||
|
||||
subprocess.run([get_tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
|
||||
with open(in_path, 'rb') as file:
|
||||
_,size_orig = get_compress_sizes(file.read())
|
||||
|
||||
_, size_orig = get_compress_sizes(file.read())
|
||||
|
||||
if os.path.getsize(out_path) != size_orig:
|
||||
raise Exception('EFI_DECOMPRESS_ERROR')
|
||||
except Exception:
|
||||
raise OSError('EFI decompressed file & header size mismatch!')
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
if not silent:
|
||||
printer(f'Error: TianoCompress could not extract file {in_path}!', padding)
|
||||
|
||||
printer(f'Error: TianoCompress could not extract file {in_path}: {error}!', padding)
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
if not silent:
|
||||
printer('Succesfull EFI decompression via TianoCompress!', padding)
|
||||
|
||||
|
||||
return 0
|
||||
|
|
|
@ -1,72 +1,72 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
Copyright (C) 2022-2024 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from common.path_ops import project_root, safe_path
|
||||
from common.system import get_os_ver, printer
|
||||
from common.externals import get_szip_path
|
||||
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):
|
||||
if exit_code not in (0,1):
|
||||
raise Exception(f'BAD_EXIT_CODE_{exit_code}')
|
||||
""" Check 7-Zip bad exit codes (0 OK, 1 Warning) """
|
||||
|
||||
if exit_code not in (0, 1):
|
||||
raise ValueError(f'Bad exit code: {exit_code}')
|
||||
|
||||
|
||||
# Check if file is 7-Zip supported
|
||||
def is_szip_supported(in_path, padding=0, args=None, check=False, silent=False):
|
||||
""" Check if file is 7-Zip supported """
|
||||
|
||||
try:
|
||||
if args is None:
|
||||
args = []
|
||||
|
||||
|
||||
szip_c = [get_szip_path(), 't', in_path, *args, '-bso0', '-bse0', '-bsp0']
|
||||
|
||||
|
||||
szip_t = subprocess.run(szip_c, check=False)
|
||||
|
||||
|
||||
if check:
|
||||
check_bad_exit_code(szip_t.returncode)
|
||||
except Exception:
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
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 True
|
||||
|
||||
# Archive decompression via 7-Zip
|
||||
|
||||
def szip_decompress(in_path, out_path, in_name, padding=0, args=None, check=False, silent=False):
|
||||
""" Archive decompression via 7-Zip """
|
||||
|
||||
if not in_name:
|
||||
in_name = 'archive'
|
||||
|
||||
|
||||
try:
|
||||
if args is None:
|
||||
args = []
|
||||
|
||||
|
||||
szip_c = [get_szip_path(), 'x', *args, '-aou', '-bso0', '-bse0', '-bsp0', f'-o{out_path}', in_path]
|
||||
|
||||
|
||||
szip_x = subprocess.run(szip_c, check=False)
|
||||
|
||||
|
||||
if check:
|
||||
check_bad_exit_code(szip_x.returncode)
|
||||
|
||||
|
||||
if not os.path.isdir(out_path):
|
||||
raise Exception('EXTRACT_DIR_MISSING')
|
||||
except Exception:
|
||||
raise OSError(f'Extraction directory not found: {out_path}')
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
if not silent:
|
||||
printer(f'Error: 7-Zip could not extract {in_name} file {in_path}!', padding)
|
||||
|
||||
printer(f'Error: 7-Zip could not extract {in_name} file {in_path}: {error}!', padding)
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
if not silent:
|
||||
printer(f'Succesfull {in_name} decompression via 7-Zip!', padding)
|
||||
|
||||
|
||||
return 0
|
||||
|
|
|
@ -1,38 +1,66 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# 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.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():
|
||||
"""
|
||||
https://github.com/allowitsme/big-tool by Dmitry Frolov
|
||||
https://github.com/platomav/BGScriptTool by Plato Mavropoulos
|
||||
"""
|
||||
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from external.big_script_tool import BigScript # pylint: disable=E0401,E0611
|
||||
except Exception:
|
||||
BigScript = None
|
||||
|
||||
return BigScript
|
||||
from external.big_script_tool import BigScript # pylint: disable=C0415
|
||||
|
||||
# Get UEFIFind path
|
||||
def get_uefifind_path():
|
||||
exec_name = f'UEFIFind{".exe" if get_os_ver()[1] else ""}'
|
||||
|
||||
return safe_path(project_root(), ['external', exec_name])
|
||||
return BigScript
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
|
||||
# Get UEFIExtract path
|
||||
def get_uefiextract_path():
|
||||
exec_name = f'UEFIExtract{".exe" if get_os_ver()[1] else ""}'
|
||||
|
||||
return safe_path(project_root(), ['external', exec_name])
|
||||
return None
|
||||
|
||||
|
||||
def get_comextract_path() -> str:
|
||||
""" Get ToshibaComExtractor path """
|
||||
|
||||
# Get ToshibaComExtractor path
|
||||
def get_comextract_path():
|
||||
exec_name = f'comextract{".exe" if get_os_ver()[1] else ""}'
|
||||
|
||||
|
||||
return safe_path(project_root(), ['external', exec_name])
|
||||
|
||||
|
||||
def get_szip_path() -> str:
|
||||
""" Get 7-Zip path """
|
||||
|
||||
exec_name = '7z.exe' if get_os_ver()[1] else '7zzs'
|
||||
|
||||
return safe_path(project_root(), ['external', exec_name])
|
||||
|
||||
|
||||
def get_tiano_path() -> str:
|
||||
""" Get TianoCompress path """
|
||||
|
||||
exec_name = f'TianoCompress{".exe" if get_os_ver()[1] else ""}'
|
||||
|
||||
return safe_path(project_root(), ['external', exec_name])
|
||||
|
||||
|
||||
def get_uefifind_path() -> str:
|
||||
""" Get UEFIFind path """
|
||||
|
||||
exec_name = f'UEFIFind{".exe" if get_os_ver()[1] else ""}'
|
||||
|
||||
return safe_path(project_root(), ['external', exec_name])
|
||||
|
||||
|
||||
def get_uefiextract_path() -> str:
|
||||
""" Get UEFIExtract path """
|
||||
|
||||
exec_name = f'UEFIExtract{".exe" if get_os_ver()[1] else ""}'
|
||||
|
||||
return safe_path(project_root(), ['external', exec_name])
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# 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):
|
||||
s = ('th', 'st', 'nd', 'rd') + ('th',) * 10
|
||||
|
||||
v = number % 100
|
||||
|
||||
return f'{number}{s[v % 10]}' if v > 13 else f'{number}{s[v]}'
|
||||
"""
|
||||
Get ordinal (textual) representation of input numerical value
|
||||
https://leancrew.com/all-this/2020/06/ordinals-in-python/ by Dr. Drang
|
||||
"""
|
||||
|
||||
txt = ('th', 'st', 'nd', 'rd') + ('th',) * 10
|
||||
|
||||
val = number % 100
|
||||
|
||||
return f'{number}{txt[val % 10]}' if val > 13 else f'{number}{txt[val]}'
|
||||
|
|
|
@ -1,154 +1,213 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
Copyright (C) 2022-2024 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import stat
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
|
||||
from pathlib import Path, PurePath
|
||||
|
||||
from common.system import get_os_ver
|
||||
from common.text_ops import is_encased, to_string
|
||||
|
||||
# Fix illegal/reserved Windows characters
|
||||
MAX_WIN_COMP_LEN = 255
|
||||
|
||||
|
||||
def safe_name(in_name):
|
||||
"""
|
||||
Fix illegal/reserved Windows characters
|
||||
Can also be used to nuke dangerous paths
|
||||
"""
|
||||
|
||||
name_repr = repr(in_name).strip("'")
|
||||
|
||||
return re.sub(r'[\\/:"*?<>|]+', '_', name_repr)
|
||||
|
||||
# Check and attempt to fix illegal/unsafe OS path traversals
|
||||
|
||||
def safe_path(base_path, user_paths):
|
||||
""" Check and attempt to fix illegal/unsafe OS path traversals """
|
||||
|
||||
# Convert base path to absolute path
|
||||
base_path = real_path(base_path)
|
||||
|
||||
|
||||
# Merge user path(s) to string with OS separators
|
||||
user_path = to_string(user_paths, os.sep)
|
||||
|
||||
|
||||
# Create target path from base + requested user path
|
||||
target_path = norm_path(base_path, user_path)
|
||||
|
||||
|
||||
# Check if target path is OS illegal/unsafe
|
||||
if is_safe_path(base_path, target_path):
|
||||
return target_path
|
||||
|
||||
|
||||
# Re-create target path from base + leveled/safe illegal "path" (now file)
|
||||
nuked_path = norm_path(base_path, safe_name(user_path))
|
||||
|
||||
|
||||
# Check if illegal path leveling worked
|
||||
if is_safe_path(base_path, nuked_path):
|
||||
return nuked_path
|
||||
|
||||
# Still illegal, raise exception to halt execution
|
||||
raise 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):
|
||||
""" Check for illegal/unsafe OS path traversal """
|
||||
|
||||
base_path = real_path(base_path)
|
||||
|
||||
|
||||
target_path = real_path(target_path)
|
||||
|
||||
|
||||
common_path = os.path.commonpath((base_path, target_path))
|
||||
|
||||
|
||||
return base_path == common_path
|
||||
|
||||
# Create normalized base path + OS separator + 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)
|
||||
|
||||
# Get absolute path, resolving any symlinks
|
||||
|
||||
def real_path(in_path):
|
||||
""" Get absolute path, resolving any symlinks """
|
||||
|
||||
return os.path.realpath(in_path)
|
||||
|
||||
# Get Windows/Posix OS agnostic path
|
||||
|
||||
def agnostic_path(in_path):
|
||||
""" Get Windows/Posix OS agnostic path """
|
||||
|
||||
return PurePath(in_path.replace('\\', os.sep))
|
||||
|
||||
# Get absolute parent of path
|
||||
|
||||
def path_parent(in_path):
|
||||
""" Get absolute parent of path """
|
||||
|
||||
return Path(in_path).parent.absolute()
|
||||
|
||||
# Get final path component, with suffix
|
||||
def path_name(in_path):
|
||||
return PurePath(in_path).name
|
||||
|
||||
# Get final path component, w/o suffix
|
||||
def path_name(in_path, limit=False):
|
||||
""" Get final path component, with suffix """
|
||||
|
||||
comp_name = PurePath(in_path).name
|
||||
|
||||
if limit and get_os_ver()[1]:
|
||||
comp_name = comp_name[:MAX_WIN_COMP_LEN - len(extract_suffix())]
|
||||
|
||||
return comp_name
|
||||
|
||||
|
||||
def path_stem(in_path):
|
||||
""" Get final path component, w/o suffix """
|
||||
|
||||
return PurePath(in_path).stem
|
||||
|
||||
# Get list of path file extensions
|
||||
|
||||
def path_suffixes(in_path):
|
||||
""" Get list of path file extensions """
|
||||
|
||||
return PurePath(in_path).suffixes or ['']
|
||||
|
||||
# Check if path is absolute
|
||||
|
||||
def is_path_absolute(in_path):
|
||||
""" Check if 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):
|
||||
""" Create folder(s), controlling parents, existence and prior deletion """
|
||||
|
||||
if delete:
|
||||
del_dirs(in_path)
|
||||
|
||||
|
||||
Path.mkdir(Path(in_path), parents=parents, exist_ok=exist_ok)
|
||||
|
||||
# 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):
|
||||
""" Copy file to path with or w/o metadata """
|
||||
|
||||
if meta:
|
||||
shutil.copy2(in_path, out_path)
|
||||
else:
|
||||
shutil.copy(in_path, out_path)
|
||||
|
||||
# Clear read-only file attribute (on shutil.rmtree error)
|
||||
def clear_readonly(in_func, in_path, _):
|
||||
|
||||
def clear_readonly(in_path):
|
||||
""" Clear read-only file attribute """
|
||||
|
||||
os.chmod(in_path, stat.S_IWRITE)
|
||||
|
||||
|
||||
def clear_readonly_callback(in_func, in_path, _):
|
||||
""" Clear read-only file attribute (on shutil.rmtree error) """
|
||||
|
||||
clear_readonly(in_path)
|
||||
|
||||
in_func(in_path)
|
||||
|
||||
# Walk path to get all files
|
||||
|
||||
def get_path_files(in_path):
|
||||
""" Walk path to get all files """
|
||||
|
||||
path_files = []
|
||||
|
||||
|
||||
for root, _, files in os.walk(in_path):
|
||||
for name in files:
|
||||
path_files.append(os.path.join(root, name))
|
||||
|
||||
|
||||
return path_files
|
||||
|
||||
# Get path without leading/trailing quotes
|
||||
|
||||
def get_dequoted_path(in_path):
|
||||
""" Get path without leading/trailing quotes """
|
||||
|
||||
out_path = to_string(in_path).strip()
|
||||
|
||||
if len(out_path) >= 2 and is_encased(out_path, ("'",'"')):
|
||||
|
||||
if len(out_path) >= 2 and is_encased(out_path, ("'", '"')):
|
||||
out_path = out_path[1:-1]
|
||||
|
||||
|
||||
return out_path
|
||||
|
||||
# Set utility extraction stem
|
||||
|
||||
def extract_suffix():
|
||||
""" Set utility extraction stem """
|
||||
|
||||
return '_extracted'
|
||||
|
||||
# Get utility extraction path
|
||||
|
||||
def get_extract_path(in_path, suffix=extract_suffix()):
|
||||
""" Get utility extraction path """
|
||||
|
||||
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():
|
||||
""" Get runtime's root directory """
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
root = Path(sys.executable).parent
|
||||
else:
|
||||
root = project_root()
|
||||
|
||||
|
||||
return real_path(root)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
Copyright (C) 2022-2024 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import re
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
Copyright (C) 2022-2024 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import pefile
|
||||
|
@ -10,40 +10,57 @@ import pefile
|
|||
from common.system import printer
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is a PE file
|
||||
def is_pe_file(in_file):
|
||||
return bool(get_pe_file(in_file))
|
||||
|
||||
# Get pefile object from PE file
|
||||
def get_pe_file(in_file, fast=True):
|
||||
def is_pe_file(in_file: str | bytes) -> bool:
|
||||
""" 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)
|
||||
|
||||
|
||||
pe_file = None
|
||||
|
||||
try:
|
||||
# Analyze detected MZ > PE image buffer
|
||||
pe_file = pefile.PE(data=in_buffer, fast_load=fast)
|
||||
except Exception:
|
||||
pe_file = None
|
||||
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
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
|
||||
|
||||
# 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:
|
||||
# When fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed prior to FileInfo > StringTable
|
||||
pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']])
|
||||
|
||||
|
||||
# Retrieve MZ > PE > FileInfo > StringTable information
|
||||
pe_info = pe_file.FileInfo[0][0].StringTable[0].entries
|
||||
except Exception:
|
||||
pe_info = {}
|
||||
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
if not silent:
|
||||
printer(f'Error: Could not get PE info from pefile object: {error}!', padding)
|
||||
|
||||
return pe_info
|
||||
|
||||
# Print PE info from pefile StringTable
|
||||
def show_pe_info(pe_info, padding=0):
|
||||
if type(pe_info).__name__ == 'dict':
|
||||
for title,value in pe_info.items():
|
||||
info_title = title.decode('utf-8','ignore').strip()
|
||||
info_value = value.decode('utf-8','ignore').strip()
|
||||
|
||||
def show_pe_info(pe_info: dict, padding: int = 0) -> None:
|
||||
""" Print PE info from pefile StringTable """
|
||||
|
||||
if isinstance(pe_info, dict):
|
||||
for title, value in pe_info.items():
|
||||
info_title = title.decode('utf-8', 'ignore').strip()
|
||||
info_value = value.decode('utf-8', 'ignore').strip()
|
||||
|
||||
if info_title and info_value:
|
||||
printer(f'{info_title}: {info_value}', padding, new_line=False)
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
Copyright (C) 2022-2024 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
|
||||
char = ctypes.c_char
|
||||
uint8_t = ctypes.c_ubyte
|
||||
uint16_t = ctypes.c_ushort
|
||||
uint32_t = ctypes.c_uint
|
||||
uint64_t = ctypes.c_uint64
|
||||
Char: type[ctypes.c_char] | int = ctypes.c_char
|
||||
UInt8: type[ctypes.c_ubyte] | int = ctypes.c_ubyte
|
||||
UInt16: type[ctypes.c_ushort] | int = ctypes.c_ushort
|
||||
UInt32: type[ctypes.c_uint] | int = ctypes.c_uint
|
||||
UInt64: type[ctypes.c_uint64] | int = ctypes.c_uint64
|
||||
|
||||
|
||||
# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky
|
||||
def get_struct(buffer, start_offset, class_name, param_list=None):
|
||||
if param_list is None:
|
||||
param_list = []
|
||||
"""
|
||||
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_data = buffer[start_offset:start_offset + struct_len]
|
||||
|
||||
fit_len = min(len(struct_data), struct_len)
|
||||
|
||||
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)
|
||||
|
|
|
@ -1,68 +1,84 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
Copyright (C) 2022-2024 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from common.text_ops import padder, to_string
|
||||
|
||||
# Get Python Version (tuple)
|
||||
|
||||
def get_py_ver():
|
||||
""" Get Python Version (tuple) """
|
||||
|
||||
return sys.version_info
|
||||
|
||||
# Get OS Platform (string)
|
||||
|
||||
def get_os_ver():
|
||||
""" Get OS Platform (string) """
|
||||
|
||||
sys_os = sys.platform
|
||||
|
||||
|
||||
is_win = sys_os == 'win32'
|
||||
|
||||
is_lnx = sys_os.startswith('linux') or sys_os == 'darwin' or sys_os.find('bsd') != -1
|
||||
|
||||
|
||||
return sys_os, is_win, is_win or is_lnx
|
||||
|
||||
# Check for --auto-exit|-e
|
||||
|
||||
def is_auto_exit():
|
||||
""" Check for --auto-exit|-e """
|
||||
|
||||
return bool('--auto-exit' in sys.argv or '-e' in sys.argv)
|
||||
|
||||
# Check Python Version
|
||||
|
||||
def check_sys_py():
|
||||
""" # Check Python Version """
|
||||
|
||||
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]}!')
|
||||
|
||||
|
||||
if not is_auto_exit():
|
||||
# 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)
|
||||
|
||||
# Check OS Platform
|
||||
|
||||
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:
|
||||
printer(f'Error: Unsupported platform "{os_tag}"!')
|
||||
|
||||
|
||||
if not is_auto_exit():
|
||||
input('\nPress enter to exit')
|
||||
|
||||
sys.exit(126)
|
||||
|
||||
|
||||
sys.exit(126)
|
||||
|
||||
# Fix Windows Unicode console redirection
|
||||
if os_win:
|
||||
# noinspection PyUnresolvedReferences
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Show message(s) while controlling padding, newline, pausing & separator
|
||||
def printer(in_message='', padd_count=0, new_line=True, pause=False, sep_char=' '):
|
||||
message = to_string(in_message, sep_char)
|
||||
|
||||
padding = padder(padd_count)
|
||||
|
||||
|
||||
def printer(message=None, padd=0, new_line=True, pause=False, sep_char=' '):
|
||||
""" Show message(s), controlling padding, newline, pausing & separator """
|
||||
|
||||
message_input = '' if message is None else message
|
||||
|
||||
string = to_string(message_input, sep_char)
|
||||
|
||||
padding = padder(padd)
|
||||
|
||||
newline = '\n' if new_line else ''
|
||||
|
||||
output = newline + padding + message
|
||||
|
||||
(input if pause and not is_auto_exit() else print)(output)
|
||||
|
||||
message_output = newline + padding + string
|
||||
|
||||
(input if pause and not is_auto_exit() else print)(message_output)
|
||||
|
|
|
@ -1,111 +1,122 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
Copyright (C) 2022-2024 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import ctypes
|
||||
import os
|
||||
import sys
|
||||
import ctypes
|
||||
import argparse
|
||||
import traceback
|
||||
|
||||
from common.num_ops import get_ordinal
|
||||
from common.path_ops import get_dequoted_path, get_extract_path, get_path_files, is_path_absolute, path_parent, runtime_root, safe_path
|
||||
from common.path_ops import (get_dequoted_path, get_extract_path, get_path_files,
|
||||
is_path_absolute, path_name, path_parent, real_path, runtime_root)
|
||||
from common.system import check_sys_os, check_sys_py, get_os_ver, is_auto_exit, printer
|
||||
|
||||
|
||||
class BIOSUtility:
|
||||
|
||||
""" Template utility class for BIOSUtilities """
|
||||
|
||||
MAX_FAT32_ITEMS = 65535
|
||||
|
||||
def __init__(self, title, check, main, padding=0):
|
||||
|
||||
def __init__(self, title, check, main, args=None, padding=0):
|
||||
self._title = title
|
||||
self._main = main
|
||||
self._check = check
|
||||
self._arg_defs = args if args is not None else []
|
||||
self._padding = padding
|
||||
|
||||
self._arguments_kw = {}
|
||||
|
||||
self._arguments_kw_dest = []
|
||||
|
||||
# Initialize argparse argument parser
|
||||
self._argparser = argparse.ArgumentParser()
|
||||
|
||||
|
||||
self._argparser.add_argument('files', type=argparse.FileType('r', encoding='utf-8'), nargs='*')
|
||||
self._argparser.add_argument('-e', '--auto-exit', help='skip all user action prompts', action='store_true')
|
||||
self._argparser.add_argument('-v', '--version', help='show utility name and version', action='store_true')
|
||||
self._argparser.add_argument('-o', '--output-dir', help='extract in given output directory')
|
||||
self._argparser.add_argument('-i', '--input-dir', help='extract from given input directory')
|
||||
|
||||
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
|
||||
sys.excepthook = self._exception_handler
|
||||
|
||||
|
||||
# Check Python Version
|
||||
check_sys_py()
|
||||
|
||||
|
||||
# Check OS Platform
|
||||
check_sys_os()
|
||||
|
||||
|
||||
# Show Script Title
|
||||
printer(self._title, new_line=False)
|
||||
|
||||
|
||||
# Show Utility Version on demand
|
||||
if self._arguments.version:
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
# Set console/terminal window title (Windows only)
|
||||
if get_os_ver()[1]:
|
||||
ctypes.windll.kernel32.SetConsoleTitleW(self._title)
|
||||
|
||||
|
||||
# Process input files and generate output path
|
||||
self._process_input_files()
|
||||
|
||||
|
||||
# Count input files for exit code
|
||||
self._exit_code = len(self._input_files)
|
||||
|
||||
def parse_argument(self, *args, **kwargs):
|
||||
_dest = self._argparser.add_argument(*args, **kwargs).dest
|
||||
self._arguments = self._argparser.parse_known_args(self._arguments_unk)[0]
|
||||
self._arguments_kw.update({_dest: self._arguments.__dict__[_dest]})
|
||||
|
||||
|
||||
def run_utility(self):
|
||||
""" Run utility after checking for supported format """
|
||||
|
||||
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)
|
||||
|
||||
|
||||
if not self._check(_input_file):
|
||||
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))
|
||||
|
||||
|
||||
if os.path.isdir(_extract_path):
|
||||
for _suffix in range(2, self.MAX_FAT32_ITEMS):
|
||||
_renamed_path = f'{os.path.normpath(_extract_path)}_{get_ordinal(_suffix)}'
|
||||
|
||||
|
||||
if not os.path.isdir(_renamed_path):
|
||||
_extract_path = _renamed_path
|
||||
|
||||
break # Extract path is now unique
|
||||
|
||||
|
||||
break # Extract path is now unique
|
||||
|
||||
if self._main(_input_file, _extract_path, self._padding + 4, **self._arguments_kw) in [0, None]:
|
||||
self._exit_code -= 1
|
||||
|
||||
|
||||
printer('Done!', pause=True)
|
||||
|
||||
|
||||
sys.exit(self._exit_code)
|
||||
|
||||
# Process input files
|
||||
def _process_input_files(self):
|
||||
self._input_files = []
|
||||
|
||||
|
||||
if len(sys.argv) >= 2:
|
||||
# Drag & Drop or CLI
|
||||
if self._arguments.input_dir:
|
||||
_input_path_user = self._arguments.input_dir
|
||||
_input_path_full = self._get_input_path(_input_path_user) if _input_path_user else ''
|
||||
_input_path_full = self._get_user_path(_input_path_user) if _input_path_user else ''
|
||||
self._input_files = get_path_files(_input_path_full)
|
||||
else:
|
||||
# Parse list of input files (i.e. argparse FileType objects)
|
||||
|
@ -114,25 +125,25 @@ class BIOSUtility:
|
|||
self._input_files.append(_file_object.name)
|
||||
# Close each argparse FileType object (i.e. allow input file changes)
|
||||
_file_object.close()
|
||||
|
||||
|
||||
# Set output fallback value for missing argparse Output and Input Path
|
||||
_output_fallback = path_parent(self._input_files[0]) if self._input_files else None
|
||||
|
||||
|
||||
# Set output path via argparse Output path or argparse Input path or first input file path
|
||||
_output_path = self._arguments.output_dir or self._arguments.input_dir or _output_fallback
|
||||
else:
|
||||
# Script w/o parameters
|
||||
_input_path_user = get_dequoted_path(input('\nEnter input directory path: '))
|
||||
_input_path_full = self._get_input_path(_input_path_user) if _input_path_user else ''
|
||||
_input_path_full = self._get_user_path(_input_path_user) if _input_path_user else ''
|
||||
self._input_files = get_path_files(_input_path_full)
|
||||
|
||||
|
||||
_output_path = get_dequoted_path(input('\nEnter output directory path: '))
|
||||
|
||||
self._output_path = self._get_input_path(_output_path)
|
||||
|
||||
# Get absolute input file path
|
||||
|
||||
self._output_path = self._get_user_path(_output_path)
|
||||
|
||||
# Get absolute user file path
|
||||
@staticmethod
|
||||
def _get_input_path(input_path):
|
||||
def _get_user_path(input_path):
|
||||
if not input_path:
|
||||
# Use runtime directory if no user path is specified
|
||||
absolute_path = runtime_root()
|
||||
|
@ -142,8 +153,8 @@ class BIOSUtility:
|
|||
absolute_path = input_path
|
||||
# Otherwise, make it runtime directory relative
|
||||
else:
|
||||
absolute_path = safe_path(runtime_root(), input_path)
|
||||
|
||||
absolute_path = real_path(input_path)
|
||||
|
||||
return absolute_path
|
||||
|
||||
# https://stackoverflow.com/a/781074 by Torsten Marek
|
||||
|
@ -153,7 +164,7 @@ class BIOSUtility:
|
|||
printer('')
|
||||
else:
|
||||
printer('Error: Utility crashed, please report the following:\n')
|
||||
|
||||
|
||||
traceback.print_exception(exc_type, exc_value, exc_traceback)
|
||||
|
||||
if not is_auto_exit():
|
||||
|
|
|
@ -1,33 +1,48 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
#!/usr/bin/env python3 -B
|
||||
# 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):
|
||||
""" Generate padding (spaces or tabs) """
|
||||
|
||||
return ('\t' if tab else ' ') * padd_count
|
||||
|
||||
# Get String from given input object
|
||||
|
||||
def to_string(in_object, sep_char=''):
|
||||
if type(in_object).__name__ in ('list','tuple'):
|
||||
""" Get String from given input object """
|
||||
|
||||
if type(in_object).__name__ in ('list', 'tuple'):
|
||||
out_string = sep_char.join(map(str, in_object))
|
||||
else:
|
||||
out_string = str(in_object)
|
||||
|
||||
|
||||
return out_string
|
||||
|
||||
# Get Bytes from given buffer or file path
|
||||
|
||||
def file_to_bytes(in_object):
|
||||
""" Get Bytes from given buffer or file path """
|
||||
|
||||
object_bytes = in_object
|
||||
|
||||
if type(in_object).__name__ not in ('bytes','bytearray'):
|
||||
|
||||
if type(in_object).__name__ not in ('bytes', 'bytearray'):
|
||||
with open(to_string(in_object), 'rb') as object_data:
|
||||
object_bytes = object_data.read()
|
||||
|
||||
|
||||
return object_bytes
|
||||
|
||||
# Check if string starts and ends with given character(s)
|
||||
|
||||
def bytes_to_hex(buffer: bytes, order: str, data_len: int, slice_len: int | None = None) -> str:
|
||||
""" Converts bytes to hex string, controlling endianess, data size and string slicing """
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
return f'{int.from_bytes(buffer, order):0{data_len * 2}X}'[:slice_len]
|
||||
|
||||
|
||||
def is_encased(in_string, chars):
|
||||
""" Check if string starts and ends with given character(s) """
|
||||
|
||||
return in_string.startswith(chars) and in_string.endswith(chars)
|
||||
|
|
2
external/requirements.txt
vendored
2
external/requirements.txt
vendored
|
@ -1,2 +0,0 @@
|
|||
lznt1 >= 0.2
|
||||
pefile >= 2022.5.30
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
dissect.util == 3.15
|
||||
pefile == 2023.2.7
|
Loading…
Add table
Add a link
Reference in a new issue