mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-13 14:44:46 -04:00

Added AMI PFAT nested detection at each file Added Award BIOS payload naming at each file Switched Panasonic BIOS LZNT1 external library Improved Panasonic LZNT1 detection and length Improved Dell PFS code structure and fixed bugs Improved code exception handling (raise, catch) Improved code definitions (PEP8, docs, types) Fixed some arguments missing from help screens
276 lines
9 KiB
Python
276 lines
9 KiB
Python
#!/usr/bin/env python3 -B
|
|
# coding=utf-8
|
|
|
|
"""
|
|
Phoenix TDK Extract
|
|
Phoenix TDK Packer Extractor
|
|
Copyright (C) 2021-2024 Plato Mavropoulos
|
|
"""
|
|
|
|
import ctypes
|
|
import logging
|
|
import lzma
|
|
import os
|
|
|
|
from common.path_ops import make_dirs, safe_name
|
|
from common.pe_ops import get_pe_file, get_pe_info
|
|
from common.patterns import PAT_MICROSOFT_MZ, PAT_MICROSOFT_PE, PAT_PHOENIX_TDK
|
|
from common.struct_ops import Char, get_struct, UInt32
|
|
from common.system import printer
|
|
from common.templates import BIOSUtility
|
|
from common.text_ops import file_to_bytes
|
|
|
|
TITLE = 'Phoenix TDK Packer Extractor v3.0'
|
|
|
|
|
|
class PhoenixTdkHeader(ctypes.LittleEndianStructure):
|
|
""" Phoenix TDK Header """
|
|
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('Tag', Char * 8), # 0x00
|
|
('Size', UInt32), # 0x08
|
|
('Count', UInt32), # 0x0C
|
|
# 0x10
|
|
]
|
|
|
|
def _get_tag(self):
|
|
return self.Tag.decode('utf-8', 'ignore').strip()
|
|
|
|
def struct_print(self, padd):
|
|
""" Display structure information """
|
|
|
|
printer(['Tag :', self._get_tag()], padd, False)
|
|
printer(['Size :', f'0x{self.Size:X}'], padd, False)
|
|
printer(['Entries:', self.Count], padd, False)
|
|
|
|
|
|
class PhoenixTdkEntry(ctypes.LittleEndianStructure):
|
|
""" Phoenix TDK Entry """
|
|
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('Name', Char * 256), # 0x000
|
|
('Offset', UInt32), # 0x100
|
|
('Size', UInt32), # 0x104
|
|
('Compressed', UInt32), # 0x108
|
|
('Reserved', UInt32), # 0x10C
|
|
# 0x110
|
|
]
|
|
|
|
COMP = {0: 'None', 1: 'LZMA'}
|
|
|
|
def __init__(self, mz_base, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.mz_base = mz_base
|
|
|
|
def get_name(self):
|
|
""" Get TDK Entry decoded name """
|
|
|
|
return self.Name.decode('utf-8', 'replace').strip()
|
|
|
|
def get_offset(self):
|
|
""" Get TDK Entry absolute offset """
|
|
|
|
return self.mz_base + self.Offset
|
|
|
|
def get_compression(self):
|
|
""" Get TDK Entry compression type """
|
|
|
|
return self.COMP.get(self.Compressed, f'Unknown ({self.Compressed})')
|
|
|
|
def struct_print(self, padd):
|
|
""" Display structure information """
|
|
|
|
printer(['Name :', self.get_name()], padd, False)
|
|
printer(['Offset :', f'0x{self.get_offset():X}'], padd, False)
|
|
printer(['Size :', f'0x{self.Size:X}'], padd, False)
|
|
printer(['Compression:', self.get_compression()], padd, False)
|
|
printer(['Reserved :', f'0x{self.Reserved:X}'], padd, False)
|
|
|
|
|
|
def get_tdk_base(in_buffer, pack_off):
|
|
""" Get Phoenix TDK Executable (MZ) Base Offset """
|
|
|
|
tdk_base_off = None # Initialize Phoenix TDK Base MZ Offset
|
|
|
|
# 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_match in mz_ord:
|
|
mz_off = mz_match.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')
|
|
|
|
# Skip MZ (DOS) with bad PE (NT) image Offset
|
|
if pe_off == mz_off or pe_off >= pack_off:
|
|
continue
|
|
|
|
# Check if potential MZ > PE image magic value is valid
|
|
if PAT_MICROSOFT_PE.search(in_buffer[pe_off:pe_off + 0x4]):
|
|
try:
|
|
# Parse detected MZ > PE > Image, quickly (fast_load)
|
|
pe_file = get_pe_file(in_buffer[mz_off:], silent=True)
|
|
|
|
# Parse detected MZ > PE > Info
|
|
pe_info = get_pe_info(pe_file, silent=True)
|
|
|
|
# Parse detected MZ > PE > Info > Product Name
|
|
pe_name = pe_info.get(b'ProductName', b'')
|
|
except Exception as error: # pylint: disable=broad-except
|
|
# Any error means no MZ > PE > Info > Product Name
|
|
logging.debug('Error: Invalid potential MZ > PE match at 0x%X: %s', pe_off, error)
|
|
|
|
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 is not None:
|
|
break
|
|
else:
|
|
# No TDK Base Offset could be found, assume 0x0
|
|
tdk_base_off = 0x0
|
|
|
|
return tdk_base_off
|
|
|
|
|
|
def get_phoenix_tdk(in_buffer):
|
|
""" Scan input buffer for valid Phoenix TDK image """
|
|
|
|
# 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
|
|
|
|
|
|
def is_phoenix_tdk(in_file):
|
|
""" Check if input contains valid Phoenix TDK image """
|
|
|
|
buffer = file_to_bytes(in_file)
|
|
|
|
return bool(get_phoenix_tdk(buffer)[1] is not None)
|
|
|
|
|
|
def phoenix_tdk_extract(input_file, extract_path, padding=0):
|
|
""" Parse & Extract Phoenix Tools Development Kit (TDK) Packer """
|
|
|
|
exit_code = 0
|
|
|
|
input_buffer = file_to_bytes(input_file)
|
|
|
|
make_dirs(extract_path, delete=True)
|
|
|
|
printer('Phoenix Tools Development Kit Packer', padding)
|
|
|
|
base_off, pack_off = get_phoenix_tdk(input_buffer)
|
|
|
|
# Parse TDK Header structure
|
|
tdk_hdr = get_struct(input_buffer, pack_off, PhoenixTdkHeader)
|
|
|
|
# Print TDK Header structure info
|
|
printer('Phoenix TDK Header:\n', padding + 4)
|
|
|
|
tdk_hdr.struct_print(padding + 8)
|
|
|
|
# Check if reported TDK Header Size matches manual TDK Entry Count calculation
|
|
if tdk_hdr.Size != TDK_HDR_LEN + TDK_DUMMY_LEN + tdk_hdr.Count * TDK_MOD_LEN:
|
|
printer('Error: Phoenix TDK Header Size & Entry Count mismatch!\n', padding + 8, pause=True)
|
|
|
|
exit_code = 1
|
|
|
|
# Store TDK Entries offset after the placeholder data
|
|
entries_off = pack_off + TDK_HDR_LEN + TDK_DUMMY_LEN
|
|
|
|
# Parse and extract each TDK Header Entry
|
|
for entry_index in range(tdk_hdr.Count):
|
|
# Parse TDK Entry structure
|
|
tdk_mod = get_struct(input_buffer, entries_off + entry_index * TDK_MOD_LEN, PhoenixTdkEntry, [base_off])
|
|
|
|
# Print TDK Entry structure info
|
|
printer(f'Phoenix TDK Entry ({entry_index + 1}/{tdk_hdr.Count}):\n', padding + 8)
|
|
|
|
tdk_mod.struct_print(padding + 12)
|
|
|
|
# Get TDK Entry raw data Offset (TDK Base + Entry Offset)
|
|
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
|
|
if len(mod_data) != tdk_mod.Size:
|
|
printer('Error: Phoenix TDK Entry > Data is truncated!\n', padding + 12, pause=True)
|
|
|
|
exit_code = 3
|
|
|
|
# Check if TDK Entry Reserved is present
|
|
if tdk_mod.Reserved:
|
|
printer('Error: Phoenix TDK Entry > Reserved is not empty!\n', padding + 12, pause=True)
|
|
|
|
exit_code = 4
|
|
|
|
# Decompress TDK Entry raw data, when applicable (i.e. LZMA)
|
|
if tdk_mod.get_compression() == 'LZMA':
|
|
try:
|
|
mod_data = lzma.LZMADecompressor().decompress(mod_data)
|
|
except Exception as error: # pylint: disable=broad-except
|
|
printer(f'Error: Phoenix TDK Entry > LZMA decompression failed: {error}!\n', padding + 12, pause=True)
|
|
|
|
exit_code = 5
|
|
|
|
# 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'
|
|
|
|
# Generate TDK Entry file data output path
|
|
mod_file = os.path.join(extract_path, safe_name(mod_name))
|
|
|
|
# Account for potential duplicate file names
|
|
if os.path.isfile(mod_file):
|
|
mod_file += f'_{entry_index + 1:02d}'
|
|
|
|
# Save TDK Entry data to output file
|
|
with open(mod_file, 'wb') as out_file:
|
|
out_file.write(mod_data)
|
|
|
|
return exit_code
|
|
|
|
|
|
# Get ctypes Structure Sizes
|
|
TDK_HDR_LEN = ctypes.sizeof(PhoenixTdkHeader)
|
|
TDK_MOD_LEN = ctypes.sizeof(PhoenixTdkEntry)
|
|
|
|
# Set placeholder TDK Entries Size
|
|
TDK_DUMMY_LEN = 0x200
|
|
|
|
if __name__ == '__main__':
|
|
BIOSUtility(title=TITLE, check=is_phoenix_tdk, main=phoenix_tdk_extract).run_utility()
|