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.
This commit is contained in:
Plato Mavropoulos 2021-06-15 16:20:21 +03:00
parent b6117807ba
commit 49bd11de92
2 changed files with 269 additions and 0 deletions

View file

@ -0,0 +1,213 @@
#!/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)

View file

@ -175,6 +175,62 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con
![](https://i.imgur.com/iZD3GY0.png)
## **Phoenix SCT BIOS Extractor**
![](https://i.imgur.com/z4VM06J.png)
#### **Description**
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.
![](https://i.imgur.com/p6s8L6j.png)
<sub><sup>*Icon owned by Phoenix*</sup></sub>
#### **Usage**
You can either Drag & Drop or manually enter the full path of a folder containing Phoenix SCT BIOS images. Optional arguments:
* -h or --help : show help message and exit
* -p or --path : parse files within given folder
#### **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 utility, you do not need any 3rd party tool.
#### **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 Phoenix_SCT_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**
![](https://i.imgur.com/Td6F5mm.png)
## **Apple EFI Sucatalog Link Grabber**
![](https://i.imgur.com/zTVFs4I.png)