Files
chipsec-chipsec/chipsec/modules/tools/secureboot/te.py
T
2019-09-24 12:23:01 -07:00

530 lines
21 KiB
Python

#CHIPSEC: Platform Security Assessment Framework
#Copyright (c) 2010-2019, Intel Corporation
#
#This program is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public License
#as published by the Free Software Foundation; Version 2.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#Contact information:
#chipsec@intel.com
#
"""
Tool to test for 'TE Header' vulnerability in Secure Boot implementations as described in
`All Your Boot Are Belong To Us <https://cansecwest.com/slides/2014/AllYourBoot_csw14-intel-final.pdf>`_
Usage:
``chipsec_main.py -m tools.secureboot.te [-a <mode>,<cfg_file>,<efi_file>]``
- ``<mode>``
* ``generate_te`` (default) convert PE EFI binary ``<efi_file>`` to TE binary
* ``replace_bootloader`` replace bootloader files listed in ``<cfg_file>`` on ESP with modified ``<efi_file>``
* ``restore_bootloader`` restore original bootloader files from ``.bak`` files
- ``<cfg_file>`` path to config file listing paths to bootloader files to replace
- ``<efi_file>`` path to EFI binary to convert to TE binary. If no file path is provided, the tool will look for Shell.efi
Examples:
Convert Shell.efi PE/COFF EFI executable to TE executable:
``chipsec_main.py -m tools.secureboot.te -a generate_te,Shell.efi``
Replace bootloaders listed in te.cfg file with TE version of Shell.efi executable:
``chipsec_main.py -m tools.secureboot.te -a replace_bootloader,te.cfg,Shell.efi``
Restore bootloaders listed in te.cfg file:
``chipsec_main.py -m tools.secureboot.te -a restore_bootloader,te.cfg``
"""
import os
import shutil
import struct
import sys
from chipsec.module_common import BaseModule, ModuleResult
from chipsec.module_common import cs_input
from chipsec.logger import logger
DEFAULT_PE_FILE_PATH = "chipsec/modules/tools/secureboot/Shell.efi"
DEFAULT_CONFIG_FILE_PATH = 'chipsec/modules/tools/secureboot/te.cfg'
# typedef struct _IMAGE_DOS_HEADER
# {
# WORD e_magic;
# WORD e_cblp;
# WORD e_cp;
# WORD e_crlc;
# WORD e_cparhdr;
# WORD e_minalloc;
# WORD e_maxalloc;
# WORD e_ss;
# WORD e_sp;
# WORD e_csum;
# WORD e_ip;
# WORD e_cs;
# WORD e_lfarlc;
# WORD e_ovno;
# WORD e_res[4];
# WORD e_oemid;
# WORD e_oeminfo;
# WORD e_res2[10];
# LONG e_lfanew;
# } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
IMAGE_DOS_HEADER = "<14H4HHH10Hi"
IMAGE_DOS_HEADER_size = struct.calcsize(IMAGE_DOS_HEADER)
E_MAGIC = 0x5A4D
E_MAGIC_STR = "MZ"
# typedef struct _IMAGE_DATA_DIRECTORY
# {
# ULONG VirtualAddress;
# ULONG Size;
# } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
IMAGE_DATA_DIRECTORY = "<II"
IMAGE_DATA_DIRECTORY_size = struct.calcsize(IMAGE_DATA_DIRECTORY)
IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16
# typedef struct _IMAGE_OPTIONAL_HEADER
# {
# WORD Magic;
# UCHAR MajorLinkerVersion;
# UCHAR MinorLinkerVersion;
# ULONG SizeOfCode;
# ULONG SizeOfInitializedData;
# ULONG SizeOfUninitializedData;
# ULONG AddressOfEntryPoint;
# ULONG BaseOfCode;
# ULONG BaseOfData;
# ULONG ImageBase;
# ULONG SectionAlignment;
# ULONG FileAlignment;
# WORD MajorOperatingSystemVersion;
# WORD MinorOperatingSystemVersion;
# WORD MajorImageVersion;
# WORD MinorImageVersion;
# WORD MajorSubsystemVersion;
# WORD MinorSubsystemVersion;
# ULONG Win32VersionValue;
# ULONG SizeOfImage;
# ULONG SizeOfHeaders;
# ULONG CheckSum;
# WORD Subsystem;
# WORD DllCharacteristics;
# ULONG SizeOfStackReserve;
# ULONG SizeOfStackCommit;
# ULONG SizeOfHeapReserve;
# ULONG SizeOfHeapCommit;
# ULONG LoaderFlags;
# ULONG NumberOfRvaAndSizes;
# IMAGE_DATA_DIRECTORY DataDirectory[16];
# } IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
IMAGE_OPTIONAL_HEADER = "<HBB9I6H4I2H6I"
IMAGE_OPTIONAL_HEADER_size = struct.calcsize(IMAGE_OPTIONAL_HEADER)
IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b
# typedef struct {
# //
# // Standard fields.
# //
# UINT16 Magic;
# UINT8 MajorLinkerVersion;
# UINT8 MinorLinkerVersion;
# UINT32 SizeOfCode;
# UINT32 SizeOfInitializedData;
# UINT32 SizeOfUninitializedData;
# UINT32 AddressOfEntryPoint;
# UINT32 BaseOfCode;
# //
# // NT additional fields.
# //
# UINT64 ImageBase;
# UINT32 SectionAlignment;
# UINT32 FileAlignment;
# UINT16 MajorOperatingSystemVersion;
# UINT16 MinorOperatingSystemVersion;
# UINT16 MajorImageVersion;
# UINT16 MinorImageVersion;
# UINT16 MajorSubsystemVersion;
# UINT16 MinorSubsystemVersion;
# UINT32 Win32VersionValue;
# UINT32 SizeOfImage;
# UINT32 SizeOfHeaders;
# UINT32 CheckSum;
# UINT16 Subsystem;
# UINT16 DllCharacteristics;
# UINT64 SizeOfStackReserve;
# UINT64 SizeOfStackCommit;
# UINT64 SizeOfHeapReserve;
# UINT64 SizeOfHeapCommit;
# UINT32 LoaderFlags;
# UINT32 NumberOfRvaAndSizes;
# EFI_IMAGE_DATA_DIRECTORY DataDirectory[EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES];
# } EFI_IMAGE_OPTIONAL_HEADER64;
IMAGE_OPTIONAL_HEADER64 = "<HBBIIIIIQIIHHHHHHIIIIHHQQQQII"
IMAGE_OPTIONAL_HEADER64_size = struct.calcsize(IMAGE_OPTIONAL_HEADER64)
IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b
# typedef struct _IMAGE_FILE_HEADER
# {
# WORD Machine;
# WORD NumberOfSections;
# ULONG TimeDateStamp;
# ULONG PointerToSymbolTable;
# ULONG NumberOfSymbols;
# WORD SizeOfOptionalHeader;
# WORD Characteristics;
# } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
IMAGE_FILE_HEADER = "<2H3I2H"
IMAGE_FILE_HEADER_size = struct.calcsize(IMAGE_FILE_HEADER)
# typedef struct _IMAGE_NT_HEADERS
# {
# ULONG Signature;
# IMAGE_FILE_HEADER FileHeader;
# IMAGE_OPTIONAL_HEADER OptionalHeader;
# } IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;
IMAGE_NT_SIGNATURE = 0x00004550
IMAGE_NT_HEADERS_size = (4 + IMAGE_FILE_HEADER_size + IMAGE_OPTIONAL_HEADER_size + IMAGE_NUMBEROF_DIRECTORY_ENTRIES * IMAGE_DATA_DIRECTORY_size)
# typedef struct _IMAGE_SECTION_HEADER
# {
# UCHAR Name[8];
# ULONG Misc;
# ULONG VirtualAddress;
# ULONG SizeOfRawData;
# ULONG PointerToRawData;
# ULONG PointerToRelocations;
# ULONG PointerToLinenumbers;
# WORD NumberOfRelocations;
# WORD NumberOfLinenumbers;
# ULONG Characteristics;
# } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
IMAGE_SECTION_HEADER = "<8s6I2HI"
IMAGE_SECTION_HEADER_size = struct.calcsize(IMAGE_SECTION_HEADER)
# PE executable structure
#
# MS-DOS header
# ...
# e_lfanew -------------------+
# |
# |
# IMAGE_NT_HEADERS Header <----+
# ...
# SECTION TABLE
# ...
# TE header
#
# typedef struct {
# UINT16 Signature; // signature for TE format = "VZ"
# UINT16 Machine; // from the original file header
# UINT8 NumberOfSections; // from the original file header
# UINT8 Subsystem; // from original optional header
# UINT16 StrippedSize; // how many bytes we removed from the header
# UINT32 AddressOfEntryPoint; // offset to entry point -- from original optional header
# UINT32 BaseOfCode; // from original image -- required for ITP debug
# UINT64 ImageBase; // from original file header
# IMAGE_DATA_DIRECTORY DataDirectory[2]; // only base relocation and debug directory
# } EFI_TE_IMAGE_HEADER;
EFI_TE_IMAGE_HEADER = "<HHBBHIIQ"
EFI_TE_IMAGE_HEADER_SIGNATURE = 0x5A56
EFI_IMAGE_DIRECTORY_ENTRY_EXPORT = 0
EFI_IMAGE_DIRECTORY_ENTRY_IMPORT = 1
EFI_IMAGE_DIRECTORY_ENTRY_RESOURCE = 2
EFI_IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3
EFI_IMAGE_DIRECTORY_ENTRY_SECURITY = 4
EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC = 5
EFI_IMAGE_DIRECTORY_ENTRY_DEBUG = 6
EFI_IMAGE_DIRECTORY_ENTRY_COPYRIGHT = 7
EFI_IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8
EFI_IMAGE_DIRECTORY_ENTRY_TLS = 9
EFI_IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG =10
EFI_TE_IMAGE_DIRECTORY_ENTRY_BASERELOC = 0
EFI_TE_IMAGE_DIRECTORY_ENTRY_DEBUG = 1
def IsValidPEHeader(data):
size = len(data)
if size < IMAGE_DOS_HEADER_size:
#print "size < IMAGE_DOS_HEADER_size"
return False
signature, = struct.unpack("<H", data[:2])
if (signature != E_MAGIC):
#print "signature != E_MAGIC, 0x%04X != 0x%04X" % (E_MAGIC)
return False
e_lfanew, = struct.unpack("<I", data[IMAGE_DOS_HEADER_size - 4:IMAGE_DOS_HEADER_size])
if (e_lfanew >= size):
#print "e_lfanew >= size"
return False
if ((size - e_lfanew) < IMAGE_NT_HEADERS_size):
#print "(size - e_lfanew) < IMAGE_NT_HEADERS_size"
return False
pe_signature, = struct.unpack("<I", data[e_lfanew:e_lfanew+4])
if (pe_signature != IMAGE_NT_SIGNATURE):
#print "pe_signature != IMAGE_NT_SIGNATURE"
return False
return True
def replace_header(data):
if (not IsValidPEHeader(data)):
return None
size = len(data)
e_lfanew, = struct.unpack("<I", data[IMAGE_DOS_HEADER_size - 4:IMAGE_DOS_HEADER_size])
# TimeDateStamp, PointerToSymbolTable, NumberOfSymbols, SizeOfOptionalHeader, Characteristics
Machine, NumberOfSections, u1, u2, u3, SizeOfOptionalHeader, u5 \
= struct.unpack(IMAGE_FILE_HEADER, data[e_lfanew+4:e_lfanew+4+IMAGE_FILE_HEADER_size])
StrippedSize = e_lfanew + 4 + IMAGE_FILE_HEADER_size + SizeOfOptionalHeader
if (StrippedSize > size):
#print " *** strip more bytes than the file size"
return None
if (StrippedSize & ~0xffff):
#print " *** strip more than 64K bytes"
return None
dof = e_lfanew+4+IMAGE_FILE_HEADER_size
Magic, = struct.unpack("<H", data[dof:dof+2])
if (Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC):
Magic, MajorLinkerVersion, MinorLinkerVersion, SizeOfCode, SizeOfInitializedData, \
SizeOfUninitializedData, AddressOfEntryPoint, BaseOfCode, BaseOfData, ImageBase, \
SectionAlignment, FileAlignment, MajorOperatingSystemVersion, MinorOperatingSystemVersion, \
MajorImageVersion, MinorImageVersion, MajorSubsystemVersion, MinorSubsystemVersion, \
Win32VersionValue, SizeOfImage, SizeOfHeaders, CheckSum, Subsystem, DllCharacteristics, \
SizeOfStackReserve, SizeOfStackCommit, SizeOfHeapReserve, SizeOfHeapCommit, LoaderFlags, \
NumberOfRvaAndSizes = struct.unpack(IMAGE_OPTIONAL_HEADER, data[dof:dof+IMAGE_OPTIONAL_HEADER_size])
dof = dof + IMAGE_OPTIONAL_HEADER_size
elif (Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC):
Magic, MajorLinkerVersion, MinorLinkerVersion, SizeOfCode, SizeOfInitializedData, \
SizeOfUninitializedData, AddressOfEntryPoint, BaseOfCode, ImageBase, SectionAlignment, \
FileAlignment, MajorOperatingSystemVersion, MinorOperatingSystemVersion, MajorImageVersion, \
MinorImageVersion, MajorSubsystemVersion, MinorSubsystemVersion, Win32VersionValue, \
SizeOfImage, SizeOfHeaders, CheckSum, Subsystem, DllCharacteristics, SizeOfStackReserve, \
SizeOfStackCommit, SizeOfHeapReserve, SizeOfHeapCommit, LoaderFlags, NumberOfRvaAndSizes \
= struct.unpack(IMAGE_OPTIONAL_HEADER64, data[dof:dof+IMAGE_OPTIONAL_HEADER64_size])
dof = dof + IMAGE_OPTIONAL_HEADER64_size
else:
#print " *** Unsupported magic: {:X}".format(Magic)
return None
if (NumberOfSections &~0xFF):
#print " *** NumberOfSections cannot be packed: {:X}".format(NumberOfSections)
return None
if (Subsystem &~0xFF):
#print " *** Subsystem cannot be packed: {:X}".format(NumberOfSections)
return None
basereloc_off = dof + EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC*IMAGE_DATA_DIRECTORY_size
debug_off = dof + EFI_IMAGE_DIRECTORY_ENTRY_DEBUG*IMAGE_DATA_DIRECTORY_size
BASERELOC = "\x00\x00\x00\x00\x00\x00\x00\x00"
DEBUG = "\x00\x00\x00\x00\x00\x00\x00\x00"
if (NumberOfRvaAndSizes > EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC):
BASERELOC = data[basereloc_off:basereloc_off+IMAGE_DATA_DIRECTORY_size]
if (NumberOfRvaAndSizes > EFI_IMAGE_DIRECTORY_ENTRY_DEBUG):
DEBUG = data[debug_off:debug_off+IMAGE_DATA_DIRECTORY_size]
te_header = struct.pack(EFI_TE_IMAGE_HEADER,\
EFI_TE_IMAGE_HEADER_SIGNATURE, Machine, NumberOfSections, Subsystem, StrippedSize, AddressOfEntryPoint, BaseOfCode, ImageBase)
te_data = te_header + BASERELOC + DEBUG + data[StrippedSize:]
return te_data
def produce_te(fname, outfname):
data = ''
with open(fname, 'rb') as f:
data = f.read()
te_data = replace_header(data)
if (te_data is None):
return 0
with open(outfname, 'wb') as fte:
fte.write(te_data)
return 1
def replace_efi_binary(orig_efi_binary, new_efi_binary):
logger().log( "[*] replacing EFI binary '{}'..".format(orig_efi_binary) )
te_binary = new_efi_binary + '.te'
if not os.path.exists(te_binary): produce_te(new_efi_binary, te_binary)
# back up original binary
backup = orig_efi_binary + '.bak'
if not os.path.exists(backup): os.rename(orig_efi_binary, backup)
try:
shutil.copy(te_binary, orig_efi_binary)
except OSError as err:
logger().error( 'Cannot replace binary ({})'.format(err) )
return False
return True
def umount(drive):
import subprocess
if os.path.exists(drive):
res = subprocess.call( ["mountvol.exe", drive, "/D"] )
if res != 0: logger().warn( "Cannot unmount EFI System partition: {:d}".format(res) )
def get_efi_mount():
import subprocess
for l in range(ord('z'), ord('a'), -1):
if not os.path.exists('%c:\\' % l):
res = subprocess.call( ["mountvol.exe", "%c:\\" % l, "/S"] )
if res != 0:
logger().error( "Cannot mount EFI System partition (status = {:d})".format(res) )
return None
return '%c:\\' % l
logger().error( "Cannot mount EFI System partition. No drive letters to use." )
return None
def get_bootloader_paths( cfg_file ):
bootloader_paths = []
fcfg = open( cfg_file, 'r' )
logger().log( "[*] reading paths from '{}'..".format(cfg_file) )
for line in fcfg:
bl_path = line.rstrip()
if bl_path is not None:
logger().log( " adding path '{}'..".format(bl_path) )
bootloader_paths.append( bl_path )
return bootloader_paths
def replace_bootloader( bootloader_paths, new_bootloader_file, do_mount=True ):
logger().log( "[*] Replacing bootloaders on EFI System Partition (ESP).." )
dsk = get_efi_mount() if do_mount else ''
if dsk is None: return False
for pth in bootloader_paths:
bootloader_path = os.path.join(dsk,pth)
if os.path.exists(bootloader_path): replace_efi_binary( bootloader_path, new_bootloader_file )
if do_mount: umount( dsk )
logger().log( "[*] You will need to reboot the system to see the changes" )
return True
def restore_efi_binary( orig_efi_binary ):
logger().log( "[*] Restoring {}..".format(orig_efi_binary) )
backup = orig_efi_binary + ".bak"
if not os.path.exists(backup):
logger().error( "Cannot restore original binary: '{}' not found".format(backup) )
return False
try:
if os.path.exists(orig_efi_binary): os.remove(orig_efi_binary)
os.rename(backup, orig_efi_binary)
except OSError as err:
logger().error( 'Cannot restore original binary ({})'.format(err) )
return False
return True
def restore_bootloader( bootloader_paths, do_mount=True ):
logger().log( "[*] Restoring bootloaders on EFI System Partition (ESP).." )
dsk = get_efi_mount() if do_mount else ''
if dsk is None: return False
for pth in bootloader_paths:
bootloader_path = os.path.join(dsk,pth)
if os.path.exists(bootloader_path): restore_efi_binary( bootloader_path )
if do_mount: umount( dsk )
logger().log( "[*] You will need to reboot the system to see the changes" )
return True
def confirm():
logger().warn("***************************************************************************************")
logger().warn("*")
logger().warn("* RUNNING THIS TOOL MAY RESULT IN UNBOOTABLE OS!")
logger().warn("* USE IT FOR TESTING PURPOSES ON TEST SYSTEMS ONLY")
logger().warn("*")
logger().warn("* The tool converts PE/COFF EFI executables to TE EFI executables.")
logger().warn("* The tool can also automatically replace files (boot loaders)")
logger().warn("* listed in the configuration file with the generated TE executable.")
logger().warn("*")
logger().warn("* If after reboot, TE executable runs then the firmware doesn't properly")
logger().warn("* enforce Secure Boot checks on TE EFI executables")
logger().warn("*")
logger().warn("* If TE executable doesn't run then the firmware correctly blocked it.")
logger().warn("* To restore OS boot loader in this case you may use one of the following:")
logger().warn("* - Disable Secure Boot in BIOS, boot to external drive (e.g. Linux or UEFI shell)")
logger().warn("* then restore original boot loader executables from .bak files")
logger().warn("* - On Windows, use recovery mode which should automatically restore correct executables")
logger().warn("*")
logger().warn("***************************************************************************************")
s = cs_input( "Type 'yes' to continue running the tool > " )
if s != 'yes': sys.exit( 0 )
def usage():
logger().log( 'Usage:\n' + \
'chipsec_main.py -m tools.secureboot.te [-a <mode>,<cfg_file>,<efi_file>]\n' + \
' <mode>\n' + \
' generate_te - (default) convert PE EFI binary <efi_file> to TE binary\n' + \
' replace_bootloader - replace bootloader files listed in <cfg_file> on ESP with modified <efi_file>\n' + \
' restore_bootloader - restore original bootloader files from .bak files\n' + \
' <cfg_file> - path to config file listing paths to bootloader files to replace\n' + \
' <efi_file> - path to EFI binary to convert to TE binary\n' + \
' If no file path is provided, the tool will look for Shell.efi\n' )
class te(BaseModule):
def __init__(self):
BaseModule.__init__(self)
def is_supported(self):
#win8 = self.cs.helper.is_win8_or_greater()
efi_mode = self.cs.helper.EFI_supported()
if not efi_mode: self.logger.log_skipped_check( "OS did not boot in UEFI mode" )
return efi_mode
def run( self, module_argv ):
self.logger.start_test( "'TE Header' Secure Boot Bypass Test" )
usage()
sts = False
do_mount = True
file_path = DEFAULT_PE_FILE_PATH
te_cfg = DEFAULT_CONFIG_FILE_PATH
mode = module_argv[0] if len(module_argv) > 0 else 'generate_te'
if 'generate_te' == mode:
if len(module_argv) > 1: file_path = module_argv[1]
if not os.path.exists( file_path ):
self.logger.error( "Cannot find file '{}'".format(file_path) )
return ModuleResult.ERROR
sts = replace_efi_binary( file_path, file_path )
elif 'restore_bootloader' == mode or 'replace_bootloader' == mode:
confirm()
if len(module_argv) > 1: te_cfg = module_argv[1]
if not os.path.exists( te_cfg ):
self.logger.error( "Cannot find file '{}'".format(te_cfg) )
return ModuleResult.ERROR
bootloader_paths = get_bootloader_paths( te_cfg )
if 0 == len(bootloader_paths):
self.logger.log( "[*] no bootloaders to replace. Exit.." )
return ModuleResult.SKIPPED
do_mount = self.cs.helper.is_windows() # @TODO
if 'restore_bootloader' == mode:
sts = restore_bootloader( bootloader_paths, do_mount )
elif 'replace_bootloader' == mode:
if len(module_argv) > 2: file_path = module_argv[2]
sts = replace_bootloader( bootloader_paths, file_path, do_mount )
else:
self.logger.error( "Invalid mode: '{}'".format(mode) )
return (ModuleResult.PASSED if sts else ModuleResult.ERROR)