mirror of
https://codeberg.org/libreboot/BIOSUtilities.git
synced 2025-06-30 22:39:57 +00:00
Compare commits
52 commits
Author | SHA1 | Date | |
---|---|---|---|
|
03ae0cf070 | ||
|
65467da21c | ||
|
71cbfdaf8b | ||
|
5d5dc5828b | ||
|
c1f4ab9121 | ||
|
6de50c422f | ||
|
5f364f4759 | ||
|
0c6c35b354 | ||
|
0e170334c6 | ||
|
389c30bb65 | ||
|
c144ad804c | ||
|
48562b0f68 | ||
|
df47293d01 | ||
|
69784889cb | ||
|
0317009e09 | ||
|
4749414f81 | ||
|
cd2704f743 | ||
|
be90f364d2 | ||
|
82cd4336bd | ||
|
fc73921967 | ||
|
f5905ec662 | ||
|
fddd33aafd | ||
|
7111757764 | ||
|
aea54aeaad | ||
|
8b561640db | ||
|
7bb0c5f9a9 | ||
|
b4a93513f7 | ||
|
8d262318dd | ||
|
1480d663be | ||
|
dd250a8595 | ||
|
2fb8ff913b | ||
|
ec73b9e950 | ||
|
7e96a62f42 | ||
|
cf88fc7a5d | ||
|
9b29c37c65 | ||
|
982e3f3fc9 | ||
|
a2eca0aac6 | ||
|
44546a67c5 | ||
|
40686d5edf | ||
|
672b4b2321 | ||
|
d02de2ac57 | ||
|
d6e8d31391 | ||
|
96e87455de | ||
|
2a98460d71 | ||
|
f2be701423 | ||
|
46172a218b | ||
|
7c00479a9e | ||
|
132457afda | ||
|
2029ffc8b7 | ||
|
0f05e9b0be | ||
|
e56aa66895 | ||
|
ec14803065 |
47 changed files with 4841 additions and 3751 deletions
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -1,2 +0,0 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Skip all external files
|
||||
external/*
|
||||
|
||||
# Keep external > requirements file
|
||||
!external/requirements.txt
|
|
@ -1,321 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
AMI PFAT Extract
|
||||
AMI BIOS Guard Extractor
|
||||
Copyright (C) 2018-2021 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
print('AMI BIOS Guard Extractor v3.2')
|
||||
|
||||
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 re
|
||||
import ctypes
|
||||
import shutil
|
||||
import traceback
|
||||
|
||||
# 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: ABGE crashed, please report the following:\n')
|
||||
traceback.print_exception(exc_type, exc_value, tb)
|
||||
input('\nPress enter to exit')
|
||||
sys.exit(1)
|
||||
|
||||
# Pause after any unexpected python exception
|
||||
sys.excepthook = show_exception_and_exit
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
# https://github.com/allowitsme/big-tool by Dmitry Frolov
|
||||
try :
|
||||
from big_script_tool import BigScript
|
||||
is_bgst = True
|
||||
except :
|
||||
is_bgst = False
|
||||
|
||||
# Set ctypes Structure types
|
||||
char = ctypes.c_char
|
||||
uint8_t = ctypes.c_ubyte
|
||||
uint16_t = ctypes.c_ushort
|
||||
uint32_t = ctypes.c_uint
|
||||
uint64_t = ctypes.c_uint64
|
||||
|
||||
class PFAT_Header(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Size', uint32_t), # 0x00
|
||||
('Checksum', uint32_t), # 0x04 Unknown 16-bits
|
||||
('Tag', char*8), # 0x04 _AMIPFAT
|
||||
('Flags', uint8_t), # 0x10
|
||||
# 0x11
|
||||
]
|
||||
|
||||
def pfat_print(self) :
|
||||
print('\n PFAT Main Header:\n')
|
||||
print(' Size : 0x%X' % self.Size)
|
||||
print(' Checksum : 0x%0.4X' % self.Checksum)
|
||||
print(' Tag : %s' % self.Tag.decode('utf-8'))
|
||||
print(' Flags : 0x%0.2X' % self.Flags)
|
||||
|
||||
class PFAT_Block_Header(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('PFATVerMajor', uint16_t), # 0x00
|
||||
('PFATVerMinor', uint16_t), # 0x02
|
||||
('PlatformID', uint8_t*16), # 0x04
|
||||
('Attributes', uint32_t), # 0x14
|
||||
('ScriptVerMajor', uint16_t), # 0x16
|
||||
('ScriptVerMinor', uint16_t), # 0x18
|
||||
('ScriptSize', uint32_t), # 0x1C
|
||||
('DataSize', uint32_t), # 0x20
|
||||
('BIOSSVN', uint32_t), # 0x24
|
||||
('ECSVN', uint32_t), # 0x28
|
||||
('VendorInfo', uint32_t), # 0x2C
|
||||
# 0x30
|
||||
]
|
||||
|
||||
def __init__(self, count, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.count = count
|
||||
|
||||
def get_flags(self) :
|
||||
attr = PFAT_Block_Header_GetAttributes()
|
||||
attr.asbytes = self.Attributes
|
||||
|
||||
return attr.b.SFAM, attr.b.ProtectEC, attr.b.GFXMitDis, attr.b.FTU, attr.b.Reserved
|
||||
|
||||
def pfat_print(self) :
|
||||
no_yes = ['No','Yes']
|
||||
f1,f2,f3,f4,f5 = self.get_flags()
|
||||
|
||||
PlatformID = bytes(self.PlatformID).strip(b'\x00')
|
||||
if PlatformID.isalpha() : # STRING
|
||||
PlatformID = PlatformID.decode('utf-8', 'ignore')
|
||||
else : # GUID
|
||||
PlatformID = '%0.*X' % (0x10 * 2, int.from_bytes(self.PlatformID, 'big'))
|
||||
PlatformID = '{%s-%s-%s-%s-%s}' % (PlatformID[:8], PlatformID[8:12], PlatformID[12:16], PlatformID[16:20], PlatformID[20:])
|
||||
|
||||
print('\n PFAT Block %s Header:\n' % self.count)
|
||||
print(' PFAT Version : %d.%d' % (self.PFATVerMajor, self.PFATVerMinor))
|
||||
print(' Platform ID : %s' % PlatformID)
|
||||
print(' Signed Flash Address Map : %s' % no_yes[f1])
|
||||
print(' Protected EC OpCodes : %s' % no_yes[f2])
|
||||
print(' Graphics Security Disable : %s' % no_yes[f3])
|
||||
print(' Fault Tolerant Update : %s' % no_yes[f4])
|
||||
print(' Attributes Reserved : 0x%X' % f5)
|
||||
print(' Script Version : %d.%d' % (self.ScriptVerMajor, self.ScriptVerMinor))
|
||||
print(' Script Size : 0x%X' % self.ScriptSize)
|
||||
print(' Data Size : 0x%X' % self.DataSize)
|
||||
print(' BIOS SVN : 0x%X' % self.BIOSSVN)
|
||||
print(' EC SVN : 0x%X' % self.ECSVN)
|
||||
print(' Vendor Info : 0x%X' % self.VendorInfo)
|
||||
|
||||
class PFAT_Block_Header_Attributes(ctypes.LittleEndianStructure):
|
||||
_fields_ = [
|
||||
('SFAM', uint32_t, 1), # Signed Flash Address Map
|
||||
('ProtectEC', uint32_t, 1), # Protected EC OpCodes
|
||||
('GFXMitDis', uint32_t, 1), # GFX Security Disable
|
||||
('FTU', uint32_t, 1), # Fault Tolerant Update
|
||||
('Reserved', uint32_t, 28)
|
||||
]
|
||||
|
||||
class PFAT_Block_Header_GetAttributes(ctypes.Union):
|
||||
_fields_ = [
|
||||
('b', PFAT_Block_Header_Attributes),
|
||||
('asbytes', uint32_t)
|
||||
]
|
||||
|
||||
class PFAT_Block_RSA(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Unknown0', uint32_t), # 0x00
|
||||
('Unknown1', uint32_t), # 0x04
|
||||
('PublicKey', uint32_t*64), # 0x08
|
||||
('Exponent', uint32_t), # 0x108
|
||||
('Signature', uint32_t*64), # 0x10C
|
||||
# 0x20C
|
||||
]
|
||||
|
||||
def __init__(self, count, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.count = count
|
||||
|
||||
def pfat_print(self) :
|
||||
PublicKey = '%0.*X' % (0x100 * 2, int.from_bytes(self.PublicKey, 'little'))
|
||||
Signature = '%0.*X' % (0x100 * 2, int.from_bytes(self.Signature, 'little'))
|
||||
|
||||
print('\n PFAT Block %s Signature:\n' % self.count)
|
||||
print(' Unknown 0 : 0x%X' % self.Unknown0)
|
||||
print(' Unknown 1 : 0x%X' % self.Unknown1)
|
||||
print(' Public Key : %s [...]' % PublicKey[:8])
|
||||
print(' Exponent : 0x%X' % self.Exponent)
|
||||
print(' Signature : %s [...]' % Signature[:8])
|
||||
|
||||
# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky
|
||||
def get_struct(buffer, start_offset, class_name, param_list = None) :
|
||||
if param_list is None : param_list = []
|
||||
|
||||
structure = class_name(*param_list) # Unpack parameter list
|
||||
struct_len = ctypes.sizeof(structure)
|
||||
struct_data = buffer[start_offset:start_offset + struct_len]
|
||||
fit_len = min(len(struct_data), struct_len)
|
||||
|
||||
if (start_offset >= len(buffer)) or (fit_len < struct_len) :
|
||||
input('\n Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name.__name__))
|
||||
sys.exit(1)
|
||||
|
||||
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)
|
||||
|
||||
return structure
|
||||
|
||||
if len(sys.argv) >= 2 :
|
||||
# Drag & Drop or CLI
|
||||
ami_pfat = sys.argv[1:]
|
||||
else :
|
||||
# Folder path
|
||||
ami_pfat = []
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
print('\nWorking...')
|
||||
for root, _, files in os.walk(in_path):
|
||||
for name in files :
|
||||
ami_pfat.append(os.path.join(root, name))
|
||||
|
||||
pfat_index = 1
|
||||
input_name = ''
|
||||
input_extension = ''
|
||||
output_path = ''
|
||||
block_hdr_size = ctypes.sizeof(PFAT_Block_Header)
|
||||
block_rsa_size = ctypes.sizeof(PFAT_Block_RSA)
|
||||
pfat_pat = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL)
|
||||
|
||||
for input_file in ami_pfat :
|
||||
file_data = b''
|
||||
final_data = b''
|
||||
block_name = ''
|
||||
block_count = 0
|
||||
file_index = 0
|
||||
blocks = []
|
||||
|
||||
with open(input_file, 'rb') as in_file : buffer = in_file.read()
|
||||
|
||||
pfat_match = pfat_pat.search(buffer)
|
||||
|
||||
if pfat_index == 1 :
|
||||
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))
|
||||
|
||||
if not pfat_match :
|
||||
print('\n Error: This is not an AMI BIOS Guard (PFAT) image!')
|
||||
continue
|
||||
|
||||
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
|
||||
|
||||
if not pfat_match : continue
|
||||
|
||||
buffer = buffer[pfat_match.start() - 0x8:]
|
||||
|
||||
pfat_hdr = get_struct(buffer, 0, PFAT_Header)
|
||||
|
||||
hdr_size = pfat_hdr.Size
|
||||
hdr_data = buffer[0x11:hdr_size].decode('utf-8').splitlines()
|
||||
|
||||
pfat_hdr.pfat_print()
|
||||
print(' Title : %s' % hdr_data[0])
|
||||
|
||||
file_path = os.path.join(output_path, '%s%s -- %d' % (input_name, input_extension, pfat_index))
|
||||
|
||||
for entry in hdr_data[1:] :
|
||||
entry_data = entry.split(' ')
|
||||
entry_data = [s for s in entry_data if s != '']
|
||||
entry_flags = int(entry_data[0])
|
||||
entry_param = entry_data[1]
|
||||
entry_blocks = int(entry_data[2])
|
||||
entry_name = entry_data[3][1:]
|
||||
|
||||
for i in range(entry_blocks) : blocks.append([entry_name, entry_param, entry_flags, i + 1, entry_blocks])
|
||||
|
||||
block_count += entry_blocks
|
||||
|
||||
block_start = hdr_size
|
||||
for i in range(block_count) :
|
||||
is_file_start = blocks[i][0] != block_name
|
||||
|
||||
if is_file_start : print('\n %s (Parameter: %s, Flags: 0x%X)' % (blocks[i][0], blocks[i][1], blocks[i][2]))
|
||||
|
||||
block_hdr = get_struct(buffer, block_start, PFAT_Block_Header, ['%d/%d' % (blocks[i][3], blocks[i][4])])
|
||||
block_hdr.pfat_print()
|
||||
|
||||
block_script_size = block_hdr.ScriptSize
|
||||
block_script_data = buffer[block_start + block_hdr_size:block_start + block_hdr_size + block_script_size]
|
||||
block_data_start = block_start + block_hdr_size + block_script_size
|
||||
block_data_end = block_data_start + block_hdr.DataSize
|
||||
block_data = buffer[block_data_start:block_data_end]
|
||||
|
||||
block_rsa = get_struct(buffer, block_data_end, PFAT_Block_RSA, ['%d/%d' % (blocks[i][3], blocks[i][4])])
|
||||
block_rsa.pfat_print()
|
||||
|
||||
print('\n PFAT Block %d/%d Script:\n' % (blocks[i][3], blocks[i][4]))
|
||||
is_opcode_div = len(block_script_data) % 8 == 0
|
||||
is_begin_end = block_script_data[:8] + block_script_data[-8:] == b'\x01' + b'\x00' * 7 + b'\xFF' + b'\x00' * 7
|
||||
if is_opcode_div and is_begin_end and is_bgst :
|
||||
block_script_decomp = BigScript(code_bytes=block_script_data)
|
||||
block_script_lines = block_script_decomp.to_string().replace('\t',' ').split('\n')
|
||||
for line in block_script_lines :
|
||||
spacing = ' ' * 16 if line.endswith(('begin','end',':')) else ' ' * 24
|
||||
operands = [op for op in line.split(' ') if op != '']
|
||||
print(spacing + ('{:<12s}' + '{:<11s}' * (len(operands) - 1)).format(*operands))
|
||||
elif not is_opcode_div :
|
||||
print(' Error: Script not divisible by OpCode length!')
|
||||
elif not is_begin_end :
|
||||
print(' Error: Script lacks Begin and/or End OpCodes!')
|
||||
elif not is_bgst :
|
||||
print(' Error: BIOS Guard Script Tool dependency missing!')
|
||||
|
||||
file_data += block_data
|
||||
final_data += block_data
|
||||
|
||||
if i and is_file_start and file_data :
|
||||
file_index += 1
|
||||
with open('%s_%0.2d -- %s' % (file_path, file_index, block_name), 'wb') as o : o.write(file_data)
|
||||
file_data = b''
|
||||
|
||||
block_name = blocks[i][0]
|
||||
block_start = block_data_end + block_rsa_size
|
||||
|
||||
with open('%s_%0.2d -- %s' % (file_path, file_index + 1, block_name), 'wb') as o : o.write(file_data) # Last File
|
||||
|
||||
eof_data = buffer[block_start:] # Store any data after the end of PFAT
|
||||
|
||||
with open('%s_00 -- AMI_PFAT_%d_DATA_ALL.bin' % (file_path, pfat_index), 'wb') as final : final.write(final_data + eof_data)
|
||||
|
||||
if eof_data[:-0x100] != b'\xFF' * (len(eof_data) - 0x100) :
|
||||
eof_path = '%s_%0.2d -- AMI_PFAT_%d_DATA_END.bin' % (file_path, file_index + 2, pfat_index)
|
||||
with open(eof_path, 'wb') as final : final.write(eof_data)
|
||||
|
||||
if pfat_pat.search(eof_data) :
|
||||
pfat_index += 1
|
||||
ami_pfat_index = ami_pfat.index(input_file) + 1
|
||||
ami_pfat.insert(ami_pfat_index, eof_path)
|
||||
else :
|
||||
pfat_index = 1
|
||||
|
||||
input('\nDone!')
|
|
@ -1,610 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
AMI UCP Extract
|
||||
AMI UCP BIOS Extractor
|
||||
Copyright (C) 2021 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
title = 'AMI UCP BIOS Extractor v1.2'
|
||||
|
||||
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 re
|
||||
import shutil
|
||||
import ctypes
|
||||
import argparse
|
||||
import traceback
|
||||
import subprocess
|
||||
import contextlib
|
||||
|
||||
# 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
|
||||
ucp_parser = argparse.ArgumentParser()
|
||||
ucp_parser.add_argument('executables', type=argparse.FileType('r'), nargs='*')
|
||||
ucp_parser.add_argument('-p', '--path', help='parse files within given folder', type=str)
|
||||
ucp_parser.add_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true')
|
||||
ucp_params = ucp_parser.parse_args()
|
||||
|
||||
verify_chk16 = bool(ucp_params.checksum) # Get Checksum16 Verification optional argument
|
||||
|
||||
# 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(ucp_params.path) :
|
||||
ucp_exec = get_files(ucp_params.path) # CLI with --path
|
||||
else :
|
||||
ucp_exec = []
|
||||
for executable in ucp_params.executables :
|
||||
ucp_exec.append(executable.name) # Drag & Drop
|
||||
else :
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
ucp_exec = get_files(in_path) # Direct Run
|
||||
|
||||
# Set ctypes Structure types
|
||||
char = ctypes.c_char
|
||||
uint8_t = ctypes.c_ubyte
|
||||
uint16_t = ctypes.c_ushort
|
||||
uint32_t = ctypes.c_uint
|
||||
uint64_t = ctypes.c_uint64
|
||||
|
||||
class UAF_HDR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('ModuleTag', char*4), # 0x00
|
||||
('ModuleSize', uint32_t), # 0x04
|
||||
('Checksum', uint16_t), # 0x08
|
||||
('Unknown0', uint8_t), # 0x0A
|
||||
('Unknown1', uint8_t), # 0x0A
|
||||
('Reserved', uint32_t), # 0x0C
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def __init__(self, padd, *args, **kwargs) :
|
||||
super().__init__(*args, **kwargs)
|
||||
self.p = padd
|
||||
|
||||
def ucp_print(self, chk16) :
|
||||
print('\n%s Utility Auxiliary File:\n' % self.p)
|
||||
print('%s Module Tag : %s' % (self.p, self.ModuleTag.decode('utf-8')))
|
||||
print('%s Module Size : 0x%X' % (self.p, self.ModuleSize))
|
||||
print('%s Checksum : 0x%0.4X (%s)' % (self.p, self.Checksum, chk16))
|
||||
print('%s Unknown 0 : 0x%0.2X' % (self.p, self.Unknown0))
|
||||
print('%s Unknown 1 : 0x%0.2X' % (self.p, self.Unknown1))
|
||||
print('%s Reserved : 0x%0.8X' % (self.p, self.Reserved))
|
||||
|
||||
class UAF_MOD(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('CompressSize', uint32_t), # 0x00
|
||||
('OriginalSize', uint32_t), # 0x04
|
||||
# 0x08
|
||||
]
|
||||
|
||||
def __init__(self, padd, *args, **kwargs) :
|
||||
super().__init__(*args, **kwargs)
|
||||
self.p = padd
|
||||
|
||||
def ucp_print(self) :
|
||||
print('%s Compress Size : 0x%X' % (self.p, self.CompressSize))
|
||||
print('%s Original Size : 0x%X' % (self.p, self.OriginalSize))
|
||||
|
||||
class UII_HDR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('UIISize', uint16_t), # 0x00
|
||||
('Checksum', uint16_t), # 0x02
|
||||
('UtilityVersion', uint32_t), # 0x04 i.e. AFU (Unknown Encoding, Signed)
|
||||
('InfoSize', uint16_t), # 0x08
|
||||
('SupportBIOS', uint8_t), # 0x0A
|
||||
('SupportOS', uint8_t), # 0x0B
|
||||
('DataBusWidth', uint8_t), # 0x0C
|
||||
('ProgramType', uint8_t), # 0x0D
|
||||
('ProgramMode', uint8_t), # 0x0E
|
||||
('SourceSafeRelease', uint8_t), # 0x0F
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def __init__(self, padd, *args, **kwargs) :
|
||||
super().__init__(*args, **kwargs)
|
||||
self.p = padd
|
||||
|
||||
def ucp_print(self, chk16) :
|
||||
sbios = {1: 'ALL', 2: 'AMIBIOS8', 3: 'UEFI', 4: 'AMIBIOS8/UEFI'}
|
||||
sos = {1: 'DOS', 2: 'EFI', 3: 'Windows', 4: 'Linux', 5: 'FreeBSD', 6: 'MacOS', 128: 'Multi-Platform'}
|
||||
dbwidth = {1: '16b', 2: '16/32b', 3: '32b', 4: '64b'}
|
||||
ptype = {1: 'Executable', 2: 'Library', 3: 'Driver'}
|
||||
pmode = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'}
|
||||
|
||||
SupportBIOS = sbios[self.SupportBIOS] if self.SupportBIOS in sbios else 'Unknown (%d)' % self.SupportBIOS
|
||||
SupportOS = sos[self.SupportOS] if self.SupportOS in sos else 'Unknown (%d)' % self.SupportOS
|
||||
DataBusWidth = dbwidth[self.DataBusWidth] if self.DataBusWidth in dbwidth else 'Unknown (%d)' % self.DataBusWidth
|
||||
ProgramType = ptype[self.ProgramType] if self.ProgramType in ptype else 'Unknown (%d)' % self.ProgramType
|
||||
ProgramMode = pmode[self.ProgramMode] if self.ProgramMode in pmode else 'Unknown (%d)' % self.ProgramMode
|
||||
|
||||
print('\n%s Utility Identification Information:\n' % self.p)
|
||||
print('%s UII Size : 0x%X' % (self.p, self.UIISize))
|
||||
print('%s Checksum : 0x%0.4X (%s)' % (self.p, self.Checksum, chk16))
|
||||
print('%s Tool Version : 0x%0.8X (Unknown)' % (self.p, self.UtilityVersion))
|
||||
print('%s Info Size : 0x%X' % (self.p, self.InfoSize))
|
||||
print('%s Supported BIOS : %s' % (self.p, SupportBIOS))
|
||||
print('%s Supported OS : %s' % (self.p, SupportOS))
|
||||
print('%s Data Bus Width : %s' % (self.p, DataBusWidth))
|
||||
print('%s Program Type : %s' % (self.p, ProgramType))
|
||||
print('%s Program Mode : %s' % (self.p, ProgramMode))
|
||||
print('%s SourceSafe Tag : %0.2d' % (self.p, self.SourceSafeRelease))
|
||||
|
||||
class DIS_HDR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('PasswordSize', uint16_t), # 0x00
|
||||
('EntryCount', uint16_t), # 0x02
|
||||
('Password', char*12), # 0x04
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def __init__(self, padd, *args, **kwargs) :
|
||||
super().__init__(*args, **kwargs)
|
||||
self.p = padd
|
||||
|
||||
def ucp_print(self) :
|
||||
print('\n%s Default Command Status Header:\n' % self.p)
|
||||
print('%s Password Size : 0x%X' % (self.p, self.PasswordSize))
|
||||
print('%s Entry Count : %d' % (self.p, self.EntryCount))
|
||||
print('%s Password : %s' % (self.p, self.Password.decode('utf-8')))
|
||||
|
||||
class DIS_MOD(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('EnabledDisabled', uint8_t), # 0x00
|
||||
('ShownHidden', uint8_t), # 0x01
|
||||
('Command', char*32), # 0x02
|
||||
('Description', char*256), # 0x22
|
||||
# 0x122
|
||||
]
|
||||
|
||||
def __init__(self, padd, *args, **kwargs) :
|
||||
super().__init__(*args, **kwargs)
|
||||
self.p = padd
|
||||
|
||||
def ucp_print(self) :
|
||||
enabled = {0: 'Disabled', 1: 'Enabled'}
|
||||
shown = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'}
|
||||
|
||||
EnabledDisabled = enabled[self.EnabledDisabled] if self.EnabledDisabled in enabled else 'Unknown (%d)' % self.EnabledDisabled
|
||||
ShownHidden = shown[self.ShownHidden] if self.ShownHidden in shown else 'Unknown (%d)' % self.ShownHidden
|
||||
|
||||
print('\n%s Default Command Status Entry:\n' % self.p)
|
||||
print('%s State : %s' % (self.p, EnabledDisabled))
|
||||
print('%s Display : %s' % (self.p, ShownHidden))
|
||||
print('%s Command : %s' % (self.p, self.Command.decode('utf-8').strip()))
|
||||
print('%s Description : %s' % (self.p, self.Description.decode('utf-8').strip()))
|
||||
|
||||
# Process ctypes Structure Classes
|
||||
# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky
|
||||
def get_struct(buffer, start_offset, class_name, param_list = None) :
|
||||
if param_list is None : param_list = []
|
||||
|
||||
structure = class_name(*param_list) # Unpack parameter list
|
||||
struct_len = ctypes.sizeof(structure)
|
||||
struct_data = buffer[start_offset:start_offset + struct_len]
|
||||
fit_len = min(len(struct_data), struct_len)
|
||||
|
||||
if (start_offset >= len(buffer)) or (fit_len < struct_len) :
|
||||
print('\n Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name.__name__))
|
||||
|
||||
input('\n Press enter to exit')
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)
|
||||
|
||||
return structure
|
||||
|
||||
# Get Checksum16 validity result
|
||||
def checksum16(buffer, check) :
|
||||
if not check : return 'Skipped'
|
||||
|
||||
chk16 = 0
|
||||
|
||||
for idx in range(0, len(buffer), 2) :
|
||||
chk16 += int.from_bytes(buffer[idx:idx + 2], 'little')
|
||||
|
||||
chk16 &= 0xFFFF
|
||||
|
||||
return 'Good' if chk16 == 0 else 'Bad'
|
||||
|
||||
# Get all input file AMI UCP patterns
|
||||
def get_matches(buffer) :
|
||||
uaf_len_max = 0 # Length of largest detected @UAF
|
||||
uaf_hdr_off = 0 # Offset of largest detected @UAF
|
||||
|
||||
for uaf in ami_ucp_pat.finditer(buffer) :
|
||||
uaf_len_cur = int.from_bytes(buffer[uaf.start() + 0x4:uaf.start() + 0x8], 'little')
|
||||
|
||||
if uaf_len_cur > uaf_len_max :
|
||||
uaf_len_max = uaf_len_cur
|
||||
uaf_hdr_off = uaf.start()
|
||||
|
||||
return uaf_hdr_off, uaf_len_max
|
||||
|
||||
# Parse & Extract AMI UCP structures
|
||||
def ucp_extract(buffer, out_dir, level, padd) :
|
||||
nal_dict = {} # Initialize @NAL Dictionary per UCP
|
||||
|
||||
uaf_hdr = get_struct(buffer, 0, UAF_HDR, [padd]) # Parse @UAF Header Structure
|
||||
|
||||
uaf_chk = checksum16(buffer, verify_chk16) # Get @UAF Header Checksum16
|
||||
|
||||
# Print @UAF Header Info
|
||||
uaf_hdr.ucp_print(uaf_chk)
|
||||
print('%s Compress Size : 0x%X' % (padd, len(buffer)))
|
||||
print('%s Original Size : 0x%X' % (padd, len(buffer)))
|
||||
print('%s Module Name : %s' % (padd, tag_dict['UAF']))
|
||||
|
||||
if uaf_chk == 'Bad' :
|
||||
input('\n%s Error: Invalid AMI UCP Module UAF Checksum!' % padd)
|
||||
|
||||
uaf_off = uaf_hdr_len # Parsed @UAF, next Modules
|
||||
uaf_all = [] # Initialize list of all UAF Modules
|
||||
is_pfat = False # Initialize PFAT BIOS detection
|
||||
is_dual = False # Initialize AMI/Insyde detection
|
||||
|
||||
while buffer[uaf_off] == 0x40 : # ASCII of @ is 0x40
|
||||
uaf_hdr = get_struct(buffer, uaf_off, UAF_HDR, [padd]) # Parse UAF Module Structure
|
||||
|
||||
uaf_tag = uaf_hdr.ModuleTag.decode('utf-8')[1:] # Get unique UAF Module Tag
|
||||
|
||||
if uaf_tag == 'PFC' : is_pfat = True # Detect if UAF Module has PFAT BIOS
|
||||
|
||||
if uaf_tag == 'AMI' : is_dual = True # Detect if UAF Module has dual AMI/Insyde BIOS
|
||||
|
||||
uaf_all.append([uaf_tag, uaf_off, uaf_hdr]) # Store UAF Module Info
|
||||
|
||||
uaf_off += uaf_hdr.ModuleSize # Adjust to next UAF Module offset
|
||||
|
||||
if uaf_off >= len(buffer) : break # Stop parsing at EOF
|
||||
|
||||
# Check if UAF Module NAL exists and place it first
|
||||
# Parsing NAL first allows naming all UAF Modules
|
||||
for i in range(len(uaf_all)) :
|
||||
if uaf_all[i][0] == 'NAL' :
|
||||
uaf_all.insert(1, uaf_all.pop(i)) # After UII for visual purposes
|
||||
break # NAL found, skip the rest
|
||||
|
||||
# Parse all UAF Modules
|
||||
for uaf in uaf_all :
|
||||
uaf_tag = uaf[0] # Store UAF Module Tag
|
||||
uaf_off = uaf[1] # Store UAF Module Offset
|
||||
uaf_hdr = uaf[2] # Store UAF Module Struct
|
||||
|
||||
uaf_data_all = buffer[uaf_off:uaf_off + uaf_hdr.ModuleSize] # UAF Module Entire Data
|
||||
|
||||
uaf_data_mod = uaf_data_all[uaf_hdr_len:] # UAF Module EFI Data
|
||||
|
||||
uaf_data_raw = uaf_data_mod[uaf_mod_len:] # UAF Module Raw Data
|
||||
|
||||
uaf_chk = checksum16(uaf_data_all, verify_chk16) # Get UAF Module Checksum16
|
||||
|
||||
uaf_hdr.ucp_print(uaf_chk) # Print UAF Module Info
|
||||
|
||||
uaf_mod = get_struct(buffer, uaf_off + uaf_hdr_len, UAF_MOD, [padd]) # Parse UAF Module EFI Structure
|
||||
|
||||
uaf_mod.ucp_print() # Print UAF Module EFI Info
|
||||
|
||||
is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect UAF Module EFI Compression
|
||||
|
||||
rom_name = 'PFAT' if is_pfat else 'BIOS' # Set UAF Module BIOS/ROM name based on PFAT state
|
||||
|
||||
if uaf_tag in nal_dict : uaf_name = nal_dict[uaf_tag] # Always prefer NAL naming first
|
||||
elif uaf_tag in tag_dict : uaf_name = tag_dict[uaf_tag] # Otherwise use built-in naming
|
||||
elif uaf_tag == 'ROM' : uaf_name = '%s.bin' % rom_name # BIOS/PFAT Firmware
|
||||
elif uaf_tag.startswith('R0') : uaf_name = '%s_0%s.bin' % (rom_name, uaf_tag[2:]) # BIOS/PFAT Firmware
|
||||
elif uaf_tag.startswith('S0') : uaf_name = '%s_0%s.sig' % (rom_name, uaf_tag[2:]) # BIOS/PFAT Signature
|
||||
elif uaf_tag.startswith('DR') : uaf_name = 'DROM_0%s.bin' % uaf_tag[2:] # Thunderbolt Retimer Firmware
|
||||
elif uaf_tag.startswith('DS') : uaf_name = 'DROM_0%s.sig' % uaf_tag[2:] # Thunderbolt Retimer Signature
|
||||
elif uaf_tag.startswith('EC') : uaf_name = 'EC_0%s.bin' % uaf_tag[2:] # Embedded Controller Firmware
|
||||
elif uaf_tag.startswith('ME') : uaf_name = 'ME_0%s.bin' % uaf_tag[2:] # Management Engine Firmware
|
||||
else : uaf_name = uaf_tag # Could not name the UAF Module, use Tag instead
|
||||
|
||||
if uaf_name != uaf_tag :
|
||||
uaf_fext = '' # File extension included in name
|
||||
print('%s Module Name : %s' % (padd, uaf_name))
|
||||
elif uaf_tag in ['CMD','PFC','VER','MEC','NAL','CKV'] :
|
||||
uaf_fext = '.txt' # Known Text files
|
||||
print('%s Module Name : %s%s (Unknown)' % (padd, uaf_name, uaf_fext))
|
||||
else :
|
||||
uaf_fext = '.bin' # Unknown files, assume binary
|
||||
print('%s Module Name : %s%s (Unknown)' % (padd, uaf_name, uaf_fext))
|
||||
|
||||
# Check if unknown UAF Module Tag is present in NAL but not in built-in dictionary
|
||||
if uaf_tag in nal_dict and uaf_tag not in tag_dict and not uaf_tag.startswith(('ROM','R0','S0','DR','DS')) :
|
||||
input('\n%s Note: Detected new AMI UCP Module %s (%s) in NAL!' % (padd, uaf_tag, nal_dict[uaf_tag]))
|
||||
|
||||
# Generate UAF Module File name, depending on whether decompression will be required
|
||||
uaf_fname = os.path.join(out_dir, '%s%s' % (uaf_name, '.temp' if is_comp else uaf_fext))
|
||||
|
||||
if uaf_chk == 'Bad' :
|
||||
input('\n%s Error: Invalid AMI UCP Module %s Checksum!' % (padd, uaf_tag))
|
||||
|
||||
# Parse Utility Identification Information UAF Module (UII)
|
||||
if uaf_tag == 'UII' :
|
||||
info_hdr = get_struct(uaf_data_raw, 0, UII_HDR, [padd]) # Parse UII Module Raw Structure
|
||||
|
||||
info_chk = checksum16(uaf_data_raw, verify_chk16) # Get UII Module Checksum16
|
||||
|
||||
info_hdr.ucp_print(info_chk) # Print UII Module Info
|
||||
|
||||
# Get UII Module Description text field
|
||||
desc = uaf_data_raw[info_hdr.InfoSize:info_hdr.UIISize].strip(b'\x00').decode('utf-8')
|
||||
|
||||
print('%s Description : %s' % (padd, desc)) # Print UII Module Description
|
||||
|
||||
if info_chk == 'Bad' :
|
||||
input('\n%s Error: Invalid AMI UCP Module %s > Info Checksum!' % (padd, uaf_tag))
|
||||
|
||||
# Store/Save UII Module Info in file
|
||||
with open(uaf_fname[:-3] + 'txt', 'a') as uii :
|
||||
with contextlib.redirect_stdout(uii) :
|
||||
info_hdr.ucp_print(info_chk) # Store UII Module Info
|
||||
|
||||
print('%s Description : %s' % (padd, desc)) # Store UII Module Description
|
||||
|
||||
# Adjust UAF Module Raw Data for extraction
|
||||
if is_comp :
|
||||
# Some Compressed UAF Module EFI data lack necessary padding in the end
|
||||
if uaf_mod.CompressSize > len(uaf_data_raw) :
|
||||
comp_padd = b'\x00' * (uaf_mod.CompressSize - len(uaf_data_raw))
|
||||
uaf_data_raw = uaf_data_mod[:uaf_mod_len] + uaf_data_raw + comp_padd # Add missing padding for decompression
|
||||
else :
|
||||
uaf_data_raw = uaf_data_mod[:uaf_mod_len] + uaf_data_raw # Add the EFI/Tiano Compression info before Raw Data
|
||||
else :
|
||||
uaf_data_raw = uaf_data_raw[:uaf_mod.OriginalSize] # No compression, extend to end of Original UAF Module size
|
||||
|
||||
# Store/Save UAF Module file
|
||||
if uaf_tag != 'UII' : # Skip UII binary, already parsed
|
||||
with open(uaf_fname, 'wb') as out : out.write(uaf_data_raw)
|
||||
|
||||
# UAF Module EFI/Tiano Decompression
|
||||
if is_comp :
|
||||
try :
|
||||
dec_fname = uaf_fname[:-5] + uaf_fext # Decompressed UAF Module file path
|
||||
subprocess.run(['TianoCompress', '-d', uaf_fname, '-o', dec_fname, '--uefi', '-q'], check = True, stdout = subprocess.DEVNULL)
|
||||
|
||||
with open(dec_fname, 'rb') as dec : uaf_data_raw = dec.read() # Read back the UAF Module decompressed Raw data
|
||||
|
||||
if len(uaf_data_raw) == 0 : raise Exception('DECOMP_OUT_EMPTY') # If decompressed file is empty, something went wrong
|
||||
|
||||
os.remove(uaf_fname) # Successful decompression, delete compressed UAF Module file
|
||||
|
||||
uaf_fname = dec_fname # Adjust UAF Module file path to the decompressed one
|
||||
except :
|
||||
print('\n%s Error: Could not extract AMI UCP Module %s via TianoCompress!' % (padd, uaf_tag))
|
||||
input('%s Make sure that "TianoCompress" executable exists!' % padd)
|
||||
|
||||
# Process and Print known text only UAF Modules (after EFI/Tiano Decompression)
|
||||
if uaf_tag in ['CMD','PFC','VER','MEC','CKV'] : # Always referenced in tag_desc
|
||||
text_data = uaf_data_raw.decode('utf-8')
|
||||
print('\n%s %s:\n\n%s %s' % (padd, tag_desc[uaf_tag], padd, text_data))
|
||||
|
||||
# Parse Default Command Status UAF Module (DIS)
|
||||
if len(uaf_data_raw) and uaf_tag == 'DIS' :
|
||||
dis_hdr = get_struct(uaf_data_raw, 0, DIS_HDR, [padd]) # Parse DIS Module Raw Header Structure
|
||||
dis_hdr.ucp_print() # Print DIS Module Raw Header Info
|
||||
|
||||
# Store/Save DIS Module Header Info in file
|
||||
with open(uaf_fname[:-3] + 'txt', 'a') as dis :
|
||||
with contextlib.redirect_stdout(dis) :
|
||||
dis_hdr.ucp_print() # Store DIS Module Header Info
|
||||
|
||||
dis_data = uaf_data_raw[uaf_hdr_len:] # DIS Module Entries Data
|
||||
|
||||
# Parse all DIS Module Entries
|
||||
for e_idx in range(dis_hdr.EntryCount) :
|
||||
dis_mod = get_struct(dis_data, e_idx * 0x122, DIS_MOD, [padd]) # Parse DIS Module Raw Entry Structure
|
||||
dis_mod.ucp_print() # Print DIS Module Raw Entry Info
|
||||
|
||||
# Store/Save DIS Module Entry Info in file
|
||||
with open(uaf_fname[:-3] + 'txt', 'a') as dis :
|
||||
with contextlib.redirect_stdout(dis) :
|
||||
dis_mod.ucp_print() # Store DIS Module Entry Info
|
||||
|
||||
os.remove(uaf_fname) # Delete DIS Module binary, info exported as text
|
||||
|
||||
# Parse Non-AMI List (?) UAF Module (NAL)
|
||||
if len(uaf_data_raw) >= 5 and (uaf_tag,uaf_data_raw[0],uaf_data_raw[4]) == ('NAL',0x40,0x3A) :
|
||||
nal_info = uaf_data_raw.decode('utf-8').strip().replace('\r','').split('\n')
|
||||
|
||||
print('\n%s UAF List:\n' % padd)
|
||||
|
||||
# Parse all NAL Module Entries
|
||||
for info in nal_info :
|
||||
print('%s %s : %s' % (padd, info[1:4], info[5:])) # Print NAL Module Tag-Path Info
|
||||
nal_dict[info[1:4]] = os.path.basename(info[5:]) # Assign a file name (w/o path) to each Tag
|
||||
|
||||
# Parse Insyde BIOS UAF Module (INS)
|
||||
if len(uaf_data_raw) >= 2 and (uaf_tag,is_dual,uaf_data_raw[:2]) == ('INS',True,b'\x4D\x5A') :
|
||||
ins_dir = os.path.join(out_dir, '%s_extracted (SFX)' % uaf_tag) # Generate extraction directory
|
||||
|
||||
print('\n%s Insyde BIOS 7-Zip SFX Archive:\n\n%s 7-Zip will be used for extraction' % (padd, padd))
|
||||
|
||||
# INS Module extraction
|
||||
try :
|
||||
subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + ins_dir, uaf_fname], check = True, stdout = subprocess.DEVNULL)
|
||||
|
||||
if not os.path.isdir(ins_dir) : raise Exception('EXTR_DIR_MISSING') # If extraction folder is missing, something went wrong
|
||||
|
||||
os.remove(uaf_fname) # Successful extraction, delete archived INS Module file
|
||||
except :
|
||||
print('\n%s Error: Could not extract AMI UCP Module %s via 7-Zip!' % (padd, uaf_tag))
|
||||
input('%s Make sure that "7z" executable exists!' % padd)
|
||||
|
||||
# Detect AMI BIOS Guard (PFAT) image and print extraction instructions/utility
|
||||
if len(uaf_data_raw) >= 16 and (is_pfat,uaf_data_raw[0x8:0x10]) == (True,b'_AMIPFAT') :
|
||||
print('\n%s AMI BIOS Guard (PFAT) Image:\n' % padd)
|
||||
print('%s Use "AMI BIOS Guard Extractor" from https://github.com/platomav/BIOSUtilities' % padd)
|
||||
|
||||
# Detect Intel Management Engine (ME) image and print parsing instructions/utility
|
||||
if len(uaf_data_raw) and uaf_name.startswith('ME_0') :
|
||||
print('\n%s Intel Management Engine (ME) Firmware:\n' % padd)
|
||||
print('%s Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer' % padd)
|
||||
|
||||
# Get best Nested AMI UCP Pattern match based on @UAF Size
|
||||
uaf_hdr_off,uaf_len_max = get_matches(uaf_data_raw)
|
||||
|
||||
# Parse Nested AMI UCP Structure
|
||||
if uaf_hdr_off :
|
||||
level += 1 # Increase structure Level to control output padding
|
||||
uaf_dir = os.path.join(out_dir, '%s_extracted (UCP)' % uaf_tag) # Generate extraction directory
|
||||
os.mkdir(uaf_dir) # Create extraction directory
|
||||
ucp_extract(uaf_data_raw[uaf_hdr_off:uaf_hdr_off + uaf_len_max], uaf_dir, level, ' ' * level) # Call recursively
|
||||
os.remove(uaf_fname) # Delete raw nested AMI UCP Structure after successful recursion/extraction
|
||||
|
||||
# Utility Auxiliary File (@UAF) and Utility Identification Information (@UII)
|
||||
ami_ucp_pat = re.compile(br'\x40\x55\x41\x46.{12}\x40\x55\x49\x49', re.DOTALL)
|
||||
|
||||
# Get common ctypes Structure Sizes
|
||||
uaf_hdr_len = ctypes.sizeof(UAF_HDR)
|
||||
uaf_mod_len = ctypes.sizeof(UAF_MOD)
|
||||
|
||||
# User friendly Tag Descriptions
|
||||
tag_desc = {
|
||||
'CMD' : 'AMI AFU Command',
|
||||
'PFC' : 'AMI BGT Command',
|
||||
'VER' : 'OEM Version',
|
||||
'CKV' : 'Check Version',
|
||||
'MEC' : 'ME FWUpdLcl Command',
|
||||
}
|
||||
|
||||
# AMI UCP Tag-File Dictionary
|
||||
tag_dict = {
|
||||
'W32' : 'amifldrv32.sys',
|
||||
'W64' : 'amifldrv64.sys',
|
||||
'VXD' : 'amifldrv.vxd',
|
||||
'DCT' : 'DevCon32.exe',
|
||||
'DCX' : 'DevCon64.exe',
|
||||
'CMD' : 'AFU_Command.txt',
|
||||
'PFC' : 'BGT_Command.txt',
|
||||
'VER' : 'OEM_Version.txt',
|
||||
'CKV' : 'Check_Version.txt',
|
||||
'OKM' : 'OK_Message.txt',
|
||||
'CPM' : 'AC_Message.txt',
|
||||
'DIS' : 'Command_Status.bin',
|
||||
'UAF' : 'UCP_Main.bin',
|
||||
'UII' : 'UCP_Info.txt',
|
||||
'NAL' : 'UAF_List.txt',
|
||||
'MEC' : 'FWUpdLcl.txt',
|
||||
'MED' : 'FWUpdLcl_DOS.exe',
|
||||
'MET' : 'FWUpdLcl_WIN.exe',
|
||||
'AMI' : 'UCP_Nested.bin',
|
||||
'INS' : 'Insyde_Nested.bin',
|
||||
'RFI' : 'CryptRSA.efi',
|
||||
'R3I' : 'CryptRSA32.efi',
|
||||
'UFI' : 'HpBiosUpdate.efi',
|
||||
'US9' : 'HpBiosUpdate.s09',
|
||||
'US2' : 'HpBiosUpdate.s12',
|
||||
'USG' : 'HpBiosUpdate.sig',
|
||||
'3FI' : 'HpBiosUpdate32.efi',
|
||||
'3S9' : 'HpBiosUpdate32.s09',
|
||||
'3S2' : 'HpBiosUpdate32.s12',
|
||||
'3SG' : 'HpBiosUpdate32.sig',
|
||||
'MFI' : 'HpBiosMgmt.efi',
|
||||
'MS9' : 'HpBiosMgmt.s09',
|
||||
'MS2' : 'HpBiosMgmt.s12',
|
||||
'US4' : 'HpBiosUpdate.s14',
|
||||
'3S4' : 'HpBiosUpdate32.s14',
|
||||
'MS4' : 'HpBiosMgmt.s14',
|
||||
'M3I' : 'HpBiosMgmt32.efi',
|
||||
'M39' : 'HpBiosMgmt32.s09',
|
||||
'M32' : 'HpBiosMgmt32.s12',
|
||||
'M34' : 'HpBiosMgmt32.s14',
|
||||
'BME' : 'BiosMgmt.efi',
|
||||
'BM9' : 'BiosMgmt.s09',
|
||||
'B12' : 'BiosMgmt.s12',
|
||||
'B14' : 'BiosMgmt.s14',
|
||||
'B3E' : 'BiosMgmt32.efi',
|
||||
'B39' : 'BiosMgmt32.s09',
|
||||
'B32' : 'BiosMgmt32.s12',
|
||||
'B34' : 'BiosMgmt32.s14',
|
||||
'DFE' : 'HpDevFwUpdate.efi',
|
||||
'DFS' : 'HpDevFwUpdate.s12',
|
||||
'ENB' : 'ENBG64.exe',
|
||||
}
|
||||
|
||||
# Process each input AMI UCP BIOS executable
|
||||
for input_file in ucp_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()
|
||||
|
||||
# Get best AMI UCP Pattern match based on @UAF Size
|
||||
uaf_hdr_off,uaf_len_max = get_matches(input_data)
|
||||
|
||||
# Check if AMI UCP Pattern was found on executable
|
||||
if not uaf_hdr_off :
|
||||
print('\n Error: This is not an AMI UCP BIOS executable!')
|
||||
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
|
||||
|
||||
print('\n AMI Utility Configuration Program')
|
||||
|
||||
level = 0 # Set initial AMI UCP structure Level to control padding in nested ones
|
||||
|
||||
ucp_extract(input_data[uaf_hdr_off:uaf_hdr_off + uaf_len_max], output_path, level, '') # Call the AMI UCP Extractor function
|
||||
|
||||
print('\n Extracted AMI UCP BIOS executable!')
|
||||
|
||||
input('\nDone!')
|
||||
|
||||
sys.exit(0)
|
319
AMI_PFAT_Extract.py
Normal file
319
AMI_PFAT_Extract.py
Normal file
|
@ -0,0 +1,319 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
AMI PFAT Extract
|
||||
AMI BIOS Guard Extractor
|
||||
Copyright (C) 2018-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'AMI BIOS Guard Extractor v4.0_a12'
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import ctypes
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.externals import get_bgs_tool
|
||||
from common.num_ops import get_ordinal
|
||||
from common.path_ops import make_dirs, safe_name, get_extract_path, extract_suffix
|
||||
from common.patterns import PAT_AMI_PFAT
|
||||
from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
class AmiBiosGuardHeader(ctypes.LittleEndianStructure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Size', uint32_t), # 0x00 Header + Entries
|
||||
('Checksum', uint32_t), # 0x04 ?
|
||||
('Tag', char*8), # 0x04 _AMIPFAT
|
||||
('Flags', uint8_t), # 0x10 ?
|
||||
# 0x11
|
||||
]
|
||||
|
||||
def struct_print(self, p):
|
||||
printer(['Size :', f'0x{self.Size:X}'], p, False)
|
||||
printer(['Checksum:', f'0x{self.Checksum:04X}'], p, False)
|
||||
printer(['Tag :', self.Tag.decode('utf-8')], p, False)
|
||||
printer(['Flags :', f'0x{self.Flags:02X}'], p, False)
|
||||
|
||||
class IntelBiosGuardHeader(ctypes.LittleEndianStructure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('BGVerMajor', uint16_t), # 0x00
|
||||
('BGVerMinor', uint16_t), # 0x02
|
||||
('PlatformID', uint8_t*16), # 0x04
|
||||
('Attributes', uint32_t), # 0x14
|
||||
('ScriptVerMajor', uint16_t), # 0x16
|
||||
('ScriptVerMinor', uint16_t), # 0x18
|
||||
('ScriptSize', uint32_t), # 0x1C
|
||||
('DataSize', uint32_t), # 0x20
|
||||
('BIOSSVN', uint32_t), # 0x24
|
||||
('ECSVN', uint32_t), # 0x28
|
||||
('VendorInfo', uint32_t), # 0x2C
|
||||
# 0x30
|
||||
]
|
||||
|
||||
def get_platform_id(self):
|
||||
id_byte = bytes(self.PlatformID)
|
||||
|
||||
id_text = re.sub(r'[\n\t\r\x00 ]', '', id_byte.decode('utf-8','ignore'))
|
||||
|
||||
id_hexs = f'{int.from_bytes(id_byte, "big"):0{0x10 * 2}X}'
|
||||
id_guid = f'{{{id_hexs[:8]}-{id_hexs[8:12]}-{id_hexs[12:16]}-{id_hexs[16:20]}-{id_hexs[20:]}}}'
|
||||
|
||||
return f'{id_text} {id_guid}'
|
||||
|
||||
def get_flags(self):
|
||||
attr = IntelBiosGuardHeaderGetAttributes()
|
||||
attr.asbytes = self.Attributes
|
||||
|
||||
return attr.b.SFAM, attr.b.ProtectEC, attr.b.GFXMitDis, attr.b.FTU, attr.b.Reserved
|
||||
|
||||
def struct_print(self, p):
|
||||
no_yes = ['No','Yes']
|
||||
f1,f2,f3,f4,f5 = self.get_flags()
|
||||
|
||||
printer(['BIOS Guard Version :', f'{self.BGVerMajor}.{self.BGVerMinor}'], p, False)
|
||||
printer(['Platform Identity :', self.get_platform_id()], p, False)
|
||||
printer(['Signed Flash Address Map :', no_yes[f1]], p, False)
|
||||
printer(['Protected EC OpCodes :', no_yes[f2]], p, False)
|
||||
printer(['Graphics Security Disable :', no_yes[f3]], p, False)
|
||||
printer(['Fault Tolerant Update :', no_yes[f4]], p, False)
|
||||
printer(['Attributes Reserved :', f'0x{f5:X}'], p, False)
|
||||
printer(['Script Version :', f'{self.ScriptVerMajor}.{self.ScriptVerMinor}'], p, False)
|
||||
printer(['Script Size :', f'0x{self.ScriptSize:X}'], p, False)
|
||||
printer(['Data Size :', f'0x{self.DataSize:X}'], p, False)
|
||||
printer(['BIOS Security Version Number:', f'0x{self.BIOSSVN:X}'], p, False)
|
||||
printer(['EC Security Version Number :', f'0x{self.ECSVN:X}'], p, False)
|
||||
printer(['Vendor Information :', f'0x{self.VendorInfo:X}'], p, False)
|
||||
|
||||
class IntelBiosGuardHeaderAttributes(ctypes.LittleEndianStructure):
|
||||
_fields_ = [
|
||||
('SFAM', uint32_t, 1), # Signed Flash Address Map
|
||||
('ProtectEC', uint32_t, 1), # Protected EC OpCodes
|
||||
('GFXMitDis', uint32_t, 1), # GFX Security Disable
|
||||
('FTU', uint32_t, 1), # Fault Tolerant Update
|
||||
('Reserved', uint32_t, 28) # Reserved/Unknown
|
||||
]
|
||||
|
||||
class IntelBiosGuardHeaderGetAttributes(ctypes.Union):
|
||||
_fields_ = [
|
||||
('b', IntelBiosGuardHeaderAttributes),
|
||||
('asbytes', uint32_t)
|
||||
]
|
||||
|
||||
class IntelBiosGuardSignature2k(ctypes.LittleEndianStructure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Unknown0', uint32_t), # 0x000
|
||||
('Unknown1', uint32_t), # 0x004
|
||||
('Modulus', uint32_t*64), # 0x008
|
||||
('Exponent', uint32_t), # 0x108
|
||||
('Signature', uint32_t*64), # 0x10C
|
||||
# 0x20C
|
||||
]
|
||||
|
||||
def struct_print(self, p):
|
||||
Modulus = f'{int.from_bytes(self.Modulus, "little"):0{0x100 * 2}X}'
|
||||
Signature = f'{int.from_bytes(self.Signature, "little"):0{0x100 * 2}X}'
|
||||
|
||||
printer(['Unknown 0:', f'0x{self.Unknown0:X}'], p, False)
|
||||
printer(['Unknown 1:', f'0x{self.Unknown1:X}'], p, False)
|
||||
printer(['Modulus :', f'{Modulus[:32]} [...]'], p, False)
|
||||
printer(['Exponent :', f'0x{self.Exponent:X}'], p, False)
|
||||
printer(['Signature:', f'{Signature[:32]} [...]'], p, False)
|
||||
|
||||
def is_ami_pfat(input_file):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
return bool(get_ami_pfat(input_buffer))
|
||||
|
||||
def get_ami_pfat(input_file):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
match = PAT_AMI_PFAT.search(input_buffer)
|
||||
|
||||
return input_buffer[match.start() - 0x8:] if match else b''
|
||||
|
||||
def get_file_name(index, name):
|
||||
return safe_name(f'{index:02d} -- {name}')
|
||||
|
||||
def parse_bg_script(script_data, padding=0):
|
||||
is_opcode_div = len(script_data) % 8 == 0
|
||||
|
||||
if not is_opcode_div:
|
||||
printer('Error: Script is not divisible by OpCode length!', padding, False)
|
||||
|
||||
return 1
|
||||
|
||||
is_begin_end = script_data[:8] + script_data[-8:] == b'\x01' + b'\x00' * 7 + b'\xFF' + b'\x00' * 7
|
||||
|
||||
if not is_begin_end:
|
||||
printer('Error: Script lacks Begin and/or End OpCodes!', padding, False)
|
||||
|
||||
return 2
|
||||
|
||||
BigScript = get_bgs_tool()
|
||||
|
||||
if not BigScript:
|
||||
printer('Note: BIOS Guard Script Tool optional dependency is missing!', padding, False)
|
||||
|
||||
return 3
|
||||
|
||||
script = BigScript(code_bytes=script_data).to_string().replace('\t',' ').split('\n')
|
||||
|
||||
for opcode in script:
|
||||
if opcode.endswith(('begin','end')): spacing = padding
|
||||
elif opcode.endswith(':'): spacing = padding + 4
|
||||
else: spacing = padding + 12
|
||||
|
||||
operands = [operand for operand in opcode.split(' ') if operand]
|
||||
printer(('{:<12s}' + '{:<11s}' * (len(operands) - 1)).format(*operands), spacing, False)
|
||||
|
||||
return 0
|
||||
|
||||
def parse_pfat_hdr(buffer, padding=0):
|
||||
block_all = []
|
||||
|
||||
pfat_hdr = get_struct(buffer, 0x0, AmiBiosGuardHeader)
|
||||
|
||||
hdr_size = pfat_hdr.Size
|
||||
hdr_data = buffer[PFAT_AMI_HDR_LEN:hdr_size]
|
||||
hdr_text = hdr_data.decode('utf-8').splitlines()
|
||||
|
||||
printer('AMI BIOS Guard Header:\n', padding)
|
||||
|
||||
pfat_hdr.struct_print(padding + 4)
|
||||
|
||||
hdr_title,*hdr_files = hdr_text
|
||||
|
||||
files_count = len(hdr_files)
|
||||
|
||||
hdr_tag,*hdr_indexes = hdr_title.split('II')
|
||||
|
||||
printer(hdr_tag + '\n', padding + 4)
|
||||
|
||||
bgt_indexes = [int(h, 16) for h in re.findall(r'.{1,4}', hdr_indexes[0])] if hdr_indexes else []
|
||||
|
||||
for index,entry in enumerate(hdr_files):
|
||||
entry_parts = entry.split(';')
|
||||
|
||||
info = entry_parts[0].split()
|
||||
name = entry_parts[1]
|
||||
|
||||
flags = int(info[0])
|
||||
param = info[1]
|
||||
count = int(info[2])
|
||||
|
||||
order = get_ordinal((bgt_indexes[index] if bgt_indexes else index) + 1)
|
||||
|
||||
desc = f'{name} (Index: {index + 1:02d}, Flash: {order}, Parameter: {param}, Flags: 0x{flags:X}, Blocks: {count})'
|
||||
|
||||
block_all += [(desc, name, order, param, flags, index, i, count) for i in range(count)]
|
||||
|
||||
_ = [printer(block[0], padding + 8, False) for block in block_all if block[6] == 0]
|
||||
|
||||
return block_all, hdr_size, files_count
|
||||
|
||||
def parse_pfat_file(input_file, extract_path, padding=0):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
pfat_buffer = get_ami_pfat(input_buffer)
|
||||
|
||||
file_path = ''
|
||||
all_blocks_dict = {}
|
||||
|
||||
extract_name = os.path.basename(extract_path).rstrip(extract_suffix())
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
block_all,block_off,file_count = parse_pfat_hdr(pfat_buffer, padding)
|
||||
|
||||
for block in block_all:
|
||||
file_desc,file_name,_,_,_,file_index,block_index,block_count = block
|
||||
|
||||
if block_index == 0:
|
||||
printer(file_desc, padding + 4)
|
||||
|
||||
file_path = os.path.join(extract_path, get_file_name(file_index + 1, file_name))
|
||||
|
||||
all_blocks_dict[file_index] = b''
|
||||
|
||||
block_status = f'{block_index + 1}/{block_count}'
|
||||
|
||||
bg_hdr = get_struct(pfat_buffer, block_off, IntelBiosGuardHeader)
|
||||
|
||||
printer(f'Intel BIOS Guard {block_status} Header:\n', padding + 8)
|
||||
|
||||
bg_hdr.struct_print(padding + 12)
|
||||
|
||||
bg_script_bgn = block_off + PFAT_BLK_HDR_LEN
|
||||
bg_script_end = bg_script_bgn + bg_hdr.ScriptSize
|
||||
bg_script_bin = pfat_buffer[bg_script_bgn:bg_script_end]
|
||||
|
||||
bg_data_bgn = bg_script_end
|
||||
bg_data_end = bg_data_bgn + bg_hdr.DataSize
|
||||
bg_data_bin = pfat_buffer[bg_data_bgn:bg_data_end]
|
||||
|
||||
block_off = bg_data_end # Assume next block starts at data end
|
||||
|
||||
is_sfam,_,_,_,_ = bg_hdr.get_flags() # SFAM, ProtectEC, GFXMitDis, FTU, Reserved
|
||||
|
||||
if is_sfam:
|
||||
bg_sig_bgn = bg_data_end
|
||||
bg_sig_end = bg_sig_bgn + PFAT_BLK_S2K_LEN
|
||||
bg_sig_bin = pfat_buffer[bg_sig_bgn:bg_sig_end]
|
||||
|
||||
if len(bg_sig_bin) == PFAT_BLK_S2K_LEN:
|
||||
bg_sig = get_struct(bg_sig_bin, 0x0, IntelBiosGuardSignature2k)
|
||||
|
||||
printer(f'Intel BIOS Guard {block_status} Signature:\n', padding + 8)
|
||||
|
||||
bg_sig.struct_print(padding + 12)
|
||||
|
||||
block_off = bg_sig_end # Adjust next block to start at data + signature end
|
||||
|
||||
printer(f'Intel BIOS Guard {block_status} Script:\n', padding + 8)
|
||||
|
||||
_ = parse_bg_script(bg_script_bin, padding + 12)
|
||||
|
||||
with open(file_path, 'ab') as out_dat:
|
||||
out_dat.write(bg_data_bin)
|
||||
|
||||
all_blocks_dict[file_index] += bg_data_bin
|
||||
|
||||
pfat_oob_data = pfat_buffer[block_off:] # Store out-of-bounds data after the end of PFAT files
|
||||
|
||||
pfat_oob_name = get_file_name(file_count + 1, f'{extract_name}_OOB.bin')
|
||||
|
||||
pfat_oob_path = os.path.join(extract_path, pfat_oob_name)
|
||||
|
||||
with open(pfat_oob_path, 'wb') as out_oob:
|
||||
out_oob.write(pfat_oob_data)
|
||||
|
||||
if is_ami_pfat(pfat_oob_data):
|
||||
parse_pfat_file(pfat_oob_data, get_extract_path(pfat_oob_path), padding)
|
||||
|
||||
in_all_data = b''.join([block[1] for block in sorted(all_blocks_dict.items())])
|
||||
|
||||
in_all_name = get_file_name(0, f'{extract_name}_ALL.bin')
|
||||
|
||||
in_all_path = os.path.join(extract_path, in_all_name)
|
||||
|
||||
with open(in_all_path, 'wb') as out_all:
|
||||
out_all.write(in_all_data + pfat_oob_data)
|
||||
|
||||
return 0
|
||||
|
||||
PFAT_AMI_HDR_LEN = ctypes.sizeof(AmiBiosGuardHeader)
|
||||
PFAT_BLK_HDR_LEN = ctypes.sizeof(IntelBiosGuardHeader)
|
||||
PFAT_BLK_S2K_LEN = ctypes.sizeof(IntelBiosGuardSignature2k)
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_ami_pfat, parse_pfat_file).run_utility()
|
515
AMI_UCP_Extract.py
Normal file
515
AMI_UCP_Extract.py
Normal file
|
@ -0,0 +1,515 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
AMI UCP Extract
|
||||
AMI UCP Update Extractor
|
||||
Copyright (C) 2021-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'AMI UCP Update Extractor v2.0_a20'
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import struct
|
||||
import ctypes
|
||||
import contextlib
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.checksums import get_chk_16
|
||||
from common.comp_efi import efi_decompress, is_efi_compressed
|
||||
from common.path_ops import agnostic_path, make_dirs, safe_name, safe_path, get_extract_path
|
||||
from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG
|
||||
from common.struct_ops import char, get_struct, uint8_t, uint16_t, uint32_t
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes, to_string
|
||||
|
||||
from AMI_PFAT_Extract import is_ami_pfat, parse_pfat_file
|
||||
from Insyde_IFD_Extract import insyde_ifd_extract, is_insyde_ifd
|
||||
|
||||
class UafHeader(ctypes.LittleEndianStructure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('ModuleTag', char*4), # 0x00
|
||||
('ModuleSize', uint32_t), # 0x04
|
||||
('Checksum', uint16_t), # 0x08
|
||||
('Unknown0', uint8_t), # 0x0A
|
||||
('Unknown1', uint8_t), # 0x0A
|
||||
('Reserved', uint8_t*4), # 0x0C
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def _get_reserved(self):
|
||||
res_bytes = bytes(self.Reserved)
|
||||
|
||||
res_hex = f'0x{int.from_bytes(res_bytes, "big"):0{0x4 * 2}X}'
|
||||
|
||||
res_str = re.sub(r'[\n\t\r\x00 ]', '', res_bytes.decode('utf-8','ignore'))
|
||||
|
||||
res_txt = f' ({res_str})' if len(res_str) else ''
|
||||
|
||||
return f'{res_hex}{res_txt}'
|
||||
|
||||
def struct_print(self, p):
|
||||
printer(['Tag :', self.ModuleTag.decode('utf-8')], p, False)
|
||||
printer(['Size :', f'0x{self.ModuleSize:X}'], p, False)
|
||||
printer(['Checksum :', f'0x{self.Checksum:04X}'], p, False)
|
||||
printer(['Unknown 0 :', f'0x{self.Unknown0:02X}'], p, False)
|
||||
printer(['Unknown 1 :', f'0x{self.Unknown1:02X}'], p, False)
|
||||
printer(['Reserved :', self._get_reserved()], p, False)
|
||||
|
||||
class UafModule(ctypes.LittleEndianStructure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('CompressSize', uint32_t), # 0x00
|
||||
('OriginalSize', uint32_t), # 0x04
|
||||
# 0x08
|
||||
]
|
||||
|
||||
def struct_print(self, p, filename, description):
|
||||
printer(['Compress Size:', f'0x{self.CompressSize:X}'], p, False)
|
||||
printer(['Original Size:', f'0x{self.OriginalSize:X}'], p, False)
|
||||
printer(['Filename :', filename], p, False)
|
||||
printer(['Description :', description], p, False)
|
||||
|
||||
class UiiHeader(ctypes.LittleEndianStructure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('UIISize', uint16_t), # 0x00
|
||||
('Checksum', uint16_t), # 0x02
|
||||
('UtilityVersion', uint32_t), # 0x04 AFU|BGT (Unknown, Signed)
|
||||
('InfoSize', uint16_t), # 0x08
|
||||
('SupportBIOS', uint8_t), # 0x0A
|
||||
('SupportOS', uint8_t), # 0x0B
|
||||
('DataBusWidth', uint8_t), # 0x0C
|
||||
('ProgramType', uint8_t), # 0x0D
|
||||
('ProgramMode', uint8_t), # 0x0E
|
||||
('SourceSafeRel', uint8_t), # 0x0F
|
||||
# 0x10
|
||||
]
|
||||
|
||||
SBI = {1: 'ALL', 2: 'AMIBIOS8', 3: 'UEFI', 4: 'AMIBIOS8/UEFI'}
|
||||
SOS = {1: 'DOS', 2: 'EFI', 3: 'Windows', 4: 'Linux', 5: 'FreeBSD', 6: 'MacOS', 128: 'Multi-Platform'}
|
||||
DBW = {1: '16b', 2: '16/32b', 3: '32b', 4: '64b'}
|
||||
PTP = {1: 'Executable', 2: 'Library', 3: 'Driver'}
|
||||
PMD = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'}
|
||||
|
||||
def struct_print(self, p, description):
|
||||
SupportBIOS = self.SBI.get(self.SupportBIOS, f'Unknown ({self.SupportBIOS})')
|
||||
SupportOS = self.SOS.get(self.SupportOS, f'Unknown ({self.SupportOS})')
|
||||
DataBusWidth = self.DBW.get(self.DataBusWidth, f'Unknown ({self.DataBusWidth})')
|
||||
ProgramType = self.PTP.get(self.ProgramType, f'Unknown ({self.ProgramType})')
|
||||
ProgramMode = self.PMD.get(self.ProgramMode, f'Unknown ({self.ProgramMode})')
|
||||
|
||||
printer(['UII Size :', f'0x{self.UIISize:X}'], p, False)
|
||||
printer(['Checksum :', f'0x{self.Checksum:04X}'], p, False)
|
||||
printer(['Tool Version :', f'0x{self.UtilityVersion:08X}'], p, False)
|
||||
printer(['Info Size :', f'0x{self.InfoSize:X}'], p, False)
|
||||
printer(['Supported BIOS:', SupportBIOS], p, False)
|
||||
printer(['Supported OS :', SupportOS], p, False)
|
||||
printer(['Data Bus Width:', DataBusWidth], p, False)
|
||||
printer(['Program Type :', ProgramType], p, False)
|
||||
printer(['Program Mode :', ProgramMode], p, False)
|
||||
printer(['SourceSafe Tag:', f'{self.SourceSafeRel:02d}'], p, False)
|
||||
printer(['Description :', description], p, False)
|
||||
|
||||
class DisHeader(ctypes.LittleEndianStructure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('PasswordSize', uint16_t), # 0x00
|
||||
('EntryCount', uint16_t), # 0x02
|
||||
('Password', char*12), # 0x04
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def struct_print(self, p):
|
||||
printer(['Password Size:', f'0x{self.PasswordSize:X}'], p, False)
|
||||
printer(['Entry Count :', self.EntryCount], p, False)
|
||||
printer(['Password :', self.Password.decode('utf-8')], p, False)
|
||||
|
||||
class DisModule(ctypes.LittleEndianStructure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('EnabledDisabled', uint8_t), # 0x00
|
||||
('ShownHidden', uint8_t), # 0x01
|
||||
('Command', char*32), # 0x02
|
||||
('Description', char*256), # 0x22
|
||||
# 0x122
|
||||
]
|
||||
|
||||
ENDIS = {0: 'Disabled', 1: 'Enabled'}
|
||||
SHOWN = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'}
|
||||
|
||||
def struct_print(self, p):
|
||||
EnabledDisabled = self.ENDIS.get(self.EnabledDisabled, f'Unknown ({self.EnabledDisabled})')
|
||||
ShownHidden = self.SHOWN.get(self.ShownHidden, f'Unknown ({self.ShownHidden})')
|
||||
|
||||
printer(['State :', EnabledDisabled], p, False)
|
||||
printer(['Display :', ShownHidden], p, False)
|
||||
printer(['Command :', self.Command.decode('utf-8').strip()], p, False)
|
||||
printer(['Description:', self.Description.decode('utf-8').strip()], p, False)
|
||||
|
||||
# Validate UCP Module Checksum-16
|
||||
def chk16_validate(data, tag, padd=0):
|
||||
if get_chk_16(data) != 0:
|
||||
printer(f'Error: Invalid UCP Module {tag} Checksum!', padd, pause=True)
|
||||
else:
|
||||
printer(f'Checksum of UCP Module {tag} is valid!', padd)
|
||||
|
||||
# Check if input is AMI UCP image
|
||||
def is_ami_ucp(in_file):
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
return bool(get_ami_ucp(buffer)[0] is not None)
|
||||
|
||||
# Get all input file AMI UCP patterns
|
||||
def get_ami_ucp(in_file):
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
uaf_len_max = 0x0 # Length of largest detected @UAF|@HPU
|
||||
uaf_buf_bin = None # Buffer of largest detected @UAF|@HPU
|
||||
uaf_buf_tag = '@UAF' # Tag of largest detected @UAF|@HPU
|
||||
|
||||
for uaf in PAT_AMI_UCP.finditer(buffer):
|
||||
uaf_len_cur = int.from_bytes(buffer[uaf.start() + 0x4:uaf.start() + 0x8], 'little')
|
||||
|
||||
if uaf_len_cur > uaf_len_max:
|
||||
uaf_len_max = uaf_len_cur
|
||||
uaf_hdr_off = uaf.start()
|
||||
uaf_buf_bin = buffer[uaf_hdr_off:uaf_hdr_off + uaf_len_max]
|
||||
uaf_buf_tag = uaf.group(0)[:4].decode('utf-8','ignore')
|
||||
|
||||
return uaf_buf_bin, uaf_buf_tag
|
||||
|
||||
# Get list of @UAF|@HPU Modules
|
||||
def get_uaf_mod(buffer, uaf_off=0x0):
|
||||
uaf_all = [] # Initialize list of all @UAF|@HPU Modules
|
||||
|
||||
while buffer[uaf_off] == 0x40: # ASCII of @ is 0x40
|
||||
uaf_hdr = get_struct(buffer, uaf_off, UafHeader) # Parse @UAF|@HPU Module Structure
|
||||
|
||||
uaf_tag = uaf_hdr.ModuleTag.decode('utf-8') # Get unique @UAF|@HPU Module Tag
|
||||
|
||||
uaf_all.append([uaf_tag, uaf_off, uaf_hdr]) # Store @UAF|@HPU Module Info
|
||||
|
||||
uaf_off += uaf_hdr.ModuleSize # Adjust to next @UAF|@HPU Module offset
|
||||
|
||||
if uaf_off >= len(buffer):
|
||||
break # Stop parsing at EOF
|
||||
|
||||
# Check if @UAF|@HPU Module @NAL exists and place it first
|
||||
# Parsing @NAL first allows naming all @UAF|@HPU Modules
|
||||
for mod_idx,mod_val in enumerate(uaf_all):
|
||||
if mod_val[0] == '@NAL':
|
||||
uaf_all.insert(1, uaf_all.pop(mod_idx)) # After UII for visual purposes
|
||||
|
||||
break # @NAL found, skip the rest
|
||||
|
||||
return uaf_all
|
||||
|
||||
# Parse & Extract AMI UCP structures
|
||||
def ucp_extract(in_file, extract_path, padding=0, checksum=False):
|
||||
input_buffer = file_to_bytes(in_file)
|
||||
|
||||
nal_dict = {} # Initialize @NAL Dictionary per UCP
|
||||
|
||||
printer('Utility Configuration Program', padding)
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
# Get best AMI UCP Pattern match based on @UAF|@HPU Size
|
||||
ucp_buffer,ucp_tag = get_ami_ucp(input_buffer)
|
||||
|
||||
uaf_hdr = get_struct(ucp_buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure
|
||||
|
||||
printer(f'Utility Auxiliary File > {ucp_tag}:\n', padding + 4)
|
||||
|
||||
uaf_hdr.struct_print(padding + 8)
|
||||
|
||||
fake = struct.pack('<II', len(ucp_buffer), len(ucp_buffer)) # Generate UafModule Structure
|
||||
|
||||
uaf_mod = get_struct(fake, 0x0, UafModule) # Parse @UAF|@HPU Module EFI Structure
|
||||
|
||||
uaf_name = UAF_TAG_DICT[ucp_tag][0] # Get @UAF|@HPU Module Filename
|
||||
uaf_desc = UAF_TAG_DICT[ucp_tag][1] # Get @UAF|@HPU Module Description
|
||||
|
||||
uaf_mod.struct_print(padding + 8, uaf_name, uaf_desc) # Print @UAF|@HPU Module EFI Info
|
||||
|
||||
if checksum:
|
||||
chk16_validate(ucp_buffer, ucp_tag, padding + 8)
|
||||
|
||||
uaf_all = get_uaf_mod(ucp_buffer, UAF_HDR_LEN)
|
||||
|
||||
for mod_info in uaf_all:
|
||||
nal_dict = uaf_extract(ucp_buffer, extract_path, mod_info, padding + 8, checksum, nal_dict)
|
||||
|
||||
# Parse & Extract AMI UCP > @UAF|@HPU Module/Section
|
||||
def uaf_extract(buffer, extract_path, mod_info, padding=0, checksum=False, nal_dict=None):
|
||||
if nal_dict is None:
|
||||
nal_dict = {}
|
||||
|
||||
uaf_tag,uaf_off,uaf_hdr = mod_info
|
||||
|
||||
uaf_data_all = buffer[uaf_off:uaf_off + uaf_hdr.ModuleSize] # @UAF|@HPU Module Entire Data
|
||||
|
||||
uaf_data_mod = uaf_data_all[UAF_HDR_LEN:] # @UAF|@HPU Module EFI Data
|
||||
|
||||
uaf_data_raw = uaf_data_mod[UAF_MOD_LEN:] # @UAF|@HPU Module Raw Data
|
||||
|
||||
printer(f'Utility Auxiliary File > {uaf_tag}:\n', padding)
|
||||
|
||||
uaf_hdr.struct_print(padding + 4) # Print @UAF|@HPU Module Info
|
||||
|
||||
uaf_mod = get_struct(buffer, uaf_off + UAF_HDR_LEN, UafModule) # Parse UAF Module EFI Structure
|
||||
|
||||
is_comp = uaf_mod.CompressSize != uaf_mod.OriginalSize # Detect @UAF|@HPU Module EFI Compression
|
||||
|
||||
if uaf_tag in nal_dict:
|
||||
uaf_name = nal_dict[uaf_tag][1] # Always prefer @NAL naming first
|
||||
elif uaf_tag in UAF_TAG_DICT:
|
||||
uaf_name = UAF_TAG_DICT[uaf_tag][0] # Otherwise use built-in naming
|
||||
elif uaf_tag == '@ROM':
|
||||
uaf_name = 'BIOS.bin' # BIOS/PFAT Firmware (w/o Signature)
|
||||
elif uaf_tag.startswith('@R0'):
|
||||
uaf_name = f'BIOS_0{uaf_tag[3:]}.bin' # BIOS/PFAT Firmware
|
||||
elif uaf_tag.startswith('@S0'):
|
||||
uaf_name = f'BIOS_0{uaf_tag[3:]}.sig' # BIOS/PFAT Signature
|
||||
elif uaf_tag.startswith('@DR'):
|
||||
uaf_name = f'DROM_0{uaf_tag[3:]}.bin' # Thunderbolt Retimer Firmware
|
||||
elif uaf_tag.startswith('@DS'):
|
||||
uaf_name = f'DROM_0{uaf_tag[3:]}.sig' # Thunderbolt Retimer Signature
|
||||
elif uaf_tag.startswith('@EC'):
|
||||
uaf_name = f'EC_0{uaf_tag[3:]}.bin' # Embedded Controller Firmware
|
||||
elif uaf_tag.startswith('@ME'):
|
||||
uaf_name = f'ME_0{uaf_tag[3:]}.bin' # Management Engine Firmware
|
||||
else:
|
||||
uaf_name = uaf_tag # Could not name the @UAF|@HPU Module, use Tag instead
|
||||
|
||||
uaf_fext = '' if uaf_name != uaf_tag else '.bin'
|
||||
|
||||
uaf_fdesc = UAF_TAG_DICT[uaf_tag][1] if uaf_tag in UAF_TAG_DICT else uaf_name
|
||||
|
||||
uaf_mod.struct_print(padding + 4, uaf_name + uaf_fext, uaf_fdesc) # Print @UAF|@HPU Module EFI Info
|
||||
|
||||
# Check if unknown @UAF|@HPU Module Tag is present in @NAL but not in built-in dictionary
|
||||
if uaf_tag in nal_dict and uaf_tag not in UAF_TAG_DICT and not uaf_tag.startswith(('@ROM','@R0','@S0','@DR','@DS')):
|
||||
printer(f'Note: Detected new AMI UCP Module {uaf_tag} ({nal_dict[uaf_tag][1]}) in @NAL!', padding + 4, pause=True)
|
||||
|
||||
# Generate @UAF|@HPU Module File name, depending on whether decompression will be required
|
||||
uaf_sname = safe_name(uaf_name + ('.temp' if is_comp else uaf_fext))
|
||||
if uaf_tag in nal_dict:
|
||||
uaf_npath = safe_path(extract_path, nal_dict[uaf_tag][0])
|
||||
make_dirs(uaf_npath, exist_ok=True)
|
||||
uaf_fname = safe_path(uaf_npath, uaf_sname)
|
||||
else:
|
||||
uaf_fname = safe_path(extract_path, uaf_sname)
|
||||
|
||||
if checksum:
|
||||
chk16_validate(uaf_data_all, uaf_tag, padding + 4)
|
||||
|
||||
# Parse Utility Identification Information @UAF|@HPU Module (@UII)
|
||||
if uaf_tag == '@UII':
|
||||
info_hdr = get_struct(uaf_data_raw, 0, UiiHeader) # Parse @UII Module Raw Structure
|
||||
|
||||
info_data = uaf_data_raw[max(UII_HDR_LEN,info_hdr.InfoSize):info_hdr.UIISize] # @UII Module Info Data
|
||||
|
||||
# Get @UII Module Info/Description text field
|
||||
info_desc = info_data.decode('utf-8','ignore').strip('\x00 ')
|
||||
|
||||
printer('Utility Identification Information:\n', padding + 4)
|
||||
|
||||
info_hdr.struct_print(padding + 8, info_desc) # Print @UII Module Info
|
||||
|
||||
if checksum:
|
||||
chk16_validate(uaf_data_raw, '@UII > Info', padding + 8)
|
||||
|
||||
# Store/Save @UII Module Info in file
|
||||
with open(uaf_fname[:-4] + '.txt', 'a', encoding='utf-8') as uii_out:
|
||||
with contextlib.redirect_stdout(uii_out):
|
||||
info_hdr.struct_print(0, info_desc) # Store @UII Module Info
|
||||
|
||||
# Adjust @UAF|@HPU Module Raw Data for extraction
|
||||
if is_comp:
|
||||
# Some Compressed @UAF|@HPU Module EFI data lack necessary EOF padding
|
||||
if uaf_mod.CompressSize > len(uaf_data_raw):
|
||||
comp_padd = b'\x00' * (uaf_mod.CompressSize - len(uaf_data_raw))
|
||||
uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw + comp_padd # Add missing padding for decompression
|
||||
else:
|
||||
uaf_data_raw = uaf_data_mod[:UAF_MOD_LEN] + uaf_data_raw # Add the EFI/Tiano Compression info before Raw Data
|
||||
else:
|
||||
uaf_data_raw = uaf_data_raw[:uaf_mod.OriginalSize] # No compression, extend to end of Original @UAF|@HPU Module size
|
||||
|
||||
# Store/Save @UAF|@HPU Module file
|
||||
if uaf_tag != '@UII': # Skip @UII binary, already parsed
|
||||
with open(uaf_fname, 'wb') as uaf_out:
|
||||
uaf_out.write(uaf_data_raw)
|
||||
|
||||
# @UAF|@HPU Module EFI/Tiano Decompression
|
||||
if is_comp and is_efi_compressed(uaf_data_raw, False):
|
||||
dec_fname = uaf_fname.replace('.temp', uaf_fext) # Decompressed @UAF|@HPU Module file path
|
||||
|
||||
if efi_decompress(uaf_fname, dec_fname, padding + 4) == 0:
|
||||
with open(dec_fname, 'rb') as dec:
|
||||
uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data
|
||||
|
||||
os.remove(uaf_fname) # Successful decompression, delete compressed @UAF|@HPU Module file
|
||||
|
||||
uaf_fname = dec_fname # Adjust @UAF|@HPU Module file path to the decompressed one
|
||||
|
||||
# Process and Print known text only @UAF|@HPU Modules (after EFI/Tiano Decompression)
|
||||
if uaf_tag in UAF_TAG_DICT and UAF_TAG_DICT[uaf_tag][2] == 'Text':
|
||||
printer(f'{UAF_TAG_DICT[uaf_tag][1]}:', padding + 4)
|
||||
printer(uaf_data_raw.decode('utf-8','ignore'), padding + 8)
|
||||
|
||||
# Parse Default Command Status @UAF|@HPU Module (@DIS)
|
||||
if len(uaf_data_raw) and uaf_tag == '@DIS':
|
||||
dis_hdr = get_struct(uaf_data_raw, 0x0, DisHeader) # Parse @DIS Module Raw Header Structure
|
||||
|
||||
printer('Default Command Status Header:\n', padding + 4)
|
||||
|
||||
dis_hdr.struct_print(padding + 8) # Print @DIS Module Raw Header Info
|
||||
|
||||
# Store/Save @DIS Module Header Info in file
|
||||
with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis:
|
||||
with contextlib.redirect_stdout(dis):
|
||||
dis_hdr.struct_print(0) # Store @DIS Module Header Info
|
||||
|
||||
dis_data = uaf_data_raw[DIS_HDR_LEN:] # @DIS Module Entries Data
|
||||
|
||||
# Parse all @DIS Module Entries
|
||||
for mod_idx in range(dis_hdr.EntryCount):
|
||||
dis_mod = get_struct(dis_data, mod_idx * DIS_MOD_LEN, DisModule) # Parse @DIS Module Raw Entry Structure
|
||||
|
||||
printer(f'Default Command Status Entry {mod_idx + 1:02d}/{dis_hdr.EntryCount:02d}:\n', padding + 8)
|
||||
|
||||
dis_mod.struct_print(padding + 12) # Print @DIS Module Raw Entry Info
|
||||
|
||||
# Store/Save @DIS Module Entry Info in file
|
||||
with open(uaf_fname[:-3] + 'txt', 'a', encoding='utf-8') as dis:
|
||||
with contextlib.redirect_stdout(dis):
|
||||
printer()
|
||||
dis_mod.struct_print(4) # Store @DIS Module Entry Info
|
||||
|
||||
os.remove(uaf_fname) # Delete @DIS Module binary, info exported as text
|
||||
|
||||
# Parse Name List @UAF|@HPU Module (@NAL)
|
||||
if len(uaf_data_raw) >= 5 and (uaf_tag,uaf_data_raw[0],uaf_data_raw[4]) == ('@NAL',0x40,0x3A):
|
||||
nal_info = uaf_data_raw.decode('utf-8','ignore').replace('\r','').strip().split('\n')
|
||||
|
||||
printer('AMI UCP Module Name List:\n', padding + 4)
|
||||
|
||||
# Parse all @NAL Module Entries
|
||||
for info in nal_info:
|
||||
info_tag,info_value = info.split(':',1)
|
||||
|
||||
printer(f'{info_tag} : {info_value}', padding + 8, False) # Print @NAL Module Tag-Path Info
|
||||
|
||||
info_part = agnostic_path(info_value).parts # Split OS agnostic path in parts
|
||||
info_path = to_string(info_part[1:-1], os.sep) # Get path without drive/root or file
|
||||
info_name = info_part[-1] # Get file from last path part
|
||||
|
||||
nal_dict[info_tag] = (info_path,info_name) # Assign a file path & name to each Tag
|
||||
|
||||
# Parse Insyde BIOS @UAF|@HPU Module (@INS)
|
||||
if uaf_tag == '@INS' and is_insyde_ifd(uaf_fname):
|
||||
ins_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-IFD')) # Generate extraction directory
|
||||
|
||||
if insyde_ifd_extract(uaf_fname, get_extract_path(ins_dir), padding + 4) == 0:
|
||||
os.remove(uaf_fname) # Delete raw nested Insyde IFD image after successful extraction
|
||||
|
||||
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
|
||||
if is_ami_pfat(uaf_data_raw):
|
||||
pfat_dir = os.path.join(extract_path, safe_name(uaf_name))
|
||||
|
||||
parse_pfat_file(uaf_data_raw, get_extract_path(pfat_dir), padding + 4)
|
||||
|
||||
os.remove(uaf_fname) # Delete raw PFAT BIOS image after successful extraction
|
||||
|
||||
# Detect Intel Engine firmware image and show ME Analyzer advice
|
||||
if uaf_tag.startswith('@ME') and PAT_INTEL_ENG.search(uaf_data_raw):
|
||||
printer('Intel Management Engine (ME) Firmware:\n', padding + 4)
|
||||
printer('Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer', padding + 8, False)
|
||||
|
||||
# Parse Nested AMI UCP image
|
||||
if is_ami_ucp(uaf_data_raw):
|
||||
uaf_dir = os.path.join(extract_path, safe_name(f'{uaf_tag}_nested-UCP')) # Generate extraction directory
|
||||
|
||||
ucp_extract(uaf_data_raw, get_extract_path(uaf_dir), padding + 4, checksum) # Call recursively
|
||||
|
||||
os.remove(uaf_fname) # Delete raw nested AMI UCP image after successful extraction
|
||||
|
||||
return nal_dict
|
||||
|
||||
# Get common ctypes Structure Sizes
|
||||
UAF_HDR_LEN = ctypes.sizeof(UafHeader)
|
||||
UAF_MOD_LEN = ctypes.sizeof(UafModule)
|
||||
DIS_HDR_LEN = ctypes.sizeof(DisHeader)
|
||||
DIS_MOD_LEN = ctypes.sizeof(DisModule)
|
||||
UII_HDR_LEN = ctypes.sizeof(UiiHeader)
|
||||
|
||||
# AMI UCP Tag Dictionary
|
||||
UAF_TAG_DICT = {
|
||||
'@3FI' : ['HpBiosUpdate32.efi', 'HpBiosUpdate32.efi', ''],
|
||||
'@3S2' : ['HpBiosUpdate32.s12', 'HpBiosUpdate32.s12', ''],
|
||||
'@3S4' : ['HpBiosUpdate32.s14', 'HpBiosUpdate32.s14', ''],
|
||||
'@3S9' : ['HpBiosUpdate32.s09', 'HpBiosUpdate32.s09', ''],
|
||||
'@3SG' : ['HpBiosUpdate32.sig', 'HpBiosUpdate32.sig', ''],
|
||||
'@AMI' : ['UCP_Nested.bin', 'Nested AMI UCP', ''],
|
||||
'@B12' : ['BiosMgmt.s12', 'BiosMgmt.s12', ''],
|
||||
'@B14' : ['BiosMgmt.s14', 'BiosMgmt.s14', ''],
|
||||
'@B32' : ['BiosMgmt32.s12', 'BiosMgmt32.s12', ''],
|
||||
'@B34' : ['BiosMgmt32.s14', 'BiosMgmt32.s14', ''],
|
||||
'@B39' : ['BiosMgmt32.s09', 'BiosMgmt32.s09', ''],
|
||||
'@B3E' : ['BiosMgmt32.efi', 'BiosMgmt32.efi', ''],
|
||||
'@BM9' : ['BiosMgmt.s09', 'BiosMgmt.s09', ''],
|
||||
'@BME' : ['BiosMgmt.efi', 'BiosMgmt.efi', ''],
|
||||
'@CKV' : ['Check_Version.txt', 'Check Version', 'Text'],
|
||||
'@CMD' : ['AFU_Command.txt', 'AMI AFU Command', 'Text'],
|
||||
'@CML' : ['CMOSD4.txt', 'CMOS Item Number-Value (MSI)', 'Text'],
|
||||
'@CMS' : ['CMOSD4.exe', 'Get or Set CMOS Item (MSI)', ''],
|
||||
'@CPM' : ['AC_Message.txt', 'Confirm Power Message', ''],
|
||||
'@DCT' : ['DevCon32.exe', 'Device Console WIN32', ''],
|
||||
'@DCX' : ['DevCon64.exe', 'Device Console WIN64', ''],
|
||||
'@DFE' : ['HpDevFwUpdate.efi', 'HpDevFwUpdate.efi', ''],
|
||||
'@DFS' : ['HpDevFwUpdate.s12', 'HpDevFwUpdate.s12', ''],
|
||||
'@DIS' : ['Command_Status.bin', 'Default Command Status', ''],
|
||||
'@ENB' : ['ENBG64.exe', 'ENBG64.exe', ''],
|
||||
'@HPU' : ['UCP_Main.bin', 'Utility Auxiliary File (HP)', ''],
|
||||
'@INS' : ['Insyde_Nested.bin', 'Nested Insyde SFX', ''],
|
||||
'@M32' : ['HpBiosMgmt32.s12', 'HpBiosMgmt32.s12', ''],
|
||||
'@M34' : ['HpBiosMgmt32.s14', 'HpBiosMgmt32.s14', ''],
|
||||
'@M39' : ['HpBiosMgmt32.s09', 'HpBiosMgmt32.s09', ''],
|
||||
'@M3I' : ['HpBiosMgmt32.efi', 'HpBiosMgmt32.efi', ''],
|
||||
'@MEC' : ['FWUpdLcl.txt', 'Intel FWUpdLcl Command', 'Text'],
|
||||
'@MED' : ['FWUpdLcl_DOS.exe', 'Intel FWUpdLcl DOS', ''],
|
||||
'@MET' : ['FWUpdLcl_WIN32.exe', 'Intel FWUpdLcl WIN32', ''],
|
||||
'@MFI' : ['HpBiosMgmt.efi', 'HpBiosMgmt.efi', ''],
|
||||
'@MS2' : ['HpBiosMgmt.s12', 'HpBiosMgmt.s12', ''],
|
||||
'@MS4' : ['HpBiosMgmt.s14', 'HpBiosMgmt.s14', ''],
|
||||
'@MS9' : ['HpBiosMgmt.s09', 'HpBiosMgmt.s09', ''],
|
||||
'@NAL' : ['UCP_List.txt', 'AMI UCP Module Name List', ''],
|
||||
'@OKM' : ['OK_Message.txt', 'OK Message', ''],
|
||||
'@PFC' : ['BGT_Command.txt', 'AMI BGT Command', 'Text'],
|
||||
'@R3I' : ['CryptRSA32.efi', 'CryptRSA32.efi', ''],
|
||||
'@RFI' : ['CryptRSA.efi', 'CryptRSA.efi', ''],
|
||||
'@UAF' : ['UCP_Main.bin', 'Utility Auxiliary File (AMI)', ''],
|
||||
'@UFI' : ['HpBiosUpdate.efi', 'HpBiosUpdate.efi', ''],
|
||||
'@UII' : ['UCP_Info.txt', 'Utility Identification Information', ''],
|
||||
'@US2' : ['HpBiosUpdate.s12', 'HpBiosUpdate.s12', ''],
|
||||
'@US4' : ['HpBiosUpdate.s14', 'HpBiosUpdate.s14', ''],
|
||||
'@US9' : ['HpBiosUpdate.s09', 'HpBiosUpdate.s09', ''],
|
||||
'@USG' : ['HpBiosUpdate.sig', 'HpBiosUpdate.sig', ''],
|
||||
'@VER' : ['OEM_Version.txt', 'OEM Version', 'Text'],
|
||||
'@VXD' : ['amifldrv.vxd', 'amifldrv.vxd', ''],
|
||||
'@W32' : ['amifldrv32.sys', 'amifldrv32.sys', ''],
|
||||
'@W64' : ['amifldrv64.sys', 'amifldrv64.sys', ''],
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
utility = BIOSUtility(TITLE, is_ami_ucp, ucp_extract)
|
||||
utility.parse_argument('-c', '--checksum', help='verify AMI UCP Checksums (slow)', action='store_true')
|
||||
utility.run_utility()
|
|
@ -1,104 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Apple EFI Rename
|
||||
Apple EFI File Renamer
|
||||
Copyright (C) 2018-2019 Plato Mavropoulos
|
||||
https://github.com/tianocore/edk2/blob/master/Vlv2TbltDevicePkg/Include/Library/BiosIdLib.h
|
||||
"""
|
||||
|
||||
print('Apple EFI File Renamer v1.3\n')
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import zlib
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
pattern = re.compile(br'\x24\x49\x42\x49\x4F\x53\x49\x24') # Intel $IBIOSI$
|
||||
|
||||
if len(sys.argv) >= 3 and sys.argv[1] == '-skip' :
|
||||
# Called via Apple_EFI_Package
|
||||
apple_efi = sys.argv[2:]
|
||||
skip_pause = True
|
||||
elif len(sys.argv) >= 2 :
|
||||
# Drag & Drop or CLI
|
||||
apple_efi = sys.argv[1:]
|
||||
skip_pause = False
|
||||
else :
|
||||
# Folder path
|
||||
apple_efi = []
|
||||
skip_pause = False
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
print('\nWorking...\n')
|
||||
for root, dirs, files in os.walk(in_path):
|
||||
for name in files :
|
||||
apple_efi.append(os.path.join(root, name))
|
||||
|
||||
for input_file in apple_efi :
|
||||
file_path = os.path.abspath(input_file)
|
||||
file_name = os.path.basename(input_file)
|
||||
file_dir = os.path.dirname(file_path)
|
||||
file_ext = os.path.splitext(file_path)[1]
|
||||
error = False
|
||||
|
||||
with open(input_file, 'rb') as in_file : buffer = in_file.read()
|
||||
|
||||
is_ibiosi = pattern.search(buffer) # Detect $IBIOSI$ pattern
|
||||
|
||||
if not is_ibiosi :
|
||||
|
||||
# On some Apple EFI, the $IBIOSI$ pattern is within compressed modules so we need to use UEFIFind and UEFIExtract
|
||||
|
||||
try :
|
||||
uefifind = subprocess.check_output(['UEFIFind', file_path, 'body', 'list', '244942494F534924'], universal_newlines=True)
|
||||
uefiextr = subprocess.run(['UEFIExtract', file_path, uefifind[0:36], '-o', '_$IBIOSI$_', '-m', 'body'], stdout=subprocess.DEVNULL)
|
||||
|
||||
with open(os.path.join('_$IBIOSI$_', 'body.bin'), 'rb') as in_file : buffer = in_file.read()
|
||||
|
||||
is_ibiosi = pattern.search(buffer) # Detect decompressed $IBIOSI$ pattern
|
||||
|
||||
shutil.rmtree('_$IBIOSI$_') # Remove temporary folder
|
||||
|
||||
except :
|
||||
error = True
|
||||
|
||||
if not error :
|
||||
|
||||
bios_info = buffer[is_ibiosi.end():is_ibiosi.end() + 0x42].decode('utf-16')
|
||||
|
||||
BoardID = bios_info[:7].strip()
|
||||
BoardRev = bios_info[7]
|
||||
OEMID = bios_info[9:12] # 88Z
|
||||
MajorVer = bios_info[13:17]
|
||||
BuildType = bios_info[18] # B
|
||||
MinorVer = bios_info[19:21]
|
||||
Year = bios_info[22:24]
|
||||
Month = bios_info[24:26]
|
||||
Day = bios_info[26:28]
|
||||
Hour = bios_info[28:30]
|
||||
Minute = bios_info[30:32]
|
||||
|
||||
file_chk = zlib.adler32(buffer) # Checksum for EFI with same $IBIOSI$ but different PRD/PRE status
|
||||
|
||||
new_name = '%s%s_%s_%s%s_20%s-%s-%s_%s-%s_%0.8X%s' % (BoardID, BoardRev, MajorVer, BuildType, MinorVer, Year, Month, Day, Hour, Minute, file_chk, file_ext)
|
||||
|
||||
file_path_new = os.path.join(file_dir, new_name)
|
||||
|
||||
if not os.path.isfile(file_path_new) : os.replace(file_path, file_path_new) # Rename input EFI with proper name
|
||||
|
||||
print(new_name)
|
||||
print('\nBoard Identity: %s%s' % (BoardID, BoardRev))
|
||||
print('Apple Identity: %s' % OEMID)
|
||||
print('Major Version: %s' % MajorVer)
|
||||
print('Minor Version: %s' % MinorVer)
|
||||
print('Build Type: %s' % BuildType)
|
||||
print('Build Date: 20%s-%s-%s' % (Year, Month, Day))
|
||||
print('Build Time: %s:%s\n' % (Hour, Minute))
|
||||
|
||||
else :
|
||||
print('\nError: Could not find $IBIOSI$ pattern at %s!' % file_name)
|
||||
print(' Make sure that "UEFIFind" and "UEFIExtract" executables exist!\n')
|
||||
|
||||
if not skip_pause : input('Done!')
|
|
@ -1,146 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Apple EFI Split
|
||||
Apple EFI IM4P Splitter
|
||||
Copyright (C) 2018-2020 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
title = 'Apple EFI IM4P Splitter v2.1'
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
im4p = re.compile(br'\x16\x04\x49\x4D\x34\x50\x16\x04') # Apple IM4P
|
||||
ifd = re.compile(br'\x5A\xA5\xF0\x0F.{172}\xFF{16}', re.DOTALL) # Intel Flash Descriptor (Z¥π. + [0xAC] + 0xFF * 16)
|
||||
|
||||
# Flash Descriptor Component Sizes
|
||||
comp_dict = {
|
||||
0 : 0x80000, # 512 KB
|
||||
1 : 0x100000, # 1 MB
|
||||
2 : 0x200000, # 2 MB
|
||||
3 : 0x400000, # 4 MB
|
||||
4 : 0x800000, # 8 MB
|
||||
5 : 0x1000000, # 16 MB
|
||||
6 : 0x2000000, # 32 MB
|
||||
7 : 0x4000000, # 64 MB
|
||||
8 : 0x8000000, # 128 MB
|
||||
9 : 0x10000000, # 256 MB
|
||||
}
|
||||
|
||||
# Get input catalog file paths
|
||||
if len(sys.argv) >= 3 and sys.argv[1] == '-skip' :
|
||||
# Called via Apple_EFI_Package
|
||||
apple_im4p = sys.argv[2:]
|
||||
skip_pause = True
|
||||
skip_space = ' '
|
||||
print('\n%s%s' % (skip_space, title)) # Print Title
|
||||
elif len(sys.argv) >= 2 :
|
||||
# Drag & Drop or CLI
|
||||
apple_im4p = sys.argv[1:]
|
||||
skip_pause = False
|
||||
skip_space = ''
|
||||
print('\n%s%s' % (skip_space, title)) # Print Title
|
||||
else :
|
||||
# Folder path
|
||||
apple_im4p = []
|
||||
skip_pause = False
|
||||
skip_space = ''
|
||||
print('\n%s%s' % (skip_space, title)) # Print Title
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
print('\nWorking...')
|
||||
for root, dirs, files in os.walk(in_path):
|
||||
for name in files :
|
||||
apple_im4p.append(os.path.join(root, name))
|
||||
|
||||
for input_file in apple_im4p :
|
||||
file_path = os.path.abspath(input_file)
|
||||
file_name = os.path.basename(input_file)
|
||||
file_dir = os.path.dirname(file_path)
|
||||
file_ext = os.path.splitext(file_path)[1]
|
||||
|
||||
print('\n%sFile: %s' % (skip_space, file_name)) # Print File Name
|
||||
|
||||
# Must be IM4P file because its size is 0x0 dependent
|
||||
if file_ext not in ('.im4p','.IM4P') :
|
||||
print('\n%s Error: Could not find IM4P file extension at %s!' % (skip_space, file_name))
|
||||
continue # Critical error
|
||||
|
||||
with open(input_file, 'rb') as in_file : buffer = in_file.read()
|
||||
|
||||
is_im4p = im4p.search(buffer) # Detect IM4P pattern
|
||||
|
||||
if not is_im4p :
|
||||
print('\n%s Error: Could not find IM4P pattern at %s!' % (skip_space, file_name))
|
||||
continue # Critical error
|
||||
|
||||
im4p_size = int.from_bytes(buffer[2:is_im4p.start()], 'big') # Variable, from 0x2 - IM4P
|
||||
im4p_type = buffer[is_im4p.end():is_im4p.end() + 0x4].decode('utf-8') # mefi
|
||||
|
||||
if im4p_type != 'mefi' :
|
||||
print('\n%s Error: Could not find "mefi" IM4P Type at %s!' % (skip_space, file_name))
|
||||
continue # Critical error
|
||||
|
||||
# After IM4P mefi (0x15), multi EFI payloads have _MEFIBIN (0x100) which is difficult to reverse without varying samples.
|
||||
# However, _MEFIBIN is not required for splitting SPI/EFI images because Intel Flash Descriptor Component Density exists.
|
||||
mefi_data_start = is_im4p.start() + buffer[is_im4p.start() - 0x1] # IM4P mefi payload start offset
|
||||
mefi_data_size = int.from_bytes(buffer[is_im4p.end() + 0x9:is_im4p.end() + 0xD], 'big') # IM4P mefi payload size
|
||||
mefibin_exist = buffer[mefi_data_start:mefi_data_start + 0x8] == b'_MEFIBIN' # Check if mefi is followed by _MEFIBIN
|
||||
efi_data_start = mefi_data_start + 0x100 if mefibin_exist else mefi_data_start # Actual multi EFI payloads start after _MEFIBIN
|
||||
efi_data_size = mefi_data_size - 0x100 if mefibin_exist else mefi_data_size # Actual multi EFI payloads size without _MEFIBIN
|
||||
buffer = buffer[efi_data_start:efi_data_start + efi_data_size] # Adjust input file buffer to actual multi EFI payloads data
|
||||
|
||||
fd_matches = list(ifd.finditer(buffer)) # Find Intel Flash Descriptor pattern matches
|
||||
fd_count = len(fd_matches) # Count found Intel Flash Descriptor pattern matches
|
||||
fd_final = [] # Initialize final Intel Flash Descriptor info storage
|
||||
|
||||
# Parse Intel Flash Descriptor pattern matches
|
||||
for fd_idx in range(fd_count) :
|
||||
fd = fd_matches[fd_idx] # Get Intel Flash Descriptor match object
|
||||
|
||||
fd_flmap0_fcba = buffer[fd.start() + 0x4] * 0x10 # Component Base Address from FD start (ICH8-ICH10 = 1, IBX = 2, CPT+ = 3)
|
||||
|
||||
# I/O Controller Hub (ICH)
|
||||
if fd_flmap0_fcba == 0x10 :
|
||||
start_substruct = 0x0 # At ICH, Flash Descriptor starts at 0x0
|
||||
end_substruct = 0xBC # 0xBC for [0xAC] + 0xFF * 16 sanity check
|
||||
# Platform Controller Hub (PCH)
|
||||
else :
|
||||
start_substruct = 0x10 # At PCH, Flash Descriptor starts at 0x10
|
||||
end_substruct = 0xBC # 0xBC for [0xAC] + 0xFF * 16 sanity check
|
||||
|
||||
fd_match_start = fd.start() - start_substruct # Actual Flash Descriptor Start Offset
|
||||
fd_match_end = fd.end() - end_substruct # Actual Flash Descriptor End Offset
|
||||
|
||||
# Calculate Intel Flash Descriptor Flash Component Total Size
|
||||
fd_flmap0_nc = ((int.from_bytes(buffer[fd_match_end:fd_match_end + 0x4], 'little') >> 8) & 3) + 1 # Component Count (00 = 1, 01 = 2)
|
||||
fd_flmap1_isl = buffer[fd_match_end + 0x7] # PCH/ICH Strap Length (ME 2-8 & TXE 0-2 & SPS 1-2 <= 0x12, ME 9+ & TXE 3+ & SPS 3+ >= 0x13)
|
||||
fd_comp_den = buffer[fd_match_start + fd_flmap0_fcba] # Component Density Byte (ME 2-8 & TXE 0-2 & SPS 1-2 = 0:5, ME 9+ & TXE 3+ & SPS 3+ = 0:7)
|
||||
fd_comp_1_bitwise = 0xF if fd_flmap1_isl >= 0x13 else 0x7 # Component 1 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4)
|
||||
fd_comp_2_bitwise = 0x4 if fd_flmap1_isl >= 0x13 else 0x3 # Component 2 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4)
|
||||
fd_comp_all_size = comp_dict[fd_comp_den & fd_comp_1_bitwise] # Component 1 Density (FCBA > C0DEN)
|
||||
if fd_flmap0_nc == 2 : fd_comp_all_size += comp_dict[fd_comp_den >> fd_comp_2_bitwise] # Component 2 Density (FCBA > C1DEN)
|
||||
|
||||
fd_final.append((fd_match_start,fd_comp_all_size)) # Store Intel Flash Descriptor final info
|
||||
|
||||
# Split IM4P via the final Intel Flash Descriptor matches
|
||||
for fd_idx in range(fd_count) :
|
||||
fd = fd_final[fd_idx] # Get Intel Flash Descriptor final info [FD Start, FD Component(s) Size]
|
||||
|
||||
# The Intel Flash Descriptor Flash Component Total Size should be enough to split the IM4P.
|
||||
# However, for sanity, its Size can be compared to the Size different of Next - Current FD.
|
||||
fd_diff_size = len(buffer) - fd[0] if fd_idx == fd_count - 1 else fd_final[fd_idx + 1][0] - fd[0] # Last FD ends at mefi payload end
|
||||
if fd[1] != fd_diff_size : # FD Total Component Size should be equal to Next-Current FD Difference Size
|
||||
print('\n%s Error: Intel FD %d/%d Component Size 0x%X != Next-Current FD Size Difference 0x%X!'
|
||||
% (skip_space, fd_idx + 1, fd_count, fd[1], fd_diff_size))
|
||||
|
||||
file_data = buffer[fd[0]:fd[0] + max(fd[1], fd_diff_size)] # Split EFI image file data (use largest FD Size just in case)
|
||||
|
||||
file_path_new = os.path.join(file_dir, '%s_%d.fd' % (file_name[:-5], fd_idx + 1)) # Split EFI image file path
|
||||
|
||||
with open(file_path_new, 'wb') as spi_image : spi_image.write(file_data) # Store split EFI image file
|
||||
|
||||
print('\n%s Split IM4P into %d EFI image(s)!' % (skip_space, fd_count))
|
||||
|
||||
if not skip_pause : input('\nDone!')
|
|
@ -1,101 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Apple EFI Package
|
||||
Apple EFI Package Extractor
|
||||
Copyright (C) 2019 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
print('Apple EFI Package Extractor v1.1')
|
||||
|
||||
import os
|
||||
import sys
|
||||
import zlib
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
if len(sys.argv) >= 2 :
|
||||
pkg = sys.argv[1:]
|
||||
else :
|
||||
pkg = []
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
print('\nWorking...')
|
||||
for root, dirs, files in os.walk(in_path):
|
||||
for name in files :
|
||||
pkg.append(os.path.join(root, name))
|
||||
|
||||
anytoiso_path = 'C:\\Program Files (x86)\\AnyToISO\\anytoiso.exe'
|
||||
|
||||
final_path = os.path.join(os.getcwd(), 'AppleEFI')
|
||||
if os.path.exists(final_path) : shutil.rmtree(final_path)
|
||||
|
||||
for input_file in pkg :
|
||||
file_path = os.path.abspath(input_file)
|
||||
file_name = os.path.basename(input_file)
|
||||
file_dir = os.path.dirname(file_path)
|
||||
file_ext = os.path.splitext(file_path)[1]
|
||||
|
||||
print('\nFile: %s\n' % file_name)
|
||||
|
||||
with open(input_file, 'rb') as in_buff : file_adler = zlib.adler32(in_buff.read()) & 0xFFFFFFFF
|
||||
|
||||
pkg_payload = os.path.join(final_path, '%s_%0.8X' % (file_name, file_adler))
|
||||
pkg_temp = os.path.join(final_path, '__TEMP_%s_%0.8X' % (file_name, file_adler))
|
||||
os.makedirs(pkg_temp)
|
||||
|
||||
subprocess.run([anytoiso_path, '/extract', file_path, pkg_temp], check = True, stdout=subprocess.DEVNULL)
|
||||
|
||||
if os.path.isfile(os.path.join(pkg_temp, 'Scripts')) :
|
||||
scripts_init = os.path.join(pkg_temp, 'Scripts')
|
||||
scripts_cpgz = os.path.join(pkg_temp, 'Scripts.cpgz')
|
||||
scripts_extr = os.path.join(pkg_temp, 'Scripts', '')
|
||||
efi_path = os.path.join(scripts_extr, 'Tools', 'EFIPayloads', '')
|
||||
|
||||
os.replace(scripts_init, scripts_cpgz)
|
||||
|
||||
subprocess.run([anytoiso_path, '/extract', scripts_cpgz, scripts_extr], check = True, stdout=subprocess.DEVNULL)
|
||||
|
||||
shutil.copytree(efi_path, pkg_payload)
|
||||
|
||||
elif os.path.isfile(os.path.join(pkg_temp, 'Payload')) :
|
||||
payload_init = os.path.join(pkg_temp, 'Payload')
|
||||
payload_pbzx = os.path.join(pkg_temp, 'Payload.pbzx')
|
||||
payload_extr = os.path.join(pkg_temp, 'Payload', '')
|
||||
zip_path = os.path.join(payload_extr, 'usr', 'standalone', 'firmware', 'bridgeOSCustomer.bundle', 'Contents', 'Resources', 'UpdateBundle')
|
||||
efi_path = os.path.join(zip_path, 'boot', 'Firmware', 'MacEFI', '')
|
||||
|
||||
os.replace(payload_init, payload_pbzx)
|
||||
|
||||
subprocess.run([anytoiso_path, '/extract', payload_pbzx, payload_extr], check = True, stdout=subprocess.DEVNULL)
|
||||
|
||||
subprocess.run([anytoiso_path, '/extract', zip_path + '.zip', zip_path], check = True, stdout=subprocess.DEVNULL)
|
||||
|
||||
if os.path.exists(efi_path) : shutil.copytree(efi_path, pkg_payload)
|
||||
|
||||
shutil.rmtree(pkg_temp)
|
||||
|
||||
im4p_files = []
|
||||
for root, dirs, files in os.walk(pkg_payload):
|
||||
for name in files :
|
||||
if name.endswith('.im4p') :
|
||||
im4p_files.append(os.path.join(root, name))
|
||||
|
||||
if im4p_files : subprocess.run(['python', 'Apple_EFI_Split.py', '-skip', *im4p_files], check = True, stdout=subprocess.DEVNULL)
|
||||
for im4p in im4p_files : os.remove(im4p)
|
||||
|
||||
final_files = []
|
||||
for root, dirs, files in os.walk(pkg_payload):
|
||||
for name in files :
|
||||
final_files.append(os.path.join(root, name))
|
||||
|
||||
if final_files : subprocess.run(['python', 'Apple_EFI_Rename.py', '-skip', *final_files], check = True, stdout=subprocess.DEVNULL)
|
||||
|
||||
for root, dirs, files in os.walk(pkg_payload):
|
||||
for name in files :
|
||||
if not os.path.isfile(os.path.join(final_path, name)) :
|
||||
shutil.copy2(os.path.join(root, name), os.path.join(final_path, name))
|
||||
|
||||
shutil.rmtree(pkg_payload)
|
||||
|
||||
else :
|
||||
input('\nDone!')
|
|
@ -1,71 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Apple EFI Links
|
||||
Apple EFI Sucatalog Link Grabber
|
||||
Copyright (C) 2018-2019 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
print('Apple EFI Sucatalog Link Grabber v1.2\n')
|
||||
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
# Remove previous output files
|
||||
if os.path.isfile('OUT.txt') : os.remove('OUT.txt')
|
||||
|
||||
# Get input catalog file paths
|
||||
if len(sys.argv) >= 2 :
|
||||
# Drag & Drop or CLI
|
||||
catalogs = sys.argv[1:]
|
||||
else :
|
||||
# Working directory
|
||||
catalogs = []
|
||||
for root, dirs, files in os.walk(os.getcwd()) :
|
||||
for name in files :
|
||||
if name.endswith('.sucatalog') :
|
||||
catalogs.append(os.path.join(root, name))
|
||||
|
||||
print('Working...')
|
||||
|
||||
# Parse each input xml file
|
||||
for input_file in catalogs :
|
||||
input_name,input_extension = os.path.splitext(os.path.basename(input_file))
|
||||
|
||||
print('\n%s%s' % (input_name, input_extension))
|
||||
|
||||
with open(input_file, 'r') as in_file :
|
||||
for line in in_file :
|
||||
# Find EFI Firmware package links
|
||||
if ('.pkg' in line or '.tar' in line) and ('FirmwareUpd' in line or '/BridgeOSUpdateCustomer' in line or 'EFIUpd' in line) \
|
||||
and 'Bluetooth' not in line and 'DPVGA' not in line and 'Thunderbolt' not in line and 'PMG5' not in line and 'HardDrive' not in line :
|
||||
if '.pkg' in line : link = line[line.find('http'):line.find('.pkg') + 4] # Remove xml formatting
|
||||
else : link = line[line.find('http'):line.find('.tar') + 4]
|
||||
|
||||
with open('OUT.txt', 'a') as out_file : out_file.write(link + '\n') # Store links in temporary output file
|
||||
|
||||
# Parse temporary output file
|
||||
if os.path.isfile('OUT.txt') :
|
||||
with open('OUT.txt', 'r+') as out_file :
|
||||
parsed_lines = []
|
||||
final_lines = []
|
||||
|
||||
for line in out_file :
|
||||
if line not in parsed_lines : # Remove duplicate links
|
||||
final_lines.append(line)
|
||||
parsed_lines.append(line)
|
||||
|
||||
final_lines = ''.join(map(str, sorted(final_lines)))
|
||||
|
||||
current_datetime = datetime.datetime.utcnow().isoformat(timespec='seconds').replace('-','').replace('T','').replace(':','')
|
||||
|
||||
output_file = 'EFI %s.txt' % current_datetime
|
||||
|
||||
with open(output_file, 'w') as efi_file : efi_file.write(final_lines) # Save final output file
|
||||
|
||||
print('\nStored %s!' % output_file)
|
||||
|
||||
os.remove('OUT.txt') # Remove temporary output file
|
||||
|
||||
input('\nDone!')
|
167
Apple_EFI_ID.py
Normal file
167
Apple_EFI_ID.py
Normal file
|
@ -0,0 +1,167 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Apple EFI ID
|
||||
Apple EFI Image Identifier
|
||||
Copyright (C) 2018-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'Apple EFI Image Identifier v2.0_a5'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import zlib
|
||||
import struct
|
||||
import ctypes
|
||||
import subprocess
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.externals import get_uefifind_path, get_uefiextract_path
|
||||
from common.path_ops import del_dirs, path_parent, path_suffixes
|
||||
from common.patterns import PAT_APPLE_EFI
|
||||
from common.struct_ops import char, get_struct, uint8_t
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
class IntelBiosId(ctypes.LittleEndianStructure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Signature', char*8), # 0x00
|
||||
('BoardID', uint8_t*16), # 0x08
|
||||
('Dot1', uint8_t*2), # 0x18
|
||||
('BoardExt', uint8_t*6), # 0x1A
|
||||
('Dot2', uint8_t*2), # 0x20
|
||||
('VersionMajor', uint8_t*8), # 0x22
|
||||
('Dot3', uint8_t*2), # 0x2A
|
||||
('BuildType', uint8_t*2), # 0x2C
|
||||
('VersionMinor', uint8_t*4), # 0x2E
|
||||
('Dot4', uint8_t*2), # 0x32
|
||||
('Year', uint8_t*4), # 0x34
|
||||
('Month', uint8_t*4), # 0x38
|
||||
('Day', uint8_t*4), # 0x3C
|
||||
('Hour', uint8_t*4), # 0x40
|
||||
('Minute', uint8_t*4), # 0x44
|
||||
('NullTerminator', uint8_t*2), # 0x48
|
||||
# 0x4A
|
||||
]
|
||||
|
||||
# https://github.com/tianocore/edk2-platforms/blob/master/Platform/Intel/BoardModulePkg/Include/Guid/BiosId.h
|
||||
|
||||
@staticmethod
|
||||
def decode(field):
|
||||
return struct.pack('B' * len(field), *field).decode('utf-16','ignore').strip('\x00 ')
|
||||
|
||||
def get_bios_id(self):
|
||||
BoardID = self.decode(self.BoardID)
|
||||
BoardExt = self.decode(self.BoardExt)
|
||||
VersionMajor = self.decode(self.VersionMajor)
|
||||
BuildType = self.decode(self.BuildType)
|
||||
VersionMinor = self.decode(self.VersionMinor)
|
||||
BuildDate = f'20{self.decode(self.Year)}-{self.decode(self.Month)}-{self.decode(self.Day)}'
|
||||
BuildTime = f'{self.decode(self.Hour)}-{self.decode(self.Minute)}'
|
||||
|
||||
return BoardID, BoardExt, VersionMajor, BuildType, VersionMinor, BuildDate, BuildTime
|
||||
|
||||
def struct_print(self, p):
|
||||
BoardID,BoardExt,VersionMajor,BuildType,VersionMinor,BuildDate,BuildTime = self.get_bios_id()
|
||||
|
||||
printer(['Intel Signature:', self.Signature.decode('utf-8')], p, False)
|
||||
printer(['Board Identity: ', BoardID], p, False)
|
||||
printer(['Apple Identity: ', BoardExt], p, False)
|
||||
printer(['Major Version: ', VersionMajor], p, False)
|
||||
printer(['Minor Version: ', VersionMinor], p, False)
|
||||
printer(['Build Type: ', BuildType], p, False)
|
||||
printer(['Build Date: ', BuildDate], p, False)
|
||||
printer(['Build Time: ', BuildTime.replace('-',':')], p, False)
|
||||
|
||||
# Check if input is Apple EFI image
|
||||
def is_apple_efi(input_file):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
if PAT_APPLE_EFI.search(input_buffer):
|
||||
return True
|
||||
|
||||
if not os.path.isfile(input_file):
|
||||
return False
|
||||
|
||||
try:
|
||||
_ = subprocess.run([get_uefifind_path(), input_file, 'body', 'list', PAT_UEFIFIND],
|
||||
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# Parse & Identify (or Rename) Apple EFI image
|
||||
def apple_efi_identify(input_file, extract_path, padding=0, rename=False):
|
||||
if not os.path.isfile(input_file):
|
||||
printer('Error: Could not find input file path!', padding)
|
||||
|
||||
return 1
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
bios_id_match = PAT_APPLE_EFI.search(input_buffer) # Detect $IBIOSI$ pattern
|
||||
|
||||
if bios_id_match:
|
||||
bios_id_res = f'0x{bios_id_match.start():X}'
|
||||
|
||||
bios_id_hdr = get_struct(input_buffer, bios_id_match.start(), IntelBiosId)
|
||||
else:
|
||||
# The $IBIOSI$ pattern is within EFI compressed modules so we need to use UEFIFind and UEFIExtract
|
||||
try:
|
||||
bios_id_res = subprocess.check_output([get_uefifind_path(), input_file, 'body', 'list', PAT_UEFIFIND],
|
||||
text=True)[:36]
|
||||
|
||||
del_dirs(extract_path) # UEFIExtract must create its output folder itself, make sure it is not present
|
||||
|
||||
_ = subprocess.run([get_uefiextract_path(), input_file, bios_id_res, '-o', extract_path, '-m', 'body'],
|
||||
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
with open(os.path.join(extract_path, 'body.bin'), 'rb') as raw_body:
|
||||
body_buffer = raw_body.read()
|
||||
|
||||
bios_id_match = PAT_APPLE_EFI.search(body_buffer) # Detect decompressed $IBIOSI$ pattern
|
||||
|
||||
bios_id_hdr = get_struct(body_buffer, bios_id_match.start(), IntelBiosId)
|
||||
|
||||
del_dirs(extract_path) # Successful UEFIExtract extraction, remove its output (temp) folder
|
||||
except Exception:
|
||||
printer('Error: Failed to parse compressed $IBIOSI$ pattern!', padding)
|
||||
|
||||
return 2
|
||||
|
||||
printer(f'Detected $IBIOSI$ at {bios_id_res}\n', padding)
|
||||
|
||||
bios_id_hdr.struct_print(padding + 4)
|
||||
|
||||
if rename:
|
||||
input_parent = path_parent(input_file)
|
||||
|
||||
input_suffix = path_suffixes(input_file)[-1]
|
||||
|
||||
input_adler32 = zlib.adler32(input_buffer)
|
||||
|
||||
ID,Ext,Major,Type,Minor,Date,Time = bios_id_hdr.get_bios_id()
|
||||
|
||||
output_name = f'{ID}_{Ext}_{Major}_{Type}{Minor}_{Date}_{Time}_{input_adler32:08X}{input_suffix}'
|
||||
|
||||
output_file = os.path.join(input_parent, output_name)
|
||||
|
||||
if not os.path.isfile(output_file):
|
||||
os.replace(input_file, output_file) # Rename input file based on its EFI tag
|
||||
|
||||
printer(f'Renamed to {output_name}', padding)
|
||||
|
||||
return 0
|
||||
|
||||
PAT_UEFIFIND = f'244942494F534924{"."*32}2E00{"."*12}2E00{"."*16}2E00{"."*12}2E00{"."*40}0000'
|
||||
|
||||
if __name__ == '__main__':
|
||||
utility = BIOSUtility(TITLE, is_apple_efi, apple_efi_identify)
|
||||
utility.parse_argument('-r', '--rename', help='rename EFI image based on its tag', action='store_true')
|
||||
utility.run_utility()
|
145
Apple_EFI_IM4P.py
Normal file
145
Apple_EFI_IM4P.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Apple EFI IM4P
|
||||
Apple EFI IM4P Splitter
|
||||
Copyright (C) 2018-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'Apple EFI IM4P Splitter v3.0_a5'
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.path_ops import make_dirs, path_stem
|
||||
from common.patterns import PAT_APPLE_IM4P, PAT_INTEL_IFD
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is Apple EFI IM4P image
|
||||
def is_apple_im4p(input_file):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
is_im4p = PAT_APPLE_IM4P.search(input_buffer)
|
||||
|
||||
is_ifd = PAT_INTEL_IFD.search(input_buffer)
|
||||
|
||||
return bool(is_im4p and is_ifd)
|
||||
|
||||
# Parse & Split Apple EFI IM4P image
|
||||
def apple_im4p_split(input_file, extract_path, padding=0):
|
||||
exit_codes = []
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
# Detect IM4P EFI pattern
|
||||
im4p_match = PAT_APPLE_IM4P.search(input_buffer)
|
||||
|
||||
# After IM4P mefi (0x15), multi EFI payloads have _MEFIBIN (0x100) but is difficult to RE w/o varying samples.
|
||||
# However, _MEFIBIN is not required for splitting SPI images due to Intel Flash Descriptor Components Density.
|
||||
|
||||
# IM4P mefi payload start offset
|
||||
mefi_data_bgn = im4p_match.start() + input_buffer[im4p_match.start() - 0x1]
|
||||
|
||||
# IM4P mefi payload size
|
||||
mefi_data_len = int.from_bytes(input_buffer[im4p_match.end() + 0x5:im4p_match.end() + 0x9], 'big')
|
||||
|
||||
# Check if mefi is followed by _MEFIBIN
|
||||
mefibin_exist = input_buffer[mefi_data_bgn:mefi_data_bgn + 0x8] == b'_MEFIBIN'
|
||||
|
||||
# Actual multi EFI payloads start after _MEFIBIN
|
||||
efi_data_bgn = mefi_data_bgn + 0x100 if mefibin_exist else mefi_data_bgn
|
||||
|
||||
# Actual multi EFI payloads size without _MEFIBIN
|
||||
efi_data_len = mefi_data_len - 0x100 if mefibin_exist else mefi_data_len
|
||||
|
||||
# Adjust input file buffer to actual multi EFI payloads data
|
||||
input_buffer = input_buffer[efi_data_bgn:efi_data_bgn + efi_data_len]
|
||||
|
||||
# Parse Intel Flash Descriptor pattern matches
|
||||
for ifd in PAT_INTEL_IFD.finditer(input_buffer):
|
||||
# Component Base Address from FD start (ICH8-ICH10 = 1, IBX = 2, CPT+ = 3)
|
||||
ifd_flmap0_fcba = input_buffer[ifd.start() + 0x4] * 0x10
|
||||
|
||||
# I/O Controller Hub (ICH)
|
||||
if ifd_flmap0_fcba == 0x10:
|
||||
# At ICH, Flash Descriptor starts at 0x0
|
||||
ifd_bgn_substruct = 0x0
|
||||
|
||||
# 0xBC for [0xAC] + 0xFF * 16 sanity check
|
||||
ifd_end_substruct = 0xBC
|
||||
|
||||
# Platform Controller Hub (PCH)
|
||||
else:
|
||||
# At PCH, Flash Descriptor starts at 0x10
|
||||
ifd_bgn_substruct = 0x10
|
||||
|
||||
# 0xBC for [0xAC] + 0xFF * 16 sanity check
|
||||
ifd_end_substruct = 0xBC
|
||||
|
||||
# Actual Flash Descriptor Start Offset
|
||||
ifd_match_start = ifd.start() - ifd_bgn_substruct
|
||||
|
||||
# Actual Flash Descriptor End Offset
|
||||
ifd_match_end = ifd.end() - ifd_end_substruct
|
||||
|
||||
# Calculate Intel Flash Descriptor Flash Component Total Size
|
||||
|
||||
# Component Count (00 = 1, 01 = 2)
|
||||
ifd_flmap0_nc = ((int.from_bytes(input_buffer[ifd_match_end:ifd_match_end + 0x4], 'little') >> 8) & 3) + 1
|
||||
|
||||
# PCH/ICH Strap Length (ME 2-8 & TXE 0-2 & SPS 1-2 <= 0x12, ME 9+ & TXE 3+ & SPS 3+ >= 0x13)
|
||||
ifd_flmap1_isl = input_buffer[ifd_match_end + 0x7]
|
||||
|
||||
# Component Density Byte (ME 2-8 & TXE 0-2 & SPS 1-2 = 0:5, ME 9+ & TXE 3+ & SPS 3+ = 0:7)
|
||||
ifd_comp_den = input_buffer[ifd_match_start + ifd_flmap0_fcba]
|
||||
|
||||
# Component 1 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4)
|
||||
ifd_comp_1_bitwise = 0xF if ifd_flmap1_isl >= 0x13 else 0x7
|
||||
|
||||
# Component 2 Density Bits (ME 2-8 & TXE 0-2 & SPS 1-2 = 3, ME 9+ & TXE 3+ & SPS 3+ = 4)
|
||||
ifd_comp_2_bitwise = 0x4 if ifd_flmap1_isl >= 0x13 else 0x3
|
||||
|
||||
# Component 1 Density (FCBA > C0DEN)
|
||||
ifd_comp_all_size = IFD_COMP_LEN[ifd_comp_den & ifd_comp_1_bitwise]
|
||||
|
||||
# Component 2 Density (FCBA > C1DEN)
|
||||
if ifd_flmap0_nc == 2:
|
||||
ifd_comp_all_size += IFD_COMP_LEN[ifd_comp_den >> ifd_comp_2_bitwise]
|
||||
|
||||
ifd_data_bgn = ifd_match_start
|
||||
ifd_data_end = ifd_data_bgn + ifd_comp_all_size
|
||||
ifd_data_txt = f'0x{ifd_data_bgn:07X}-0x{ifd_data_end:07X}'
|
||||
|
||||
output_data = input_buffer[ifd_data_bgn:ifd_data_end]
|
||||
|
||||
output_size = len(output_data)
|
||||
|
||||
output_name = path_stem(input_file) if os.path.isfile(input_file) else 'Part'
|
||||
|
||||
output_path = os.path.join(extract_path, f'{output_name}_[{ifd_data_txt}].fd')
|
||||
|
||||
with open(output_path, 'wb') as output_image:
|
||||
output_image.write(output_data)
|
||||
|
||||
printer(f'Split Apple EFI image at {ifd_data_txt}!', padding)
|
||||
|
||||
if output_size != ifd_comp_all_size:
|
||||
printer(f'Error: Bad image size 0x{output_size:07X}, expected 0x{ifd_comp_all_size:07X}!', padding + 4)
|
||||
|
||||
exit_codes.append(1)
|
||||
|
||||
return sum(exit_codes)
|
||||
|
||||
# Intel Flash Descriptor Component Sizes (4MB, 8MB, 16MB and 32MB)
|
||||
IFD_COMP_LEN = {3: 0x400000, 4: 0x800000, 5: 0x1000000, 6: 0x2000000}
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_apple_im4p, apple_im4p_split).run_utility()
|
118
Apple_EFI_PBZX.py
Normal file
118
Apple_EFI_PBZX.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Apple PBZX Extract
|
||||
Apple EFI PBZX Extractor
|
||||
Copyright (C) 2021-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'Apple EFI PBZX Extractor v1.0_a5'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import lzma
|
||||
import ctypes
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_szip import is_szip_supported, szip_decompress
|
||||
from common.path_ops import make_dirs, path_stem
|
||||
from common.patterns import PAT_APPLE_PBZX
|
||||
from common.struct_ops import get_struct, uint32_t
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
class PbzxChunk(ctypes.BigEndianStructure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Reserved0', uint32_t), # 0x00
|
||||
('InitSize', uint32_t), # 0x04
|
||||
('Reserved1', uint32_t), # 0x08
|
||||
('CompSize', uint32_t), # 0x0C
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def struct_print(self, p):
|
||||
printer(['Reserved 0 :', f'0x{self.Reserved0:X}'], p, False)
|
||||
printer(['Initial Size :', f'0x{self.InitSize:X}'], p, False)
|
||||
printer(['Reserved 1 :', f'0x{self.Reserved1:X}'], p, False)
|
||||
printer(['Compressed Size:', f'0x{self.CompSize:X}'], p, False)
|
||||
|
||||
# Check if input is Apple PBZX image
|
||||
def is_apple_pbzx(input_file):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
return bool(PAT_APPLE_PBZX.search(input_buffer[:0x4]))
|
||||
|
||||
# Parse & Extract Apple PBZX image
|
||||
def apple_pbzx_extract(input_file, extract_path, padding=0):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
cpio_bin = b'' # Initialize PBZX > CPIO Buffer
|
||||
cpio_len = 0x0 # Initialize PBZX > CPIO Length
|
||||
|
||||
chunk_off = 0xC # First PBZX Chunk starts at 0xC
|
||||
while chunk_off < len(input_buffer):
|
||||
chunk_hdr = get_struct(input_buffer, chunk_off, PbzxChunk)
|
||||
|
||||
printer(f'PBZX Chunk at 0x{chunk_off:08X}\n', padding)
|
||||
|
||||
chunk_hdr.struct_print(padding + 4)
|
||||
|
||||
# PBZX Chunk data starts after its Header
|
||||
comp_bgn = chunk_off + PBZX_CHUNK_HDR_LEN
|
||||
|
||||
# To avoid a potential infinite loop, double-check Compressed Size
|
||||
comp_end = comp_bgn + max(chunk_hdr.CompSize, PBZX_CHUNK_HDR_LEN)
|
||||
|
||||
comp_bin = input_buffer[comp_bgn:comp_end]
|
||||
|
||||
try:
|
||||
# Attempt XZ decompression, if applicable to Chunk data
|
||||
cpio_bin += lzma.LZMADecompressor().decompress(comp_bin)
|
||||
|
||||
printer('Successful LZMA decompression!', padding + 8)
|
||||
except Exception:
|
||||
# Otherwise, Chunk data is not compressed
|
||||
cpio_bin += comp_bin
|
||||
|
||||
# Final CPIO size should match the sum of all Chunks > Initial Size
|
||||
cpio_len += chunk_hdr.InitSize
|
||||
|
||||
# Next Chunk starts at the end of current Chunk's data
|
||||
chunk_off = comp_end
|
||||
|
||||
# Check that CPIO size is valid based on all Chunks > Initial Size
|
||||
if cpio_len != len(cpio_bin):
|
||||
printer('Error: Unexpected CPIO archive size!', padding)
|
||||
|
||||
return 1
|
||||
|
||||
cpio_name = path_stem(input_file) if os.path.isfile(input_file) else 'Payload'
|
||||
|
||||
cpio_path = os.path.join(extract_path, f'{cpio_name}.cpio')
|
||||
|
||||
with open(cpio_path, 'wb') as cpio_object:
|
||||
cpio_object.write(cpio_bin)
|
||||
|
||||
# Decompress PBZX > CPIO archive with 7-Zip
|
||||
if is_szip_supported(cpio_path, padding, args=['-tCPIO'], check=True):
|
||||
if szip_decompress(cpio_path, extract_path, 'CPIO', padding, args=['-tCPIO'], check=True) == 0:
|
||||
os.remove(cpio_path) # Successful extraction, delete PBZX > CPIO archive
|
||||
else:
|
||||
return 3
|
||||
else:
|
||||
return 2
|
||||
|
||||
return 0
|
||||
|
||||
# Get common ctypes Structure Sizes
|
||||
PBZX_CHUNK_HDR_LEN = ctypes.sizeof(PbzxChunk)
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_apple_pbzx, apple_pbzx_extract).run_utility()
|
148
Apple_EFI_PKG.py
Normal file
148
Apple_EFI_PKG.py
Normal file
|
@ -0,0 +1,148 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Apple EFI PKG
|
||||
Apple EFI Package Extractor
|
||||
Copyright (C) 2019-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'Apple EFI Package Extractor v2.0_a5'
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_szip import is_szip_supported, szip_decompress
|
||||
from common.path_ops import copy_file, del_dirs, get_path_files, make_dirs, path_name, path_parent, get_extract_path
|
||||
from common.patterns import PAT_APPLE_PKG
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
from Apple_EFI_ID import apple_efi_identify, is_apple_efi
|
||||
from Apple_EFI_IM4P import apple_im4p_split, is_apple_im4p
|
||||
from Apple_EFI_PBZX import apple_pbzx_extract, is_apple_pbzx
|
||||
|
||||
# Check if input is Apple EFI PKG package
|
||||
def is_apple_pkg(input_file):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
return bool(PAT_APPLE_PKG.search(input_buffer[:0x4]))
|
||||
|
||||
# Split Apple EFI image (if applicable) and Rename
|
||||
def efi_split_rename(in_file, out_path, padding=0):
|
||||
exit_codes = []
|
||||
|
||||
working_dir = get_extract_path(in_file)
|
||||
|
||||
if is_apple_im4p(in_file):
|
||||
printer(f'Splitting IM4P via {is_apple_im4p.__module__}...', padding)
|
||||
im4p_exit = apple_im4p_split(in_file, working_dir, padding + 4)
|
||||
exit_codes.append(im4p_exit)
|
||||
else:
|
||||
make_dirs(working_dir, delete=True)
|
||||
copy_file(in_file, working_dir, True)
|
||||
|
||||
for efi_file in get_path_files(working_dir):
|
||||
if is_apple_efi(efi_file):
|
||||
printer(f'Renaming EFI via {is_apple_efi.__module__}...', padding)
|
||||
name_exit = apple_efi_identify(efi_file, efi_file, padding + 4, True)
|
||||
exit_codes.append(name_exit)
|
||||
|
||||
for named_file in get_path_files(working_dir):
|
||||
copy_file(named_file, out_path, True)
|
||||
|
||||
del_dirs(working_dir)
|
||||
|
||||
return sum(exit_codes)
|
||||
|
||||
# Parse & Extract Apple EFI PKG packages
|
||||
def apple_pkg_extract(input_file, extract_path, padding=0):
|
||||
if not os.path.isfile(input_file):
|
||||
printer('Error: Could not find input file path!', padding)
|
||||
return 1
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
xar_path = os.path.join(extract_path, 'xar')
|
||||
|
||||
# Decompress PKG > XAR archive with 7-Zip
|
||||
if is_szip_supported(input_file, padding, args=['-tXAR'], check=True):
|
||||
if szip_decompress(input_file, xar_path, 'XAR', padding, args=['-tXAR'], check=True) != 0:
|
||||
return 3
|
||||
else:
|
||||
return 2
|
||||
|
||||
for xar_file in get_path_files(xar_path):
|
||||
if path_name(xar_file) == 'Payload':
|
||||
pbzx_module = is_apple_pbzx.__module__
|
||||
if is_apple_pbzx(xar_file):
|
||||
printer(f'Extracting PBZX via {pbzx_module}...', padding + 4)
|
||||
pbzx_path = get_extract_path(xar_file)
|
||||
if apple_pbzx_extract(xar_file, pbzx_path, padding + 8) == 0:
|
||||
printer(f'Succesfull PBZX extraction via {pbzx_module}!', padding + 4)
|
||||
for pbzx_file in get_path_files(pbzx_path):
|
||||
if path_name(pbzx_file) == 'UpdateBundle.zip':
|
||||
if is_szip_supported(pbzx_file, padding + 8, args=['-tZIP'], check=True):
|
||||
zip_path = get_extract_path(pbzx_file)
|
||||
if szip_decompress(pbzx_file, zip_path, 'ZIP', padding + 8, args=['-tZIP'], check=True) == 0:
|
||||
for zip_file in get_path_files(zip_path):
|
||||
if path_name(path_parent(zip_file)) == 'MacEFI':
|
||||
printer(path_name(zip_file), padding + 12)
|
||||
if efi_split_rename(zip_file, extract_path, padding + 16) != 0:
|
||||
printer(f'Error: Could not split and rename {path_name(zip_file)}!', padding)
|
||||
return 10
|
||||
else:
|
||||
return 9
|
||||
else:
|
||||
return 8
|
||||
break # ZIP found, stop
|
||||
else:
|
||||
printer('Error: Could not find "UpdateBundle.zip" file!', padding)
|
||||
return 7
|
||||
else:
|
||||
printer(f'Error: Failed to extract PBZX file via {pbzx_module}!', padding)
|
||||
return 6
|
||||
else:
|
||||
printer(f'Error: Failed to detect file as PBZX via {pbzx_module}!', padding)
|
||||
return 5
|
||||
|
||||
break # Payload found, stop searching
|
||||
|
||||
if path_name(xar_file) == 'Scripts':
|
||||
if is_szip_supported(xar_file, padding + 4, args=['-tGZIP'], check=True):
|
||||
gzip_path = get_extract_path(xar_file)
|
||||
if szip_decompress(xar_file, gzip_path, 'GZIP', padding + 4, args=['-tGZIP'], check=True) == 0:
|
||||
for gzip_file in get_path_files(gzip_path):
|
||||
if is_szip_supported(gzip_file, padding + 8, args=['-tCPIO'], check=True):
|
||||
cpio_path = get_extract_path(gzip_file)
|
||||
if szip_decompress(gzip_file, cpio_path, 'CPIO', padding + 8, args=['-tCPIO'], check=True) == 0:
|
||||
for cpio_file in get_path_files(cpio_path):
|
||||
if path_name(path_parent(cpio_file)) == 'EFIPayloads':
|
||||
printer(path_name(cpio_file), padding + 12)
|
||||
if efi_split_rename(cpio_file, extract_path, padding + 16) != 0:
|
||||
printer(f'Error: Could not split and rename {path_name(cpio_file)}!', padding)
|
||||
return 15
|
||||
else:
|
||||
return 14
|
||||
else:
|
||||
return 13
|
||||
else:
|
||||
return 12
|
||||
else:
|
||||
return 11
|
||||
|
||||
break # Scripts found, stop searching
|
||||
else:
|
||||
printer('Error: Could not find "Payload" or "Scripts" file!', padding)
|
||||
return 4
|
||||
|
||||
del_dirs(xar_path) # Delete temporary/working XAR folder
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_apple_pkg, apple_pkg_extract).run_utility()
|
|
@ -1,65 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Award BIOS Extract
|
||||
Award BIOS Module Extractor
|
||||
Copyright (C) 2018-2019 Plato Mavropoulos
|
||||
http://www.onicos.com/staff/iz/formats/lzh.html
|
||||
https://ist.uwaterloo.ca/~schepers/formats/LHA.TXT
|
||||
https://sites.google.com/site/pinczakko/pinczakko-s-guide-to-award-bios-reverse-engineering
|
||||
"""
|
||||
|
||||
print('Award BIOS Module Extractor v1.2\n')
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
if len(sys.argv) >= 2 :
|
||||
# Drag & Drop or CLI
|
||||
awd_images = sys.argv[1:]
|
||||
else :
|
||||
# Folder path
|
||||
awd_images = []
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
print('\nWorking...')
|
||||
for root, dirs, files in os.walk(in_path):
|
||||
for name in files :
|
||||
awd_images.append(os.path.join(root, name))
|
||||
|
||||
pat_lzh = re.compile(br'\x2D\x6C((\x68(([\x30-\x37])|(\x64)))|(\x7A([\x34\x73])))\x2D') # All 11 LZH Method IDs (Award probably used LH0 and LH5 only)
|
||||
|
||||
# Create output folder
|
||||
extr_path = os.path.join(os.getcwd(), 'AWD_Extracted')
|
||||
if not os.path.exists(extr_path) : os.makedirs(extr_path)
|
||||
|
||||
for in_file in awd_images :
|
||||
file_path = os.path.abspath(in_file)
|
||||
file_name = os.path.basename(in_file)
|
||||
file_dir = os.path.dirname(file_path)
|
||||
file_ext = os.path.splitext(file_path)[1]
|
||||
match_lzh_list = []
|
||||
|
||||
print('\nFile: %s%s' % (file_name, file_ext))
|
||||
|
||||
with open(in_file, 'rb') as awd_img : buffer = awd_img.read()
|
||||
|
||||
match_lzh_list += pat_lzh.finditer(buffer) # Detect LZH patterns
|
||||
|
||||
for match_lzh in match_lzh_list :
|
||||
hdr_size = buffer[match_lzh.start() - 0x2] # From LZH Tag (0x2+)
|
||||
comp_size = int.from_bytes(buffer[match_lzh.end():match_lzh.end() + 0x4], 'little') # From LZH Header end
|
||||
mod_data = buffer[match_lzh.start() - 0x2:match_lzh.start() + hdr_size + comp_size]
|
||||
|
||||
with open('mod_temp.bin', 'wb') as lzh_img : lzh_img.write(mod_data)
|
||||
|
||||
try :
|
||||
decomp = subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o%s' % os.path.join(extr_path, file_name), 'mod_temp.bin']) # 7-Zip
|
||||
except :
|
||||
print('Error: Could not decompress LZH image at %s!' % file_name)
|
||||
print(' Make sure that "7z" executable exists!\n')
|
||||
|
||||
os.remove('mod_temp.bin') # Remove temporary LZH image
|
||||
|
||||
input('\nDone!')
|
74
Award_BIOS_Extract.py
Normal file
74
Award_BIOS_Extract.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Award BIOS Extract
|
||||
Award BIOS Module Extractor
|
||||
Copyright (C) 2018-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'Award BIOS Module Extractor v2.0_a5'
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_szip import szip_decompress
|
||||
from common.path_ops import make_dirs, safe_name, get_extract_path
|
||||
from common.patterns import PAT_AWARD_LZH
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is Award BIOS image
|
||||
def is_award_bios(in_file):
|
||||
in_buffer = file_to_bytes(in_file)
|
||||
|
||||
return bool(PAT_AWARD_LZH.search(in_buffer))
|
||||
|
||||
# Parse & Extract Award BIOS image
|
||||
def award_bios_extract(input_file, extract_path, padding=0):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
for lzh_match in PAT_AWARD_LZH.finditer(input_buffer):
|
||||
lzh_type = lzh_match.group(0).decode('utf-8')
|
||||
lzh_text = f'LZH-{lzh_type.strip("-").upper()}'
|
||||
|
||||
lzh_bgn = lzh_match.start()
|
||||
|
||||
mod_bgn = lzh_bgn - 0x2
|
||||
hdr_len = input_buffer[mod_bgn]
|
||||
mod_len = int.from_bytes(input_buffer[mod_bgn + 0x7:mod_bgn + 0xB], 'little')
|
||||
mod_end = lzh_bgn + hdr_len + mod_len
|
||||
mod_bin = input_buffer[mod_bgn:mod_end]
|
||||
|
||||
tag_bgn = mod_bgn + 0x16
|
||||
tag_end = tag_bgn + input_buffer[mod_bgn + 0x15]
|
||||
tag_txt = input_buffer[tag_bgn:tag_end].decode('utf-8','ignore')
|
||||
|
||||
printer(f'{lzh_text} > {tag_txt} [0x{mod_bgn:06X}-0x{mod_end:06X}]', padding)
|
||||
|
||||
mod_path = os.path.join(extract_path, safe_name(tag_txt))
|
||||
lzh_path = f'{mod_path}.lzh'
|
||||
|
||||
with open(lzh_path, 'wb') as lzh_file:
|
||||
lzh_file.write(mod_bin) # Store LZH archive
|
||||
|
||||
# 7-Zip returns critical exit code (i.e. 2) if LZH CRC is wrong, do not check result
|
||||
szip_decompress(lzh_path, extract_path, lzh_text, padding + 4, check=False)
|
||||
|
||||
# Manually check if 7-Zip extracted LZH due to its CRC check issue
|
||||
if os.path.isfile(mod_path):
|
||||
os.remove(lzh_path) # Successful extraction, delete LZH archive
|
||||
|
||||
# Extract any nested LZH archives
|
||||
if is_award_bios(mod_path):
|
||||
# Recursively extract nested Award BIOS modules
|
||||
award_bios_extract(mod_path, get_extract_path(mod_path), padding + 8)
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_award_bios, award_bios_extract).run_utility()
|
|
@ -1,943 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Dell PFS Extract
|
||||
Dell PFS BIOS Extractor
|
||||
Copyright (C) 2019-2021 Plato Mavropoulos
|
||||
Inspired from https://github.com/LongSoft/PFSExtractor-RS by Nikolaj Schlej
|
||||
"""
|
||||
|
||||
title = 'Dell PFS BIOS Extractor v4.9'
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import zlib
|
||||
import lzma
|
||||
import shutil
|
||||
import struct
|
||||
import ctypes
|
||||
import argparse
|
||||
import traceback
|
||||
|
||||
# Set ctypes Structure types
|
||||
char = ctypes.c_char
|
||||
uint8_t = ctypes.c_ubyte
|
||||
uint16_t = ctypes.c_ushort
|
||||
uint32_t = ctypes.c_uint
|
||||
uint64_t = ctypes.c_uint64
|
||||
|
||||
class PFS_HDR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Tag', char*8), # 0x00
|
||||
('HeaderVersion', uint32_t), # 0x08
|
||||
('PayloadSize', uint32_t), # 0x0C
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def pfs_print(self) :
|
||||
print('\nPFS Header:\n')
|
||||
print('Tag : %s' % self.Tag.decode('utf-8'))
|
||||
print('HeaderVersion : %d' % self.HeaderVersion)
|
||||
print('PayloadSize : 0x%X' % self.PayloadSize)
|
||||
|
||||
class PFS_FTR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('PayloadSize', uint32_t), # 0x00
|
||||
('Checksum', uint32_t), # 0x04 ~CRC32 w/ Vector 0
|
||||
('Tag', char*8), # 0x08
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def pfs_print(self) :
|
||||
print('\nPFS Footer:\n')
|
||||
print('PayloadSize : 0x%X' % self.PayloadSize)
|
||||
print('Checksum : 0x%0.8X' % self.Checksum)
|
||||
print('Tag : %s' % self.Tag.decode('utf-8'))
|
||||
|
||||
class PFS_ENTRY(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('GUID', uint32_t*4), # 0x00 Little Endian
|
||||
('HeaderVersion', uint32_t), # 0x10 1
|
||||
('VersionType', uint8_t*4), # 0x14
|
||||
('Version', uint16_t*4), # 0x18
|
||||
('Reserved', uint64_t), # 0x20
|
||||
('DataSize', uint32_t), # 0x28
|
||||
('DataSigSize', uint32_t), # 0x2C
|
||||
('DataMetSize', uint32_t), # 0x30
|
||||
('DataMetSigSize', uint32_t), # 0x34
|
||||
('Unknown', uint32_t*4), # 0x38
|
||||
# 0x48
|
||||
]
|
||||
|
||||
def pfs_print(self) :
|
||||
GUID = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.GUID))
|
||||
VersionType = ''.join('%0.2X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.VersionType))
|
||||
Version = ''.join('%0.4X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.Version))
|
||||
Unknown = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.Unknown))
|
||||
|
||||
print('\nPFS Entry:\n')
|
||||
print('GUID : %s' % GUID)
|
||||
print('HeaderVersion : %d' % self.HeaderVersion)
|
||||
print('VersionType : %s' % VersionType)
|
||||
print('Version : %s' % Version)
|
||||
print('Reserved : 0x%X' % self.Reserved)
|
||||
print('DataSize : 0x%X' % self.DataSize)
|
||||
print('DataSigSize : 0x%X' % self.DataSigSize)
|
||||
print('DataMetSize : 0x%X' % self.DataMetSize)
|
||||
print('DataMetSigSize : 0x%X' % self.DataMetSigSize)
|
||||
print('Unknown : %s' % Unknown)
|
||||
|
||||
class PFS_ENTRY_R2(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('GUID', uint32_t*4), # 0x00 Little Endian
|
||||
('HeaderVersion', uint32_t), # 0x10 2
|
||||
('VersionType', uint8_t*4), # 0x14
|
||||
('Version', uint16_t*4), # 0x18
|
||||
('Reserved', uint64_t), # 0x20
|
||||
('DataSize', uint32_t), # 0x28
|
||||
('DataSigSize', uint32_t), # 0x2C
|
||||
('DataMetSize', uint32_t), # 0x30
|
||||
('DataMetSigSize', uint32_t), # 0x34
|
||||
('Unknown', uint32_t*8), # 0x38
|
||||
# 0x58
|
||||
]
|
||||
|
||||
def pfs_print(self) :
|
||||
GUID = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.GUID))
|
||||
VersionType = ''.join('%0.2X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.VersionType))
|
||||
Version = ''.join('%0.4X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.Version))
|
||||
Unknown = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.Unknown))
|
||||
|
||||
print('\nPFS Entry:\n')
|
||||
print('GUID : %s' % GUID)
|
||||
print('HeaderVersion : %d' % self.HeaderVersion)
|
||||
print('VersionType : %s' % VersionType)
|
||||
print('Version : %s' % Version)
|
||||
print('Reserved : 0x%X' % self.Reserved)
|
||||
print('DataSize : 0x%X' % self.DataSize)
|
||||
print('DataSigSize : 0x%X' % self.DataSigSize)
|
||||
print('DataMetSize : 0x%X' % self.DataMetSize)
|
||||
print('DataMetSigSize : 0x%X' % self.DataMetSigSize)
|
||||
print('Unknown : %s' % Unknown)
|
||||
|
||||
class PFS_INFO(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('HeaderVersion', uint32_t), # 0x00
|
||||
('GUID', uint32_t*4), # 0x04 Little Endian
|
||||
('Version', uint16_t*4), # 0x14
|
||||
('VersionType', uint8_t*4), # 0x1C
|
||||
('CharacterCount', uint16_t), # 0x20 UTF-16 2-byte Characters
|
||||
# 0x22
|
||||
]
|
||||
|
||||
def pfs_print(self) :
|
||||
GUID = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.GUID))
|
||||
Version = ''.join('%0.4X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.Version))
|
||||
VersionType = ''.join('%0.2X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.VersionType))
|
||||
|
||||
print('\nPFS Information:\n')
|
||||
print('HeaderVersion : %d' % self.HeaderVersion)
|
||||
print('GUID : %s' % GUID)
|
||||
print('Version : %s' % Version)
|
||||
print('VersionType : %s' % VersionType)
|
||||
print('CharacterCount : %d' % (self.CharacterCount * 2))
|
||||
|
||||
class METADATA_INFO(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('ModelIDs', char*501), # 0x000
|
||||
('FileName', char*100), # 0x1F5
|
||||
('FileVersion', char*33), # 0x259
|
||||
('Date', char*33), # 0x27A
|
||||
('Brand', char*80), # 0x29B
|
||||
('ModelFile', char*80), # 0x2EB
|
||||
('ModelName', char*100), # 0x33B
|
||||
('ModelVersion', char*33), # 0x39F
|
||||
# 0x3C0
|
||||
]
|
||||
|
||||
def pfs_print(self) :
|
||||
print('\nMetadata Information:\n')
|
||||
print('Model IDs : %s' % self.ModelIDs.decode('utf-8').strip(',END'))
|
||||
print('File Name : %s' % self.FileName.decode('utf-8'))
|
||||
print('File Version : %s' % self.FileVersion.decode('utf-8'))
|
||||
print('Date : %s' % self.Date.decode('utf-8'))
|
||||
print('Brand : %s' % self.Brand.decode('utf-8'))
|
||||
print('Model File : %s' % self.ModelFile.decode('utf-8'))
|
||||
print('Model Name : %s' % self.ModelName.decode('utf-8'))
|
||||
print('Model Version : %s' % self.ModelVersion.decode('utf-8'))
|
||||
|
||||
def pfs_write(self) :
|
||||
return '%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s' % (self.ModelIDs.decode('utf-8').strip(',END'), self.FileName.decode('utf-8'),
|
||||
self.FileVersion.decode('utf-8'), self.Date.decode('utf-8'), self.Brand.decode('utf-8'), self.ModelFile.decode('utf-8'),
|
||||
self.ModelName.decode('utf-8'), self.ModelVersion.decode('utf-8'))
|
||||
|
||||
class CHUNK_INFO_HDR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Unknown0', uint32_t), # 0x00
|
||||
('DellTag', char*16), # 0x04
|
||||
('Unknown1', uint32_t), # 0x14
|
||||
('Unknown2', uint32_t), # 0x18
|
||||
('FlagsSize', uint32_t), # 0x1C
|
||||
('ChunkSize', uint32_t), # 0x20
|
||||
('Unknown3', uint16_t), # 0x24
|
||||
('EndTag', char*2), # 0x26
|
||||
# 0x28
|
||||
]
|
||||
|
||||
def pfs_print(self) :
|
||||
print('\nChunk Header:\n')
|
||||
print('Unknown 0 : 0x%X' % self.Unknown0)
|
||||
print('Dell Tag : %s' % self.DellTag.replace(b'\x00',b'\x20').decode('utf-8','ignore').strip())
|
||||
print('Unknown 1 : 0x%X' % self.Unknown1)
|
||||
print('Unknown 2 : 0x%X' % self.Unknown2)
|
||||
print('Flags Size : 0x%X' % self.FlagsSize)
|
||||
print('Chunk Size : 0x%X' % self.ChunkSize)
|
||||
print('Unknown 3 : 0x%X' % self.Unknown3)
|
||||
print('End Tag : %s' % self.EndTag.decode('utf-8','ignore'))
|
||||
|
||||
class CHUNK_INFO_FTR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('EndMarker', uint64_t), # 0x00
|
||||
# 0x08
|
||||
]
|
||||
|
||||
def pfs_print(self) :
|
||||
print('\nChunk Footer:\n')
|
||||
print('End Marker : 0x%X' % self.EndMarker)
|
||||
|
||||
# Dell PFS.HDR. Extractor
|
||||
def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
||||
# Get PFS Header Structure values
|
||||
pfs_hdr = get_struct(buffer, 0, PFS_HDR)
|
||||
|
||||
# Validate that a PFS Header was parsed
|
||||
if pfs_hdr.Tag != b'PFS.HDR.' :
|
||||
print('\n Error: PFS Header could not be found!')
|
||||
return # Critical error, abort
|
||||
|
||||
# Validate that a known PFS Header Version was encountered
|
||||
if pfs_hdr.HeaderVersion not in (1,2) :
|
||||
print('\n Error: Unknown PFS Header Version %d!' % pfs_hdr.HeaderVersion)
|
||||
|
||||
# Get PFS Footer Data after PFS Header Payload
|
||||
footer = buffer[pfs_header_size + pfs_hdr.PayloadSize:pfs_header_size + pfs_hdr.PayloadSize + pfs_footer_size]
|
||||
|
||||
# Get PFS Footer Structure values
|
||||
pfs_ftr = pfs_hdr = get_struct(footer, 0, PFS_FTR)
|
||||
|
||||
# Validate that a PFS Footer was parsed
|
||||
if pfs_ftr.Tag != b'PFS.FTR.' :
|
||||
print('\n Error: PFS Footer could not be found!')
|
||||
|
||||
# Validate that the PFS Header Payload Size matches the one at the PFS Footer
|
||||
if pfs_hdr.PayloadSize != pfs_ftr.PayloadSize :
|
||||
print('\n Error: PFS Header & Footer Payload Size mismatch!')
|
||||
|
||||
# Get PFS Payload Data
|
||||
payload = buffer[pfs_header_size:pfs_header_size + pfs_hdr.PayloadSize]
|
||||
|
||||
# Calculate the PFS Payload Data CRC-32 w/ Vector 0 Checksum
|
||||
footer_checksum = ~zlib.crc32(payload, 0) & 0xFFFFFFFF
|
||||
|
||||
# Validate PFS Payload Data Checksum via the PFS Footer
|
||||
if pfs_ftr.Checksum != footer_checksum :
|
||||
print('\n Error: Invalid PFS Footer Payload Checksum!')
|
||||
|
||||
# Parse all PFS Payload Entries/Components
|
||||
entry_index = 1 # Index number of each PFS Entry
|
||||
entry_start = 0 # Increasing PFS Entry starting offset
|
||||
entries_all = [] # Storage for each PFS Entry details
|
||||
pfs_info = [] # Buffer for PFS Information Entry Data
|
||||
pfs_entry_struct, pfs_entry_size = get_pfs_entry(payload, entry_start) # Get PFS Entry Info
|
||||
while len(payload[entry_start:entry_start + pfs_entry_size]) == pfs_entry_size :
|
||||
# Get PFS Entry Structure values
|
||||
pfs_entry = get_struct(payload, entry_start, pfs_entry_struct)
|
||||
|
||||
# Validate that a known PFS Entry Header Version was encountered
|
||||
if pfs_entry.HeaderVersion not in (1,2) :
|
||||
print('\n Error: Unknown PFS Entry Header Version %d!' % pfs_entry.HeaderVersion)
|
||||
|
||||
# Validate that the PFS Entry Reserved field is empty
|
||||
if pfs_entry.Reserved != 0 :
|
||||
print('\n Error: Detected non-empty PFS Entry Reserved field!')
|
||||
|
||||
# Get PFS Entry Version string via "Version" and "VersionType" fields
|
||||
entry_version = get_version(pfs_entry.Version, pfs_entry.VersionType)
|
||||
|
||||
# Get PFS Entry GUID in Big Endian format
|
||||
entry_guid = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(pfs_entry.GUID))
|
||||
|
||||
# PFS Entry Data starts after the PFS Entry Structure
|
||||
entry_data_start = entry_start + pfs_entry_size
|
||||
entry_data_end = entry_data_start + pfs_entry.DataSize
|
||||
|
||||
# PFS Entry Data Signature starts after PFS Entry Data
|
||||
entry_data_sig_start = entry_data_end
|
||||
entry_data_sig_end = entry_data_sig_start + pfs_entry.DataSigSize
|
||||
|
||||
# PFS Entry Metadata starts after PFS Entry Data Signature
|
||||
entry_met_start = entry_data_sig_end
|
||||
entry_met_end = entry_met_start + pfs_entry.DataMetSize
|
||||
|
||||
# PFS Entry Metadata Signature starts after PFS Entry Metadata
|
||||
entry_met_sig_start = entry_met_end
|
||||
entry_met_sig_end = entry_met_sig_start + pfs_entry.DataMetSigSize
|
||||
|
||||
entry_data = payload[entry_data_start:entry_data_end] # Store PFS Entry Data
|
||||
entry_data_sig = payload[entry_data_sig_start:entry_data_sig_end] # Store PFS Entry Data Signature
|
||||
entry_met = payload[entry_met_start:entry_met_end] # Store PFS Entry Metadata
|
||||
entry_met_sig = payload[entry_met_sig_start:entry_met_sig_end] # Store PFS Entry Metadata Signature
|
||||
|
||||
entry_type = 'OTHER' # Adjusted later if PFS Entry is Zlib, Chunks, PFS Info, Model Info
|
||||
|
||||
# Get PFS Information from the PFS Entry with GUID E0717CE3A9BB25824B9F0DC8FD041960 or B033CB16EC9B45A14055F80E4D583FD3
|
||||
if entry_guid in ['E0717CE3A9BB25824B9F0DC8FD041960','B033CB16EC9B45A14055F80E4D583FD3'] :
|
||||
pfs_info = entry_data
|
||||
entry_type = 'PFS_INFO'
|
||||
|
||||
# Get Model Information from the PFS Entry with GUID 6F1D619A22A6CB924FD4DA68233AE3FB
|
||||
elif entry_guid == '6F1D619A22A6CB924FD4DA68233AE3FB' :
|
||||
entry_type = 'MODEL_INFO'
|
||||
|
||||
# Get Nested PFS from the PFS Entry with GUID 900FAE60437F3AB14055F456AC9FDA84
|
||||
elif entry_guid == '900FAE60437F3AB14055F456AC9FDA84' :
|
||||
entry_type = 'NESTED_PFS' # Nested PFS are usually zlib-compressed so it might change to 'ZLIB' later
|
||||
|
||||
# Store all relevant PFS Entry details
|
||||
entries_all.append([entry_index, entry_guid, entry_version, entry_type, entry_data, entry_data_sig, entry_met, entry_met_sig])
|
||||
|
||||
entry_index += 1 # Increase PFS Entry Index number for user-friendly output and name duplicates
|
||||
entry_start = entry_met_sig_end # Next PFS Entry starts after PFS Entry Metadata Signature
|
||||
|
||||
# Parse all PFS Information Entries/Descriptors
|
||||
info_start = 0 # Increasing PFS Information Entry starting offset
|
||||
info_all = [] # Storage for each PFS Information Entry details
|
||||
while len(pfs_info[info_start:info_start + pfs_info_size]) == pfs_info_size :
|
||||
# Get PFS Information Structure values
|
||||
entry_info = get_struct(pfs_info, info_start, PFS_INFO)
|
||||
|
||||
# Validate that a known PFS Information Header Version was encountered
|
||||
if entry_info.HeaderVersion != 1 :
|
||||
print('\n Error: Unknown PFS Information Header Version %d!' % entry_info.HeaderVersion)
|
||||
break # Skip PFS Information Entries/Descriptors in case of assertion error
|
||||
|
||||
# Get PFS Information GUID in Big Endian format to match each Info to the equivalent stored PFS Entry details
|
||||
entry_guid = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(entry_info.GUID))
|
||||
|
||||
# The PFS Information Structure is not complete by itself. The size of the last field (Entry Name) is determined from CharacterCount
|
||||
# multiplied by 2 due to usage of UTF-16 2-byte Characters. Any Entry Name leading and/or trailing space/null characters are stripped
|
||||
entry_name = pfs_info[info_start + pfs_info_size:info_start + pfs_info_size + entry_info.CharacterCount * 2].decode('utf-16').strip()
|
||||
|
||||
# Get PFS Information Version string via "Version" and "VersionType" fields
|
||||
# PFS Information Version string must be preferred over PFS Entry's Version
|
||||
entry_version = get_version(entry_info.Version, entry_info.VersionType)
|
||||
|
||||
# Store all relevant PFS Information details
|
||||
info_all.append([entry_guid, entry_name, entry_version])
|
||||
|
||||
# The next PFS Information starts after the calculated Entry Name size
|
||||
# Two space/null characters seem to always exist after the Entry Name
|
||||
info_start += (pfs_info_size + entry_info.CharacterCount * 2 + 0x2)
|
||||
|
||||
# Parse Nested PFS Metadata when its PFS Information Entry is missing
|
||||
for index in range(len(entries_all)) :
|
||||
if entries_all[index][3] == 'NESTED_PFS' and not pfs_info :
|
||||
entry_guid = entries_all[index][1] # Nested PFS Entry GUID in Big Endian format
|
||||
entry_metadata = entries_all[index][6] # Use Metadata as PFS Information Entry
|
||||
|
||||
# When PFS Information Entry exists, Nested PFS Metadata contains only Model IDs
|
||||
# When it's missing, the Metadata structure is large and contains equivalent info
|
||||
if len(entry_metadata) >= met_info_size :
|
||||
# Get Nested PFS Metadata Structure values
|
||||
entry_info = get_struct(entry_metadata, 0, METADATA_INFO)
|
||||
|
||||
# As Nested PFS Entry Name, we'll use the actual PFS File Name
|
||||
entry_name = entry_info.FileName.decode('utf-8').strip('.exe')
|
||||
|
||||
# As Nested PFS Entry Version, we'll use the actual PFS File Version
|
||||
entry_version = entry_info.FileVersion.decode('utf-8')
|
||||
|
||||
# Store all relevant Nested PFS Metadata/Information details
|
||||
info_all.append([entry_guid, entry_name, entry_version])
|
||||
|
||||
# Re-set Nested PFS Entry Version from Metadata
|
||||
entries_all[index][2] = entry_version
|
||||
|
||||
# Parse each PFS Entry Data for special types (zlib or Chunks)
|
||||
for index in range(len(entries_all)) :
|
||||
entry_data = entries_all[index][4] # Get PFS Entry Data
|
||||
entry_type = entries_all[index][3] # Get PFS Entry Type
|
||||
|
||||
# Very small PFS Entry Data cannot be of special type
|
||||
if len(entry_data) < pfs_header_size : continue
|
||||
|
||||
# Get possible PFS Header Structure values
|
||||
entry_hdr = get_struct(entry_data, 0, PFS_HDR)
|
||||
|
||||
# Check for possibly zlib-compressed (0x4 Compressed Size + Compressed Data) PFS Entry Data
|
||||
# The 0xE sized zlib "BIOS" section pattern (0xAA type) should be found after the Compressed Size
|
||||
zlib_bios_hdr_match = zlib_bios_header.search(entry_data)
|
||||
|
||||
# Check if a sub PFS Header with Payload has Chunked Entries
|
||||
pfs_entry_struct, pfs_entry_size = get_pfs_entry(entry_data, pfs_header_size) # Get PFS Entry Info
|
||||
chunk_info_hdr_off = pfs_header_size + pfs_entry_size # Chunk Info Header starts after PFS Header & PFS Entry
|
||||
if len(entry_data[chunk_info_hdr_off:chunk_info_hdr_off + chunk_info_header_size]) == chunk_info_header_size :
|
||||
chunk_info_hdr = get_struct(entry_data, chunk_info_hdr_off, CHUNK_INFO_HDR) # Get Chunk Info Header
|
||||
chunk_dell_tag = chunk_info_hdr.DellTag.replace(b'\x00',b'\x20').decode('utf-8','ignore').strip() # Chunk Dell Tag
|
||||
chunk_flags_size = chunk_info_hdr.FlagsSize # Size of Chunk Info Flags
|
||||
chunk_payload_size = chunk_info_hdr.ChunkSize # Size of Chunk Raw Data
|
||||
else :
|
||||
chunk_dell_tag = 'None' # Payload does not have Chunked Entries
|
||||
chunk_flags_size = 0
|
||||
|
||||
if entry_hdr.Tag == b'PFS.HDR.' and chunk_dell_tag.startswith('Dell') :
|
||||
# Validate that a known sub PFS Header Version was encountered
|
||||
if entry_hdr.HeaderVersion not in (1,2) :
|
||||
print('\n Error: Unknown sub PFS Entry Header Version %d!' % entry_hdr.HeaderVersion)
|
||||
|
||||
# Get sub PFS Footer Data after sub PFS Header Payload
|
||||
chunks_footer = entry_data[pfs_header_size + entry_hdr.PayloadSize:pfs_header_size + entry_hdr.PayloadSize + pfs_footer_size]
|
||||
|
||||
# Get sub PFS Footer Structure values
|
||||
entry_ftr = get_struct(chunks_footer, 0, PFS_FTR)
|
||||
|
||||
# Validate that a sub PFS Footer was parsed
|
||||
if entry_ftr.Tag != b'PFS.FTR.' :
|
||||
print('\n Error: Sub PFS Entry Footer could not be found!')
|
||||
|
||||
# Validate that the sub PFS Header Payload Size matches the one at the sub PFS Footer
|
||||
if entry_hdr.PayloadSize != entry_ftr.PayloadSize :
|
||||
print('\n Error: Sub PFS Entry Header & Footer Payload Size mismatch!')
|
||||
|
||||
# Get sub PFS Entry Structure values
|
||||
pfs_chunk_entry = get_struct(entry_data, pfs_header_size, pfs_entry_struct)
|
||||
|
||||
# Validate that a known sub PFS Entry Header Version was encountered
|
||||
if pfs_chunk_entry.HeaderVersion not in (1,2) :
|
||||
print('\n Error: Unknown sub PFS Chunk Entry Header Version %d!' % pfs_chunk_entry.HeaderVersion)
|
||||
|
||||
# Validate that the sub PFS Entry Reserved field is empty
|
||||
if pfs_chunk_entry.Reserved != 0 :
|
||||
print('\n Error: Detected non-empty sub PFS Chunk Entry Reserved field!')
|
||||
|
||||
# Validate that the Chunk Extra Info Footer End Marker is proper
|
||||
chunk_info_ftr_off = chunk_info_hdr_off + chunk_info_header_size + chunk_flags_size
|
||||
chunk_info_ftr = get_struct(entry_data, chunk_info_ftr_off, CHUNK_INFO_FTR)
|
||||
if chunk_info_ftr.EndMarker != 0xFF :
|
||||
print('\n Error: Unknown sub PFS Chunk Info Footer End Marker 0x%X!' % chunk_info_ftr.EndMarker)
|
||||
|
||||
# Get sub PFS Payload Data
|
||||
chunks_payload = entry_data[pfs_header_size:pfs_header_size + entry_hdr.PayloadSize]
|
||||
|
||||
# Calculate the sub PFS Payload Data CRC-32 w/ Vector 0 Checksum
|
||||
chunks_footer_checksum = ~zlib.crc32(chunks_payload, 0) & 0xFFFFFFFF
|
||||
|
||||
# Validate sub PFS Payload Data Checksum via the sub PFS Footer
|
||||
if entry_ftr.Checksum != chunks_footer_checksum :
|
||||
print('\n Error: Invalid sub PFS Entry Footer Payload Checksum!')
|
||||
|
||||
# Parse all sub PFS Payload Entries/Chunks
|
||||
chunk_data_all = [] # Storage for each sub PFS Entry/Chunk Order + Data
|
||||
chunk_entry_start = 0 # Increasing sub PFS Entry/Chunk starting offset
|
||||
pfs_entry_struct, pfs_entry_size = get_pfs_entry(chunks_payload, chunk_entry_start) # Get PFS Entry Info (initial)
|
||||
while len(chunks_payload[chunk_entry_start:chunk_entry_start + pfs_entry_size]) == pfs_entry_size :
|
||||
# Get Next PFS Entry Info, skip at the first loop iteration because we already have it
|
||||
if chunk_entry_start : pfs_entry_struct, pfs_entry_size = get_pfs_entry(chunks_payload, chunk_entry_start)
|
||||
|
||||
# Get sub PFS Entry Structure values
|
||||
pfs_chunk_entry = get_struct(chunks_payload, chunk_entry_start, pfs_entry_struct)
|
||||
|
||||
# Validate that a known sub PFS Entry Header Version was encountered
|
||||
if pfs_chunk_entry.HeaderVersion not in (1,2) :
|
||||
print('\n Error: Unknown sub PFS Chunk Entry Header Version %d!' % pfs_chunk_entry.HeaderVersion)
|
||||
|
||||
# Validate that the sub PFS Entry Reserved field is empty
|
||||
if pfs_chunk_entry.Reserved != 0 :
|
||||
print('\n Error: Detected non-empty sub PFS Chunk Entry Reserved field!')
|
||||
|
||||
# Each sub PFS Payload Entry/Chunk includes some Extra Chunk Data/Information at the beginning
|
||||
# The Chunk Extra Info consists of a Header (0x28), variable sized Flags & End Marker Footer (0x8)
|
||||
# We need the Chunk Extra Info size so that its Data can be removed from the final Chunk Raw Data
|
||||
chunk_info_hdr_off = chunk_entry_start + pfs_entry_size # Chunk Info Header starts after PFS Entry
|
||||
chunk_info_hdr = get_struct(chunks_payload, chunk_info_hdr_off, CHUNK_INFO_HDR) # Get Chunk Info Header
|
||||
chunk_dell_tag = chunk_info_hdr.DellTag.replace(b'\x00',b'\x20').decode('utf-8','ignore').strip() # Chunk Dell Tag
|
||||
chunk_flags_size = chunk_info_hdr.FlagsSize # Size of Chunk Info Flags
|
||||
chunk_payload_size = chunk_info_hdr.ChunkSize # Size of Chunk Raw Data
|
||||
chunk_info_size = pfs_chunk_entry.DataSize - chunk_payload_size # Size of Chunk Info
|
||||
|
||||
# Validate that the Chunk Extra Info Header Dell Tag is proper
|
||||
if not chunk_dell_tag.startswith('Dell') :
|
||||
print('\n Error: Unknown sub PFS Chunk Info Header Dell Tag %s!' % chunk_dell_tag)
|
||||
|
||||
# Validate that the Chunk Extra Info Footer End Marker is proper
|
||||
chunk_info_ftr_off = chunk_info_hdr_off + chunk_info_header_size + chunk_flags_size
|
||||
chunk_info_ftr = get_struct(chunks_payload, chunk_info_ftr_off, CHUNK_INFO_FTR)
|
||||
if chunk_info_ftr.EndMarker != 0xFF :
|
||||
print('\n Error: Unknown sub PFS Chunk Info Footer End Marker 0x%X!' % chunk_info_ftr.EndMarker)
|
||||
|
||||
# The sub PFS Payload Entries/Chunks are not in proper order by default
|
||||
# We can get the Chunk Order Number from Chunk Extra Info > Flags byte 0x16
|
||||
chunk_entry_number = chunks_payload[chunk_info_hdr_off + chunk_info_header_size + 0x16] # Get Chunk Order Number
|
||||
|
||||
# Get sub PFS Entry Version string via "Version" and "VersionType" fields
|
||||
# This is not useful as the Version of each Chunk does not matter at all
|
||||
#chunk_entry_version = get_version(pfs_chunk_entry.Version, pfs_chunk_entry.VersionType)
|
||||
|
||||
# Sub PFS Entry Data starts after the sub PFS Entry Structure
|
||||
chunk_entry_data_start = chunk_entry_start + pfs_entry_size
|
||||
chunk_entry_data_end = chunk_entry_data_start + pfs_chunk_entry.DataSize
|
||||
|
||||
# Sub PFS Entry Data Signature starts after sub PFS Entry Data
|
||||
chunk_entry_data_sig_start = chunk_entry_data_end
|
||||
chunk_entry_data_sig_end = chunk_entry_data_sig_start + pfs_chunk_entry.DataSigSize
|
||||
|
||||
# Sub PFS Entry Metadata starts after sub PFS Entry Data Signature
|
||||
chunk_entry_met_start = chunk_entry_data_sig_end
|
||||
chunk_entry_met_end = chunk_entry_met_start + pfs_chunk_entry.DataMetSize
|
||||
|
||||
# Sub PFS Entry Metadata Signature starts after sub PFS Entry Metadata
|
||||
chunk_entry_met_sig_start = chunk_entry_met_end
|
||||
chunk_entry_met_sig_end = chunk_entry_met_sig_start + pfs_chunk_entry.DataMetSigSize
|
||||
|
||||
chunk_entry_data = chunks_payload[chunk_entry_data_start:chunk_entry_data_end] # Store sub PFS Entry Data
|
||||
#chunk_entry_data_sig = chunks_payload[chunk_entry_data_sig_start:chunk_entry_data_sig_end] # Store sub PFS Entry Data Signature
|
||||
#chunk_entry_met = chunks_payload[chunk_entry_met_start:chunk_entry_met_end] # Store sub PFS Entry Metadata
|
||||
#chunk_entry_met_sig = chunks_payload[chunk_entry_met_sig_start:chunk_entry_met_sig_end] # Store sub PFS Entry Metadata Signature
|
||||
|
||||
# Store each sub PFS Entry/Chunk Extra Info Size, Order Number & Raw Data
|
||||
chunk_data_all.append((chunk_entry_number, chunk_entry_data, chunk_info_size))
|
||||
|
||||
chunk_entry_start = chunk_entry_met_sig_end # Next sub PFS Entry/Chunk starts after sub PFS Entry Metadata Signature
|
||||
|
||||
chunk_data_all.sort() # Sort all sub PFS Entries/Chunks based on their Order Number
|
||||
|
||||
entry_data = b'' # Initialize new PFS Entry Data
|
||||
for chunk in chunk_data_all : # Merge all sub PFS Chunks into the final new PFS Entry Data
|
||||
entry_data += chunk[1][chunk[2]:] # Skip the sub PFS Chunk Extra Info when merging
|
||||
|
||||
entry_type = 'CHUNKS' # Re-set PFS Entry Type from OTHER to CHUNKS, in case such info is needed afterwards
|
||||
|
||||
# Check if the PFS Entry Data are zlib-compressed in a "BIOS" pattern with 0xAA type.
|
||||
# A zlib-compressed PFS Entry Data contains a full PFS structure, like the main Dell PFS BIOS image.
|
||||
elif zlib_bios_hdr_match :
|
||||
# Store the compressed zlib stream start offset
|
||||
compressed_start = zlib_bios_hdr_match.start() + 0xC
|
||||
|
||||
# Store the "BIOS" section header start offset
|
||||
header_start = zlib_bios_hdr_match.start() - 0x4
|
||||
|
||||
# Store the "BIOS" section header contents (16 bytes)
|
||||
header_data = entry_data[header_start:compressed_start]
|
||||
|
||||
# Check if the "BIOS" section header Checksum XOR 8 is valid
|
||||
if chk_xor_8(header_data[:0xF], 0) != header_data[0xF] :
|
||||
print('\n Error: Dell sub-PFS BIOS section data is corrupted!')
|
||||
|
||||
# Store the compressed zlib stream size from the header contents
|
||||
compressed_size_hdr = int.from_bytes(header_data[:0x4], 'little')
|
||||
|
||||
# Store the compressed zlib stream end offset
|
||||
compressed_end = compressed_start + compressed_size_hdr
|
||||
|
||||
# Store the compressed zlib stream contents
|
||||
compressed_data = entry_data[compressed_start:compressed_end]
|
||||
|
||||
# Check if the compressed zlib stream is complete, based on header
|
||||
if len(compressed_data) != compressed_size_hdr :
|
||||
print('\n Error: Dell sub-PFS BIOS section data is corrupted!')
|
||||
|
||||
# Store the "BIOS" section footer contents (16 bytes)
|
||||
footer_data = entry_data[compressed_end:compressed_end + 0x10]
|
||||
|
||||
# Check if the "BIOS" section footer Checksum XOR 8 is valid
|
||||
if chk_xor_8(footer_data[:0xF], 0) != footer_data[0xF] :
|
||||
print('\n Error: Dell sub-PFS BIOS section data is corrupted!')
|
||||
|
||||
# Search input PFS Entry Data for zlib "BIOS" section footer
|
||||
zlib_bios_ftr_match = zlib_bios_footer.search(footer_data)
|
||||
|
||||
# Check if "BIOS" section footer was found in the PFS Entry Data
|
||||
if not zlib_bios_ftr_match :
|
||||
print('\n Error: Dell sub-PFS BIOS section data is corrupted!')
|
||||
|
||||
# Store the compressed zlib stream size from the footer contents
|
||||
compressed_size_ftr = int.from_bytes(footer_data[:0x4], 'little')
|
||||
|
||||
# Check if the compressed zlib stream is complete, based on footer
|
||||
if compressed_size_ftr != compressed_size_hdr :
|
||||
print('\n Error: Dell sub-PFS BIOS section data is corrupted!')
|
||||
|
||||
# Decompress "BIOS" section payload, starting from zlib header start of 0x789C
|
||||
entry_data = zlib.decompress(compressed_data)
|
||||
|
||||
entry_type = 'ZLIB' # Re-set PFS Entry Type from OTHER to ZLIB, in case such info is needed afterwards
|
||||
|
||||
pfs_count += 1 # Increase the count/index of parsed main PFS structures by one
|
||||
|
||||
# Get the Name of the zlib-compressed full PFS structure via the already stored PFS Information
|
||||
# The zlib-compressed full PFS structure(s) are used to contain multiple BIOS (CombineBiosNameX)
|
||||
# When zlib-compressed full PFS structure(s) exist within the main/first full PFS structure,
|
||||
# its PFS Information should contain their names (CombineBiosNameX). Since the main/first
|
||||
# full PFS structure has count/index 1, the rest start at 2+ and thus, their PFS Information
|
||||
# names can be retrieved in order by subtracting 2 from the main/first PFS Information values
|
||||
sub_pfs_name = ' %s v%s' % (info_all[pfs_count - 2][1], info_all[pfs_count - 2][2]) if info_all else ' UNKNOWN'
|
||||
|
||||
# Recursively call the Dell PFS.HDR. Extractor function for each zlib-compressed full PFS structure
|
||||
pfs_extract(entry_data, pfs_count, sub_pfs_name, pfs_count) # For recursive calls, pfs_index = pfs_count
|
||||
|
||||
entries_all[index][4] = entry_data # Adjust PFS Entry Data after merging Chunks or zlib-decompressing
|
||||
entries_all[index][3] = entry_type # Adjust PFS Entry Type from OTHER to either CHUNKS or ZLIB
|
||||
|
||||
# Name & Store each PFS Entry/Component Data, Data Signature, Metadata, Metadata Signature
|
||||
for entry_index in range(len(entries_all)) :
|
||||
file_index = entries_all[entry_index][0]
|
||||
file_guid = entries_all[entry_index][1]
|
||||
file_version = entries_all[entry_index][2]
|
||||
file_type = entries_all[entry_index][3]
|
||||
file_data = entries_all[entry_index][4]
|
||||
file_data_sig = entries_all[entry_index][5]
|
||||
file_meta = entries_all[entry_index][6]
|
||||
file_meta_sig = entries_all[entry_index][7]
|
||||
|
||||
# Give Names to special PFS Entries, not covered by PFS Information
|
||||
if file_type == 'MODEL_INFO' :
|
||||
file_name = 'Model Information'
|
||||
elif file_type == 'PFS_INFO' :
|
||||
file_name = 'PFS Information'
|
||||
if not is_advanced : continue # Don't store PFS Information in non-advanced user mode
|
||||
else :
|
||||
file_name = ''
|
||||
|
||||
# Most PFS Entry Names & Versions are found at PFS Information via their GUID
|
||||
# Version can be found at PFS_ENTRY but prefer PFS Information when possible
|
||||
for info_index in range(len(info_all)) :
|
||||
info_guid = info_all[info_index][0]
|
||||
info_name = info_all[info_index][1]
|
||||
info_version = info_all[info_index][2]
|
||||
|
||||
# Give proper Name & Version info if Entry/Information GUIDs match
|
||||
if info_guid == file_guid :
|
||||
file_name = info_name
|
||||
file_version = info_version
|
||||
|
||||
info_all[info_index][0] = 'USED' # PFS with zlib-compressed full PFS (multiple BIOS) use the same GUID
|
||||
break # Break at 1st Name match to not rename from next zlib-compressed full PFS with the same GUID
|
||||
|
||||
data_ext = '.data.bin' if is_advanced else '.bin' # Simpler Data Extension for non-advanced users
|
||||
meta_ext = '.meta.bin' if is_advanced else '.bin' # Simpler Metadata Extension for non-advanced users
|
||||
full_name = '%d%s -- %d %s v%s' % (pfs_index, pfs_name, file_index, file_name, file_version) # Full Entry Name
|
||||
safe_name = re.sub(r'[\\/*?:"<>|]', '_', full_name) # Replace common Windows reserved/illegal filename characters
|
||||
|
||||
is_zlib = file_type == 'ZLIB' # Determine if PFS Entry Data was zlib-compressed
|
||||
|
||||
# For both advanced & non-advanced users, the goal is to store final/usable files only
|
||||
# so empty or intermediate files such as sub-PFS, PFS w/ Chunks or zlib-PFS are skipped
|
||||
if file_data and not is_zlib : # Store Data (advanced & non-advanced users)
|
||||
# Some Data may be Text or XML files with useful information for non-advanced users
|
||||
is_text, final_data, file_ext, write_mode = bin_is_text(file_data, file_type, False, is_advanced)
|
||||
|
||||
final_name = '%s%s' % (safe_name, data_ext[:-4] + file_ext if is_text else data_ext)
|
||||
final_path = os.path.join(output_path, final_name)
|
||||
|
||||
with open(final_path, write_mode) as o : o.write(final_data) # Write final Data
|
||||
|
||||
if file_data_sig and is_advanced : # Store Data Signature (advanced users only)
|
||||
final_name = '%s.data.sig' % safe_name
|
||||
final_path = os.path.join(output_path, final_name)
|
||||
|
||||
with open(final_path, 'wb') as o : o.write(file_data_sig) # Write final Data Signature
|
||||
|
||||
# Main/First PFS CombineBiosNameX Metadata files must be kept for accurate Model Information
|
||||
# All users should check these files in order to choose the correct CombineBiosNameX modules
|
||||
if file_meta and (is_zlib or is_advanced) : # Store Metadata (advanced & maybe non-advanced users)
|
||||
# Some Data may be Text or XML files with useful information for non-advanced users
|
||||
is_text, final_data, file_ext, write_mode = bin_is_text(file_meta, file_type, True, is_advanced)
|
||||
|
||||
final_name = '%s%s' % (safe_name, meta_ext[:-4] + file_ext if is_text else meta_ext)
|
||||
final_path = os.path.join(output_path, final_name)
|
||||
|
||||
with open(final_path, write_mode) as o : o.write(final_data) # Write final Data Metadata
|
||||
|
||||
if file_meta_sig and is_advanced : # Store Metadata Signature (advanced users only)
|
||||
final_name = '%s.meta.sig' % safe_name
|
||||
final_path = os.path.join(output_path, final_name)
|
||||
|
||||
with open(final_path, 'wb') as o : o.write(file_meta_sig) # Write final Data Metadata Signature
|
||||
|
||||
# Check if file is Text/XML and Convert
|
||||
def bin_is_text(buffer, file_type, is_metadata, is_advanced) :
|
||||
is_text = False
|
||||
write_mode = 'wb'
|
||||
extension = '.bin'
|
||||
|
||||
# Only for non-advanced users due to signature (.sig) invalidation
|
||||
if not is_advanced :
|
||||
if b',END' in buffer[-0x7:] : # Text Type 1
|
||||
is_text = True
|
||||
write_mode = 'w'
|
||||
extension = '.txt'
|
||||
buffer = buffer.decode('utf-8').split(',END')[0].replace(';','\n')
|
||||
elif buffer.startswith(b'VendorName=Dell') : # Text Type 2
|
||||
is_text = True
|
||||
write_mode = 'w'
|
||||
extension = '.txt'
|
||||
buffer = buffer.split(b'\x00')[0].decode('utf-8').replace(';','\n')
|
||||
elif b'<Rimm x-schema="' in buffer[:0x50] : # XML Type
|
||||
is_text = True
|
||||
write_mode = 'w'
|
||||
extension = '.xml'
|
||||
buffer = buffer.decode('utf-8')
|
||||
elif file_type in ('NESTED_PFS','ZLIB') and is_metadata and len(buffer) == met_info_size : # Text Type 3
|
||||
is_text = True
|
||||
write_mode = 'w'
|
||||
extension = '.txt'
|
||||
buffer = get_struct(buffer, 0, METADATA_INFO).pfs_write()
|
||||
|
||||
return is_text, buffer, extension, write_mode
|
||||
|
||||
# Determine PFS Entry Version string via "Version" and "VersionType" fields
|
||||
def get_version(version_fields, version_types) :
|
||||
version = '' # Initialize Version string
|
||||
|
||||
# Each Version Type (1 byte) determines the type of each Version Value (2 bytes)
|
||||
# Version Type 'N' is Number, 'A' is Text and ' ' is Empty/Unused
|
||||
for idx in range(len(version_fields)) :
|
||||
eol = '' if idx == len(version_fields) - 1 else '.'
|
||||
|
||||
if version_types[idx] == 65 : version += '%X%s' % (version_fields[idx], eol) # 0x41 = ASCII
|
||||
elif version_types[idx] == 78 : version += '%d%s' % (version_fields[idx], eol) # 0x4E = Number
|
||||
elif version_types[idx] in (0, 32) : version = version.strip('.') # 0x00 or 0x20 = Unused
|
||||
else :
|
||||
version += '%X%s' % (version_fields[idx], eol) # Unknown
|
||||
print('\n Error: Unknown PFS Entry Version Type 0x%0.2X!' % version_types[idx])
|
||||
|
||||
return version
|
||||
|
||||
# Get PFS Entry Structure & Size via its Version
|
||||
def get_pfs_entry(buffer, offset) :
|
||||
pfs_entry_ver = int.from_bytes(buffer[offset + 0x10:offset + 0x14], 'little') # PFS Entry Version
|
||||
|
||||
if pfs_entry_ver == 1 : return PFS_ENTRY, ctypes.sizeof(PFS_ENTRY)
|
||||
if pfs_entry_ver == 2 : return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2)
|
||||
|
||||
return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2)
|
||||
|
||||
# Calculate Checksum XOR 8 of data
|
||||
def chk_xor_8(data, init_value) :
|
||||
value = init_value
|
||||
for byte in data : value = value ^ byte
|
||||
value ^= 0x0
|
||||
|
||||
return value
|
||||
|
||||
# Process ctypes Structure Classes
|
||||
# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky
|
||||
def get_struct(buffer, start_offset, class_name, param_list = None) :
|
||||
if param_list is None : param_list = []
|
||||
|
||||
structure = class_name(*param_list) # Unpack parameter list
|
||||
struct_len = ctypes.sizeof(structure)
|
||||
struct_data = buffer[start_offset:start_offset + struct_len]
|
||||
fit_len = min(len(struct_data), struct_len)
|
||||
|
||||
if (start_offset >= len(buffer)) or (fit_len < struct_len) :
|
||||
print('\n Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name.__name__))
|
||||
|
||||
input('\nPress enter to exit')
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)
|
||||
|
||||
return structure
|
||||
|
||||
# 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
|
||||
|
||||
# Show script title
|
||||
print('\n' + title)
|
||||
|
||||
# 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
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('images', type=argparse.FileType('r'), nargs='*')
|
||||
parser.add_argument('-a', '--advanced', help='extract in advanced user mode', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get ctypes Structure Sizes
|
||||
pfs_header_size = ctypes.sizeof(PFS_HDR)
|
||||
pfs_footer_size = ctypes.sizeof(PFS_FTR)
|
||||
pfs_info_size = ctypes.sizeof(PFS_INFO)
|
||||
met_info_size = ctypes.sizeof(METADATA_INFO)
|
||||
chunk_info_header_size = ctypes.sizeof(CHUNK_INFO_HDR)
|
||||
chunk_info_footer_size = ctypes.sizeof(CHUNK_INFO_FTR)
|
||||
|
||||
# The Dell ThinOS PKG BIOS images usually contain more than one section. Each section starts with
|
||||
# a 0x30 sized header, which begins with pattern 72135500. The section length is found at 0x10-0x14
|
||||
# and its (optional) MD5 hash at 0x20-0x30. The section data can be raw or LZMA2 (7zXZ) compressed.
|
||||
# The LZMA2 section includes the actual Dell PFS BIOS image, so it needs to be decompressed first.
|
||||
# For the purposes of this utility, we are only interested in extracting the Dell PFS BIOS section.
|
||||
lzma_pkg_header = re.compile(br'\x72\x13\x55\x00.{45}\x37\x7A\x58\x5A', re.DOTALL)
|
||||
|
||||
# The Dell PFS BIOS images usually contain more than one section. Each section is zlib-compressed
|
||||
# with header pattern ********++EEAA761BECBB20F1E651--789C where ******** is the zlib stream size,
|
||||
# ++ is the section type and -- the header Checksum XOR 8. The "BIOS" section has type 0xAA and its
|
||||
# files are stored in PFS format. The "Utility" section has type 0xBB and its files are stored in PFS
|
||||
# BIN or 7z formats. There could be more section types but for the purposes of this utility, we are
|
||||
# only interested in extracting the "BIOS" section files. Each section is followed by a footer pattern
|
||||
# ********EEAAEE8F491BE8AE143790-- where ******** is the zlib stream size and ++ the footer Checksum XOR 8.
|
||||
zlib_bios_header = re.compile(br'\xAA\xEE\xAA\x76\x1B\xEC\xBB\x20\xF1\xE6\x51.\x78\x9C', re.DOTALL)
|
||||
zlib_bios_footer = re.compile(br'\xEE\xAA\xEE\x8F\x49\x1B\xE8\xAE\x14\x37\x90')
|
||||
|
||||
if len(sys.argv) >= 2 :
|
||||
# Drag & Drop or CLI
|
||||
pfs_exec = []
|
||||
for image in args.images :
|
||||
pfs_exec.append(image.name)
|
||||
else :
|
||||
# Folder path
|
||||
pfs_exec = []
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
print('\nWorking...')
|
||||
for root, _, files in os.walk(in_path):
|
||||
for name in files :
|
||||
pfs_exec.append(os.path.join(root, name))
|
||||
|
||||
# Process each input Dell PFS BIOS image
|
||||
for input_file in pfs_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()
|
||||
|
||||
# Search input image for ThinOS PKG 7zXZ section header
|
||||
lzma_pkg_hdr_match = lzma_pkg_header.search(input_data)
|
||||
|
||||
# Decompress ThinOS PKG 7zXZ section first, if present
|
||||
if lzma_pkg_hdr_match :
|
||||
lzma_len_off = lzma_pkg_hdr_match.start() + 0x10
|
||||
lzma_len_int = int.from_bytes(input_data[lzma_len_off:lzma_len_off + 0x4], 'little')
|
||||
lzma_bin_off = lzma_pkg_hdr_match.end() - 0x5
|
||||
lzma_bin_dat = input_data[lzma_bin_off:lzma_bin_off + lzma_len_int]
|
||||
|
||||
# Check if the compressed 7zXZ stream is complete, based on header
|
||||
if len(lzma_bin_dat) != lzma_len_int :
|
||||
print('\n Error: This Dell ThinOS PKG BIOS image is corrupted!')
|
||||
continue # Next input file
|
||||
|
||||
input_data = lzma.decompress(lzma_bin_dat)
|
||||
|
||||
# Search input image for zlib "BIOS" section header
|
||||
zlib_bios_hdr_match = zlib_bios_header.search(input_data)
|
||||
|
||||
# Check if "BIOS" section was found in the image
|
||||
if not zlib_bios_hdr_match :
|
||||
print('\n Error: This is not a Dell PFS BIOS image!')
|
||||
continue # Next input file
|
||||
|
||||
# Store the compressed zlib stream start offset
|
||||
compressed_start = zlib_bios_hdr_match.start() + 0xC
|
||||
|
||||
# Store the "BIOS" section header start offset
|
||||
header_start = zlib_bios_hdr_match.start() - 0x4
|
||||
|
||||
# Store the "BIOS" section header contents (16 bytes)
|
||||
header_data = input_data[header_start:compressed_start]
|
||||
|
||||
# Check if the "BIOS" section header Checksum XOR 8 is valid
|
||||
if chk_xor_8(header_data[:0xF], 0) != header_data[0xF] :
|
||||
print('\n Error: This Dell PFS BIOS image is corrupted!')
|
||||
continue # Next input file
|
||||
|
||||
# Store the compressed zlib stream size from the header contents
|
||||
compressed_size_hdr = int.from_bytes(header_data[:0x4], 'little')
|
||||
|
||||
# Store the compressed zlib stream end offset
|
||||
compressed_end = compressed_start + compressed_size_hdr
|
||||
|
||||
# Store the compressed zlib stream contents
|
||||
compressed_data = input_data[compressed_start:compressed_end]
|
||||
|
||||
# Check if the compressed zlib stream is complete, based on header
|
||||
if len(compressed_data) != compressed_size_hdr :
|
||||
print('\n Error: This Dell PFS BIOS image is corrupted!')
|
||||
continue # Next input file
|
||||
|
||||
# Store the "BIOS" section footer contents (16 bytes)
|
||||
footer_data = input_data[compressed_end:compressed_end + 0x10]
|
||||
|
||||
# Check if the "BIOS" section footer Checksum XOR 8 is valid
|
||||
if chk_xor_8(footer_data[:0xF], 0) != footer_data[0xF] :
|
||||
print('\n Error: This Dell PFS BIOS image is corrupted!')
|
||||
continue # Next input file
|
||||
|
||||
# Search input image for zlib "BIOS" section footer
|
||||
zlib_bios_ftr_match = zlib_bios_footer.search(footer_data)
|
||||
|
||||
# Check if "BIOS" section footer was found in the image
|
||||
if not zlib_bios_ftr_match :
|
||||
print('\n Error: This Dell PFS BIOS image is corrupted!')
|
||||
continue # Next input file
|
||||
|
||||
# Store the compressed zlib stream size from the footer contents
|
||||
compressed_size_ftr = int.from_bytes(footer_data[:0x4], 'little')
|
||||
|
||||
# Check if the compressed zlib stream is complete, based on footer
|
||||
if compressed_size_ftr != compressed_size_hdr :
|
||||
print('\n Error: This Dell PFS BIOS image is corrupted!')
|
||||
continue # Next input file
|
||||
|
||||
# Decompress "BIOS" section payload, starting from zlib header start of 0x789C
|
||||
input_data = zlib.decompress(compressed_data)
|
||||
|
||||
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
|
||||
|
||||
pfs_name = '' # N/A for Main/First/Initial full PFS, used for sub-PFS recursions
|
||||
pfs_index = 1 # Main/First/Initial full PFS Index is 1
|
||||
pfs_count = 1 # Main/First/Initial full PFS Count is 1
|
||||
is_advanced = bool(args.advanced) # Set Advanced user mode optional argument
|
||||
|
||||
pfs_extract(input_data, pfs_index, pfs_name, pfs_count) # Call the Dell PFS.HDR. Extractor function
|
||||
|
||||
print('\n Extracted Dell PFS BIOS image!')
|
||||
|
||||
input('\nDone!')
|
||||
|
||||
sys.exit(0)
|
1067
Dell_PFS_Extract.py
Normal file
1067
Dell_PFS_Extract.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,82 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Fujitsu SFX Extractor
|
||||
Fujitsu SFX BIOS Extractor
|
||||
Copyright (C) 2019-2021 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
print('Fujitsu SFX BIOS Extractor v2.1')
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
if len(sys.argv) >= 2 :
|
||||
# Drag & Drop or CLI
|
||||
fjsfx_exec = sys.argv[1:]
|
||||
else :
|
||||
# Folder path
|
||||
fjsfx_exec = []
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
print('\nWorking...')
|
||||
for root, dirs, files in os.walk(in_path):
|
||||
for name in files :
|
||||
fjsfx_exec.append(os.path.join(root, name))
|
||||
|
||||
# "FjSfxBinay" + Microsoft CAB Header XOR 0xFF (Tag[4] + Res[4] + Size[4] + Res[4] + Offset[4] + Res[4] + Ver[2]) pattern
|
||||
mscf_pattern = re.compile(br'\x46\x6A\x53\x66\x78\x42\x69\x6E\x61\x79\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL)
|
||||
|
||||
for input_file in fjsfx_exec :
|
||||
file_path = os.path.abspath(input_file)
|
||||
file_dir = os.path.dirname(file_path)
|
||||
file_name = os.path.basename(file_path)
|
||||
|
||||
print('\nFile: ' + file_name)
|
||||
|
||||
# Open Fujitsu SFX Binary Packager executable as mutable bytearray
|
||||
with open(input_file, 'rb') as in_file : FjSfx = bytearray(in_file.read())
|
||||
|
||||
match_mscf = mscf_pattern.search(FjSfx) # Search for Fujitsu Microsoft CAB Header XOR 0xFF pattern
|
||||
|
||||
# Check if Microsoft CAB Header XOR 0xFF pattern exists
|
||||
if match_mscf :
|
||||
print('\n Detected Obfuscation!')
|
||||
|
||||
mscf_start = match_mscf.start() + 0xA # Microsoft CAB Header XOR 0xFF starts after "FjSfxBinay" signature
|
||||
|
||||
# Determine the Microsoft CAB image Size
|
||||
cab_size = int.from_bytes(FjSfx[mscf_start + 0x8:mscf_start + 0xC], 'little') # Get LE XOR-ed CAB Size
|
||||
xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB Size XOR value
|
||||
cab_size = cab_size ^ xor_size # Perform XOR 0xFF and get actual CAB Size
|
||||
|
||||
print('\n Removing Obfuscation...')
|
||||
|
||||
# Determine the Microsoft CAB image Data
|
||||
cab_data = int.from_bytes(FjSfx[mscf_start:mscf_start + cab_size], 'big') # Get BE XOR-ed CAB Data
|
||||
xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB Data XOR value
|
||||
cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB Data
|
||||
|
||||
print('\n Extracting...')
|
||||
|
||||
with open('fjsfx_temp.cab', 'wb') as cab_file : cab_file.write(cab_data) # Create temporary CAB image
|
||||
|
||||
extr_path = os.path.join(file_dir, file_name[:-4], '') # Create CAB image extraction path
|
||||
|
||||
try :
|
||||
decomp = subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + extr_path, 'fjsfx_temp.cab']) # 7-Zip
|
||||
|
||||
print('\n Extracted!')
|
||||
except :
|
||||
print('\n Error: Could not decompress Microsoft CAB image!')
|
||||
print(' Make sure that "7z" executable exists!')
|
||||
|
||||
os.remove('fjsfx_temp.cab') # Remove temporary CAB image
|
||||
|
||||
else :
|
||||
print('\n Error: This is not a Fujitsu SFX BIOS image!')
|
||||
continue # Next input file
|
||||
|
||||
else :
|
||||
input('\nDone!')
|
|
@ -1,108 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Fujitsu UPC Extract
|
||||
Fujitsu UPC BIOS Extractor
|
||||
Copyright (C) 2021 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
title = 'Fujitsu UPC 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 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
|
||||
upc_parser = argparse.ArgumentParser()
|
||||
upc_parser.add_argument('upc', type=argparse.FileType('r'), nargs='*')
|
||||
upc_parser.add_argument('-p', '--path', help='parse files within given folder', type=str)
|
||||
upc_params = upc_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(upc_params.path) :
|
||||
upc_exec = get_files(upc_params.path) # CLI with --path
|
||||
else :
|
||||
upc_exec = []
|
||||
for executable in upc_params.upc :
|
||||
upc_exec.append(executable.name) # Drag & Drop
|
||||
else :
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
upc_exec = get_files(in_path) # Direct Run
|
||||
|
||||
# Process each input Fujitsu UPC BIOS image
|
||||
for input_file in upc_exec :
|
||||
input_name,input_extension = os.path.splitext(os.path.basename(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 : upc_data = in_file.read()
|
||||
|
||||
if input_extension.upper() != '.UPC' or int.from_bytes(upc_data[0x0:0x4], 'little') + 0x8 != len(upc_data) :
|
||||
print('\n Error: This is not a Fujitsu UPC BIOS image!')
|
||||
continue # Next input file
|
||||
|
||||
output_file = input_file[:-4] + '.bin' # Decompressed filename
|
||||
|
||||
# EFI/Tiano Decompression
|
||||
try :
|
||||
subprocess.run(['TianoCompress', '-d', input_file, '-o', output_file, '--uefi', '-q'], check = True, stdout = subprocess.DEVNULL)
|
||||
|
||||
if os.path.getsize(output_file) != int.from_bytes(upc_data[0x4:0x8], 'little') : raise Exception('EFI_DECOMP_ERROR')
|
||||
except :
|
||||
print('\n Error: Could not extract input file via TianoCompress!')
|
||||
input(' Make sure that "TianoCompress" executable exists!')
|
||||
|
||||
print('\n Extracted Fujitsu UPC BIOS image!')
|
||||
|
||||
input('\nDone!')
|
||||
|
||||
sys.exit(0)
|
89
Fujitsu_SFX_Extract.py
Normal file
89
Fujitsu_SFX_Extract.py
Normal file
|
@ -0,0 +1,89 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Fujitsu SFX Extractor
|
||||
Fujitsu SFX BIOS Extractor
|
||||
Copyright (C) 2019-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'Fujitsu SFX BIOS Extractor v3.0_a3'
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_szip import is_szip_supported, szip_decompress
|
||||
from common.path_ops import make_dirs
|
||||
from common.patterns import PAT_FUJITSU_SFX
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is Fujitsu SFX image
|
||||
def is_fujitsu_sfx(in_file):
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
return bool(PAT_FUJITSU_SFX.search(buffer))
|
||||
|
||||
# Extract Fujitsu SFX image
|
||||
def fujitsu_cabinet(in_file, extract_path, padding=0):
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
match_cab = PAT_FUJITSU_SFX.search(buffer) # Microsoft CAB Header XOR 0xFF
|
||||
|
||||
if not match_cab:
|
||||
return 1
|
||||
|
||||
printer('Detected obfuscated CAB archive!', padding)
|
||||
|
||||
# Microsoft CAB Header XOR 0xFF starts after "FjSfxBinay" signature
|
||||
cab_start = match_cab.start() + 0xA
|
||||
|
||||
# Determine the Microsoft CAB image size
|
||||
cab_size = int.from_bytes(buffer[cab_start + 0x8:cab_start + 0xC], 'little') # Get LE XOR-ed CAB size
|
||||
xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB size XOR value
|
||||
cab_size ^= xor_size # Perform XOR 0xFF and get actual CAB size
|
||||
|
||||
printer('Removing obfuscation...', padding + 4)
|
||||
|
||||
# Determine the Microsoft CAB image Data
|
||||
cab_data = int.from_bytes(buffer[cab_start:cab_start + cab_size], 'big') # Get BE XOR-ed CAB data
|
||||
xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB data XOR value
|
||||
cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB data
|
||||
|
||||
printer('Extracting archive...', padding + 4)
|
||||
|
||||
cab_path = os.path.join(extract_path, 'FjSfxBinay.cab')
|
||||
|
||||
with open(cab_path, 'wb') as cab_file:
|
||||
cab_file.write(cab_data) # Create temporary CAB archive
|
||||
|
||||
if is_szip_supported(cab_path, padding + 8, check=True):
|
||||
if szip_decompress(cab_path, extract_path, 'CAB', padding + 8, check=True) == 0:
|
||||
os.remove(cab_path) # Successful extraction, delete temporary CAB archive
|
||||
else:
|
||||
return 3
|
||||
else:
|
||||
return 2
|
||||
|
||||
return 0
|
||||
|
||||
# Parse & Extract Fujitsu SFX image
|
||||
def fujitsu_sfx_extract(in_file, extract_path, padding=0):
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
if fujitsu_cabinet(buffer, extract_path, padding) == 0:
|
||||
printer('Successfully Extracted!', padding)
|
||||
else:
|
||||
printer('Error: Failed to Extract image!', padding)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_fujitsu_sfx, fujitsu_sfx_extract).run_utility()
|
44
Fujitsu_UPC_Extract.py
Normal file
44
Fujitsu_UPC_Extract.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Fujitsu UPC Extract
|
||||
Fujitsu UPC BIOS Extractor
|
||||
Copyright (C) 2021-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'Fujitsu UPC BIOS Extractor v2.0_a5'
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_efi import efi_decompress, is_efi_compressed
|
||||
from common.path_ops import make_dirs, path_suffixes
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is Fujitsu UPC image
|
||||
def is_fujitsu_upc(in_file):
|
||||
in_buffer = file_to_bytes(in_file)
|
||||
|
||||
is_ext = path_suffixes(in_file)[-1].upper() == '.UPC' if os.path.isfile(in_file) else True
|
||||
|
||||
is_efi = is_efi_compressed(in_buffer)
|
||||
|
||||
return is_ext and is_efi
|
||||
|
||||
# Parse & Extract Fujitsu UPC image
|
||||
def fujitsu_upc_extract(input_file, extract_path, padding=0):
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
image_base = os.path.basename(input_file)
|
||||
image_name = image_base[:-4] if image_base.upper().endswith('.UPC') else image_base
|
||||
image_path = os.path.join(extract_path, f'{image_name}.bin')
|
||||
|
||||
return efi_decompress(input_file, image_path, padding)
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_fujitsu_upc, fujitsu_upc_extract).run_utility()
|
217
Insyde_IFD_Extract.py
Normal file
217
Insyde_IFD_Extract.py
Normal file
|
@ -0,0 +1,217 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Insyde IFD Extract
|
||||
Insyde iFlash/iFdPacker Extractor
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'Insyde iFlash/iFdPacker Extractor v2.0_a11'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import ctypes
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_szip import is_szip_supported, szip_decompress
|
||||
from common.path_ops import get_path_files, make_dirs, safe_name, get_extract_path
|
||||
from common.patterns import PAT_INSYDE_IFL, PAT_INSYDE_SFX
|
||||
from common.struct_ops import char, get_struct, uint32_t
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
class IflashHeader(ctypes.LittleEndianStructure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Signature', char*8), # 0x00 $_IFLASH
|
||||
('ImageTag', char*8), # 0x08
|
||||
('TotalSize', uint32_t), # 0x10 from header end
|
||||
('ImageSize', uint32_t), # 0x14 from header end
|
||||
# 0x18
|
||||
]
|
||||
|
||||
def _get_padd_len(self):
|
||||
return self.TotalSize - self.ImageSize
|
||||
|
||||
def get_image_tag(self):
|
||||
return self.ImageTag.decode('utf-8','ignore').strip('_')
|
||||
|
||||
def struct_print(self, p):
|
||||
printer(['Signature :', self.Signature.decode('utf-8')], p, False)
|
||||
printer(['Image Name:', self.get_image_tag()], p, False)
|
||||
printer(['Image Size:', f'0x{self.ImageSize:X}'], p, False)
|
||||
printer(['Total Size:', f'0x{self.TotalSize:X}'], p, False)
|
||||
printer(['Padd Size :', f'0x{self._get_padd_len():X}'], p, False)
|
||||
|
||||
# Check if input is Insyde iFlash/iFdPacker Update image
|
||||
def is_insyde_ifd(input_file):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
is_ifl = bool(insyde_iflash_detect(input_buffer))
|
||||
|
||||
is_sfx = bool(PAT_INSYDE_SFX.search(input_buffer))
|
||||
|
||||
return is_ifl or is_sfx
|
||||
|
||||
# Parse & Extract Insyde iFlash/iFdPacker Update images
|
||||
def insyde_ifd_extract(input_file, extract_path, padding=0):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
iflash_code = insyde_iflash_extract(input_buffer, extract_path, padding)
|
||||
|
||||
ifdpack_path = os.path.join(extract_path, 'Insyde iFdPacker SFX')
|
||||
|
||||
ifdpack_code = insyde_packer_extract(input_buffer, ifdpack_path, padding)
|
||||
|
||||
return iflash_code and ifdpack_code
|
||||
|
||||
# Detect Insyde iFlash Update image
|
||||
def insyde_iflash_detect(input_buffer):
|
||||
iflash_match_all = []
|
||||
iflash_match_nan = [0x0,0xFFFFFFFF]
|
||||
|
||||
for iflash_match in PAT_INSYDE_IFL.finditer(input_buffer):
|
||||
ifl_bgn = iflash_match.start()
|
||||
|
||||
if len(input_buffer[ifl_bgn:]) <= INS_IFL_LEN:
|
||||
continue
|
||||
|
||||
ifl_hdr = get_struct(input_buffer, ifl_bgn, IflashHeader)
|
||||
|
||||
if ifl_hdr.TotalSize in iflash_match_nan \
|
||||
or ifl_hdr.ImageSize in iflash_match_nan \
|
||||
or ifl_hdr.TotalSize < ifl_hdr.ImageSize \
|
||||
or ifl_bgn + INS_IFL_LEN + ifl_hdr.TotalSize > len(input_buffer):
|
||||
continue
|
||||
|
||||
iflash_match_all.append([ifl_bgn, ifl_hdr])
|
||||
|
||||
return iflash_match_all
|
||||
|
||||
# Extract Insyde iFlash Update image
|
||||
def insyde_iflash_extract(input_buffer, extract_path, padding=0):
|
||||
insyde_iflash_all = insyde_iflash_detect(input_buffer)
|
||||
|
||||
if not insyde_iflash_all:
|
||||
return 127
|
||||
|
||||
printer('Detected Insyde iFlash Update image!', padding)
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
exit_codes = []
|
||||
|
||||
for insyde_iflash in insyde_iflash_all:
|
||||
exit_code = 0
|
||||
|
||||
ifl_bgn,ifl_hdr = insyde_iflash
|
||||
|
||||
img_bgn = ifl_bgn + INS_IFL_LEN
|
||||
img_end = img_bgn + ifl_hdr.ImageSize
|
||||
img_bin = input_buffer[img_bgn:img_end]
|
||||
|
||||
if len(img_bin) != ifl_hdr.ImageSize:
|
||||
exit_code = 1
|
||||
|
||||
img_val = [ifl_hdr.get_image_tag(), 'bin']
|
||||
img_tag,img_ext = INS_IFL_IMG.get(img_val[0], img_val)
|
||||
|
||||
img_name = f'{img_tag} [0x{img_bgn:08X}-0x{img_end:08X}]'
|
||||
|
||||
printer(f'{img_name}\n', padding + 4)
|
||||
|
||||
ifl_hdr.struct_print(padding + 8)
|
||||
|
||||
if img_val == [img_tag,img_ext]:
|
||||
printer(f'Note: Detected new Insyde iFlash tag {img_tag}!', padding + 12, pause=True)
|
||||
|
||||
out_name = f'{img_name}.{img_ext}'
|
||||
|
||||
out_path = os.path.join(extract_path, safe_name(out_name))
|
||||
|
||||
with open(out_path, 'wb') as out_image:
|
||||
out_image.write(img_bin)
|
||||
|
||||
printer(f'Succesfull Insyde iFlash > {img_tag} extraction!', padding + 12)
|
||||
|
||||
exit_codes.append(exit_code)
|
||||
|
||||
return sum(exit_codes)
|
||||
|
||||
# Extract Insyde iFdPacker 7-Zip SFX 7z Update image
|
||||
def insyde_packer_extract(input_buffer, extract_path, padding=0):
|
||||
match_sfx = PAT_INSYDE_SFX.search(input_buffer)
|
||||
|
||||
if not match_sfx:
|
||||
return 127
|
||||
|
||||
printer('Detected Insyde iFdPacker Update image!', padding)
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
sfx_buffer = bytearray(input_buffer[match_sfx.end() - 0x5:])
|
||||
|
||||
if sfx_buffer[:0x5] == b'\x6E\xF4\x79\x5F\x4E':
|
||||
printer('Detected Insyde iFdPacker > 7-Zip SFX > Obfuscation!', padding + 4)
|
||||
|
||||
for index,byte in enumerate(sfx_buffer):
|
||||
sfx_buffer[index] = byte // 2 + (128 if byte % 2 else 0)
|
||||
|
||||
printer('Removed Insyde iFdPacker > 7-Zip SFX > Obfuscation!', padding + 8)
|
||||
|
||||
printer('Extracting Insyde iFdPacker > 7-Zip SFX archive...', padding + 4)
|
||||
|
||||
if bytes(INS_SFX_PWD, 'utf-16le') in input_buffer[:match_sfx.start()]:
|
||||
printer('Detected Insyde iFdPacker > 7-Zip SFX > Password!', padding + 8)
|
||||
printer(INS_SFX_PWD, padding + 12)
|
||||
|
||||
sfx_path = os.path.join(extract_path, 'Insyde_iFdPacker_SFX.7z')
|
||||
|
||||
with open(sfx_path, 'wb') as sfx_file:
|
||||
sfx_file.write(sfx_buffer)
|
||||
|
||||
if is_szip_supported(sfx_path, padding + 8, args=[f'-p{INS_SFX_PWD}'], check=True):
|
||||
if szip_decompress(sfx_path, extract_path, 'Insyde iFdPacker > 7-Zip SFX',
|
||||
padding + 8, args=[f'-p{INS_SFX_PWD}'], check=True) == 0:
|
||||
os.remove(sfx_path)
|
||||
else:
|
||||
return 125
|
||||
else:
|
||||
return 126
|
||||
|
||||
exit_codes = []
|
||||
|
||||
for sfx_file in get_path_files(extract_path):
|
||||
if is_insyde_ifd(sfx_file):
|
||||
printer(f'{os.path.basename(sfx_file)}', padding + 12)
|
||||
|
||||
ifd_code = insyde_ifd_extract(sfx_file, get_extract_path(sfx_file), padding + 16)
|
||||
|
||||
exit_codes.append(ifd_code)
|
||||
|
||||
return sum(exit_codes)
|
||||
|
||||
# Insyde iFdPacker known 7-Zip SFX Password
|
||||
INS_SFX_PWD = 'Y`t~i!L@i#t$U%h^s7A*l(f)E-d=y+S_n?i'
|
||||
|
||||
# Insyde iFlash known Image Names
|
||||
INS_IFL_IMG = {
|
||||
'BIOSCER' : ['Certificate', 'bin'],
|
||||
'BIOSCR2' : ['Certificate 2nd', 'bin'],
|
||||
'BIOSIMG' : ['BIOS-UEFI', 'bin'],
|
||||
'DRV_IMG' : ['isflash', 'efi'],
|
||||
'EC_IMG' : ['Embedded Controller', 'bin'],
|
||||
'INI_IMG' : ['platform', 'ini'],
|
||||
'ME_IMG' : ['Management Engine', 'bin'],
|
||||
'OEM_ID' : ['OEM Identifier', 'bin'],
|
||||
}
|
||||
|
||||
# Get common ctypes Structure Sizes
|
||||
INS_IFL_LEN = ctypes.sizeof(IflashHeader)
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_insyde_ifd, insyde_ifd_extract).run_utility()
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2019-2021 Plato Mavropoulos
|
||||
Copyright (c) 2019-2022 Plato Mavropoulos
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Panasonic BIOS Extract
|
||||
Panasonic BIOS Update Extractor
|
||||
Copyright (C) 2018 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
print('Panasonic BIOS Update Extractor v1.0')
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import pefile
|
||||
import subprocess
|
||||
|
||||
if len(sys.argv) >= 2 :
|
||||
# Drag & Drop or CLI
|
||||
panasonic = sys.argv[1:]
|
||||
else :
|
||||
# Folder path
|
||||
panasonic = []
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
print('\nWorking...')
|
||||
for root, dirs, files in os.walk(in_path):
|
||||
for name in files :
|
||||
panasonic.append(os.path.join(root, name))
|
||||
|
||||
for input_file in panasonic :
|
||||
file_path = os.path.abspath(input_file)
|
||||
file_name = os.path.basename(input_file)
|
||||
file_dir = os.path.dirname(file_path)
|
||||
file_ext = os.path.splitext(file_path)[1]
|
||||
|
||||
# Create output folder
|
||||
extr_path = os.path.join(os.getcwd(), 'RCDATA')
|
||||
if os.path.exists(extr_path) : shutil.rmtree(extr_path)
|
||||
os.makedirs(extr_path)
|
||||
|
||||
max_size = 0
|
||||
max_file = None
|
||||
pe = pefile.PE(input_file) # Analyze Portable Executable (PE)
|
||||
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries :
|
||||
# Parse Resource Data directories only
|
||||
if entry.struct.name == 'IMAGE_RESOURCE_DIRECTORY_ENTRY' and entry.struct.Id == 10 : # RCDATA ID = 10
|
||||
for resource in entry.directory.entries :
|
||||
offset = resource.directory.entries[0].data.struct.OffsetToData
|
||||
size = resource.directory.entries[0].data.struct.Size
|
||||
data = pe.get_data(offset, size)
|
||||
file = os.path.join(extr_path, '%X_%X.bin' % (offset, size))
|
||||
with open(file, 'wb') as out_file : out_file.write(data)
|
||||
|
||||
# Remember largest resource (SPI/BIOS)
|
||||
if size > max_size :
|
||||
max_size = size
|
||||
max_file = file
|
||||
|
||||
if not max_file :
|
||||
print('\nError: No Panasonic BIOS Update at %s!' % file_name)
|
||||
shutil.rmtree(extr_path) # Remove temporary folder
|
||||
continue # Next input file
|
||||
|
||||
# Call Rustam Abdullaev's unpack_lznt1 to extract the LZNT1-compressed SPI/BIOS resource at 0x8 onwards
|
||||
try :
|
||||
subprocess.run(['unpack_lznt1', max_file, os.path.join(file_dir, file_name[:-4] + '.bin'), '8'], check = True, stdout = subprocess.DEVNULL)
|
||||
print('\nExtracted %s via unpack_lznt1' % (file_name[:-4] + '.bin'))
|
||||
except :
|
||||
print('\nError: Could not extract %s via unpack_lznt1!' % (file_name[:-4] + '.bin'))
|
||||
print(' Make sure that "unpack_lznt1.exe" executable exists!')
|
||||
|
||||
shutil.rmtree(extr_path) # Remove temporary folder
|
||||
|
||||
else :
|
||||
input('\nDone!')
|
209
Panasonic_BIOS_Extract.py
Normal file
209
Panasonic_BIOS_Extract.py
Normal file
|
@ -0,0 +1,209 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Panasonic BIOS Extract
|
||||
Panasonic BIOS Package Extractor
|
||||
Copyright (C) 2018-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'Panasonic BIOS Package Extractor v2.0_a10'
|
||||
|
||||
import os
|
||||
import io
|
||||
import sys
|
||||
import lznt1
|
||||
import pefile
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_szip import is_szip_supported, szip_decompress
|
||||
from common.path_ops import get_path_files, make_dirs, path_stem, safe_name
|
||||
from common.pe_ops import get_pe_file, get_pe_info, is_pe_file, show_pe_info
|
||||
from common.patterns import PAT_MICROSOFT_CAB
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
from AMI_PFAT_Extract import is_ami_pfat, parse_pfat_file
|
||||
|
||||
# Check if input is Panasonic BIOS Package PE
|
||||
def is_panasonic_pkg(in_file):
|
||||
in_buffer = file_to_bytes(in_file)
|
||||
|
||||
pe_file = get_pe_file(in_buffer, fast=True)
|
||||
|
||||
if not pe_file:
|
||||
return False
|
||||
|
||||
pe_info = get_pe_info(pe_file)
|
||||
|
||||
if not pe_info:
|
||||
return False
|
||||
|
||||
if pe_info.get(b'FileDescription',b'').upper() != b'UNPACK UTILITY':
|
||||
return False
|
||||
|
||||
if not PAT_MICROSOFT_CAB.search(in_buffer):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# Search and Extract Panasonic BIOS Package PE CAB archive
|
||||
def panasonic_cab_extract(buffer, extract_path, padding=0):
|
||||
pe_path,pe_file,pe_info = [None] * 3
|
||||
|
||||
cab_bgn = PAT_MICROSOFT_CAB.search(buffer).start()
|
||||
cab_len = int.from_bytes(buffer[cab_bgn + 0x8:cab_bgn + 0xC], 'little')
|
||||
cab_end = cab_bgn + cab_len
|
||||
cab_bin = buffer[cab_bgn:cab_end]
|
||||
cab_tag = f'[0x{cab_bgn:06X}-0x{cab_end:06X}]'
|
||||
|
||||
cab_path = os.path.join(extract_path, f'CAB_{cab_tag}.cab')
|
||||
|
||||
with open(cab_path, 'wb') as cab_file:
|
||||
cab_file.write(cab_bin) # Store CAB archive
|
||||
|
||||
if is_szip_supported(cab_path, padding, check=True):
|
||||
printer(f'Panasonic BIOS Package > PE > CAB {cab_tag}', padding)
|
||||
|
||||
if szip_decompress(cab_path, extract_path, 'CAB', padding + 4, check=True) == 0:
|
||||
os.remove(cab_path) # Successful extraction, delete CAB archive
|
||||
else:
|
||||
return pe_path, pe_file, pe_info
|
||||
else:
|
||||
return pe_path, pe_file, pe_info
|
||||
|
||||
for file_path in get_path_files(extract_path):
|
||||
pe_file = get_pe_file(file_path, fast=True)
|
||||
if pe_file:
|
||||
pe_info = get_pe_info(pe_file)
|
||||
if pe_info.get(b'FileDescription',b'').upper() == b'BIOS UPDATE':
|
||||
pe_path = file_path
|
||||
break
|
||||
else:
|
||||
return pe_path, pe_file, pe_info
|
||||
|
||||
return pe_path, pe_file, pe_info
|
||||
|
||||
# Extract & Decompress Panasonic BIOS Update PE RCDATA (LZNT1)
|
||||
def panasonic_res_extract(pe_name, pe_file, extract_path, padding=0):
|
||||
is_rcdata = False
|
||||
|
||||
# When fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed prior to RCDATA Directories
|
||||
pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']])
|
||||
|
||||
# Parse all Resource Data Directories > RCDATA (ID = 10)
|
||||
for entry in pe_file.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if entry.struct.name == 'IMAGE_RESOURCE_DIRECTORY_ENTRY' and entry.struct.Id == 0xA:
|
||||
is_rcdata = True
|
||||
for resource in entry.directory.entries:
|
||||
res_bgn = resource.directory.entries[0].data.struct.OffsetToData
|
||||
res_len = resource.directory.entries[0].data.struct.Size
|
||||
res_end = res_bgn + res_len
|
||||
res_bin = pe_file.get_data(res_bgn, res_len)
|
||||
res_tag = f'{pe_name} [0x{res_bgn:06X}-0x{res_end:06X}]'
|
||||
res_out = os.path.join(extract_path, f'{res_tag}')
|
||||
|
||||
printer(res_tag, padding + 4)
|
||||
|
||||
try:
|
||||
res_raw = lznt1.decompress(res_bin[0x8:])
|
||||
|
||||
printer('Succesfull LZNT1 decompression via lznt1!', padding + 8)
|
||||
except Exception:
|
||||
res_raw = res_bin
|
||||
|
||||
printer('Succesfull PE Resource extraction!', padding + 8)
|
||||
|
||||
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
|
||||
if is_ami_pfat(res_raw):
|
||||
pfat_dir = os.path.join(extract_path, res_tag)
|
||||
|
||||
parse_pfat_file(res_raw, pfat_dir, padding + 12)
|
||||
else:
|
||||
if is_pe_file(res_raw):
|
||||
res_ext = 'exe'
|
||||
elif res_raw.startswith(b'[') and res_raw.endswith((b'\x0D\x0A',b'\x0A')):
|
||||
res_ext = 'txt'
|
||||
else:
|
||||
res_ext = 'bin'
|
||||
|
||||
if res_ext == 'txt':
|
||||
printer(new_line=False)
|
||||
for line in io.BytesIO(res_raw).readlines():
|
||||
line_text = line.decode('utf-8','ignore').rstrip()
|
||||
printer(line_text, padding + 12, new_line=False)
|
||||
|
||||
with open(f'{res_out}.{res_ext}', 'wb') as out_file:
|
||||
out_file.write(res_raw)
|
||||
|
||||
return is_rcdata
|
||||
|
||||
# Extract Panasonic BIOS Update PE Data when RCDATA is not available
|
||||
def panasonic_img_extract(pe_name, pe_path, pe_file, extract_path, padding=0):
|
||||
pe_data = file_to_bytes(pe_path)
|
||||
|
||||
sec_bgn = pe_file.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']].VirtualAddress
|
||||
img_bgn = pe_file.OPTIONAL_HEADER.BaseOfData + pe_file.OPTIONAL_HEADER.SizeOfInitializedData
|
||||
img_end = sec_bgn or len(pe_data)
|
||||
img_bin = pe_data[img_bgn:img_end]
|
||||
img_tag = f'{pe_name} [0x{img_bgn:X}-0x{img_end:X}]'
|
||||
img_out = os.path.join(extract_path, f'{img_tag}.bin')
|
||||
|
||||
printer(img_tag, padding + 4)
|
||||
|
||||
with open(img_out, 'wb') as out_img:
|
||||
out_img.write(img_bin)
|
||||
|
||||
printer('Succesfull PE Data extraction!', padding + 8)
|
||||
|
||||
return bool(img_bin)
|
||||
|
||||
# Parse & Extract Panasonic BIOS Package PE
|
||||
def panasonic_pkg_extract(input_file, extract_path, padding=0):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
pkg_pe_file = get_pe_file(input_buffer, fast=True)
|
||||
|
||||
if not pkg_pe_file:
|
||||
return 2
|
||||
|
||||
pkg_pe_info = get_pe_info(pkg_pe_file)
|
||||
|
||||
if not pkg_pe_info:
|
||||
return 3
|
||||
|
||||
pkg_pe_name = path_stem(input_file)
|
||||
|
||||
printer(f'Panasonic BIOS Package > PE ({pkg_pe_name})\n', padding)
|
||||
|
||||
show_pe_info(pkg_pe_info, padding + 4)
|
||||
|
||||
upd_pe_path,upd_pe_file,upd_pe_info = panasonic_cab_extract(input_buffer, extract_path, padding + 4)
|
||||
|
||||
if not (upd_pe_path and upd_pe_file and upd_pe_info):
|
||||
return 4
|
||||
|
||||
upd_pe_name = safe_name(path_stem(upd_pe_path))
|
||||
|
||||
printer(f'Panasonic BIOS Update > PE ({upd_pe_name})\n', padding + 12)
|
||||
|
||||
show_pe_info(upd_pe_info, padding + 16)
|
||||
|
||||
is_upd_res, is_upd_img = False, False
|
||||
|
||||
is_upd_res = panasonic_res_extract(upd_pe_name, upd_pe_file, extract_path, padding + 16)
|
||||
|
||||
if not is_upd_res:
|
||||
is_upd_img = panasonic_img_extract(upd_pe_name, upd_pe_path, upd_pe_file, extract_path, padding + 16)
|
||||
|
||||
os.remove(upd_pe_path)
|
||||
|
||||
return 0 if is_upd_res or is_upd_img else 1
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_panasonic_pkg, panasonic_pkg_extract).run_utility()
|
|
@ -1,213 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Phoenix SCT Extract
|
||||
Phoenix SCT BIOS Extractor
|
||||
Copyright (C) 2021 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
title = 'Phoenix SCT 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 re
|
||||
import lzma
|
||||
import shutil
|
||||
import ctypes
|
||||
import argparse
|
||||
import traceback
|
||||
|
||||
# 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
|
||||
sct_parser = argparse.ArgumentParser()
|
||||
sct_parser.add_argument('executables', type=argparse.FileType('r'), nargs='*')
|
||||
sct_parser.add_argument('-p', '--path', help='parse files within given folder', type=str)
|
||||
sct_params = sct_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(sct_params.path) :
|
||||
sct_exec = get_files(sct_params.path) # CLI with --path
|
||||
else :
|
||||
sct_exec = []
|
||||
for executable in sct_params.executables :
|
||||
sct_exec.append(executable.name) # Drag & Drop
|
||||
else :
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
sct_exec = get_files(in_path) # Direct Run
|
||||
|
||||
# Set ctypes Structure types
|
||||
char = ctypes.c_char
|
||||
uint8_t = ctypes.c_ubyte
|
||||
uint16_t = ctypes.c_ushort
|
||||
uint32_t = ctypes.c_uint
|
||||
uint64_t = ctypes.c_uint64
|
||||
|
||||
class SCT_HDR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('Tag', char*8), # 0x00
|
||||
('Size', uint32_t), # 0x08
|
||||
('Count', uint32_t), # 0x0C
|
||||
# 0x10
|
||||
]
|
||||
|
||||
def sct_print(self) :
|
||||
print('\n Phoenix SCT Header:\n')
|
||||
print(' Tag : %s' % self.Tag.decode('utf-8','replace').strip())
|
||||
print(' Size : 0x%X' % self.Size)
|
||||
print(' Count : %d' % self.Count)
|
||||
|
||||
class SCT_MOD(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
|
||||
]
|
||||
|
||||
def sct_print(self) :
|
||||
print('\n Phoenix SCT Entry:\n')
|
||||
print(' Name : %s' % self.Name.decode('utf-8','replace').strip())
|
||||
print(' Offset : 0x%X' % self.Offset)
|
||||
print(' Size : 0x%X' % self.Size)
|
||||
print(' Compressed : %s' % ['No','Yes'][self.Compressed])
|
||||
print(' Reserved : 0x%X' % self.Reserved)
|
||||
|
||||
# Process ctypes Structure Classes
|
||||
# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky
|
||||
def get_struct(buffer, start_offset, class_name, param_list = None) :
|
||||
if param_list is None : param_list = []
|
||||
|
||||
structure = class_name(*param_list) # Unpack parameter list
|
||||
struct_len = ctypes.sizeof(structure)
|
||||
struct_data = buffer[start_offset:start_offset + struct_len]
|
||||
fit_len = min(len(struct_data), struct_len)
|
||||
|
||||
if (start_offset >= len(buffer)) or (fit_len < struct_len) :
|
||||
print('\n Error: Offset 0x%X out of bounds at %s, possibly incomplete image!' % (start_offset, class_name.__name__))
|
||||
|
||||
input('\n Press enter to exit')
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)
|
||||
|
||||
return structure
|
||||
|
||||
# Phoenix SCT BIOS Package Pattern ($PACK + Size + Count)
|
||||
sct_pat = re.compile(br'\x24\x50\x41\x43\x4B\x00{3}..\x00{2}.\x00{3}', re.DOTALL)
|
||||
|
||||
# Get common ctypes Structure Sizes
|
||||
sct_hdr_len = ctypes.sizeof(SCT_HDR)
|
||||
sct_mod_len = ctypes.sizeof(SCT_MOD)
|
||||
|
||||
# Size of dummy/placeholder SCT Entries
|
||||
sct_dummy_len = 0x200 # Top 2, Names only
|
||||
|
||||
# Process each input Phoenix SCT BIOS executable
|
||||
for input_file in sct_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()
|
||||
|
||||
sct_match = sct_pat.search(input_data) # Search for Phoenix SCT BIOS Pattern
|
||||
|
||||
# Check if Phoenix SCT BIOS Pattern was found on executable
|
||||
if not sct_match :
|
||||
print('\n Error: This is not a Phoenix SCT BIOS executable!')
|
||||
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
|
||||
|
||||
print('\n Phoenix SecureCore Technology')
|
||||
|
||||
sct_hdr = get_struct(input_data, sct_match.start(), SCT_HDR) # Parse SCT Header Structure
|
||||
sct_hdr.sct_print() # Print SCT Header Info
|
||||
|
||||
# Check if reported SCT Header Size matches manual SCT Entry Count calculation
|
||||
if sct_hdr.Size != sct_hdr_len + sct_dummy_len + sct_hdr.Count * sct_mod_len :
|
||||
input('\n Error: This Phoenix SCT BIOS image is corrupted!')
|
||||
continue # Next input file
|
||||
|
||||
# Store all SCT $PACK Data w/o initial dummy/placeholder Entries
|
||||
pack_data = input_data[sct_match.end() + sct_dummy_len:sct_match.start() + sct_hdr.Size]
|
||||
|
||||
# Parse each SCT Entry
|
||||
for e_idx in range(sct_hdr.Count) :
|
||||
mod_hdr = get_struct(pack_data, e_idx * sct_mod_len, SCT_MOD) # Parse SCT Entry Structure
|
||||
mod_hdr.sct_print() # Print SCT Entry Info
|
||||
|
||||
mod_data = input_data[mod_hdr.Offset:mod_hdr.Offset + mod_hdr.Size] # Store SCT Entry Raw Data
|
||||
|
||||
# Check if SCT Entry Raw Data is complete
|
||||
if len(mod_data) != mod_hdr.Size :
|
||||
input('\n Error: This Phoenix SCT BIOS image is incomplete!')
|
||||
|
||||
# Store SCT Entry LZMA Decompressed Data, when applicable
|
||||
if mod_hdr.Compressed : mod_data = lzma.LZMADecompressor().decompress(mod_data)
|
||||
|
||||
# Replace common Windows reserved/illegal filename characters
|
||||
mod_fname = re.sub(r'[\\/*?:"<>|]', '_', mod_hdr.Name.decode('utf-8','replace').strip())
|
||||
|
||||
with open(os.path.join(output_path, mod_fname), 'wb') as out : out.write(mod_data) # Store SCT Entry Data/File
|
||||
|
||||
print('\n Extracted Phoenix SCT BIOS executable!')
|
||||
|
||||
input('\nDone!')
|
||||
|
||||
sys.exit(0)
|
243
Phoenix_TDK_Extract.py
Normal file
243
Phoenix_TDK_Extract.py
Normal file
|
@ -0,0 +1,243 @@
|
|||
#!/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_a10'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import lzma
|
||||
import ctypes
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
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_t
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
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 __init__(self, mz_base, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.Base = mz_base
|
||||
|
||||
def get_name(self):
|
||||
return self.Name.decode('utf-8','replace').strip()
|
||||
|
||||
def get_offset(self):
|
||||
return self.Base + self.Offset
|
||||
|
||||
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.get_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)
|
||||
|
||||
# Get Phoenix TDK Executable (MZ) Base Offset
|
||||
def get_tdk_base(in_buffer, pack_off):
|
||||
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 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')
|
||||
|
||||
# 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:], fast=True)
|
||||
|
||||
# Parse detected MZ > PE > Info
|
||||
pe_info = get_pe_info(pe_file)
|
||||
|
||||
# Parse detected MZ > PE > Info > Product Name
|
||||
pe_name = pe_info.get(b'ProductName',b'')
|
||||
except Exception:
|
||||
# Any error means no MZ > PE > Info > Product Name
|
||||
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
|
||||
|
||||
# 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):
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
return bool(get_phoenix_tdk(buffer)[1] is not None)
|
||||
|
||||
# Parse & Extract Phoenix Tools Development Kit (TDK) Packer
|
||||
def phoenix_tdk_extract(input_file, extract_path, padding=0):
|
||||
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:
|
||||
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
|
||||
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, is_phoenix_tdk, phoenix_tdk_extract).run_utility()
|
|
@ -1,174 +0,0 @@
|
|||
#!/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)
|
136
Portwell_EFI_Extract.py
Normal file
136
Portwell_EFI_Extract.py
Normal file
|
@ -0,0 +1,136 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Portwell EFI Extract
|
||||
Portwell EFI Update Extractor
|
||||
Copyright (C) 2021-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'Portwell EFI Update Extractor v2.0_a12'
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_efi import efi_decompress, is_efi_compressed
|
||||
from common.path_ops import make_dirs, safe_name
|
||||
from common.pe_ops import get_pe_file
|
||||
from common.patterns import PAT_MICROSOFT_MZ, PAT_PORTWELL_EFI
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
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 Exception:
|
||||
pe_buffer = b''
|
||||
|
||||
is_mz = PAT_MICROSOFT_MZ.search(in_buffer[:0x2]) # EFI images start with PE Header MZ
|
||||
|
||||
is_uu = PAT_PORTWELL_EFI.search(pe_buffer[:0x4]) # Portwell EFI files start with <UU>
|
||||
|
||||
return bool(is_mz and is_uu)
|
||||
|
||||
# Get PE of Portwell EFI executable
|
||||
def get_portwell_pe(in_buffer):
|
||||
pe_file = get_pe_file(in_buffer, fast=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_file, extract_path, padding=0):
|
||||
efi_files = [] # Initialize EFI Payload file chunks
|
||||
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
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)
|
||||
|
||||
# Split EFI Payload into <UU> file chunks
|
||||
efi_list = list(PAT_PORTWELL_EFI.finditer(pe_data))
|
||||
for idx,val in enumerate(efi_list):
|
||||
efi_bgn = val.end()
|
||||
efi_end = len(pe_data) if idx == len(efi_list) - 1 else efi_list[idx + 1].start()
|
||||
efi_files.append(pe_data[efi_bgn:efi_end])
|
||||
|
||||
parse_efi_files(extract_path, efi_files, 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(f'[{file_index}] {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__':
|
||||
BIOSUtility(TITLE, is_portwell_efi, portwell_efi_extract).run_utility()
|
63
Toshiba_COM_Extract.py
Normal file
63
Toshiba_COM_Extract.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Toshiba COM Extract
|
||||
Toshiba BIOS COM Extractor
|
||||
Copyright (C) 2018-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'Toshiba BIOS COM Extractor v2.0_a4'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.externals import get_comextract_path
|
||||
from common.path_ops import make_dirs, path_stem, path_suffixes
|
||||
from common.patterns import PAT_TOSHIBA_COM
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is Toshiba BIOS COM image
|
||||
def is_toshiba_com(in_file):
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
is_ext = path_suffixes(in_file)[-1].upper() == '.COM' if os.path.isfile(in_file) else True
|
||||
|
||||
is_com = PAT_TOSHIBA_COM.search(buffer)
|
||||
|
||||
return is_ext and is_com
|
||||
|
||||
# Parse & Extract Toshiba BIOS COM image
|
||||
def toshiba_com_extract(input_file, extract_path, padding=0):
|
||||
if not os.path.isfile(input_file):
|
||||
printer('Error: Could not find input file path!', padding)
|
||||
|
||||
return 1
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
output_name = path_stem(input_file)
|
||||
output_file = os.path.join(extract_path, f'{output_name}.bin')
|
||||
|
||||
try:
|
||||
subprocess.run([get_comextract_path(), input_file, output_file], check=True, stdout=subprocess.DEVNULL)
|
||||
|
||||
if not os.path.isfile(output_file):
|
||||
raise Exception('EXTRACT_FILE_MISSING')
|
||||
except Exception:
|
||||
printer(f'Error: ToshibaComExtractor could not extract file {input_file}!', padding)
|
||||
|
||||
return 2
|
||||
|
||||
printer(f'Succesfull {output_name} extraction via ToshibaComExtractor!', padding)
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_toshiba_com, toshiba_com_extract).run_utility()
|
|
@ -1,126 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
VAIO Package Extractor
|
||||
VAIO Packaging Manager Extractor
|
||||
Copyright (C) 2019-2020 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
print('VAIO Packaging Manager Extractor v2.0')
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
if len(sys.argv) >= 2 :
|
||||
# Drag & Drop or CLI
|
||||
vaio_exec = sys.argv[1:]
|
||||
else :
|
||||
# Folder path
|
||||
vaio_exec = []
|
||||
in_path = input('\nEnter the full folder path: ')
|
||||
print('\nWorking...')
|
||||
for root, dirs, files in os.walk(in_path):
|
||||
for name in files :
|
||||
vaio_exec.append(os.path.join(root, name))
|
||||
|
||||
# Microsoft CAB Header XOR 0xFF (Tag[4] + Res[4] + Size[4] + Res[4] + Offset[4] + Res[4] + Ver[2]) pattern
|
||||
mscf_pattern = re.compile(br'\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL)
|
||||
|
||||
# VAIO Packaging Manager Configuration file ("[Setting]" + Windows_new_line) pattern
|
||||
vaio_pattern = re.compile(br'\x5B\x53\x65\x74\x74\x69\x6E\x67\x5D\x0D\x0A')
|
||||
|
||||
# VAIO Packaging Manager Configuration file entry "UseVAIOCheck" pattern
|
||||
check_pattern = re.compile(br'\x0A\x55\x73\x65\x56\x41\x49\x4F\x43\x68\x65\x63\x6B\x3D')
|
||||
|
||||
# VAIO Packaging Manager Configuration file entry "ExtractPathByUser" pattern
|
||||
path_pattern = re.compile(br'\x0A\x45\x78\x74\x72\x61\x63\x74\x50\x61\x74\x68\x42\x79\x55\x73\x65\x72\x3D')
|
||||
|
||||
for input_file in vaio_exec :
|
||||
file_path = os.path.abspath(input_file)
|
||||
file_dir = os.path.dirname(file_path)
|
||||
file_name = os.path.basename(file_path)
|
||||
|
||||
print('\nFile: ' + file_name)
|
||||
|
||||
# Open Locked VAIO Packaging Manager executable as mutable bytearray
|
||||
with open(input_file, 'rb') as in_file : vaio_data = bytearray(in_file.read())
|
||||
|
||||
match_mscf = mscf_pattern.search(vaio_data) # Search for Microsoft CAB Header XOR 0xFF pattern
|
||||
|
||||
match_vaio = vaio_pattern.search(vaio_data) if not match_mscf else None # Search for VAIO Packaging Manager Configuration file
|
||||
|
||||
# Check if Microsoft CAB Header XOR 0xFF pattern exists
|
||||
if match_mscf :
|
||||
print('\n Detected Obfuscation!')
|
||||
|
||||
# Determine the Microsoft CAB image Size
|
||||
cab_size = int.from_bytes(vaio_data[match_mscf.start() + 0x8:match_mscf.start() + 0xC], 'little') # Get LE XOR-ed CAB Size
|
||||
xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB Size XOR value
|
||||
cab_size = cab_size ^ xor_size # Perform XOR 0xFF and get actual CAB Size
|
||||
|
||||
print('\n Removing Obfuscation...')
|
||||
|
||||
# Determine the Microsoft CAB image Data
|
||||
cab_data = int.from_bytes(vaio_data[match_mscf.start():match_mscf.start() + cab_size], 'big') # Get BE XOR-ed CAB Data
|
||||
xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB Data XOR value
|
||||
cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB Data
|
||||
|
||||
print('\n Extracting...')
|
||||
|
||||
with open('vaio_temp.cab', 'wb') as cab_file : cab_file.write(cab_data) # Create temporary CAB image
|
||||
|
||||
extr_path = os.path.join(file_dir, file_name[:-4], '') # Create CAB image extraction path
|
||||
|
||||
try :
|
||||
decomp = subprocess.run(['7z', 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + extr_path, 'vaio_temp.cab']) # 7-Zip
|
||||
|
||||
print('\n Extracted!')
|
||||
except :
|
||||
print('\n Error: Could not decompress Microsoft CAB image!')
|
||||
print(' Make sure that "7z" executable exists!')
|
||||
|
||||
os.remove('vaio_temp.cab') # Remove temporary CAB image
|
||||
|
||||
# Check if VAIO Packaging Manager Configuration file pattern exists
|
||||
elif match_vaio :
|
||||
print('\n Error: Failed to Extract, attempting to Unlock instead...')
|
||||
|
||||
# Initialize VAIO Package Configuration file variables (assume overkill size of 0x500)
|
||||
info_start, info_end, val_false, val_true = [match_vaio.start(), match_vaio.start() + 0x500, b'', b'']
|
||||
|
||||
# Get VAIO Package Configuration file info, split at new_line and stop at payload DOS header (EOF)
|
||||
vaio_info = vaio_data[info_start:info_end].split(b'\x0D\x0A\x4D\x5A')[0].replace(b'\x0D',b'').split(b'\x0A')
|
||||
|
||||
# Determine VAIO Package Configuration file True & False values
|
||||
for info in vaio_info :
|
||||
if info.startswith(b'ExtractPathByUser=') : val_false = bytearray(b'0' if info[18:] in (b'0',b'1') else info[18:]) # Should be 0/No/False
|
||||
if info.startswith(b'UseCompression=') : val_true = bytearray(b'1' if info[15:] in (b'0',b'1') else info[15:]) # Should be 1/Yes/True
|
||||
else :
|
||||
if val_false == val_true or not val_false or not val_true :
|
||||
print('\n Error: Could not determine True/False values!')
|
||||
print(' Please report this VAIO Packaging Manager!')
|
||||
continue # Next input file
|
||||
|
||||
# Find and replace UseVAIOCheck entry from 1/Yes/True to 0/No/False
|
||||
UseVAIOCheck = check_pattern.search(vaio_data[info_start:])
|
||||
if UseVAIOCheck : vaio_data[info_start + UseVAIOCheck.end():info_start + UseVAIOCheck.end() + len(val_false)] = val_false
|
||||
else : print('\n Error: Could not find UseVAIOCheck entry!')
|
||||
|
||||
# Find and replace ExtractPathByUser entry from 0/No/False to 1/Yes/True
|
||||
ExtractPathByUser = path_pattern.search(vaio_data[info_start:])
|
||||
if ExtractPathByUser : vaio_data[info_start + ExtractPathByUser.end():info_start + ExtractPathByUser.end() + len(val_false)] = val_true
|
||||
else : print('\n Error: Could not find ExtractPathByUser entry!')
|
||||
|
||||
# Store Unlocked VAIO Packaging Manager executable
|
||||
if UseVAIOCheck and ExtractPathByUser :
|
||||
with open(os.path.join(file_dir, file_name + '_Unlocked.exe'), 'wb') as unl_file : unl_file.write(vaio_data)
|
||||
print('\n Unlocked!')
|
||||
|
||||
else :
|
||||
print('\n Error: No VAIO Packaging Manager found!')
|
||||
continue # Next input file
|
||||
|
||||
else :
|
||||
input('\nDone!')
|
147
VAIO_Package_Extract.py
Normal file
147
VAIO_Package_Extract.py
Normal file
|
@ -0,0 +1,147 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
VAIO Package Extractor
|
||||
VAIO Packaging Manager Extractor
|
||||
Copyright (C) 2019-2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
TITLE = 'VAIO Packaging Manager Extractor v3.0_a8'
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Stop __pycache__ generation
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
from common.comp_szip import is_szip_supported, szip_decompress
|
||||
from common.path_ops import make_dirs
|
||||
from common.patterns import PAT_VAIO_CAB, PAT_VAIO_CFG, PAT_VAIO_CHK, PAT_VAIO_EXT
|
||||
from common.system import printer
|
||||
from common.templates import BIOSUtility
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is VAIO Packaging Manager
|
||||
def is_vaio_pkg(in_file):
|
||||
buffer = file_to_bytes(in_file)
|
||||
|
||||
return bool(PAT_VAIO_CFG.search(buffer))
|
||||
|
||||
# Extract VAIO Packaging Manager executable
|
||||
def vaio_cabinet(name, buffer, extract_path, padding=0):
|
||||
match_cab = PAT_VAIO_CAB.search(buffer) # Microsoft CAB Header XOR 0xFF
|
||||
|
||||
if not match_cab:
|
||||
return 1
|
||||
|
||||
printer('Detected obfuscated CAB archive!', padding)
|
||||
|
||||
# Determine the Microsoft CAB image size
|
||||
cab_size = int.from_bytes(buffer[match_cab.start() + 0x8:match_cab.start() + 0xC], 'little') # Get LE XOR-ed CAB size
|
||||
xor_size = int.from_bytes(b'\xFF' * 0x4, 'little') # Create CAB size XOR value
|
||||
cab_size ^= xor_size # Perform XOR 0xFF and get actual CAB size
|
||||
|
||||
printer('Removing obfuscation...', padding + 4)
|
||||
|
||||
# Determine the Microsoft CAB image Data
|
||||
cab_data = int.from_bytes(buffer[match_cab.start():match_cab.start() + cab_size], 'big') # Get BE XOR-ed CAB data
|
||||
xor_data = int.from_bytes(b'\xFF' * cab_size, 'big') # Create CAB data XOR value
|
||||
cab_data = (cab_data ^ xor_data).to_bytes(cab_size, 'big') # Perform XOR 0xFF and get actual CAB data
|
||||
|
||||
printer('Extracting archive...', padding + 4)
|
||||
|
||||
cab_path = os.path.join(extract_path, f'{name}_Temporary.cab')
|
||||
|
||||
with open(cab_path, 'wb') as cab_file:
|
||||
cab_file.write(cab_data) # Create temporary CAB archive
|
||||
|
||||
if is_szip_supported(cab_path, padding + 8, check=True):
|
||||
if szip_decompress(cab_path, extract_path, 'CAB', padding + 8, check=True) == 0:
|
||||
os.remove(cab_path) # Successful extraction, delete temporary CAB archive
|
||||
else:
|
||||
return 3
|
||||
else:
|
||||
return 2
|
||||
|
||||
return 0
|
||||
|
||||
# Unlock VAIO Packaging Manager executable
|
||||
def vaio_unlock(name, buffer, extract_path, padding=0):
|
||||
match_cfg = PAT_VAIO_CFG.search(buffer)
|
||||
|
||||
if not match_cfg:
|
||||
return 1
|
||||
|
||||
printer('Attempting to Unlock executable!', padding)
|
||||
|
||||
# Initialize VAIO Package Configuration file variables (assume overkill size of 0x500)
|
||||
cfg_bgn,cfg_end,cfg_false,cfg_true = [match_cfg.start(), match_cfg.start() + 0x500, b'', b'']
|
||||
|
||||
# Get VAIO Package Configuration file info, split at new_line and stop at payload DOS header (EOF)
|
||||
cfg_info = buffer[cfg_bgn:cfg_end].split(b'\x0D\x0A\x4D\x5A')[0].replace(b'\x0D',b'').split(b'\x0A')
|
||||
|
||||
printer('Retrieving True/False values...', padding + 4)
|
||||
|
||||
# Determine VAIO Package Configuration file True & False values
|
||||
for info in cfg_info:
|
||||
if info.startswith(b'ExtractPathByUser='):
|
||||
cfg_false = bytearray(b'0' if info[18:] in (b'0',b'1') else info[18:]) # Should be 0/No/False
|
||||
if info.startswith(b'UseCompression='):
|
||||
cfg_true = bytearray(b'1' if info[15:] in (b'0',b'1') else info[15:]) # Should be 1/Yes/True
|
||||
|
||||
# Check if valid True/False values have been retrieved
|
||||
if cfg_false == cfg_true or not cfg_false or not cfg_true:
|
||||
printer('Error: Could not retrieve True/False values!', padding + 8)
|
||||
return 2
|
||||
|
||||
printer('Adjusting UseVAIOCheck entry...', padding + 4)
|
||||
|
||||
# Find and replace UseVAIOCheck entry from 1/Yes/True to 0/No/False
|
||||
vaio_check = PAT_VAIO_CHK.search(buffer[cfg_bgn:])
|
||||
if vaio_check:
|
||||
buffer[cfg_bgn + vaio_check.end():cfg_bgn + vaio_check.end() + len(cfg_true)] = cfg_false
|
||||
else:
|
||||
printer('Error: Could not find entry UseVAIOCheck!', padding + 8)
|
||||
return 3
|
||||
|
||||
printer('Adjusting ExtractPathByUser entry...', padding + 4)
|
||||
|
||||
# Find and replace ExtractPathByUser entry from 0/No/False to 1/Yes/True
|
||||
user_path = PAT_VAIO_EXT.search(buffer[cfg_bgn:])
|
||||
if user_path:
|
||||
buffer[cfg_bgn + user_path.end():cfg_bgn + user_path.end() + len(cfg_false)] = cfg_true
|
||||
else:
|
||||
printer('Error: Could not find entry ExtractPathByUser!', padding + 8)
|
||||
return 4
|
||||
|
||||
printer('Storing unlocked executable...', padding + 4)
|
||||
|
||||
# Store Unlocked VAIO Packaging Manager executable
|
||||
if vaio_check and user_path:
|
||||
unlock_path = os.path.join(extract_path, f'{name}_Unlocked.exe')
|
||||
with open(unlock_path, 'wb') as unl_file:
|
||||
unl_file.write(buffer)
|
||||
|
||||
return 0
|
||||
|
||||
# Parse & Extract or Unlock VAIO Packaging Manager
|
||||
def vaio_pkg_extract(input_file, extract_path, padding=0):
|
||||
input_buffer = file_to_bytes(input_file)
|
||||
|
||||
input_name = os.path.basename(input_file)
|
||||
|
||||
make_dirs(extract_path, delete=True)
|
||||
|
||||
if vaio_cabinet(input_name, input_buffer, extract_path, padding) == 0:
|
||||
printer('Successfully Extracted!', padding)
|
||||
elif vaio_unlock(input_name, bytearray(input_buffer), extract_path, padding) == 0:
|
||||
printer('Successfully Unlocked!', padding)
|
||||
else:
|
||||
printer('Error: Failed to Extract or Unlock executable!', padding)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
BIOSUtility(TITLE, is_vaio_pkg, vaio_pkg_extract).run_utility()
|
25
common/checksums.py
Normal file
25
common/checksums.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
# Get Checksum 16-bit
|
||||
def get_chk_16(data, value=0, order='little'):
|
||||
for idx in range(0, len(data), 2):
|
||||
# noinspection PyTypeChecker
|
||||
value += int.from_bytes(data[idx:idx + 2], order)
|
||||
|
||||
value &= 0xFFFF
|
||||
|
||||
return value
|
||||
|
||||
# Get Checksum 8-bit XOR
|
||||
def get_chk_8_xor(data, value=0):
|
||||
for byte in data:
|
||||
value ^= byte
|
||||
|
||||
value ^= 0x0
|
||||
|
||||
return value
|
57
common/comp_efi.py
Normal file
57
common/comp_efi.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from common.path_ops import project_root, safe_path
|
||||
from common.system import get_os_ver, printer
|
||||
|
||||
def get_compress_sizes(data):
|
||||
size_compress = int.from_bytes(data[0x0:0x4], 'little')
|
||||
size_original = int.from_bytes(data[0x4:0x8], 'little')
|
||||
|
||||
return size_compress, size_original
|
||||
|
||||
def is_efi_compressed(data, strict=True):
|
||||
size_comp,size_orig = get_compress_sizes(data)
|
||||
|
||||
check_diff = size_comp < size_orig
|
||||
|
||||
if strict:
|
||||
check_size = size_comp + 0x8 == len(data)
|
||||
else:
|
||||
check_size = size_comp + 0x8 <= len(data)
|
||||
|
||||
return check_diff and check_size
|
||||
|
||||
# Get TianoCompress path
|
||||
def get_tiano_path():
|
||||
exec_name = f'TianoCompress{".exe" if get_os_ver()[1] else ""}'
|
||||
|
||||
return safe_path(project_root(), ['external',exec_name])
|
||||
|
||||
# EFI/Tiano Decompression via TianoCompress
|
||||
def efi_decompress(in_path, out_path, padding=0, silent=False, comp_type='--uefi'):
|
||||
try:
|
||||
subprocess.run([get_tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type], check=True, stdout=subprocess.DEVNULL)
|
||||
|
||||
with open(in_path, 'rb') as file:
|
||||
_,size_orig = get_compress_sizes(file.read())
|
||||
|
||||
if os.path.getsize(out_path) != size_orig:
|
||||
raise Exception('EFI_DECOMPRESS_ERROR')
|
||||
except Exception:
|
||||
if not silent:
|
||||
printer(f'Error: TianoCompress could not extract file {in_path}!', padding)
|
||||
|
||||
return 1
|
||||
|
||||
if not silent:
|
||||
printer('Succesfull EFI decompression via TianoCompress!', padding)
|
||||
|
||||
return 0
|
72
common/comp_szip.py
Normal file
72
common/comp_szip.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from common.path_ops import project_root, safe_path
|
||||
from common.system import get_os_ver, printer
|
||||
|
||||
# Get 7-Zip path
|
||||
def get_szip_path():
|
||||
exec_name = '7z.exe' if get_os_ver()[1] else '7zzs'
|
||||
|
||||
return safe_path(project_root(), ['external',exec_name])
|
||||
|
||||
# Check 7-Zip bad exit codes (0 OK, 1 Warning)
|
||||
def check_bad_exit_code(exit_code):
|
||||
if exit_code not in (0,1):
|
||||
raise Exception(f'BAD_EXIT_CODE_{exit_code}')
|
||||
|
||||
# Check if file is 7-Zip supported
|
||||
def is_szip_supported(in_path, padding=0, args=None, check=False, silent=False):
|
||||
try:
|
||||
if args is None:
|
||||
args = []
|
||||
|
||||
szip_c = [get_szip_path(), 't', in_path, *args, '-bso0', '-bse0', '-bsp0']
|
||||
|
||||
szip_t = subprocess.run(szip_c, check=False)
|
||||
|
||||
if check:
|
||||
check_bad_exit_code(szip_t.returncode)
|
||||
except Exception:
|
||||
if not silent:
|
||||
printer(f'Error: 7-Zip could not check support for file {in_path}!', padding)
|
||||
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# Archive decompression via 7-Zip
|
||||
def szip_decompress(in_path, out_path, in_name, padding=0, args=None, check=False, silent=False):
|
||||
if not in_name:
|
||||
in_name = 'archive'
|
||||
|
||||
try:
|
||||
if args is None:
|
||||
args = []
|
||||
|
||||
szip_c = [get_szip_path(), 'x', *args, '-aou', '-bso0', '-bse0', '-bsp0', f'-o{out_path}', in_path]
|
||||
|
||||
szip_x = subprocess.run(szip_c, check=False)
|
||||
|
||||
if check:
|
||||
check_bad_exit_code(szip_x.returncode)
|
||||
|
||||
if not os.path.isdir(out_path):
|
||||
raise Exception('EXTRACT_DIR_MISSING')
|
||||
except Exception:
|
||||
if not silent:
|
||||
printer(f'Error: 7-Zip could not extract {in_name} file {in_path}!', padding)
|
||||
|
||||
return 1
|
||||
|
||||
if not silent:
|
||||
printer(f'Succesfull {in_name} decompression via 7-Zip!', padding)
|
||||
|
||||
return 0
|
38
common/externals.py
Normal file
38
common/externals.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
from common.path_ops import project_root, safe_path
|
||||
from common.system import get_os_ver
|
||||
|
||||
# https://github.com/allowitsme/big-tool by Dmitry Frolov
|
||||
# https://github.com/platomav/BGScriptTool by Plato Mavropoulos
|
||||
def get_bgs_tool():
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from external.big_script_tool import BigScript # pylint: disable=E0401,E0611
|
||||
except Exception:
|
||||
BigScript = None
|
||||
|
||||
return BigScript
|
||||
|
||||
# Get UEFIFind path
|
||||
def get_uefifind_path():
|
||||
exec_name = f'UEFIFind{".exe" if get_os_ver()[1] else ""}'
|
||||
|
||||
return safe_path(project_root(), ['external', exec_name])
|
||||
|
||||
# Get UEFIExtract path
|
||||
def get_uefiextract_path():
|
||||
exec_name = f'UEFIExtract{".exe" if get_os_ver()[1] else ""}'
|
||||
|
||||
return safe_path(project_root(), ['external', exec_name])
|
||||
|
||||
# Get ToshibaComExtractor path
|
||||
def get_comextract_path():
|
||||
exec_name = f'comextract{".exe" if get_os_ver()[1] else ""}'
|
||||
|
||||
return safe_path(project_root(), ['external', exec_name])
|
14
common/num_ops.py
Normal file
14
common/num_ops.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
# https://leancrew.com/all-this/2020/06/ordinals-in-python/ by Dr. Drang
|
||||
def get_ordinal(number):
|
||||
s = ('th', 'st', 'nd', 'rd') + ('th',) * 10
|
||||
|
||||
v = number % 100
|
||||
|
||||
return f'{number}{s[v % 10]}' if v > 13 else f'{number}{s[v]}'
|
154
common/path_ops.py
Normal file
154
common/path_ops.py
Normal file
|
@ -0,0 +1,154 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import stat
|
||||
import shutil
|
||||
from pathlib import Path, PurePath
|
||||
|
||||
from common.text_ops import is_encased, to_string
|
||||
|
||||
# Fix illegal/reserved Windows characters
|
||||
def safe_name(in_name):
|
||||
name_repr = repr(in_name).strip("'")
|
||||
|
||||
return re.sub(r'[\\/:"*?<>|]+', '_', name_repr)
|
||||
|
||||
# Check and attempt to fix illegal/unsafe OS path traversals
|
||||
def safe_path(base_path, user_paths):
|
||||
# Convert base path to absolute path
|
||||
base_path = real_path(base_path)
|
||||
|
||||
# Merge user path(s) to string with OS separators
|
||||
user_path = to_string(user_paths, os.sep)
|
||||
|
||||
# Create target path from base + requested user path
|
||||
target_path = norm_path(base_path, user_path)
|
||||
|
||||
# Check if target path is OS illegal/unsafe
|
||||
if is_safe_path(base_path, target_path):
|
||||
return target_path
|
||||
|
||||
# Re-create target path from base + leveled/safe illegal "path" (now file)
|
||||
nuked_path = norm_path(base_path, safe_name(user_path))
|
||||
|
||||
# Check if illegal path leveling worked
|
||||
if is_safe_path(base_path, nuked_path):
|
||||
return nuked_path
|
||||
|
||||
# Still illegal, raise exception to halt execution
|
||||
raise Exception(f'ILLEGAL_PATH_TRAVERSAL: {user_path}')
|
||||
|
||||
# Check for illegal/unsafe OS path traversal
|
||||
def is_safe_path(base_path, target_path):
|
||||
base_path = real_path(base_path)
|
||||
|
||||
target_path = real_path(target_path)
|
||||
|
||||
common_path = os.path.commonpath((base_path, target_path))
|
||||
|
||||
return base_path == common_path
|
||||
|
||||
# Create normalized base path + OS separator + user path
|
||||
def norm_path(base_path, user_path):
|
||||
return os.path.normpath(base_path + os.sep + user_path)
|
||||
|
||||
# Get absolute path, resolving any symlinks
|
||||
def real_path(in_path):
|
||||
return os.path.realpath(in_path)
|
||||
|
||||
# Get Windows/Posix OS agnostic path
|
||||
def agnostic_path(in_path):
|
||||
return PurePath(in_path.replace('\\', os.sep))
|
||||
|
||||
# Get absolute parent of path
|
||||
def path_parent(in_path):
|
||||
return Path(in_path).parent.absolute()
|
||||
|
||||
# Get final path component, with suffix
|
||||
def path_name(in_path):
|
||||
return PurePath(in_path).name
|
||||
|
||||
# Get final path component, w/o suffix
|
||||
def path_stem(in_path):
|
||||
return PurePath(in_path).stem
|
||||
|
||||
# Get list of path file extensions
|
||||
def path_suffixes(in_path):
|
||||
return PurePath(in_path).suffixes or ['']
|
||||
|
||||
# Check if path is absolute
|
||||
def is_path_absolute(in_path):
|
||||
return Path(in_path).is_absolute()
|
||||
|
||||
# Create folder(s), controlling parents, existence and prior deletion
|
||||
def make_dirs(in_path, parents=True, exist_ok=False, delete=False):
|
||||
if delete:
|
||||
del_dirs(in_path)
|
||||
|
||||
Path.mkdir(Path(in_path), parents=parents, exist_ok=exist_ok)
|
||||
|
||||
# Delete folder(s), if present
|
||||
def del_dirs(in_path):
|
||||
if Path(in_path).is_dir():
|
||||
shutil.rmtree(in_path, onerror=clear_readonly)
|
||||
|
||||
# Copy file to path with or w/o metadata
|
||||
def copy_file(in_path, out_path, meta=False):
|
||||
if meta:
|
||||
shutil.copy2(in_path, out_path)
|
||||
else:
|
||||
shutil.copy(in_path, out_path)
|
||||
|
||||
# Clear read-only file attribute (on shutil.rmtree error)
|
||||
def clear_readonly(in_func, in_path, _):
|
||||
os.chmod(in_path, stat.S_IWRITE)
|
||||
in_func(in_path)
|
||||
|
||||
# Walk path to get all files
|
||||
def get_path_files(in_path):
|
||||
path_files = []
|
||||
|
||||
for root, _, files in os.walk(in_path):
|
||||
for name in files:
|
||||
path_files.append(os.path.join(root, name))
|
||||
|
||||
return path_files
|
||||
|
||||
# Get path without leading/trailing quotes
|
||||
def get_dequoted_path(in_path):
|
||||
out_path = to_string(in_path).strip()
|
||||
|
||||
if len(out_path) >= 2 and is_encased(out_path, ("'",'"')):
|
||||
out_path = out_path[1:-1]
|
||||
|
||||
return out_path
|
||||
|
||||
# Set utility extraction stem
|
||||
def extract_suffix():
|
||||
return '_extracted'
|
||||
|
||||
# Get utility extraction path
|
||||
def get_extract_path(in_path, suffix=extract_suffix()):
|
||||
return f'{in_path}{suffix}'
|
||||
|
||||
# Get project's root directory
|
||||
def project_root():
|
||||
root = Path(__file__).parent.parent
|
||||
|
||||
return real_path(root)
|
||||
|
||||
# Get runtime's root directory
|
||||
def runtime_root():
|
||||
if getattr(sys, 'frozen', False):
|
||||
root = Path(sys.executable).parent
|
||||
else:
|
||||
root = project_root()
|
||||
|
||||
return real_path(root)
|
34
common/patterns.py
Normal file
34
common/patterns.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
PAT_AMI_PFAT = re.compile(br'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL)
|
||||
PAT_AMI_UCP = re.compile(br'@(UAF|HPU).{12}@', re.DOTALL)
|
||||
PAT_APPLE_EFI = re.compile(br'\$IBIOSI\$.{16}\x2E\x00.{6}\x2E\x00.{8}\x2E\x00.{6}\x2E\x00.{20}\x00{2}', re.DOTALL)
|
||||
PAT_APPLE_IM4P = re.compile(br'\x16\x04IM4P\x16\x04mefi')
|
||||
PAT_APPLE_PBZX = re.compile(br'pbzx')
|
||||
PAT_APPLE_PKG = re.compile(br'xar!')
|
||||
PAT_AWARD_LZH = re.compile(br'-lh[04567]-')
|
||||
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_PKG = re.compile(br'\x72\x13\x55\x00.{45}7zXZ', re.DOTALL)
|
||||
PAT_FUJITSU_SFX = re.compile(br'FjSfxBinay\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL)
|
||||
PAT_INSYDE_IFL = re.compile(br'\$_IFLASH')
|
||||
PAT_INSYDE_SFX = re.compile(br'\x0D\x0A;!@InstallEnd@!\x0D\x0A(7z\xBC\xAF\x27|\x6E\xF4\x79\x5F\x4E)')
|
||||
PAT_INTEL_ENG = re.compile(br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', re.DOTALL)
|
||||
PAT_INTEL_IFD = re.compile(br'\x5A\xA5\xF0\x0F.{172}\xFF{16}', re.DOTALL)
|
||||
PAT_MICROSOFT_CAB = re.compile(br'MSCF\x00{4}')
|
||||
PAT_MICROSOFT_MZ = re.compile(br'MZ')
|
||||
PAT_MICROSOFT_PE = re.compile(br'PE\x00{2}')
|
||||
PAT_PHOENIX_TDK = re.compile(br'\$PACK\x00{3}..\x00{2}.\x00{3}', re.DOTALL)
|
||||
PAT_PORTWELL_EFI = re.compile(br'<U{2}>')
|
||||
PAT_TOSHIBA_COM = re.compile(br'\x00{2}[\x00-\x02]BIOS.{20}[\x00\x01]', re.DOTALL)
|
||||
PAT_VAIO_CAB = re.compile(br'\xB2\xAC\xBC\xB9\xFF{4}.{4}\xFF{4}.{4}\xFF{4}\xFC\xFE', re.DOTALL)
|
||||
PAT_VAIO_CFG = re.compile(br'\[Setting]\x0D\x0A')
|
||||
PAT_VAIO_CHK = re.compile(br'\x0AUseVAIOCheck=')
|
||||
PAT_VAIO_EXT = re.compile(br'\x0AExtractPathByUser=')
|
49
common/pe_ops.py
Normal file
49
common/pe_ops.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import pefile
|
||||
|
||||
from common.system import printer
|
||||
from common.text_ops import file_to_bytes
|
||||
|
||||
# Check if input is a PE file
|
||||
def is_pe_file(in_file):
|
||||
return bool(get_pe_file(in_file))
|
||||
|
||||
# Get pefile object from PE file
|
||||
def get_pe_file(in_file, fast=True):
|
||||
in_buffer = file_to_bytes(in_file)
|
||||
|
||||
try:
|
||||
# Analyze detected MZ > PE image buffer
|
||||
pe_file = pefile.PE(data=in_buffer, fast_load=fast)
|
||||
except Exception:
|
||||
pe_file = None
|
||||
|
||||
return pe_file
|
||||
|
||||
# Get PE info from pefile object
|
||||
def get_pe_info(pe_file):
|
||||
try:
|
||||
# When fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed prior to FileInfo > StringTable
|
||||
pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']])
|
||||
|
||||
# Retrieve MZ > PE > FileInfo > StringTable information
|
||||
pe_info = pe_file.FileInfo[0][0].StringTable[0].entries
|
||||
except Exception:
|
||||
pe_info = {}
|
||||
|
||||
return pe_info
|
||||
|
||||
# Print PE info from pefile StringTable
|
||||
def show_pe_info(pe_info, padding=0):
|
||||
if type(pe_info).__name__ == 'dict':
|
||||
for title,value in pe_info.items():
|
||||
info_title = title.decode('utf-8','ignore').strip()
|
||||
info_value = value.decode('utf-8','ignore').strip()
|
||||
if info_title and info_value:
|
||||
printer(f'{info_title}: {info_value}', padding, new_line=False)
|
28
common/struct_ops.py
Normal file
28
common/struct_ops.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
|
||||
char = ctypes.c_char
|
||||
uint8_t = ctypes.c_ubyte
|
||||
uint16_t = ctypes.c_ushort
|
||||
uint32_t = ctypes.c_uint
|
||||
uint64_t = ctypes.c_uint64
|
||||
|
||||
# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky
|
||||
def get_struct(buffer, start_offset, class_name, param_list=None):
|
||||
if param_list is None:
|
||||
param_list = []
|
||||
|
||||
structure = class_name(*param_list) # Unpack parameter list
|
||||
struct_len = ctypes.sizeof(structure)
|
||||
struct_data = buffer[start_offset:start_offset + struct_len]
|
||||
fit_len = min(len(struct_data), struct_len)
|
||||
|
||||
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)
|
||||
|
||||
return structure
|
68
common/system.py
Normal file
68
common/system.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from common.text_ops import padder, to_string
|
||||
|
||||
# Get Python Version (tuple)
|
||||
def get_py_ver():
|
||||
return sys.version_info
|
||||
|
||||
# Get OS Platform (string)
|
||||
def get_os_ver():
|
||||
sys_os = sys.platform
|
||||
|
||||
is_win = sys_os == 'win32'
|
||||
is_lnx = sys_os.startswith('linux') or sys_os == 'darwin' or sys_os.find('bsd') != -1
|
||||
|
||||
return sys_os, is_win, is_win or is_lnx
|
||||
|
||||
# Check for --auto-exit|-e
|
||||
def is_auto_exit():
|
||||
return bool('--auto-exit' in sys.argv or '-e' in sys.argv)
|
||||
|
||||
# Check Python Version
|
||||
def check_sys_py():
|
||||
sys_py = get_py_ver()
|
||||
|
||||
if sys_py < (3,10):
|
||||
sys.stdout.write(f'\nError: Python >= 3.10 required, not {sys_py[0]}.{sys_py[1]}!')
|
||||
|
||||
if not is_auto_exit():
|
||||
# noinspection PyUnresolvedReferences
|
||||
(raw_input if sys_py[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602
|
||||
|
||||
sys.exit(125)
|
||||
|
||||
# Check OS Platform
|
||||
def check_sys_os():
|
||||
os_tag,os_win,os_sup = get_os_ver()
|
||||
|
||||
if not os_sup:
|
||||
printer(f'Error: Unsupported platform "{os_tag}"!')
|
||||
|
||||
if not is_auto_exit():
|
||||
input('\nPress enter to exit')
|
||||
|
||||
sys.exit(126)
|
||||
|
||||
# Fix Windows Unicode console redirection
|
||||
if os_win:
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Show message(s) while controlling padding, newline, pausing & separator
|
||||
def printer(in_message='', padd_count=0, new_line=True, pause=False, sep_char=' '):
|
||||
message = to_string(in_message, sep_char)
|
||||
|
||||
padding = padder(padd_count)
|
||||
|
||||
newline = '\n' if new_line else ''
|
||||
|
||||
output = newline + padding + message
|
||||
|
||||
(input if pause and not is_auto_exit() else print)(output)
|
162
common/templates.py
Normal file
162
common/templates.py
Normal file
|
@ -0,0 +1,162 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import ctypes
|
||||
import argparse
|
||||
import traceback
|
||||
|
||||
from common.num_ops import get_ordinal
|
||||
from common.path_ops import get_dequoted_path, get_extract_path, get_path_files, is_path_absolute, path_parent, runtime_root, safe_path
|
||||
from common.system import check_sys_os, check_sys_py, get_os_ver, is_auto_exit, printer
|
||||
|
||||
class BIOSUtility:
|
||||
|
||||
MAX_FAT32_ITEMS = 65535
|
||||
|
||||
def __init__(self, title, check, main, padding=0):
|
||||
self._title = title
|
||||
self._main = main
|
||||
self._check = check
|
||||
self._padding = padding
|
||||
self._arguments_kw = {}
|
||||
|
||||
# Initialize argparse argument parser
|
||||
self._argparser = argparse.ArgumentParser()
|
||||
|
||||
self._argparser.add_argument('files', type=argparse.FileType('r', encoding='utf-8'), nargs='*')
|
||||
self._argparser.add_argument('-e', '--auto-exit', help='skip all user action prompts', action='store_true')
|
||||
self._argparser.add_argument('-v', '--version', help='show utility name and version', action='store_true')
|
||||
self._argparser.add_argument('-o', '--output-dir', help='extract in given output directory')
|
||||
self._argparser.add_argument('-i', '--input-dir', help='extract from given input directory')
|
||||
|
||||
self._arguments,self._arguments_unk = self._argparser.parse_known_args()
|
||||
|
||||
# Managed Python exception handler
|
||||
sys.excepthook = self._exception_handler
|
||||
|
||||
# Check Python Version
|
||||
check_sys_py()
|
||||
|
||||
# Check OS Platform
|
||||
check_sys_os()
|
||||
|
||||
# Show Script Title
|
||||
printer(self._title, new_line=False)
|
||||
|
||||
# Show Utility Version on demand
|
||||
if self._arguments.version:
|
||||
sys.exit(0)
|
||||
|
||||
# Set console/terminal window title (Windows only)
|
||||
if get_os_ver()[1]:
|
||||
ctypes.windll.kernel32.SetConsoleTitleW(self._title)
|
||||
|
||||
# Process input files and generate output path
|
||||
self._process_input_files()
|
||||
|
||||
# Count input files for exit code
|
||||
self._exit_code = len(self._input_files)
|
||||
|
||||
def parse_argument(self, *args, **kwargs):
|
||||
_dest = self._argparser.add_argument(*args, **kwargs).dest
|
||||
self._arguments = self._argparser.parse_known_args(self._arguments_unk)[0]
|
||||
self._arguments_kw.update({_dest: self._arguments.__dict__[_dest]})
|
||||
|
||||
def run_utility(self):
|
||||
for _input_file in self._input_files:
|
||||
_input_name = os.path.basename(_input_file)
|
||||
|
||||
printer(['***', _input_name], self._padding)
|
||||
|
||||
if not self._check(_input_file):
|
||||
printer('Error: This is not a supported input!', self._padding + 4)
|
||||
|
||||
continue # Next input file
|
||||
|
||||
_extract_path = os.path.join(self._output_path, get_extract_path(_input_name))
|
||||
|
||||
if os.path.isdir(_extract_path):
|
||||
for _suffix in range(2, self.MAX_FAT32_ITEMS):
|
||||
_renamed_path = f'{os.path.normpath(_extract_path)}_{get_ordinal(_suffix)}'
|
||||
|
||||
if not os.path.isdir(_renamed_path):
|
||||
_extract_path = _renamed_path
|
||||
|
||||
break # Extract path is now unique
|
||||
|
||||
if self._main(_input_file, _extract_path, self._padding + 4, **self._arguments_kw) in [0, None]:
|
||||
self._exit_code -= 1
|
||||
|
||||
printer('Done!', pause=True)
|
||||
|
||||
sys.exit(self._exit_code)
|
||||
|
||||
# Process input files
|
||||
def _process_input_files(self):
|
||||
self._input_files = []
|
||||
|
||||
if len(sys.argv) >= 2:
|
||||
# Drag & Drop or CLI
|
||||
if self._arguments.input_dir:
|
||||
_input_path_user = self._arguments.input_dir
|
||||
_input_path_full = self._get_input_path(_input_path_user) if _input_path_user else ''
|
||||
self._input_files = get_path_files(_input_path_full)
|
||||
else:
|
||||
# Parse list of input files (i.e. argparse FileType objects)
|
||||
for _file_object in self._arguments.files:
|
||||
# Store each argparse FileType object's name (i.e. path)
|
||||
self._input_files.append(_file_object.name)
|
||||
# Close each argparse FileType object (i.e. allow input file changes)
|
||||
_file_object.close()
|
||||
|
||||
# Set output fallback value for missing argparse Output and Input Path
|
||||
_output_fallback = path_parent(self._input_files[0]) if self._input_files else None
|
||||
|
||||
# Set output path via argparse Output path or argparse Input path or first input file path
|
||||
_output_path = self._arguments.output_dir or self._arguments.input_dir or _output_fallback
|
||||
else:
|
||||
# Script w/o parameters
|
||||
_input_path_user = get_dequoted_path(input('\nEnter input directory path: '))
|
||||
_input_path_full = self._get_input_path(_input_path_user) if _input_path_user else ''
|
||||
self._input_files = get_path_files(_input_path_full)
|
||||
|
||||
_output_path = get_dequoted_path(input('\nEnter output directory path: '))
|
||||
|
||||
self._output_path = self._get_input_path(_output_path)
|
||||
|
||||
# Get absolute input file path
|
||||
@staticmethod
|
||||
def _get_input_path(input_path):
|
||||
if not input_path:
|
||||
# Use runtime directory if no user path is specified
|
||||
absolute_path = runtime_root()
|
||||
else:
|
||||
# Check if user specified path is absolute
|
||||
if is_path_absolute(input_path):
|
||||
absolute_path = input_path
|
||||
# Otherwise, make it runtime directory relative
|
||||
else:
|
||||
absolute_path = safe_path(runtime_root(), input_path)
|
||||
|
||||
return absolute_path
|
||||
|
||||
# https://stackoverflow.com/a/781074 by Torsten Marek
|
||||
@staticmethod
|
||||
def _exception_handler(exc_type, exc_value, exc_traceback):
|
||||
if exc_type is KeyboardInterrupt:
|
||||
printer('')
|
||||
else:
|
||||
printer('Error: Utility crashed, please report the following:\n')
|
||||
|
||||
traceback.print_exception(exc_type, exc_value, exc_traceback)
|
||||
|
||||
if not is_auto_exit():
|
||||
input('\nPress enter to exit')
|
||||
|
||||
sys.exit(127)
|
33
common/text_ops.py
Normal file
33
common/text_ops.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env python3
|
||||
#coding=utf-8
|
||||
|
||||
"""
|
||||
Copyright (C) 2022 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
# Generate padding (spaces or tabs)
|
||||
def padder(padd_count, tab=False):
|
||||
return ('\t' if tab else ' ') * padd_count
|
||||
|
||||
# Get String from given input object
|
||||
def to_string(in_object, sep_char=''):
|
||||
if type(in_object).__name__ in ('list','tuple'):
|
||||
out_string = sep_char.join(map(str, in_object))
|
||||
else:
|
||||
out_string = str(in_object)
|
||||
|
||||
return out_string
|
||||
|
||||
# Get Bytes from given buffer or file path
|
||||
def file_to_bytes(in_object):
|
||||
object_bytes = in_object
|
||||
|
||||
if type(in_object).__name__ not in ('bytes','bytearray'):
|
||||
with open(to_string(in_object), 'rb') as object_data:
|
||||
object_bytes = object_data.read()
|
||||
|
||||
return object_bytes
|
||||
|
||||
# Check if string starts and ends with given character(s)
|
||||
def is_encased(in_string, chars):
|
||||
return in_string.startswith(chars) and in_string.endswith(chars)
|
2
external/requirements.txt
vendored
Normal file
2
external/requirements.txt
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
lznt1 >= 0.2
|
||||
pefile >= 2022.5.30
|
Loading…
Add table
Add a link
Reference in a new issue