Improved AMI UCP > NAL unpacking

Fix potential illegal path traversals
This commit is contained in:
platomav 2022-04-15 18:17:58 +03:00
parent 672b4b2321
commit 40686d5edf
13 changed files with 102 additions and 43 deletions

View file

@ -7,7 +7,7 @@ AMI UCP BIOS Extractor
Copyright (C) 2021-2022 Plato Mavropoulos
"""
title = 'AMI UCP BIOS Extractor v2.0_a6'
title = 'AMI UCP BIOS Extractor v2.0_a7'
import os
import re
@ -16,6 +16,7 @@ import shutil
import struct
import ctypes
import contextlib
from pathlib import Path, PurePath
# Stop __pycache__ generation
sys.dont_write_bytecode = True
@ -23,10 +24,11 @@ sys.dont_write_bytecode = True
from common.a7z_comp import a7z_decompress, is_7z_supported
from common.checksums import get_chk_16
from common.efi_comp import efi_decompress, is_efi_compressed
from common.path_ops import safe_name
from common.path_ops import get_safe_name, get_safe_path
from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG
from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t
from common.system import script_init, argparse_init, printer
from common.text_ops import to_string
from AMI_PFAT_Extract import get_ami_pfat, parse_pfat_file
@ -211,7 +213,7 @@ def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False):
printer('Utility Configuration Program', padding)
extract_path = os.path.join(out_path + '_extracted', '')
extract_path = os.path.join(out_path + '_extracted')
if os.path.isdir(extract_path): shutil.rmtree(extract_path)
@ -259,7 +261,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na
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] # Always prefer @NAL naming first
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 = 'BIOS_0%s.bin' % uaf_tag[3:] # BIOS/PFAT Firmware
@ -278,10 +280,16 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na
# 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('Note: Detected new AMI UCP Module %s (%s) in @NAL!' % (uaf_tag, nal_dict[uaf_tag]), padding + 4, pause=True)
printer('Note: Detected new AMI UCP Module %s (%s) in @NAL!' % (uaf_tag, nal_dict[uaf_tag][1]), padding + 4, pause=True)
# Generate @UAF|@HPU Module File name, depending on whether decompression will be required
uaf_fname = os.path.join(extract_path, safe_name(uaf_name + ('.temp' if is_comp else uaf_fext)))
uaf_sname = get_safe_name(uaf_name + ('.temp' if is_comp else uaf_fext))
if uaf_tag in nal_dict:
uaf_npath = get_safe_path(extract_path, nal_dict[uaf_tag][0])
Path.mkdir(Path(uaf_npath), parents=True, exist_ok=True)
uaf_fname = get_safe_path(uaf_npath, uaf_sname)
else:
uaf_fname = get_safe_path(extract_path, uaf_sname)
if is_checksum: chk16_validate(uaf_data_all, uaf_tag, padding + 4)
@ -375,15 +383,19 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na
# Parse all @NAL Module Entries
for info in nal_info:
info_tag,info_val = info.split(':',1)
info_tag,info_value = info.split(':',1)
printer(info_tag + ' : ' + info_val, padding + 8, False) # Print @NAL Module Tag-Path Info
printer(info_tag + ' : ' + info_value, padding + 8, False) # Print @NAL Module Tag-Path Info
nal_dict[info_tag] = os.path.basename(info_val) # Assign a file name (w/o path) to each Tag
info_part = PurePath(info_value.replace('\\', os.sep)).parts # Split 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_7z_supported(uaf_fname):
ins_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory
ins_dir = os.path.join(extract_path, get_safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory
printer('Insyde BIOS 7z SFX Archive:', padding + 4)
@ -394,7 +406,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na
pfat_match,pfat_buffer = get_ami_pfat(uaf_data_raw)
if pfat_match:
pfat_dir = os.path.join(extract_path, safe_name(uaf_name))
pfat_dir = os.path.join(extract_path, get_safe_name(uaf_name))
parse_pfat_file(pfat_buffer, pfat_dir, padding + 4)
@ -410,7 +422,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na
# Parse Nested AMI UCP Structure
if nested_uaf_off:
uaf_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory
uaf_dir = os.path.join(extract_path, get_safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory
ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum) # Call recursively