mirror of
https://github.com/chipsec/chipsec
synced 2026-06-08 13:31:00 +00:00
f7fe1be074
E231, E201, E226 Signed-off-by: brentholtsclaw <brent.holtsclaw@intel.com>
193 lines
8.9 KiB
Python
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
|