Kerberos_utils.ps1
# 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 } } } # Returns RCA for given key and data function Get-RC4{ Param( [Byte[]]$Key, [Byte[]]$Data ) Process { $nk = New-Object byte[] 256 $s = New-Object byte[] 256 for ($i = 0; $i -lt 256; $i++) { $nk[$i] = $Key[($i % $Key.Length)] $s[$i] = [byte]$i } $j = 0 for ($i = 0; $i -lt 256; $i++) { $j = ($j + $s[$i] + $nk[$i]) % 256 $swap = $s[$i] $s[$i] = $s[$j] $s[$j] = $swap } $output = New-Object byte[] ($Data.Length) $i = 0 $j = 0 for ($c = 0; $c -lt $data.Length; $c++) { $i = ($i + 1) % 256 $j = ($j + $s[$i]) % 256 $swap = $s[$i]; $s[$i] = $s[$j]; $s[$j] = $swap; $k = $s[(($s[$i] + $s[$j]) % 256)] $keyed = $data[$c] -bxor $k $output[$c] = [byte]$keyed } return $output } } # 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)) } } # Decrypts kerberos ticket using the given password # Aug 20th 2019 function Decrypt-Kerberos { Param( [Parameter(ParameterSetName='Password',Mandatory=$True)] [String]$Password, [Parameter(ParameterSetName='Key',Mandatory=$True)] [byte[]]$Key, [Parameter(Mandatory=$True)] [byte[]]$Cipher ) Process { if($Key -ne $null) { # This used for the second decrypted block (authenticator) using the session key $k1=$Key [byte[]]$Salt=@(0x0B, 0x00, 0x00, 0x00) } else { # This used for the first decrypted block $k1=Get-MD4 -String $Password -AsByteArray [byte[]]$Salt=@(0x02, 0x00, 0x00, 0x00) } $hmac= [System.Security.Cryptography.HMACMD5]::new($k1) $k2=$hmac.ComputeHash($Salt) $checksum = $Cipher[0..15] $hmac = [System.Security.Cryptography.HMACMD5]::new($k2) $k3=$hmac.ComputeHash($checksum) $cipher = $Cipher[16..$Cipher.Length] [byte[]]$plainText = Get-RC4 -Key $k3 -Data $cipher $actualChecksum = $hmac.ComputeHash($plainText) if((Compare-Object -ReferenceObject $checksum -DifferenceObject $actualChecksum) -ne $null) { Throw "Checksums doesn't match!" } Write-Host "Stuff $($plaintext[0..7] | format-hex))" $output = $plaintext[8..$plainText.Length] return $output } } # Encrypts the kerberos ticket using the given password (=key) function Encrypt-Kerberos { Param( [Parameter(ParameterSetName='Password',Mandatory=$True)] [String]$Password, [Parameter(ParameterSetName='Key',Mandatory=$True)] [byte[]]$Key, [Parameter(Mandatory=$True)] [byte[]]$Data ) Process { # Create some random bytes $stuff = (New-Guid).ToByteArray()[0..7] if($Key -ne $null) { $k1=$Key [byte[]]$Salt=@(0x0B, 0x00, 0x00, 0x00) } else { $k1=Get-MD4 -String $Password -AsByteArray [byte[]]$Salt=@(0x02, 0x00, 0x00, 0x00) } $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[]]$cipher = Get-RC4 -Key $k3 -Data $plaintext return [byte[]]$checksum+$cipher } } # 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-DERDate { Param( [Parameter(Mandatory=$True)] [DateTime]$Date ) Process { return Add-DERUtf8String -Text $Date.ToUniversalTime().ToString("yyyyMMddHHmmssZ") -Tag 0x18 } } # 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", [ValidateSet('graph_api','aadsync','pta','teams','office','exo','sara','office_mgmt','onedrive')] [String]$ClientId="graph_api" ) 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=@" <?xml version='1.0' encoding='UTF-8'?> <s:Envelope xmlns:s='http://www.w3.org/2003/05/soap-envelope' xmlns:wsse='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' xmlns:saml='urn:oasis:names:tc:SAML:1.0:assertion' xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy' xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' xmlns:wsa='http://www.w3.org/2005/08/addressing' xmlns:wssc='http://schemas.xmlsoap.org/ws/2005/02/sc' xmlns:wst='http://schemas.xmlsoap.org/ws/2005/02/trust'> <s:Header> <wsa:Action s:mustUnderstand='1'>http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</wsa:Action> <wsa:To s:mustUnderstand='1'>https://autologon.microsoftazuread-sso.com/$domain/winauth/trust/2005/windowstransport?client-request-id=$requestId</wsa:To> <wsa:MessageID>urn:uuid:$((New-Guid).ToString())</wsa:MessageID> </s:Header> <s:Body> <wst:RequestSecurityToken Id='RST0'> <wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType> <wsp:AppliesTo> <wsa:EndpointReference> <wsa:Address>urn:federation:MicrosoftOnline</wsa:Address> </wsa:EndpointReference> </wsp:AppliesTo> <wst:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</wst:KeyType> </wst:RequestSecurityToken> </s:Body> </s:Envelope> "@ $headers = @{ "SOAPAction"="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" "Authorization" = "Negotiate $KerberosTicket" } try { $response = Invoke-WebRequest -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="<saml:Assertion xmlns:saml=`"urn:oasis:names:tc:SAML:1.0:assertion`"><DesktopSsoToken>$dssoToken</DesktopSsoToken></saml:Assertion>" $B64samlAssertion=[convert]::ToBase64String([text.encoding]::UTF8.GetBytes($samlAssertion)) $body=@{ "grant_type" = "urn:ietf:params:oauth:grant-type:saml1_1-bearer" "assertion" = $B64samlAssertion "client_id" = $client_ids[$ClientId] "resource" = $Resource "tbidv2" = "" # Optional, see https://tools.ietf.org/html/draft-ietf-tokbind-protocol-19 "scope" = "openid" "redirect_uri" = "urn:ietf:wg:oauth:2.0:oob" # 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 -Uri "https://login.microsoftonline.com/common/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 # Save the tokens to cache $Script:tokens[$Resource]=$response # Return $accessToken = $token.access_token $refreshToken = $token.refresh_token return $accessToken } } # 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 } } # 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 } } } |