BIOSUtilities/Apple_EFI_PBZX.py

129 lines
3.9 KiB
Python
Raw Permalink Normal View History

#!/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()