BIOSUtilities/Phoenix SCT BIOS Extractor/Phoenix_SCT_Extract.py
Plato Mavropoulos 49bd11de92 Phoenix SCT BIOS Extractor v1.0
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.
2021-06-15 16:20:21 +03:00

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)