Authenticode certificates and checks from a KM driver

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:

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

When a user verifies the Authenticode signature embedded:

  1. Public key is extracted from the embedded cert
  2. Encrypted digest embedded is decrypted with the public key
  3. 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
  4. 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.exeDigiCert EV Code Signing CA (SHA2)DigiCert High Assurance EV Root CA
IssuerCN = 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
SubjectCN = 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
Thumbprint1bfc555fd6489422bc2baba036b31aa75dc0aa1760ee3fc53d4bdfd1697ae5beae1cab1c0f3ad4e35fb7ee0633e259dbad0c4c9ae6d38f1a61c7dc25
Serial0ee3f1c8f451cbf21203341a53f23e7103f1b4e15f3a82f1149678b3d7d8475c02ac5c266a0b409b8f0b79f2ae462577
Signature algorithimsha256RSAsha256RSAsha1RSA

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:

ContextRegistry pathExplanation
User HKCU:\SOFTWARE\Microsoft\SystemCertificates\ Physical store for user-specific public keys
UserHKCU:\SOFTWARE\Policies\Microsoft\SystemCertificates\ Physical store for user-specific public keys installed by Active Directory (AD) Group Policy Objects (GPOs)
ComputerHKLM:\SOFTWARE\Microsoft\SystemCertificates\ Physical store for machine-wide public keys
ComputerHKLM:\SOFTWARE\Microsoft\Cryptography\Services\ Physical store for keys associated with a specific service
ComputerKLM:\SOFTWARE\Policies\Microsoft\SystemCertificates\ Physical store for machine-wide public keys installed by GPOs
ComputerHKLM:\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

ContextFile locationExplanation
UserC:\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.
  • AuthRoot: Certificate store for third-party certification authorities (CAs).
  • CertificationAuthority: Certificate store for intermediate certification authorities (CAs).
  • Disallowed: 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.
  • Root: Certificate store for certificate authorities (CA) that you trust.
  • TrustedPeople: 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:

TagLengthValueExplanation
300DSequence with a length of 13
OC05 48 45 4C 4C 4FUTF8 String with a length of 6 and the value HELLO
02011EInteger with a length of 1 and the value 0x1E (30)
010100Boolean 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
  • 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:

// Public Key
30 82 01 0a 02 82 01 01  00 b3 cc 04 ee e8 24 99
23 43 3b 6b 34 ce e6 ee  80 c5 8b ab 38 a4 28 f4
55 8f ab 8b e5 ab cb 44  62 25 c8 71 24 1f f6 15
03 35 76 a9 b0 9b a5 db  28 8a 24 45 fc e6 e4 fd
22 fe b7 17 8b d6 d3 77  11 42 bb 1a e3 6f f9 42
d2 b5 9b 41 d6 b1 5d 8f  f9 87 18 fb 55 fd 5e 1a
51 3d bc 44 ac 96 ba d5  97 9d 88 e4 59 cf e7 21
f1 07 28 d7 a5 ef 17 00  2c 5e 1e 1d 67 c5 b4 a9
83 8f 2e 2c 08 51 64 e4  14 89 91 2e 6e e4 30 f5
24 e9 b3 0e c3 08 98 a0  9c f1 c9 68 7a 3d dd d6
f9 9c 3b db 2c 96 a2 7f  17 97 87 f8 16 5f ed 64
fb 1d 72 43 ff 1a 95 3d  91 28 90 68 56 76 2b 66
4e ff a8 ee 36 9f 49 cb  a6 ee 87 9c 76 22 1d fb
98 48 9e 72 2e d1 1b b1  d8 2d 52 6b eb fa 62 00
9a c8 83 04 b6 64 ed 45  4c 21 34 f4 8d 36 3b 8b
b4 52 47 b3 a9 4e 2b 54  94 0f b2 b7 06 54 25 23
1f 36 fb 07 ba d4 a3 ea  b7 02 03 01 00 01

// Image digest - raw
ef 64 7e 10 ca 06 df 3d 22 d1 10 fb 78 17 92 b4 
f2 2c 0e f7 1e c1 95 cf 0c 33 59 a4 b8 b2 ff cb

// Content info (Bcrypt header + Image digest)
30 5c 06 0a 2b 06 01 04 01 82 37 02 01 04 a0 4e 
30 4c 30 17 06 0a 2b 06 01 04 01 82 37 02 01 0f 
30 09 03 01 00 a0 04 a2 02 80 00 30 31 30 0d 06 
09 60 86 48 01 65 03 04 02 01 05 00 04 20 ef 64 
7e 10 ca 06 df 3d 22 d1 10 fb 78 17 92 b4 f2 2c 
0e f7 1e c1 95 cf 0c 33 59 a4 b8 b2 ff cb

// Digest algo (_DIGEST_ALGO_IDENTIFIER_SHA256)
60 86 48 01 65 03 04 02 01 

// Signer info - signature algo (ALGO_IDENTIFIER_RSA)
2a 86 48 86 f7 0d 01 01 01

// Message digest e.g. SHA256(Content info)
a7 ee 55 49 55 11 15 df f5 ea 6a b4 10 44 9f de 
24 66 c5 f4 3f 8e 5c e4 de 01 20 4c 55 ab ca 6e

// Encrypted digest
33 d9 b3 76 1d a7 37 70 a1 b1 5b 3a 11 e8 12 34
03 43 b2 18 b4 b1 af 60 ad 2f cb 41 33 f5 6d 13
f5 6c 3f 84 d6 ef a1 77 07 b1 55 2c 9e 12 d5 1b
1f 3c 0e 6b 5e 9a d9 2f 5f d7 8b b2 a0 a1 62 c1
80 60 4f 8f ad 2c 0d 90 0e 1a 96 aa 13 24 8f ca
d8 05 c1 aa 98 1d df df 93 85 ea eb 7e 0c 7f 94
df d0 36 e5 ca df fc 64 d1 a0 7b 30 5a 87 a0 38
7b cb ea e1 6c d1 53 ad ab 48 3f 7f 73 90 66 39
bc b1 6a 8a b2 42 1c 7b bb 63 9f 3c 03 e6 82 f5
3a 0e fc 22 d1 9c eb a5 da 2b be 93 b3 9d 88 a3
43 d0 0c 70 90 06 84 0d 33 ff 3a 8e 3a b6 8f a6
7d 54 4a ea 59 f6 b4 7f 95 51 49 dc 96 0a ba 29
8e a1 71 36 d5 a9 d2 72 39 0d 54 15 b8 62 5c f1
aa 3a 2b f4 18 16 5b 8b 8b 62 49 02 89 c6 7d ea
29 15 0e c3 a9 15 ee 7f bd da 96 6d 35 6f 4d 2b
7e e1 04 f0 de be 7b 01 15 a4 07 a8 09 df 69 d9

// Signer info - signed attributes
31 81 9a 30 19 06 09 2a 86 48 86 f7 0d 01 09 03
31 0c 06 0a 2b 06 01 04 01 82 37 02 01 04 30 1c
06 0a 2b 06 01 04 01 82 37 02 01 0b 31 0e 30 0c
06 0a 2b 06 01 04 01 82 37 02 01 15 30 2e 06 0a
2b 06 01 04 01 82 37 02 01 0c 31 20 30 1e a0 1c
80 1a 00 41 00 64 00 6f 00 62 00 65 00 20 00 41
00 63 00 72 00 6f 00 62 00 61 00 74 30 2f 06 09
2a 86 48 86 f7 0d 01 09 04 31 22 04 20 a7 ee 55
49 55 11 15 df f5 ea 6a b4 10 44 9f de 24 66 c5
f4 3f 8e 5c e4 de 01 20 4c 55 ab ca 6e