Portwell EFI BIOS Extractor v2.0_a4

Replaced any assertions
This commit is contained in:
platomav 2022-05-15 19:40:54 +03:00
parent 1480d663be
commit 8d262318dd
5 changed files with 229 additions and 6 deletions

View file

@ -159,7 +159,7 @@ def chk16_validate(data, tag, padd=0):
else:
printer('Checksum of UCP Module %s is valid!' % tag, padd)
# Check if input path or buffer is AMI UCP image
# Check if input is AMI UCP image
def is_ami_ucp(in_file):
buffer = file_to_bytes(in_file)

View file

@ -7,7 +7,7 @@ Dell PFS Update Extractor
Copyright (C) 2018-2022 Plato Mavropoulos
"""
TITLE = 'Dell PFS Update Extractor v6.0_a8'
TITLE = 'Dell PFS Update Extractor v6.0_a9'
import os
import io
@ -206,7 +206,7 @@ def is_pfs_hdr(in_buffer):
def is_pfs_ftr(in_buffer):
return PAT_DELL_FTR.search(in_buffer)
# Check if input path or buffer is Dell PFS/PKG image
# Check if input is Dell PFS/PKG image
def is_dell_pfs(in_file):
in_buffer = file_to_bytes(in_file)
@ -304,7 +304,7 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p
# Decompress PFS ZLIB section payload
try:
assert not is_zlib_error # ZLIB errors are critical
if is_zlib_error: raise Exception('ZLIB_ERROR') # ZLIB errors are critical
section_data = zlib.decompress(compressed_data) # ZLIB decompression
except:
section_data = zlib_data # Fallback to raw ZLIB data upon critical error

153
Portwell_EFI_Extract.py Normal file
View file

@ -0,0 +1,153 @@
#!/usr/bin/env python3
#coding=utf-8
"""
Portwell EFI Extract
Portwell EFI BIOS Extractor
Copyright (C) 2021-2022 Plato Mavropoulos
"""
TITLE = 'Portwell EFI BIOS Extractor v2.0_a4'
import os
import sys
import pefile
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.efi_comp import efi_decompress, is_efi_compressed
from common.path_ops import safe_name, make_dirs
from common.system import script_init, argparse_init, printer
from common.text_ops import file_to_bytes
PEFI_MAGIC = br'MZ'
FILE_MAGIC = br'<UU>'
FILE_NAMES = {
0 : 'Flash.efi',
1 : 'Fparts.txt',
2 : 'Update.nsh',
3 : 'Temp.bin',
4 : 'SaveDmiData.efi'
}
# Check if input is Portwell EFI executable
def is_portwell_efi(in_file):
in_buffer = file_to_bytes(in_file)
try: pe_buffer = get_portwell_pe(in_buffer)[1]
except: pe_buffer = b''
is_mz = in_buffer.startswith(PEFI_MAGIC) # EFI images start with PE Header MZ
is_uu = pe_buffer.startswith(FILE_MAGIC) # Portwell EFI files start with <UU>
return is_mz and is_uu
# Get PE of Portwell EFI executable
def get_portwell_pe(in_buffer):
pe_file = pefile.PE(data=in_buffer, fast_load=True) # Analyze EFI Portable Executable (PE)
pe_data = in_buffer[pe_file.OPTIONAL_HEADER.SizeOfImage:] # Skip EFI executable (pylint: disable=E1101)
return pe_file, pe_data
# Parse & Extract Portwell UEFI Unpacker
def portwell_efi_extract(input_buffer, out_path, padding=0):
extract_path = os.path.join(out_path + '_extracted')
make_dirs(extract_path, delete=True)
pe_file,pe_data = get_portwell_pe(input_buffer)
efi_title = get_unpacker_tag(input_buffer, pe_file)
printer(efi_title, padding)
efi_files = pe_data.split(FILE_MAGIC) # Split EFI Payload into <UU> file chunks
parse_efi_files(extract_path, efi_files[1:], padding)
# Get Portwell UEFI Unpacker tag
def get_unpacker_tag(input_buffer, pe_file):
unpacker_tag_txt = 'UEFI Unpacker'
for pe_section in pe_file.sections:
# Unpacker Tag, Version, Strings etc are found in .data PE section
if pe_section.Name.startswith(b'.data'):
pe_data_bgn = pe_section.PointerToRawData
pe_data_end = pe_data_bgn + pe_section.SizeOfRawData
# Decode any valid UTF-16 .data PE section info to a parsable text buffer
pe_data_txt = input_buffer[pe_data_bgn:pe_data_end].decode('utf-16','ignore')
# Search .data for UEFI Unpacker tag
unpacker_tag_bgn = pe_data_txt.find(unpacker_tag_txt)
if unpacker_tag_bgn != -1:
unpacker_tag_len = pe_data_txt[unpacker_tag_bgn:].find('=')
if unpacker_tag_len != -1:
unpacker_tag_end = unpacker_tag_bgn + unpacker_tag_len
unpacker_tag_raw = pe_data_txt[unpacker_tag_bgn:unpacker_tag_end]
# Found full UEFI Unpacker tag, store and slightly beautify the resulting text
unpacker_tag_txt = unpacker_tag_raw.strip().replace(' ',' ').replace('<',' <')
break # Found PE .data section, skip the rest
return unpacker_tag_txt
# Process Portwell UEFI Unpacker payload files
def parse_efi_files(extract_path, efi_files, padding):
for file_index,file_data in enumerate(efi_files):
if file_data in (b'', b'NULL'): continue # Skip empty/unused files
file_name = FILE_NAMES.get(file_index, f'Unknown_{file_index}.bin') # Assign Name to EFI file
printer(file_name, padding + 4) # Print EFI file name, indicate progress
if file_name.startswith('Unknown_'):
printer(f'Note: Detected new Portwell EFI file ID {file_index}!', padding + 8, pause=True) # Report new EFI files
file_path = os.path.join(extract_path, safe_name(file_name)) # Store EFI file output path
with open(file_path, 'wb') as out_file: out_file.write(file_data) # Store EFI file data to drive
# Attempt to detect EFI compression & decompress when applicable
if is_efi_compressed(file_data):
comp_fname = file_path + '.temp' # Store temporary compressed file name
os.replace(file_path, comp_fname) # Rename initial/compressed file
if efi_decompress(comp_fname, file_path, padding + 8) == 0:
os.remove(comp_fname) # Successful decompression, delete compressed file
if __name__ == '__main__':
# Set argparse Arguments
argparser = argparse_init()
arguments = argparser.parse_args()
# Initialize script (must be after argparse)
exit_code,input_files,output_path,padding = script_init(TITLE, arguments, 4)
for input_file in input_files:
input_name = os.path.basename(input_file)
printer(['***', input_name], padding - 4)
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
if not is_portwell_efi(input_buffer):
printer('Error: This is not a Portwell EFI Update Package!', padding)
continue # Next input file
extract_path = os.path.join(output_path, input_name)
portwell_efi_extract(input_buffer, extract_path, padding)
exit_code -= 1
printer('Done!', pause=True)
sys.exit(exit_code)

View file

@ -8,6 +8,7 @@
* [**Dell PFS Update Extractor**](#dell-pfs-update-extractor)
* [**AMI UCP BIOS Extractor**](#ami-ucp-bios-extractor)
* [**AMI BIOS Guard Extractor**](#ami-bios-guard-extractor)
* [**Portwell EFI BIOS Extractor**](#portwell-efi-bios-extractor)
## **Dell PFS Update Extractor**
@ -98,7 +99,7 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
To run the utility, you must have the following 3rd party tools at the "external" project directory:
* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress)
* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux)
* [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zz|7zzs for Linux)
Optionally, to decompile the AMI UCP \> AMI PFAT \> Intel BIOS Guard Scripts (when applicable), you must have the following 3rd party utility at the "external" project directory:
@ -198,5 +199,74 @@ Some Anti-Virus software may claim that the built/frozen/compiled executable con
![]()
## **Portwell EFI BIOS Extractor**
![]()
#### **Description**
Parses Portwell UEFI Unpacker EFI executables (usually named "Update.efi") and extracts their firmware (e.g. SPI, BIOS/UEFI, EC etc) and utilities (e.g. Flasher etc) components. It supports all known Portwell UEFI Unpacker revisions (v1.1, v1.2, v2.0) and formats (used, empty, null), including those which contain EFI 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 Portwell UEFI Unpacker EFI executable file(s). Optional arguments:
* -h or --help : show help message and exit
* -v or --version : show utility name and version
* -i or --input-dir : extract from given input directory
* -o or --output-dir : extract in given output directory
* -e or --auto-exit : skip press enter to exit prompts
* --static : use static-built external dependencies
#### **Compatibility**
Should work at all Windows, Linux or macOS operating systems which have Python 3.8 support.
#### **Prerequisites**
To run the utility, you must have the following 3rd party Python module installed:
* [pefile](https://pypi.org/project/pefile/)
> pip3 install pefile
Moreover, you must have the following 3rd party tool at the "external" project directory:
* [TianoCompress](https://github.com/tianocore/edk2/tree/master/BaseTools/Source/C/TianoCompress/) (e.g. [TianoCompress.exe for Windows](https://github.com/tianocore/edk2-BaseTools-win32/) or TianoCompress for Linux)
#### **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.8.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. Place prerequisite at the "external" project directory:
> TianoCompress
5. Build/Freeze/Compile:
> pyinstaller --add-data="external/*;external/" --noupx --onefile \<path-to-project\>\/Portwell_EFI_Extract.py
You should find the final utility executable at "dist" folder
#### **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**
![]()
###### _Donate Button Card Image: [Credit and Loan Pack](https://flaticon.com/free-icon/credit-card_3898076) by **Freepik** under Flaticon license_
###### _Donate Button Paypal Image: [Credit Cards Pack](https://flaticon.com/free-icon/paypal_349278) by **Freepik** under Flaticon license_

View file

@ -47,6 +47,6 @@ def efi_decompress(in_path, out_path, padding=0, comp_type='--uefi'):
return 1
printer('Succesfull EFI/Tiano decompression via TianoCompress!', padding)
printer('Succesfull EFI decompression via TianoCompress!', padding)
return 0