mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-13 06:34:42 -04:00
Phoenix TDK Packer Extractor v2.0_a5
Added detection of TDK Packer executable base offset Improve TDK unpacking at weird images
This commit is contained in:
parent
7bb0c5f9a9
commit
8b561640db
4 changed files with 120 additions and 35 deletions
|
@ -7,18 +7,19 @@ Phoenix TDK Packer Extractor
|
||||||
Copyright (C) 2021-2022 Plato Mavropoulos
|
Copyright (C) 2021-2022 Plato Mavropoulos
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TITLE = 'Phoenix TDK Packer Extractor v2.0_a4'
|
TITLE = 'Phoenix TDK Packer Extractor v2.0_a5'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import lzma
|
import lzma
|
||||||
|
import pefile
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
# Stop __pycache__ generation
|
# Stop __pycache__ generation
|
||||||
sys.dont_write_bytecode = True
|
sys.dont_write_bytecode = True
|
||||||
|
|
||||||
from common.path_ops import safe_name, make_dirs
|
from common.path_ops import safe_name, make_dirs
|
||||||
from common.patterns import PAT_PHOENIX_TDK
|
from common.patterns import PAT_PHOENIX_TDK, PAT_MICROSOFT_MZ, PAT_MICROSOFT_PE
|
||||||
from common.struct_ops import get_struct, char, uint32_t
|
from common.struct_ops import get_struct, char, uint32_t
|
||||||
from common.system import script_init, argparse_init, printer
|
from common.system import script_init, argparse_init, printer
|
||||||
from common.text_ops import file_to_bytes
|
from common.text_ops import file_to_bytes
|
||||||
|
@ -53,31 +54,98 @@ class PhoenixTdkEntry(ctypes.LittleEndianStructure):
|
||||||
|
|
||||||
COMP = {0: 'None', 1: 'LZMA'}
|
COMP = {0: 'None', 1: 'LZMA'}
|
||||||
|
|
||||||
|
def __init__(self, mz_base, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.Base = mz_base
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return self.Name.decode('utf-8','replace').strip()
|
return self.Name.decode('utf-8','replace').strip()
|
||||||
|
|
||||||
|
def get_offset(self):
|
||||||
|
return self.Base + self.Offset
|
||||||
|
|
||||||
def get_compression(self):
|
def get_compression(self):
|
||||||
return self.COMP.get(self.Compressed, f'Unknown ({self.Compressed})')
|
return self.COMP.get(self.Compressed, f'Unknown ({self.Compressed})')
|
||||||
|
|
||||||
def struct_print(self, p):
|
def struct_print(self, p):
|
||||||
printer(['Name :', self.get_name()], p, False)
|
printer(['Name :', self.get_name()], p, False)
|
||||||
printer(['Offset :', f'0x{self.Offset:X}'], p, False)
|
printer(['Offset :', f'0x{self.get_offset():X}'], p, False)
|
||||||
printer(['Size :', f'0x{self.Size:X}'], p, False)
|
printer(['Size :', f'0x{self.Size:X}'], p, False)
|
||||||
printer(['Compression:', self.get_compression()], p, False)
|
printer(['Compression:', self.get_compression()], p, False)
|
||||||
printer(['Reserved :', f'0x{self.Reserved:X}'], p, False)
|
printer(['Reserved :', f'0x{self.Reserved:X}'], p, False)
|
||||||
|
|
||||||
# Scan input buffer for Phoenix TDK pattern
|
# Get Phoenix TDK Executable (MZ) Base Offset
|
||||||
def get_phoenix_tdk(in_buffer):
|
def get_tdk_base(in_buffer, pack_off):
|
||||||
return PAT_PHOENIX_TDK.search(in_buffer)
|
tdk_base_off = None # Initialize Phoenix TDK Base MZ Offset
|
||||||
|
|
||||||
# Check if input is Phoenix TDK image
|
# Scan input file for all Microsoft executable patterns (MZ) before TDK Header Offset
|
||||||
|
mz_all = [mz for mz in PAT_MICROSOFT_MZ.finditer(in_buffer) if mz.start() < pack_off]
|
||||||
|
|
||||||
|
# Phoenix TDK Header structure is an index table for all TDK files
|
||||||
|
# Each TDK file is referenced from the TDK Packer executable base
|
||||||
|
# The TDK Header is always at the end of the TDK Packer executable
|
||||||
|
# Thus, prefer the TDK Packer executable (MZ) closest to TDK Header
|
||||||
|
# For speed, check MZ closest to (or at) 0x0 first (expected input)
|
||||||
|
mz_ord = [mz_all[0]] + list(reversed(mz_all[1:]))
|
||||||
|
|
||||||
|
# Parse each detected MZ
|
||||||
|
for mz in mz_ord:
|
||||||
|
mz_off = mz.start()
|
||||||
|
|
||||||
|
# MZ (DOS) > PE (NT) image offset is found at offset 0x3C-0x40 relative to MZ base
|
||||||
|
pe_off = mz_off + int.from_bytes(in_buffer[mz_off + 0x3C:mz_off + 0x40], 'little')
|
||||||
|
|
||||||
|
# Check if potential MZ > PE image magic value is valid
|
||||||
|
if PAT_MICROSOFT_PE.search(in_buffer[pe_off:pe_off + 0x4]):
|
||||||
|
try:
|
||||||
|
# Analyze detected MZ > PE image buffer
|
||||||
|
pe_file = pefile.PE(data=in_buffer[mz_off:])
|
||||||
|
|
||||||
|
# Attempt to retrieve the PE > "Product Name" version string value
|
||||||
|
pe_name = pe_file.FileInfo[0][0].StringTable[0].entries[b'ProductName']
|
||||||
|
except:
|
||||||
|
# Any error means no PE > "Product Name" retrieved
|
||||||
|
pe_name = b''
|
||||||
|
|
||||||
|
# Check for valid Phoenix TDK Packer PE > "Product Name"
|
||||||
|
# Expected value is "TDK Packer (Extractor for Windows)"
|
||||||
|
if pe_name.upper().startswith(b'TDK PACKER'):
|
||||||
|
# Set TDK Base Offset to valid TDK Packer MZ offset
|
||||||
|
tdk_base_off = mz_off
|
||||||
|
|
||||||
|
# Stop parsing detected MZ once TDK Base Offset is found
|
||||||
|
if tdk_base_off:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# No TDK Base Offset could be found, assume 0x0
|
||||||
|
tdk_base_off = 0x0
|
||||||
|
|
||||||
|
return tdk_base_off
|
||||||
|
|
||||||
|
# Scan input buffer for valid Phoenix TDK image
|
||||||
|
def get_phoenix_tdk(in_buffer):
|
||||||
|
# Scan input buffer for Phoenix TDK pattern
|
||||||
|
tdk_match = PAT_PHOENIX_TDK.search(in_buffer)
|
||||||
|
|
||||||
|
if not tdk_match:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# Set Phoenix TDK Header ($PACK) Offset
|
||||||
|
tdk_pack_off = tdk_match.start()
|
||||||
|
|
||||||
|
# Get Phoenix TDK Executable (MZ) Base Offset
|
||||||
|
tdk_base_off = get_tdk_base(in_buffer, tdk_pack_off)
|
||||||
|
|
||||||
|
return tdk_base_off, tdk_pack_off
|
||||||
|
|
||||||
|
# Check if input contains valid Phoenix TDK image
|
||||||
def is_phoenix_tdk(in_file):
|
def is_phoenix_tdk(in_file):
|
||||||
buffer = file_to_bytes(in_file)
|
buffer = file_to_bytes(in_file)
|
||||||
|
|
||||||
return bool(get_phoenix_tdk(buffer))
|
return bool(get_phoenix_tdk(buffer)[1])
|
||||||
|
|
||||||
# Parse & Extract Phoenix Tools Development Kit (TDK) Packer
|
# Parse & Extract Phoenix Tools Development Kit (TDK) Packer
|
||||||
def phoenix_tdk_extract(input_buffer, output_path, padding=0):
|
def phoenix_tdk_extract(input_buffer, output_path, pack_off, base_off=0, padding=0):
|
||||||
exit_code = 0
|
exit_code = 0
|
||||||
|
|
||||||
extract_path = os.path.join(f'{output_path}_extracted')
|
extract_path = os.path.join(f'{output_path}_extracted')
|
||||||
|
@ -86,11 +154,8 @@ def phoenix_tdk_extract(input_buffer, output_path, padding=0):
|
||||||
|
|
||||||
printer('Phoenix Tools Development Kit Packer', padding)
|
printer('Phoenix Tools Development Kit Packer', padding)
|
||||||
|
|
||||||
# Search for Phoenix TDK Package pattern
|
|
||||||
tdk_match = get_phoenix_tdk(input_buffer)
|
|
||||||
|
|
||||||
# Parse TDK Header structure
|
# Parse TDK Header structure
|
||||||
tdk_hdr = get_struct(input_buffer, tdk_match.start(), PhoenixTdkHeader)
|
tdk_hdr = get_struct(input_buffer, pack_off, PhoenixTdkHeader)
|
||||||
|
|
||||||
# Print TDK Header structure info
|
# Print TDK Header structure info
|
||||||
printer('Phoenix TDK Header:\n', padding + 4)
|
printer('Phoenix TDK Header:\n', padding + 4)
|
||||||
|
@ -101,34 +166,46 @@ def phoenix_tdk_extract(input_buffer, output_path, padding=0):
|
||||||
printer('Error: Phoenix TDK Header Size & Entry Count mismatch!\n', padding + 8, pause=True)
|
printer('Error: Phoenix TDK Header Size & Entry Count mismatch!\n', padding + 8, pause=True)
|
||||||
exit_code = 1
|
exit_code = 1
|
||||||
|
|
||||||
# Store TDK Entries offset after the dummy/placeholder data
|
# Store TDK Entries offset after the placeholder data
|
||||||
entries_off = tdk_match.start() + TDK_HDR_LEN + TDK_DUMMY_LEN
|
entries_off = pack_off + TDK_HDR_LEN + TDK_DUMMY_LEN
|
||||||
|
|
||||||
# Parse and extract each TDK Header Entry
|
# Parse and extract each TDK Header Entry
|
||||||
for entry_index in range(tdk_hdr.Count):
|
for entry_index in range(tdk_hdr.Count):
|
||||||
# Parse TDK Entry structure
|
# Parse TDK Entry structure
|
||||||
tdk_mod = get_struct(input_buffer, entries_off + entry_index * TDK_MOD_LEN, PhoenixTdkEntry)
|
tdk_mod = get_struct(input_buffer, entries_off + entry_index * TDK_MOD_LEN, PhoenixTdkEntry, [base_off])
|
||||||
|
|
||||||
# Print TDK Entry structure info
|
# Print TDK Entry structure info
|
||||||
printer(f'Phoenix TDK Entry ({entry_index + 1}/{tdk_hdr.Count}):\n', padding + 8)
|
printer(f'Phoenix TDK Entry ({entry_index + 1}/{tdk_hdr.Count}):\n', padding + 8)
|
||||||
tdk_mod.struct_print(padding + 12)
|
tdk_mod.struct_print(padding + 12)
|
||||||
|
|
||||||
# Store TDK Entry raw data (relative to 0x0, not TDK Header)
|
# Get TDK Entry raw data Offset (TDK Base + Entry Offset)
|
||||||
mod_data = input_buffer[tdk_mod.Offset:tdk_mod.Offset + tdk_mod.Size]
|
mod_off = tdk_mod.get_offset()
|
||||||
|
|
||||||
|
# Check if TDK Entry raw data Offset is valid
|
||||||
|
if mod_off >= len(input_buffer):
|
||||||
|
printer('Error: Phoenix TDK Entry > Offset is out of bounds!\n', padding + 12, pause=True)
|
||||||
|
exit_code = 2
|
||||||
|
|
||||||
|
# Store TDK Entry raw data (relative to TDK Base, not TDK Header)
|
||||||
|
mod_data = input_buffer[mod_off:mod_off + tdk_mod.Size]
|
||||||
|
|
||||||
# Check if TDK Entry raw data is complete
|
# Check if TDK Entry raw data is complete
|
||||||
if len(mod_data) != tdk_mod.Size:
|
if len(mod_data) != tdk_mod.Size:
|
||||||
printer('Error: Phoenix TDK Entry > Data is truncated!\n', padding + 12, pause=True)
|
printer('Error: Phoenix TDK Entry > Data is truncated!\n', padding + 12, pause=True)
|
||||||
exit_code = 2
|
exit_code = 3
|
||||||
|
|
||||||
# Check if TDK Entry Reserved is present
|
# Check if TDK Entry Reserved is present
|
||||||
if tdk_mod.Reserved:
|
if tdk_mod.Reserved:
|
||||||
printer('Error: Phoenix TDK Entry > Reserved is not empty!\n', padding + 12, pause=True)
|
printer('Error: Phoenix TDK Entry > Reserved is not empty!\n', padding + 12, pause=True)
|
||||||
exit_code = 3
|
exit_code = 4
|
||||||
|
|
||||||
# Decompress TDK Entry raw data, when applicable (i.e. LZMA)
|
# Decompress TDK Entry raw data, when applicable (i.e. LZMA)
|
||||||
if tdk_mod.get_compression() == 'LZMA':
|
if tdk_mod.get_compression() == 'LZMA':
|
||||||
|
try:
|
||||||
mod_data = lzma.LZMADecompressor().decompress(mod_data)
|
mod_data = lzma.LZMADecompressor().decompress(mod_data)
|
||||||
|
except:
|
||||||
|
printer('Error: Phoenix TDK Entry > LZMA decompression failed!\n', padding + 12, pause=True)
|
||||||
|
exit_code = 5
|
||||||
|
|
||||||
# Generate TDK Entry file name, avoid crash if Entry data is bad
|
# Generate TDK Entry file name, avoid crash if Entry data is bad
|
||||||
mod_name = tdk_mod.get_name() or f'Unknown_{entry_index + 1:02d}.bin'
|
mod_name = tdk_mod.get_name() or f'Unknown_{entry_index + 1:02d}.bin'
|
||||||
|
@ -148,8 +225,8 @@ def phoenix_tdk_extract(input_buffer, output_path, padding=0):
|
||||||
TDK_HDR_LEN = ctypes.sizeof(PhoenixTdkHeader)
|
TDK_HDR_LEN = ctypes.sizeof(PhoenixTdkHeader)
|
||||||
TDK_MOD_LEN = ctypes.sizeof(PhoenixTdkEntry)
|
TDK_MOD_LEN = ctypes.sizeof(PhoenixTdkEntry)
|
||||||
|
|
||||||
# Set dummy/placeholder TDK Entries Size
|
# Set placeholder TDK Entries Size
|
||||||
TDK_DUMMY_LEN = 0x200 # Top 2, Names only
|
TDK_DUMMY_LEN = 0x200
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Set argparse Arguments
|
# Set argparse Arguments
|
||||||
|
@ -166,15 +243,17 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
|
with open(input_file, 'rb') as in_file: input_buffer = in_file.read()
|
||||||
|
|
||||||
|
tdk_base_off,tdk_pack_off = get_phoenix_tdk(input_buffer)
|
||||||
|
|
||||||
# Check if Phoenix TDK Packer pattern was found on executable
|
# Check if Phoenix TDK Packer pattern was found on executable
|
||||||
if not is_phoenix_tdk(input_buffer):
|
if not tdk_pack_off:
|
||||||
printer('Error: This is not a Phoenix TDK Packer executable!', padding)
|
printer('Error: This is not a Phoenix TDK Packer executable!', padding)
|
||||||
|
|
||||||
continue # Next input file
|
continue # Next input file
|
||||||
|
|
||||||
extract_path = os.path.join(output_path, input_name)
|
extract_path = os.path.join(output_path, input_name)
|
||||||
|
|
||||||
if phoenix_tdk_extract(input_buffer, extract_path, padding) == 0:
|
if phoenix_tdk_extract(input_buffer, extract_path, tdk_pack_off, tdk_base_off, padding) == 0:
|
||||||
exit_code -= 1
|
exit_code -= 1
|
||||||
|
|
||||||
printer('Done!', pause=True)
|
printer('Done!', pause=True)
|
||||||
|
|
|
@ -7,7 +7,7 @@ Portwell EFI Update Extractor
|
||||||
Copyright (C) 2021-2022 Plato Mavropoulos
|
Copyright (C) 2021-2022 Plato Mavropoulos
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TITLE = 'Portwell EFI Update Extractor v2.0_a5'
|
TITLE = 'Portwell EFI Update Extractor v2.0_a6'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
@ -18,13 +18,10 @@ sys.dont_write_bytecode = True
|
||||||
|
|
||||||
from common.efi_comp import efi_decompress, is_efi_compressed
|
from common.efi_comp import efi_decompress, is_efi_compressed
|
||||||
from common.path_ops import safe_name, make_dirs
|
from common.path_ops import safe_name, make_dirs
|
||||||
|
from common.patterns import PAT_PORTWELL_EFI, PAT_MICROSOFT_MZ
|
||||||
from common.system import script_init, argparse_init, printer
|
from common.system import script_init, argparse_init, printer
|
||||||
from common.text_ops import file_to_bytes
|
from common.text_ops import file_to_bytes
|
||||||
|
|
||||||
PEFI_MAGIC = br'MZ'
|
|
||||||
|
|
||||||
FILE_MAGIC = br'<UU>'
|
|
||||||
|
|
||||||
FILE_NAMES = {
|
FILE_NAMES = {
|
||||||
0 : 'Flash.efi',
|
0 : 'Flash.efi',
|
||||||
1 : 'Fparts.txt',
|
1 : 'Fparts.txt',
|
||||||
|
@ -40,8 +37,8 @@ def is_portwell_efi(in_file):
|
||||||
try: pe_buffer = get_portwell_pe(in_buffer)[1]
|
try: pe_buffer = get_portwell_pe(in_buffer)[1]
|
||||||
except: pe_buffer = b''
|
except: pe_buffer = b''
|
||||||
|
|
||||||
is_mz = in_buffer.startswith(PEFI_MAGIC) # EFI images start with PE Header MZ
|
is_mz = in_buffer.startswith(PAT_MICROSOFT_MZ.pattern) # EFI images start with PE Header MZ
|
||||||
is_uu = pe_buffer.startswith(FILE_MAGIC) # Portwell EFI files start with <UU>
|
is_uu = pe_buffer.startswith(PAT_PORTWELL_EFI.pattern) # Portwell EFI files start with <UU>
|
||||||
|
|
||||||
return is_mz and is_uu
|
return is_mz and is_uu
|
||||||
|
|
||||||
|
@ -65,7 +62,7 @@ def portwell_efi_extract(input_buffer, output_path, padding=0):
|
||||||
|
|
||||||
printer(efi_title, padding)
|
printer(efi_title, padding)
|
||||||
|
|
||||||
efi_files = pe_data.split(FILE_MAGIC) # Split EFI Payload into <UU> file chunks
|
efi_files = pe_data.split(PAT_PORTWELL_EFI.pattern) # Split EFI Payload into <UU> file chunks
|
||||||
|
|
||||||
parse_efi_files(extract_path, efi_files[1:], padding)
|
parse_efi_files(extract_path, efi_files[1:], padding)
|
||||||
|
|
||||||
|
|
10
README.md
10
README.md
|
@ -225,7 +225,9 @@ Should work at all Windows, Linux or macOS operating systems which have Python 3
|
||||||
|
|
||||||
#### **Prerequisites**
|
#### **Prerequisites**
|
||||||
|
|
||||||
No prerequisites needed to run the utility.
|
To run the utility, you must have the following 3rd party Python module installed:
|
||||||
|
|
||||||
|
* [pefile](https://pypi.org/project/pefile/)
|
||||||
|
|
||||||
#### **Build/Freeze/Compile with PyInstaller**
|
#### **Build/Freeze/Compile with PyInstaller**
|
||||||
|
|
||||||
|
@ -239,7 +241,11 @@ PyInstaller can build/freeze/compile the utility at all three supported platform
|
||||||
|
|
||||||
> pip3 install pyinstaller
|
> pip3 install pyinstaller
|
||||||
|
|
||||||
3. Build/Freeze/Compile:
|
3. Use pip to install pefile:
|
||||||
|
|
||||||
|
> pip3 install pefile
|
||||||
|
|
||||||
|
4. Build/Freeze/Compile:
|
||||||
|
|
||||||
> pyinstaller --noupx --onefile \<path-to-project\>\/Phoenix_TDK_Extract.py
|
> pyinstaller --noupx --onefile \<path-to-project\>\/Phoenix_TDK_Extract.py
|
||||||
|
|
||||||
|
|
|
@ -13,4 +13,7 @@ PAT_DELL_FTR = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90')
|
||||||
PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL)
|
PAT_DELL_HDR = re.compile(br'\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL)
|
||||||
PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL)
|
PAT_DELL_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL)
|
||||||
PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL)
|
PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL)
|
||||||
|
PAT_MICROSOFT_MZ = re.compile(br'MZ')
|
||||||
|
PAT_MICROSOFT_PE = re.compile(br'PE\x00\x00')
|
||||||
PAT_PHOENIX_TDK = re.compile(br'\$PACK\x00{3}..\x00{2}.\x00{3}', re.DOTALL)
|
PAT_PHOENIX_TDK = re.compile(br'\$PACK\x00{3}..\x00{2}.\x00{3}', re.DOTALL)
|
||||||
|
PAT_PORTWELL_EFI = re.compile(br'<UU>')
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue