Hunting Sliver C2 with Microsoft Defender XDR and Sentinel: A Practical Guide
When Bishop Fox released Sliver as an open-source C2 framework, it was meant for legitimate red team operations. Fast forward to 2026, and we're seeing Sliver adopted by ransomware operators and APT groups including APT29 (Cozy Bear). The UK NCSC, Microsoft, and multiple DFIR teams have confirmed it: Sliver isn't just a red team exercise anymore. It's a live threat.
From my MDR work, I've learned that C2 detection is the difference between catching an intrusion during initial access and responding to a full-blown ransomware incident. Today I'm sharing practical hunting strategies for Sliver that focus on behaviors, not fragile IOCs.
Why Sliver Is Hard to Detect
Sliver presents unique detection challenges:
- Go-based implants: Static compilation makes traditional AV signature detection difficult
- Multiple C2 protocols: HTTP/HTTPS, DNS, mTLS, WireGuard, and TCP
- Highly configurable: URLs, certificates, ports, and encoders can be changed per campaign
- In-memory execution: Supports process injection and token impersonation
- Custom encoders: Base32/58/64, hex, and even "English words" encoding for DNS
The good news? These same features create detectable behavioral patterns that persist regardless of configuration.
Endpoint Hunting: What Sliver Does on the Host
Detecting Sliver's PowerShell Patterns
Sliver's interactive shell frequently spawns PowerShell with specific UTF-8 encoding arguments:
// Hunt for Sliver's characteristic PowerShell spawning pattern
DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine has_all ("-NoExit", "UTF8")
or ProcessCommandLine has "[System.Text.Encoding]::UTF8"
| extend ParentInfo = strcat(InitiatingProcessFileName, " (", InitiatingProcessFolderPath, ")")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, ParentInfo
| where InitiatingProcessFileName !in~ ("code.exe", "devenv.exe", "WindowsTerminal.exe")
Hunting Process Injection via Notepad
Sliver's default execute-shellcode command injects into notepad.exe. This creates an obvious behavioral anomaly:
// Notepad with network connections = process injection indicator
DeviceNetworkEvents
| where Timestamp > ago(30d)
| where InitiatingProcessFileName =~ "notepad.exe"
| where RemoteIPType != "Loopback"
| join kind=inner (
DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName =~ "notepad.exe"
| project DeviceId, NotepadPID = ProcessId, NotepadParent = InitiatingProcessFileName,
NotepadCmd = ProcessCommandLine, NotepadStart = Timestamp
) on DeviceId
| where InitiatingProcessId == NotepadPID
| project Timestamp, DeviceName, RemoteIP, RemotePort, RemoteUrl,
NotepadParent, NotepadCmd, NotepadStart
| extend Alert = "Notepad.exe making external network connections - possible injection"
Detecting Large Unsigned Go Binaries
Sliver implants are statically compiled Go binaries. They're typically larger than normal Windows executables:
// Hunt for suspicious large unsigned executables making network calls
DeviceFileEvents
| where Timestamp > ago(30d)
| where ActionType == "FileCreated"
| where FileName endswith ".exe"
| where FileSize > 5000000 // > 5MB (Go binaries are chunky)
| where FolderPath !has_any ("\\Program Files", "\\Windows\\", "\\Microsoft")
| join kind=inner (
DeviceProcessEvents
| where Timestamp > ago(30d)
| where ProcessVersionInfoCompanyName == "" or isempty(ProcessVersionInfoCompanyName)
| project DeviceId, SHA256, ProcessStart = Timestamp,
IsNetworkActive = (InitiatingProcessFileName != "")
) on DeviceId, SHA256
| project Timestamp, DeviceName, FileName, FolderPath, FileSize, SHA256, ProcessStart
| summarize FirstSeen = min(Timestamp), Devices = dcount(DeviceName) by FileName, SHA256, FileSize
Network Hunting: Sliver's C2 Fingerprints
DNS Tunneling Detection
Sliver encodes data in DNS queries using Base58/Base32. This creates abnormally long subdomain labels:
// Detect DNS tunneling patterns consistent with Sliver
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where ActionType == "DnsQueryResponse"
| extend Labels = split(RemoteUrl, ".")
| extend FirstLabel = tostring(Labels[0])
| extend FirstLabelLength = strlen(FirstLabel)
| where FirstLabelLength > 40 // Sliver's Base58 creates long labels
| summarize
QueryCount = count(),
UniqueSubdomains = dcount(FirstLabel),
AvgLabelLength = avg(FirstLabelLength),
Devices = dcount(DeviceName)
by ParentDomain = strcat(Labels[-2], ".", Labels[-1]), bin(Timestamp, 1h)
| where UniqueSubdomains > 50 and AvgLabelLength > 35
| extend Alert = "High-entropy DNS subdomain activity - possible Sliver DNS C2"
HTTP C2 Profile Detection
Sliver's default HTTP profile uses specific file extensions for different message types:
// Hunt for Sliver's default HTTP C2 file extension patterns
let sliverExtensions = dynamic([".woff", ".html", ".js", ".php", ".png"]);
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where ActionType == "ConnectionSuccess"
| where RemotePort in (80, 443, 8080, 8443)
| where RemoteUrl has_any (sliverExtensions)
| extend FileExt = extract(@"(\.\w+)(?:\?|$)", 1, RemoteUrl)
| where FileExt in (sliverExtensions)
| summarize
Hits = count(),
Extensions = make_set(FileExt),
UniqueUrls = dcount(RemoteUrl)
by DeviceName, RemoteIP, bin(Timestamp, 1h)
| where array_length(Extensions) >= 3 // Multiple Sliver extensions to same IP
| extend Alert = "HTTP traffic pattern matches Sliver C2 profile"
mTLS/Multiplayer Listener Detection
Sliver's operator port defaults to TCP 31337 with predictable certificate attributes:
// Hunt for Sliver mTLS default port and certificate patterns
DeviceNetworkEvents
| where Timestamp > ago(30d)
| where RemotePort == 31337
| where ActionType == "ConnectionSuccess"
| summarize
ConnectionCount = count(),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp)
by DeviceName, RemoteIP, InitiatingProcessFileName
| where ConnectionCount > 5
| extend Alert = "Multiple connections to port 31337 - possible Sliver mTLS C2"
WireGuard Detection (Unsanctioned Tunnels)
If WireGuard isn't approved in your environment, any WireGuard traffic is suspicious:
// Detect unauthorized WireGuard traffic
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where RemotePort == 51820 // WireGuard default
| where InitiatingProcessFileName !in~ ("wireguard.exe", "wg.exe") // Known legitimate
| project Timestamp, DeviceName, RemoteIP, RemotePort, InitiatingProcessFileName
| extend Alert = "Potential unauthorized WireGuard tunnel - investigate for C2"
Building a Sliver Detection Workbook in Sentinel
Combine these hunts into a Sentinel workbook for continuous monitoring:
// Sliver Composite Hunt - Sentinel Scheduled Rule
let DNSTunneling = DeviceNetworkEvents
| where ActionType == "DnsQueryResponse"
| extend FirstLabelLen = strlen(tostring(split(RemoteUrl, ".")[0]))
| where FirstLabelLen > 40
| summarize DNSScore = count() by DeviceId, DeviceName;
let HTTPPattern = DeviceNetworkEvents
| where RemoteUrl has_any (".woff", ".html", ".js", ".php", ".png")
| summarize HTTPScore = count() by DeviceId, DeviceName;
let ProcessInjection = DeviceNetworkEvents
| where InitiatingProcessFileName =~ "notepad.exe"
| where RemoteIPType != "Loopback"
| summarize InjectionScore = count() by DeviceId, DeviceName;
// Correlate across all signals
DeviceInfo
| join kind=leftouter DNSTunneling on DeviceId
| join kind=leftouter HTTPPattern on DeviceId
| join kind=leftouter ProcessInjection on DeviceId
| extend TotalScore = coalesce(DNSScore, 0) + coalesce(HTTPScore, 0) + (coalesce(InjectionScore, 0) * 10)
| where TotalScore > 20
| project DeviceName, TotalScore, DNSScore, HTTPScore, InjectionScore
| order by TotalScore desc
Response Playbook
When you get a hit:
- Isolate immediately: Use Defender XDR device isolation
- Collect memory: Sliver leaves artifacts in memory (the Go runtime)
- Trace the parent: What spawned the suspicious process?
- Network forensics: Identify the C2 destination before it rotates
- Scope horizontally: Check for lateral movement indicators
Key Takeaways
- Behavior over IOCs: Sliver's configurability makes IOC-based detection fragile
- DNS is your friend: DNS tunneling patterns are hard to mask
- Watch notepad.exe: Network-active notepad is almost always malicious
- Correlate signals: Individual indicators may be weak. Combined, they're strong.
- Port 31337: Still surprisingly common in default Sliver deployments
The gap between red team tools and real-world threats continues to shrink. Sliver is just the latest example. Build these hunts into your detection pipeline now, before you need them in an incident.
Questions about these detections? Reach out on LinkedIn.