Have you ever wondered what is going on under that annoying Windows pop-up message telling you that a trusted application is going to make changes in your computer? If you have, keep reading.
For these curious people, this trusting chain is accomplished thanks to code signing. In this post, we will describe code signing and how it is performed in Windows OS.
Code signing (called Authenticode in Windows) helps to establish trust in computer software, since it authenticates the software publisher and guarantees code integrity through the validation of the digital signature shipped within the software. Among other files, it is normally used to sign Portable Executable (PE) files such as executable (
.exe), dynamically loaded library (
.dll), and driver (
The Authenticode signature of a PE file follows the PKCS#7 structure that includes the signature (the hash value of the PE file), a timestamp (optional) and the certificate chain. Roughly speaking, the Authenticode signature is a binary data blob consisting of a certificate and a signed hash of the PE file.
Regarding the hash calculation, it excludes certain parts of the PE header that are altered in the signing process itself (shown in gray color in Figure 1). For example, the
Checksum field from
Optional Header needs to be recalculated once the certificate is embedded into the PE file.
As hash algorithms, Authenticode supports MD5 (only for backward compatibility), SHA-1, and SHA-256 hashes. A PE file can be dual signed by applying multiple signatures, which is strongly recommended when using deprecated hashing algorithms such as MD5. The certificate chain is built to a trusted root certificate by using X.509 chain-building rules. This trusted root certificate is mandatory as long as the root certificate is not present in the users’ root stores.
An Authenticode-signed Windows file can be shipped in two different ways, either through an embedded signature within the PE file structure or through a digitally-signed catalog file. Whether embedded signed or catalog signed, both signatures are stored as PKCS#7 signed data, which follows the Abstract Syntax Notation One (ASN.1) format. You can read more about the standard on its specification, but we only need to know that Authenticode signatures follows the binary DER-encoded ASN.1 format.
The digital signature data in an embedded Authenticode signed Windows PE file is appended to the PE file. The offset and size of the embedded signature is stored in the Security directory entry within the Data directories array of the PE optional header (namely, in entry 4). The Data directories array contains offsets and sizes of different structures within the PE file, such as the export, import, or relocation directories, among others. All directories but the security directory store their offset as relative virtual address (RVA) offset, which means that they are relative to the virtual address where the PE file is loaded into memory. On the contrary, the security directory stores the offset as a file offset instead (an example is shown in Figure 2).
The binary data stored at the security directory file offset is a
WIN_CERTIFICATE structure, which defines the signature length (in bytes), the certificate revision, the type of certificate, and the certificate chain (see Figure 3). As told above, the certificate type of Authenticode signatures matches a PKCS#7
SignedData structure (
A catalog file (extension .cat) collects digital signatures for an arbitrary number of files. It contains a collection of cryptographic hashes, each one corresponding to a file that is included in the collection (see Figure 4). To prevent unauthorized modifications, catalog files are also Authenticode-signed files. Catalog files are located in the
system32/catroot path within the Windows directory (normally,
C:\Windows). The database of catalog files in a Windows OS is maintained in a separate file, named
catdb, which is located in the
system32/catroot2 path within the Windows directory.
The above description only has sense if we can verify the Authenticode signature. Generally speaking, to verify a PE file we need to (1) calculate again the file hash and compare it with the one shipped within the signature, and (2) check that the certificate is valid (that is, the certificate chain ends in a trusted root certificate, the certificate isn’t expired and not revoked). If everything is checked successfully, it is said that we can trust the PE file comes from a trusted third-party and the file content was not modified since the moment the file was signed.
Don’t worry! You don’t need to code yourself this workflow (although you may, since it is a good practice to learn how to work with PE structures). All this process is magically done by Windows thanks to the
WinVerifyTrust function of
WINTRUST.dll (it also relies on cryptographic functions provided by
CRYPT32.DLL). Its function header is:
LONG WinVerifyTrust( HWND hwnd, GUID *pgActionID, LPVOID pWVTData );
We need to pass an optional window handle to a caller window (
NULL for our purpose), a pointer to a GUID structure that indicates the type of verification action to be performed (
WINTRUST_ACTION_GENERIC_VERIFY_V2 type for Authenticode), and a pointer to a
WINTRUST_DATA structure which specifies what to be evaluated (for example, to validate if certificate is revoked). A full example can be found in Windows Dev Center.
WinVerifyTrust function performs several actions and eventually invokes the
CertGetCertificateChain function (in
CRYPT32.DLL), which builds a certificate chain
context starting from an end certificate and going back (if
possible) to a trusted root certificate. Its function header is:
BOOL CertGetCertificateChain( HCERTCHAINENGINE hChainEngine, PCCERT_CONTEXT pCertContext, LPFILETIME pTime, HCERTSTORE hAdditionalStore, PCERT_CHAIN_PARA pChainPara, DWORD dwFlags, LPVOID pvReserved, PCCERT_CHAIN_CONTEXT *ppChainContext );
This function accepts some flags to indicate whether extra processing is required when checking the certificate. These flags and their meaning is further explained in the MSDN documentation.
If you want to read more technical stuff regarding the execution flow of the
WinVerifyTrust function, you can keep an eye on our recently paper published in FSIDI. We have explained in detail the trace of Windows functions when an Authenticode signature is validated.
In our next blog, we’ll present how to perform a manual Authenticode signature validation using OpenSSL when we are not in a Windows OS, or when we are unable to use the well-known official Microsoft’s sigcheck tool to do so.