mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-12 22:26:13 -04:00

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.
314 lines
No EOL
11 KiB
Python
314 lines
No EOL
11 KiB
Python
#!/usr/bin/env python3
|
|
#coding=utf-8
|
|
|
|
"""
|
|
AMI PFAT Extract
|
|
AMI BIOS Guard Extractor
|
|
Copyright (C) 2018-2020 Plato Mavropoulos
|
|
"""
|
|
|
|
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 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
|
|
uint8_t = ctypes.c_ubyte
|
|
uint16_t = ctypes.c_ushort
|
|
uint32_t = ctypes.c_uint
|
|
uint64_t = ctypes.c_uint64
|
|
|
|
class PFAT_Header(ctypes.LittleEndianStructure) :
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('Size', uint32_t), # 0x00
|
|
('Checksum', uint32_t), # 0x04 Unknown 16-bits
|
|
('Tag', char*8), # 0x04 _AMIPFAT
|
|
('Flags', uint8_t), # 0x10
|
|
# 0x11
|
|
]
|
|
|
|
def pfat_print(self) :
|
|
print('\nPFAT Main Header:\n')
|
|
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)
|
|
|
|
class PFAT_Block_Header(ctypes.LittleEndianStructure) :
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('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
|
|
]
|
|
|
|
def __init__(self, count, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.count = count
|
|
|
|
def get_flags(self) :
|
|
attr = PFAT_Block_Header_GetAttributes()
|
|
attr.asbytes = self.Attributes
|
|
|
|
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_ = [
|
|
('Unknown0', uint32_t), # 0x00
|
|
('Unknown1', uint32_t), # 0x04
|
|
('PublicKey', uint32_t*64), # 0x08
|
|
('Exponent', uint32_t), # 0x108
|
|
('Signature', uint32_t*64), # 0x10C
|
|
# 0x20C
|
|
]
|
|
|
|
def __init__(self, count, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.count = count
|
|
|
|
def pfat_print(self) :
|
|
PublicKey = '%0.*X' % (0x100 * 2, int.from_bytes(self.PublicKey, 'little'))
|
|
Signature = '%0.*X' % (0x100 * 2, int.from_bytes(self.Signature, 'little'))
|
|
|
|
print('\n PFAT Block %s Signature:\n' % self.count)
|
|
print(' Unknown 0 : 0x%X' % self.Unknown0)
|
|
print(' Unknown 1 : 0x%X' % self.Unknown1)
|
|
print(' Public Key : %s [...]' % PublicKey[:8])
|
|
print(' Exponent : 0x%X' % self.Exponent)
|
|
print(' Signature : %s [...]' % Signature[:8])
|
|
|
|
# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky
|
|
def get_struct(buffer, start_offset, class_name, param_list = None) :
|
|
if param_list is None : param_list = []
|
|
|
|
structure = class_name(*param_list) # Unpack parameter list
|
|
struct_len = ctypes.sizeof(structure)
|
|
struct_data = buffer[start_offset:start_offset + struct_len]
|
|
fit_len = min(len(struct_data), struct_len)
|
|
|
|
if (start_offset >= len(buffer)) or (fit_len < struct_len) :
|
|
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)
|
|
|
|
return structure
|
|
|
|
if len(sys.argv) >= 2 :
|
|
# Drag & Drop or CLI
|
|
ami_pfat = sys.argv[1:]
|
|
else :
|
|
# Folder path
|
|
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 :
|
|
ami_pfat.append(os.path.join(root, name))
|
|
|
|
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 = []
|
|
|
|
with open(input_file, 'rb') as in_file : buffer = in_file.read()
|
|
|
|
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])
|
|
|
|
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_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_flags, i + 1, entry_blocks])
|
|
|
|
block_count += entry_blocks
|
|
|
|
block_start = hdr_size
|
|
for i in range(block_count) :
|
|
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.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.pfat_print()
|
|
|
|
print('\n PFAT Block %d/%d Script:\n' % (blocks[i][3], blocks[i][4]))
|
|
is_opcode_div = len(block_script_data) % 8 == 0
|
|
is_begin_end = block_script_data[:8] + block_script_data[-8:] == b'\x01' + b'\x00' * 7 + b'\xFF' + b'\x00' * 7
|
|
if is_opcode_div and is_begin_end and is_bgst :
|
|
block_script_decomp = BigScript(code_bytes=block_script_data)
|
|
block_script_lines = block_script_decomp.to_string().replace('\t',' ').split('\n')
|
|
for line in block_script_lines :
|
|
spacing = ' ' * 12 if line.endswith(('begin','end',':')) else ' ' * 20
|
|
operands = [op for op in line.split(' ') if op != '']
|
|
print(spacing + ('{:<12s}' + '{:<11s}' * (len(operands) - 1)).format(*operands))
|
|
elif not is_opcode_div :
|
|
print(' Error: Script not divisible by OpCode length!')
|
|
elif not is_begin_end :
|
|
print(' Error: Script lacks Begin and/or End OpCodes!')
|
|
elif not is_bgst :
|
|
print(' Error: BIOS Guard Script Tool dependency missing!')
|
|
|
|
file_data += block_data
|
|
final_image += block_data
|
|
|
|
if i and is_file_start and file_data :
|
|
file_index += 1
|
|
with open('%s_%0.2d -- %s' % (file_path, file_index, block_name), 'wb') as o : o.write(file_data)
|
|
file_data = b''
|
|
|
|
block_name = blocks[i][0]
|
|
block_start = block_data_end + block_rsa_size
|
|
|
|
with open('%s_%0.2d -- %s' % (file_path, file_index + 1, block_name), 'wb') as o : o.write(file_data) # Last File
|
|
|
|
with open('%s_00 -- AMI_PFAT_%d_DATA_ALL.bin' % (file_path, pfat_index), 'wb') as final : final.write(final_image)
|
|
|
|
eof_data = buffer[block_start:] # Store any data after the end of PFAT
|
|
if eof_data[:-0x100] != b'\xFF' * (len(eof_data) - 0x100) :
|
|
eof_path = '%s_%0.2d -- AMI_PFAT_%d_DATA_END.bin' % (file_path, file_index + 2, pfat_index)
|
|
with open(eof_path, 'wb') as final : final.write(eof_data)
|
|
|
|
if pfat_pat.search(eof_data) :
|
|
pfat_index += 1
|
|
ami_pfat_index = ami_pfat.index(input_file) + 1
|
|
ami_pfat.insert(ami_pfat_index, eof_path)
|
|
else :
|
|
pfat_index = 1
|
|
|
|
else :
|
|
input('\nDone!') |