Supported File Types

Various file types in Windows support Authenticode. For each file type, Windows must know how to create a unique digest of the file, and how to extract the embedded Authenticode signature from the file, and verify that those two match. This can get quite complicated; for instance, with PE executable files, the digest should not include the embedded signature itself, nor the checksum, as those would alter the signature.

Subject Interface Packages (SIPs)

To let Windows know how to create, store, retrieve, and verify a signature, specific Windows APIs called Subject Interface Packages (SIPs) are used. Each file type (subject) as a different SIP, i.e. typically a different DLL, that instruments Windows on these types of actions.

While Windows ships with a default set of SIPs, it is possible to create and register SIPs for any file type. All SIP methods are registered in registry key HKLM\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0. The default ones are:

File type

GUID

DLL

Verify method

MSI (.msi)

{000C10F1-0000-0000-C000-000000000046}

MSISIP.DLL

MsiSIPVerifyIndirectData

JScript

{06C9E010-38CE-11D4-A2A3-00104BD35090}

wshext.dll

VerifyIndirectData

AppX

{0AC5DF4B-CE07-4DE2-B76E-23C839A09FD1}

AppxSip.dll

AppxSipVerifyIndirectData

AppX Bundle

{0F5F58B3-AADE-4B9A-A434-95742D92ECEB}

AppxSip.dll

AppxBundleSipVerifyIndirectData

?

{18B3C141-AE0D-40F9-9465-E542AFC1ABC7}

WINTRUST.DLL

CryptSIPVerifyIndirectData

VBScript

{1629F04E-2799-4DB5-8FE5-ACE10F17EBAB}

wshext.dll

VerifyIndirectData

Windows Script

{1A610570-38CE-11D4-A2A3-00104BD35090}

wshext.dll

VerifyIndirectData

AppX Extensions

{1AD2DCB4-1FC8-42EF-8D9B-1EDFB2F7C75D}

AppxSip.dll

ExtensionsSipVerifyIndirectData

AppX P7X Signature

{5598CFF1-68DB-4340-B57F-1CACF88C9A51}

AppxSip.dll

P7xSipVerifyIndirectData

PowerShell (.ps1)

{603BCC1F-4B59-4E08-B724-D2C6297EF351}

pwrshsip.dll

PsVerifyHash

Structured Storage*

{941C2937-1292-11D1-85BE-00C04FC295EE}

WINTRUST.DLL

CryptSIPVerifyIndirectData

CTL

{9BA61D3F-E73A-11D0-8CD2-00C04FC295EE}

WINTRUST.DLL

CryptSIPVerifyIndirectData

Electronic Software Distribution

{9F3053C5-439D-4BF7-8A77-04F0450A1D9F}

EsdSip.dll

EsdSipVerifyHash

Office VBA

{9FA65764-C36F-4319-9737-658A34585BB7}

mso.dll

MsoVBADigSigVerifyIndirectData

PE (.exe)

{C689AAB8-8E78-11D0-8C47-00C04FC295EE}

WINTRUST.DLL

CryptSIPVerifyIndirectData

Java Class*

{C689AAB9-8E78-11D0-8C47-00C04FC295EE}

WINTRUST.DLL

CryptSIPVerifyIndirectData

Cabinet (.cab)

{C689AABA-8E78-11D0-8C47-00C04FC295EE}

WINTRUST.DLL

CryptSIPVerifyIndirectData

Encrypted AppX

{CF78C6DE-64A2-4799-B506-89ADFF5D16D6}

AppxSip.dll

EappxSipVerifyIndirectData

Encrypted AppX Bundle

{D1D04F0C-9ABA-430D-B0E4-D7E96ACCE66C}

AppxSip.dll

EappxBundleSipVerifyIndirectData

Flat Image

{DE351A42-8E59-11D0-8C47-00C04FC295EE}

WINTRUST.DLL

CryptSIPVerifyIndirectData

Catalog (.cat)

{DE351A43-8E59-11D0-8C47-00C04FC295EE}

WINTRUST.DLL

CryptSIPVerifyIndirectData

The SIPs for Structured Storage and Java Class are deprecated and may be absent in current versions of Windows.

See also

More information about developing, and abusing, custom SIPs can be found here:

AuthenticodeFile subclasses

In Signify, we implement a subset of available file types by subclassing signify.authenticode.AuthenticodeFile. Each subclass implements the required methods for extracting the signature from the file and verifying it, much like SIPs in Windows.

The easiest method of using this is by simply calling signify.authenticode.AuthenticodeFile.from_stream(), which should automatically determine the appropriate subclass based on the provided file.

In some cases, you may need to instantiate a subclass yourself, or access various methods provided by a subclass.

PE (.exe)

This class is used for the verification of Portable Executable (PE) files. This file type is best documented and forms the basis of our knowledge of how Authenticode works. See the official 2008 Microsoft paper Windows Authenticode Portable Executable Signature Format for more details on this format.

Hash calculation

The PE Certificate Table contains the Authenticode SignedData block for PE files. As a result, properly hashing PE files requires omitting the 4-byte checksum in the PE header, the certificate table pointer in the PE Data Directories, and the certificate table itself.

Note that the paper specifies that the sections must be hashed in file size order, which means that we can just use the file on disk for our purposes.

The SignedData object itself is located in the certificate table, which is comprised of a simple structure:

typedef struct _WIN_CERTIFICATE
{
    DWORD       dwLength;
    WORD        wRevision;
    WORD        wCertificateType;
    BYTE        bCertificate[ANYSIZE_ARRAY];
} WIN_CERTIFICATE, *LPWIN_CERTIFICATE

The wRevision must be 0x0200 (revision 2.0), as there’s currently no support for 0x0100 (revision 1.0). Similarly, the wCertificateType must be 0x0002 (PKCS Signed Data).

Page hashes

The SpcIndirectDataContent class may contain a binary structure that defines hashes for portions of the file (in the SpcLink.moniker field). If this is the case, the moniker will use class ID a6b586d5-b4a1-2466-ae05-a217da8e60d6, and its serialized data will contain another SpcAttributeTypeAndOptionalValue with microsoft_spc_pe_image_page_hashes_v1 (OID 1.3.6.1.4.1.311.2.3.1) for SHA-1 or microsoft_spc_pe_image_page_hashes_v2 (OID 1.3.6.1.4.1.311.2.3.2) for SHA-256.

The value will be a binary structure that describes offsets (4 bytes integer) and hash digest (digest length of the algorithm) of parts of the binary. These offsets appear to be relative to the entire file, and the final offset is always at the end of the file (describing the end of the previous hash), and the final hash is ignored:

0000000  08d88d96cb3fddf7a7c73598e95388ce60432c2c5ff17b8c558ce599645db73e
0001024  5ebe1d0255524e4291105759b80abad8294e269e3e11fce76ed6b2e005a79df0
0005120  255d7a5768ac44963184e0b5281d64fd9282f953211d03fd49a3d8190044dc35
...
1436160  35c36ac4c657e82cc3aa1311373c1b17552780f64e000a2c31742125365145cd
1438720  0000000000000000000000000000000000000000000000000000000000000000

Each hash is then calculated between the two defined offsets, using the same omissions as for normal Authenticode validation. The hashes are filled with NULL bytes when the hash would be shorter than the page size (typically 4096), ignoring omissions.

In the example above, for the first hash, we would calculate the hash over the first 1024 bytes of the PE file, skipping the checksum and table locations located in the PE header file, and then add 3072 NULL bytes to complete a full PE page. Note that the actual digest is calculated over less than 4096 bytes due to the omissions.

class signify.authenticode.signed_file.SignedPEFile(file_obj: BinaryIO)
__init__(file_obj: BinaryIO)

A PE file that is to be parsed to find the relevant sections for Authenticode parsing.

Parameters:

file_obj – A PE file opened in binary file

get_authenticode_omit_sections() dict[str, RelRange] | None

Returns all ranges of the raw file that are relevant for exclusion for the calculation of the hash function used in Authenticode.

The relevant sections are (as per http://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/Authenticode_PE.docx, chapter Calculating the PE Image Hash):

  • The location of the checksum

  • The location of the entry of the Certificate Table in the Data Directory

  • The location of the Certificate Table.

Returns:

dict if successful, or None if not successful

get_fingerprint(digest_algorithm: HashFunction, start: int = 0, end: int = -1, aligned: bool = False) bytes

Gets the fingerprint for this file, with the provided start and end, and optionally aligned to the PE file’s alignment.

get_fingerprinter() SignedPEFingerprinter

Returns a fingerprinter object for this file.

Return type:

signify.fingerprinter.SignedPEFingerprinter

get_fingerprints(*digest_algorithms: HashFunction) dict[str, bytes]

Calculate multiple fingerprints at once.

This can sometimes provide a small speed-up by pre-calculating all hashes.

iter_embedded_signatures(*, include_nested: bool = True, ignore_parse_errors: bool = True) Iterator[AuthenticodeSignature]

Returns an iterator over AuthenticodeSignature objects embedded in this Authenticode-signed file.

Parameters:
  • include_nested – If True, will also iterate over all nested SignedData structures

  • ignore_parse_errors

    Indicates how to handle ParseError that may be raised while fetching embedded AuthenticodeSignature structures.

    When True, which is the default and seems to be how Windows handles this as well, this will fetch as many valid AuthenticodeSignature structures until an exception occurs.

    Note that this will also silence the ParseError that occurs when there’s no valid AuthenticodeSignature to fetch.

    When False, this will raise the ParseError as soon as one occurs.

Raises:
  • ParseError – For parse errors in the signed file

  • signify.authenticode.AuthenticodeParseError – For parse errors in the SignedData

property page_size: int

Gets the page size from the optional COFF header, or if not available, returns 4096 as best guess.

verify_additional_hashes(indirect_data: IndirectData) None

Verifies the page hashes (if available) in the SpcPeImageData field.

class signify.authenticode.signed_file.SignedPEFingerprinter(file_obj: BinaryIO | SignedPEFile, block_size: int = 1000000)

An extension of the Fingerprinter class that enables the calculation of authentihashes of PE Files.

__init__(file_obj: BinaryIO | SignedPEFile, block_size: int = 1000000)

Allow file_obj to be a SignedPEFile instance.

add_signed_pe_hashers(*hashers: HashFunction, start: int = 0, end: int = -1, block_size: int | None = None) bool

Specialized method of add_hashers() to add hashers with ranges limited to those that are needed to calculate the hash of signed PE Files.

MSI (.msi)

This class is used for the verification of MSI files.

The MSI OLE file will contain a DigitalSignature section that contains the SignedData object.

The digest is calculated by recursively hashing all content entries in the OLE file in alphabetical order, except for the DigitalSignature and MsiDigitalSignatureEx sections.

When the MsiDigitalSignatureEx section is present, a so-called pre-hash is also calculated. The pre-hash hashes the metadata, i.e. various properties of (recursive) section headers, including the root section. The calculated pre-hash is prepended to the content hashed in DigitalSignature (i.e. hash(hash(metadata) + content)) and additionally stored in the MsiDigitalSignatureEx section.

class signify.authenticode.signed_file.SignedMsiFile(file_obj: BinaryIO | OleFileIO)

Implementation of MSI file Authenticode validation

__init__(file_obj: BinaryIO | OleFileIO)
Parameters:

file_obj – An MSI file opened in binary mode, or a olefile.OleFileIO object.

get_fingerprint(digest_algorithm: HashFunction) bytes

Compute the fingerprint for this MSI file.

property has_prehash: bool

Pre-hashes are ‘metadata’ hashes used when the MsiDigitalSignatureEx section is present. The pre-hash only hashes metadata, and the basic hash hashes the file content only.

The pre-hash is prepended to the MSI’s basic hash.

iter_embedded_signatures(*, include_nested: bool = True, ignore_parse_errors: bool = True) Iterator[AuthenticodeSignature]

Returns an iterator over AuthenticodeSignature objects embedded in this Authenticode-signed file.

Parameters:
  • include_nested – If True, will also iterate over all nested SignedData structures

  • ignore_parse_errors

    Indicates how to handle ParseError that may be raised while fetching embedded AuthenticodeSignature structures.

    When True, which is the default and seems to be how Windows handles this as well, this will fetch as many valid AuthenticodeSignature structures until an exception occurs.

    Note that this will also silence the ParseError that occurs when there’s no valid AuthenticodeSignature to fetch.

    When False, this will raise the ParseError as soon as one occurs.

Raises:
  • ParseError – For parse errors in the signed file

  • signify.authenticode.AuthenticodeParseError – For parse errors in the SignedData

verify_additional_hashes(indirect_data: IndirectData) None

Verifies the extended digest of MSI files.

Catalog (.cat)

Catalog Files (.cat) are used for externally signing files, mostly used in the case of driver files. They can be used to sign virtually any type of file. They are usually found in a subdirectory of C:\Windows\System32\CatRoot.

A Windows Service (cryptsvc) is used to register and index the catalog files on the system. The catdb database file, located in the catroot2 folder, is used by this service to store its data. Signify does not parse the contents of these files, but Dissect appears to be able to.

The same file format is used by Certificate Trust Lists (e.g. authroot.stl), which is used for distributing lists of certificates, particularly the Microsoft Root CA program, but can be used more widely.

Both files can be checked, although the content of the file is simply signed by using a generic SignedData object, and there’s no indirect data that contains an additional signature. That’s why this file format does not return AuthenticodeSignature objects, but rather a different subclass of SignedData.

If you wanted to verify this type of file through another catalog, you’d need to interpret it as a flat image. This is not natively supported, since this is most likely done by mistake and will probably lead to more confusion.

class signify.authenticode.signed_file.CtlFile(ctl: CertificateTrustList)

Validates CTL (Certificate Trust List) files, which commonly are either the Root Certificate Trust Lists (authroot.stl), or Catalog Files (.cat).

Note that this subclass is not fully type-safe, as it will not return AuthenticodeSignature objects.

__init__(ctl: CertificateTrustList) None
Parameters:

ctl – The CertificateTrustList object we’re operating on.

classmethod from_envelope(data: bytes) Self

Creates a CtlFile from a data envelope.

iter_embedded_signatures(*, include_nested: bool = True, ignore_parse_errors: bool = True) Iterator[CertificateTrustList]

Returns an iterator over the CertificateTrustList object.

Parameters:
  • include_nested – Ignored.

  • ignore_parse_errors – Ignored.

verify_signature(signed_data: AuthenticodeSignature | CertificateTrustList, *, expected_hashes: dict[str, bytes], **kwargs: Any) tuple[SignedData, IndirectData | None, Iterable[list[Certificate]]]

Change signature verification to directly use the CertificateTrustLis instead of its TrustSubjects.

Flat Image

Flat files are generic files, i.e. none of the other defined types. These can be hashed, but must be verified by an external signature (i.e. a catalog file).

This type is not automatically loaded, so you must use allow_flat=True to get a flat file. Then you can add a catalog and verify the file:

file = AuthenticodeFile.from_stream(open(r"bar.dat", "rb"), allow_flat=True)
file.add_catalog(open(r"foo.cat", "rb"))
file.verify()
class signify.authenticode.signed_file.FlatFile(file_obj: BinaryIO)

Simple flat file implementation that fully hashes the file’s contents.

__init__(file_obj: BinaryIO) None
get_fingerprint(digest_algorithm: HashFunction) bytes

Gets the fingerprint for this file

get_fingerprinter() Fingerprinter

Returns a fingerprinter object for this file.

get_fingerprints(*digest_algorithms: HashFunction) dict[str, bytes]

Calculate multiple fingerprints at once.

This can sometimes provide a small speed-up by pre-calculating all hashes.

iter_embedded_signatures(*, include_nested: bool = True, ignore_parse_errors: bool = True) Iterator[AuthenticodeSignature]

Returns an iterator over AuthenticodeSignature objects embedded in this Authenticode-signed file.

Parameters:
  • include_nested – If True, will also iterate over all nested SignedData structures

  • ignore_parse_errors

    Indicates how to handle ParseError that may be raised while fetching embedded AuthenticodeSignature structures.

    When True, which is the default and seems to be how Windows handles this as well, this will fetch as many valid AuthenticodeSignature structures until an exception occurs.

    Note that this will also silence the ParseError that occurs when there’s no valid AuthenticodeSignature to fetch.

    When False, this will raise the ParseError as soon as one occurs.

Raises:
  • ParseError – For parse errors in the signed file

  • signify.authenticode.AuthenticodeParseError – For parse errors in the SignedData

Signature File (.p7x)

Simple transparent AuthenticodeFile class that operates on an already-parsed signify.authenticode.AuthenticodeSignature. This can be used in places where the parsed SignedData object is present, but the original file is no longer present, or for parsing P7X files.

Note that this subclass does not know what content to hash or how to hash it, and so has only limited use. It cannot be verified through catalog files, and must be interpreted as flat image if you need to do this.

class signify.authenticode.signed_file.AuthenticodeSignatureFile(signed_data: AuthenticodeSignature | None)

Simple transparent AuthenticodeFile class that operates on an already-parsed AuthenticodeSignature. This can be used in places where the parsed SignedData object is present, but the original file is no longer present, or for parsing P7X files.

Note that the get_fingerprint() method is not implemented, so all hashes must be provided as expected hashes in the verify() method.

Remember that you can directly manipulate AuthenticodeSignature objects and that this class is a very simple shim. If you don’t need the features provided by AuthenticodeFile, simply use the AuthenticodeSignature directly.

__init__(signed_data: AuthenticodeSignature | None) None
Parameters:

signed_data – The signed data object we’re operating on. Can be None to allow filling it in later (see from_envelope()).

classmethod from_envelope(data: bytes) Self

Creates a AuthenticodeSignature from a data envelope. This will instantiate an ‘empty’ AuthenticodeSignatureFile object, and fill it in.

iter_embedded_signatures(*, include_nested: bool = True, ignore_parse_errors: bool = True) Iterator[AuthenticodeSignature]

Returns an iterator over AuthenticodeSignature objects embedded in this Authenticode-signed file.

Parameters:
  • include_nested – If True, will also iterate over all nested SignedData structures

  • ignore_parse_errors

    Indicates how to handle ParseError that may be raised while fetching embedded AuthenticodeSignature structures.

    When True, which is the default and seems to be how Windows handles this as well, this will fetch as many valid AuthenticodeSignature structures until an exception occurs.

    Note that this will also silence the ParseError that occurs when there’s no valid AuthenticodeSignature to fetch.

    When False, this will raise the ParseError as soon as one occurs.

Raises:
  • ParseError – For parse errors in the signed file

  • signify.authenticode.AuthenticodeParseError – For parse errors in the SignedData