def signed_datas(self): """Returns an iterator over :class:`signify.authenticode.SignedData` objects relevant for this PE file. :raises SignedPEParseError: For parse errors in the PEFile :raises signify.authenticode.AuthenticodeParseError: For parse errors in the SignedData :return: iterator of signify.authenticode.SignedData """ from signify.authenticode import SignedData found = False for certificate in self._parse_cert_table(): if certificate['revision'] != 0x200: raise SignedPEParseError("Unknown certificate revision %x" % certificate['revision']) if certificate['type'] == 2: yield SignedData.from_certificate(certificate['certificate'], pefile=self) found = True if not found: raise SignedPEParseError( "A SignedData structure was not found in the PE file's Certificate Table" )
def _parse_cert_table(self): """Parses the Certificate Table, iterates over all certificates""" locations = self.get_authenticode_omit_sections() if not locations or 'certtable' not in locations: raise SignedPEParseError( "The PE file does not contain a certificate table.") position = locations['certtable'].start while position < sum(locations['certtable']): # check if this position is viable, we need at least 8 bytes for our header if position + 8 > self._filelength: raise SignedPEParseError( "Position of certificate table is beyond length of file") self.file.seek(position, os.SEEK_SET) length = struct.unpack('<I', self.file.read(4))[0] revision = struct.unpack('<H', self.file.read(2))[0] certificate_type = struct.unpack('<H', self.file.read(2))[0] # check if we are not going to perform a negative read (and 0 bytes is weird as well) if length <= 8: raise SignedPEParseError( "Invalid length in certificate table header") certificate = self.file.read(length - 8) yield { 'revision': revision, 'type': certificate_type, 'certificate': certificate } position += length + (8 - (length % 8))
def iter_signed_datas(self, include_nested=True): """Returns an iterator over :class:`AuthenticodeSignedData` objects relevant for this PE file. :param include_nested: Boolean, if True, will also iterate over all nested SignedData structures :raises SignedPEParseError: For parse errors in the PEFile :raises signify.authenticode.AuthenticodeParseError: For parse errors in the SignedData :return: iterator of signify.authenticode.SignedData """ from signify.authenticode.structures import AuthenticodeSignedData def recursive_nested(signed_data): yield signed_data if include_nested: for nested in signed_data.signer_info.nested_signed_datas: yield from recursive_nested(nested) found = False for certificate in self._parse_cert_table(): if certificate['revision'] != 0x200: raise SignedPEParseError("Unknown certificate revision %x" % certificate['revision']) if certificate['type'] == 2: yield from recursive_nested(AuthenticodeSignedData.from_envelope(certificate['certificate'], pefile=self)) found = True if not found: raise SignedPEParseError("A SignedData structure was not found in the PE file's Certificate Table")
def _parse_cert_table(self): """Parses the Certificate Table, iterates over all certificates""" locations = self.get_authenticode_omit_sections() if not locations or 'certtable' not in locations: raise SignedPEParseError( "The PE file does not contain a certificate table.") position = locations['certtable'].start while position < sum(locations['certtable']): self.file.seek(position, os.SEEK_SET) length = struct.unpack('<I', self.file.read(4))[0] revision = struct.unpack('<H', self.file.read(2))[0] certificate_type = struct.unpack('<H', self.file.read(2))[0] certificate = self.file.read(length - 8) yield { 'revision': revision, 'type': certificate_type, 'certificate': certificate } position += length + (8 - (length % 8))
def _parse_pe_header_locations(self): """Parses a PE file to find the sections to exclude from the AuthentiCode hash. See http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx for information about the structure. """ location = {} # Check if file starts with MZ self.file.seek(0, os.SEEK_SET) if self.file.read(2) != b'MZ': raise SignedPEParseError("MZ header not found") # Offset to e_lfanew (which is the PE header) is at 0x3C of the MZ header self.file.seek(0x3C, os.SEEK_SET) pe_offset = struct.unpack('<I', self.file.read(4))[0] if pe_offset >= self._filelength: raise SignedPEParseError( "PE header location is beyond file boundaries (%d >= %d)" % (pe_offset, self._filelength)) # Check if the PE header is PE self.file.seek(pe_offset, os.SEEK_SET) if self.file.read(4) != b'PE\0\0': raise SignedPEParseError("PE header not found") # The COFF header contains the size of the optional header self.file.seek(pe_offset + 20, os.SEEK_SET) optional_header_size = struct.unpack('<H', self.file.read(2))[0] optional_header_offset = pe_offset + 24 if optional_header_size + optional_header_offset > self._filelength: # This is not strictly a failure for windows, but such files better # be treated as generic files. They can not be carrying SignedData. raise SignedPEParseError( "The optional header exceeds the file length (%d + %d > %d)" % (optional_header_size, optional_header_offset, self._filelength)) if optional_header_size < 68: # We can't do authenticode-style hashing. If this is a valid binary, # which it can be, the header still does not even contain a checksum. raise SignedPEParseError( "The optional header size is %d < 68, which is insufficient for authenticode", optional_header_size) # The optional header contains the signature of the image self.file.seek(optional_header_offset, os.SEEK_SET) signature = struct.unpack('<H', self.file.read(2))[0] if signature == 0x10b: # IMAGE_NT_OPTIONAL_HDR32_MAGIC rva_base = optional_header_offset + 92 # NumberOfRvaAndSizes cert_base = optional_header_offset + 128 # Certificate Table elif signature == 0x20b: # IMAGE_NT_OPTIONAL_HDR64_MAGIC rva_base = optional_header_offset + 108 # NumberOfRvaAndSizes cert_base = optional_header_offset + 144 # Certificate Table else: # A ROM image or such, not in the PE/COFF specs. Not sure what to do. raise SignedPEParseError( "The PE Optional Header signature is %x, which is unknown", signature) # According to the specification, the checksum should not be hashed. location['checksum'] = RelRange(optional_header_offset + 64, 4) # Read the RVA if optional_header_offset + optional_header_size < rva_base + 4: logger.debug( "The PE Optional Header size can not accommodate for the NumberOfRvaAndSizes field" ) return location self.file.seek(rva_base, os.SEEK_SET) number_of_rva = struct.unpack('<I', self.file.read(4))[0] if number_of_rva < 5: logger.debug( "The PE Optional Header does not have a Certificate Table entry in its Data Directory; " "NumberOfRvaAndSizes = %d", number_of_rva) return location if optional_header_offset + optional_header_size < cert_base + 8: logger.debug( "The PE Optional Header size can not accommodate for a Certificate Table entry in its Data " "Directory") return location # According to the spec, the certificate table entry of the data directory should be omitted location['datadir_certtable'] = RelRange(cert_base, 8) # Read the certificate table entry of the Data Directory self.file.seek(cert_base, os.SEEK_SET) address, size = struct.unpack('<II', self.file.read(8)) if not size: logger.debug("The Certificate Table is empty") return location if address < optional_header_size + optional_header_offset or address + size > self._filelength: logger.debug( "The location of the Certificate Table in the binary makes no sense and is either beyond the " "boundaries of the file, or in the middle of the PE header; " "VirtualAddress: %x, Size: %x", address, size) return location location['certtable'] = RelRange(address, size) return location