diff --git a/Apple_EFI_Split.py b/Apple_EFI_Split.py new file mode 100644 index 0000000..930d54e --- /dev/null +++ b/Apple_EFI_Split.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +#coding=utf-8 + +""" +Apple EFI Split +Apple EFI IM4P Splitter +Copyright (C) 2018-2022 Plato Mavropoulos +""" + +TITLE = 'Apple EFI IM4P Splitter v3.0_a2' + +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 +from common.system import argparse_init, printer, script_init +from common.text_ops import file_to_bytes + +# Check if input is Apple EFI IM4P image +def is_apple_im4p(input_file): + 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, output_path, padding=0): + input_buffer = file_to_bytes(input_file) + + extract_path = os.path.join(f'{output_path}_extracted') + + 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() + 0x9:im4p_match.end() + 0xD], '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_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) + +# Intel Flash Descriptor Component Sizes (4MB, 8MB, 16MB and 32MB) +IFD_COMP_LEN = {3: 0x400000, 4: 0x800000, 5: 0x1000000, 6: 0x2000000} + +if __name__ == '__main__': + # Set argparse Arguments + argparser = argparse_init() + arguments = argparser.parse_args() + + # Initialize script (must be after argparse) + exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4) + + for input_file in input_files: + input_name = os.path.basename(input_file) + + printer(['***', input_name], padding - 4) + + if not is_apple_im4p(input_file): + printer('Error: This is not an Apple EFI IM4P image!', padding) + + continue # Next input file + + extract_path = os.path.join(output_path, input_name) + + apple_im4p_split(input_file, extract_path, padding) + + exit_code -= 1 + + printer('Done!', pause=True) + + sys.exit(exit_code) diff --git a/README.md b/README.md index d8eaaa1..897f3ad 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ * [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor) * [**AMI UCP Update Extractor**](#ami-ucp-update-extractor) +* [**Apple EFI IM4P Splitter**](#apple-efi-im4p-splitter) * [**Apple EFI Image Identifier**](#apple-efi-image-identifier) * [**Award BIOS Module Extractor**](#award-bios-module-extractor) * [**Dell PFS/PKG Update Extractor**](#dell-pfspkg-update-extractor) @@ -143,6 +144,58 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con ![]() +## **Apple EFI IM4P Splitter** + +![]() + +#### **Description** + +Parses Apple IM4P multi-EFI files and splits all detected EFI firmware into separate Intel SPI/BIOS images. + +#### **Usage** + +You can either Drag & Drop or manually enter Apple EFI IM4P file(s). Optional arguments: + +* -h or --help : show help message and exit +* -v or --version : show utility name and version +* -i or --input-dir : extract from given input directory +* -o or --output-dir : extract in given output directory +* -e or --auto-exit : skip press enter to exit prompts + +#### **Compatibility** + +Should work at all Windows, Linux or macOS operating systems which have Python 3.10 support. + +#### **Prerequisites** + +To run the utility, you do not need any prerequisites. + +#### **Build/Freeze/Compile with PyInstaller** + +PyInstaller can build/freeze/compile the utility at all three supported platforms, it is simple to run and gets updated often. + +1. Make sure Python 3.10.0 or newer is installed: + +> python --version + +2. Use pip to install PyInstaller: + +> pip3 install pyinstaller + +3. Build/Freeze/Compile: + +> pyinstaller --noupx --onefile \\/Apple_EFI_Split.py + +At dist folder you should find the final utility executable + +#### **Anti-Virus False Positives** + +Some Anti-Virus software may claim that the built/frozen/compiled executable contains viruses. Any such detections are false positives, usually of PyInstaller. You can switch to a better Anti-Virus software, report the false positive to their support, add the executable to the exclusions, build/freeze/compile yourself or use the Python script directly. + +#### **Pictures** + +![]() + ## **Apple EFI Image Identifier** ![]() diff --git a/common/patterns.py b/common/patterns.py index 018833f..27405e8 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -10,6 +10,7 @@ import re PAT_AMI_PFAT = re.compile(br'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}@', re.DOTALL) PAT_APPLE_EFI = re.compile(br'\$IBIOSI\$.{16}\x2E\x00.{6}\x2E\x00.{8}\x2E\x00.{6}\x2E\x00.{20}\x00{2}', re.DOTALL) +PAT_APPLE_IM4P = re.compile(br'\x16\x04IM4P\x16\x04mefi') PAT_AWARD_LZH = re.compile(br'-lh[04567]-') PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90') PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL) @@ -18,6 +19,7 @@ PAT_FUJITSU_SFX = re.compile(br'FjSfxBinay\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4} PAT_INSYDE_IFL = re.compile(br'\$_IFLASH') PAT_INSYDE_SFX = re.compile(br'\x0D\x0A;!@InstallEnd@!\x0D\x0A(7z\xBC\xAF\x27|\x6E\xF4\x79\x5F\x4E)') PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL) +PAT_INTEL_IFD = re.compile(br'\x5A\xA5\xF0\x0F.{172}\xFF{16}', re.DOTALL) PAT_MICROSOFT_CAB = re.compile(br'MSCF\x00{4}') PAT_MICROSOFT_MZ = re.compile(br'MZ') PAT_MICROSOFT_PE = re.compile(br'PE\x00{2}')