Introduction

Windows does not make it easy to do digital signature checks from a kernel driver. All the documented and easy to use wintrust.h APIs only reside in usermode, so you’re left to fend for your self.

The 1st option is to use OpenSSL but if you build a driver targeting 1903+, the WHQL process seems to require submitting additional logs like SDV etc. The OpenSSL project is littered with issues, you will get many 1000’s of warning from memory misalignment to type issues. More importantly the encrypted digest generated was never my expected value, it’s possible I could have done something wrong in the implementation. Plus if you read through the code and search for “hack”, “todo”, it makes me uneasy. Lastly a driver compiled with OpenSSL is typically 250KB, whereas using native method is only 30KB, and when you have limited space in (which is another story) it’s certainly important to us.

The 2nd option is to use the undocumented exports from the Code Integrity DLL loaded into the System [4] process. Whilst not the safest method for release drivers on customer machines, it is relatively simpler to implement and nicely explained by Ido Moshe and Liron Zuarets in this post https://medium.com/cybereason/code-integrity-in-the-kernel-66b3f5cce5f

Lastly the 3rd option is to use Bcrypt. It is certainly possible to link against this header from a kernel driver in C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared\bcrypt.h and linking the kernel library ksecdd.lib . One can find a variety of interesting exports such as BCryptOpenAlgorithmProvider, BCryptImportKeyPair, BCryptGenerateSymmetricKey, BCryptCreateHash, BCryptHashData, BCryptDecrypt, BCryptEncrypt, BCryptExportKey, BCryptVerifySignature etc, but a lot of struct’s usually created by other functions and passed around as HANDLEs, must now be manually and painfully crafted by you and passed around. Exports can be seen via:

dumpbin.exe /exports "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\km\x64\ksecdd.lib"

Authenticode

Authenticode is a Microsoft code signing technology using asymmetric encryption with public/private keys. It is an embedded DER-encoded X509 certificate in a non-executable portion of the driver or can use a catalog file (a detached signature) to verify the driver integrity. Authenticode is fully explained by Microsoft in Authenticode_PE.docx in 2008 however not all sections are up to date, for example the document states certificate Thumbprints are MD5 hashes, in modern times the Thumbprint (hash of the DER encoded cert) are actually SHA1 hashes.

Adding a certificate, or multiple certificates part of a chain, does not affect the binary signature because the certificate table itself, certificate table pointer, and file checksum are not part of the certificate signature as seen in Fig 1.



Figure 1 – Overview of the PE file and Authenticode signature format (Microsoft)

Notice a security issue? What if you added arbitrary data as a Certificate entry in a well signed binary, well the hashes would still be valid. Fortunately .cat files resolve this issue, but not all drivers are catalog signed.

* So if you’re a red teamer, why drop mimikatz or something loud; just add a new 3rd party root CA, not like anyone will notice with all the other junk in there as seen in Fig 4.

Some useful terminology:

Digest/Message digest = the output of a hash function

Encrypted digest = signer’s private key is used to encrypt the digest resulting in a signature

Signing the message = entire process of calculating the digest and encrypting

Public key = just contains a modulus and exponent

PKCS #7 = is a standard format for cryptographic data, including signed data, certificates, and certificate revocation lists (CRLs)

The basic overview of the signing process is:

PE image is hashed, specifically in Fig 1 the white background parts with e.g. SHA256 Signer’s private key e.g. (RSA 2048) encrypts the above digest giving us an encrypted digest The encrypted digest is combined with the certificate and hash algorithm creating a signature block This signature block is inserted into the PE file

When a user verifies the Authenticode signature embedded:

Public key is extracted from the embedded cert Encrypted digest embedded is decrypted with the public key The same hash algorithm (e.g. SHA256) that was used to create the original digest, is ran again on the PE code/data/etc sections, to create a digest Digests from step 2 and 3 are compared. If they’re the same, the public key matches the private key used to sign the original code, proving the code hasn’t been modified

Certificate chains

RDNs are zero or more comma-separated components called relative Distinguished Names, forming a DN. Looking at the DNs of a binary, here’s Adobe Reader uploaded locally. In Fig 2 and Table 1 we can see 10 RDNs for AcroRd32.exe Subject field.



Figure 2 – Cert Subject of Adobe Reader

Figure 3 – Certificate path

The process to check a chain of certificates back to its root, is simply to compare the child Issuer to a parent Subject. In Fig 3 above is the path, and below in Table 1 are the corresponding links between child (issuer field) and parent (subject field). And almost certainly that DigiCert Root CA with Thumbprint [5fb7ee..] is installed on your machine, as seen below in Fig 4.

AcroRd32.exe DigiCert EV Code Signing CA (SHA2) DigiCert High Assurance EV Root CA Issuer CN = DigiCert EV Code Signing CA (SHA2)

OU = www.digicert.com

O = DigiCert Inc

C = US CN = DigiCert High Assurance EV Root CA

OU = www.digicert.com

O = DigiCert Inc

C = US CN = DigiCert High Assurance EV Root CA

OU = www.digicert.com

O = DigiCert Inc

C = US Subject CN = Adobe Inc.

OU = Acrobat DC

O = Adobe Inc.

L = San Jose

S = ca

C = US

SERIALNUMBER = 2748129

2.5.4.15 = Private Organization

1.3.6.1.4.1.311.60.2.1.2 = Delaware

1.3.6.1.4.1.311.60.2.1.3 = US CN = DigiCert EV Code Signing CA (SHA2)

OU = www.digicert.com

O = DigiCert Inc

C = US CN = DigiCert High Assurance EV Root CA

OU = www.digicert.com

O = DigiCert Inc

C = US Thumbprint 1bfc555fd6489422bc2baba036b31aa75dc0aa17 60ee3fc53d4bdfd1697ae5beae1cab1c0f3ad4e3 5fb7ee0633e259dbad0c4c9ae6d38f1a61c7dc25 Serial 0ee3f1c8f451cbf21203341a53f23e71 03f1b4e15f3a82f1149678b3d7d8475c 02ac5c266a0b409b8f0b79f2ae462577 Signature algorithim sha256RSA sha256RSA sha1RSA

Table 1 – The links between child and parent certs

Certificate locations

The location of root certificates can be found in the registry under a HKLM key. You may think this is potentially a security issue as I did initially, but an admin could just kernel debug the machine or attach to lsass.exe and steal the cached credentials or using psexec as SYSTEM overwrite protected folders; so an admin adding and removing system certificates is certainly within there power. In Table 2 and 3 is Window Certificate Manager store for all types of certs:

Context Registry path Explanation User HKCU:\SOFTWARE\Microsoft\SystemCertificates\ Physical store for user-specific public keys User HKCU:\SOFTWARE\Policies\Microsoft\SystemCertificates\ Physical store for user-specific public keys installed by Active Directory (AD) Group Policy Objects (GPOs) Computer HKLM:\SOFTWARE\Microsoft\SystemCertificates\ Physical store for machine-wide public keys Computer HKLM:\SOFTWARE\Microsoft\Cryptography\Services\ Physical store for keys associated with a specific service Computer KLM:\SOFTWARE\Policies\Microsoft\SystemCertificates\ Physical store for machine-wide public keys installed by GPOs Computer HKLM:\SOFTWARE\Microsoft\EnterpriseCertificates\ Physical store for machine-wide public keys installed by the Enterprise PKI Containers within an AD domain

Table 2 – Registry paths for cert stores

Context File location Explanation User C:\user\abc\APPDATA\Microsoft\SystemCertificates\ Physical store for user-specific public keys and pointers to private keys User C:\user\abc\APPDATA\Microsoft\Crypto\ Physical store for user-specific private key containers Computer C:\ProgramData\Microsoft\Crypto\ Physical store for machine-wide private key containers

Table 3 – Folders for cert stores

The types of cert stores are:

AddressBook : Certificate store for other people and resources.

: Certificate store for other people and resources. AuthRoot : Certificate store for third-party certification authorities (CAs).

: Certificate store for third-party certification authorities (CAs). CertificationAuthority : Certificate store for intermediate certification authorities (CAs).

: Certificate store for intermediate certification authorities (CAs). Disallowed : Certificate store for certificates that have been revoked so they aren’t forgotten.

: Certificate store for certificates that have been revoked so they aren’t forgotten. My : Certificate store for your personal certificates that you use and is where most custom certificates.

: Certificate store for your personal certificates that you use and is where most custom certificates. Root : Certificate store for certificate authorities (CA) that you trust.

: Certificate store for certificate authorities (CA) that you trust. TrustedPeople : Certificate store for other people and resources that you trust.

: Certificate store for other people and resources that you trust. TrustedPublisher: Certificate store for application publishers that you trust.

Our “DigiCert High Assurance EV Root CA” is a third-party CA, so we can look under the reg key for machine wide system certificates HKLM\SOFTWARE\Microsoft\SystemCertificates\AuthRoot\Certificates

The thumbprint for our DigiCert Root cert was 5fb7ee0633e259dbad0c4c9ae6d38f1a61c7dc25 and in Fig 4 we see it in the registry. This is the performant way Windows searches certs, by using Thumbprints.



Figure 4 – A whole boat load of non-Microsoft root CA’s on my machine

Another way to view individual cert details is to manually extract it from your signed binary, where you can “Copy to file” any cert in the chain. Then you can use the tool certutil to dump a vast amount of data, or use certutil to view details about the overall binary.

OID

OID notation is a dotted string of numbers, for example 1.2.840.113549.1.9.4 which is represented in hex as 06 09 2A 86 48 86 F7 0D 01 09 04 this online conversion tool is helpful, and a python3 script I created to convert between them faster https://github.com/AstralVX/oidhex_to_dot – OID for messageDigest is broken down to:

1.2.840.113549.1.9.4 – messageDigest

1.2.840.113549.1.9 – PKCS-9 Signatures

1.2.840.113549.1 – PKCS

1.2.840.113549 – RSADSI

1.2.840 – USA

1.2 – ISO member body

1 – ISO assigned OIDs

TLV

Every ASN.1 data is encoded as a Tag Length Value (TLV) triplet. The tag defines the object type e.g. int, bool, string, sequence, etc. A TLV can also have sub TLVs.

Example ASN.1 data: 30 0D 0C 05 48 45 4C 4C 4F 02 01 1E 01 01 00

Table 4 explains the TLV, and below it is the ASN.1 sequence, and C style struct:

Tag Length Value Explanation 30 0D Sequence with a length of 13 OC 05 48 45 4C 4C 4F UTF8 String with a length of 6 and the value HELLO 02 01 1E Integer with a length of 1 and the value 0x1E (30) 01 01 00 Boolean with a length of 1 and the value 0 (FALSE)

//ASN.1 sequence Sequence { Name::= UTF8 String Age::= Integer isSmoker::= [0] Boolean OPTIONAL } //C struct struct person { CHAR[5] Name, INT age, BOOL isSmoker}

ASN.1

ASN.1 is a general way to describe structures. The Authenticode digital signature contains Authenticated attributes (all the juicy parts) and Unauthenticated attributes (timestamping signature, MS countersigned). Explorer is pretty lame as seen in Fig 5 and only shows a few OIDs with readable names, it doesn’t do all the conversions, and misses out some important OIDs. What we do see is in Authenticated attributes is:

1.2.840.113549.1.9.3 = contentType. Contains a messageDigest OID (1.3.6.1.4.1.311.2.1.4) 1.3.6.1.4.1.311.2.1.11 = SPC_STATEMENT_TYPE_OBJID 1.3.6.1.4.1.311.2.1.12 = SpcSpOpusInfo. Contains: programName [optional] description, field [optional], a URL 1.2.840.113549.1.9.4 = messageDigest

Where 1.3.6.1.4.1.311.2.1.4 == SPC_INDIRECT_DATA_OBJID (06 0a 2b 06 01 04 01 82 37 02 01 04), which is a SpcIndirectDataContent struct:

SpcIndirectDataContent (struct) { Data - SpcAttributeTypeAndOptionalValue (struct) { Type - OID: SPC_PE_IMAGE_DATAOBJ [1.3.6.1.4.1.311.2.1.15] (06 0a 2b 06 01 04 01 82 37 02 01 0f) Value - SpcPeImageData (struct) { Flags - File - SPCLink (struct) { URL [0]: Moniker [1]: SpcSerializedObject (struct) { SpcUuid - 10 byte const GUID (a6 b5 86 d5 b4 a1 24 66 ae 05 a2 17 da 8e 60 d6) SerializedData - struct of page hashes if present } } File [2]: SpcString (struct) containing Unicode string "<<<Obsolete>>>" } } MessageDigest - DigestInfo (struct) { DigestAlgorithim - must be same as SignerInfo.DigestAlgorithim Digest - message digest value } }



Figure 5 – Digital signature tab of Adobe Reader

Implementation

Due to Copyright of work for my company, I can not release the full crypto code which is a few thousand lines of code. But at a very high level one can do the following:

Read PE header of target binary for digital signature check, parse DOS/NT sections, and extract the embedded Authenticode certs out

Start the process of parsing TLVs – parse the SignedData sequence, then another TLV later is digest algorithm, then you can get ContentInfo struct, SignerInfo struct

One can then loop between certs and check for serials/valid date ranges/hash algos/EKU code signing etc etc between the intermediate certs

To validate the topmost parent is a root cert, you can generate a Thumbprint of the topmost cert using Bcrypt and check in the root cert store for that Thumbprint

Then verify the message digest by hashing the actual binary with the appropriate digest algo, and comparing it to the embedded AuthenticodeInfo signer message digest

Next you verify the encrypted digest which proves it was the signer who signed the raw message digest. That’s by done by generating a digest of the signed attribute struct, and applying their public key to the embedded encrypted digest to obtain the unsigned digest, if they match it proves the signer signed it with their private key. Tons of Bcrypyt needed here and the trickiest part, use of manually crafted pRsaKeyBlob which is composed of

[BCRYPT_RSAKEY_BLOB, pbPublicExponent (big endian), pbRawPublicKey (big endian)]. You can then use that crafted struct in BCryptImportKeyPair(BCRYPT_RSAPUBLIC_BLOB), and BCryptEncrypt(BCRYPT_PAD_NONE) which encrypts the unsigned digest with the public key

[BCRYPT_RSAKEY_BLOB, pbPublicExponent (big endian), pbRawPublicKey (big endian)]. You can then use that crafted struct in BCryptImportKeyPair(BCRYPT_RSAPUBLIC_BLOB), and BCryptEncrypt(BCRYPT_PAD_NONE) which encrypts the unsigned digest with the public key Lastly is the validation of the image digest – (hash as explained in the Authenticode docx at the start of this blog, dos/nt/sections/etc), and compare that generated hash to the image digest stored in authenticode

Unfortunately there is tons of TLV parsing, Bcrypt, buffer overrun checks, digests galore, blacklisted cert checks, cert pinning checks, within all those points. It’s not for the faint of heart to implement, not just a couple of a Bcrypt calls and some parsing, it’s multiple 1000’s of lines worth of work. But now you have an idea. Fig 6 shows some headers you would need to create.



Figure 6 – Structs related to Authenticode processing

Sample values

Using the same Adobe binary, if one needs a sample with calculated values to compare: