class access_uefispec(BaseModule): def __init__(self): BaseModule.__init__(self) self._uefi = UEFI(self.cs) nv = EFI_VARIABLE_NON_VOLATILE bs = EFI_VARIABLE_BOOTSERVICE_ACCESS rt = EFI_VARIABLE_RUNTIME_ACCESS ta = EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS self.uefispec_vars = { #### From UEFI Spec Table 11 "Global Variables" "LangCodes": bs | rt, "Lang": nv | bs | rt, "Timeout": nv | bs | rt, "PlatformLangCodes": bs | rt, "PlatformLang": nv | bs | rt, "ConIn": nv | bs | rt, "ConOut": nv | bs | rt, "ErrOut": nv | bs | rt, "ConInDev": bs | rt, "ConOutDev": bs | rt, "ErrOutDev": bs | rt, "Boot0001": nv | bs | rt, "Boot0002": nv | bs | rt, "BootOrder": nv | bs | rt, "BootNext": nv | bs | rt, "BootCurrent": bs | rt, "BootOptionSupport": bs | rt, "Driver0001": nv | bs | rt, "DriverOrder": nv | bs | rt, "Key0001": nv | bs | rt, "HwErrRecSupport": nv | bs | rt, # HwErrRecSupport should be RO "SetupMode": bs | rt, # SetupMode should be RO "KEK": nv | bs | rt | ta, "PK": nv | bs | rt | ta, "SignatureSupport": bs | rt, # RO "SecureBoot": bs | rt, # RO "KEKDefault": bs | rt, # RO "PKDefault": bs | rt, # RO "dbDefault": bs | rt, # RO "dbxDefault": bs | rt, # RO "dbtDefault": bs | rt, # RO "OsIndicationsSupported": bs | rt, # RO "OsIndications": nv | bs | rt, "VendorKeys": bs | rt # RO } self.uefispec_ro_vars = ("HwErrRecSupport", "SetupMode", "SignatureSupport", "SecureBoot", "KEKDefault", "PKDefault", "dbDefault", "dbxDefault", "dbtDefault", "OsIndicationsSupported", "VendorKeys") 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 diff_var(self, data1, data2): if data1 is None or data2 is None: return data1 != data2 oldstr = ":".join("{:02x}".format(c) for c in data1) newstr = ":".join("{:02x}".format(c) for c in data2) if oldstr != newstr: print(oldstr) print(newstr) return True else: return False def can_modify(self, name, guid, data): ret = False #origdata = _uefi.get_EFI_variable(name, guid) origdata = data datalen = len(bytearray(data)) baddata = 'Z' * datalen #0x5A is ASCII 'Z' if baddata == origdata: baddata = 'A' * datalen #in case we failed to restore previously status = self._uefi.set_EFI_variable(name, guid, baddata) if status != StatusCode.EFI_SUCCESS: self.logger.log_good( 'Writing EFI variable {} did not succeed.'.format(name)) newdata = self._uefi.get_EFI_variable(name, guid) if self.diff_var(newdata, origdata): self.logger.log_bad( 'Corruption of EFI variable of concern {}. Trying to recover.'. format(name)) ret = True self._uefi.set_EFI_variable(name, guid, origdata) if self.diff_var(self._uefi.get_EFI_variable(name, guid), origdata): nameguid = name + ' (' + guid + ')' self.logger.log_bad( 'RECOVERY FAILED. Variable {} remains corrupted. Original data value: {}' .format(nameguid, origdata)) return ret def check_vars(self, do_modify): res = ModuleResult.PASSED vars = self._uefi.list_EFI_variables() if vars is None: self.logger.log_warning( 'Could not enumerate UEFI Variables from runtime.') self.logger.log_important( "Note that UEFI variables may still exist, OS just did not expose runtime UEFI Variable API to read them.\nYou can extract variables directly from ROM file via 'chipsec_util.py uefi nvram bios.bin' command and verify their attributes manually." ) return ModuleResult.SKIPPED uefispec_concern = [] ro_concern = [] rw_variables = [] self.logger.log('[*] Testing UEFI variables ..') for name in vars.keys(): if name is None: pass if vars[name] is None: pass if len(vars[name]) > 1: self.logger.log_important( 'Found two instances of the variable {}.'.format(name)) for (off, buf, hdr, data, guid, attrs) in vars[name]: self.logger.log('[*] Variable {} ({})'.format( name, get_attr_string(attrs))) perms = self.uefispec_vars.get(name) if perms is not None: if perms != attrs: attr_diffs = (perms ^ attrs) extra_attr = attr_diffs & attrs missing_attr = attr_diffs & ~extra_attr uefispec_concern.append(name) if extra_attr != 0: self.logger.log_important( ' Extra attributes:' + get_attr_string(extra_attr)) if (extra_attr & ~( EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | EFI_VARIABLE_APPEND_WRITE) != 0): res = ModuleResult.FAILED if missing_attr != 0: self.logger.log_important( ' Missing attributes:' + get_attr_string(missing_attr)) if res != ModuleResult.FAILED: res = ModuleResult.WARNING if do_modify: self.logger.log( "[*] Testing modification of {} ..".format(name)) if name in self.uefispec_ro_vars: if self.can_modify(name, guid, data): ro_concern.append(name) self.logger.log_bad( "Variable {} should be read only.".format( name)) res = ModuleResult.FAILED else: if self.can_modify(name, guid, data): rw_variables.append(name) if uefispec_concern: self.logger.log('') self.logger.log_bad( 'Variables with attributes that differ from UEFI spec:') for name in uefispec_concern: self.logger.log(' {}'.format(name)) if do_modify: if ro_concern: self.logger.log('') self.logger.log_bad( 'Variables that should have been read-only and were not:') for name in ro_concern: self.logger.log(' {}'.format(name)) if rw_variables: self.logger.log('') self.logger.log_unknown( 'Variables that are read-write (manual investigation is required):' ) for name in rw_variables: self.logger.log(' {}'.format(name)) self.logger.log('') if ModuleResult.PASSED == res: self.logger.log_passed( 'All checked EFI variables are protected according to spec.') elif ModuleResult.FAILED == res: self.logger.log_failed( 'Some EFI variables were not protected according to spec.') return res def run(self, module_argv): self.logger.start_test("Access Control of EFI Variables") do_modify = (len(module_argv) > 0 and module_argv[0] == OPT_MODIFY) self.res = self.check_vars(do_modify) return self.res
class variables(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 can_modify(self, name, guid, data, attrs): self.logger.log(" > attempting to modify variable {}:{}".format( guid, name)) datalen = len(data) #print_buffer( data ) baddata = chr(ord(data[0]) ^ 0xFF) + data[1:] #if datalen > 1: baddata = baddata[:datalen-1] + chr( ord(baddata[datalen-1]) ^ 0xFF ) status = self._uefi.set_EFI_variable(name, guid, baddata) if StatusCode.EFI_SUCCESS != status: self.logger.log( ' < modification of {} returned error 0x{:X}'.format( name, status)) else: self.logger.log( ' < modification of {} returned succees'.format(name)) self.logger.log( ' > checking variable {} contents after modification..'.format( name)) newdata = self._uefi.get_EFI_variable(name, guid) #print_buffer( newdata ) #chipsec.file.write_file( name+'_'+guid+'.bin', data ) #chipsec.file.write_file( name+'_'+guid+'.bin.bad', baddata ) #chipsec.file.write_file( name+'_'+guid+'.bin.new', newdata ) _changed = (data != newdata) if _changed: self.logger.log_bad( "EFI variable {} has been modified. Restoring original contents.." .format(name)) self._uefi.set_EFI_variable(name, guid, data) # checking if restored correctly restoreddata = self._uefi.get_EFI_variable(name, guid) #print_buffer( restoreddata ) if (restoreddata != data): self.logger.error( "Failed to restore contents of variable {} failed!".format( name)) else: self.logger.log( " contents of variable {} have been restored".format( name)) else: self.logger.log_good("Could not modify UEFI variable {}:{}".format( guid, name)) return _changed ## check_secureboot_variable_attributes # checks authentication attributes of Secure Boot EFI variables def check_secureboot_variable_attributes(self, do_modify): res = ModuleResult.ERROR not_found = 0 not_auth = 0 not_wp = 0 is_secureboot_enabled = False sbvars = self._uefi.list_EFI_variables() if sbvars is None: self.logger.log_warn_check('Could not enumerate UEFI variables.') return ModuleResult.SKIPPED for name in SECURE_BOOT_VARIABLES: if name in sbvars.keys() and sbvars[name] is not None: if len(sbvars[name]) > 1: self.logger.log_failed_check( 'There should only be one instance of variable {}'. format(name)) return ModuleResult.FAILED for (off, buf, hdr, data, guid, attrs) in sbvars[name]: self.logger.log( "[*] Checking protections of UEFI variable {}:{}". format(guid, name)) # check the status of Secure Boot if EFI_VAR_NAME_SecureBoot == name: is_secureboot_enabled = (data is not None and len(data) == 1 and ord(data) == 0x1) # # Verify if the Secure Boot key/database variable is authenticated # if name in SECURE_BOOT_KEY_VARIABLES: if IS_VARIABLE_ATTRIBUTE( attrs, EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS): self.logger.log_good( 'Variable {}:{} is authenticated (AUTHENTICATED_WRITE_ACCESS)' .format(guid, name)) elif IS_VARIABLE_ATTRIBUTE( attrs, EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS ): self.logger.log_good( 'Variable {}:{} is authenticated (TIME_BASED_AUTHENTICATED_WRITE_ACCESS)' .format(guid, name)) else: not_auth += 1 self.logger.log_bad( 'Variable {}:{} is not authenticated'.format( guid, name)) # # Attempt to modify contents of the variables # if do_modify: if self.can_modify(name, guid, data, attrs): not_wp += 1 else: not_found += 1 self.logger.log_important( 'Secure Boot variable {} is not found'.format(name)) continue self.logger.log('') self.logger.log('[*] Secure Boot appears to be {}abled'.format( 'en' if is_secureboot_enabled else 'dis')) if len(SECURE_BOOT_VARIABLES) == not_found: # None of Secure Boot variables were not found self.logger.log_skipped_check( 'None of required Secure Boot variables found. Secure Boot is not enabled' ) return ModuleResult.SKIPPED else: # Some Secure Boot variables exist sb_vars_failed = (not_found > 0) or (not_auth > 0) or (not_wp > 0) if sb_vars_failed: if not_found > 0: self.logger.log_bad( "Some required Secure Boot variables are missing") if not_auth > 0: self.logger.log_bad( 'Some Secure Boot keying variables are not authenticated' ) if not_wp > 0: self.logger.log_bad( 'Some Secure Boot variables can be modified') if is_secureboot_enabled: self.logger.log_failed_check( 'Not all Secure Boot UEFI variables are protected') return ModuleResult.FAILED else: self.logger.log_warn_check( 'Not all Secure Boot UEFI variables are protected') return ModuleResult.WARNING else: self.logger.log_passed_check( 'All Secure Boot UEFI variables are protected') return ModuleResult.PASSED # -------------------------------------------------------------------------- # run( module_argv ) # Required function: run here all tests from this module # -------------------------------------------------------------------------- def run(self, module_argv): self.logger.start_test("Attributes of Secure Boot EFI Variables") do_modify = (len(module_argv) > 0 and module_argv[0] == OPT_MODIFY) return self.check_secureboot_variable_attributes(do_modify)