Skip to content

Commit

Permalink
Portwell EFI BIOS Extractor v1.0
Browse files Browse the repository at this point in the history
Parses Portwell UEFI Unpacker EFI images (usually named "Update.efi"), extracts their SPI/BIOS/UEFI/EC firmware components and shows all relevant info. It supports all Portwell UEFI Unpacker revisions and formats, including those which contain Tiano compressed files. The output comprises only final firmware components and utilities which are directly usable by end users.
  • Loading branch information
platomav committed Jun 19, 2021
1 parent 49bd11d commit fe05f05
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 3 deletions.
174 changes: 174 additions & 0 deletions Portwell EFI BIOS Extractor/Portwell_EFI_Extract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#!/usr/bin/env python3
#coding=utf-8

"""
Portwell EFI Extract
Portwell EFI BIOS Extractor
Copyright (C) 2021 Plato Mavropoulos
"""

title = 'Portwell EFI 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 pefile
import shutil
import ctypes
import argparse
import traceback
import subprocess

# 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
efi_parser = argparse.ArgumentParser()
efi_parser.add_argument('efi', type=argparse.FileType('r'), nargs='*')
efi_parser.add_argument('-p', '--path', help='parse files within given folder', type=str)
efi_params = efi_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(efi_params.path) :
efi_exec = get_files(efi_params.path) # CLI with --path
else :
efi_exec = []
for executable in efi_params.efi :
efi_exec.append(executable.name) # Drag & Drop
else :
in_path = input('\nEnter the full folder path: ')
efi_exec = get_files(in_path) # Direct Run

# Portwell UEFI Unpacker File Names (v1.1 - v1.2)
file_names = {0 : 'Flash.efi', 1 : 'Fparts.txt', 2 : 'Update.nsh', 3 : 'Temp.bin', 4 : 'SaveDmiData.efi'}

# Process each input Portwell EFI Update Package
for input_file in efi_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()

try :
assert input_data[0x0:0x2] == b'\x4D\x5A' # EFI images start with DOS Header MZ

pe = pefile.PE(input_file, fast_load=True) # Analyze EFI Portable Executable (PE)

payload_data = input_data[pe.OPTIONAL_HEADER.SizeOfImage:] # Skip EFI executable (pylint: disable=E1101)

assert payload_data[0x0:0x4] == b'\x3C\x55\x55\x3E' # Portwell EFI files start with <UU>
except :
print('\n Error: This is not a Portwell EFI Update Package!')
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

pack_tag = 'UEFI Unpacker' # Initialize Portwell UEFI Unpacker tag

# Get Portwell UEFI Unpacker tag
for s in pe.sections :
if s.Name.startswith(b'.data') : # Unpacker Tag, Version, Strings etc are found in .data PE section
# Decode any valid UTF-16 .data PE section info to a parsable text buffer
info = input_data[s.PointerToRawData:s.PointerToRawData + s.SizeOfRawData].decode('utf-16','ignore')

# Search .data for UEFI Unpacker tag
pack_tag_off = info.find('UEFI Unpacker')
if pack_tag_off != -1 :
pack_tag_len = info[pack_tag_off:].find('=')
if pack_tag_len != -1 :
# Found full UEFI Unpacker tag, store and slightly beautify the resulting text
pack_tag = info[pack_tag_off:pack_tag_off + pack_tag_len].strip().replace(' ',' ').replace('<',' <')

break # Found PE .data section, skip the rest

print('\n Portwell %s' % pack_tag) # Print Portwell UEFI Unpacker tag

efi_files = payload_data.split(b'\x3C\x55\x55\x3E')[1:] # Split EFI Payload into <UU> file chunks

# Parse each EFI Payload File
for i in range(len(efi_files)) :
file_data = efi_files[i] # Store EFI File data

if len(file_data) == 0 or file_data == b'NULL' : continue # Skip empty/unused files

is_known = i in file_names # Check if EFI file is known & Store result

file_name = file_names[i] if is_known else 'Unknown_%d.bin' % i # Assign Name to EFI file

print('\n %s' % file_name) # Print EFI file name, indicate progress

if not is_known : input('\n Note: Detected unknown Portwell EFI file with ID %d!' % i) # Report new EFI files

file_path = os.path.join(output_path, file_name) # Store EFI file output path

with open(file_path, 'wb') as o : o.write(file_data) # Store EFI file data to drive

# Attempt to detect EFI/Tiano Compression & Decompress when applicable
if int.from_bytes(file_data[0x0:0x4], 'little') + 0x8 == len(file_data) :
try :
comp_fname = file_path + '.temp' # Store temporary compressed file name

os.replace(file_path, comp_fname) # Rename initial/compressed file

subprocess.run(['TianoCompress', '-d', comp_fname, '-o', file_path, '--uefi', '-q'], check = True, stdout = subprocess.DEVNULL)

if os.path.getsize(file_path) != int.from_bytes(file_data[0x4:0x8], 'little') : raise Exception('EFI_DECOMP_ERROR')

os.remove(comp_fname) # Successful decompression, delete initial/compressed file

except :
print('\n Error: Could not extract file %s via TianoCompress!' % file_name)
input(' Make sure that "TianoCompress" executable exists!')

print('\n Extracted Portwell EFI Update Package!')

input('\nDone!')

sys.exit(0)
85 changes: 82 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@

<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DJDZD3PRGCSCL"><img border="0" title="BIOS Utilities Donation via Paypal or Debit/Credit Card" alt="BIOS Utilities Donation via Paypal or Debit/Credit Card" src="https://user-images.githubusercontent.com/11527726/109392268-e0f68280-7923-11eb-83d8-0a63f0d20783.png"></a>

* [**Dell PFS BIOS Extractor**](#dell-pfs-bios-extractor)
* [**AMI UCP BIOS Extractor**](#ami-ucp-bios-extractor)
* [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor)
* [**Phoenix SCT BIOS Extractor**](#phoenix-sct-bios-extractor)
* [**Portwell EFI BIOS Extractor**](#portwell-efi-bios-extractor)
* [**Panasonic BIOS Update Extractor**](#panasonic-bios-update-extractor)
* [**VAIO Packaging Manager Extractor**](#vaio-packaging-manager-extractor)
* [**Fujitsu SFX Packager Extractor**](#fujitsu-sfx-packager-extractor)
* [**Award BIOS Module Extractor**](#award-bios-module-extractor)
* [**Apple EFI Sucatalog Link Grabber**](#apple-efi-sucatalog-link-grabber)
* [**Apple EFI File Renamer**](#apple-efi-file-renamer)
* [**Apple EFI IM4P Splitter**](#apple-efi-im4p-splitter)
* [**Apple EFI Package Extractor**](#apple-efi-package-extractor)

## **Dell PFS BIOS Extractor**

![](https://i.imgur.com/Oy1IkcW.png)
Expand Down Expand Up @@ -231,6 +245,71 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con

![](https://i.imgur.com/Td6F5mm.png)

## **Portwell EFI BIOS Extractor**

![](https://i.imgur.com/ySdUSgf.png)

#### **Description**

Parses Portwell UEFI Unpacker EFI images (usually named "Update.efi"), extracts their SPI/BIOS/UEFI/EC firmware components and shows all relevant info. It supports all Portwell UEFI Unpacker revisions and formats, including those which contain Tiano compressed files. The output comprises only final firmware components and utilities which are directly usable by end users.

#### **Usage**

You can either Drag & Drop or manually enter the full path of a folder containing Portwell UEFI Unpacker EFI 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 python script, you need to have the following 3rd party Python module installed:

* [pefile](https://pypi.org/project/pefile/)

> pip3 install pefile
To run the python script or its built/frozen/compiled binary, you need to additionally have the following 3rd party tool 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/))

#### **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. Use pip to install pefile:

> pip3 install pefile
4. Build/Freeze/Compile:

> pyinstaller --noupx --onefile Portwell_EFI_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/EhCzMLk.png)

## **Apple EFI Sucatalog Link Grabber**

![](https://i.imgur.com/zTVFs4I.png)
Expand Down Expand Up @@ -445,9 +524,9 @@ Should work at all Windows operating systems which have Python 3.7 support. Wind

#### **Prerequisites**

To run the python script, you need to have the following 3rd party Python modules installed:
To run the python script, you need to have the following 3rd party Python module installed:

* [PEfile](https://pypi.python.org/pypi/pefile/)
* [pefile](https://pypi.org/project/pefile/)

> pip3 install pefile
Expand All @@ -467,7 +546,7 @@ PyInstaller can build/freeze/compile the utility at Windows, it is simple to run

> pip3 install pyinstaller
3. Use pip to install PEfile:
3. Use pip to install pefile:

> pip3 install pefile
Expand Down

0 comments on commit fe05f05

Please sign in to comment.