Files
chipsec-chipsec/chipsec/modules/common/uefi/s3bootscript.py
T
2021-08-26 10:51:56 -07:00

204 lines
8.9 KiB
Python

#CHIPSEC: Platform Security Assessment Framework
#Copyright (c) 2010-2021, 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>]``
- ``-a <script_address>``: Specify the bootscript address
Where:
- ``[]``: optional line
Examples:
>>> chipsec_main.py -m common.uefi.s3bootscript
>>> chipsec_main.py -m common.uefi.s3bootscript -a 0x00000000BDE10000
.. NOTE::
Requires an OS with UEFI Runtime API support.
"""
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("OS does not support UEFI Runtime API")
self.res = ModuleResult.NOTAPPLICABLE
return supported
def is_inside_SMRAM(self, pa):
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:
n_dispatch += 1
dispatchstr = "Dispatch opcode (off 0x{:04X}) with entry-point 0x{:016X}".format(e.offset_in_script, e.decoded_opcode.entrypoint)
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_warning("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('S3 Boot-Script and Dispatch entry-points do not appear to be protected')
else:
# DISPATCH_OPCODES_PROTECTED
status = ModuleResult.WARNING
self.logger.log_warning('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_warning("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 ):
self.logger.start_test("S3 Resume Boot-Script Protections")
self.res = ModuleResult.ERROR
if len(module_argv) > 2:
self.logger.error( 'Expected module options: -a <bootscript_address>' )
return self.res
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:
self.res = self.check_s3_bootscripts( script_pa )
else:
self.res = 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
self.res = ModuleResult.ERROR
return self.res