Decrypting credentials from SCCM site servers configured for high availability

Two young programmers working at separate desks on their respective computers

Author

Dave Cossa

Senior Red Team Operator

X-Force Adversary Services

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.

Background on SCCM credential storage

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:

RSA/AES encrypted password blob structure
RSA/AES encrypted password blob 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:

Decryption code within the SCCM Configuration Manager app
Decryption code within the SCCM Configuration Manager app

As a practical example of this encrypted blob format, let’s look at an encrypted password from a single-site SCCM deployment:

Typical encrypted password blob structure for RSA/AES encryption
Typical encrypted password blob structure for RSA/AES encryption

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:

New “mystery” blob format
New “mystery” blob format

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.

Active and passive site servers

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.

Contents of SC_UserAccount table in SCCM database after configuring a passive site server
Contents of SC_UserAccount table in SCCM database after configuring a passive site server

This was exactly what I was hoping for and meant that I could begin reverse engineering what was going on under the hood.

SCCM “reverse engineering”

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:

Decryption methods used by Microsoft Configuration Manager
Decryption methods used by Microsoft Configuration Manager

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.

Code backing the form responsible for new account addition in the Configuration Manager GUI
Code backing the form responsible for new account addition in the Configuration Manager GUI

The controls created in the above code correspond to what appears in the SCCM console’s Add New Account dialog window:

Buttons, boxes, and labels in the form within the GUI appear to align with identified code
Buttons, boxes, and labels in the form within the GUI appear to align with identified code

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).

Logic in the base SMSDialogBase class responsible for calling the ValidateDialog method
Logic in the base SMSDialogBase class responsible for calling the ValidateDialog method

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:

ValidateDialog method retrieves the already-encrypted password and stores it in the encryptedPassword string
ValidateDialog method retrieves the already-encrypted password and stores it in the encryptedPassword string

Looking at the initialization of the form once again shows us that the Password and Confirm Password text boxes are of type SmsEncryptedPasswordTextBox.

Instantiation of password textboxes used for adding an account to SCCM
Instantiation of password textboxes used for adding an account to SCCM

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:

Encryption helper method call to encrypt the password
Encryption helper method call to encrypt the password

This Encrypt method is called using RSA + AES and performs the expected steps of:

  1. Acquiring a context for the AES provider
  2. Generating an AES key
  3. Importing the RSA public key
  4. Encrypting the AES key with the RSA public key
Method called to encrypt account passwords utilizes AES + RSA
Method called to encrypt account passwords utilizes AES + RSA

After that, we can see the encrypted blob assembled in the expected single-site format containing:

  1. RSA-encrypted AES key length
  2. Plaintext password length
  3. RSA-encrypted AES key
  4. AES-encrypted password
Building the RSA/AES encrypted password blob
Building the RSA/AES encrypted password blob

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.

EncryptedPassword written to Reserved2 field of object
EncryptedPassword written to Reserved2 field of object

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.

Querying Reserved2 property via WMI shows new active-passive formatted encrypted passwords
Querying Reserved2 property via WMI shows new active-passive formatted encrypted passwords

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.

Creation of a demo user
Creation of a demo user

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).

Screenshot showing the encrypted password for the account was stored in the original RSA/AES blob format
What in tarnation

Then imagine my even greater surprise when about one minute later, the format of the blob changed to the new mystery structure, like magic.

Screenshot showing same encrypted password in blob format changed to the new mystery structure
What in even more tarnation

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.

Password for the demo user being updated in the SC_UserAccount table by SMS_FAILOVER_MANAGER
Password for the demo user being updated in the SC_UserAccount table by SMS_FAILOVER_MANAGER

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.

FailOverMgr.log entries showing the password update observed in the SQL database
FailOverMgr.log entries showing the password update observed in the SQL database

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.

Failovermgr.dll loaded by SCCM server process
Failovermgr.dll loaded by SCCM server process

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.

SCCM reverse engineering (for real this time)

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.

Identifying the string previously observed in FailOverMgr.log
Identifying the string previously observed in FailOverMgr.log

This code showed three primary things of interest:

  1. A call to CServerAccount::Decrypt using the original RSA/AES provider observed when encrypting SCCM passwords the “classic” way.
  2. A call to CServerAccount::EncryptCng, which appeared to be responsible for the new “mystery” blob format.
  3. The “Successfully reencrypted user information” string being logged, which aligns directly with what was observed in the log file above, letting us know we’re looking in the right spot.
Decryption and re-encryption of credentials within failovermgr.dll
Decryption and re-encryption of credentials within failovermgr.dll

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.

Call to CServerAccount::GetEncryptedDataFormat in the CServerAccount::Decrypt code to determine how to process encrypted data
Call to CServerAccount::GetEncryptedDataFormat in the CServerAccount::Decrypt code to determine how to process encrypted data

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).

CServerAccount:: GetEncryptedDataFormat logic, will always return zero if first four bytes are anything other than zero
CServerAccount:: GetEncryptedDataFormat logic, will always return zero if first four bytes are anything other than zero

Applying this to both the original blob format and our new mystery format gives the following:

  • Classic format ([0x0C,0x01, 0x00, 0x00], [0x0B, 0x00, 0x00, 0x00]) == first four bytes != 0, so 0 is returned.
  • New format: ([0x00, 0x00, 0x00,0x00], [0x01, 0x00, 0x00,0x00]) == first four bytes are zero so second four bytes are returned == 1

Taking the return value of 0 for the classic-style blob causes most of the processing logic to be skipped:

Execution flow when decrypting an RSA/AES password blob
Execution flow when decrypting an RSA/AES password blob

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.

RSA/AES decryption logic calling the Microsoft Systems Management Server container
RSA/AES decryption logic calling the Microsoft Systems 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.

New encrypted blob structure results in CServerAcccount::DecryptCng being called
New encrypted blob structure results in CServerAcccount::DecryptCng being called

This DecryptCng function does a few things:

  1. Retrieves an encrypted master key value from somewhere with the CServerAccount::GetEncryptedMasterKey function.
  2. Decrypts the master key using the classic RSA/AES method used by SCCM.
  3. Plugs that decrypted the value in as a key used to do a BCrypt decryption of the blob.
  4. Performs the actual decryption.
code showing CServerAcccount::DecryptCng execution logic
CServerAcccount::DecryptCng execution logic

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.

Master key returned directly if < 3600 seconds elapsed, otherwise returned from the site database
Master key returned directly if < 3600 seconds elapsed, otherwise returned from the site database

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.

code for retrieving the Site Master key via a call to an MSWNET endpoint
Retrieving the Site Master key via a call to an MSWNET endpoint

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.

chart showing contents of vSMS_SC_SiteControlXML view within the SCCM site database
Contents of vSMS_SC_SiteControlXML view within the SCCM site database

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.

Encrypted Site Master Key value contained within the Site Control XML
Encrypted Site Master Key value contained within the Site Control XML

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:

  1. An AES key is pulled out of the blob and unwrapped (decrypted) using the BCrypt provider containing the decrypted Site Master Key
  2. This AES key is then used to decrypt the encrypted password contained at the end of the blob
code decrypting BCrypt/AES password blobs
Decrypting BCrypt/AES password blobs

Overall, the new BCrypt/AES blob has a structure of:

graph of new BCrypt/AES password blob structure
New BCrypt/AES password blob structure

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:

Successfully decrypting BCrypt/AES blob to recover a plaintext password
Successfully decrypting BCrypt/AES blob to recover a plaintext password

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.

Successfully decrypting BCrypt/AES blob on passive SCCM site server
Successfully decrypting BCrypt/AES blob on passive SCCM site server

Conclusion

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.

References

Related solutions
IBM Verify

Build a secure, vendor-agnostic identity framework that modernizes IAM, integrates with existing tools, and enables seamless hybrid access without added complexity.

Explore IBM verify
Threat detection response solutions

Accelerate response by prioritizing high-impact risks and automating remediation across teams.

Explore threat detection response solutions
IBM Cyber Threat Management

Predict, prevent, and respond to modern threats to strengthen business resilience.

Explore IBM cyber threat management
Take the next step

Discover how IBM Verify modernizes IAM by integrating with your existing tools to deliver secure, seamless hybrid identity access.

Discover IBM Verify Explore threat detection response solutions