2024-04-23 18:22:53 -04:00
|
|
|
#!/usr/bin/env python3 -B
|
|
|
|
# coding=utf-8
|
2022-08-15 11:29:58 -04:00
|
|
|
|
|
|
|
"""
|
2022-08-28 13:02:55 -04:00
|
|
|
Apple EFI ID
|
2022-08-15 11:29:58 -04:00
|
|
|
Apple EFI Image Identifier
|
2024-04-23 18:22:53 -04:00
|
|
|
Copyright (C) 2018-2024 Plato Mavropoulos
|
2022-08-15 11:29:58 -04:00
|
|
|
"""
|
|
|
|
|
2024-04-23 18:22:53 -04:00
|
|
|
import ctypes
|
|
|
|
import logging
|
2022-08-15 11:29:58 -04:00
|
|
|
import os
|
|
|
|
import struct
|
|
|
|
import subprocess
|
2024-04-23 18:22:53 -04:00
|
|
|
import zlib
|
2022-08-15 11:29:58 -04:00
|
|
|
|
2024-04-23 18:22:53 -04:00
|
|
|
from common.externals import get_uefiextract_path, get_uefifind_path
|
2022-08-15 11:29:58 -04:00
|
|
|
from common.path_ops import del_dirs, path_parent, path_suffixes
|
|
|
|
from common.patterns import PAT_APPLE_EFI
|
2024-04-23 18:22:53 -04:00
|
|
|
from common.struct_ops import Char, get_struct, UInt8
|
2022-09-12 16:09:12 -04:00
|
|
|
from common.system import printer
|
|
|
|
from common.templates import BIOSUtility
|
2022-08-15 11:29:58 -04:00
|
|
|
from common.text_ops import file_to_bytes
|
|
|
|
|
2024-04-23 18:22:53 -04:00
|
|
|
TITLE = 'Apple EFI Image Identifier v3.0'
|
|
|
|
|
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
class IntelBiosId(ctypes.LittleEndianStructure):
|
2024-04-23 18:22:53 -04:00
|
|
|
""" Intel BIOS ID Structure """
|
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
_pack_ = 1
|
|
|
|
_fields_ = [
|
2024-04-23 18:22:53 -04:00
|
|
|
('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
|
2022-08-15 11:29:58 -04:00
|
|
|
# 0x4A
|
|
|
|
]
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
# https://github.com/tianocore/edk2-platforms/blob/master/Platform/Intel/BoardModulePkg/Include/Guid/BiosId.h
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
@staticmethod
|
2024-04-23 18:22:53 -04:00
|
|
|
def _decode(field):
|
|
|
|
return struct.pack('B' * len(field), *field).decode('utf-16', 'ignore').strip('\x00 ')
|
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
def get_bios_id(self):
|
2024-04-23 18:22:53 -04:00
|
|
|
""" 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)
|
|
|
|
|
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
def is_apple_efi(input_file):
|
2024-04-23 18:22:53 -04:00
|
|
|
""" Check if input is Apple EFI image """
|
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
input_buffer = file_to_bytes(input_file)
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
if PAT_APPLE_EFI.search(input_buffer):
|
|
|
|
return True
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
if not os.path.isfile(input_file):
|
|
|
|
return False
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
try:
|
|
|
|
_ = subprocess.run([get_uefifind_path(), input_file, 'body', 'list', PAT_UEFIFIND],
|
2024-04-23 18:22:53 -04:00
|
|
|
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
return True
|
2024-04-23 18:22:53 -04:00
|
|
|
except Exception as error: # pylint: disable=broad-except
|
|
|
|
logging.debug('Error: Could not check if input is Apple EFI image: %s', error)
|
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
return False
|
|
|
|
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-09-12 16:09:12 -04:00
|
|
|
def apple_efi_identify(input_file, extract_path, padding=0, rename=False):
|
2024-04-23 18:22:53 -04:00
|
|
|
""" Parse & Identify (or Rename) Apple EFI image """
|
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
if not os.path.isfile(input_file):
|
|
|
|
printer('Error: Could not find input file path!', padding)
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
return 1
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
input_buffer = file_to_bytes(input_file)
|
2024-04-23 18:22:53 -04:00
|
|
|
|
|
|
|
bios_id_match = PAT_APPLE_EFI.search(input_buffer) # Detect $IBIOSI$ pattern
|
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
if bios_id_match:
|
|
|
|
bios_id_res = f'0x{bios_id_match.start():X}'
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
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],
|
2024-04-23 18:22:53 -04:00
|
|
|
text=True)[:36]
|
|
|
|
|
|
|
|
del_dirs(extract_path) # UEFIExtract must create its output folder itself, make sure it is not present
|
|
|
|
|
2022-09-12 16:09:12 -04:00
|
|
|
_ = subprocess.run([get_uefiextract_path(), input_file, bios_id_res, '-o', extract_path, '-m', 'body'],
|
2024-04-23 18:22:53 -04:00
|
|
|
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
|
|
|
2022-09-12 16:09:12 -04:00
|
|
|
with open(os.path.join(extract_path, 'body.bin'), 'rb') as raw_body:
|
2022-08-15 11:29:58 -04:00
|
|
|
body_buffer = raw_body.read()
|
2024-04-23 18:22:53 -04:00
|
|
|
|
|
|
|
bios_id_match = PAT_APPLE_EFI.search(body_buffer) # Detect decompressed $IBIOSI$ pattern
|
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
bios_id_hdr = get_struct(body_buffer, bios_id_match.start(), IntelBiosId)
|
2024-04-23 18:22:53 -04:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
return 2
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
printer(f'Detected $IBIOSI$ at {bios_id_res}\n', padding)
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
bios_id_hdr.struct_print(padding + 4)
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
if rename:
|
|
|
|
input_parent = path_parent(input_file)
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-28 13:02:55 -04:00
|
|
|
input_suffix = path_suffixes(input_file)[-1]
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
input_adler32 = zlib.adler32(input_buffer)
|
2024-04-23 18:22:53 -04:00
|
|
|
|
|
|
|
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}'
|
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
output_file = os.path.join(input_parent, output_name)
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
if not os.path.isfile(output_file):
|
2024-04-23 18:22:53 -04:00
|
|
|
os.replace(input_file, output_file) # Rename input file based on its EFI tag
|
|
|
|
|
2022-08-28 13:02:55 -04:00
|
|
|
printer(f'Renamed to {output_name}', padding)
|
2024-04-23 18:22:53 -04:00
|
|
|
|
2022-08-15 11:29:58 -04:00
|
|
|
return 0
|
|
|
|
|
2024-04-23 18:22:53 -04:00
|
|
|
|
|
|
|
PAT_UEFIFIND = f'244942494F534924{"." * 32}2E00{"." * 12}2E00{"." * 16}2E00{"." * 12}2E00{"." * 40}0000'
|
2022-08-15 11:29:58 -04:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2024-04-23 18:22:53 -04:00
|
|
|
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)
|
|
|
|
|
2022-09-12 16:09:12 -04:00
|
|
|
utility.run_utility()
|