mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-09 13:52:00 -04:00
AMI UCP BIOS Extractor v1.0
Parses AMI UCP (Utility Configuration Program) BIOS images, extracts their SPI/BIOS/UEFI firmware components and shows all relevant info. It supports all AMI UCP revisions and formats, including those with nested AMI UCP or Insyde SFX structures. The output comprises only final firmware components and utilities which are directly usable by end users.
This commit is contained in:
parent
be940bb4ee
commit
b6117807ba
2 changed files with 666 additions and 0 deletions
607
AMI UCP BIOS Extractor/AMI_UCP_Extract.py
Normal file
607
AMI UCP BIOS Extractor/AMI_UCP_Extract.py
Normal file
|
@ -0,0 +1,607 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
AMI UCP Extract
|
||||
AMI UCP BIOS Extractor
|
||||
Copyright (C) 2021 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
title = 'AMI UCP BIOS Extractor v1.0'
|
||||
|
||||
print('\n' + title) # Print script title
|
||||
|
||||
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') # pylint: disable=E0602
|
||||
sys.exit(1)
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import ctypes
|
||||
import argparse
|
||||
import traceback
|
||||
import subprocess
|
||||
import contextlib
|
||||
|
||||
# Pause after any unexpected Python exception
|
||||
# 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: %s crashed, please report the following:\n' % title)
|
||||
traceback.print_exception(exc_type, exc_value, tb)
|
||||
input('\nPress enter to exit')
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
# Set pause-able Python exception handler
|
||||
sys.excepthook = show_exception_and_exit
|
||||
|
||||
# Set console/shell window title
|
||||
user_os = sys.platform
|
||||
if user_os == 'win32' : ctypes.windll.kernel32.SetConsoleTitleW(title)
|
||||
elif user_os.startswith('linux') or user_os == 'darwin' or user_os.find('bsd') != -1 : sys.stdout.write('\x1b]2;' + title + '\x07')
|
||||
|
||||
# Set argparse Arguments
|
||||
ucp_parser = argparse.ArgumentParser()
|
||||
ucp_parser.add_argument('executables', type=argparse.FileType('r'), nargs='*')
|
||||
ucp_parser.add_argument('-p', '--path', help='parse files within given folder', type=str)
|
||||
ucp_parser.add_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true')
|
||||
ucp_params = ucp_parser.parse_args()
|
||||
|
||||
verify_chk16 = bool(ucp_params.checksum) # Get Checksum16 Verification optional argument
|
||||
|
||||
# Get all files within path
|
||||
def get_files(path) :
|
||||
inputs = []
|
||||
|
||||
for root, _, files in os.walk(path):
|
||||
for name in files :
|
||||
inputs.append(os.path.join(root, name))
|
||||
|
||||
return inputs
|
||||
|
||||
if len(sys.argv) >= 2 :
|
||||
if bool(ucp_params.path) :
|
||||
ucp_exec = get_files(ucp_params.path) # CLI with --path
|
||||
else :
|
||||
ucp_exec = []
|
||||
for executable in ucp_params.executables :
|
||||
ucp_exec.append(executable.name) # Drag & Drop
|
||||
else :
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
ucp_exec = get_files(in_path) # Direct Run
|
||||
|
||||
# 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 UAF_HDR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('ModuleTag', char*4), # 0x00
|
||||
('ModuleSize', uint32_t), # 0x04
|
||||
('Checksum', uint16_t), # 0x08
|
||||
('Unknown0', uint8_t), # 0x0A
|
||||
('Unknown1', uint8_t), # 0x0A
|
||||
('Reserved', uint32_t), # 0x0C
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def __init__(self, padd, *args, **kwargs) :
|
||||
super().__init__(*args, **kwargs)
|
||||
self.p = padd
|
||||
|
||||
def ucp_print(self, chk16) :
|
||||
print('\n%s Utility Auxiliary File:\n' % self.p)
|
||||
print('%s Module Tag : %s' % (self.p, self.ModuleTag.decode('utf-8')))
|
||||
print('%s Module Size : 0x%X' % (self.p, self.ModuleSize))
|
||||
print('%s Checksum : 0x%0.4X (%s)' % (self.p, self.Checksum, chk16))
|
||||
print('%s Unknown 0 : 0x%0.2X' % (self.p, self.Unknown0))
|
||||
print('%s Unknown 1 : 0x%0.2X' % (self.p, self.Unknown1))
|
||||
print('%s Reserved : 0x%0.8X' % (self.p, self.Reserved))
|
||||
|
||||
class UAF_MOD(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('CompressSize', uint32_t), # 0x00
|
||||
('OriginalSize', uint32_t), # 0x04
|
||||
# 0x08
|
||||
]
|
||||
|
||||
def __init__(self, padd, *args, **kwargs) :
|
||||
super().__init__(*args, **kwargs)
|
||||
self.p = padd
|
||||
|
||||
def ucp_print(self) :
|
||||
print('%s Compress Size : 0x%X' % (self.p, self.CompressSize))
|
||||
print('%s Original Size : 0x%X' % (self.p, self.OriginalSize))
|
||||
|
||||
class UII_HDR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('UIISize', uint16_t), # 0x00
|
||||
('Checksum', uint16_t), # 0x02
|
||||
('UtilityVersion', uint32_t), # 0x04 i.e. AFU (Unknown Encoding, Signed)
|
||||
('InfoSize', uint16_t), # 0x08
|
||||
('SupportBIOS', uint8_t), # 0x0A
|
||||
('SupportOS', uint8_t), # 0x0B
|
||||
('DataBusWidth', uint8_t), # 0x0C
|
||||
('ProgramType', uint8_t), # 0x0D
|
||||
('ProgramMode', uint8_t), # 0x0E
|
||||
('SourceSafeRelease', uint8_t), # 0x0F
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def __init__(self, padd, *args, **kwargs) :
|
||||
super().__init__(*args, **kwargs)
|
||||
self.p = padd
|
||||
|
||||
def ucp_print(self, chk16) :
|
||||
sbios = {1: 'ALL', 2: 'AMIBIOS8', 3: 'UEFI', 4: 'AMIBIOS8/UEFI'}
|
||||
sos = {1: 'DOS', 2: 'EFI', 3: 'Windows', 4: 'Linux', 5: 'FreeBSD', 6: 'MacOS', 128: 'Multi-Platform'}
|
||||
dbwidth = {1: '16b', 2: '16/32b', 3: '32b', 4: '64b'}
|
||||
ptype = {1: 'Executable', 2: 'Library', 3: 'Driver'}
|
||||
pmode = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'}
|
||||
|
||||
SupportBIOS = sbios[self.SupportBIOS] if self.SupportBIOS in sbios else 'Unknown (%d)' % self.SupportBIOS
|
||||
SupportOS = sos[self.SupportOS] if self.SupportOS in sos else 'Unknown (%d)' % self.SupportOS
|
||||
DataBusWidth = dbwidth[self.DataBusWidth] if self.DataBusWidth in dbwidth else 'Unknown (%d)' % self.DataBusWidth
|
||||
ProgramType = ptype[self.ProgramType] if self.ProgramType in ptype else 'Unknown (%d)' % self.ProgramType
|
||||
ProgramMode = pmode[self.ProgramMode] if self.ProgramMode in pmode else 'Unknown (%d)' % self.ProgramMode
|
||||
|
||||
print('\n%s Utility Identification Information:\n' % self.p)
|
||||
print('%s UII Size : 0x%X' % (self.p, self.UIISize))
|
||||
print('%s Checksum : 0x%0.4X (%s)' % (self.p, self.Checksum, chk16))
|
||||
print('%s Tool Version : 0x%0.8X (Unknown)' % (self.p, self.UtilityVersion))
|
||||
print('%s Info Size : 0x%X' % (self.p, self.InfoSize))
|
||||
print('%s Supported BIOS : %s' % (self.p, SupportBIOS))
|
||||
print('%s Supported OS : %s' % (self.p, SupportOS))
|
||||
print('%s Data Bus Width : %s' % (self.p, DataBusWidth))
|
||||
print('%s Program Type : %s' % (self.p, ProgramType))
|
||||
print('%s Program Mode : %s' % (self.p, ProgramMode))
|
||||
print('%s SourceSafe Tag : %0.2d' % (self.p, self.SourceSafeRelease))
|
||||
|
||||
class DIS_HDR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('PasswordSize', uint16_t), # 0x00
|
||||
('EntryCount', uint16_t), # 0x02
|
||||
('Password', char*12), # 0x04
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def __init__(self, padd, *args, **kwargs) :
|
||||
super().__init__(*args, **kwargs)
|
||||
self.p = padd
|
||||
|
||||
def ucp_print(self) :
|
||||
print('\n%s Default Command Status Header:\n' % self.p)
|
||||
print('%s Password Size : 0x%X' % (self.p, self.PasswordSize))
|
||||
print('%s Entry Count : %d' % (self.p, self.EntryCount))
|
||||
print('%s Password : %s' % (self.p, self.Password.decode('utf-8')))
|
||||
|
||||
class DIS_MOD(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('EnabledDisabled', uint8_t), # 0x00
|
||||
('ShownHidden', uint8_t), # 0x01
|
||||
('Command', char*32), # 0x02
|
||||
('Description', char*256), # 0x22
|
||||
# 0x122
|
||||
]
|
||||
|
||||
def __init__(self, padd, *args, **kwargs) :
|
||||
super().__init__(*args, **kwargs)
|
||||
self.p = padd
|
||||
|
||||
def ucp_print(self) :
|
||||
enabled = {0: 'Disabled', 1: 'Enabled'}
|
||||
shown = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'}
|
||||
|
||||
EnabledDisabled = enabled[self.EnabledDisabled] if self.EnabledDisabled in enabled else 'Unknown (%d)' % self.EnabledDisabled
|
||||
ShownHidden = shown[self.ShownHidden] if self.ShownHidden in shown else 'Unknown (%d)' % self.ShownHidden
|
||||
|
||||
print('\n%s Default Command Status Entry:\n' % self.p)
|
||||
print('%s State : %s' % (self.p, EnabledDisabled))
|
||||
print('%s Display : %s' % (self.p, ShownHidden))
|
||||
print('%s Command : %s' % (self.p, self.Command.decode('utf-8').strip()))
|
||||
print('%s Description : %s' % (self.p, self.Description.decode('utf-8').strip()))
|
||||
|
||||
# Process ctypes Structure Classes
|
||||
# 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) :
|
||||
print('\n Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name.__name__))
|
||||
|
||||
input('\n Press enter to exit')
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)
|
||||
|
||||
return structure
|
||||
|
||||
# Get Checksum16 validity result
|
||||
def checksum16(buffer, check) :
|
||||
if not check : return 'Skipped'
|
||||
|
||||
chk16 = 0
|
||||
|
||||
for idx in range(0, len(buffer), 2) :
|
||||
chk16 += int.from_bytes(buffer[idx:idx + 2], 'little')
|
||||
|
||||
chk16 &= 0xFFFF
|
||||
|
||||
return 'Good' if chk16 == 0 else 'Bad'
|
||||
|
||||
# Get all input file AMI UCP patterns
|
||||
def get_matches(buffer) :
|
||||
uaf_len_max = 0 # Length of largest detected @UAF
|
||||
uaf_hdr_off = 0 # Offset of largest detected @UAF
|
||||
|
||||
for uaf in ami_ucp_pat.finditer(buffer) :
|
||||
uaf_len_cur = int.from_bytes(buffer[uaf.start() + 0x4:uaf.start() + 0x8], 'little')
|
||||
|
||||
if uaf_len_cur > uaf_len_max :
|
||||
uaf_len_max = uaf_len_cur
|
||||
uaf_hdr_off = uaf.start()
|
||||
|
||||
return uaf_hdr_off, uaf_len_max
|
||||
|
||||
# Parse & Extract AMI UCP structures
|
||||
def ucp_extract(buffer, out_dir, level, padd) :
|
||||
nal_dict = {} # Initialize @NAL Dictionary per UCP
|
||||
|
||||
uaf_hdr = get_struct(buffer, 0, UAF_HDR, [padd]) # Parse @UAF Header Structure
|
||||
|
||||
uaf_chk = checksum16(buffer, verify_chk16) # Get @UAF Header Checksum16
|
||||
|
||||
# Print @UAF Header Info
|
||||
uaf_hdr.ucp_print(uaf_chk)
|
||||
print('%s Compress Size : 0x%X' % (padd, len(buffer)))
|
||||
print('%s Original Size : 0x%X' % (padd, len(buffer)))
|
||||
print('%s Module Name : %s' % (padd, tag_dict['UAF']))
|
||||
|
||||
if uaf_chk == 'Bad' :
|
||||
input('\n%s Error: Invalid AMI UCP Module UAF Checksum!' % padd)
|
||||
|
||||
uaf_off = uaf_hdr_len # Parsed @UAF, next Modules
|
||||
uaf_all = [] # Initialize list of all UAF Modules
|
||||
is_pfat = False # Initialize PFAT BIOS detection
|
||||
is_dual = False # Initialize AMI/Insyde detection
|
||||
|
||||
while buffer[uaf_off] == 0x40 : # ASCII of @ is 0x40
|
||||
uaf_hdr = get_struct(buffer, uaf_off, UAF_HDR, [padd]) # Parse UAF Module Structure
|
||||
|
||||
uaf_tag = uaf_hdr.ModuleTag.decode('utf-8')[1:] # Get unique UAF Module Tag
|
||||
|
||||
if uaf_tag == 'PFC' : is_pfat = True # Detect if UAF Module has PFAT BIOS
|
||||
|
||||
if uaf_tag == 'AMI' : is_dual = True # Detect if UAF Module has dual AMI/Insyde BIOS
|
||||
|
||||
uaf_all.append([uaf_tag, uaf_off, uaf_hdr]) # Store UAF Module Info
|
||||
|
||||
uaf_off += uaf_hdr.ModuleSize # Adjust to next UAF Module offset
|
||||
|
||||
if uaf_off >= len(buffer) : break # Stop parsing at EOF
|
||||
|
||||
# Check if UAF Module NAL exists and place it first
|
||||
# Parsing NAL first allows naming all UAF Modules
|
||||
for i in range(len(uaf_all)) :
|
||||
if uaf_all[i][0] == 'NAL' :
|
||||
uaf_all.insert(1, uaf_all.pop(i)) # After UII for visual purposes
|
||||
break # NAL found, skip the rest
|
||||
|
||||
# Parse all UAF Modules
|
||||
for uaf in uaf_all :
|
||||
uaf_tag = uaf[0] # Store UAF Module Tag
|
||||
uaf_off = uaf[1] # Store UAF Module Offset
|
||||
uaf_hdr = uaf[2] # Store UAF Module Struct
|
||||
|
||||
uaf_data_all = buffer[uaf_off:uaf_off + uaf_hdr.ModuleSize] # UAF Module Entire Data
|
||||
|
||||
uaf_data_mod = uaf_data_all[uaf_hdr_len:] # UAF Module EFI Data
|
||||
|
||||
uaf_data_raw = uaf_data_mod[uaf_mod_len:] # UAF Module Raw Data
|
||||
|
||||
uaf_chk = checksum16(uaf_data_all, verify_chk16) # Get UAF Module Checksum16
|
||||
|
||||
uaf_hdr.ucp_print(uaf_chk) # Print UAF Module Info
|
||||
|
||||
uaf_mod = get_struct(buffer, uaf_off + uaf_hdr_len, UAF_MOD, [padd]) # Parse UAF Module EFI Structure
|
||||
|
||||
uaf_mod.ucp_print() # Print UAF Module EFI Info
|
||||
|
||||
is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect UAF Module EFI Compression
|
||||
|
||||
rom_name = 'PFAT' if is_pfat else 'BIOS' # Set UAF Module BIOS/ROM name based on PFAT state
|
||||
|
||||
if uaf_tag in nal_dict : uaf_name = nal_dict[uaf_tag] # Always prefer NAL naming first
|
||||
elif uaf_tag in tag_dict : uaf_name = tag_dict[uaf_tag] # Otherwise use built-in naming
|
||||
elif uaf_tag == 'ROM' : uaf_name = '%s.bin' % rom_name # BIOS/PFAT Firmware
|
||||
elif uaf_tag.startswith('R0') : uaf_name = '%s_0%s.bin' % (rom_name, uaf_tag[2:]) # BIOS/PFAT Firmware
|
||||
elif uaf_tag.startswith('S0') : uaf_name = '%s_0%s.sig' % (rom_name, uaf_tag[2:]) # BIOS/PFAT Signature
|
||||
elif uaf_tag.startswith('DR') : uaf_name = 'DROM_0%s.bin' % uaf_tag[2:] # Thunderbolt Retimer Firmware
|
||||
elif uaf_tag.startswith('DS') : uaf_name = 'DROM_0%s.sig' % uaf_tag[2:] # Thunderbolt Retimer Signature
|
||||
elif uaf_tag.startswith('EC') : uaf_name = 'EC_0%s.bin' % uaf_tag[2:] # Embedded Controller Firmware
|
||||
elif uaf_tag.startswith('ME') : uaf_name = 'ME_0%s.bin' % uaf_tag[2:] # Management Engine Firmware
|
||||
else : uaf_name = uaf_tag # Could not name the UAF Module, use Tag instead
|
||||
|
||||
if uaf_name != uaf_tag :
|
||||
uaf_fext = '' # File extension included in name
|
||||
print('%s Module Name : %s' % (padd, uaf_name))
|
||||
elif uaf_tag in ['CMD','PFC','VER','MEC','NAL','CKV'] :
|
||||
uaf_fext = '.txt' # Known Text files
|
||||
print('%s Module Name : %s%s (Unknown)' % (padd, uaf_name, uaf_fext))
|
||||
else :
|
||||
uaf_fext = '.bin' # Unknown files, assume binary
|
||||
print('%s Module Name : %s%s (Unknown)' % (padd, uaf_name, uaf_fext))
|
||||
|
||||
# Check if unknown UAF Module Tag is present in NAL but not in built-in dictionary
|
||||
if uaf_tag in nal_dict and uaf_tag not in tag_dict and not uaf_tag.startswith(('ROM','R0','S0','DR','DS')) :
|
||||
input('\n%s Note: Detected new AMI UCP Module %s (%s) in NAL!' % (padd, uaf_tag, nal_dict[uaf_tag]))
|
||||
|
||||
# Generate UAF Module File name, depending on whether decompression will be required
|
||||
uaf_fname = os.path.join(out_dir, '%s%s' % (uaf_name, '.temp' if is_comp else uaf_fext))
|
||||
|
||||
if uaf_chk == 'Bad' :
|
||||
input('\n%s Error: Invalid AMI UCP Module %s Checksum!' % (padd, uaf_tag))
|
||||
|
||||
# Parse Utility Identification Information UAF Module (UII)
|
||||
if uaf_tag == 'UII' :
|
||||
info_hdr = get_struct(uaf_data_raw, 0, UII_HDR, [padd]) # Parse UII Module Raw Structure
|
||||
|
||||
info_chk = checksum16(uaf_data_raw, verify_chk16) # Get UII Module Checksum16
|
||||
|
||||
info_hdr.ucp_print(info_chk) # Print UII Module Info
|
||||
|
||||
# Get UII Module Description text field
|
||||
desc = uaf_data_raw[info_hdr.InfoSize:info_hdr.UIISize].strip(b'\x00').decode('utf-8')
|
||||
|
||||
print('%s Description : %s' % (padd, desc)) # Print UII Module Description
|
||||
|
||||
if info_chk == 'Bad' :
|
||||
input('\n%s Error: Invalid AMI UCP Module %s > Info Checksum!' % (padd, uaf_tag))
|
||||
|
||||
# Store/Save UII Module Info in file
|
||||
with open(uaf_fname[:-3] + 'txt', 'a') as uii :
|
||||
with contextlib.redirect_stdout(uii) :
|
||||
info_hdr.ucp_print(info_chk) # Store UII Module Info
|
||||
|
||||
print('%s Description : %s' % (padd, desc)) # Store UII Module Description
|
||||
|
||||
# Process and Print known text only UAF Modules
|
||||
if uaf_tag in ['CMD','PFC','VER','MEC','CKV'] : # Always referenced in tag_desc
|
||||
text_data = uaf_data_raw.decode('utf-8')
|
||||
print('\n%s %s:\n\n%s %s' % (padd, tag_desc[uaf_tag], padd, text_data))
|
||||
|
||||
# Adjust UAF Module Raw Data for extraction
|
||||
if is_comp :
|
||||
# Some Compressed UAF Module EFI data lack necessary padding in the end
|
||||
if uaf_mod.CompressSize > len(uaf_data_raw) :
|
||||
comp_padd = b'\x00' * (uaf_mod.CompressSize - len(uaf_data_raw))
|
||||
uaf_data_raw = uaf_data_mod[:uaf_mod_len] + uaf_data_raw + comp_padd # Add missing padding for decompression
|
||||
else :
|
||||
uaf_data_raw = uaf_data_mod[:uaf_mod_len] + uaf_data_raw # Add the EFI/Tiano Compression info before Raw Data
|
||||
else :
|
||||
uaf_data_raw = uaf_data_raw[:uaf_mod.OriginalSize] # No compression, extend to end of Original UAF Module size
|
||||
|
||||
# Store/Save UAF Module file
|
||||
if uaf_tag != 'UII' : # Skip UII binary, already parsed
|
||||
with open(uaf_fname, 'wb') as out : out.write(uaf_data_raw)
|
||||
|
||||
# UAF Module EFI/Tiano Decompression
|
||||
if is_comp :
|
||||
try :
|
||||
dec_fname = uaf_fname[:-5] + uaf_fext # Decompressed UAF Module file path
|
||||
subprocess.run(['TianoCompress', '-d', uaf_fname, '-o', dec_fname, '--uefi', '-q'], check = True, stdout = subprocess.DEVNULL)
|
||||
|
||||
with open(dec_fname, 'rb') as dec : uaf_data_raw = dec.read() # Read back the UAF Module decompressed Raw data
|
||||
|
||||
if len(uaf_data_raw) == 0 : raise Exception('DECOMP_OUT_EMPTY') # If decompressed file is empty, something went wrong
|
||||
|
||||
os.remove(uaf_fname) # Successful decompression, delete compressed UAF Module file
|
||||
|
||||
uaf_fname = dec_fname # Adjust UAF Module file path to the decompressed one
|
||||
except :
|
||||
print('\n%s Error: Could not extract AMI UCP Module %s via TianoCompress!' % (padd, uaf_tag))
|
||||
input('%s Make sure that "TianoCompress" executable exists!' % padd)
|
||||
|
||||
# Parse Default Command Status UAF Module (DIS)
|
||||
if len(uaf_data_raw) and uaf_tag == 'DIS' :
|
||||
dis_hdr = get_struct(uaf_data_raw, 0, DIS_HDR, [padd]) # Parse DIS Module Raw Header Structure
|
||||
dis_hdr.ucp_print() # Print DIS Module Raw Header Info
|
||||
|
||||
# Store/Save DIS Module Header Info in file
|
||||
with open(uaf_fname[:-3] + 'txt', 'a') as dis :
|
||||
with contextlib.redirect_stdout(dis) :
|
||||
dis_hdr.ucp_print() # Store DIS Module Header Info
|
||||
|
||||
dis_data = uaf_data_raw[uaf_hdr_len:] # DIS Module Entries Data
|
||||
|
||||
# Parse all DIS Module Entries
|
||||
for e_idx in range(dis_hdr.EntryCount) :
|
||||
dis_mod = get_struct(dis_data, e_idx * 0x122, DIS_MOD, [padd]) # Parse DIS Module Raw Entry Structure
|
||||
dis_mod.ucp_print() # Print DIS Module Raw Entry Info
|
||||
|
||||
# Store/Save DIS Module Entry Info in file
|
||||
with open(uaf_fname[:-3] + 'txt', 'a') as dis :
|
||||
with contextlib.redirect_stdout(dis) :
|
||||
dis_mod.ucp_print() # Store DIS Module Entry Info
|
||||
|
||||
os.remove(uaf_fname) # Delete DIS Module binary, info exported as text
|
||||
|
||||
# Parse Non-AMI List (?) UAF Module (NAL)
|
||||
if len(uaf_data_raw) >= 5 and (uaf_tag,uaf_data_raw[0],uaf_data_raw[4]) == ('NAL',0x40,0x3A) :
|
||||
nal_info = uaf_data_raw.decode('utf-8').strip().replace('\r','').split('\n')
|
||||
|
||||
print('\n%s UAF List:\n' % padd)
|
||||
|
||||
# Parse all NAL Module Entries
|
||||
for info in nal_info :
|
||||
print('%s %s : %s' % (padd, info[1:4], info[5:])) # Print NAL Module Tag-Path Info
|
||||
nal_dict[info[1:4]] = os.path.basename(info[5:]) # Assign a file name (w/o path) to each Tag
|
||||
|
||||
# Parse Insyde BIOS UAF Module (INS)
|
||||
if len(uaf_data_raw) >= 2 and (uaf_tag,is_dual,uaf_data_raw[:2]) == ('INS',True,b'\x4D\x5A') :
|
||||
ins_dir = os.path.join(out_dir, '%s_extracted (SFX)' % uaf_tag) # Generate extraction directory
|
||||
|
||||
print('\n%s Insyde BIOS 7-Zip SFX Archive:\n\n%s 7-Zip will be used for extraction' % (padd, padd))
|
||||
|
||||
# INS Module extraction
|
||||
try :
|
||||
subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + ins_dir, uaf_fname], check = True, stdout = subprocess.DEVNULL)
|
||||
|
||||
if not os.path.isdir(ins_dir) : raise Exception('EXTR_DIR_MISSING') # If extraction folder is missing, something went wrong
|
||||
|
||||
os.remove(uaf_fname) # Successful extraction, delete archived INS Module file
|
||||
except :
|
||||
print('\n%s Error: Could not extract AMI UCP Module %s via 7-Zip!' % (padd, uaf_tag))
|
||||
input('%s Make sure that "7z" executable exists!' % padd)
|
||||
|
||||
# Detect AMI BIOS Guard (PFAT) image and print extraction instructions/utility
|
||||
if len(uaf_data_raw) >= 16 and (is_pfat,uaf_data_raw[0x8:0x10]) == (True,b'_AMIPFAT') :
|
||||
print('\n%s AMI BIOS Guard (PFAT) Image:\n' % padd)
|
||||
print('%s Use "AMI BIOS Guard Extractor" from https://github.com/platomav/BIOSUtilities' % padd)
|
||||
|
||||
# Detect Intel Management Engine (ME) image and print parsing instructions/utility
|
||||
if len(uaf_data_raw) and uaf_tag.startswith('ME') :
|
||||
print('\n%s Intel Management Engine (ME) Image:\n' % padd)
|
||||
print('%s Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer' % padd)
|
||||
|
||||
# Get best Nested AMI UCP Pattern match based on @UAF Size
|
||||
uaf_hdr_off,uaf_len_max = get_matches(uaf_data_raw)
|
||||
|
||||
# Parse Nested AMI UCP Structure
|
||||
if uaf_hdr_off :
|
||||
level += 1 # Increase structure Level to control output padding
|
||||
uaf_dir = os.path.join(out_dir, '%s_extracted (UCP)' % uaf_tag) # Generate extraction directory
|
||||
os.mkdir(uaf_dir) # Create extraction directory
|
||||
ucp_extract(uaf_data_raw[uaf_hdr_off:uaf_hdr_off + uaf_len_max], uaf_dir, level, ' ' * level) # Call recursively
|
||||
os.remove(uaf_fname) # Delete raw nested AMI UCP Structure after successful recursion/extraction
|
||||
|
||||
# Utility Auxiliary File (@UAF) and Utility Identification Information (@UII)
|
||||
ami_ucp_pat = re.compile(br'\x40\x55\x41\x46.{12}\x40\x55\x49\x49', re.DOTALL)
|
||||
|
||||
# Get common ctypes Structure Sizes
|
||||
uaf_hdr_len = ctypes.sizeof(UAF_HDR)
|
||||
uaf_mod_len = ctypes.sizeof(UAF_MOD)
|
||||
|
||||
# User friendly Tag Descriptions
|
||||
tag_desc = {
|
||||
'CMD' : 'AMI AFU Command',
|
||||
'PFC' : 'AMI BGT Command',
|
||||
'VER' : 'OEM Version',
|
||||
'CKV' : 'Check Version',
|
||||
'MEC' : 'ME FWUpdLcl',
|
||||
}
|
||||
|
||||
# AMI UCP Tag-File Dictionary
|
||||
tag_dict = {
|
||||
'W32' : 'amifldrv32.sys',
|
||||
'W64' : 'amifldrv64.sys',
|
||||
'VXD' : 'amifldrv.vxd',
|
||||
'DCT' : 'DevCon32.exe',
|
||||
'DCX' : 'DevCon64.exe',
|
||||
'CMD' : 'AFU_Command.txt',
|
||||
'PFC' : 'BGT_Command.txt',
|
||||
'VER' : 'OEM_Version.txt',
|
||||
'CKV' : 'Check_Version.txt',
|
||||
'DIS' : 'Command_Status.bin',
|
||||
'UAF' : 'UCP_Main.bin',
|
||||
'UII' : 'UCP_Info.txt',
|
||||
'NAL' : 'UAF_List.txt',
|
||||
'MEC' : 'FWUpdLcl.txt',
|
||||
'MED' : 'FWUpdLcl_DOS.exe',
|
||||
'MET' : 'FWUpdLcl_WIN.exe',
|
||||
'AMI' : 'UCP_Nested.bin',
|
||||
'INS' : 'Insyde_Nested.bin',
|
||||
'RFI' : 'CryptRSA.efi',
|
||||
'R3I' : 'CryptRSA32.efi',
|
||||
'UFI' : 'HpBiosUpdate.efi',
|
||||
'US9' : 'HpBiosUpdate.s09',
|
||||
'US2' : 'HpBiosUpdate.s12',
|
||||
'USG' : 'HpBiosUpdate.sig',
|
||||
'3FI' : 'HpBiosUpdate32.efi',
|
||||
'3S9' : 'HpBiosUpdate32.s09',
|
||||
'3S2' : 'HpBiosUpdate32.s12',
|
||||
'3SG' : 'HpBiosUpdate32.sig',
|
||||
'MFI' : 'HpBiosMgmt.efi',
|
||||
'MS9' : 'HpBiosMgmt.s09',
|
||||
'MS2' : 'HpBiosMgmt.s12',
|
||||
'US4' : 'HpBiosUpdate.s14',
|
||||
'3S4' : 'HpBiosUpdate32.s14',
|
||||
'MS4' : 'HpBiosMgmt.s14',
|
||||
'M3I' : 'HpBiosMgmt32.efi',
|
||||
'M39' : 'HpBiosMgmt32.s09',
|
||||
'M32' : 'HpBiosMgmt32.s12',
|
||||
'M34' : 'HpBiosMgmt32.s14',
|
||||
'BME' : 'BiosMgmt.efi',
|
||||
'BM9' : 'BiosMgmt.s09',
|
||||
'B12' : 'BiosMgmt.s12',
|
||||
'B14' : 'BiosMgmt.s14',
|
||||
'B3E' : 'BiosMgmt32.efi',
|
||||
'B39' : 'BiosMgmt32.s09',
|
||||
'B32' : 'BiosMgmt32.s12',
|
||||
'B34' : 'BiosMgmt32.s14',
|
||||
'DFE' : 'HpDevFwUpdate.efi',
|
||||
'DFS' : 'HpDevFwUpdate.s12',
|
||||
}
|
||||
|
||||
# Process each input AMI UCP BIOS executable
|
||||
for input_file in ucp_exec :
|
||||
input_name,input_extension = os.path.splitext(os.path.basename(input_file))
|
||||
input_dir = os.path.dirname(os.path.abspath(input_file))
|
||||
|
||||
print('\n*** %s%s' % (input_name, input_extension))
|
||||
|
||||
# Check if input file exists
|
||||
if not os.path.isfile(input_file) :
|
||||
print('\n Error: This input file does not exist!')
|
||||
continue # Next input file
|
||||
|
||||
with open(input_file, 'rb') as in_file : input_data = in_file.read()
|
||||
|
||||
# Get best AMI UCP Pattern match based on @UAF Size
|
||||
uaf_hdr_off,uaf_len_max = get_matches(input_data)
|
||||
|
||||
# Check if AMI UCP Pattern was found on executable
|
||||
if not uaf_hdr_off :
|
||||
print('\n Error: This is not an AMI UCP BIOS executable!')
|
||||
continue # Next input file
|
||||
|
||||
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
|
||||
|
||||
print('\n AMI Utility Configuration Program')
|
||||
|
||||
level = 0 # Set initial AMI UCP structure Level to control padding in nested ones
|
||||
|
||||
ucp_extract(input_data[uaf_hdr_off:uaf_hdr_off + uaf_len_max], output_path, level, '') # Call the AMI UCP Extractor function
|
||||
|
||||
print('\n Extracted AMI UCP BIOS executable!')
|
||||
|
||||
input('\nDone!')
|
||||
|
||||
sys.exit(0)
|
59
README.md
59
README.md
|
@ -58,6 +58,65 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con
|
|||
|
||||

|
||||
|
||||
## **AMI UCP BIOS Extractor**
|
||||
|
||||

|
||||
|
||||
#### **Description**
|
||||
|
||||
Parses AMI UCP (Utility Configuration Program) BIOS images, extracts their SPI/BIOS/UEFI firmware components and shows all relevant info. It supports all AMI UCP revisions and formats, including those with nested AMI UCP or Insyde SFX structures. The output comprises only final firmware components and utilities which are directly usable by end users.
|
||||
|
||||
Note that AMI UCP BIOS images are protected by various checksums but, due to algorithmic performance reasons, AMI UCP BIOS Extractor does not check them by default. An optional parameter is provided though, for verifying all checksums during extraction.
|
||||
|
||||
#### **Usage**
|
||||
|
||||
You can either Drag & Drop or manually enter the full path of a folder containing AMI UCP BIOS images. Optional arguments:
|
||||
|
||||
* -h or --help : show help message and exit
|
||||
* -p or --path : parse files within given folder
|
||||
* -c or --checksum : verify AMI UCP Checksums (slow)
|
||||
|
||||
#### **Download**
|
||||
|
||||
An already built/frozen/compiled binary is provided by me for Windows only. Thus, **you don't need to manually build/freeze/compile it under Windows**. Instead, download the latest version from the [Releases](https://github.com/platomav/BIOSUtilities/releases) tab. To extract the already built/frozen/compiled archive, you need to use programs which support RAR5 compression. Note that you need to manually apply any prerequisites.
|
||||
|
||||
#### **Compatibility**
|
||||
|
||||
Should work at all Windows, Linux or macOS operating systems which have Python 3.7 support. Windows users who plan to use the already built/frozen/compiled binary must make sure that they have the latest Windows Updates installed which include all required "Universal C Runtime (CRT)" libraries.
|
||||
|
||||
#### **Prerequisites**
|
||||
|
||||
To run the python script, you need to have the following 3rd party tools placed at the same directory:
|
||||
|
||||
* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (i.e. [TianoCompress.exe](https://github.com/tianocore/edk2-BaseTools-win32/))
|
||||
* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe)
|
||||
|
||||
#### **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.7.0 or newer is installed:
|
||||
|
||||
> python --version
|
||||
|
||||
2. Use pip to install PyInstaller:
|
||||
|
||||
> pip3 install pyinstaller
|
||||
|
||||
3. Build/Freeze/Compile:
|
||||
|
||||
> pyinstaller --noupx --onefile AMI_UCP_Extract.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**
|
||||
|
||||

|
||||
|
||||
## **AMI BIOS Guard Extractor**
|
||||
|
||||

|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue