MichaelGrafnetter / DSInternals

Directory Services Internals (DSInternals) PowerShell Module and Framework
https://www.dsinternals.com
MIT License
1.62k stars 250 forks source link

Manual input of base64 encoded password blob when using the function "ConvertFrom-ADManagedPasswordBlob"? #154

Closed jsdhasfedssad closed 1 year ago

jsdhasfedssad commented 1 year ago

Hi,

Thank you for this tool!

I read DSInternals post Retrieving Cleartext GMSA Passwords from Active Directory and wanted to combine that with the cross-domain Golden GMSA attack described here.

During testing I can successfully retrieve the password as described in the post Retrieving Cleartext GMSA Passwords from Active Directory. However, this requires being logged in on a DC that has access to the GMSA account as an account that has read rights which is not possible during cross-domain attacks. Using the tool GoldenGMSA it is possible to retrieve the password blob remotely across domains which solved this problem.

GoldenGMSA's output is a base64 encoded version of the password blob which cannot be fed into the function ConvertFrom-ADManagedPasswordBlob even after decoding it. The error I get is "Input string was not in a correct format". I understand why this happens which is why I ask you if there is any way to make this function to work on a non-DC using a manually inputted base64 encoded or decoded password blob?

The end goal is to be able to use the function ConvertTo-NTHash to get the NT hash of the GMSA account's password.

Thanks!

MichaelGrafnetter commented 1 year ago

Hi @jsdhasfedssad , the thing is that the output of the GoldenGMSA.exe compute command is the password itself and not the data structure that is understood by the ConvertFrom-ADManagedPasswordBlob cmdlet.

So just use PowerShell 101 to calculate the NT hash of the password:

$encodedPassword = 'ZSWQIz5mLbEcw6mm8J49DWCADxDT703UPFLbRftDTqE7WzAfmd7mQt75o1ZvIpTqGo5okZycD2rZgBdVu9wbTxIQL6H4hbAcODW43Yv0tXxo0IYajhCiyrJ7Vvchs4dWUtCED/0pOdStxG2H7dSriLgoN62YJushjRIkHGQvK4E5zPvu5dREepOCDuhAE6F1mo33kKtpo7fWvlNgQgYNIvCYqtv4PnYzNkRZYqbMZwhocjkMRFq9WzA5YzYZwj5wgeBmi7Fl2bcm7139AIAIZgUs1XaMEPXnjlFGizH8ry9b9iOsv6YaHWBtktm5ECSVcHGHpLISVk7Rzn2tRvljew=='
$binaryPassword = [System.Convert]::FromBase64String($encodedPassword)
$unicodePassword = [System.Text.Encoding]::Unicode.GetString($binaryPassword)
$encryptedPassword = ConvertTo-SecureString -String $unicodePassword -AsPlainText -Force
ConvertTo-NTHash -Password $encryptedPassword

The output should be 768b14d275314229334684ebc73833b9. And if you want to generate Kerberos AES keys instead, use the ConvertTo-KerberosKey cmdlet, which additionally requires the corresponding salt.

jsdhasfedssad commented 1 year ago

I tested converting a blob to a NT hash using DSInternals and your Powershell instructions. While this works the outputted NT hash is incorrect. If I instead use Python to convert the very same input the outputted NT hash is correct. The two NT hashes converted using the same input does not match. See below.

Enumeration of GMSA accounts, calculation of the password blob by requesting the KDS key and finally converting to a NT hash using Powershell and DSInternals: gmsa1

Using the same values as above, converting to a NT hash using Python and verification of both NT hashes of which the one outputted by DSInternals is incorrect: gmsa2

Even though I now have a way of getting the NT hash that works I am still interested in doing the same using Powershell and DSInternals if possible.

MichaelGrafnetter commented 1 year ago

Hi @jsdhasfedssad , I digged deeper and the issue seems to be UTF-16 related. Example: When I use DSInternals to decode a sample managed password blob from AD, I get this BASE64(Unicode(cleartext)) value: 1TkRlDJIn28+Z5CsuUTNvt7krq1eMo5pDzbBWcMctnovsxLDMDUh8Nh344cOt+JxBue/7AnFoEbFposEBQiuMdob80eOTLjswaYUKRPyYbtfAuS41PNICPthtmioAZGUaOiRwspVZqi0/7RDDw39/0KcOH0V4uqfp9an07PN9dMNU5c7Zm/BgR0Kww8vfpusOSIA9Crho1PqA9Qo8CqPJsA6HpbFuoVpYIRnQD9KcdVlsDkLL610Ztwr4gAsXtKQkScIsktw6KBdqUJFlQT9/4nhPRUY+k22/f9iMZoBgijZs+NPSIr9//DklemB+2KLBE6VeFcg9MNskQVI0BbJ8g== My code sample from above calculates the right NT hash, which is 2b242f534c181917f3f3c8a1afd98ec2. But GoldenGMSA.exe gives me a value that differs in just a few bytes (out of 256): 1TkRlDJIn28+Z5CsuUTNvt7krq1eMo5pDzbBWcMctnovsxLDMDUh8Nh344cOt+JxBue/7AnFoEbFposEBQiuMdob80eOTLjswaYUKRPyYbtfAuS41PNICPthtmioAZGUaOiRwspVZqi0/7RDDw3z2UKcOH0V4uqfp9an07PN9dMNU5c7Zm/BgR0Kww8vfpusOSIA9Crho1PqA9Qo8CqPJsA6HpbFuoVpYIRnQD9KcdVlsDkLL610Ztwr4gAsXtKQkScIsktw6KBdqUJFlQSF2onhPRUY+k2299piMZoBgijZs+NPSIrE3/DklemB+2KLBE6VeFcg9MNskQVI0BbJ8g==

The difference seems to be in Unicode normalization (DSInternals produces normalized strings, while GoldenGMSA does not), but calling the String.Normalize() method does not seem to do the trick.

MichaelGrafnetter commented 1 year ago

OK, @jsdhasfedssad , so here is a code snippet that gives the right results:

[string] $encodedPassword = '1TkRlDJIn28+Z5CsuUTNvt7krq1eMo5pDzbBWcMctnovsxLDMDUh8Nh344cOt+JxBue/7AnFoEbFposEBQiuMdob80eOTLjswaYUKRPyYbtfAuS41PNICPthtmioAZGUaOiRwspVZqi0/7RDDw3z2UKcOH0V4uqfp9an07PN9dMNU5c7Zm/BgR0Kww8vfpusOSIA9Crho1PqA9Qo8CqPJsA6HpbFuoVpYIRnQD9KcdVlsDkLL610Ztwr4gAsXtKQkScIsktw6KBdqUJFlQSF2onhPRUY+k2299piMZoBgijZs+NPSIrE3/DklemB+2KLBE6VeFcg9MNskQVI0BbJ8g=='
[byte[]] $binaryPassword = [System.Convert]::FromBase64String($encodedPassword)
[System.Runtime.InteropServices.GCHandle] $pinnedArray = [System.Runtime.InteropServices.GCHandle]::Alloc($binaryPassword, [System.Runtime.InteropServices.GCHandleType]::Pinned)
try
{
    [string] $unicodePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($pinnedArray.AddrOfPinnedObject(), $binaryPassword.Count / 2)
}
finally
{
    $pinnedArray.Free()
}
$encryptedPassword = ConvertTo-SecureString -String $unicodePassword -AsPlainText -Force
ConvertTo-NTHash -Password $encryptedPassword

Probably not the cleanest solution, but it at least works.