PKCS

Recently I was working on a tool written in Python that takes an inventory of executables installed on a system. Part of this inventory is digital signature information of signed Windows binaries. On Windows, executables are in Portable Executable (PE) format.

A great module for PE’s exists for Python: pefile. From their website:

pefile is a multi-platform Python module to read and work with Portable Executable (aka PE) files. Most of the information in the PE Header is accessible, as well as all the sections, section’s information and data.

With pefile, we can now access the digital signature block in an executable, if any:

import pefile

#  load and parse PE
pe = pefile.PE('/path/to/foo.exe')

#  get the security directory entry
address = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']].VirtualAddress

if address > 0:  
  # Always in DER format AFAIK
  derData = pe.write()[address + 8:];

Now that we have the DER data, some other interesting Python modules come into play:

  1. pyasn1: A module for parsing Abstract Syntax Notation One (ASN.1) data. The DER data we just acquired is ASN.1 encoded.
  2. pyasn1-modules: A collection of ASN.1-based protocols modules

Using the the above modules, we can start to parse and extract information from the DER data:

# ...continuation from above ...

(contentInfo, rest) = decode(derData, asn1Spec=rfc2315.ContentInfo())

contentType = contentInfo.getComponentByName('contentType')

if contentType == rfc2315.signedData:  
  signedData = decode(
    contentInfo.getComponentByName('content'),
    asn1Spec=rfc2315.SignedData())

for sd in signedData:  
  if sd == '':
    continue

  signerInfos = sd.getComponentByName('signerInfos')
    for si in signerInfos:
      issuerAndSerial = si.getComponentByName('issuerAndSerialNumber')
      issuer = issuerAndSerial.getComponentByName('issuer').getComponent()
      for i in issuer:
        for r in i:
          at = r.getComponentByName('type')                       
          if rfc2459.id_at_countryName == at:
            cn = decode(
                r.getComponentByName('value'), 
                asn1Spec=rfc2459.X520countryName())
            print(cn[0])
          elif rfc2459.id_at_organizationName == at:
            on = decode(
                r.getComponentByName('value'),
                asn1Spec=rfc2459.X520OrganizationName())
            print(on[0].getComponent())
          elif rfc2459.id_at_organizationalUnitName == at:
            ou = decode(
                r.getComponentByName('value'),
                asn1Spec=rfc2459.X520OrganizationalUnitName())
            print(ou[0].getComponent())
          elif rfc2459.id_at_commonName == at:
            cn = decode(
                r.getComponentByName('value'),
                asn1Spec=rfc2459.X520CommonName())
            print(cn[0].getComponent())
          else:
            print at

…not too bad!

The above code is for illustration purposes and omits nearly all error checking, but should get the point across.