Tip: Reading Certificates from Windows Registry

This topic has come up a few times, and today in an internal chat a few of us worked through it so I thought I'd post this tip.
Reading Certificates from the Windows Registry is a complex thing. Aside from each certificate being stored in REG_BINARY format in keys beneath HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\ROOT\Certificates, we also have to deal with the complication that...what's stored in the value is not just the certificate.

The value is a container format there, described at [MS-GPEF]: Certificate BLOB | Microsoft Learn and [MS-GPEF]: Certificate BLOB Encoding | Microsoft Learn and also described quite helpfully in the blog post at Extracting Certificates From the Windows Registry – NVISO Labs

Based on those two resources, we are able to determine a few things:

  1. The value in the Registry contains a Certificate BLOB which is not just an encoded certificate
  2. The value in the Registry contains a variable number of different properties that may come before the ceritificate itself ( [MS-GPEF]: Certificate BLOB Properties | Microsoft Learn )
  3. Before the start of the certificate the following bytes are present:

Reserved (8 bytes): This field MUST be set to the following bytes, in the following order: 0x20 0x00 0x00 0x00 0x01 0x00 0x00 0x00. ( [MS-GPEF]: Certificate BLOB Encoding | Microsoft Learn )

  1. After that reserved delimiter, the next four bytes contain the length of the certificate field
  2. After the four bytes of length, the certificate itself is stored as ASN.1 DER encoded binary

In BigFix, we have several inspector that can create the 'x509 certificate' object:

q: properties whose (it as string contains "encoded")
A: pem encoded certificate of <file>: x509 certificate
A: pem encoded certificate string of <string>: x509 certificate
A: base64 der encoded certificate string of <string>: x509 certificate

The 'pem encoded certificate' creation classes are not going to help here, because Windows is storing these as der-encoded binaries rather than base64-encoded strings.

Read that again, it confused me for some time...the pem encoded certificate inspectors read a base64-encoded pem file or string, the ones that look like

-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgIJAKc5k1a2QkczMA0GCSqGSIb3DQEBCwUAMFsxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp
c2NvMQ8wDQYDVQQKDAZNeUNvcnAxDjAMBgNVBAMMBWxvY2FsMB4XDTIzMTAyNDE1
MjM0NVoXDTI0MTAyMzE1MjM0NVowWzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh
bGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBk15Q29y
cDEOMAwGA1UEAwwFbG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDZr3t6mP9Q8b5Qj8n5N6m+3q4r8b5Qj8n5N6m+3q4r8b5Qj8n5N6m+3q4r8b5Q
j8n5N6m+3q4r8b5Qj8n5N6m+3q4r8b5Qj8n5N6m+3q4r8b5Qj8n5N6m+3q4r8b5Q
j8n5N6m+3q4r8b5Qj8n5N6m+3q4r8b5Qj8n5N6m+3q4r8b5Qj8n5N6m+3q4r8b5Q
j8n5N6m+3q4r8b5Qj8n5N6m+3q4r8b5Qj8n5N6m+3q4r8b5Qj8n5N6m+3q4r8b5Q
j8n5N6m+3q4r8b5Qj8n5N6m+3q4rAgMBAAGjUzBRMB0GA1UdDgQWBBS3q4r8b5Qj
8n5N6m+3q4r8b5Qj8DAfBgNVHSMEGDAWgBS3q4r8b5Qj8n5N6m+3q4r8b5Qj8DAP
BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAA2a97epj/UPG+UI/J
+Tepvt6uK/G+UI/J+Tepvt6uK/G+UI/J+Tepvt6uK/G+UI/J+Tepvt6uK/G+UI/J
+Tepvt6uK/G+UI/J+Tepvt6uK/G+UI/J+Tepvt6uK/G+UI/J+Tepvt6uK/G+UI/J
+Tepvt6uK/G+UI/J+Tepvt6uK/G+UI/J+Tepvt6uK/G+UI/J+Tepvt6uK/G+UI/J
+Tepvt6uK/G+UI/J+Tepvt6uK/G+UI/J+Tepvt6uK/G+UI/J+Tepvt6uK/G+UI/J
-----END CERTIFICATE-----

The base64 der encoded certificate string of <string> inspector is for loading a certificate from binary content - but it requires us to base64-encode the binary to load it from a string

So this challenge has several parts

  • Read the REG_BINARY value from the registry
  • Skip everything before the bytes 0x20 0x00 0x00 0x00 0x01 0x00 0x00 0x00
  • Skip the next 4 bytes after that (the 'length' field)
  • Read the remainder of the REG_BINARY into a list of bytes
  • (I've found so far in practice that the Certificate ends the blob value, nothing comes after it; otherwise we'd have to pay more attention to the 'length' value to avoid reading past the certificate)
  • base64-encode those bytes to create a base64-encoded string
  • Read that base64-encoded string as an x509 certificate
  • Retrieve x509 properties from the certificate

We can start by listing the registry keys and checking that we can read the whole REG_BINARY value from each:

// for each certificate, retrieve the registry key name and the REG_BINARY 'blob' value from beneath it as a string
q: (names of it, (it as string) of value "blob" of it) of keys of key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\ROOT\Certificates" of native registry
A: 0119E81BE9A14CD8E22F40AC118C687ECBA3F4D8, 5c0000000100000004000000001000007c0000000100000020000000fc02a49e2e1e8e488ca2912135572cc2f8e71bb0e2f28596b3722299f5cb9c6219000000<snip>
...

Next, split the blob to retrieve only that portion that comes after the known delimiter

q: (names of it, following text of first "2000000001000000" of (it as string) of value "blob" of it) of keys of key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\ROOT\Certificates" of native registry
A: 0119E81BE9A14CD8E22F40AC118C687ECBA3F4D8, 0706000030820603308203eba0

At the start of the blob excerpt, 07 06 00 00 are the 4-bytes that indicate the length of the certificate, while 30 82 06 03 through the end of the value represent the hexadecimal values of the bytes of the certificate itself.
Aside from finding the delimiter, we need to skip 8 characters (i.e. four bytes) to skip the 'length' value.

q: (names of it, following text of first 8 of following text of first "2000000001000000" of (it as string) of value "blob" of it) of keys of key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\ROOT\Certificates" of native registry
A: 0119E81BE9A14CD8E22F40AC118C687ECBA3F4D8, 30820603308203eba00302010202

Now we have the bytes representing the start of the certificate itself (DER-encoded certificates always start with 0x30)
How do we base64 encode it?

I started off down a path that eventually worked, but was much more complicated, I'll post that version as a reply later in case that's helpful in some case. But after I was done, with some help from @brolly33 he clued me in to 'hexadecimal string'. It does what it says - it creates a string from a hexadecimal values -- which is what's there in the registry. So out next step could be

Q: (names of it, ( (hexadecimal string (it )) of following text of first 8 of following text of first "2000000001000000" of (it as string) of value "blob" of it)) of keys of key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\ROOT\Certificates" of native registry
A: 0119E81BE9A14CD8E22F40AC118C687ECBA3F4D8, (((( 0‚%06%030‚%03ë %03%02%01%02%02%10/ÖzC"“2EéS4>âtf0%0d%06%09*†H†÷%0d%01%01%0b%05%000...

The main difference here, is that the "string" that is shown here, is built by applying the earlier hexadecimal values as bytes. Unprintable characters are represented here as percent-encoded values. Let's compare byte per byte:

string value hexadecimal string value
30 "0" (0x30 = decimal 48, ASCII character 48="0" = "%30")
82 "," (0x82 = decimal 130 = "," = "%82"
06 "%06" (0x06 = decimal 6, an unprintable character

I need to keep this 'hexadecimal string' handy, it saved me a lot of effort on manually percent-encoding the characters on my original path

In any case, now we have a string that is a sequence of both printable ASCII and non-printable characters. That's something that we can base64-encode:

q: (names of it, (base64 encode (it)) of ( (hexadecimal string (it )) of following text of first 8 of following text of first "2000000001000000" of (it as string) of value "blob" of it)) of keys of key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\ROOT\Certificates" of native registry
A: 0119E81BE9A14CD8E22F40AC118C687ECBA3F4D8, MIIGAzCCA+ugAwIBAgIQL9Z6QyKTMpBF6VM0PuJ0ZjANBgkqhk

Here I'm getting hopeful, because I've done enough to recognize that "MII" as something that often appears at the start of a base64-encoded certificate.

Now we can load that base64-encoded string as a certificate using the base64 der encoded certificate string inspector. I like to check existences first, so I know we were actually able to load the certificate type:

q: (names of it, exists ( base64 der encoded certificate string of it) of (base64 encode (it)) of ( (hexadecimal string (it )) of following text of first 8 of following text of first "2000000001000000" of (it as string) of value "blob" of it)) of keys of key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\ROOT\Certificates" of native registry
A: 0119E81BE9A14CD8E22F40AC118C687ECBA3F4D8, True
A: 0563B8630D62D75ABBC8AB1E4BDFB5A899B24D43, True
A: 06F1AA330B927B753A40E68CDF22E34BCBEF3352, True

Final Form

Now that we can enumerate the registry and load the certificates, we can retrieve the 'x509 certificate' properties:

q: (names of it, (subjects of it, invalid afters of it)  of ( base64 der encoded certificate string of it) of (base64 encode (it)) of ( (hexadecimal string (it )) of following text of first 8 of following text of first "2000000001000000" of (it as string) of value "blob" of it)) of keys of key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\ROOT\Certificates" of native registry
A: 0119E81BE9A14CD8E22F40AC118C687ECBA3F4D8, ( ( C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Time Stamp Root Certificate Authority 2014 ), ( Sat, 22 Oct 2039 17:15:19 -0500 ) )
A: 0563B8630D62D75ABBC8AB1E4BDFB5A899B24D43, ( ( C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Assured ID Root CA ), ( Sun, 09 Nov 2031 19:00:00 -0500 ) )
A: 06F1AA330B927B753A40E68CDF22E34BCBEF3352, ( ( C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft ECC Product Root Certificate Authority 2018 ), ( Fri, 27 Feb 2043 15:50:46 -0500 ) )

I hope this was helpful, please do let me know!

tags: certificate, pem, base64, hexadecimal string, REG_BINARY, BLOB

6 Likes

Follow-up - the more complicated path

Before @brolly33 pointed out the 'hexadecimal string' inspector, I was on a path of

  • retrieve the REG_BINARY
  • convert it to a string
  • Find the delimiter and skip to 8 characters beyond it (the algorithm diverges after this)
  • -- Split the string into a series of 2-character substrings by looping through 'positions', so now I have strings "30", "82", and "06" to start.
  • -- For each 2-character pair insert a percent character before it, with "%25" so now the strings look like "%2530", "%2582", and "%2506"
  • -- percent-decode each string, so now they are "%30", "%82", and "%06" - the way hexadecimal integer values are represented in strings. Some of these are printable characters, some are not
  • -- Concatenate the strings together, so now it's "%30%82%06".
  • With the percent-encoded string now we can base64 encode and then load the certificate as before.
q: (subject of it, invalid after of it, issuer of it) of ( base64 der encoded certificate string of it) of (base64 encode (it)) of concatenation of (percent decode("%25" & concatenation of (firsts 2 of following texts of it))) of positions (integers in (0, length of it, 2)) of following text of first 8 of following text of first "2000000001000000" of (it as string) of value "blob" of key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\ROOT\Certificates\3B1EFD3A66EA28B16697394703A72CA340A05BD5" of native registry
A: ( C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Root Certificate Authority 2010 ), ( Sat, 23 Jun 2035 17:04:01 -0500 ), ( C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Root Certificate Authority 2010 )

I really liked percent decode ("%25" & it) great technique that I did not see coming.

Follow-up: Improvement from @jgstew

James Stewart has, as usual, suggested a cleaner ordering for the query. The logic remains the same, but his version is quite a bit more clear so I'll post it here without further discussion:

subjects of base64 der encoded certificates string of (base64 encodes it) of (hexadecimal strings it) of following texts of firsts 8 of following texts of firsts "2000000001000000" of (it as string) of values "blob" of keys of keys "Certificates" of keys of keys "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates" of native registry
4 Likes