Files
chipsec-chipsec/chipsec/modules/common/uefi/s3bootscript.py
T
brentholtsclaw f7fe1be074 More whitespace cleanup
E231, E201, E226

Signed-off-by: brentholtsclaw <brent.holtsclaw@intel.com>
2020-07-31 13:46:13 -07:00

193 lines
8.9 KiB
Python

#CHIPSEC: Platform Security Assessment Framework
#Copyright (c) 2010-2020, 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
#
"""
Checks protections of the S3 resume boot-script implemented by the UEFI based firmware
References:
`VU#976132 UEFI implementations do not properly secure the EFI S3 Resume Boot Path boot script <https://www.kb.cert.org/vuls/id/976132>`_
`Technical Details of the S3 Resume Boot Script Vulnerability <http://www.intelsecurity.com/advanced-threat-research/content/WP_Intel_ATR_S3_ResBS_Vuln.pdf>`_ by Intel Security's Advanced Threat Research team.
`Attacks on UEFI Security <https://events.ccc.de/congress/2014/Fahrplan/system/attachments/2557/original/AttacksOnUEFI_Slides.pdf>`_ by Rafal Wojtczuk and Corey Kallenberg.
`Attacking UEFI Boot Script <https://bromiumlabs.files.wordpress.com/2015/01/venamis_whitepaper.pdf>`_ by Rafal Wojtczuk and Corey Kallenberg.
`Exploiting UEFI boot script table vulnerability <http://blog.cr4.sh/2015/02/exploiting-uefi-boot-script-table.html>`_ by Dmytro Oleksiuk.
Usage:
>>> chipsec_main.py -m common.uefi.s3bootscript [-a <script_address>]
Examples:
>>> chipsec_main.py -m common.uefi.s3bootscript
>>> chipsec_main.py -m common.uefi.s3bootscript -a 0x00000000BDE10000
"""
from chipsec.module_common import BaseModule, ModuleResult, MTAG_BIOS, MTAG_SMM, MTAG_SECUREBOOT
from chipsec.defines import BOUNDARY_1MB, BOUNDARY_4GB
from chipsec.hal.uefi import UEFI, parse_script
from chipsec.hal.uefi_common import S3BootScriptOpcode
TAGS = [MTAG_BIOS, MTAG_SMM, MTAG_SECUREBOOT]
########################################################################################################
#
# Main module functionality
#
########################################################################################################
BOOTSCRIPT_OK = 0x0
BOOTSCRIPT_INSIDE_SMRAM = 0x1
BOOTSCRIPT_OUTSIDE_SMRAM = 0x2
DISPATCH_OPCODES_UNPROTECTED = 0x4
DISPATCH_OPCODES_PROTECTED = 0x8
HIGH_BIOS_RANGE_SIZE = 2 *BOUNDARY_1MB
class s3bootscript(BaseModule):
def __init__(self):
BaseModule.__init__(self)
self._uefi = UEFI( self.cs )
def is_supported(self):
supported = self.cs.helper.EFI_supported()
if not supported: self.logger.log_skipped_check( "OS does not support UEFI Runtime API" )
return supported
def is_inside_SMRAM( self, pa ):
#return ( (self.smrrbase & self.smrrmask) == ( pa & self.smrrmask ) )
return ( pa >= self.smrambase and pa < self.smramlimit)
def is_inside_SPI( self, pa ):
return ( pa >= (BOUNDARY_4GB - HIGH_BIOS_RANGE_SIZE) and pa < BOUNDARY_4GB )
def check_dispatch_opcodes( self, bootscript_entries ):
self.logger.log( '[*] Checking entry-points of Dispatch opcodes..' )
dispatch_ep_ok = True
n_dispatch = 0
for e in bootscript_entries:
if e.decoded_opcode is None: continue
if S3BootScriptOpcode.EFI_BOOT_SCRIPT_DISPATCH_OPCODE == e.decoded_opcode.opcode:
#chipsec.hal.uefi_common.S3BootScriptOpcode.EFI_BOOT_SCRIPT_DISPATCH_2_OPCODE == e.decoded_opcode.opcode:
n_dispatch += 1
dispatchstr = "Dispatch opcode (off 0x{:04X}) with entry-point 0x{:016X}".format(e.offset_in_script, e.decoded_opcode.entrypoint)
#self.logger.log( e )
if not self.is_inside_SMRAM( e.decoded_opcode.entrypoint ) and not self.is_inside_SPI( e.decoded_opcode.entrypoint ):
dispatch_ep_ok = False
self.logger.log_bad( dispatchstr + " > UNPROTECTED" )
else:
self.logger.log_good( dispatchstr + " > PROTECTED" )
self.logger.log( "[*] Found {:d} Dispatch opcodes".format(n_dispatch) )
return dispatch_ep_ok
def check_s3_bootscript( self, bootscript_pa ):
res = BOOTSCRIPT_OK
self.logger.log( "[*] Checking S3 boot-script at 0x{:016X}".format(bootscript_pa) )
# Checking if it's in SMRAM
scriptInsideSMRAM = self.is_inside_SMRAM( bootscript_pa )
if scriptInsideSMRAM:
res |= BOOTSCRIPT_INSIDE_SMRAM
self.logger.log_good( 'S3 boot-script is in SMRAM' )
self.logger.log_important( "Note: the test could not verify Dispatch opcodes because the script is in SMRAM. Entry-points of Dispatch opcodes also need to be protected." )
else:
res |= BOOTSCRIPT_OUTSIDE_SMRAM
self.logger.log_bad( 'S3 boot-script is not in SMRAM' )
self.logger.log( '[*] Reading S3 boot-script from memory..' )
script_all = self.cs.mem.read_physical_mem( bootscript_pa, 0x100000 )
self.logger.log( '[*] Decoding S3 boot-script opcodes..' )
script_entries = parse_script( script_all, False )
dispatch_opcodes_ok = self.check_dispatch_opcodes( script_entries )
if dispatch_opcodes_ok:
res |= DISPATCH_OPCODES_PROTECTED
self.logger.log_important( "S3 boot-script is not in protected memory but didn't find unprotected Dispatch entry-points" )
else:
res |= DISPATCH_OPCODES_UNPROTECTED
self.logger.log_bad( 'Entry-points of Dispatch opcodes in S3 boot-script are not in protected memory' )
return res
def check_s3_bootscripts( self, bsaddress = None ):
res = 0
scriptInsideSMRAM = False
if bsaddress:
bootscript_PAs = [ bsaddress ]
else:
found, bootscript_PAs = self._uefi.find_s3_bootscript()
if not found:
self.logger.log_good( "Didn't find any S3 boot-scripts in EFI variables" )
self.logger.log_warn_check( "S3 Boot-Script was not found. Firmware may be using other ways to store/locate it, or OS might be blocking access." )
return ModuleResult.WARNING
self.logger.log_important( 'Found {:d} S3 boot-script(s) in EFI variables'.format(len(bootscript_PAs)) )
for bootscript_pa in bootscript_PAs:
if 0 == bootscript_pa: continue
res |= self.check_s3_bootscript( bootscript_pa )
self.logger.log('')
if (res & BOOTSCRIPT_OUTSIDE_SMRAM) != 0:
# BOOTSCRIPT_OUTSIDE_SMRAM
if (res & DISPATCH_OPCODES_UNPROTECTED) != 0:
# DISPATCH_OPCODES_UNPROTECTED
status = ModuleResult.FAILED
self.logger.log_failed_check( 'S3 Boot-Script and Dispatch entry-points do not appear to be protected' )
else:
# DISPATCH_OPCODES_PROTECTED
status = ModuleResult.WARNING
self.logger.log_warn_check( 'S3 Boot-Script is not in SMRAM but Dispatch entry-points appear to be protected. Recommend further testing' )
else:
# BOOTSCRIPT_INSIDE_SMRAM
status = ModuleResult.WARNING
self.logger.log_warn_check( "S3 Boot-Script is inside SMRAM. The script is protected but Dispatch opcodes cannot be inspected" )
self.logger.log_important( "Additional testing of the S3 boot-script can be done using tools.uefi.s3script_modify" )
return status
def run( self, module_argv ):
if len(module_argv) > 2:
self.logger.error( 'Expected module options: -a <bootscript_address>' )
return ModuleResult.ERROR
self.logger.start_test( "S3 Resume Boot-Script Protections" )
script_pa = None
if len(module_argv) > 0:
script_pa = int(module_argv[0], 16)
self.logger.log( '[*] Using manually assigned S3 Boot-Script table base: 0x{:016X}'.format(script_pa) )
(self.smrambase, self.smramlimit, self.smramsize) = self.cs.cpu.get_SMRAM()
if self.smrambase is not None and self.smramlimit is not None:
self.logger.log( '[*] SMRAM: Base = 0x{:016X}, Limit = 0x{:016X}, Size = 0x{:08X}'.format(self.smrambase, self.smramlimit, self.smramsize) )
try:
if script_pa is not None: return self.check_s3_bootscripts( script_pa )
else: return self.check_s3_bootscripts( )
except:
self.logger.error("The module was not able to recognize the S3 resume boot script on this platform.")
if self.logger.VERBOSE: raise
return ModuleResult.ERROR