mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-09 13:52:00 -04:00

File AMI_PFAT_X_DATA_ALL now includes AMI_PFAT_X_DATA_END Applied various static analysis code fixes
313 lines
No EOL
11 KiB
Python
313 lines
No EOL
11 KiB
Python
#!/usr/bin/env python3
|
|
#coding=utf-8
|
|
|
|
"""
|
|
AMI PFAT Extract
|
|
AMI BIOS Guard Extractor
|
|
Copyright (C) 2018-2021 Plato Mavropoulos
|
|
"""
|
|
|
|
print('AMI BIOS Guard Extractor v3.1')
|
|
|
|
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 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_data = 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_data += 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
|
|
|
|
eof_data = buffer[block_start:] # Store any data after the end of PFAT
|
|
|
|
with open('%s_00 -- AMI_PFAT_%d_DATA_ALL.bin' % (file_path, pfat_index), 'wb') as final : final.write(final_data + eof_data)
|
|
|
|
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
|
|
|
|
input('\nDone!') |