From 8155afb2f446d7b9d67cf40d7d0beaa1dc8ac8be Mon Sep 17 00:00:00 2001 From: c7zero Date: Mon, 5 Dec 2016 15:11:51 -0800 Subject: [PATCH] Improved EFI binaries search and updated blacklist config (#131) * Changes in calculating hashes of EFI binaries Calculating hashes of actual .efi executable files (PE32/TE sections of EFI binaries) rather than of entire EFI binaries. There's still an option (WRITE_ALL_HASHES) to calculate hashes of entire EFI binaries. * Improved UEFI search and updated blacklist config 1. Updated EFI binaries search - searching only leaf nodes (PE/TE executable sections) 2. Added exclusion criteria and improved JSON format 3. Changed tools.uefi.blacklist module to return a warning 4. Updated ThinkPwn rules in blacklist.json - Updated config to match by GUID AND regexp of SmmRuntime protcol GUID within the binary to skip binaries consuming SmmRuntime protcol - Added exclusion rules for UEFI update images with patched SystemSmmRuntimeRt.efi. Config excludes patched binaries from Lenovo and HP. Tested on Intel, Lenovo, HP and Gigabyte images. --- chipsec/hal/spi_uefi.py | 229 +++++++++++++++------- chipsec/modules/tools/uefi/blacklist.json | 46 ++++- chipsec/modules/tools/uefi/blacklist.py | 20 +- 3 files changed, 202 insertions(+), 93 deletions(-) diff --git a/chipsec/hal/spi_uefi.py b/chipsec/hal/spi_uefi.py index 2c252b91..23d7b055 100644 --- a/chipsec/hal/spi_uefi.py +++ b/chipsec/hal/spi_uefi.py @@ -63,6 +63,11 @@ CMD_UEFI_FILE_INSERT_BEFORE = 1 CMD_UEFI_FILE_INSERT_AFTER = 2 CMD_UEFI_FILE_REPLACE = 3 +# +# Calculate hashes for all FVs, FW files and sections (PE/COFF or TE executables) +# and write them on the file system +# +WRITE_ALL_HASHES = False def decompress_section_data( _uefi, section_dir_path, sec_fs_name, compressed_data, compression_type, remove_files=False ): compressed_name = os.path.join(section_dir_path, "%s.gz" % sec_fs_name) @@ -155,18 +160,24 @@ class EFI_MODULE(object): self.clsname = "EFI module" self.indent = '' - self.MD5 = '' - self.SHA1 = '' - self.SHA256 = '' + self.MD5 = None + self.SHA1 = None + self.SHA256 = None + + def name(self): + return "%s {%s}" % (self.clsname,self.Guid) def __str__(self): _ind = self.indent + DEF_INDENT - return "%sMD5 : %s\n%sSHA1 : %s\n%sSHA256: %s\n" % (_ind,self.MD5,_ind,self.SHA1,_ind,self.SHA256) - + _s = '' + if self.MD5 : _s = "\n%sMD5 : %s" % (_ind,self.MD5) + if self.SHA1 : _s += "\n%sSHA1 : %s" % (_ind,self.SHA1) + if self.SHA256: _s += "\n%sSHA256: %s" % (_ind,self.SHA256) + return _s class EFI_FV(EFI_MODULE): def __init__(self, Offset, Guid, Size, Attributes, HeaderSize, Checksum, ExtHeaderOffset, Image, CalcSum): - EFI_MODULE.__init__(self, Offset, Guid, HeaderSize, Attributes, Image) + super(EFI_FV, self).__init__(Offset, Guid, HeaderSize, Attributes, Image) self.clsname = "EFI firmware volume" self.Size = Size self.Checksum = Checksum @@ -176,14 +187,14 @@ class EFI_FV(EFI_MODULE): def __str__(self): schecksum = ('%04Xh (%04Xh) *** checksum mismatch ***' % (self.Checksum,self.CalcSum)) if self.CalcSum != self.Checksum else ('%04Xh' % self.Checksum) _s = "\n%s%s +%08Xh {%s}: Size %08Xh, Attr %08Xh, HdrSize %04Xh, ExtHdrOffset %08Xh, Checksum %s" % (self.indent,self.clsname,self.Offset,self.Guid,self.Size,self.Attributes,self.HeaderSize,self.ExtHeaderOffset,schecksum) - _s += ("\n" + super(EFI_FV, self).__str__()) + _s += super(EFI_FV, self).__str__() return _s class EFI_FILE(EFI_MODULE): - def __init__(self, Offset, Name, Type, Attributes, State, Checksum, Size, Image, HeaderSize, UD, CalcSum): - EFI_MODULE.__init__(self, Offset, Name, HeaderSize, Attributes, Image) + def __init__(self, Offset, Guid, Type, Attributes, State, Checksum, Size, Image, HeaderSize, UD, CalcSum): + super(EFI_FILE, self).__init__(Offset, Guid, HeaderSize, Attributes, Image) self.clsname = "EFI binary" - self.Name = Name + self.Name = Guid self.Type = Type self.State = State self.Size = Size @@ -194,24 +205,28 @@ class EFI_FILE(EFI_MODULE): def __str__(self): schecksum = ('%04Xh (%04Xh) *** checksum mismatch ***' % (self.Checksum,self.CalcSum)) if self.CalcSum != self.Checksum else ('%04Xh' % self.Checksum) _s = "\n%s%s +%08Xh {%s}\n%sType %02Xh, Attr %08Xh, State %02Xh, Size %06Xh, Checksum %s" % (self.indent,self.clsname,self.Offset,self.Guid,self.indent*2,self.Type,self.Attributes,self.State,self.Size,schecksum) - _s += ("\n" + super(EFI_FILE, self).__str__()) + _s += (super(EFI_FILE, self).__str__() + '\n') return _s class EFI_SECTION(EFI_MODULE): def __init__(self, Offset, Name, Type, Image, HeaderSize): - EFI_MODULE.__init__(self, Offset, None, HeaderSize, None, Image) + super(EFI_SECTION, self).__init__(Offset, None, HeaderSize, None, Image) self.clsname = "EFI section" self.Name = Name self.Type = Type - self.ui_string = '' self.DataOffset = None + self.parentGuid = None + def name(self): + return "%s section of binary {%s}" % (self.Name,self.parentGuid) + def __str__(self): _s = "%s%s +%08Xh %-16s: Type %02Xh %s" % (self.indent,self.clsname,self.Offset,self.Name,self.Type,self.ui_string) - if self.Guid: _s += ", GUID {%s}" % self.Guid - if self.Attributes: _s += ", Attr %04Xh" % self.Attributes - if self.DataOffset: _s += ", DataOffset %04Xh" % self.DataOffset + if self.Guid: _s += " GUID {%s}" % self.Guid + if self.Attributes: _s += " Attr %04Xh" % self.Attributes + if self.DataOffset: _s += " DataOffset %04Xh" % self.DataOffset + _s += super(EFI_SECTION, self).__str__() return _s def dump_fw_file( fwbin, volume_path ): @@ -219,17 +234,19 @@ def dump_fw_file( fwbin, volume_path ): pth = os.path.join( volume_path, "%s.%s-%02X" % (fwbin.Name, type_s, fwbin.Type)) if os.path.exists( pth ): pth += ("_%08X" % fwbin.Offset) write_file( pth, fwbin.Image ) - if fwbin.MD5 != '': write_file( ("%s.md5" % pth), fwbin.MD5 ) - if fwbin.SHA1 != '': write_file( ("%s.sha1" % pth), fwbin.SHA1 ) - if fwbin.SHA256 != '': write_file( ("%s.sha256" % pth), fwbin.SHA256 ) + if WRITE_ALL_HASHES: + if fwbin.MD5 : write_file( ("%s.md5" % pth), fwbin.MD5 ) + if fwbin.SHA1 : write_file( ("%s.sha1" % pth), fwbin.SHA1 ) + if fwbin.SHA256: write_file( ("%s.sha256" % pth), fwbin.SHA256 ) return ("%s.dir" % pth) def dump_fv( fv, voln, uefi_region_path ): fv_pth = os.path.join( uefi_region_path, "%02d_%s" % (voln, fv.Guid) ) write_file( fv_pth, fv.Image ) - if fv.MD5 != '': write_file( ("%s.md5" % fv_pth), fv.MD5 ) - if fv.SHA1 != '': write_file( ("%s.sha1" % fv_pth), fv.SHA1 ) - if fv.SHA256 != '': write_file( ("%s.sha256" % fv_pth), fv.SHA256 ) + if WRITE_ALL_HASHES: + if fv.MD5 : write_file( ("%s.md5" % fv_pth), fv.MD5 ) + if fv.SHA1 : write_file( ("%s.sha1" % fv_pth), fv.SHA1 ) + if fv.SHA256: write_file( ("%s.sha256" % fv_pth), fv.SHA256 ) volume_path = os.path.join( uefi_region_path, "%02d_%s.dir" % (voln, fv.Guid) ) if not os.path.exists( volume_path ): os.makedirs( volume_path ) return volume_path @@ -244,6 +261,10 @@ def dump_section( sec, secn, parent_path, efi_file ): efi_file = sec_fs_name section_path = os.path.join(parent_path, sec_fs_name) write_file( section_path, sec.Image[sec.HeaderSize:] ) + if sec.MD5 : write_file( os.path.join(parent_path, "%s.md5" % sec_fs_name), sec.MD5 ) + if sec.SHA1 : write_file( os.path.join(parent_path, "%s.sha1" % sec_fs_name), sec.SHA1 ) + if sec.SHA256: write_file( os.path.join(parent_path, "%s.sha256" % sec_fs_name), sec.SHA256 ) + else: write_file( section_path, sec.Image[sec.HeaderSize:] ) if sec.Type == EFI_SECTION_USER_INTERFACE: @@ -251,38 +272,63 @@ def dump_section( sec, secn, parent_path, efi_file ): if ui_string[-4:] != '.efi': ui_string = "%s.efi" % ui_string if efi_file is not None: os.rename(os.path.join(parent_path, efi_file), os.path.join(parent_path, ui_string)) + os.rename(os.path.join(parent_path, "%s.md5" % efi_file), os.path.join(parent_path, "%s.md5" % ui_string)) + os.rename(os.path.join(parent_path, "%s.sha1" % efi_file), os.path.join(parent_path, "%s.sha1" % ui_string)) + os.rename(os.path.join(parent_path, "%s.sha256" % efi_file), os.path.join(parent_path, "%s.sha256" % ui_string)) efi_file = None section_dir_path = "%s.dir" % section_path return sec_fs_name,section_dir_path,efi_file -def add_hashes( efi ): +def add_hashes( efi, off=0 ): if efi.Image is None: return hmd5 = hashlib.md5() - hmd5.update( efi.Image ) + hmd5.update( efi.Image[off:] ) efi.MD5 = hmd5.hexdigest() hsha1 = hashlib.sha1() - hsha1.update( efi.Image ) + hsha1.update( efi.Image[off:] ) efi.SHA1 = hsha1.hexdigest() hsha256 = hashlib.sha256() - hsha256.update( efi.Image ) + hsha256.update( efi.Image[off:] ) efi.SHA256 = hsha256.hexdigest() # -# Format of EFI binaries match rules (any field can be empty or missing): +# - EFI binaries are searched according to criteria defined by "match" rules. +# - EFI binaries matching exclusion criteria defined by "exclude" rules are excluded from matching. +# +# Format of the matching rules (any field can be empty or missing): # - Individual rules are OR'ed -# - match criteria within a given rule are AND'ed +# - criteria within a given rule are AND'ed # # Example: -# { -# "rule00": { "guid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" } -# "rule01": { "name": "module0", "md5": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "sha1": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "sha256": "", "regexp": "" } +# +# "UEFI_rootkitX": { +# "description": "yet another UEFI implant X", +# "match": { +# "rktX_rule1" : { "guid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" }, +# "rktX_rule2" : { "name": "rootkitX.efi" } +# } +# }, +# +# "UEFI_vulnerabilityX": { +# "description": "yet another UEFI vulnerability X", +# "match": { +# "vulnX_rule1": { "guid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "regexp": "IAMVULNERABLE" }, +# "vulnX_rule2": { "md5": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "sha1": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" } +# }, +# "exclude": { +# "vulnX_patched": { "md5": "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH", "sha1": "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH" } +# } # } # -# Above search configuration will result in a match if the following EFI module is found: -# - module with guid "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" +# Above example results in a match if the following EFI binary is found: +# - with GUID "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" # OR -# - module with name "module0" AND md5 hash "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" AND sha1 hash "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +# - with name "module0" AND contains a byte sequence matching regular expression "blah" +# OR +# - with MD5 hash "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" AND SHA-1 hash "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +# Unless it's a EFI binary: +# - with MD5 hash "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH" AND SHA-1 hash "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH" # MATCH_NAME = 0x1 MATCH_GUID = (0x1 << 1) @@ -291,14 +337,15 @@ MATCH_HASH_MD5 = (0x1 << 3) MATCH_HASH_SHA1 = (0x1 << 4) MATCH_HASH_SHA256 = (0x1 << 5) -def check_match_criteria( efi, match_criteria ): +def check_rules( efi, rules, entry_name, bLog=True ): bfound = False - _log = '' - - for k in match_criteria.keys(): + for rule_name in rules.keys(): + what = None + offset = 0 match_mask = 0x00000000 match_result = 0x00000000 - rule = match_criteria[k] + fname = "%s.%s" % (entry_name,rule_name) + rule = rules[rule_name] # # Determine which criteria are defined in the current rule # @@ -308,20 +355,20 @@ def check_match_criteria( efi, match_criteria ): if ('md5' in rule) and (rule['md5'] != ''): match_mask |= MATCH_HASH_MD5 if ('sha1' in rule) and (rule['sha1'] != ''): match_mask |= MATCH_HASH_SHA1 if ('sha256' in rule) and (rule['sha256'] != ''): match_mask |= MATCH_HASH_SHA256 - - _s = "[uefi] found matching %s (rule '%s'):" % (efi.clsname,k) # # Check criteria defined in the current rule against the current EFI module # if (match_mask & MATCH_NAME) == MATCH_NAME: if type(efi) is EFI_SECTION and efi.ui_string == rule['name']: match_result |= MATCH_NAME if (match_mask & MATCH_GUID) == MATCH_GUID: - if ((type(efi) is EFI_FILE) and (efi.Name == rule['guid'])) or (efi.Guid == rule['guid']): match_result |= MATCH_GUID + if (type(efi) is EFI_SECTION and efi.parentGuid == rule['guid']) or \ + (efi.Guid == rule['guid']): match_result |= MATCH_GUID if (match_mask & MATCH_REGEXP) == MATCH_REGEXP: m = re.compile(rule['regexp']).search( efi.Image ) if m: match_result |= MATCH_REGEXP - _log = " + regexp: bytes '%s' at offset %Xh" % (binascii.hexlify(m.group(0)),m.start()) + what = binascii.hexlify(m.group(0)) + offset = m.start() if (match_mask & MATCH_HASH_MD5) == MATCH_HASH_MD5: if efi.MD5 == rule['md5']: match_result |= MATCH_HASH_MD5 if (match_mask & MATCH_HASH_SHA1) == MATCH_HASH_SHA1: @@ -330,20 +377,47 @@ def check_match_criteria( efi, match_criteria ): if efi.SHA256 == rule['sha256']: match_result |= MATCH_HASH_SHA256 brule_match = ((match_result & match_mask) == match_mask) + if brule_match and bLog: + logger().log_important( "match '%s'" % fname ) + if (match_result & MATCH_NAME ) == MATCH_NAME : logger().log( " name : '%s'" % rule['name'] ) + if (match_result & MATCH_GUID ) == MATCH_GUID : logger().log( " GUID : {%s}" % rule['guid'] ) + if (match_result & MATCH_REGEXP ) == MATCH_REGEXP : logger().log( " regexp: bytes '%s' at offset %Xh" % (what,offset) ) + if (match_result & MATCH_HASH_MD5 ) == MATCH_HASH_MD5 : logger().log( " MD5 : %s" % rule['md5'] ) + if (match_result & MATCH_HASH_SHA1 ) == MATCH_HASH_SHA1 : logger().log( " SHA1 : %s" % rule['sha1'] ) + if (match_result & MATCH_HASH_SHA256) == MATCH_HASH_SHA256: logger().log( " SHA256: %s" % rule['sha256'] ) + # + # Rules are OR'ed unless matching rule is explicitly excluded from match + # bfound = bfound or brule_match - if brule_match: - logger().log( _s ) - if (match_result & MATCH_NAME ) == MATCH_NAME : logger().log( " + name : '%s'" % rule['name'] ) - if (match_result & MATCH_GUID ) == MATCH_GUID : logger().log( " + GUID : {%s}" % rule['guid'] ) - if (match_result & MATCH_REGEXP ) == MATCH_REGEXP : logger().log( _log ) - if (match_result & MATCH_HASH_MD5 ) == MATCH_HASH_MD5 : logger().log( " + MD5 : %s" % rule['md5'] ) - if (match_result & MATCH_HASH_SHA1 ) == MATCH_HASH_SHA1 : logger().log( " + SHA1 : %s" % rule['sha1'] ) - if (match_result & MATCH_HASH_SHA256) == MATCH_HASH_SHA256: logger().log( " + SHA256: %s" % rule['sha256'] ) - logger().log( efi ) return bfound -def traverse_uefi_section( _uefi, fwtype, data, Size, offset, polarity, parent_offset, printall=True, dumpall=True, parent_path='', match_criteria=None, findall=True ): +def check_match_criteria(efi, criteria): + bfound = False + logger().log("[uefi] checking %s" % efi.name()) + for k in criteria.keys(): + entry = criteria[k] + # Check if the EFI binary is a match + if 'match' in entry: + bmatch = check_rules(efi, entry['match'], k) + if bmatch: + logger().log_important("found EFI binary matching '%s'" % k) + if 'description' in entry: logger().log(" %s" % entry['description']) + logger().log(efi) + # Check if the matched binary should be excluded + # There's no point in checking a binary against exclusions if it wasn't a match + if 'exclude' in entry: + if check_rules(efi, entry['exclude'], "%s.exclude" % k): + logger().log_important("matched EFI binary is excluded from '%s'. Skipping..." % k) + continue + # we are here if the matched binary wasn't excluded + # the binary is a final match if it matches either of search entries + bfound = bfound or bmatch + + return bfound + + +def traverse_uefi_section( _uefi, fwtype, data, Size, offset, polarity, parent_offset, parent_guid, printall=True, dumpall=True, parent_path='', match_criteria=None, findall=True ): found, secn, efi_file, section_dir_path = False, 0, None, '' # caller specified non-empty matching rules so we'll need to look for specific EFI modules as we parse FVs bsearch = (match_criteria is not None) @@ -352,40 +426,47 @@ def traverse_uefi_section( _uefi, fwtype, data, Size, offset, polarity, parent_o while next_offset is not None: sec = EFI_SECTION( _off, _name, _type, _img, _hdrsz ) sec.indent = DEF_INDENT*2 + sec.parentGuid = parent_guid # pick random file name in case dumpall=False - we'll need it to decompress the section sec_fs_name = "sect%02d_%s" % (secn, ''.join(random.choice(string.ascii_lowercase) for _ in range(4))) - if sec.Type == EFI_SECTION_USER_INTERFACE: + + if sec.Type in (EFI_SECTION_PE32, EFI_SECTION_TE, EFI_SECTION_PIC, EFI_SECTION_COMPATIBILITY16): + # "leaf" executable section: update hashes and check against match criteria + add_hashes( sec, sec.HeaderSize ) + if bsearch and check_match_criteria( sec, match_criteria ): + if findall: found = True + else: return True + elif sec.Type == EFI_SECTION_USER_INTERFACE: + # "leaf" UI section: update section's UI name sec.ui_string = unicode(sec.Image[sec.HeaderSize:], "utf-16-le")[:-1] + elif sec.Type == EFI_SECTION_GUID_DEFINED: + guid0, guid1, guid2, guid3, sec.DataOffset, sec.Attributes = struct.unpack(EFI_GUID_DEFINED_SECTION, sec.Image[sec.HeaderSize:sec.HeaderSize+EFI_GUID_DEFINED_SECTION_size]) + sec.Guid = guid_str(guid0, guid1, guid2, guid3) if printall: logger().log( sec ) if dumpall: sec_fs_name,section_dir_path,efi_file = dump_section( sec, secn, parent_path, efi_file ) - # only check the match rules if we need to find specific EFI module - if bsearch and check_match_criteria( sec, match_criteria ): - if findall: found = True - else: return True + # "container" sections: keep parsing if sec.Type in (EFI_SECTION_COMPRESSION, EFI_SECTION_GUID_DEFINED, EFI_SECTION_FIRMWARE_VOLUME_IMAGE, EFI_SECTION_RAW): if dumpall: os.makedirs( section_dir_path ) if sec.Type == EFI_SECTION_COMPRESSION: ul, ct = struct.unpack(EFI_COMPRESSION_SECTION, sec.Image[sec.HeaderSize:sec.HeaderSize+EFI_COMPRESSION_SECTION_size]) d = decompress_section_data( _uefi, section_dir_path, sec_fs_name, sec.Image[sec.HeaderSize+EFI_COMPRESSION_SECTION_size:], ct, True ) if d: - f = traverse_uefi_section( _uefi, fwtype, d, len(d), 0, polarity, 0, printall, dumpall, section_dir_path, match_criteria, findall ) + f = traverse_uefi_section( _uefi, fwtype, d, len(d), 0, polarity, 0, parent_guid, printall, dumpall, section_dir_path, match_criteria, findall ) if bsearch and f: if findall: found = True else: return True elif sec.Type == EFI_SECTION_GUID_DEFINED: - guid0, guid1, guid2, guid3, sec.DataOffset, sec.Attributes = struct.unpack(EFI_GUID_DEFINED_SECTION, sec.Image[sec.HeaderSize:sec.HeaderSize+EFI_GUID_DEFINED_SECTION_size]) - sec.Guid = guid_str(guid0, guid1, guid2, guid3) if sec.Guid == EFI_CRC32_GUIDED_SECTION_EXTRACTION_PROTOCOL_GUID: - f = traverse_uefi_section( _uefi, fwtype, sec.Image[sec.DataOffset:], Size - sec.DataOffset, 0, polarity, 0, printall, dumpall, section_dir_path,match_criteria, findall ) + f = traverse_uefi_section( _uefi, fwtype, sec.Image[sec.DataOffset:], Size - sec.DataOffset, 0, polarity, 0, parent_guid, printall, dumpall, section_dir_path, match_criteria, findall ) if bsearch and f: if findall: found = True else: return True elif sec.Guid == LZMA_CUSTOM_DECOMPRESS_GUID: d = decompress_section_data( _uefi, section_dir_path, sec_fs_name, sec.Image[sec.DataOffset:], 2, True ) if d: - f = traverse_uefi_section( _uefi, fwtype, d, len(d), 0, polarity, 0, printall, dumpall, section_dir_path, match_criteria, findall ) + f = traverse_uefi_section( _uefi, fwtype, d, len(d), 0, polarity, 0, parent_guid, printall, dumpall, section_dir_path, match_criteria, findall ) if bsearch and f: if findall: found = True else: return True @@ -399,15 +480,17 @@ def traverse_uefi_section( _uefi, fwtype, data, Size, offset, polarity, parent_o secn += 1 return found + # # traverse_uefi_region - searches for a specific EFI binary by its file/UI name, EFI GUID or hash # # Input arguments: # _uefi - instance of chipsec.hal.uefi.UEFI class # data - an image containing UEFI firmware volumes +# fwtype - platform specific firmware type used to detect NVRAM format (VSS, EVSA, NVAR...) +# uefi_path - root path for EFI hierarchy (used if dumpall==True) # printall - a bool flag that tells to print EFI binaries hierarchy # dumpall - a bool flag that tells to dump all EFI binaries onto the file system -# uefi_path - root path for EFI hierarchy (used if dumpall==True) # match_criteria - criteria to search for sepecific node in EFI hierarchy (Name, GUID, hash, etc.) # findall - a bool flag that tells to find all matching EFI modules in the image (rather than returning upon the first match) # @@ -424,9 +507,9 @@ def traverse_uefi_region( _uefi, data, fwtype, uefi_path='', printall=True, dump if printall: logger().log( fv ) if dumpall: volume_path = dump_fv( fv, voln, uefi_path ) # only check the match rules if we need to find specific EFI module - if bsearch and check_match_criteria( fv, match_criteria ): - if findall: found = True - else: return True + #if bsearch and check_match_criteria( fv, match_criteria ): + # if findall: found = True + # else: return True polarity = bit_set( fv.Attributes, EFI_FVB2_ERASE_POLARITY ) # @@ -441,16 +524,12 @@ def traverse_uefi_region( _uefi, data, fwtype, uefi_path='', printall=True, dump add_hashes( fwbin ) if printall: logger().log( fwbin ) - if dumpall: fwbin_dir = dump_fw_file( fwbin, volume_path ) - # only check the match rules if we need to find specific EFI module - if bsearch and check_match_criteria( fwbin, match_criteria ): - if findall: found = True - else: return True - - if dumpall: os.makedirs( fwbin_dir ) + if dumpall: + fwbin_dir = dump_fw_file( fwbin, volume_path ) + os.makedirs( fwbin_dir ) if fwbin.Type not in (EFI_FV_FILETYPE_ALL, EFI_FV_FILETYPE_RAW, EFI_FV_FILETYPE_FFS_PAD): - f = traverse_uefi_section( _uefi, fwtype, fwbin.Image, fwbin.Size, fwbin.HeaderSize, polarity, fv.Offset + fwbin.Offset, printall, dumpall, fwbin_dir, match_criteria, findall ) + f = traverse_uefi_section( _uefi, fwtype, fwbin.Image, fwbin.Size, fwbin.HeaderSize, polarity, fv.Offset + fwbin.Offset, fwbin.Guid, printall, dumpall, fwbin_dir, match_criteria, findall ) if bsearch and f: if findall: found = True else: return True diff --git a/chipsec/modules/tools/uefi/blacklist.json b/chipsec/modules/tools/uefi/blacklist.json index 1e60d33c..d64099d5 100644 --- a/chipsec/modules/tools/uefi/blacklist.json +++ b/chipsec/modules/tools/uefi/blacklist.json @@ -1,13 +1,39 @@ { - "HT_rkloader" : { "guid": "F50248A9-2F4D-4DE9-86AE-BDA84D07A41C" }, - "HT_rkloader_name" : { "name": "rkloader" }, - "HT_Ntfs" : { "guid": "F50258A9-2F4D-4DA9-861E-BDA84D07A44C" }, - "HT_Ntfs_name" : { "name": "Ntfs" }, - "HT_app" : { "guid": "EAEA9AEC-C9C1-46E2-9D52-432AD25A9B0B" }, + "HT_UEFI_Rootkit": { + "description": "HackingTeam UEFI Rootkit (http://www.intelsecurity.com/advanced-threat-research/content/data/HT-UEFI-rootkit.html)", + "match": { + "rkloader" : { "guid": "F50248A9-2F4D-4DE9-86AE-BDA84D07A41C" }, + "rkloader_name" : { "name": "rkloader" }, + "Ntfs" : { "guid": "F50258A9-2F4D-4DA9-861E-BDA84D07A44C" }, + "Ntfs_name" : { "name": "Ntfs" }, + "app" : { "guid": "EAEA9AEC-C9C1-46E2-9D52-432AD25A9B0B" } + } + }, - "ThinkPwn_SmmRuntimeProtGuid" : { "regexp": "\\xA1\\x97\\x68\\xA5\\x7F\\xA7\\x00\\x46\\x84\\xDB\\x22\\xB0\\xA8\\x01\\xFA\\x9A" }, - "ThinkPwn_SystemSmmRuntimeRt_name" : { "name": "SystemSmmRuntimeRt.efi" }, - "ThinkPwn_SystemSmmRuntimeRt" : { "guid": "7C79AC8C-5E6C-4E3D-BA6F-C260EE7C172E" }, - "ThinkPwn_SmmRuntime_name" : { "name": "SmmRuntime" }, - "ThinkPwn_SmmRuntime" : { "guid": "A56897A1-A77F-4600-84DB-22B0A801FA9A" } + "ThinkPwn": { + "description": "ThinkPwn: SystemSmmRuntimeRt SMM vulnerability (http://blog.cr4.sh/2016/06/exploring-and-exploiting-lenovo.html)", + "match": { + "SystemSmmRuntimeRt": { "guid": "7C79AC8C-5E6C-4E3D-BA6F-C260EE7C172E", "regexp": "\\xA1\\x97\\x68\\xA5\\x7F\\xA7\\x00\\x46\\x84\\xDB\\x22\\xB0\\xA8\\x01\\xFA\\x9A" }, + "SmmRuntime" : { "guid": "A56897A1-A77F-4600-84DB-22B0A801FA9A", "regexp": "\\xA1\\x97\\x68\\xA5\\x7F\\xA7\\x00\\x46\\x84\\xDB\\x22\\xB0\\xA8\\x01\\xFA\\x9A" } + }, + "exclude": { + "Lenovo_T450s_x240" : { "md5": "2a56c7dfaefc4de482f8af4aa5344206", "sha1": "7e6d203f062c9d933f530cf36eb1d2538a37f8cc" }, + "Lenovo_Yoga_X1_W550s" : { "md5": "353359a2314d2c666f03abcf38c26409", "sha1": "0ea473b6e15d54281ccebca162fae56af483a4ba" }, + "Lenovo_X131e" : { "md5": "0d3b180710f4d0cf87f582a073ef1a93", "sha1": "4aa062e25eb31d6f049b0f98df5df40c566a5dc9" }, + "Lenovo_S230_S430" : { "md5": "6143b779f97c7905e6f89f770579f4d3", "sha1": "486ba83b874dcd95cc95dd4e78a65f3f16a5433c" }, + "Lenovo_L450" : { "md5": "efe2d006c3b9baf64cf9f716c64413e3", "sha1": "0def22266f9764f19b11fe6a68018b3428442eb3" }, + "Lenovo_ThinkPad10" : { "md5": "c0544a33eab93b29092123a668c1bfb7", "sha1": "ee7641d623f1559c39d91a734254b4a34b2acea8" }, + "Lenovo_ThinkServerRD340": { "md5": "0dd581382fcc07f4b61c1061083d717f", "sha1": "fdc0806cc23bacdaaf3447d114203a4dceebb81e" }, + "Lenovo_ThinkServerRD640": { "md5": "0ed692f3f1ebf038f860b0d94608b573", "sha1": "283e039e7ec093dac3efa088616e549c78a1c2e1" }, + + "HP_Pavilion13-p_F.0A" : { "md5": "259e47de95e0b167d8deec275ad443d0", "sha1": "427bf3302d73066aee4946012d04c37bf497a416" }, + "HP_Pavilion14-f_F.0B" : { "md5": "9c097da7e33d8f26ce69eb90ff89b0bd", "sha1": "3131940c4e05779170b3c3bbdb8c0d19b614f49d" }, + "HP_Pavilion14-15-17_F.27" : { "md5": "6c674d78b11b1819440e027cdcb4a6e7", "sha1": "dc7424f60db6f9207372ccb3a4066de9203cc974" }, + "HP_Pavilion14-n-15-n_F.70" : { "md5": "75b2bbaf9a25dcd42c39a67152bedf6b", "sha1": "2598f0dde53e56465a2b8176fe315ff42cd92014" }, + "HP_ProBook4x3xs-6x65b_F.63" : { "md5": "641afcf52b5ed7abbbeb3dde40cc5bd1", "sha1": "8588fb5b230ec1c0ae12217cee3a5ee98ba112a5" }, + "HP_ProBook4x4xs-4x5G1_F.64" : { "md5": "9d1e45eaaaf92d30d0edd00bcdb4213d", "sha1": "5542ad8f0f84323bb7b8416eccb0778f97d52093" }, + "HP_ProBook6475b_F.65" : { "md5": "97a19f161359502c9bf2e0898fc35721", "sha1": "3619a78915abc195fc44d6c59984903c9444a1ab" }, + "HP_ProBook4x5G2-6x5G1_EliteBook7x5G2": { "md5": "37a51e769fbb2df9a086ed4543380488", "sha1": "b27ac995729a64e84c444d7e3d6a5bd6f07611a3" } + } + } } diff --git a/chipsec/modules/tools/uefi/blacklist.py b/chipsec/modules/tools/uefi/blacklist.py index 8b76dafe..ab8e40fd 100644 --- a/chipsec/modules/tools/uefi/blacklist.py +++ b/chipsec/modules/tools/uefi/blacklist.py @@ -78,12 +78,9 @@ Examples: Decodes 'uefi.rom' binary with UEFI firmware image and checks for black-listed EFI modules defined in 'blacklist.json' config - None: -i and --no_driver arguments can be used in this case because the test - does not depend on the platform and no kernel driver is required when firmware image is specified Important! This module can only detect what it knows about from its config file. If a bad or vulnerable binary is not detected then its 'signature' needs to be added to the config. - ''' @@ -94,7 +91,7 @@ class blacklist(BaseModule): self.uefi = chipsec.hal.uefi.UEFI( self.cs ) self.cfg_name = 'blacklist.json' self.image = None - self.efi_blacklist = {} + self.efi_blacklist = None def is_supported(self): return True @@ -102,8 +99,15 @@ class blacklist(BaseModule): def check_blacklist( self ): res = ModuleResult.PASSED - self.logger.log( "[*] looking for black-listed EFI binaries defined in '%s'..." % self.cfg_name ) - #self.logger.log( self.efi_blacklist ) + self.logger.log( "[*] searching for EFI binaries that match criteria from '%s':" % self.cfg_name ) + for k in self.efi_blacklist.keys(): + entry = self.efi_blacklist[k] + self.logger.log( " %-16s - %s" % (k,entry['description'] if 'description' in entry else '') ) + #if 'match' in entry: + # for c in entry['match'].keys(): self.logger.log( "[*] %s" % entry['match'][c] ) + #if 'exclude' in entry: + # self.logger.log( "[*] excluding binaries:" ) + # for c in entry['exclude']: self.logger.log( "[*] %s" % entry['exclude'][c] ) # no need to output the entire hierarchy of EFI modules printall = False @@ -118,8 +122,8 @@ class blacklist(BaseModule): self.logger.log( '' ) if found: - res = ModuleResult.FAILED - self.logger.log_failed_check("Black-listed EFI binary found in the UEFI firmware image") + res = ModuleResult.WARNING + self.logger.log_warn_check("Black-listed EFI binary found in the UEFI firmware image") else: self.logger.log_passed_check("Didn't find any black-listed EFI binary") return res