AD_utils.ps1
# This file contains functions for various Active Directory related operations # Import the Native Methods dll Add-Type -path "$PSScriptRoot\Win32Ntv.dll" # 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 ) 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() Process { # Construct the bootkey $computer = Get-ItemPropertyValue "HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName" -Name "ComputerName" Write-Verbose "ComputerName: $computer" return $computer } } # 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 function Get-LSASecrets { <# .SYNOPSIS Gets computer's LSA Secrets .DESCRIPTION Gets computer's Local Security Authority (LSA) secrets. MUST be run as an administrator. .Example Get-AADIntLSASecrets Name : $MACHINE.ACC Password : {1, 2, 3, 4...} PasswordHex : 01020304.. PasswordTxt : 컓噖덭а劈-⌋결 MD4 : {1, 2, 3, 4...} SHA1 : {1, 2, 3, 4...} MD4Txt : aabbccdd.. SHA1Txt : aabbccdd.. Name : DPAPI_SYSTEM Password : {1, 0, 0, 0...} PasswordHex : 0100000001082277ac85a532018930b782c30b7f2f91f7677e258665f0a016a7c215ceaf29ee1ae17b9f017b9 PasswordTxt : 挌榵 MD4 : {1, 2, 3, 4...} SHA1 : {1, 2, 3, 4...} MD4Txt : aabbccdd.. SHA1Txt : aabbccdd.. Name : NL$KM Password : {1, 2, 3, 4...} PasswordHex : 01020304.. PasswordTxt : ⬡ꎛ MD4 : {1, 2, 3, 4...} SHA1 : {1, 2, 3, 4...} MD4Txt : aabbccdd.. SHA1Txt : aabbccdd.. Name : _SC_ADSync Password : {1, 2, 3, 4...} PasswordHex : 01020304.. PasswordTxt : a5bTiGcvC8fr=E;MQ331IOt/&RP,!m:qjiRXaS;xr4V#6t74;&7mXWoOoz"57K/kKTz#xdBBqb.GDKly MD4 : {1, 2, 3, 4...} SHA1 : {1, 2, 3, 4...} MD4Txt : aabbccdd.. SHA1Txt : aabbccdd.. #> [cmdletbinding()] Param( [Parameter(Mandatory=$False)] [String[]]$Users ) Begin { $sha1Prov = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider } Process { # First elevate the current thread by copying the token from LSASS.EXE if([AADInternals.Native]::copyLsassToken()) { # # 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)]) $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)]) 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 # # If users list not provided, retrieve all secrets if([string]::IsNullOrEmpty($Users)) { $Users = Get-ChildItem "HKLM:\SECURITY\Policy\Secrets\" | select -ExpandProperty PSChildName } foreach($user in $Users) { # Return values $attributes=[ordered]@{} $md4=$null $sha1=$null $Md4txt=$null $Sha1txt=$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_")) # Service accounts doesn't have password Blob - just dump the data after the header { $pwdb = $decPwdBlob[16..$($decPwdBlob.length-1)] } else { $pwdb = Parse-LSAPasswordBlob -PasswordBlob $decPwdBlob } # Strip the first DWORD for DPAPI_SYSTEM if($name -eq "DPAPI_SYSTEM") { $pwdb = $pwdb[4..$($pwdb.Length)] } else { $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" } # Add to return value $attributes["Name"] = $user $attributes["Password"] = $pwdb $attributes["PasswordHex"] = Convert-ByteArrayToHex -Bytes $pwdb $attributes["PasswordTxt"] = "" try{ $attributes["PasswordTxt"] = ([text.encoding]::Unicode.getString($pwdb)).trimend(@(0x00,0x0a,0x0d)) } catch{} $attributes["MD4"] = $md4 $attributes["SHA1"] = $sha1 $attributes["MD4Txt"] = $md4txt $attributes["SHA1Txt"] = $sha1txt Write-Verbose "$($user): $(Convert-ByteArrayToHex -Bytes $pwdb)" -ErrorAction SilentlyContinue New-Object psobject -Property $attributes } else { Write-Error "No secrets found for user $user" } } } else { Write-Error "Could not copy LSASS.EXE token. MUST be run as administrator" } } } # 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() Process { # First elevate the current thread by copying the token from LSASS.EXE 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=$true)] [String]$UserName, [Parameter(Mandatory=$true)] [String]$SID, [Parameter(Mandatory=$true, ParameterSetName="password")] [String]$Password, [Parameter(Mandatory=$true, ParameterSetName="systemkey")] [byte[]]$SystemKey, [Parameter(Mandatory=$false)] [String]$UsersFolder="C:\Users" ) Process { $retVal=@{} #$bSID=[System.Security.Principal.SecurityIdentifier]::new($SID) $keysPath="$UsersFolder\$userName\AppData\Roaming\Microsoft\Protect\$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-Content "$keysPath\$fileName" -Encoding Byte $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 $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 $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 $decMasterKey = [AADInternals.Native]::getMasterkey($derivedKey, $Data, $Salt, $Iterations) } 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=$true)] [System.Collections.Hashtable]$MasterKeys, [Parameter(Mandatory=$false)] [String]$UsersFolder="C:\Users" ) Process { $retVal=@() $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) { Write-Verbose "Found credentials file: $("$localPath\$fileName")`n`n" $binCredentials = Get-Content "$localPath\$fileName" -Encoding Byte $retVal += Parse-CredentialsBlob -Data $binCredentials -MasterKeys $MasterKeys } # Get the roaming credentials foreach($fileName in $roamingNames) { Write-Verbose "Found credentials file: $("$roamingPath\$fileName")`n`n" $binCredentials = Get-Content "$roamingPath\$fileName" -Encoding Byte $retVal += Parse-CredentialsBlob -Data $binCredentials -MasterKeys $MasterKeys } return $retVal } } # Parses the given credentials blob with # Apr 28th 2020 function Parse-CredentialsBlob { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [byte[]]$Data, [Parameter(Mandatory=$true)] [System.Collections.Hashtable]$MasterKeys ) Begin { $persistenceTxt = @("none", "session", "local_machine", "enterprise"); } Process { # Parse and decrypt the DPAPI blob $DPAPIBlob = Parse-DPAPIBlob -Data $Data[12..$($data.Length)] # Get the masterkey guid from DPAPI blob $mkGuid = $DPAPIBlob.MasterKeyGuid # Get the correct masterkey $masterKey = $MasterKeys[$mkGuid] if(!$masterKey) { Write-Error "DPAPI masterkey $mkGuid not found!" return $null } # 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 } } } # 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 $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-Content "$keysPath\Preferred" -Encoding Byte $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-Content $fileName -Encoding Byte # Parse the masterkey blob $mk = Parse-MasterkeyBlob -Data $binMasterKey $decKey = Decrypt-MasterkeyBlob -Systemkey $systemKey -Data $mk.DomainKey $retVal = @{$mk.MasterKeyGuid = $decKey} return $retVal } } |