BIOSUtilities/Phoenix_TDK_Extract.py

183 lines
6.3 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
#coding=utf-8
"""
Phoenix TDK Extract
Phoenix TDK Packer Extractor
Copyright (C) 2021-2022 Plato Mavropoulos
"""
TITLE = 'Phoenix TDK Packer Extractor v2.0_a4'
import os
import sys
import lzma
import ctypes
# Stop __pycache__ generation
sys.dont_write_bytecode = True
from common.path_ops import safe_name, make_dirs
from common.patterns import PAT_PHOENIX_TDK
from common.struct_ops import get_struct, char, uint32_t
from common.system import script_init, argparse_init, printer
from common.text_ops import file_to_bytes
class PhoenixTdkHeader(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('Tag', char*8), # 0x00
('Size', uint32_t), # 0x08
('Count', uint32_t), # 0x0C
# 0x10
]
def _get_tag(self):
return self.Tag.decode('utf-8','ignore').strip()
def struct_print(self, p):
printer(['Tag :', self._get_tag()], p, False)
printer(['Size :', f'0x{self.Size:X}'], p, False)
printer(['Entries:', self.Count], p, False)
class PhoenixTdkEntry(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
]
COMP = {0: 'None', 1: 'LZMA'}
def get_name(self):
return self.Name.decode('utf-8','replace').strip()
def get_compression(self):
return self.COMP.get(self.Compressed, f'Unknown ({self.Compressed})')
def struct_print(self, p):
printer(['Name :', self.get_name()], p, False)
printer(['Offset :', f'0x{self.Offset:X}'], p, False)
printer(['Size :', f'0x{self.Size:X}'], p, False)
printer(['Compression:', self.get_compression()], p, False)
printer(['Reserved :', f'0x{self.Reserved:X}'], p, False)
# Scan input buffer for Phoenix TDK pattern
def get_phoenix_tdk(in_buffer):
return PAT_PHOENIX_TDK.search(in_buffer)
# Check if input is Phoenix TDK image
def is_phoenix_tdk(in_file):
buffer = file_to_bytes(in_file)
return bool(get_phoenix_tdk(buffer))
# Parse & Extract Phoenix Tools Development Kit (TDK) Packer
def phoenix_tdk_extract(input_buffer, output_path, padding=0):
exit_code = 0
extract_path = os.path.join(f'{output_path}_extracted')
make_dirs(extract_path, delete=True)
printer('Phoenix Tools Development Kit Packer', padding)
# Search for Phoenix TDK Package pattern
tdk_match = get_phoenix_tdk(input_buffer)
# Parse TDK Header structure
tdk_hdr = get_struct(input_buffer, tdk_match.start(), 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 dummy/placeholder data
entries_off = tdk_match.start() + 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)
# 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)
# Store TDK Entry raw data (relative to 0x0, not TDK Header)
mod_data = input_buffer[tdk_mod.Offset:tdk_mod.Offset + 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 = 2
# 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 = 3
# Decompress TDK Entry raw data, when applicable (i.e. LZMA)
if tdk_mod.get_compression() == 'LZMA':
mod_data = lzma.LZMADecompressor().decompress(mod_data)
# 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 dummy/placeholder TDK Entries Size
TDK_DUMMY_LEN = 0x200 # Top 2, Names only
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()
# Check if Phoenix TDK Packer pattern was found on executable
if not is_phoenix_tdk(input_buffer):
printer('Error: This is not a Phoenix TDK Packer executable!', padding)
continue # Next input file
extract_path = os.path.join(output_path, input_name)
if phoenix_tdk_extract(input_buffer, extract_path, padding) == 0:
exit_code -= 1
printer('Done!', pause=True)
sys.exit(exit_code)