AD_utils.ps1
# This file contains functions for various Active Directory related operations # Hash and encryption algorithms $ALGS=@{ 0x00006603 = "3DES" 0x00006609 = "3DES 112" 0x00006611 = "AES" 0x0000660e = "AES 128" 0x0000660f = "AES 192" 0x00006610 = "AES 256" 0x0000aa03 = "AGREEDKEY ANY" 0x0000660c = "CYLINK MEK" 0x00006601 = "DES" 0x00006604 = "DESX" 0x0000aa02 = "DH EPHEM" 0x0000aa01 = "DH SF" 0x00002200 = "DSS SIGN" 0x0000aa05 = "ECDH" 0x0000ae06 = "ECDH EPHEM" 0x00002203 = "ECDSA" 0x0000a001 = "ECMQV" 0x0000800b = "HASH REPLACE OWF" 0x0000a003 = "HUGHES MD5" 0x00008009 = "HMAC" 0x0000aa04 = "KEA KEYX" 0x00008005 = "MAC" 0x00008001 = "MD2" 0x00008002 = "MD4" 0x00008003 = "MD5" 0x00002000 = "NO SIGN" 0xffffffff = "OID INFO CNG ONLY" 0xfffffffe = "OID INFO PARAMETERS" 0x00004c04 = "PCT1 MASTER" 0x00006602 = "RC2" 0x00006801 = "RC4" 0x0000660d = "RC5" 0x0000a400 = "RSA KEYX" 0x00002400 = "RSA SIGN" 0x00004c07 = "SCHANNEL ENC KEY" 0x00004c03 = "SCHANNEL MAC KEY" 0x00004c02 = "SCHANNEL MASTER HASH" 0x00006802 = "SEAL" 0x00008004 = "SHA1" 0x0000800c = "SHA 256" 0x0000800d = "SHA 384" 0x0000800e = "SHA 512" 0x0000660a = "SKIPJACK" 0x00004c05 = "SSL2 MASTER" 0x00004c01 = "SSL3 MASTER" 0x00008008 = "SSL3 SHAMD5" 0x0000660b = "TEK" 0x00004c06 = "TLS1 MASTER" 0x0000800a = "TLS1PRF" } # Gets the class name of the given registry key (can't be read with pure PowerShell) # Mar 25th 2020 function Invoke-RegQueryInfoKey { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [Microsoft.Win32.RegistryKey]$RegKey ) Begin { } Process { # Create the StringBuilder and length to retrieve the class name $length = 255 $name = New-Object System.Text.StringBuilder $length # LastWrite [int64]$lw=0 $error = [AADInternals.Native]::RegQueryInfoKey( $RegKey.Handle, $name, # ClassName [ref] $length, # ClassNameLength $null, # Reserved [ref] $null, # SubKeyCount [ref] $null, # MaxSubKeyNameLength [ref] $null, # MaxClassLength [ref] $null, # ValueCount [ref] $null, # MaxValueNameLength [ref] $null, # MaxValueValueLength [ref] $null, # SecurityDescriptorSize [ref] $lw # LastWrite ) if ($error -ne 0) { Throw "Error while invoking RegQueryInfoKey" } else { $hexValue = $name.ToString() if([String]::IsNullOrEmpty($hexValue)) { Write-Error "RegQueryInfoKey: ClassName is empty" } else { return Convert-HexToByteArray $hexValue } } } } # Gets the boot key from the registry # Mar 25th 2020 function Get-Bootkey { [cmdletbinding()] Param() Process { # Get the current controlset $cc = "{0:000}" -f (Get-ItemPropertyValue "HKLM:\SYSTEM\Select" -Name "Current") # Construct the bootkey $lsaKey = "HKLM:\SYSTEM\ControlSet$cc\Control\Lsa" $bootKey = Invoke-RegQueryInfoKey (Get-Item "$lsaKey\JD") $bootKey += Invoke-RegQueryInfoKey (Get-Item "$lsaKey\Skew1") $bootKey += Invoke-RegQueryInfoKey (Get-Item "$lsaKey\GBG") $bootKey += Invoke-RegQueryInfoKey (Get-Item "$lsaKey\Data") # Return the bootkey with the correct byte order $bootKeyBytes=@( $bootKey[0x08] $bootKey[0x05] $bootKey[0x04] $bootKey[0x02] $bootKey[0x0B] $bootKey[0x09] $bootKey[0x0D] $bootKey[0x03] $bootKey[0x00] $bootKey[0x06] $bootKey[0x01] $bootKey[0x0C] $bootKey[0x0E] $bootKey[0x0A] $bootKey[0x0F] $bootKey[0x07] ) Write-Verbose "BootKey (SysKey): $((Convert-ByteArrayToHex -Bytes $bootKeyBytes).toLower())" return $bootKeyBytes } } # Gets the computer name # Apr 24th 2020 function Get-ComputerName { [cmdletbinding()] Param( [Switch]$FQDN ) Process { # Get the server name from the registry $computer = Get-ItemPropertyValue "HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName" -Name "ComputerName" if($FQDN) { # Get the domain part $domain = Get-ComputerDomainName $computer+=".$domain" } Write-Verbose "ComputerName: $computer" return $computer } } # Gets the domain of the computer # Aug 28th 2022 function Get-ComputerDomainName { [cmdletbinding()] Param( [Switch]$NetBIOS ) Process { # Get the FQDN from the registry $domainName = Get-ItemPropertyValue "HKLM:\System\CurrentControlSet\Services\Tcpip\Parameters" -Name "Domain" if(!$domainName) { throw "Could not get FQDN from registry." } Write-Verbose "Domain name: $domainName" # Get NetBIOS using WMIC if($NetBIOS) { Write-Verbose "Getting NetBIOS domain name for $domainName using WMIC" $wmiDomain = Get-WmiObject Win32_NTDomain -Filter "DnsForestName = '$($domainName)'" if(!$wmiDomain) { throw "Could not get NetBIOS domain for $FQDN using WMIC" } $DomainName = $wmiDomain.DomainName Write-Verbose "NetBIOS domain: $domainName" } return $domainName } } # Gets the machine guid # Mar 25th 2020 function Get-MachineGuid { Process { $registryValue = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Cryptography" -Name "MachineGuid" return [guid] $registryValue.MachineGuid } } # Gets the DPAPI_SYSTEM keys # Apr 23rd 2020 function Get-DPAPIKeys { <# .SYNOPSIS Gets DPAPI system keys .DESCRIPTION Gets DPAPI system keys which can be used to decrypt secrets of all users encrypted with DPAPI. MUST be run on a domain controller as an administrator .Example Get-AADIntDPAPIKeys UserKey UserKeyHex MachineKey MachineKeyHex ------- ---------- ---------- ------------- {16, 130, 39, 122...} 1082277ac85a532018930b782c30b7f2f91f7677 {226, 88, 102, 95...} e258665f0a016a7c215ceaf29ee1ae17b9f017b9 .Example $dpapi_keys=Get-AADIntDPAPIKeys #> [cmdletbinding()] Param() Process { $LSAsecrets = Get-LSASecrets -Users "DPAPI_SYSTEM" foreach($secret in $LSAsecrets) { if($secret.Name -eq "DPAPI_SYSTEM") { # Strip the first two DWORDs $key = $secret.Password[4..$($secret.Password.Length-1)] $userKey = $key[0..19] $machineKey = $key[20..39] $attributes=[ordered]@{ "UserKey" = $userKey "UserKeyHex" = Convert-ByteArrayToHex -Bytes $userKey "MachineKey" = $machineKey "MachineKeyHex" = Convert-ByteArrayToHex -Bytes $machineKey } return New-Object psobject -Property $attributes } } } } # Decrypts the given data using the given key and InitialVector (IV) # Apr 24th 2020 function Decrypt-LSASecretData { [cmdletbinding()] Param( [Parameter(Mandatory=$True)] [byte[]]$Data, [Parameter(Mandatory=$True)] [byte[]]$Key, [Parameter(Mandatory=$True)] [byte[]]$InitialVector ) Process { # Create a SHA256 object $sha256 = [System.Security.Cryptography.SHA256]::Create() # Derive the encryption key (first hash with the key, and then 1000 times with IV) $sha256.TransformBlock($Key,0,$Key.Length,$null,0) | Out-Null for($a = 0 ; $a -lt 999; $a++) { $sha256.TransformBlock($InitialVector,0,$InitialVector.Length,$null,0) | Out-Null } $sha256.TransformFinalBlock($InitialVector,0,$InitialVector.Length) | Out-Null $encryptionKey = $sha256.Hash # Create an AES decryptor $aes=New-Object -TypeName System.Security.Cryptography.AesCryptoServiceProvider $aes.Mode="ECB" $aes.Padding="None" $aes.KeySize = 256 $aes.Key = $encryptionKey # Decrypt the data $dec = $aes.CreateDecryptor() $decryptedData = $dec.TransformFinalBlock($Data,0,$Data.Count) # return return $decryptedData } } # Parse LSA secrets Blob # Apr 24th 2020 function Parse-LSASecretBlob { [cmdletbinding()] Param( [Parameter(Mandatory=$True)] [byte[]]$Data ) Process { $version = [System.BitConverter]::ToInt32($Data[3..0], 0) $guid = [guid][byte[]]($Data[4..19]) $algorithm = [System.BitConverter]::ToInt32($Data, 20) $flags = [System.BitConverter]::ToInt32($Data, 24) $lazyIv = $Data[28..59] Write-Verbose "Key ID: $($guid.ToString())" New-Object -TypeName PSObject -Property @{ "Version" = $version "GUID" = $guid "Algorighm" = $algorithm "Flags" = $flags "IV" = $lazyIv "Data" = $Data[60..$($Data.Length)] } } } # Parse LSA password Blob # Apr 24th 2020 function Parse-LSAPasswordBlob { [cmdletbinding()] Param( [Parameter(Mandatory=$True)] [byte[]]$PasswordBlob ) Process { # Get the size $BlobSize = [System.BitConverter]::ToInt32($PasswordBlob,0) # Get the actual data (strip the first four DWORDs) $Blob = $PasswordBlob[16..$(16+$BlobSize-1)] Write-Verbose "Password Blob: $(Convert-ByteArrayToHex -Bytes $Blob)" return $Blob } } # Parses LSA keystream # Apr 24th 2020 function Parse-LSAKeyStream { [cmdletbinding()] Param( [Parameter(Mandatory=$True)] [byte[]]$KeyStream ) Process { # Get the stream size $streamSize = [System.BitConverter]::ToInt32($KeyStream,0) # Get the actual data (strip the first four DWORDs) $streamData = $KeyStream[16..$(16+$streamSize-1)] # Parse the keystream metadata $ksType = [System.BitConverter]::ToInt32($streamData[0..3], 0) $CurrentKeyID = [guid][byte[]]($streamData[4..19]) Write-Verbose "Current LSA key Id: $($CurrentKeyID.ToString())" $ksType2 = [System.BitConverter]::ToInt32($streamData, 20) $ksNumKeys = [System.BitConverter]::ToInt32($streamData, 24) Write-Verbose "Number of LSA keys: $ksNumKeys" # Loop through the list of the keys, start right after the header information $pos=28 $keys=@{} for($a = 0; $a -lt $ksNumKeys ; $a++) { $keyId = [guid][byte[]]($streamData[$pos..$($pos+15)]) $pos+=16 $keyType = [System.BitConverter]::ToInt32($streamData[$pos..$($pos+3)], 0) $pos+=4 $keySize = [System.BitConverter]::ToInt32($streamData[$pos..$($pos+3)], 0) $pos+=4 $keyBytes = [byte[]]($streamData[$pos..$($pos+$keySize-1)]) $pos+=$keySize Write-Verbose "LSA Key $($a+1) Id:$($keyId.ToString()), $((Convert-ByteArrayToHex -Bytes $keyBytes).toLower())" $keys[$keyId.ToString()] = $keyBytes } return $keys } } # Gets LSA secrets # Apr 24th 2020 # Aug 29th 2022: Added support for Group Managed Service Accounts (GMSA) function Get-LSASecrets { <# .SYNOPSIS Gets computer's LSA Secrets from registry. .DESCRIPTION Gets computer's Local Security Authority (LSA) secrets from registry. MUST be run as an administrator. .PARAMETER AccountName The account name of a service .PARAMETER Users List of users .Example Get-AADIntLSASecrets Name : $MACHINE.ACC Account : Password : {131, 100, 104, 117...} PasswordHex : 836468758afd792.. PasswordTxt : 撃畨ﶊ脅䰐血⺹颶姾.. Credentials : MD4 : {219, 201, 145, 228...} SHA1 : {216, 95, 90, 3...} MD4Txt : dbc991e4e611cf4dbd0d853f54489caf SHA1Txt : d85f5a030b06061329ba93ac7da2f446981a02b6 Name : DPAPI_SYSTEM Account : Password : {1, 0, 0, 0...} PasswordHex : 010000000c63b569390.. PasswordTxt : 挌榵9႘ૂਧ绣똚鲐쒽뾮㌡懅.. Credentials : MD4 : {85, 41, 246, 248...} SHA1 : {32, 31, 39, 107...} MD4Txt : 5529f6f89c797f7d95224a554f460ea5 SHA1Txt : 201f276b05fa087a0b7e37f7052d581813d52b46 Name : NL$KM Account : Password : {209, 118, 66, 10...} PasswordHex : d176420abde330d3e443212b... PasswordTxt : 监ੂ팰䏤⬡ꎛ녀䚃劤⪠钤␎/뜕ະ... Credentials : MD4 : {157, 45, 19, 202...} SHA1 : {197, 144, 115, 117...} MD4Txt : 9d2d13cac899b491114129e5ebe00939 SHA1Txt : c590737514c8f22607fc79d771b61b1a1505c3ee Name : _SC_AADConnectProvisioningAgent Account : COMPANY\provAgentgMSA Password : {176, 38, 6, 7...} PasswordHex : b02606075f962ab4474bd570dc.. PasswordTxt : ⚰܆陟됪䭇... Credentials : System.Management.Automation.PSCredential MD4 : {123, 211, 194, 182...} SHA1 : {193, 238, 187, 166...} MD4Txt : 7bd3c2b62b66024e4e066a1f4902221e SHA1Txt : c1eebba61a72d8a4e78b1cefd27c555b83a39cb4 Name : _SC_ADSync Account : COMPANY\AAD_5baf82738e9c Password : {41, 0, 45, 0...} PasswordHex : 29002d004e0024002a00... PasswordTxt : )-N$*s=322jSQnm-YG#z2z... Credentials : System.Management.Automation.PSCredential MD4 : {81, 210, 222, 155...} SHA1 : {94, 74, 122, 142...} MD4Txt : 51d2de9b89b81d0cb371a829a2d19fe2 SHA1Txt : 5e4a7a8e220652c11cf64d25b1dcf63da7ce4bf1 Name : _SC_GMSA_DPAPI_{C6810348-4834-4a1e-817D-5838604E6004}_15030c93b7affb1fe7dc418b9dab42addf5 74c56b3e7a83450fc4f3f8a382028 Account : Password : {131, 250, 57, 146...} PasswordHex : 83fa3992cd076f3476e8be7e04... PasswordTxt : 廙鈹ߍ㑯纾�≦瀛・௰镭꾔浪�ꨲ컸O⩂�.. Credentials : MD4 : {198, 74, 199, 231...} SHA1 : {78, 213, 16, 126...} MD4Txt : c64ac7e7d2defe99afdf0026b79bbab9 SHA1Txt : 4ed5107ee08123635f08390e106ed000f96273fd Name : _SC_GMSA_{84A78B8C-56EE-465b-8496-FFB35A1B52A7}_15030c93b7affb1fe7dc418b9dab42addf574c56b 3e7a83450fc4f3f8a382028 Account : COMPANY\sv_ADFS Password : {213, 89, 245, 60...} PasswordHex : d559f53cdc2aa6dffe32d6b23... PasswordTxt : 姕㳵⫝̸�㋾닖�स䥥⫮Ꭸ베꺻ᢆ㒍梩神蔼廄... Credentials : System.Management.Automation.PSCredential MD4 : {223, 4, 60, 193...} SHA1 : {86, 201, 125, 70...} MD4Txt : df043cc10709bd9f94aa273ec7a54b68 SHA1Txt : 56c97d46b5072ebb8c5c7bfad4b8c1c18f3b48d0 .Example Get-AADIntLSASecrets -AccountName COMPANY\AAD_5baf82738e9c Name : _SC_ADSync Account : COMPANY\AAD_5baf82738e9c Password : {41, 0, 45, 0...} PasswordHex : 29002d004e0024002a00... PasswordTxt : )-N$*s=322jSQnm-YG#z2z... Credentials : System.Management.Automation.PSCredential MD4 : {81, 210, 222, 155...} SHA1 : {94, 74, 122, 142...} MD4Txt : 51d2de9b89b81d0cb371a829a2d19fe2 SHA1Txt : 5e4a7a8e220652c11cf64d25b1dcf63da7ce4bf1 .Example Get-AADIntLSASecrets -AccountName COMPANY\sv_ADFS Name : _SC_GMSA_{84A78B8C-56EE-465b-8496-FFB35A1B52A7}_15030c93b7affb1fe7dc418b9dab42addf574c56b 3e7a83450fc4f3f8a382028 Account : COMPANY\sv_ADFS Password : {213, 89, 245, 60...} PasswordHex : d559f53cdc2aa6dffe32d6b23... PasswordTxt : 姕㳵⫝̸�㋾닖�स䥥⫮Ꭸ베꺻ᢆ㒍梩神蔼廄... Credentials : System.Management.Automation.PSCredential MD4 : {223, 4, 60, 193...} SHA1 : {86, 201, 125, 70...} MD4Txt : df043cc10709bd9f94aa273ec7a54b68 SHA1Txt : 56c97d46b5072ebb8c5c7bfad4b8c1c18f3b48d0 .Example Get-AADIntLSASecrets -Users DPAPI_SYSTEM Name : DPAPI_SYSTEM Account : Password : {1, 0, 0, 0...} PasswordHex : 010000000c63b569390.. PasswordTxt : 挌榵9႘ૂਧ绣똚鲐쒽뾮㌡懅.. Credentials : MD4 : {85, 41, 246, 248...} SHA1 : {32, 31, 39, 107...} MD4Txt : 5529f6f89c797f7d95224a554f460ea5 SHA1Txt : 201f276b05fa087a0b7e37f7052d581813d52b46 #> [cmdletbinding()] Param( [Parameter(ParameterSetName='Users',Mandatory=$False)] [String[]]$Users, [Parameter(ParameterSetName='Account',Mandatory=$True)] [String]$Service, [Parameter(ParameterSetName='Service',Mandatory=$True)] [String]$AccountName ) Begin { $sha1Prov = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider } Process { # If not running as a system, run script as service if(!(Is-System)) { try { Write-Verbose "Elevating to LOCAL SYSTEM." $cmdToRun = "Set-Location '$PSScriptRoot';. '.\CommonUtils.ps1';. '.\CommonUtils_endpoints.ps1';. '.\Device_utils.ps1';. '.\AD_utils.ps1'; Get-LSASecrets" if($Users) { $cmdToRun += " -Users '$($Users -join "','")'" } elseif($Service) { $cmdToRun += " -Service '$Service'" } elseif($AccountName) { $cmdToRun += " -AccountName '$AccountName'" } Write-Verbose "Command = $cmdTorun" try { $output = Invoke-ScriptAs -Command $cmdToRun } catch { throw "Unable to get LSA secrets" } $secrets = ConvertFrom-Json -InputObject $output foreach($secret in $secrets) { $md4 = $null $sha1 = $null $Md4txt = $null $Sha1txt = $null $pwdb = Convert-B64ToByteArray -B64 $secret.Password $secret.PSObject.Properties.Remove("Password") $secret | Add-Member -NotePropertyName "Password" -NotePropertyValue $pwdb # Strip the first DWORD for DPAPI_SYSTEM if($secret.name -eq "DPAPI_SYSTEM") { $pwdb = $pwdb[4..$($pwdb.Length)] } else { if($pwdb -ne $null) { $md4=Get-MD4 -bArray $pwdb -AsByteArray $sha1 = $sha1Prov.ComputeHash($pwdb) $md4txt = Convert-ByteArrayToHex -Bytes $md4 $sha1txt = Convert-ByteArrayToHex -Bytes $sha1 Write-Verbose "MD4: $md4txt" Write-Verbose "SHA1: $sha1txt" } } $secret | Add-Member -NotePropertyName "PasswordHex" -NotePropertyValue (Convert-ByteArrayToHex -Bytes $pwdb) $secret | Add-Member -NotePropertyName "PasswordTxt" -NotePropertyValue $null try{ $secret.PasswordTxt = ([text.encoding]::Unicode.getString($pwdb)).trimend(@(0x00,0x0a,0x0d)) } catch{} $secret | Add-Member -NotePropertyName "Credentials" -NotePropertyValue $null try{ $secret.Credentials = ([pscredential]::new($secret.account,($secret.PasswordTxt | ConvertTo-SecureString -AsPlainText -Force))) } catch{} $secret | Add-Member -NotePropertyName "MD4" -NotePropertyValue $md4 $secret | Add-Member -NotePropertyName "SHA1" -NotePropertyValue $sha1 $secret | Add-Member -NotePropertyName "MD4Txt" -NotePropertyValue $md4txt $secret | Add-Member -NotePropertyName "SHA1Txt" -NotePropertyValue $sha1txt Write-Verbose "$($secret.user): $(Convert-ByteArrayToHex -Bytes $pwdb)" -ErrorAction SilentlyContinue # Return $secret } return } catch { Write-Error $_ return $null } } else { # Load common utils and native methods . ".\CommonUtils.ps1" . ".\Win32Ntv.ps1" # # Get the syskey a.k.a. bootkey # $syskey = Get-Bootkey # # Get the name and sid information # # Get the local name and sid $lnameBytes = Get-ItemPropertyValue "HKLM:\SECURITY\Policy\PolAcDmN" -Name "(default)" $LocalName = [text.encoding]::Unicode.GetString($lnameBytes[8..$($lnameBytes.Length)]) $lsidBytes = Get-ItemPropertyValue "HKLM:\SECURITY\Policy\PolAcDmS" -Name "(default)" $LocalSid=(New-Object System.Security.Principal.SecurityIdentifier($lsidBytes,0)).Value # Get the domain name and sid $dnameBytes = Get-ItemPropertyValue "HKLM:\SECURITY\Policy\PolPrDmN" -Name "(default)" $DomainName = [text.encoding]::Unicode.GetString($dnameBytes[8..$($dnameBytes.Length)]).Trim(0x00) $dsidBytes = Get-ItemPropertyValue "HKLM:\SECURITY\Policy\PolPrDmS" -Name "(default)" if($dsidBytes) { $DomainSid=(New-Object System.Security.Principal.SecurityIdentifier($dsidBytes,0)).Value } # Get the domain FQDN $fqdnBytes = Get-ItemPropertyValue "HKLM:\SECURITY\Policy\PolDnDDN" -Name "(default)" $DomainFQDN = [text.encoding]::Unicode.GetString($fqdnBytes[8..$($fqdnBytes.Length)]).Trim(0x00) Write-Verbose "Local: $LocalName ($LocalSid)" Write-Verbose "Domain: $DomainName ($DomainSid)" Write-Verbose "FQDN: $DomainFQDN" # # Get the encryption key Blob # $encKeyBlob = Parse-LSASecretBlob -Data (Get-ItemPropertyValue "HKLM:\SECURITY\Policy\PolEKList" -Name "(default)") Write-Verbose "Default key: $($encKeyBlob.GUID)" # Decrypt the encryption key Blob using the syskey $decKeyBlob = Decrypt-LSASecretData -Data ($encKeyBlob.Data) -Key $syskey -InitialVector ($encKeyBlob.IV) # Parse the keys $encKeys = Parse-LSAKeyStream -KeyStream $decKeyBlob # # Get the password Blobs for each system account # # Get service account names $gmsaNames = @{} $serviceAccounts = @{} $serviceAccountNames = Get-ServiceAccountNames #; return $serviceAccountNames # If service name was provided, find it's account if(![string]::IsNullOrEmpty($Service)) { $AccountName = $serviceAccountNames | Where-Object "Service" -eq $Service | Select-Object -ExpandProperty "AccountName" # If not found, just return $null if([string]::IsNullOrEmpty($AccountName)) { return $null } } # Loop through the service account names and get SA & gMSA names foreach($saName in $serviceAccountNames) { $svcAccount = $saName.AccountName if(![string]::IsNullOrEmpty($svcAccount) -and !$svcAccount.ToUpper().StartsWith("NT AUTHORITY\") -and !$svcAccount.ToUpper().StartsWith("NT SERVICE\") -and !$svcAccount.ToUpper().StartsWith("\DRIVER\") -and !$svcAccount.ToUpper().Equals("LOCALSYSTEM") ) { if($svcAccount.EndsWith('$')) { $svcAccount = $svcAccount.TrimEnd('$') } $serviceAccounts[$saName.Service] = $svcAccount $domain,$account = $svcAccount.Split('\') $gmsaName = Get-GMSASecretName -Type GMSA -AccountName $account -DomainName $domain $gmsaNames[$gmsaName] = $svcAccount } } # Create a user list for gMSA and SAs if AccountName was provided if(![string]::IsNullOrEmpty($AccountName)) { Write-Verbose "Trying to find secret for account: $AccountName" if($AccountName.IndexOf("\") -lt 0) { $AccountName = "$DomainName\$AccountName" } if($AccountName.EndsWith('$')) { $AccountName = $AccountName.Substring(0,$AccountName.Length-1) } $domain,$account = $AccountName.Split('\') $gmsaName = Get-GMSASecretName -Type GMSA -AccountName $account -DomainName $domain $smsaName = "_SC_{262E99C9-6160-4871-ACEC-4E61736B6F21}_$($account)$('$')" foreach($service in $serviceAccounts.Keys) { if($serviceAccounts[$service] -eq $AccountName) { $saName = $service break } } $Users = @($gmsaName, $smsaName, "_SC_$saName") } # If users list not provided, retrieve all secrets if([string]::IsNullOrEmpty($Users)) { $Users = Get-ChildItem "HKLM:\SECURITY\Policy\Secrets\" | select -ExpandProperty PSChildName } $secrets = @() foreach($user in $Users) { # Return values $attributes=[ordered]@{} $account=$null # Create the registry key $regKey = "HKLM:\SECURITY\Policy\Secrets\$user\CurrVal" if(Test-Path $regKey) { # Get the secret Blob from registry $pwdBlob = Parse-LSASecretBlob -Data (Get-ItemPropertyValue $regKey -Name "(default)") # Decrypt the password Blob using the correct encryption key $decPwdBlob = Decrypt-LSASecretData -Data ($pwdBlob.Data) -Key $encKeys[$($pwdBlob.GUID.ToString())] -InitialVector ($pwdBlob.IV) # Parse the Blob if($user.StartsWith("_SC_GMSA_")) # Group Managed Service Account or GMSA DPAPI { # Strip the header $pwdb = $decPwdBlob[16..$($decPwdBlob.length-1)] # Replace the user name if($gmsaNames.ContainsKey($user)) { $account = $gmsaNames[$user] } # Parse managed password blob for GMSA accounts if($user.StartsWith("_SC_GMSA_{84A78B8C-56EE-465b-8496-FFB35A1B52A7}_")) { $gmsa = Parse-ManagedPasswordBlob -PasswordBlob $pwdb $pwdb = $gmsa.CurrentPassword } } elseif($user.StartsWith("_SC_{262E99C9-6160-4871-ACEC-4E61736B6F21}_")) # standalone Managed Service Account sMSA { # Strip the header $pwdb = $decPwdBlob[16..$($decPwdBlob.length-1)] $account = "$DomainName\$($user.SubString(43))" # "_SC_{262E99C9-6160-4871-ACEC-4E61736B6F21}_" # Strip the dollar sign if($account.EndsWith('$')) { $account = $account.TrimEnd('$') } } elseif($user.StartsWith("_SC_")) # Service accounts doesn't have password Blob - just dump the data after the header { $serviceName = $user.SubString(4) $account = $serviceAccounts[$serviceName] $pwdb = $decPwdBlob[16..$($decPwdBlob.length-1)] } else { $pwdb = Parse-LSAPasswordBlob -PasswordBlob $decPwdBlob } # Add to return value $attributes["Name"] = $user $attributes["Account"] = $account $attributes["Password"] = Convert-ByteArrayToB64 -Bytes $pwdb $secrets += [pscustomobject]$attributes } else { Write-Verbose "No secrets found for user $user" } } return (ConvertTo-Json -InputObject $secrets -Compress) } } } # Gets LSA backup keys # Apr 24th 2020 function Get-LSABackupKeys { <# .SYNOPSIS Gets LSA backup keys .DESCRIPTION Gets Local Security Authority (LSA) backup keys which can be used to decrypt secrets of all users encrypted with DPAPI. MUST be run as an administrator .Example Get-AADIntLSABackupKeys certificate Name Id Key ----------- ---- -- --- {1, 2, 3, 4...} RSA e783c740-2284-4bd6-a121-7cc0d39a5077 {231, 131, 199, 64...} Legacy ff127a05-51b1-4d45-8655-30c883631d90 {255, 18, 122, 5...} .Example $lsabk_keys=Get-AADIntLSABackupKeys #> [cmdletbinding()] Param() Begin { $source = @" private class LSABackupkey { public string Name; public Guid Id; public byte[] Key; public LSABackupkey(string name, Guid id, byte[] key) { Name = name; Id = id; Key = key; } } "@ } Process { # First elevate the current thread by copying the token from LSASS.EXE throw "Not supported" if([AADInternals.Native]::copyLsassToken()) { # Call the native method to retrive backupkeys $backupKeys=[AADInternals.Native]::getLsaBackupKeys(); } else { Write-Error "Could not copy LSASS.EXE token. MUST be run as administrator" return } # Analyse and update the keys foreach($backupKey in $backupKeys) { if($bk=$backupKey.key) { # Get the version info (type of the key) $p=0; $version = [bitconverter]::ToInt32($bk,$p); $p+=4 if($version -eq 2) # RSA privatekey { $keyLen = [bitconverter]::ToInt32($bk,$p); $p+=4 $certLen = [bitconverter]::ToInt32($bk,$p); $p+=4 # Extract the private key and certificate $key=$bk[$p..$($p+$keyLen-1)] $p+=$keyLen $cert=$bk[$p..$($p+$certLen-1)] # Create a private key header $pvkHeader = @( # Private key magic = 0xb0b5f11e == bob's file 0x1e, 0xf1, 0xb5, 0xb0 # File version = 0 0x00, 0x00, 0x00, 0x00 # Key spec = 1 0x01, 0x00, 0x00, 0x00 # Encrypt type = 0 0x00, 0x00, 0x00, 0x00 # Encrypt data = 0 0x00, 0x00, 0x00, 0x00 ) $pvkHeader += [System.BitConverter]::GetBytes([int32]$keyLen) # Construct the private key and update key object $privateKey = $pvkHeader + $key $backupKey.key = [byte[]]$privateKey # Add certificate to key object $backupKey | Add-Member -NotePropertyName "certificate" -NotePropertyValue $cert } elseif($version -eq 1) # Legacy key { # Update the key object's key $key = $bk[$p..$($bk.Length)] $backupKey.key = $key } } } return $backupKeys } } # Gets the given user's DSAPI master keys # Apr 25th 2020 function Get-UserMasterkeys { <# .SYNOPSIS Gets user's master keys .DESCRIPTION Gets user's master keys using the password or system backup key (LSA backup key) .Example Get-AADIntUserMasterkeys -UserName "myuser" -SID "S-1-5-xxxx" -Password "password" Name Value ---- ----- ec3c7e8e-fb06-43ad-b382-8c5... {236, 60, 126, 142...} 8a26d304-198c-4495-918f-77b... {166, 95, 5, 216...} .Example $lsabk_keys=Get-AADIntLSABackupKeys PS C:\>$rsa_key=$lsabk_keys | where name -eq RSA PS C:\>Get-AADIntUserMasterkeys -UserName "myuser" -SID "S-1-5-xxxx" -SystemKey $rsa_key.key Name Value ---- ----- ec3c7e8e-fb06-43ad-b382-8c5... {236, 60, 126, 142...} 8a26d304-198c-4495-918f-77b... #> [CmdletBinding()] param( [Parameter(Mandatory=$false, ParameterSetName="credentials")] [Parameter(Mandatory=$true, ParameterSetName="password")] [Parameter(Mandatory=$true, ParameterSetName="systemkey")] [String]$UserName, [Parameter(Mandatory=$false)] [String]$SID, [Parameter(Mandatory=$true, ParameterSetName="password")] [String]$Password, [Parameter(Mandatory=$true, ParameterSetName="systemkey")] [byte[]]$SystemKey, [Parameter(Mandatory=$true, ParameterSetName="credentials")] [pscredential]$Credentials, [Parameter(Mandatory=$false)] [String]$UsersFolder="C:\Users" ) Process { $retVal=@{} # Populate username and password from credentials if($Credentials) { $UserName = $Credentials.UserName $Password = $Credentials.GetNetworkCredential().Password } # Strip the domain part $s = $UserName.IndexOf("\") if($s -gt 0) { $UserName = $UserName.Substring($s+1) } $keysPath="$UsersFolder\$userName\AppData\Roaming\Microsoft\Protect\" # If SID not provided, get one from the keysPath. There should be only one.. if([string]::IsNullOrEmpty($SID)) { $SIDs = Get-ChildItem -Path $keysPath -Filter "S-1-5-21-*" if($SIDs.Count -gt 1) { Throw "More than one credentials found, please provide SID" } else { $SID = $SIDs.Name } } $keysPath += $SID $fileNames=Get-ChildItem -Path $keysPath -Hidden | select -ExpandProperty Name foreach($fileName in $fileNames) { $guid=$null try { $guid=[guid]$fileName } catch{} if($guid -ne $null) { Write-Verbose "Found masterkey file: $("$keysPath\$fileName")`n`n" $binMasterKey = Get-BinaryContent "$keysPath\$fileName" $mk = Parse-MasterkeyBlob -Data $binMasterKey if($SystemKey) # Decrypt using SystemKey { if($mk.DomainKey) { $decKey = Decrypt-MasterkeyBlob -Systemkey $SystemKey -Data $mk.DomainKey } } else { $decKey = Decrypt-MasterkeyBlob -Data $mk.MasterKey -Password $Password -SID $SID -Salt $mk.MasterKeySalt -Iterations $mk.MasterKeyIterations -Flags $mk.MasterKeyFlags } $retVal[$mk.MasterKeyGuid] = $decKey } } return $retVal } } # Parses the given masterkey blob # Apr 25th 2020 function Parse-MasterkeyBlob { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$Data ) Process { # Parse the header $version = [System.BitConverter]::ToInt32($Data,0) $guid = [guid][text.encoding]::Unicode.GetString($Data[12..83]) $flags = [System.BitConverter]::ToInt32($Data,92) $mKeyLen = [System.BitConverter]::ToInt64($Data,96) # Master Key $bKeyLen = [System.BitConverter]::ToInt64($Data,104) # Backup Key $crHisLen = [System.BitConverter]::ToInt64($Data,112) # Credential History $dKeyLen = [System.BitConverter]::ToInt64($Data,120) # Domain Key Write-Verbose "Masterkey GUID: $guid" Write-Verbose "Masterkey length: $mKeyLen" Write-Verbose "Backupkey length: $bKeyLen" Write-Verbose "CredHist length: $crHisLen" Write-Verbose "Domainkey length: $dKeyLen`n`n" # Set the position $p = 128 # Parse Master key Blob $mkVersion = [System.BitConverter]::ToInt32($Data,$p+0) $mkSalt = $Data[$($p+4)..$($p+19)] $mkRounds = [System.BitConverter]::ToInt32($Data,$p+20) $mkHashAlg = [System.BitConverter]::ToInt32($Data,$p+24) $mkCryptAlg = [System.BitConverter]::ToInt32($Data,$p+28) $mkBytes = $Data[$($p+32)..$($p+$mKeyLen-1)] Write-Verbose "MASTERKEY" Write-Verbose "Salt: $(Convert-ByteArrayToHex -Bytes $mkSalt)" Write-Verbose "Rounds: $mkRounds" Write-Verbose "Hash Alg: $mkHashAlg $($ALGS[$mkHashAlg])" Write-Verbose "Crypt Alg: $mkCryptAlg $($ALGS[$mkCryptAlg])" Write-Verbose "Key: $(Convert-ByteArrayToHex -Bytes $mkBytes)`n`n" # Set the position $p += $mKeyLen # Parse Backup key Blob $bkVersion = [System.BitConverter]::ToInt32($Data,$p+0) $bkSalt = $Data[$($p+4)..$($p+19)] $bkRounds = [System.BitConverter]::ToInt32($Data,$p+20) $bkHashAlg = [System.BitConverter]::ToInt32($Data,$p+24) $bkCryptAlg = [System.BitConverter]::ToInt32($Data,$p+28) $bkBytes = $Data[$($p+32)..$($p+$bKeyLen-1)] Write-Verbose "BACKUPKEY" Write-Verbose "Salt: $(Convert-ByteArrayToHex -Bytes $bkSalt)" Write-Verbose "Rounds: $bkRounds" Write-Verbose "Hash Alg: $bkHashAlg $($ALGS[$bkHashAlg])" Write-Verbose "Crypt Alg: $bkCryptAlg $($ALGS[$bkCryptAlg])" Write-Verbose "Key: $(Convert-ByteArrayToHex -Bytes $bkBytes)`n`n" # Set the position $p += $bKeyLen # Parse credential history if($crHisLen -gt 0) { $crVersion = [System.BitConverter]::ToInt32($Data,$p+0) $crGuid = [guid][byte[]]($Data[$($p+4)..$($p+19)]) Write-Verbose "CREDENTIAL HISTORY" Write-Verbose "Guid: $crGuid`n`n" } # Set the position $p += $crHisLen # There seems not to be domain key for domain admins? if($p -lt $Data.Length) { # Parse Domain key Blob $dkVersion = [System.BitConverter]::ToInt32($Data,$p+0) $dkSecLen = [System.BitConverter]::ToInt32($Data,$p+4) $dkAccLen= [System.BitConverter]::ToInt32($Data,$p+8) $dkGuid = [guid][byte[]]($Data[$($p+12)..$($p+27)]) $dkBytes = $Data[$($p+28)..$($p+28+$dkSecLen-1)] $dkAccBytes = $Data[$($p+28+$dkSecLen)..$($p+28+$dkSecLen+$dkAccLen-1)] Write-Verbose "DOMAINKEY" Write-Verbose "Guid: $dkGuid" Write-Verbose "Key: $(Convert-ByteArrayToHex -Bytes $dkBytes)" Write-Verbose "Access Check:$(Convert-ByteArrayToHex -Bytes $dkAccBytes)`n`n" } # Create a return object $attributes = [ordered]@{ "MasterKeyFlags" = $flags "MasterKeyGuid" = $guid "MasterKey" = $mkBytes "MasterKeySalt" = $mkSalt "MasterKeyIterations" = $mkRounds "MasterKeyHashAlg" = $mkHashAlg "MasterKeyCryptAlg" = $mkCryptAlg "BackupKey" = $bkBytes "BackupKeySalt" = $bkSalt "BackupKeyIterations" = $bkRounds "BackupKeyHashAlg" = $bkHashAlg "BackupKeyCryptAlg" = $bkCryptAlg "DomainKeyGuid" = $dkGuid "DomainKey" = $dkBytes "DomainKeyAC" = $dkAccBytes "CredHistoryGuid" = $crGuid } return New-Object PSObject -Property $attributes } } # Parses the given masterkey blob # Apr 29th 2020 function Decrypt-MasterkeyBlob { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$Data, [Parameter(Mandatory=$true, ParameterSetName="password")] [byte[]]$Salt, [Parameter(Mandatory=$true, ParameterSetName="password")] [int]$Iterations, [Parameter(Mandatory=$true, ParameterSetName="password")] [String]$Password, [Parameter(Mandatory=$true, ParameterSetName="password")] [String]$SID, [Parameter(Mandatory=$true, ParameterSetName="systemkey")] [byte[]]$Systemkey, [Parameter(Mandatory=$true, ParameterSetName="password")] [String]$Flags, [Parameter(Mandatory=$false)] [bool]$Protected=$true ) Begin { $sha = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider } Process { if(!$SystemKey) { # Create the password hash throw "Not supported" $md4 = [AADInternals.Native]::getHash(0x00008002 <#MD4#>,[text.encoding]::Unicode.GetBytes($Password)) Write-Verbose "Password hash (MD4): $(Convert-ByteArrayToHex -Bytes $md4)" $sha1 = [AADInternals.Native]::getHash(0x00008004 <#SHA_1#>,[text.encoding]::Unicode.GetBytes($Password)) Write-Verbose "Password hash (SHA1): $(Convert-ByteArrayToHex -Bytes $sha1)" if($flags -band 4) { # SHA1 $pwdHash = $sha1 } else { # MD4 $pwdHash = $md4 } # If the account is protected, we need to get a new hash if($Protected) { # Convert SID to wide byte array $SIDbin = [text.encoding]::Unicode.getBytes($SID) $pwdHash = [AADInternals.Native]::getPBKDF2(0x0000800c <#SHA_256#>, $pwdHash, $SIDbin, 10000, 32) $pwdHash = [AADInternals.Native]::getPBKDF2(0x0000800c <#SHA_256#>, $pwdHash, $SIDbin, 1, 16) } Write-Verbose "Final user hash: $(Convert-ByteArrayToHex -Bytes $pwdHash)" # Derive the key from the password hash and SID throw "Not supported" $derivedKey = [AADInternals.Native]::getHMAC(0x00008004 <#SHA_1#>,$pwdHash,[byte[]]($SIDbin+0+0)) # SID needs null terminators ♥ MS Write-Verbose "Derived key: $(Convert-ByteArrayToHex -Bytes $derivedKey)`n`n" # Decode the masterkey using the derived key try { $decMasterKey = [AADInternals.Native]::getMasterkey($derivedKey, $Data, $Salt, $Iterations) } catch { return $null } # Okay(ish) } else { # Decode the masterkey using the provided System Key $decMasterKey = [AADInternals.Native]::getMasterkey($SystemKey,$Data) } Write-Verbose "Decrypted masterkey: $(Convert-ByteArrayToHex -Bytes $decMasterKey)`n`n" return $decMasterKey } } # Gets the given user's credentials from the vault # Apr 28th 2020 function Get-LocalUserCredentials { <# .SYNOPSIS Gets user's credentials from the local credential vault .DESCRIPTION Gets user's credentials from the local credential vault and decrypts them using the given masterkeys hashtable .Example Get-AADIntLocalUserCredentials -UserName user -MasterKeys $master_keys Target : LegacyGeneric:target=msTeams_autologon.microsoftazuread-sso.com:443/user@company.com Persistance : local_machine Edited : 26/03/2020 10.12.11 Alias : Comment : UserName : Secret : {97, 115, 100, 102...} SecretTxt : 獡晤晤 SecretTxtUtf8 : asdfdf Attributes : {} .Example $lsabk_keys=Get-AADIntLSABackupKeys PS C:\>$rsa_key=$lsabk_keys | where name -eq RSA PS C:\>$user_masterkeys=Get-AADIntUserMasterkeys -UserName "myuser" -SID "S-1-5-xxxx" -SystemKey $rsa_key.key PS C:\>Get-AADIntLocalUserCredentials -UserName "myuser" -MasterKeys $user_masterkeys #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [String]$UserName, [Parameter(Mandatory=$false)] [System.Collections.Hashtable]$MasterKeys, [Parameter(Mandatory=$false)] [String]$UsersFolder="C:\Users", [Parameter(Mandatory=$false)] [byte[]]$Entropy, [Parameter(Mandatory=$false)] [ValidateSet("LocalMachine","CurrentUser")] [string]$Scope = 'LocalMachine' ) Process { $retVal=@() # Strip the domain part $s = $UserName.IndexOf("\") if($s -gt 0) { $UserName = $UserName.Substring($s+1) } $localPath = "$UsersFolder\$userName\AppData\Local\Microsoft\Credentials" $roamingPath = "$UsersFolder\$userName\AppData\Roaming\Microsoft\Credentials" $localNames = Get-ChildItem -Path $localPath -Hidden | select -ExpandProperty Name $roamingNames = Get-ChildItem -Path $roamingPath -Hidden | select -ExpandProperty Name # Get the local credentials foreach($fileName in $localNames) { try { Write-Verbose "Found credentials file: $("$localPath\$fileName")`n`n" $binCredentials = Get-BinaryContent "$localPath\$fileName" $retVal += Parse-CredentialsBlob -Data $binCredentials -MasterKeys $MasterKeys -Entropy $Entropy -Scope $Scope } catch {} #Okay } # Get the roaming credentials foreach($fileName in $roamingNames) { try { Write-Verbose "Found credentials file: $("$roamingPath\$fileName")`n`n" $binCredentials = Get-BinaryContent "$roamingPath\$fileName" $retVal += Parse-CredentialsBlob -Data $binCredentials -MasterKeys $MasterKeys } catch {} #Okay } return $retVal } } # Parses the given credentials blob with # Apr 28th 2020 function Parse-CredentialsBlob { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$Data, [Parameter(Mandatory=$false)] [System.Collections.Hashtable]$MasterKeys, [Parameter(Mandatory=$false)] [byte[]]$Entropy, [Parameter(Mandatory=$false)] [ValidateSet("LocalMachine","CurrentUser")] [string]$Scope = 'LocalMachine' ) Begin { $persistenceTxt = @("none", "session", "local_machine", "enterprise") Add-Type -AssemblyName System.Security } Process { # Parse and the DPAPI blob $DPAPIBlob = Parse-DPAPIBlob -Data $Data[12..$($data.Length)] if($MasterKeys) { # Get the masterkey guid from DPAPI blob $mkGuid = $DPAPIBlob.MasterKeyGuid # Get the correct masterkey $masterKey = $MasterKeys[$mkGuid] if(!$masterKey) { throw "DPAPI masterkey $mkGuid not found!" } # Decrypt the credentials blob $cBlob = Decrypt-DPAPIBlob -Data $DPAPIBlob.EncryptedData -MasterKey $masterKey -Salt $DPAPIBlob.Salt if($cBlob) { Write-Verbose "Decrypted Data: $(Convert-ByteArrayToHex -Bytes $cBlob)`n`n" # # Parse the credentials blob $p=0 $crFlags = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $crSize = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $crUnk0 = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $type = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $flags = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $Time = [datetime]::FromFileTimeUtc([System.BitConverter]::ToInt64($cBlob,$p));$p+=8 $unk0 = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $persist = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $atCount = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $unk1 = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $unk2 = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $tgLen = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $target = ([text.encoding]::Unicode.GetString($cBlob[$p..$($p+$tgLen-1)])).trim(@(0x00,0x0a,0x0d)); $p+=$tgLen $alLen = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $alias = ([text.encoding]::Unicode.GetString($cBlob[$p..$($p+$alLen-1)])).trim(@(0x00,0x0a,0x0d)); $p+=$alLen $cmLen = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $comment = ([text.encoding]::Unicode.GetString($cBlob[$p..$($p+$cmLen-1)])).trim(@(0x00,0x0a,0x0d)); $p+=$cmLen $ukLen = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $unkData = ([text.encoding]::Unicode.GetString($cBlob[$p..$($p+$ukLen-1)])).trim(@(0x00,0x0a,0x0d)); $p+=$ukLen $usLen = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $userName = ([text.encoding]::Unicode.GetString($cBlob[$p..$($p+$usLen-1)])).trim(@(0x00,0x0a,0x0d)); $p+=$usLen $cbLen = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $crData = [byte[]]$cBlob[$p..($p+$cbLen-1)];$p+=$cbLen $crDataTxt = [text.encoding]::Unicode.GetString($crData).trim(@(0x00,0x0a,0x0d)) $crDataTxtUtf8 = [text.encoding]::UTF8.GetString($crData).trim(@(0x00,0x0a,0x0d)) $crAttrs=@{} for($a = 0 ; $a -lt $atCount) { $atFlag = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $kwLen = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $keyWord = ([text.encoding]::Unicode.GetString($cBlob[$p..$($p+$kwLen-1)])).trim(@(0x00,0x0a,0x0d)); $p+=$kwLen $vaLen = [System.BitConverter]::ToInt32($cBlob,$p);$p+=4 $value = ([text.encoding]::Unicode.GetString($cBlob[$p..$($p+$vaLen-1)])).trim(@(0x00,0x0a,0x0d)); $p+=$vaLen $crAttrs[$keyWord]=$value } Write-Verbose "***CREDENTIALS BLOB***" Write-Verbose "Target: $target" Write-Verbose "Last Written: $time" Write-Verbose "Persistence: $($persistenceTxt[$persist])" Write-Verbose "Alias: $alias" Write-Verbose "Comment: $comment" Write-Verbose "User name: $userName" Write-Verbose "Secret: $(Convert-ByteArrayToHex -Bytes $crData)" Write-Verbose "SecretTxt: $crDataTxt" Write-Verbose "SecretTxtUtf8: $crDataTxtUtf8" Write-Verbose "Attributes: $crAttrs`n`n`n" # Create a return object $attributes = [ordered]@{ "Target" = $target "Persistance" = $persistenceTxt[$persist] "Edited" = $time "Alias" = $alias "Comment" = $comment "UserName" = $userName "Secret" = $crData "SecretTxt" = $crDataTxt "SecretTxtUtf8" = $crDataTxtUtf8 "Attributes" = $crAttrs } return New-Object PSObject -Property $attributes } else { Write-Error "Could not decrypt the DPAPI blob." return $null } } else { [Security.Cryptography.ProtectedData]::Unprotect($DPAPIBlob,$Entropy,$Scope) } } } # Parses the given DPAPI blob # Apr 28th 2020 function Parse-DPAPIBlob { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$Data ) Process { # Parse the DPAPIBlob $p=0 $version = [System.BitConverter]::ToInt32($Data,0);$p+=4 $provGuid = [guid][byte[]]$Data[$p..($p+15)];$p+=16 $mkVersion = [System.BitConverter]::ToInt32($Data,$p);$p+=4 $mkGuid = [guid][byte[]]$Data[$p..($p+15)];$p+=16 $flags = [System.BitConverter]::ToInt32($Data,$p);$p+=4 $dscLen = [System.BitConverter]::ToInt32($Data,$p);$p+=4 $description = ([text.encoding]::Unicode.GetString($Data[$p..$($p+$dscLen-1)])).trim(@(0x00,0x0a,0x0d)); $p+=$dscLen $algCrypt = [System.BitConverter]::ToInt32($Data,$p);$p+=4 $algCryptLen = [System.BitConverter]::ToInt32($Data,$p);$p+=4 $saltLen = [System.BitConverter]::ToInt32($Data,$p);$p+=4 $salt = $Data[$p..($p+$saltLen-1)];$p+=$saltLen $hmacKeyLen = [System.BitConverter]::ToInt32($Data,$p);$p+=4 if($hmacKeyLen -gt 0) {$hmacKey = $Data[$p..($p+$hmacKeyLen-1)];$p+=$hmacKeyLen } $algHash = [System.BitConverter]::ToInt32($Data,$p);$p+=4 $algHashLen = [System.BitConverter]::ToInt32($Data,$p);$p+=4 $hmac2KeyLen = [System.BitConverter]::ToInt32($Data,$p);$p+=4 if($hmac2KeyLen -gt 0) {$hmac2Key = $Data[$p..($p+$hmac2KeyLen-1)];$p+=$hmac2KeyLen} $encDataLen = [System.BitConverter]::ToInt32($Data,$p);$p+=4 $encData = $Data[$p..($p+$encDataLen-1)];$p+=$encDataLen $signLen = [System.BitConverter]::ToInt32($Data,$p);$p+=4 $signature = $Data[$p..($p+$signLen-1)] Write-Verbose "***DPAPIBLOB***" Write-Verbose "Provider GUID: $provGuid" Write-Verbose "Masterkey GUID: $mkGuid" Write-Verbose "Description: $description" Write-Verbose "Hash Alg: $algHash $($ALGS[$algHash])" Write-Verbose "Crypt Alg: $algCrypt $($ALGS[$algCrypt])" if($hmacKey) { Write-Verbose "HMAC key: $(Convert-ByteArrayToHex -Bytes $hmacKey)"} if($hmac2Key) { Write-Verbose "HMAC key2: $(Convert-ByteArrayToHex -Bytes $hmac2Key)"} Write-Verbose "Salt: $(Convert-ByteArrayToHex -Bytes $salt)" Write-Verbose "Encrypted Data: $(Convert-ByteArrayToHex -Bytes $encData)" Write-Verbose "Signature: $(Convert-ByteArrayToHex -Bytes $signature)`n`n" # Create a return object $attributes = [ordered]@{ "ProviderGuid" = $provGuid "MasterKeyGuid" = $mkGuid "Description" = $description "HashAlg" = $algHash "CryptAlg" = $algCrypt "Salt" = $salt "EncryptedData" = $encData "Signature" = $signature "HMACKey" = $hmacKey "HMACKey2" = $hmac2Key } return New-Object PSObject -Property $attributes } } # Decrypts the DPAPI secret using the given masterkey and salt # Apr 29th 2020 function Decrypt-DPAPIBlob { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$Data, [Parameter(Mandatory=$true)] [byte[]]$MasterKey, [Parameter(Mandatory=$true)] [byte[]]$Salt ) Process { # Decrypt the DPAPI blob with the given masterkey and salt throw "Not supported" $decData = [AADInternals.Native]::getDPAPIBlob($MasterKey,$Data,$salt) return $decData } } # Gets the system masterkeys # Apr 29th 2020 function Get-SystemMasterkeys { <# .SYNOPSIS Gets local system master keys .DESCRIPTION Gets local system master keys with the givne system backup key (LSA backup key) $lsabk_keys=Get-AADIntLSABackupKeys PS C:\>$rsa_key=$lsabk_keys | where name -eq RSA PS C:\>Get-AADIntSystemMasterkeys -SystemKey $rsa_key.key Name Value ---- ----- ec3c7e8e-fb06-43ad-b382-8c5... {236, 60, 126, 142...} #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$SystemKey ) Process { $keysPath="$env:windir\System32\Microsoft\Protect\S-1-5-18" # Get the preferred masterkey guid $preferredFile = Get-BinaryContent "$keysPath\Preferred" $masterKeyGuid = ([guid][byte[]]$preferredFile[0..15]).ToString() $TimeStamp = [datetime]::FromFileTimeUtc([System.BitConverter]::ToInt64($preferredFile,16)) Write-Verbose "Preferred key: $masterKeyGuid, valid until: $TimeStamp" # Get the preferred masterkey $fileName = "$keysPath\$($masterKeyGuid.ToString())" Write-Verbose "Opening masterkey file: $fileName`n`n" $binMasterKey = Get-BinaryContent $fileName # Parse the masterkey blob $mk = Parse-MasterkeyBlob -Data $binMasterKey $decKey = Decrypt-MasterkeyBlob -Systemkey $systemKey -Data $mk.DomainKey $retVal = @{$mk.MasterKeyGuid = $decKey} return $retVal } } # Parse ManagedPassword blob # Aug 23rd 2022 function Parse-ManagedPasswordBlob { [cmdletbinding()] Param( [Parameter(Mandatory=$True)] [byte[]]$PasswordBlob ) Process { $properties = [ordered]@{} $p = 0 # Get version $version = [System.BitConverter]::ToInt32($PasswordBlob,$p); $p += 4 if($version -ne 1) { Write-Error "Invalid version: $version, was expecting 1" return $null } # Get blob size $blobSize = [System.BitConverter]::ToInt32($PasswordBlob,$p); $p += 4 if($blobSize -ne $PasswordBlob.Length) { # Blob may have null byte padding - then okay! if($PasswordBlob.Length % 16 -ne 0) { Write-Warning "ManagedPasswordBlob length $($PasswordBlob.Length) bytes, was expecting $blobSize" } } # Get the current password $curPwdOffset = [System.BitConverter]::ToInt16($PasswordBlob,$p); $p += 2 if($curPwdOffset -eq 0) { Write-Error "Invalid current password offset: 0" return $null } else { $properties["CurrentPassword"] = $PasswordBlob[$curPwdOffset..$($curPwdOffset+255)] } # Get the previous password $prevPwdOffset = [System.BitConverter]::ToInt16($PasswordBlob,$p); $p += 2 if($prevPwdOffset -ne 0) { $properties["PreviousPassword"] = $PasswordBlob[$prevPwdOffset..$($prevPwdOffset+255)] } # Get the QueryPasswordInterval $queryPwdIntervalOffset = [System.BitConverter]::ToInt16($PasswordBlob,$p); $p += 2 $properties["QueryPasswordInterval"] = [timespan]::FromTicks([System.BitConverter]::ToInt64($PasswordBlob,$queryPwdIntervalOffset)) # Get the UnchangedPasswordInterval $unchangedPwdIntervalOffset = [System.BitConverter]::ToInt16($PasswordBlob,$p); $properties["UnchangedPasswordInterval"] = [timespan]::FromTicks([System.BitConverter]::ToInt64($PasswordBlob,$unchangedPwdIntervalOffset)) return New-Object psobject -Property $properties } } # Returns the GMSA secret name for the given username # Aug 28th 2022 function Get-GMSASecretName { [cmdletbinding()] Param( [Parameter(Mandatory=$true,ValueFromPipeline)] [String]$AccountName, [Parameter(Mandatory=$False)] [String]$DomainName, [Parameter(Mandatory=$False)] [ValidateSet("GMSA","DPAPI")] [String]$Type="GMSA" ) Process { # Get the domain name from registry & wmic if not provided if([string]::IsNullOrEmpty($DomainName)) { $DomainName = Get-ComputerDomainName -NetBIOS } # Strip the dollar sign if present if($AccountName.EndsWith('$')) { $AccountName = $AccountName.SubString(0,$AccountName.Length - 1) } # Concatenate domain and account name + make upper case $GMSAAccountName = "$DomainName$AccountName".ToUpper() Write-Verbose "Calculating GMSA name for $GMSAAccountName" # Get the SHA256 hash with HMAC flag. Microsoft <3 $binAccountName = [text.encoding]::Unicode.getBytes($GMSAAccountName) Write-Debug "Encoded account name: $(Convert-ByteArrayToHex $binAccountName)" $binHash = [AADInternals.Native]::GetSHA256withHMACFlag($binAccountName) if(!$binHash) { throw "Unable to get SHA256 hash with HMAC flag for $GMSAAccountName" } Write-Debug "Hash1: $(Convert-ByteArrayToHex $binHash)" # The hex string is calculated by switching the hi/low bits of each byte. Microsoft <3 $hexLetters = "0123456789abcdef" $strHash="" $pos = 0 do{ $strHash += $hexLetters[($binHash[$pos] -band 0x0f)] $strHash += $hexLetters[($binHash[$pos] -shr 0x04)] $pos+=1 }while($pos -lt $binHash.Length) Write-Debug "Hash2: $strHash" # Prefix is hard coded $preFix="" if($Type -eq "GMSA") { $preFix = "_SC_GMSA_{84A78B8C-56EE-465b-8496-FFB35A1B52A7}_" } else { $preFix = "_SC_GMSA_DPAPI_{C6810348-4834-4a1e-817D-5838604E6004}_" } $GMSASecretName = "$preFix$strHash" Write-Verbose "GMSA secret name for $($GMSAAccountName): $GMSASecretName" # Return return $GMSASecretName } } |