# Gets sids from AD or AAD
function Get-Sids{
Param(
[Parameter(Mandatory=$False)]
[String]$AccessToken,
[Parameter(Mandatory=$False)]
[String]$SearchString,
[Parameter(Mandatory=$False)]
[String]$UserPrincipalName
)
Process
{
# If we got Access Token, search the Azure AD
if(![String]::IsNullOrEmpty($AccessToken))
{
# Get all Azure AD users, filtered users, or just one with userPrincipalName
$AADUsers = Get-AADUsers -AccessToken $AccessToken -SearchString $SearchString -UserPrincipalName $UserPrincipalName
$output=@()
if($AADUsers -ne $null)
{
foreach($AADUser in $AADUsers)
{
$properties=@{}
$properties.UserPrincipalName = $AADUser.UserPrincipalName
$properties.Sid = $AADUser.onPremisesSecurityIdentifier
$properties.FullName = $AADUser.displayName
$output+=New-Object PSObject -Property $properties
}
}
else
{
Write-Error "$UserPrincipalName not found!"
return
}
$output
}
else
{
# Make the filter if search string is given
if(![string]::IsNullOrEmpty($SearchString))
{
$Filter = "Name LIKE '$SearchString%' or Fullname LIKE '$SearchString%'"
}
# If userprincipalname is given, we can't find user with Get-WmiObject so we need to use the DirectorySearcher
elseif(![string]::IsNullOrEmpty($UserPrincipalName))
{
$ADSearch = New-Object System.DirectoryServices.DirectorySearcher
$ADSearch.Filter="UserPrincipalName=$UserPrincipalName"
$AADUser=$ADSearch.FindOne()
if($AADUser -eq $null)
{
Write-Error "$UserPrincipalName not found!"
return
}
$bSID=$AADUser.Properties.objectsid[0]
$SID=(New-Object System.Security.Principal.SecurityIdentifier($bSID,0)).Value
$properties=@{}
$properties.UserPrincipalName = $UserPrincipalName
$properties.Sid = $Sid
$properties.FullName = $AADUser.Properties.displayname[0]
return New-Object PSObject -Property $properties
}
# Query the AD using Get-WmiObject so we don't have to parse each object as with DirectorySearcher
Get-WmiObject win32_useraccount -Filter $Filter | Where-Object Disabled -eq $false | Select-Object domain,name,fullname,sid
}
}
}
# Converts bytearray to datetime
# Aug 31st 2019
function DateBytes2Date
{
Param(
[Parameter(Mandatory=$True)]
[Byte[]]$DateBytes
)
Process
{
return [datetime]::FromFileTimeUtc([System.BitConverter]::ToInt64($DateBytes,0))
}
}
# Converts datetime to bytearray
# Aug 31st 2019
function Date2DateBytes
{
Param(
[Parameter(Mandatory=$True)]
[DateTime]$Date
)
Process
{
return [System.BitConverter]::GetBytes($Date.ToFileTimeUtc())
}
}
# Converts "20190829080935Z" type of strings to datetime
# Aug 31st 2019
function DateString2Date
{
Param(
[Parameter(Mandatory=$True)]
[String]$DateString
)
Process
{
$y = $DateString.Substring(0,4)
$m = $DateString.Substring(4,2)
$d = $DateString.Substring(6,2)
$h = $DateString.Substring(8,2)
$mm = $DateString.Substring(10,2)
$s = $DateString.Substring(12,2)
$DateString = "$y-$m-$d $h`:$mm`:$s`Z"
#return Get-Date -Year $y -Month $m -Day $d -Hour $h -Minute $mm -Second $s -Millisecond 0
return Get-Date -Date $DateString
}
}
# Converts datetime to "20190829080935Z" type of string
# Aug 31st 2019
function Date2DateString
{
Param(
[Parameter(Mandatory=$True)]
[DateTime]$Date
)
Process
{
return $Date.ToUniversalTime().ToString("yyyyMMddHHmmssZ")
}
}
# Converts datetime to "20190829080935Z" type of string to bytes
# Aug 31st 2019
function Date2DateStringBytes
{
Param(
[Parameter(Mandatory=$True)]
[DateTime]$Date
)
Process
{
return [text.encoding]::ASCII.GetBytes((Date2DAteString $Date))
}
}
# Encrypts the kerberos ticket using the given password (=key)
function Encrypt-Kerberos
{
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Key,
[Parameter(Mandatory=$True)]
[byte[]]$Data,
[Parameter(Mandatory=$False)]
[byte[]]$Salt,
[Parameter(Mandatory=$False)]
[ValidateSet('Ticket','Authenticator','APRepPart','EncKrbPrivPart')]
[String]$Type="Ticket",
[Parameter(Mandatory=$False)]
[ValidateSet('RC4','AES')]
[String]$Crypto="RC4"
)
Process
{
if($Crypto -eq "RC4")
{
if(!$Salt)
{
if($Type -eq "Ticket")
{
[byte[]]$Salt=@(0x02, 0x00, 0x00, 0x00)
}
elseif($Type -eq "Authenticator")
{
[byte[]]$Salt=@(0x0B, 0x00, 0x00, 0x00)
}
elseif($Type -eq "APRepPart")
{
[byte[]]$Salt=@(0x0C, 0x00, 0x00, 0x00)
}
elseif($Type -eq "EncKrbPrivPart")
{
[byte[]]$Salt=@(0x0D, 0x00, 0x00, 0x00)
}
else
{
Throw "Unsupported decryption type"
}
}
$k1=$Key
# Confounder (8 bytes)
$stuff = Get-RandomBytes -Bytes 8
$hmac= [System.Security.Cryptography.HMACMD5]::new($k1)
$k2=$hmac.ComputeHash($Salt) # Salt
[byte[]]$plainText = $stuff + $Data
$hmac = [System.Security.Cryptography.HMACMD5]::new($k2)
$checksum = $hmac.ComputeHash($plainText)
$k3=$hmac.ComputeHash($checksum)
[byte[]]$cipherText = Get-RC4 -Key $k3 -Data $plaintext
[byte[]]$cipherText = $checksum + $cipherText
}
else
{
$Ke = DK -Key $Key -Usage Ticket -KeyDerivationMode Ke
# Confounder (16 bytes)
$stuff = Get-RandomBytes -Bytes 16
[byte[]]$plainText = $stuff + $Data
[byte[]]$cipherText = AES_CTS_Encrypt -PlainText $plainText -Key $Ke
# Calculate checksum
$ki = DK -Key $Key -Usage Ticket -KeyDerivationMode Ki
$hmacsha1 = [System.Security.Cryptography.HMACSHA1]::new($ki)
$checksum = ($hmacsha1.ComputeHash($plaintext))[0..11]
[byte[]]$cipherText = $cipherText+$checksum
}
return $cipherText
}
}
# Decrypts Kerberos data
# Mar 26th 2021
function Decrypt-Kerberos
{
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Key,
[Parameter(Mandatory=$True)]
[byte[]]$Data,
[Parameter(Mandatory=$False)]
[byte[]]$Salt,
[Parameter(Mandatory=$False)]
[ValidateSet('Ticket','Authenticator','APRepPart','EncKrbPrivPart')]
[String]$Type="Ticket",
[Parameter(Mandatory=$False)]
[ValidateSet('RC4','AES')]
[String]$Crypto="RC4"
)
Process
{
if($Crypto -eq "RC4")
{
if(!$Salt)
{
if($Type -eq "Ticket")
{
[byte[]]$Salt=@(0x02, 0x00, 0x00, 0x00)
}
elseif($Type -eq "Authenticator")
{
[byte[]]$Salt=@(0x0B, 0x00, 0x00, 0x00)
}
elseif($Type -eq "APRepPart")
{
[byte[]]$Salt=@(0x0C, 0x00, 0x00, 0x00)
}
elseif($Type -eq "EncKrbPrivPart")
{
[byte[]]$Salt=@(0x0D, 0x00, 0x00, 0x00)
}
else
{
Throw "Unsupported decryption type"
}
}
$k1=$Key
$hmac= [System.Security.Cryptography.HMACMD5]::new($k1)
$k2=$hmac.ComputeHash($Salt) # Salt
[byte[]]$cipher = $Data[16..$($Data.Count-1)]
$hmac = [System.Security.Cryptography.HMACMD5]::new($k2)
$checksum = $Data[0..15]
$k3=$hmac.ComputeHash($checksum)
[byte[]]$plainText = Get-RC4 -Key $k3 -Data $cipher
$compare = $hmac.ComputeHash($plainText)
$plainText = [byte[]]$plainText[8..$($plainText.Count-1)]
}
else
{
$Ke = DK -Key $Key -Usage Ticket -KeyDerivationMode Ke
[byte[]]$plainText = AES_CTS_Decrypt -CipherText $Data[0..$($Data.Count - 13)] -Key $Ke
# Calculate checksum
$ki = DK -Key $Key -Usage Ticket -KeyDerivationMode Ki
$hmacsha1 = [System.Security.Cryptography.HMACSHA1]::new($ki)
$compare = ($hmacsha1.ComputeHash($plainText))[0..11]
$checksum = $Data[$($plainText.Count)..$($plainText.Count+12)]
# Strip the confounder
$plainText = $plainText[16..($plainText.Count)]
}
if(@(Compare-Object $checksum $compare -SyncWindow 0).Length -gt 0)
{
Write-Warning "Checksum mismatch"
}
return $plainText
}
}
# Get DER Length Bytes
function Get-DERLengthBytes
{
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Data
)
Process
{
$length = $Data.Length
if($length -lt 128)
{
return $length
}
elseif($length -lt 256)
{
# We return 1000 0010 = multibyte (1000), one bytes (0001)
return @(0x81, $length)
}
else
{
$secondByte = $length % 256
$firstByte = ($length - $secondByte)/256
# We return 1000 0010 = multibyte (1000), two bytes (0010)
return @(0x82, $firstByte, $secondByte)
}
}
}
# Returns given der tag, length bytes, and the given data
function Add-DERTag
{
Param(
[Parameter(Mandatory=$True)]
[byte]$Tag,
[Parameter(Mandatory=$True)]
[byte[]]$Data
)
Process
{
$output = @($Tag)
$output += Get-DERLengthBytes($Data)
$output += $Data
return $output
}
}
function Add-DERSet
{
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Data
)
Process
{
$output = @(0x31)
$output += Get-DERLengthBytes($Data)
$output += $Data
return $output
}
}
function Add-DERSequence
{
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Data
)
Process
{
$output = @(0x30)
$output += Get-DERLengthBytes($Data)
$output += $Data
return $output
}
}
function Add-DERUnicodeString
{
Param(
[Parameter(Mandatory=$True)]
[String]$Text,
[byte]$Tag=0x04,
[switch]$LE
)
Process
{
$data = [system.text.encoding]::Unicode.GetBytes($Text)
# swap the bytes (little-endian)
if($LE)
{
for($a = 0; $a -lt $data.Length ; $a+=2)
{
$t=$data[$a]
$data[$a]=$data[$a+1]
$data[$a+1]=$t
}
}
$output = @($Tag)
$output += Get-DERLengthBytes($data)
$output += $data
return $output
}
}
function Add-DERUtf8String
{
Param(
[Parameter(Mandatory=$True)]
[String]$Text,
[byte]$Tag=0x1B
)
Process
{
$data = [system.text.encoding]::UTF8.GetBytes($Text)
$output = @($Tag)
$output += Get-DERLengthBytes($data)
$output += $data
return $output
}
}
function Add-DERIA5String
{
Param(
[Parameter(Mandatory=$True)]
[String]$Text,
[byte]$Tag=0x16
)
Process
{
$data = [system.text.encoding]::ASCII.GetBytes($Text)
$output = @($Tag)
$output += Get-DERLengthBytes($data)
$output += $data
return $output
}
}
function Add-DERInteger
{
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Data
)
Process
{
return Add-DERTag -Tag 0x02 -Data $Data
}
}
function Add-DERBitString
{
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Data
)
Process
{
return Add-DERTag -Tag 0x03 -Data $Data
}
}
function Add-DEROctetString
{
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Data
)
Process
{
return Add-DERTag -Tag 0x04 -Data $Data
}
}
function Add-DERObjectIdentifier
{
Param(
[Parameter(Mandatory=$True)]
[String]$ObjectIdentifier
)
Process
{
return Add-DERTag -Tag 0x06 -Data (Convert-OidToBytes -Oid $ObjectIdentifier)
}
}
function Add-DERNull
{
Process
{
return 0x05
}
}
function Add-DERDate
{
Param(
[Parameter(Mandatory=$True)]
[DateTime]$Date
)
Process
{
return Add-DERUtf8String -Text $Date.ToUniversalTime().ToString("yyyyMMddHHmmssZ") -Tag 0x18
}
}
function Add-DERBoolean
{
Param(
[Parameter(Mandatory=$True)]
[bool]$Value
)
Process
{
if($Value)
{
return @(0x01, 0xFF)
}
else
{
return @(0x01, 0x00)
}
}
}
# Gets an accesstoken using kerberos ticket
# Aug 25th 2019
function Get-AccessTokenWithKerberosTicket
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[String]$KerberosTicket,
[Parameter(Mandatory=$True)]
[String]$Domain,
[Parameter(Mandatory=$False)]
[String]$Resource="https://graph.windows.net",
[Parameter(Mandatory=$False)]
[String]$ClientId="1b730954-1685-4b74-9bfd-dac224a7b894",
[Parameter(Mandatory=$False)]
[String]$Tenant="common"
)
Process
{
$requestId = (New-Guid).ToString()
# Step 1: Get desktop sso token using kerberos ticket
$url="https://autologon.microsoftazuread-sso.com/$domain/winauth/trust/2005/windowstransport?client-request-id=$requestId"
$body=@"
http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue
https://autologon.microsoftazuread-sso.com/$domain/winauth/trust/2005/windowstransport?client-request-id=$requestId
urn:uuid:$((New-Guid).ToString())
http://schemas.xmlsoap.org/ws/2005/02/trust/Issue
urn:federation:MicrosoftOnline
http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey
"@
$headers = @{
"SOAPAction"="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue"
"Authorization" = "Negotiate $KerberosTicket"
}
try
{
$response = Invoke-WebRequest -UseBasicParsing -Uri $url -Method Post -Headers $headers -Body $body
}
catch
{
Write-Error $_.Exception
return
}
[xml]$message = $response
$dssoToken = $message.Envelope.Body.RequestSecurityTokenResponse.RequestedSecurityToken.Assertion.DesktopSsoToken
# Step 2: get the access token using dssoToken
$samlAssertion="$dssoToken"
$B64samlAssertion=[convert]::ToBase64String([text.encoding]::UTF8.GetBytes($samlAssertion))
$body=@{
"grant_type" = "urn:ietf:params:oauth:grant-type:saml1_1-bearer"
"assertion" = $B64samlAssertion
"client_id" = $ClientId
"resource" = $Resource
"tbidv2" = "" # Optional, see https://tools.ietf.org/html/draft-ietf-tokbind-protocol-19
"scope" = "openid"
"redirect_uri" = Get-AuthRedirectUrl -ClientId $ClientId -Resource $Resource # Originally: "ms-appx-web://Microsoft.AAD.BrokerPlugin/$clientId"
"win_ver" = "10.0.17763.529"
"windows_api_version" = "2.0"
"msafed" = "0"
}
try
{
$response = Invoke-WebRequest -UseBasicParsing -Uri "https://login.microsoftonline.com/$Tenant/oauth2/token" -Method Post -Body $body
}
catch
{
if(![String]::IsNullOrEmpty($_.ErrorDetails.Message))
{
$error = $_.ErrorDetails.Message.ToString() | ConvertFrom-Json
Write-Error $error.error_description
return
}
else
{
Write-Error $_.exception
return
}
}
$token = $response.content | ConvertFrom-Json
# Return
return $token
}
}
# Calculate the server checksum of PAC
function Get-ServerSignature
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Key,
[Parameter(Mandatory=$True)]
[byte[]]$Data
)
Process
{
$hmacmd5 = [System.Security.Cryptography.HMACMD5]::new([byte[]]$key)
$ksign = $hmacmd5.ComputeHash([system.text.Encoding]::ASCII.GetBytes("signaturekey")+@(0x00))
$md5 = [System.Security.Cryptography.MD5]::Create()
$tmp = $md5.ComputeHash(@(0x11, 0x00 , 0x00, 0x00)+$Data)
$hmacmd5 = [System.Security.Cryptography.HMACMD5]::new([byte[]]$ksign)
$signature = $hmacmd5.ComputeHash($tmp)
return $signature
}
}
function Get-NFold
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Data,
[Parameter(Mandatory=$True)]
[int]$Size
)
Process
{
$inBytesSize = $Data.Length
$outBytesSize = $size
$a = $outBytesSize
$b = $inBytesSize
while ($b -ne 0)
{
$c = $b
$b = $a % $b
$a = $c
}
$lcm = ($outBytesSize * $inBytesSize) / $a
$outBytes = New-Object byte[] $outBytesSize
$tmpByte = 0
for ($i = $lcm - 1; $i -ge 0; $i--)
{
$msbit = (($inBytesSize -shl 3) - 1)
$msbit += ((($inBytesSize -shl 3) + 13) * ([math]::Truncate($i / $inBytesSize)))
$msbit += (($inBytesSize - ($i % $inBytesSize)) -shl 3)
$msbit %= $inBytesSize -shl 3
$rst = $Data[($inBytesSize - 1 - ($msbit -shr 3)) % $inBytesSize] -band 0xff
$rst2 = $Data[($inBytesSize - ($msbit -shr 3)) % $inBytesSize] -band 0xff
$msbit = ((($rst -shl 8) -bor ($rst2)) -shr (($msbit -band 7) + 1)) -band 0xff
$tmpByte += $msbit
$msbit = $outBytes[$i % $outBytesSize] -band 0xff
$tmpByte += $msbit
$outBytes[$i % $outBytesSize] = [byte]($tmpByte -band 0xff)
$tmpByte = $tmpByte -shr 8
}
if ($tmpByte -ne 0)
{
for ($i = $outBytesSize - 1; $i -ge 0; $i--)
{
$tmpByte += $outBytes[$i] -band 0xff
$outBytes[$i] = [byte]($tmpByte -band 0xff)
$tmpByte = $tmpByte -shr 8
}
}
return $outBytes
}
}
# Encrypts/Decrypts the given plaintext using the given key
# Mar 29th 2021
function AES_CTS_Transform
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Data,
[Parameter(Mandatory=$True)]
[byte[]]$Key,
[Parameter(Mandatory=$False)]
[byte[]]$InitialVector = [byte[]](0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0),
[Parameter(Mandatory=$True)]
[ValidateSet('Encrypt','Decrypt')]
[String]$Mode
)
Process
{
[System.Security.Cryptography.Aes]$AES = [System.Security.Cryptography.Aes]::Create()
$AES.Padding = "None"
$AES.Mode = "CBC"
if($Mode -eq 'Encrypt')
{
$transformer = $AES.CreateEncryptor($Key,$InitialVector)
}
else
{
$transformer = $AES.CreateDecryptor($Key,$InitialVector)
}
# Create a memory stream and write the cipher text to it through CryptoStream
$ms = New-Object System.IO.MemoryStream
$cs = New-Object System.Security.Cryptography.CryptoStream($ms,$transformer,[System.Security.Cryptography.CryptoStreamMode]::Write)
$cs.Write($data,0,$data.Count)
$cs.Close()
$cs.Dispose()
$transformedData = $ms.ToArray()
$ms.Close()
$ms.Dispose()
return $transformedData
}
}
# Encrypts the given plaintext using the given key
# Mar 29th 2021
function AES_CTS_Encrypt
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[byte[]]$PlainText,
[Parameter(Mandatory=$True)]
[byte[]]$Key,
[Parameter(Mandatory=$False)]
[byte[]]$InitialVector = [byte[]](0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0)
)
Process
{
$PadSize = 16 - ($PlainText.Count % 16)
if($PlainText.Count -lt 16)
{
return $PlainText
}
if($PadSize -eq 16)
{
if($PlainText.Count -gt 16)
{
$InitialVector = [byte[]](0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0)
}
$data = $PlainText
}
else
{
$data = [byte[]]@($PlainText + (New-Object byte[] $PadSize))
}
$encData = AES_CTS_Transform -Data $data -Key $Key -InitialVector $InitialVector -Mode Encrypt
if($PlainText.Count -ge 32)
{
$encData = SwapLastTwoBlocks -Data $encData
}
$result = New-Object byte[] $PlainText.Count
[Array]::Copy($encData, 0, $result, 0, $PlainText.Count)
return $result
}
}
# Decrypts the given plaintext using the given key
# Mar 29th 2021
function AES_CTS_Decrypt
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[byte[]]$CipherText,
[Parameter(Mandatory=$True)]
[byte[]]$Key,
[Parameter(Mandatory=$False)]
[byte[]]$InitialVector = [byte[]](0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0)
)
Process
{
$PadSize = 16 - ($CipherText.Count % 16)
if($CipherText.Count -lt 16)
{
return $CipherText
}
if($PadSize -eq 16)
{
$data = $CipherText
if($data.Count -ge 32)
{
$data = SwapLastTwoBlocks -Data $data
}
if($data.Count -gt 16)
{
$InitialVector = [byte[]](0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0)
}
$decData = AES_CTS_Transform -Data $data -Key $Key -InitialVector $InitialVector -Mode Decrypt
return $decData
}
else
{
$depadded = New-Object byte[] 16
[Array]::Copy($CipherText,($CipherText.Count - 32 + $PadSize),$depadded,0,16)
[byte[]]$dn = AES_CTS_Transform -Data $depadded -Key $Key -InitialVector $InitialVector -Mode Decrypt
$data = New-Object byte[] ($CipherText.Count + $PadSize)
[Array]::Copy($CipherText,0,$data,0,$CipherText.Count)
[Array]::Copy($dn,($dn.Count - $PadSize),$data,$CipherText.Count,$PadSize)
$data = SwapLastTwoBlocks -Data $data
[byte[]]$decData = AES_CTS_Transform -Data $data -Key $Key -InitialVector $InitialVector -Mode Decrypt
$result = New-Object byte[] $CipherText.Count
[Array]::Copy($decData,0,$result,0,$CipherText.Count)
return $result
$data = [byte[]]@($PlainText + (New-Object byte[] $PadSize))
}
[System.Security.Cryptography.Aes]$AES = [System.Security.Cryptography.Aes]::Create()
$AES.Padding = "None"
$AES.Mode = "CBC"
$encryptor = $AES.CreateEncryptor($Key,$InitialVector)
# Create a memory stream and write the cipher text to it through CryptoStream
$ms = New-Object System.IO.MemoryStream
$cs = New-Object System.Security.Cryptography.CryptoStream($ms,$encryptor,[System.Security.Cryptography.CryptoStreamMode]::Write)
$cs.Write($data,0,$data.Count)
$cs.Close()
$cs.Dispose()
$encData = $ms.ToArray()
$ms.Close()
$ms.Dispose()
if($PlainText.Count -ge 32)
{
$PlainText = SwapLastTwoBlocks -Data $PlainText
}
$result = New-Object byte[] $PlainText.Count
[Array]::Copy($encData, 0, $result, 0, $PlainText.Count)
return $result
}
}
# Swaps last two blocks of the given data.
function SwapLastTwoBlocks
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Data
)
Process
{
for($i = 0 ; $i -lt 16; $i++)
{
[byte]$temp = $data[$i+$data.Count-32]
$data[$i + $Data.Count -32] = $data[$i + $Data.Count -16]
$data[$i + $Data.Count -16] = $temp
}
return $Data
}
}
# Random-octect generation function
# https://tools.ietf.org/html/rfc3961
# Mar 28th 021
function DR
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Key,
[Parameter(Mandatory=$True)]
[byte[]]$Constant,
[Parameter(Mandatory=$False)]
[int]$KeySize = 32,
[Parameter(Mandatory=$False)]
[int]$BlockSize = 16
)
Process
{
if($KeySize -ne $Key.Count)
{
Throw "Invalid key size ($($Key.count) bytes), expected $KeySize bytes)"
}
$keyBytes = New-Object byte[] $key.Count
if($Constant.Count -ne $BlockSize)
{
$Ki = Get-NFold -Data $Constant -Size $BlockSize
}
else
{
$ki = $Constant
}
$n = 0
do
{
$ki = AES_CTS_Encrypt -PlainText $ki -Key $Key
if(($n + $BlockSize) -ge $KeySize)
{
[Array]::Copy($Ki,0,$KeyBytes,$n,$KeySize-$n)
break
}
[Array]::Copy($Ki,0,$KeyBytes,$n,$BlockSize)
$n += $BlockSize
}while($n -lt $KeySize)
return $keyBytes
}
}
# Key derivation function
# https://tools.ietf.org/html/rfc3961
# Mar 28th 021
function DK
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Key,
[Parameter(ParameterSetName='KDF',Mandatory=$True)]
[ValidateSet('Unknown','PaEncTs','Ticket','EncAsRepPart','TgsReqAuthDataSessionKey','TgsReqAuthDataSubSessionKey','PaTgsReqChecksum','PaTgsReqAuthenticator','EncTgsRepPartSessionKey','EncTgsRepPartSubSessionKey','AuthenticatorChecksum','ApReqAuthenticator','EncApRepPart','EncKrbPrivPart','EncKrbCredPart','KrbSafeChecksum','OtherEncrypted','PaForUserChecksum','KrbError','AdKdcIssuedChecksum','MandatoryTicketExtension','AuthDataTicketExtension','Seal','Sign','Sequence','AcceptorSeal','AcceptorSign','InitiatorSeal','InitiatorSign','PaServerReferralData','SamChecksum','SamEncTrackId','PaServerReferral','SamEncNonceSad','PaPkInitEx','AsReq','FastReqChecksum','FastEnc','FastRep','FastFinished','EncChallengeClient','EncChallengeKdc','DigestEncrypt','DigestOpaque','Krb5SignedPath','CanonicalizedPath','HslCookie')]
[string]$Usage = "Ticket",
[Parameter(ParameterSetName='KDF',Mandatory=$True)]
[ValidateSet('Kc','Ke','Ki')]
[string]$KeyDerivationMode = 'Ke',
[Parameter(ParameterSetName='Constant',Mandatory=$True)]
[byte[]]$Constant
)
Begin
{
$KeyDerivationModes = @{
'Kc' = 0x99
'Ke' = 0xAA
'Ki' = 0x55
}
$KeyUsages = @{
Unknown = 0
PaEncTs = 1
Ticket = 2
EncAsRepPart = 3
TgsReqAuthDataSessionKey = 4
TgsReqAuthDataSubSessionKey = 5
PaTgsReqChecksum = 6
PaTgsReqAuthenticator = 7
EncTgsRepPartSessionKey = 8
EncTgsRepPartSubSessionKey = 9
AuthenticatorChecksum = 10
ApReqAuthenticator = 11
EncApRepPart = 12
EncKrbPrivPart = 13
EncKrbCredPart = 14
KrbSafeChecksum = 15
OtherEncrypted = 16
PaForUserChecksum = 17
KrbError = 18
AdKdcIssuedChecksum = 19
MandatoryTicketExtension = 20
AuthDataTicketExtension = 21
Seal = 22
Sign = 23
Sequence = 24
AcceptorSeal = 22
AcceptorSign = 23
InitiatorSeal = 24
InitiatorSign = 25
PaServerReferralData = 22
SamChecksum = 25
SamEncTrackId = 26
PaServerReferral = 26
SamEncNonceSad = 27
PaPkInitEx = 44
AsReq = 56
FastReqChecksum = 50
FastEnc = 51
FastRep = 52
FastFinished = 53
EncChallengeClient = 54
EncChallengeKdc = 55
DigestEncrypt = -18
DigestOpaque = -19
Krb5SignedPath = -21
CanonicalizedPath = -23
HslCookie = -25
}
}
Process
{
if($Constant)
{
# Generate a key
return DR -Key $Key -KeySize ($Key.Count) -Constant $Constant
}
else
{
# Generate a constant from Key Usage and Key Derivation Method
$Constant = New-Object byte[] 5
$bytes = [System.BitConverter]::GetBytes([int32]$KeyUsages[$Usage])
[array]::Reverse($bytes)
[Array]::Copy($bytes,$Constant,4)
$constant[4] = [byte]$KeyDerivationModes[$KeyDerivationMode]
return DK -Key $Key -Constant $constant
}
}
}
# Aligns sizes in PAC
Function Align-Size
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[int]$Mask,
[Parameter(Mandatory=$True)]
[int]$Size
)
Process
{
$diff = $Size % $Mask
if($diff -ne 0)
{
$Size += 8 - $diff
}
return $Size
}
}
# Returns null-bytes used to align data fields in PAC
Function Get-AlignBytes
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[int]$Mask,
[Parameter(Mandatory=$True)]
[int]$Size
)
Process
{
$diff = $Size % $Mask
if($diff -ne 0)
{
return New-Object byte[] (8-$diff)
}
else
{
return
}
}
}
# Generates Kerberos encryption key
# Apr 1st 2021
function New-KerberosKey
{
Param(
[Parameter(Mandatory=$False)]
[String]$Password,
[Parameter(Mandatory=$False)]
[String]$Hash,
[Parameter(Mandatory=$False)]
[ValidateSet('RC4','AES')]
[String]$Crypto="RC4"
)
Process
{
if([string]::IsNullOrEmpty($Password) -and [string]::IsNullOrEmpty($Hash))
{
Throw "Unable to create Kerberos encryption key. Either Password or Hash must be provided"
}
if($Crypto -eq "RC4")
{
if([string]::IsNullOrEmpty($Password))
{
$key = Convert-HexToByteArray -HexString $Hash
}
else
{
$key = Get-MD4 -String $Password -AsByteArray
}
}
elseif($Crypto -eq "AES")
{
if([string]::IsNullOrEmpty($Password))
{
$Key = Convert-HexToByteArray -HexString $Hash
}
else
{
$iterations = 4096
$pwdBytes = [text.encoding]::UTF8.GetBytes($Password)
$pbkdf2 = New-Object System.Security.Cryptography.Rfc2898DeriveBytes([byte[]]$pwdBytes,$Salt,$iterations)
$random = $pbkdf2.GetBytes(32)
$Key = DK -Key $random -Constant ([text.encoding]::UTF8.GetBytes("kerberos"))
}
}
else
{
Throw "Unsupported crypto: $Crypto"
}
return $Key
}
}