Back in 2022, Adam Chester released a C# Proof of Concept (PoC) to decrypt credentials vaulted by Microsoft’s System Center Configuration Manager (SCCM), such as Client Push Accounts, NAA’s, etc., when run on an SCCM Site Server. This original PoC can be found here, and the decryption process was further expanded upon in a companion tweet thread here. While this was the first time I had heard about credential decryption in SCCM, as Adam noted in his tweet thread, he had essentially recreated the functionality of the already existing misc:sccm module of Mimikatz, originally added to the tool in 2021. Adam’s code for credential decryption was subsequently incorporated into other toolsets (e.g., I shamelessly ripped it off and tossed it into SQLRecon).
This worked out quite well for us on our adversary simulation engagements for the most part, and we were able to pull credentials from the SCCM database and decrypt them on the site server as expected. However, periodically, we would run into issues where, for whatever reason, decryption of credentials failed completely. It quickly became apparent that, for some reason, encrypted credentials in these environments were formatted using a completely different structure than the typical blob that could be processed, resulting in existing options (that I found) for credential decryption failing.
I wasn’t able to find a great answer online to address this discrepancy, but not being able to recover passwords from SCCM in some environments ended up being a back-burner problem for our team as typically if you’re at a point where you have admin privileges on an SCCM Site Server, you can escalate through one of several other additional paths (e.g., script creation + execution on a client, injecting into a privileged session on the host, etc.). While not immediately impactful, it was always super annoying for me when I found an encrypted domain admin password that was *right* there, but I couldn’t resolve it to plaintext, and I was never happy that I couldn’t explain the formatting discrepancy.
This stuck around as one of those projects to get to “eventually” until an explanation of the root cause of this discrepancy led to me digging into SCCM encryption internals and figuring out both exactly why and what was happening with encryption in SCCM.
SCCM encrypts credentials that have been added to the platform to perform various functions (e.g., network access accounts [NAAs], client push installation accounts, etc.) and stores them within the SC_UserAccount table of the SCCM site database. In a typical single-site server configuration, these encrypted credentials take the form of RSA/AES CryptoAPI key blobs with the following structure:
Essentially, the RSA-encrypted AES key stored within the blob is first decrypted using the site server’s key pair in the Microsoft Systems Management Server key container and then is subsequently used to decrypt the password itself that is stored within the blob. This decryption process can be seen in several places within the SCCM source code, but for now, we can take a look at the source code of the Microsoft.ConfigurationManager.CommonBase.EncryptionUtilities.Decrypt method within the SCCM Configuration Manager GUI, as identified by Adam in his tweet thread here:
As a practical example of this encrypted blob format, let’s look at an encrypted password from a single-site SCCM deployment:
The above blob gives a key length of 268 (010C == 268) and a decrypted ciphertext length of 7 (nice password you got there). The 0x0C, 0x01 header byte pattern in this blob was assumed to be consistent, as it describes the key length in use. However, in some environments, accounts stored within the SC_UserAccount table of the SCCM site database were structured differently and appeared to be encrypted using a different mechanism, as can be seen below:
Trying to decrypt this other format of encrypted credential using tooling built to decrypt the standard RSA/AES blobs would immediately fail, as reading the first four bytes (meant to give you key length) would return a key length of zero. In addition to these differences in the header, the ciphertext appeared to be only about 1/3 of the size of the single-site blob structure. When I first ran into this issue on an engagement several years ago, I first guessed it may have been due to a legacy version of SCCM that performed credential encryption differently, but I wasn’t able to nail anything down definitively. The problem continued to pop up periodically, but due to alternative escalation paths wasn’t anything that became a true blocker for us on our engagements.
Things remained in this state until a response from Garrett Foster (@unsigned_sh0rt) to a question regarding this cryptographic issue on the Bloodhound Gang Slack a few months ago pointed to this different encryption structure being used by SCCM in environments configured for site server high-availability (with an active-passive site server configuration). At the time, I was somewhat aware of this configuration and had noticed it in place in some of our client environments, but had not made the connection between that and the above-noted encryption discrepancies. This eventually led to me standing up an active-passive site SCCM config in my lab, an adventure I could write another blog about (it was super lame), and digging into how encryption was working, with a goal of figuring out how to perform decryption on this alternate storage structure.
In an SCCM deployment, the site server is the central point of authority for management of the various functions the service provides. There can only be one site server per site, but this presents a problem because if this server ever goes down, admin functions and policy assignments will be broken until a new site server is spun up or the old one is otherwise restored. Client-side functionality isn’t impacted, and existing deployments can still be pushed, etc. (further info on impacts can be found here). This isn’t great, but it also isn’t something immediately likely to cause a business interruption, hence why we don’t see this deployment configuration in every environment we test. As a way around this single point of failure, Microsoft added the ability to create a passive site server, which will monitor for connectivity issues with the active server and automatically promote itself after 30 minutes of not being able to contact the active site server. Further information on active-passive configs in SCCM can be found here.
The SCCM deployment in my lab was in a typical single-site server configuration, meaning that the blobs I observed in my SCCM site database used the typical RSA/AES blob beginning with 0x0C, 0x01. After going through the “thoroughly enjoyable” process of deploying a passive site server in SCCM, I once again checked the SC_UserAccount table in my SCCM site database and noted that the format of the credentials had been updated to the new unknown blob structure.
This was exactly what I was hoping for and meant that I could begin reverse engineering what was going on under the hood.
I didn’t have much of a game plan on where to start, beyond a thought that somewhere there must be some sort of conditional logic regarding encryption – “if default do this, if active-passive do something else”, etc. Due to this, I once again consulted Adam’s original work from 2022 and saw that he found credential decryption logic in the Microsoft Configuration Manager GUI app that is used to perform management operations within SCCM. Luckily, this is a .NET assembly, making analysis of what is going on under the hood quite straightforward. By decompiling the binary (with tools like DNSpy), it is possible to reconstruct source code that is quite close to the original implementation. After building a quick reflective loader to dump all properties and methods in every managed library used by the GUI, I had a pretty decent list of candidates to look through to try and figure out the decryption flow. Several different decryption mechanisms were present in the libraries I was parsing, but nothing seemed like it was both parsing the type of data I wanted to decrypt and working with the format of the active-passive blob identified in the site database. For example, Microsoft.ConfigurationManager.CommonBase.EncryptionUtilities contained a variety of decryption-related methods:
But they either appeared to deal with incompatible data types, or at some point in their decryption chain called back into RSA/AES decryption, which we know would fail due to the structure of the updated encrypted blob. I figured I could decompile every library to find what called in to the various decryption routines and try to keep working backwards, but overall didn’t feel like I had been able to make much progress in answering my questions regarding encryption.
Not having much luck with things, I came at the problem from a different angle, as I knew that it was definitely possible to submit a new credential through the GUI. If that execution chain could be followed through the code to where encryption was performed, I was fairly confident that I could figure out the root of what was going on with the alternate blob structure.
When adding a new account within the Microsoft Configuration Manager GUI (e.g., when adding a new client push installation account), a form backed by the Microsoft.ConfigurationManagement.AdminConsole.Common.UserAccount class is displayed. The code below shows the initialization of this form and the buttons / text boxes created.
The controls created in the above code correspond to what appears in the SCCM console’s Add New Account dialog window:
Taking a look at this class, we don’t immediately see a click handler for the OK button, but looking at the base SMSDialogBase class that this class inherits from shows that when the form is closed, the ValidateDialog method is called if the form is exited with an OK status (OK button was hit).
Reviewing the implemented ValidateDialog method in the UserAccount class shows that the password is already encrypted by the time it’s used in this method:
Looking at the initialization of the form once again shows us that the Password and Confirm Password text boxes are of type SmsEncryptedPasswordTextBox.
We can then open up the source for this Microsoft.ConfigurationManagement.AdminConsole.Common. SmsEncryptedPasswordTextBox class and see that the getter for the EncryptedPassword property is what is performing the encryption of the credential, through a call to the EncryptionUtilities.Instance.Encrypt method in Microsoft.ConfigurationManager.CommonBase:
This Encrypt method is called using RSA + AES and performs the expected steps of:
After that, we can see the encrypted blob assembled in the expected single-site format containing:
In short, this got us right back to where we started, using the traditional RSA/AES encryption mechanism as outlined in Adam’s post from 2022.
Critically, there is no conditional branching in this logic that says, “if active-passive, do this completely other set of encryption steps”, meaning that, as far as I could tell, the only option available was the “classic” RSA/AES encryption.
Continuing to not have any idea of what was happening, I figured I would continue tracing the execution chain through the ValidateDialog method in the UserAccount class. Looking at the code in this method, the EncryptedPassword property returned by the encryption process is immediately written into the Reserved2 property of accountResultObject (which implements the IResultObject interface) and saved to memory, meaning that, as far as I understood, this Reserved2 property contained the RSA/AES blob, not the mystery new format. This seemed to be the end of the road for the Configuration Manager GUI code before something within the SCCM server picked it up and handled the actual database commit.
Doing a bit more digging showed that these objects were accessible via a local WMI query on the site server by querying the SMS_SCI_Reserved class. Querying the Reserved2 property here, though, showed that somehow the encrypted password blob had changed format from RSA/AES into the new, unknown blob format at some point.
For fun, I decided to try and add a new user to SCCM while monitoring the database with SQL Server Profiler to get an idea of what was doing the write and what was actually being written to the SQL database. To demo this, let’s add a new ContrivedDemoUser user to SCCM as a push installation account.
Imagine my surprise when I queried the SCCM site database immediately after and noticed the encrypted password for the account was stored in the original RSA/AES blob format (starting with 0x0C, 0x01).
Then imagine my even greater surprise when about one minute later, the format of the blob changed to the new mystery structure, like magic.
Looking at our trace in SQL Server Profiler, we can see that this re-write is done by the SMS_FAILOVER_MANAGER, which, a Google search shows, is a component of the SCCM server process.
This same Google search also shows that this component writes to FailOverMgr.log. Taking a look at this log on the SCCM site server shows what is actually happening, finally giving us a useful piece of information in figuring out what is going on with encryption.
Cool, so this SMS_FAILOVER_MANAGER component of the SCCM server process is reencrypting the credentials, after which they flip from the old RSA/AES to a new mystery format. Smsexec.exe (the SCCM server process) is luckily also a .NET process, but a reflective enumeration of methods and properties in loaded managed libraries didn’t yield anything super promising. However, smsexec.exe did have the obviously named failovermgr.dll library loaded.
This seemed to be the most promising lead yet, but unfortunately for me and my weak RE skills, this was a native library, meaning my easy streak of looking at direct source code was at an end.
I still didn’t have much of a game plan going forward, but figured it was time to fire up the ol’ IDA and see what could be figured out. As I had some strings from logs generated by (what I assumed was) this library, I figured that would be a decent enough place to start. After finding a single usage of the string identified in the log file within the library, I landed in a function that finally started to provide some clarity on what was going on.
This code showed three primary things of interest:
Digging further into CServerAccount::Decrypt (in basesvr.dll) showed that this serves as a front-door decryption function that can handle a variety of different decryption types, which are determined by the format of the encrypted blob as indicated by the return from the CServerAccount::GetEncryptedDataFormat function call.
This GetEncryptedDataFormat function call returns a value based on the second four bytes of the encrypted blob passed in (bytes 4-7), assuming the first four bytes of the encrypted blob equal 0 (otherwise, the GetEncryptedDataFormat function will always return 0).
Applying this to both the original blob format and our new mystery format gives the following:
Taking the return value of 0 for the classic-style blob causes most of the processing logic to be skipped:
Looking at the function that is called for decrypting the classic-style blob shows us the familiar RSA / AES decryption routine calling the Microsoft System Management Server container.
This was exactly what I was hoping for, as it explained how SCCM was doing the first step in the decrypt + re-encrypt process in the database.
Looking at the Decrypt function code with the returned value of 1 from the new mystery blob structure shows that it is calling the CServerAccount::DecryptCng function for decryption, which also aligns with the re-encryption code in failovermgr.dll that calls the CServerAcccount::EncryptCng function.
This DecryptCng function does a few things:
The biggest hurdle I could see here was figuring out what the master key was and where it was stored; everything else was mainly just a matter of correctly parsing the function calls and reproducing the native code. Unfortunately, the GetEncryptedMasterKey function didn’t have much in it that was too helpful – it showed that it would cache the key in memory for an hour, after which it would refresh the key from the SCCM site database. This function also clarified that the key was called the Site Master Key.
Pushing further into the CSiteSettings:GetEncryptedMasterKeyForServer function was similarly unhelpful; all that this method contained was a call to an MSWNET endpoint to retrieve the encrypted Site Master Key.
Reading up on MSWNET was a bit of a black hole of crash logs and dump files without much in the way of real information, but from what I could put together between searches and ChatGPT was that MSWNET is a protocol scheme used internally by SCCM’s Network Abstraction Layer (NAL), meaning it’s not something you can directly communicate with from outside of SCCM. I could be totally wrong on this, though, and it may be an interesting area for further research.
The message log in GetEncryptedMasterKey that stated the encrypted master key had been refreshed from the database was really all I had to go off of at this point. Knowing that this was something pertaining to site keys, I searched all columns in tables in views in the SCCM database for obviously named things like SiteMasterKey or similar. Not having any luck with that, I manually went through all site-related tables and views within the database, finally finding something interesting in the vSMS_SC_SiteControlXML view.
The SiteControl value was a rather large XML document containing a variety of properties that appeared to be related to SCCM cryptography; it contained objects such as keys, hashes, encrypted certificates, etc. Most importantly for this use case, the Site Master Key was included near the bottom of this document. This appeared to be an encrypted value that was prefixed with the now familiar 0x0C, 0x01 header denoting an RSA/AES blob.
Interestingly, each site server has its own Site Master Key, all of which are contained in this same XML document. Upon further research, I found that this XML document was the SCCM Site Control File (further information on this can be found here), a virtual file compiled using data pulled from a variety of other views/tables within the database. After chasing down the site master key info of interest, I found that it originates from the SC_SysResUse_Property table in the database.
The final step was now figuring out how the DecryptCNG method parsed the new password blob structure. Reviewing the code for the actual BCrypt decryption function called by DecryptCNG, we can see two main steps:
Overall, the new BCrypt/AES blob has a structure of:
Look pretty similar to something else? Thinking, “why did they do all of this, only to have what is essentially the same blob format with a slight variation on encryption”? Realistically, I’m assuming that this was done for key hygiene purposes (aka not needing to share the RSA private key of the site server to decrypt data).
Anyhow, with this final hurdle out of the way and the structure of the new BCrypt/AES blob format fully understood, it was pretty straightforward to merge together the classic SCCM RSA/AES decryption code (for decrypting the Site Master Key) with some new code to handle the BCrypt decryption, build out a POC for active-passive credential decryption. To demonstrate this in action, let’s grab the encrypted site master key out of the vSMS_SC_SiteControlXML view and the encrypted password for the ContrivedDemoUser account we set up before out of the SC_UserAccount table. We’ll then pass these in as args to a new decryption POC (running as a high-integrity user or NT_AUTHORITY\SYSTEM on the site server) that applies everything learned above and see that we can successfully retrieve the plaintext password for the account:
This same POC can also be run on the passive site server (with the correct Site Master Key for the server) to recover the credentials as well.
Active-passive configurations in SCCM modify the encryption of credential blobs slightly, but don’t introduce much in the way of new hurdles to successfully decrypt them once you have access to a site server. A more engagement-ready version of the above decryption POC has been added to SQLRecon, which will automatically detect the appropriate decryption pattern to use given the first four bytes of the encrypted blob and pull the appropriate Site Master Key from the database if needed. Additionally, I’ve tossed the original POC for active-passive decryption into a gist here. These should hopefully allow for the recovery of credentials in the scope of assessments, regardless of the site configuration, and alleviate a slight inconvenience that has plagued me for several years.