In this mini-series, Part 1 discussed how we could discover RSA certificates in arbitrary processes using volatility. Additionally, the `dumpcerts`

plugin was also able to find certificates in the PKCS #8 format:

```
PrivateKeyInfo ::= SEQUENCE {
version Version,
privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
privateKey PrivateKey,
attributes [0] IMPLICIT Attributes OPTIONAL }
Version ::= INTEGER
PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
PrivateKey ::= OCTET STRING
Attributes ::= SET OF Attribute
```

But what about private keys that may not be in this format, particularly in Windows environments? After all, private keys at their most basic form are not much more than a combination of numbers in some structure that can be used for decryption. This entry in the series will discuss extracting private keys in arbitrary processes.

## Private keys

Before going further, we need to understand what it is we’re looking for in an RSA private key. The RSA private and public key relationship is effectively this:

Private Key | Public Key | ||
---|---|---|---|

p |
✅ | ⛔ | prime number |

q |
✅ | ⛔ | prime number |

n |
✅ | ✅ | modulus (p * q) |

e |
✅ | ✅ | public exponent (usually 3 or 65537) |

d |
✅ | ⛔ | private exponent |

When we have a public key, the modulus and public exponent are available. However, the private exponent and the prime numbers are considered to be secret, and should not be exposed publicly. At a minimum for a private key, we need `n`

and `d`

, since that will allow `p`

and `q`

to be derived as well as any additional values required by the Chinese Remainder Theorem (CRT) for some private key operations.

## RSA on Windows

While Linux implementations typically use OpenSSL, Windows has several of its own cryptographic service providers. The cross-platform compatability of .NET and the providers available are well-documented in the Microsoft Docs. Most notably, we care about the RSA on Windows section, which talks about the behavior of the library depending on how the code is written.

- Windows CryptoAPI (CAPI) is used whenever new RSACryptoServiceProvider() is used.
- Windows Cryptography API Next Generation (CNG) is used whenever new RSACng() is used.
- The object returned by RSA.Create is internally powered by Windows CNG. This use of Windows CNG is an implementation detail and is subject to change.
- The GetRSAPublicKey extension method for X509Certificate2 returns an RSACng instance. This use of RSACng is an implementation detail and is subject to change.
- The GetRSAPrivateKey extension method for X509Certificate2 currently prefers an RSACng instance, but if RSACng can’t open the key, RSACryptoServiceProvider will be attempted. The preferred provider is an implementation detail and is subject to change.

The highlight of this section pulled from Microsoft Docs is that RSA objects are powered by the internal Windows CNG provider. So how can we determine what the structure of these objects look like? Many years ago, this might have required a lot of debugging, note-taking, and hours of effort to figure out, since the cryptographic libraries of Windows were closed-source. However, in 2019, Microsoft open-sourced SymCrypt, the core cryptographic function library used by Windows. Therefore by analyzing the open source code, we can determine what the structures look like.

## SymCrypt

The SymCrypt library is well-documented and we can quickly locate the code and structures we’re interested in. In particular, `symcrypt_internal.h`

holds structures for RSA Key structures (as well as many other types of encryption).

The four structures of interest here are `_SYMCRYPT_RSAKEY`

, `_SYMCRYPT_MODULUS`

, `_SYMCRYPT_DIVISOR`

, and `_SYMCRYPT_INT`

. Within the `_SYMCRYPT_RSAKEY`

object, there are pointers which eventually lead to the other three structures, therefore we have to keep in mind the fields available in each one.

To keep this review short, I’ll only cover the main _SYMCRYPT_RSAKEY and _SYMCRYPT_INT objects, since the others are used to traverse from the RSA key to the values we want.

### _SYMCRYPT_RSAKEY

```
typedef SYMCRYPT_ASYM_ALIGN_STRUCT _SYMCRYPT_RSAKEY {
UINT32 cbTotalSize; // Total size of the rsa key
BOOLEAN hasPrivateKey; // Set to true if there is private key information set
UINT32 nSetBitsOfModulus; // Bits of modulus specified during creation
UINT32 nBitsOfModulus; // Number of bits of the value of the modulus (not the object's size)
UINT32 nDigitsOfModulus; // Number of digits of the modulus object (always equal to SymCryptDigitsFromBits(nSetBitsOfModulus))
UINT32 nPubExp; // Number of public exponents
UINT32 nPrimes; // Number of primes, can be 0 if the object only supports public keys
UINT32 nBitsOfPrimes[SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES];
// Number of bits of the value of each prime (not the object's size)
UINT32 nDigitsOfPrimes[SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES];
// Number of digits of each prime object
UINT32 nMaxDigitsOfPrimes; // Maximum number of digits in nDigitsOfPrimes
UINT64 au64PubExp[SYMCRYPT_RSAKEY_MAX_NUMOF_PUBEXPS];
// SYMCRYPT_ASYM_ALIGN'ed buffers that point to memory allocated for each object
PBYTE pbPrimes[SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES];
PBYTE pbCrtInverses[SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES];
PBYTE pbPrivExps[SYMCRYPT_RSAKEY_MAX_NUMOF_PUBEXPS];
PBYTE pbCrtPrivExps[SYMCRYPT_RSAKEY_MAX_NUMOF_PUBEXPS * SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES];
// SymCryptObjects
PSYMCRYPT_MODULUS pmModulus; // The modulus N=p*q
PSYMCRYPT_MODULUS pmPrimes[SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES];
// Pointers to the secret primes
PSYMCRYPT_MODELEMENT peCrtInverses[SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES];
// Pointers to the CRT inverses of the primes
PSYMCRYPT_INT piPrivExps[SYMCRYPT_RSAKEY_MAX_NUMOF_PUBEXPS];
// Pointers to the corresponding private exponents
PSYMCRYPT_INT piCrtPrivExps[SYMCRYPT_RSAKEY_MAX_NUMOF_PUBEXPS * SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES];
// Pointers to the private exponents modulo each prime minus 1 (for CRT)
SYMCRYPT_MAGIC_FIELD
} SYMCRYPT_RSAKEY;
```

This structure containers the entirety of the RSA structure. While all the fields have some value, the following fields are the most interesting:

`hasPrivateKey`

: Determines if the object contains private key values. If not, it is a public key. This value is either`0`

or`1`

.`nPrimes`

: We generally expect this to be`0`

or`2`

depending on the value of`hasPrivateKey`

.`au64PubExp`

: Public exponent`e`

. Defaults to 65537 but may also be 3 in some cases.`pmModulus`

: Pointer to`_SYMCRYPT_MODULUS`

`n`

. This value can be used to match public and private keys along with`au64PubExp`

`pmPrimes`

: Pointers to`_SYMCRYPT_MODULUS`

`p`

and`q`

, the prime numbers when a private key is available.`piPrivExps`

: Pointer to`_SYMCRYPT_INT`

private exponent`d`

. With`n`

and`d`

, we can derive the values found in`pmPrimes`

.

### _SYMCRYPT_INT

```
SYMCRYPT_ASYM_ALIGN_STRUCT _SYMCRYPT_INT {
UINT32 type;
_Field_range_( 1, SYMCRYPT_FDEF_UPB_DIGITS ) UINT32 nDigits; // digit size depends on run-time decisions...
UINT32 cbSize;
SYMCRYPT_MAGIC_FIELD
SYMCRYPT_ASYM_ALIGN union {
struct {
UINT32 uint32[SYMCRYPT_ANYSIZE]; // FDEF: array UINT32[nDigits * # uint32 per digit]
} fdef;
} ti; // we must have a name here. 'ti' stands for 'Type-Int', it helps catch type errors when type-casting macros are used.
};
```

The primary interest here is `fdef`

, which contains the array of 32-bit integers that represent whatever value the structure is meant to represent. As shown in `_SYMCRYPT_RSAKEY`

, the private exponent is represented by this structure. However, this structure is also used by `_SYMCRYPT_MODULUS`

, therefore the prime numbers and the modulus are ultimately represented by this structure.

## Discovering _SYMCRYPT_RSAKEY

Discovering the location of these structures turned out to be fairly simple. The driver responsible for Windows cryptographic operations is at `C:\Windows\system32\drivers.cng.sys`

. By loading this driver into Binary Ninja, we can grab the PDB from the Microsoft Symbol server and locate interesting functions.

As expected, we can find the `MSCryptGenerateKeyPair`

function which calls ‘CreateAndInitializeNewKey`. Shoutout to properly named functions.

Inside this function, we can find the struct that is created that represents the key. We **could** trace back the arguments to the calling functions and function tables to verify this, but that falls outside the scope of this blog entry. However, what we can see is something that appears to be a magic header `KRSM`

, possibly standing for Microsoft/MSCrypt RSA Key. Coincidentally, the `validateMSCryptRsaAlgorithm`

function shown in the previous screenshot checks a different struct for the string `ARSM`

, and we can assume that means Microsoft/MSCrypt RSA Algorithm.

Additionally, there is a hardcoded value of `0x28`

right before the `KRSM`

header. This appears to be the length of the entire structure we’re looking at, which is supported by the initialization of `rax`

members right before the assignment. Therefore, we have enough to determine that the beginning of an MSCrypt RSA Key in hex form would look like `28 00 00 00 4b 52 53 4d`

, and this is a significant marker for the object in memory!

### Note on Mimikatz

I learned later that this is exactly how Mimikatz looks for BCrypt private keys, so this isn’t exactly a novel technique. However, by coming across this realization, it opens opportunities to discover additional credential types for parsing, such as symmetric keys. 😃

## Pivoting with Yara

By using the `construct`

module, we can recreate the SymCrypt structures of interest, as well as the MSCrypt RSA Key structure. Some of the values for the `BCRYPT_RSAKEY`

object were discovered by looking a little further at the structure in Binary Ninja, but not all values were found.

```
BCRYPT_RSAKEY = Struct(
"Length" / Int32ul,
"Magic" / Const(b"KRSM"),
"Algid" / Hex(Int32ul),
"ModBitLen" / Int32ul,
"Unknown1" / Int32sl,
"Unknown2" / Int32sl,
"pAlg" / Hex(Int64ul),
"pKey" / Hex(Int64ul),
)
SYMCRYPT_RSAKEY = Struct(
"cbTotalSize" / Int32ul,
"hasPrivateKey" / Int32ul,
"nSetBitsOfModulus" / Int32ul,
"nBitsOfModulus" / Int32ul,
"nDigitsOfModulus" / Int32ul,
"nPubExp" / Int32ul,
"nPrimes" / Int32ul,
"nBitsOfPrimes" / Array(2, Int32ul),
"nDigitsOfPrimes" / Array(2, Int32ul),
"nMaxDigitsOfPrimes" / Array(1, Int32ul),
"au64PubExp" / Hex(Int64ul),
"pbPrimes" / Array(2, Hex(Int64ul)),
"pbCrtInverses" / Array(2, Hex(Int64ul)),
"pbPrivExps" / Array(1, Hex(Int64ul)),
"pbCrtPrivExps" / Array(2, Hex(Int64ul)),
"pmModulus" / Hex(Int64ul),
"pmPrimes" / Array(2, Hex(Int64ul)),
"peCrtInverses" / Array(2, Hex(Int64ul)),
"piPrivExps" / Array(1, Hex(Int64ul)),
"piCrtPrivExps" / Array(2, Hex(Int64ul)),
"magic" / Hex(Int64ul),
)
SYMCRYPT_MODULUS_MONTGOMERY = Struct("inv64" / Hex(Int64ul), "rsqr" / Hex(Int32ul))
SYMCRYPT_MODULUS_PSUEDOMERSENNE = Struct("k" / Int32ul)
SYMCRYPT_INT = Struct(
"type" / Int32ul,
"nDigits" / Int32ul,
"cbSize" / Int32ul,
"magic" / Int64ul,
"unknown1" / Int64ul,
"unknown2" / Int32ul,
"fdef" / Array((this.cbSize - 0x20) // 4, Int32ul),
)
SYMCRYPT_DIVISOR = Struct(
"type" / Int32ul,
"nDigits" / Int32ul,
"cbSize" / Int32ul,
"nBits" / Int32ul,
"magic" / Int64ul,
"td" / Int64ul,
"int" / SYMCRYPT_INT,
)
SYMCRYPT_MODULUS = Struct(
"type" / Int32ul,
"nDigits" / Int32ul,
"cbSize" / Int32ul,
"flags" / Int32ul,
"cbModElement" / Int32ul,
"magic" / Int64ul,
"tm"
/ Union(
0,
"montgomery" / SYMCRYPT_MODULUS_MONTGOMERY,
"pseudoMersenne" / SYMCRYPT_MODULUS_PSUEDOMERSENNE,
),
"pUnknown" / Hex(Int64ul),
"pUnknown2" / Hex(Int64ul),
"pUnknown3" / Hex(Int64ul),
"divisor" / SYMCRYPT_DIVISOR,
)
```

Since Volatility allows us to pivot to other sections of memory, combining these `Struct`

classes with Volatility 3’s `YaraScanner`

makes it easy to parse the entire RSA key out of memory. For example, the `SYMCRYPT_RSAKEY`

output would look something like this:

```
Container:
cbTotalSize = 3488
hasPrivateKey = 1
nSetBitsOfModulus = 2048
nBitsOfModulus = 2048
nDigitsOfModulus = 4
nPubExp = 1
nPrimes = 2
nBitsOfPrimes = ListContainer:
1024
1024
nDigitsOfPrimes = ListContainer:
2
2
nMaxDigitsOfPrimes = ListContainer:
2
au64PubExp = 0x0000000000010001
pbPrimes = ListContainer:
0x0000015FF008CBA0
0x0000015FF008CE20
pbCrtInverses = ListContainer:
0x0000015FF008D0A0
0x0000015FF008D1A0
pbPrivExps = ListContainer:
0x0000015FF008D2A0
pbCrtPrivExps = ListContainer:
0x0000015FF008D3C0
0x0000015FF008D4E0
pmModulus = 0x0000015FF008C920
pmPrimes = ListContainer:
0x0000015FF008CBA0
0x0000015FF008CE20
peCrtInverses = ListContainer:
0x0000015FF008D0A0
0x0000015FF008D1A0
piPrivExps = ListContainer:
0x0000015FF008D2A0
piCrtPrivExps = ListContainer:
0x0000015FF008D3C0
0x0000015FF008D4E0
magic = 0x0000000000000000
```

What we’re interested in here is if the structure has a `1`

for `hasPrivateKey`

, which means we can extract the public and private key material. However, we have no way of knowing what certificate it represents. At least, not at first. Other values of interest here include the prime numbers (obviously) as well as the private exponent.

## Matching keys and certs

In Part 1, we were able to extract the public certificates found in a process. Therefore, we can check to see if the process we’re scanning for SymCrypt RSA keys also has the public certificate associated with it. As mentioned earlier, we can determine a key pair by comparing the value of modulus of the private key (`p`

* `q`

) to the modulus `n`

of the public certs we can extract. With a little Python magic, we can determine when there’s a match:

```
Offset PID Process Rule HasPrivateKey Modulus (First 20 Bytes) Matching
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
0x15fef506130 872 lsass.exe symcrypt_rsa_key 0 CCD95B6993F8A5C47177FBD5E1CA79E43672C6EC
0x15fef50c650 872 lsass.exe symcrypt_rsa_key 0 E44E27497E1BDF3A768BB35141057311CA367875 F42F47F1987857A58664B8830F47803669400477 -> CN=b831458c-0bd8-4f58-8c61-64838f56ba31
0x15fef7518d0 872 lsass.exe symcrypt_rsa_key 0 B2EDBB129B966FBFCA24893DB4D47C6D8B2643F1
0x15fef7557a0 872 lsass.exe symcrypt_rsa_key 0 E44E27497E1BDF3A768BB35141057311CA367875 F42F47F1987857A58664B8830F47803669400477 -> CN=b831458c-0bd8-4f58-8c61-64838f56ba31
0x15fef755c80 872 lsass.exe symcrypt_rsa_key 1 E44E27497E1BDF3A768BB35141057311CA367875 F42F47F1987857A58664B8830F47803669400477 -> CN=b831458c-0bd8-4f58-8c61-64838f56ba31
0x15fefea0150 872 lsass.exe symcrypt_rsa_key 1 E44E27497E1BDF3A768BB35141057311CA367875 F42F47F1987857A58664B8830F47803669400477 -> CN=b831458c-0bd8-4f58-8c61-64838f56ba31
0x15feff44750 872 lsass.exe symcrypt_rsa_key 1 B2EDBB129B966FBFCA24893DB4D47C6D8B2643F1
```

In this instance, we discovered four different instances of the same structure, and while all of them correspond to a certificate with the subject name `CN=b831458c-0bd8-4f58-8c61-64838f56ba31`

, only two of them have the private key material. Since we have the public certificate and the private key material, we can combine them to dump a working PFX that includes the private key. And if there is no match but we still have private key material, as shown in the hit at `0x15feff44750`

, we can still dump the key material to disk, as another process might actually be handling the public certificate. What a win!

## Conclusion

There’s still more work to do. By analyzing cng.sys, we should be able to find more structures that represent secrets and certificates that can be extracted from memory. And although LSASS was focused here, this technique works on any kernel or process dump. There is value in finding these certificates, particularly in cloud environments where certificate-based authentication is extremely common between identities and services. Parsing memory dumps with techniques other than Mimikatz is a skillset that can open new and unexpected paths for attackers and is an area of research that needs further exploration.

The volatility 3 plugin for this technique can be found on my Github.