From fa1283ee54066b0d24d8967d83dc23afe94b2e1d Mon Sep 17 00:00:00 2001 From: Plato Mavropoulos Date: Sun, 6 Dec 2020 19:23:44 +0200 Subject: [PATCH] AMI BIOS Guard Extractor v3.0 Added AMI PFAT Component new extraction method Added AMI PFAT Nested PFAT Component extraction Added Intel BIOS Guard Block Header detailed info Added Intel BIOS Guard Block Script decompilation Applied various code fixes & improvements ABGE v3.0 utility's extracting behavior has now changed. Please read the new README Description. The optional support for Intel BGSL decompilation is provided by the 3rd party dependency "BIOS Guard Script Tool" by @allowitsme. Thanks to @NikolajSchlej for bringing ABGE's v2.0 problematic extraction behavior to my attention. --- AMI BIOS Guard Extractor/AMI_PFAT_Extract.py | 261 ++++++++++++++----- README.md | 20 +- 2 files changed, 210 insertions(+), 71 deletions(-) diff --git a/AMI BIOS Guard Extractor/AMI_PFAT_Extract.py b/AMI BIOS Guard Extractor/AMI_PFAT_Extract.py index ae2a82e..a3277a2 100644 --- a/AMI BIOS Guard Extractor/AMI_PFAT_Extract.py +++ b/AMI BIOS Guard Extractor/AMI_PFAT_Extract.py @@ -1,17 +1,51 @@ #!/usr/bin/env python3 +#coding=utf-8 """ AMI PFAT Extract AMI BIOS Guard Extractor -Copyright (C) 2018-2019 Plato Mavropoulos +Copyright (C) 2018-2020 Plato Mavropoulos """ -print('AMI BIOS Guard Extractor v2.0') +print('AMI BIOS Guard Extractor v3.0') + +import sys + +# Detect Python version +sys_ver = sys.version_info +if sys_ver < (3,7) : + sys.stdout.write('\n\nError: Python >= 3.7 required, not %d.%d!\n' % (sys_ver[0], sys_ver[1])) + (raw_input if sys_ver[0] <= 2 else input)('\nPress enter to exit') + sys.exit(1) import os -import sys +import re import ctypes import struct +import shutil +import traceback + +# https://stackoverflow.com/a/781074 by Torsten Marek +def show_exception_and_exit(exc_type, exc_value, tb) : + if exc_type is KeyboardInterrupt : + print('\n') + else : + print('\nError: ABGE crashed, please report the following:\n') + traceback.print_exception(exc_type, exc_value, tb) + input('\nPress enter to exit') + sys.exit(1) + +# Pause after any unexpected python exception +sys.excepthook = show_exception_and_exit + +sys.dont_write_bytecode = True + +# https://github.com/allowitsme/big-tool by Dmitry Frolov +try : + from big_script_tool import BigScript + is_bgst = True +except : + is_bgst = False # Set ctypes Structure types char = ctypes.c_char @@ -20,37 +54,37 @@ uint16_t = ctypes.c_ushort uint32_t = ctypes.c_uint uint64_t = ctypes.c_uint64 -# noinspection PyTypeChecker class PFAT_Header(ctypes.LittleEndianStructure) : _pack_ = 1 _fields_ = [ ('Size', uint32_t), # 0x00 - ('Validation', uint32_t), # 0x04 Unknown 16-bit Checksum ? + ('Checksum', uint32_t), # 0x04 Unknown 16-bits ('Tag', char*8), # 0x04 _AMIPFAT - ('Control', uint8_t), # 0x10 0x4 + ('Flags', uint8_t), # 0x10 # 0x11 ] def pfat_print(self) : print('\nPFAT Main Header:\n') - print(' Size : 0x%X' % self.Size) - print(' Validation : 0x%X' % self.Validation) - print(' Tag : %s' % self.Tag.decode('utf-8')) - print(' Control : 0x%X' % self.Control) + print(' Size : 0x%X' % self.Size) + print(' Checksum : 0x%0.4X' % self.Checksum) + print(' Tag : %s' % self.Tag.decode('utf-8')) + print(' Flags : 0x%0.2X' % self.Flags) -# noinspection PyTypeChecker class PFAT_Block_Header(ctypes.LittleEndianStructure) : _pack_ = 1 _fields_ = [ - ('Revision', uint32_t), # 0x00 PFAT - ('Platform', char*16), # 0x04 - ('Unknown0', uint32_t), # 0x14 - ('Unknown1', uint32_t), # 0x18 - ('FlagsSize', uint32_t), # 0x1C From Block Header end - ('DataSize', uint32_t), # 0x20 From Block Flags end - ('Unknown2', uint32_t), # 0x24 - ('Unknown3', uint32_t), # 0x28 - ('Unknown4', uint32_t), # 0x2C + ('PFATVerMajor', uint16_t), # 0x00 + ('PFATVerMinor', 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 # 0x30 ] @@ -58,19 +92,53 @@ class PFAT_Block_Header(ctypes.LittleEndianStructure) : super().__init__(*args, **kwargs) self.count = count - def pfat_print(self) : - print('\n PFAT Block %s Header:\n' % self.count) - print(' Revision : %d' % self.Revision) - print(' Platform : %s' % self.Platform.decode('utf-8')) - print(' Unknown 0 : 0x%X' % self.Unknown0) - print(' Unknown 1 : 0x%X' % self.Unknown1) - print(' Flags Size : 0x%X' % self.FlagsSize) - print(' Data Size : 0x%X' % self.DataSize) - print(' Unknown 2 : 0x%X' % self.Unknown2) - print(' Unknown 3 : 0x%X' % self.Unknown3) - print(' Unknown 4 : 0x%X' % self.Unknown4) + def get_flags(self) : + attr = PFAT_Block_Header_GetAttributes() + attr.asbytes = self.Attributes -# noinspection PyTypeChecker + return attr.b.SFAM, attr.b.ProtectEC, attr.b.GFXMitDis, attr.b.FTU, attr.b.Reserved + + def pfat_print(self) : + no_yes = ['No','Yes'] + f1,f2,f3,f4,f5 = self.get_flags() + + PlatformID = bytes(self.PlatformID).strip(b'\x00') + if PlatformID.isalpha() : # STRING + PlatformID = PlatformID.decode('utf-8', 'ignore') + else : # GUID + PlatformID = '%0.*X' % (0x10 * 2, int.from_bytes(self.PlatformID, 'big')) + PlatformID = '{%s-%s-%s-%s-%s}' % (PlatformID[:8], PlatformID[8:12], PlatformID[12:16], PlatformID[16:20], PlatformID[20:]) + + print('\n PFAT Block %s Header:\n' % self.count) + print(' PFAT Version : %d.%d' % (self.PFATVerMajor, self.PFATVerMinor)) + print(' Platform ID : %s' % PlatformID) + print(' Signed Flash Address Map : %s' % no_yes[f1]) + print(' Protected EC OpCodes : %s' % no_yes[f2]) + print(' Graphics Security Disable : %s' % no_yes[f3]) + print(' Fault Tolerant Update : %s' % no_yes[f4]) + print(' Attributes Reserved : 0x%X' % f5) + print(' Script Version : %d.%d' % (self.ScriptVerMajor, self.ScriptVerMinor)) + print(' Script Size : 0x%X' % self.ScriptSize) + print(' Data Size : 0x%X' % self.DataSize) + print(' BIOS SVN : 0x%X' % self.BIOSSVN) + print(' EC SVN : 0x%X' % self.ECSVN) + print(' Vendor Info : 0x%X' % self.VendorInfo) + +class PFAT_Block_Header_Attributes(ctypes.LittleEndianStructure): + _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) + ] + +class PFAT_Block_Header_GetAttributes(ctypes.Union): + _fields_ = [ + ('b', PFAT_Block_Header_Attributes), + ('asbytes', uint32_t) + ] + class PFAT_Block_RSA(ctypes.LittleEndianStructure) : _pack_ = 1 _fields_ = [ @@ -87,17 +155,17 @@ class PFAT_Block_RSA(ctypes.LittleEndianStructure) : self.count = count def pfat_print(self) : - RSAPublicKey = ''.join('%0.8X' % int.from_bytes(struct.pack('= len(buffer)) or (fit_len < struct_len) : - print('Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name)) + input('\nError: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name.__name__)) sys.exit(1) ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len) @@ -116,72 +184,131 @@ def get_struct(buffer, start_offset, class_name, param_list = None) : if len(sys.argv) >= 2 : # Drag & Drop or CLI - pfat = sys.argv[1:] + ami_pfat = sys.argv[1:] else : # Folder path - pfat = [] + ami_pfat = [] in_path = input('\nEnter the full folder path: ') print('\nWorking...') for root, dirs, files in os.walk(in_path): for name in files : - pfat.append(os.path.join(root, name)) + ami_pfat.append(os.path.join(root, name)) -for input_file in pfat : - with open(input_file, 'rb') as in_file : buffer = in_file.read() +pfat_index = 1 +output_path = '' +block_hdr_size = ctypes.sizeof(PFAT_Block_Header) +block_rsa_size = ctypes.sizeof(PFAT_Block_RSA) +pfat_pat = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) + +for input_file in ami_pfat : + input_name,input_extension = os.path.splitext(os.path.basename(input_file)) + input_dir = os.path.dirname(os.path.abspath(input_file)) + + file_data = b'' final_image = b'' block_name = '' block_count = 0 + file_index = 0 blocks = [] - pfat_hdr = get_struct(buffer, 0, PFAT_Header) + with open(input_file, 'rb') as in_file : buffer = in_file.read() - if pfat_hdr.Tag.decode('utf-8', 'ignore') != '_AMIPFAT' : continue + pfat_match = pfat_pat.search(buffer) + + if not pfat_match : continue + + buffer = buffer[pfat_match.start() - 0x8:] + + pfat_hdr = get_struct(buffer, 0, PFAT_Header) hdr_size = pfat_hdr.Size hdr_data = buffer[0x11:hdr_size].decode('utf-8').splitlines() pfat_hdr.pfat_print() - print(' Title : %s' % hdr_data[0]) + print(' Title : %s' % hdr_data[0]) + + if pfat_index == 1 : + output_path = os.path.join(input_dir, '%s%s' % (input_name, input_extension) + '_extracted') # Set extraction directory + + if os.path.isdir(output_path) : shutil.rmtree(output_path) # Delete any existing extraction directory + + os.mkdir(output_path) # Create extraction directory + + file_path = os.path.join(output_path, '%d' % pfat_index) for entry in hdr_data[1:] : entry_data = entry.split(' ') entry_data = [s for s in entry_data if s != ''] - entry_flash = int(entry_data[0]) + entry_flags = int(entry_data[0]) entry_param = entry_data[1] entry_blocks = int(entry_data[2]) entry_name = entry_data[3][1:] - for i in range(entry_blocks) : blocks.append([entry_name, entry_param, entry_flash, i + 1, entry_blocks]) + for i in range(entry_blocks) : blocks.append([entry_name, entry_param, entry_flags, i + 1, entry_blocks]) block_count += entry_blocks block_start = hdr_size for i in range(block_count) : - if blocks[i][0] != block_name : print('\n%s (Parameter: %s, Update: %s)' % (blocks[i][0], blocks[i][1], ['No','Yes'][blocks[i][2]])) + is_file_start = blocks[i][0] != block_name + + if is_file_start : print('\n %s (Parameter: %s, Flags: 0x%X)' % (blocks[i][0], blocks[i][1], blocks[i][2])) + block_hdr = get_struct(buffer, block_start, PFAT_Block_Header, ['%d/%d' % (blocks[i][3], blocks[i][4])]) - block_hdr_size = ctypes.sizeof(PFAT_Block_Header) - block_flag_size = block_hdr.FlagsSize - flag_data = buffer[block_start + block_hdr_size:block_start + block_hdr_size + block_flag_size] # Flags not parsed - block_data_start = block_start + block_hdr_size + block_flag_size - block_data_end = block_data_start + block_hdr.DataSize block_hdr.pfat_print() + block_script_size = block_hdr.ScriptSize + block_script_data = buffer[block_start + block_hdr_size:block_start + block_hdr_size + block_script_size] + block_data_start = block_start + block_hdr_size + block_script_size + block_data_end = block_data_start + block_hdr.DataSize + block_data = buffer[block_data_start:block_data_end] + block_rsa = get_struct(buffer, block_data_end, PFAT_Block_RSA, ['%d/%d' % (blocks[i][3], blocks[i][4])]) - block_rsa_size = ctypes.sizeof(PFAT_Block_RSA) - #block_rsa_exp = block_rsa.Exponent - #block_rsa_pkey = int((''.join('%0.8X' % int.from_bytes(struct.pack(' pip3 install pyinstaller -3. Build/Freeze/Compile: +3. Copy BIOS Guard Script Tool dependency to build directory: + +> AMI_PFAT_Extract.py, big_script_tool.py + +4. Build/Freeze/Compile: > pyinstaller --noupx --onefile AMI_PFAT_Extract.py @@ -104,6 +112,10 @@ At dist folder you should find the final utility executable 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** + +![](https://i.imgur.com/iZD3GY0.png) + ## **Apple EFI Sucatalog Link Grabber** ![](https://i.imgur.com/zTVFs4I.png)