def parse(self, blob): # sanity check nt = namedtuple('PfsHeaderTuple', ['tag', 'hdr_ver', 'payload_size']) try: pfs_hdr = nt._make( struct.unpack_from(PfsFile.PFS_HEADER, blob, 0x0)) except struct.error as e: raise RuntimeError(str(e)) if pfs_hdr.tag != b'PFS.HDR.': raise RuntimeError('Not a PFS header') if pfs_hdr.hdr_ver != 1: raise RuntimeError('PFS header version %i unsupported' % pfs_hdr.hdr_ver) # parse sections offset = struct.calcsize(PfsFile.PFS_HEADER) while offset < len(blob) - struct.calcsize(PfsFile.PFS_FOOTER): # parse the section nt = namedtuple('PfsHeaderSection', [ 'guid', 'hdr_ver', 'ver_type', 'version', 'reserved', 'data_sz', 'data_sig_sz', 'metadata_sz', 'metadata_sig_sz' ]) try: pfs_sect = nt._make( struct.unpack_from(PfsFile.PFS_SECTION, blob, offset)) except struct.error as e: raise RuntimeError(str(e)) if pfs_sect.hdr_ver != 1: raise RuntimeError('PFS section version %i unsupported' % pfs_hdr.hdr_ver) offset += struct.calcsize(PfsFile.PFS_SECTION) # parse the data and ignore the rest shard = ComponentShard() shard.set_blob(blob[offset:offset + pfs_sect.data_sz]) shard.guid = str(uuid.UUID(bytes_le=pfs_sect.guid)) if shard.guid == 'fd041960-0dc8-4b9f-8225-bba9e37c71e0': self._parse_info(shard.blob) elif shard.guid == '233ae3fb-da68-4fd4-92cb-a6229a611d6f': self._parse_model(shard.blob) else: self.shards.append(shard) # advance to the next section offset += pfs_sect.data_sz offset += pfs_sect.data_sig_sz offset += pfs_sect.metadata_sz offset += pfs_sect.metadata_sig_sz # the INFO structure is typically last, so fix up added shards for shard in self.shards: if shard.guid in self._names: shard.name = 'com.dell.' + self._names[shard.guid].replace( ' ', '') else: shard.name = 'com.dell.' + shard.guid
def _run_uefi_extract_on_md(self, test, md): # remove any old shards we added for shard in md.shards: if shard.plugin_id == self.id: db.session.delete(shard) db.session.commit() # is this a AMI BIOS with PFAT sections if md.blob[8:16] == b'_AMIPFAT': pfat = PfatFile(md.blob) shards = [] for shard in pfat.shards: shards.append(shard) if shard.name == 'com.ami.BIOS_FV_BB.bin': continue shards.extend(self._get_shards_for_blob(shard.blob)) test.add_pass('Found PFAT blob') # try with the plain blob (possibly with a capsule header) and # then look for a Zlib section (with an optional PFS-prefixed) blob else: shards = self._get_shards_for_blob(md.blob) if not shards: for blob in self._find_zlib_sections(md.blob): try: pfs = PfsFile(blob) for shard in pfs.shards: shards.append(shard) shards.extend(self._get_shards_for_blob( shard.blob)) test.add_pass('Found PFS in Zlib compressed blob') except RuntimeError as _: shard = ComponentShard(plugin_id=self.id) shard.set_blob(blob) shard.name = 'Zlib' shard.guid = '68b8cc0e-4664-5c7a-9ce3-8ed9b4ffbffb' shards.append(shard) shards.extend(self._get_shards_for_blob(shard.blob)) test.add_pass('Found Zlib compressed blob') if not shards: test.add_pass('No firmware volumes found in {}'.format( md.filename_contents)) return # add shard to component for shard in shards: shard.plugin_id = self.id shard.component_id = md.component_id if self.get_setting_bool('uefi_extract_write_shards'): shard.save() md.shards.append(shard)
def _add_shards(self, fpt, md): # remove any old shards we added for shard in md.shards: if shard.plugin_id == self.id: db.session.delete(shard) db.session.commit() # add shards for entry in fpt.entries: if not entry.guid: continue if not entry.blob: continue shard = ComponentShard(component_id=md.component_id, plugin_id=self.id) shard.set_blob(entry.blob, checksums='SHA256') shard.ensure_info(entry.guid, entry.appstream_id) md.shards.append(shard)
def _convert_files_to_shards(self, files): # parse each EFI binary as a shard shards = [] for fn in files: sections = fn.rsplit('/') name = sections[-1].split('.')[0] kind = None guid = None for section in reversed(sections[:-1]): if section.find('GUID_DEFINED') != -1: continue if section.find('COMPRESSION') != -1: continue dirname_sections = section.split('.') guid = dirname_sections[0].split('_')[1].lower() kind = dirname_sections[1] break if not guid: continue appstream_kinds = { 'FV_APPLICATION': 'Application', 'FV_DRIVER': 'Driver', 'FV_DXE_CORE': 'Dxe', 'FV_PEI_CORE': 'Pei', 'FV_PEIM': 'Peim', 'FV_RAW': 'Raw', 'FV_SECURITY_CORE': 'Security', 'FV_COMBINED_PEIM_DRIVER': 'PeimDriver', } if kind in appstream_kinds: appstream_id = 'com.intel.Uefi.{}.{}'.format( appstream_kinds[kind], name) else: appstream_id = 'com.intel.Uefi.{}'.format(name) shard = ComponentShard(plugin_id=self.id, name=appstream_id, guid=guid) with open(fn, 'rb') as f: shard.set_blob(f.read()) shards.append(shard) return shards
def _run_chipsec_on_md(self, test, md): # remove any old shards we added for shard in md.shards: if shard.plugin_id == self.id: for result in shard.yara_query_results: db.session.delete(result) db.session.delete(shard) db.session.commit() # try first with the plain blob (possibly with a capsule header) and # then look for a Zlib section (with an optional PFS-prefixed) blob shards = self._get_shards_for_blob(md.blob) if not shards: for blob in self._find_zlib_sections(md.blob): try: pfs = PfsFile(blob) for shard in pfs.shards: shards.append(shard) shards.extend(self._get_shards_for_blob(shard.blob)) test.add_pass('Found PFS in Zlib compressed blob') except RuntimeError as _: shard = ComponentShard(plugin_id=self.id) shard.set_blob(blob) shard.name = 'Zlib' shard.guid = '68b8cc0e-4664-5c7a-9ce3-8ed9b4ffbffb' shards.append(shard) shards.extend(self._get_shards_for_blob(shard.blob)) test.add_pass('Found Zlib compressed blob') if not shards: test.add_pass('No firmware volumes found in {}'.format( md.filename_contents)) return # add shard to component for shard in shards: shard.plugin_id = self.id shard.component_id = md.component_id if self.get_setting_bool('chipsec_write_shards'): shard.save() md.shards.append(shard)
def _run_psptool_on_blob(self, test, md): # remove any old shards we added for shard in md.shards: if shard.plugin_id == self.id: db.session.delete(shard) db.session.commit() # parse firmware try: psp = PSPTool(md.blob, verbose=True) for directory in psp.blob.directories: for entry in directory.entries: if isinstance(entry, HeaderEntry): blob = entry.get_decompressed() appstream_id = 'com.amd.PSP.HeaderEntry.{}'.\ format(_get_readable_type(entry)) elif isinstance(entry, PubkeyEntry): blob = entry.get_pem_encoded() appstream_id = 'com.amd.PSP.Entry.{}'.\ format(_get_readable_type(entry)) else: blob = entry.get_bytes() appstream_id = 'com.amd.PSP.{}'.\ format(_get_readable_type(entry)) # add shard to component shard = ComponentShard(component_id=md.component_id, plugin_id=self.id, guid=_mkguid(hex(entry.type)), name=appstream_id) shard.set_blob(blob, checksums='SHA256') md.shards.append(shard) test.add_pass('Found {} directories'.format(len(psp.blob.directories))) except (Blob.NoFirmwareEntryTableError, AssertionError) as _: pass
import datetime # allows us to run this from the project root sys.path.append(os.path.realpath('.')) from lvfs.models import Test, Firmware, Component, Protocol, ComponentShard from plugins.pecheck import Plugin if __name__ == '__main__': for _argv in sys.argv[1:]: print('Processing', _argv) plugin = Plugin('pecheck') _test = Test(plugin.id) _fw = Firmware() _fw.timestamp = datetime.datetime.utcnow() _md = Component() _md.protocol = Protocol('org.uefi.capsule') _shard = ComponentShard(name=os.path.basename(_argv)) try: with open(_argv, 'rb') as f: _shard.set_blob(f.read()) except IsADirectoryError as _: continue _md.shards.append(_shard) _fw.mds.append(_md) plugin.run_test_on_fw(_test, _fw) for attribute in _test.attributes: print(attribute) for cert in _shard.certificates: print(cert)
def _convert_files_to_shards(self, files): # parse each EFI binary as a shard shards = [] shard_by_checksum = {} for fn in files: dirname = os.path.dirname(fn) try: with open(os.path.join(dirname, 'body.bin'), 'rb') as f: payload = f.read() except FileNotFoundError as _: continue if len(payload) < 0x100: #print('ignoring payload of {} bytes'.format(len(payload))) continue # read in child data with open(fn, 'rb') as f: data = InfoTxtFile(f.read()) if data.get('Subtype') in ['PE32 image', 'TE image']: with open(os.path.join(dirname, '..', 'info.txt'), 'rb') as f: data = InfoTxtFile(f.read()) name = data.get('Text') if not name: if data.get('CPU signature') and data.get('CPU flags'): name = '{:08X}.{:08X}'.format( data.get_int('CPU signature'), data.get_int('CPU flags')) if name: for src in [' ', '(', ')']: name = name.replace(src, '_') kind = data.get('Type') subkind = data.get('Subtype') guid = data.get('File GUID') if guid: guid = guid.lower() if subkind: kind += '::' + subkind # generate something plausible if kind == 'Microcode::Intel': guid = '3f0229ad-0a00-5269-90cf-0a45d8781b72' if not guid: #print('No GUID for', kind, fn) continue # ignore some kinds appstream_kinds = { '00h::Unknown 0': None, '01h::Compressed': None, '01h::Raw': None, '02h::Freeform': None, '02h::GUID defined': 'com.intel.Uefi.Raw', '03h::SEC core': 'com.intel.Uefi.Security', '04h::PEI core': 'com.intel.Uefi.Pei', '05h::DXE core': 'com.intel.Uefi.Dxe', '06h::PEI module': 'com.intel.Uefi.Peim', '07h::DXE driver': 'com.intel.Uefi.Driver', '09h::Application': 'com.intel.Uefi.Application', '0Ah::SMM module': 'com.intel.Uefi', '0Bh::Volume image': None, '0Ch::Combined SMM/DXE': 'com.intel.Uefi.SmmDxe', '0Dh::SMM core': 'com.intel.Uefi', '12h::TE image': None, '13h::DXE dependency': None, '14h::Version': None, '15h::UI': None, '17h::Volume image': None, '18h::Freeform subtype GUID': None, '19h::Raw': None, '1Bh::PEI dependency': None, '1Ch::MM dependency': None, 'BPDT store': None, 'CPD entry': None, 'CPD partition::Code': None, 'CPD partition::Key': None, 'CPD partition::Manifest': None, 'CPD partition::Metadata': None, 'CPD store': None, # ?? 'ECh': None, 'EDh::GUID': None, 'EEh::Name': None, 'EFh::Data': None, 'F0h::Pad': None, 'Free space': None, 'FTW store': None, 'Image::Intel': None, 'Image::UEFI': None, 'Microcode::Intel': 'com.intel.Microcode', 'NVAR entry::Full': None, 'Padding::Empty (0xFF)': None, 'Padding::Non-empty': None, 'Region::BIOS': None, 'Region::Descriptor': None, 'Region::DevExp1': None, 'Volume::FFSv2': None, 'Volume::NVRAM': 'com.intel.Uefi.NVRAM', 'VSS2 store': None, } if kind not in appstream_kinds: if len(kind) > 3: print('No appstream_kinds for', kind, fn) continue if not appstream_kinds[kind]: #print('Ignoring appstream kind', kind) continue # something plausible appstream_id = appstream_kinds[kind] if name: appstream_id += '.{}'.format(name) shard = ComponentShard(plugin_id=self.id, name=appstream_id, guid=guid) shard.set_blob(payload) # do not add duplicates! if shard.checksum in shard_by_checksum: #print('skipping duplicate {}'.format(guid)) continue # add attributes if kind == 'Microcode::Intel': # e.g. 000806E9 value = data.get_int('CPU signature') if value: shard.attributes.append( ComponentShardAttribute(key='cpuid', value='{:08X}'.format(value))) # e.g. C0 value = data.get_int('CPU flags') if value: shard.attributes.append( ComponentShardAttribute(key='platform', value='{:08X}'.format(value))) # e.g. C6 value = data.get_int('Revision') if value: shard.attributes.append( ComponentShardAttribute(key='version', value='{:08X}'.format(value))) # convert dd.mm.yyyy to yyyymmdd value = data.get('Date') if value: split = value.split('.') date_iso = '{}{}{}'.format(split[2], split[1], split[0]) shard.attributes.append( ComponentShardAttribute(key='yyyymmdd', value=date_iso)) # combined size of header and body sz_bdy = data.get_int('Full size') sz_hdr = data.get_int('Header size') if sz_bdy and sz_hdr: value = sz_bdy + sz_hdr shard.attributes.append( ComponentShardAttribute(key='size', value='{:08X}'.format(value))) # e.g. 98458A98 value = data.get_int('Checksum') if value: shard.attributes.append( ComponentShardAttribute(key='checksum', value='{:08X}'.format(value))) shard_by_checksum[shard.checksum] = shard shards.append(shard) return shards
def parse(self, blob): # sanity check PfatHeader = namedtuple('PfatHeader', ['size', 'csum', 'tag', 'ctrl']) try: pfat_hdr = PfatHeader._make( struct.unpack_from(PfatFile.PFAT_HEADER, blob, 0x0)) except struct.error as e: raise RuntimeError from e if pfat_hdr.tag != b'_AMIPFAT': raise RuntimeError('Not a PFAT header') # parse the header data which seems to be of the form: # "1 /B 4 ;BIOS_FV_BB.bin" where the blockcount is 4 in this example section_data = (blob[struct.calcsize(PfatFile.PFAT_HEADER):pfat_hdr. size].decode('utf-8').splitlines()) PfatSection = namedtuple('PfatSection', ['flash', 'param', 'blockcnt', 'filename']) sections = [] for entry in section_data[1:]: entry_data = entry.split(' ') sections.append( PfatSection( flash=int(entry_data[0]), param=entry_data[1], blockcnt=int(entry_data[2]), filename=entry_data[3][1:], )) # parse sections offset = pfat_hdr.size for section in sections: data = b'' for _ in range(section.blockcnt): PfatBlockHeader = namedtuple( 'PfatBlockHeader', [ 'revision', 'platform', 'unknown0', 'unknown1', 'flagsz', 'datasz', 'unknown2', 'unknown3', 'unknown4', ], ) block_hdr = PfatBlockHeader._make( struct.unpack_from(PfatFile.PFAT_BLOCK_HEADER, blob, offset)) block_data_start = ( offset + struct.calcsize(PfatFile.PFAT_BLOCK_HEADER) + block_hdr.flagsz) data += blob[block_data_start:block_data_start + block_hdr.datasz] offset += (struct.calcsize(PfatFile.PFAT_BLOCK_HEADER) + block_hdr.flagsz + block_hdr.datasz + struct.calcsize(PfatFile.PFAT_BLOCK_SIGN)) # add shard blob shard = ComponentShard() shard.set_blob(data) shard.name = 'com.ami.' + section.filename shard.guid = uuid.uuid3(uuid.NAMESPACE_DNS, section.filename) self.shards.append(shard)