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

Parses Phoenix SecureCore Technology (SCT) BIOS images and extracts their SPI/BIOS/UEFI firmware components. It supports all Phoenix SCT revisions and formats, including those which are originally LZMA compressed. The output comprises only final firmware components which are directly usable by end users.
213 lines
No EOL
6.9 KiB
Python
213 lines
No EOL
6.9 KiB
Python
#!/usr/bin/env python3
|
|
#coding=utf-8
|
|
|
|
"""
|
|
Phoenix SCT Extract
|
|
Phoenix SCT BIOS Extractor
|
|
Copyright (C) 2021 Plato Mavropoulos
|
|
"""
|
|
|
|
title = 'Phoenix SCT 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 lzma
|
|
import shutil
|
|
import ctypes
|
|
import argparse
|
|
import traceback
|
|
|
|
# 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
|
|
sct_parser = argparse.ArgumentParser()
|
|
sct_parser.add_argument('executables', type=argparse.FileType('r'), nargs='*')
|
|
sct_parser.add_argument('-p', '--path', help='parse files within given folder', type=str)
|
|
sct_params = sct_parser.parse_args()
|
|
|
|
# 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(sct_params.path) :
|
|
sct_exec = get_files(sct_params.path) # CLI with --path
|
|
else :
|
|
sct_exec = []
|
|
for executable in sct_params.executables :
|
|
sct_exec.append(executable.name) # Drag & Drop
|
|
else :
|
|
in_path = input('\nEnter the full folder path: ')
|
|
sct_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 SCT_HDR(ctypes.LittleEndianStructure) :
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('Tag', char*8), # 0x00
|
|
('Size', uint32_t), # 0x08
|
|
('Count', uint32_t), # 0x0C
|
|
# 0x10
|
|
]
|
|
|
|
def sct_print(self) :
|
|
print('\n Phoenix SCT Header:\n')
|
|
print(' Tag : %s' % self.Tag.decode('utf-8','replace').strip())
|
|
print(' Size : 0x%X' % self.Size)
|
|
print(' Count : %d' % self.Count)
|
|
|
|
class SCT_MOD(ctypes.LittleEndianStructure) :
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('Name', char*256), # 0x000
|
|
('Offset', uint32_t), # 0x100
|
|
('Size', uint32_t), # 0x104
|
|
('Compressed', uint32_t), # 0x108
|
|
('Reserved', uint32_t), # 0x10C
|
|
# 0x110
|
|
]
|
|
|
|
def sct_print(self) :
|
|
print('\n Phoenix SCT Entry:\n')
|
|
print(' Name : %s' % self.Name.decode('utf-8','replace').strip())
|
|
print(' Offset : 0x%X' % self.Offset)
|
|
print(' Size : 0x%X' % self.Size)
|
|
print(' Compressed : %s' % ['No','Yes'][self.Compressed])
|
|
print(' Reserved : 0x%X' % self.Reserved)
|
|
|
|
# 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
|
|
|
|
# Phoenix SCT BIOS Package Pattern ($PACK + Size + Count)
|
|
sct_pat = re.compile(br'\x24\x50\x41\x43\x4B\x00{3}..\x00{2}.\x00{3}', re.DOTALL)
|
|
|
|
# Get common ctypes Structure Sizes
|
|
sct_hdr_len = ctypes.sizeof(SCT_HDR)
|
|
sct_mod_len = ctypes.sizeof(SCT_MOD)
|
|
|
|
# Size of dummy/placeholder SCT Entries
|
|
sct_dummy_len = 0x200 # Top 2, Names only
|
|
|
|
# Process each input Phoenix SCT BIOS executable
|
|
for input_file in sct_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()
|
|
|
|
sct_match = sct_pat.search(input_data) # Search for Phoenix SCT BIOS Pattern
|
|
|
|
# Check if Phoenix SCT BIOS Pattern was found on executable
|
|
if not sct_match :
|
|
print('\n Error: This is not a Phoenix SCT 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 Phoenix SecureCore Technology')
|
|
|
|
sct_hdr = get_struct(input_data, sct_match.start(), SCT_HDR) # Parse SCT Header Structure
|
|
sct_hdr.sct_print() # Print SCT Header Info
|
|
|
|
# Check if reported SCT Header Size matches manual SCT Entry Count calculation
|
|
if sct_hdr.Size != sct_hdr_len + sct_dummy_len + sct_hdr.Count * sct_mod_len :
|
|
input('\n Error: This Phoenix SCT BIOS image is corrupted!')
|
|
continue # Next input file
|
|
|
|
# Store all SCT $PACK Data w/o initial dummy/placeholder Entries
|
|
pack_data = input_data[sct_match.end() + sct_dummy_len:sct_match.start() + sct_hdr.Size]
|
|
|
|
# Parse each SCT Entry
|
|
for e_idx in range(sct_hdr.Count) :
|
|
mod_hdr = get_struct(pack_data, e_idx * sct_mod_len, SCT_MOD) # Parse SCT Entry Structure
|
|
mod_hdr.sct_print() # Print SCT Entry Info
|
|
|
|
mod_data = input_data[mod_hdr.Offset:mod_hdr.Offset + mod_hdr.Size] # Store SCT Entry Raw Data
|
|
|
|
# Check if SCT Entry Raw Data is complete
|
|
if len(mod_data) != mod_hdr.Size :
|
|
input('\n Error: This Phoenix SCT BIOS image is incomplete!')
|
|
|
|
# Store SCT Entry LZMA Decompressed Data, when applicable
|
|
if mod_hdr.Compressed : mod_data = lzma.LZMADecompressor().decompress(mod_data)
|
|
|
|
# Replace common Windows reserved/illegal filename characters
|
|
mod_fname = re.sub(r'[\\/*?:"<>|]', '_', mod_hdr.Name.decode('utf-8','replace').strip())
|
|
|
|
with open(os.path.join(output_path, mod_fname), 'wb') as out : out.write(mod_data) # Store SCT Entry Data/File
|
|
|
|
print('\n Extracted Phoenix SCT BIOS executable!')
|
|
|
|
input('\nDone!')
|
|
|
|
sys.exit(0) |