BIOSUtilities/Apple_EFI_PBZX.py
Plato Mavropoulos d85a7f82dc Added AMI PFAT RSA 3K signed blocks support
Added AMI PFAT nested detection at each file

Added Award BIOS payload naming at each file

Switched Panasonic BIOS LZNT1 external library

Improved Panasonic LZNT1 detection and length

Improved Dell PFS code structure and fixed bugs

Improved code exception handling (raise, catch)

Improved code definitions (PEP8, docs, types)

Fixed some arguments missing from help screens
2024-04-24 01:22:53 +03:00

128 lines
3.9 KiB
Python

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