
From the hunt desk. Three engagements in the last 12 months have ended the same way. No binary on disk. No memory implant. No outbound HTTPS to a sketchy IP. The attacker’s entire campaign — from initial access through privilege escalation through data exfiltration — happened through legitimate AWS API calls made with stolen credentials, plus a single SSM Session Manager session that left no SSH log, no shell history, no agent footprint. EDR was silent. Network detection saw nothing unusual because there was nothing unusual at the network layer. The only telemetry that captured the campaign was the combination of CloudTrail and VPC Flow Logs joined at the time-window level. Neither alone told the story. Together, they did.
This post is the playbook for hunting Living-off-the-Cloud (LotC) attack chains — malware-free intrusions where the entire kill chain runs through legitimate cloud-provider services. We cover the seven LotC primitives every defender should be hunting in 2026, the fusion-correlation technique that joins CloudTrail to VPC Flow Logs without producing a Cartesian-product alert flood, and the chain-detection layer that surfaces the multi-stage sequence rather than the individual API calls.
This is post #12 — the capstone — of our VPC Flow Log detection-engineering series. It pulls signal from every prior post and adds CloudTrail as the second axis. The full series:
- #1 Adaptive C2 beaconing (FFT + DBSCAN)
- #2 Lateral movement graph detection
- #3 Low-and-slow exfiltration
- #4 Botnet coordination
- #5 Living-off-the-land Markov
- #6 DGA + DNS tunnel hunting
- #7 TLS fingerprinting
- #8 Cryptojacking
- #9 Tor egress
- #10 Kubernetes east-west
- #11 Insider UEBA
What “Living-off-the-Cloud” Actually Means
Living-off-the-land (LOTL) is the on-prem term — adversaries abusing PowerShell, WMI, certutil, and other built-in tools to evade EDR. Living-off-the-Cloud is the cloud analogue: adversaries abusing legitimate AWS services to perform every kill-chain step without dropping a binary or executing code on a compromised endpoint. The primitives are:
- SSM Session Manager as remote shell. Operator obtains AWS credentials with
ssm:StartSession, opens an interactive shell into an EC2 instance via the SSM agent. No SSH key. No bastion. No network ingress required because the SSM agent reaches out from the instance. - SSM Run Command as remote code execution. Run shell commands or PowerShell on hundreds of instances simultaneously via
ssm:SendCommand. Stealth alternative to login. - IMDS for credential pivoting. Any compromise of an EC2 instance grants access to the instance metadata service (IMDS) credentials. Those credentials inherit the instance’s IAM role permissions.
- Step Functions / Lambda as orchestrator. Operator-controlled Step Functions state machine runs the kill chain serverlessly. No infrastructure to attribute. Lambda functions execute the actual actions.
- SQS / SNS / EventBridge as C2 channel. An attacker-controlled Lambda subscribes to an SQS queue. Commands are sent via SQS message. Replies come back via another SQS queue. The “C2 traffic” looks exactly like inter-service AWS calls.
- S3 / EBS snapshots for staging and exfiltration. Stage data into an S3 bucket the operator controls (via cross-account bucket policy). Exfiltrate by sharing an EBS snapshot to an attacker-controlled account.
- Bedrock / SageMaker for cover compute. Run code in Bedrock Agents or SageMaker notebooks to execute exfiltration logic. Looks like data-science workload. (See the Bedrock threat hunting playbook for the AI-specific cases.)
None of these involve a malware binary. None of them produce network signals that look unusual in isolation. All of them produce CloudTrail events that look like routine cloud operations. The detection lives in the sequence and the correlation.
The Fusion Pipeline

- Ingest CloudTrail + VPC Flow Logs into a single Athena workgroup. Both in S3, both partitioned by day, both schema-on-read. CloudTrail in JSON, VPC Flow Logs in Parquet. This is just plumbing but it is the foundation everything else builds on.
- Time-window correlation. Join CloudTrail events to VPC Flow Logs within a configurable time window (default: 60 seconds). For each interesting CloudTrail event (StartSession, SendCommand, AssumeRole into a sensitive role, GetSessionToken, etc.), find the flows that the same source IP produced inside the window. The result is a “context bundle” per event.
- Identity-to-flow mapping. CloudTrail tells you which IAM principal made an API call from which source IP. VPC Flow Logs tell you what that source IP did at the network layer in the same window. Join them — every flow is now attributed to an identity, exactly as the UEBA pipeline in post #11 does, but driven by CloudTrail events rather than session metadata.
- LotC kill-chain matcher. Apply a library of LotC sequence patterns (see next section) to the identity-correlated event stream. Use the Markov-chain approach from post #5, but with state space extended to include cloud-API events. Each pattern match produces a kill-chain alert with the full reconstructed sequence attached.
- SOC + cloud-engineering alert. Like the Kubernetes detection in post #10, LotC requires both SOC and cloud-engineering triage. The cloud-engineering team knows which API sequences are part of legitimate operator activity; the SOC owns the response if the sequence is not.
Seven LotC Kill-Chain Sequences to Hunt
Each sequence below is a real-world attack pattern observed in the wild during 2024–2026. Pattern-match these as state sequences extended with cloud-API events.
1. Stolen-credentials → SSM Session → IMDS pivot
AssumeRole (anomalous source IP) → StartSession (interactive) → VPC Flow to 169.254.169.254 → AssumeRole (different role, escalated). The IMDS hop is the giveaway — operators love it because it gives them a new credential that survives a Session Manager disconnection.
2. Run Command sweep → EBS snapshot exfiltration
SendCommand to multiple instances → DescribeVolumes → CreateSnapshot → ModifySnapshotAttribute (share to external account). The cross-account snapshot share is the exfil moment; the surrounding events are the staging.
3. Step Functions / Lambda orchestrator
CreateStateMachine (operator code) → CreateFunction (or UpdateFunctionCode) → StartExecution → CloudWatch Logs delivery to external account. Operator-controlled Step Functions running attacker code, with logs streamed out via cross-account CloudWatch.
4. SQS-as-C2 channel
CreateQueue (operator-controlled) → SetQueueAttributes (allow external sendmessage) → CreateFunction (subscriber Lambda) → recurring ReceiveMessage / SendMessage from attacker account. The fingerprint is the recurring cross-account SQS access pattern.
5. Cross-account bucket-policy exfil
PutBucketPolicy (allow cross-account read) → GetObject (high volume from attacker account) → DeleteBucketPolicy (clean up). The policy change and the volume of reads are visible in CloudTrail; the bytes transferred are visible in VPC Flow Logs to S3.
6. Bedrock-as-exfil-channel
InvokeModel with large prompts containing sensitive data → write completions to attacker-controlled S3. The Bedrock pipeline acts as a covert exfil channel where the “exfiltrated” data is laundered through model invocation.
7. IAM-role chaining for source-IP laundering
GetSessionToken from operator's account → AssumeRole into target account → API actions from within the target account's VPC, source IP looks internal. The source IP for the final actions appears internal because the calls come from within the target VPC. CloudTrail correctly attributes them, but the network layer sees nothing strange.
Feature Engineering — Across Both Telemetry Sources
| Feature | Source | Formula / method | What it captures |
|---|---|---|---|
| SSM session frequency | CloudTrail (StartSession) | sessions per identity per day | SSM as remote shell |
| Run Command target diversity | CloudTrail (SendCommand) | distinct instance targets per identity per hour | Sweep behaviour |
| IMDS pivot flag | VPC Flow Logs | flow to 169.254.169.254 from EC2 with active SSM session | Credential laundering signature |
| AssumeRole chain depth | CloudTrail (AssumeRole) | chained AssumeRoles within identity session | Identity-chain anomaly |
| Cross-account share events | CloudTrail (ModifySnapshotAttribute / PutBucketPolicy) | events sharing resource externally | Exfil staging |
| Step Functions API velocity | CloudTrail (sfn:* events) | state-machine creations per identity | Orchestrator-build signature |
| SQS cross-account pattern | CloudTrail + VPC Flow | recurring cross-account queue activity | SQS-as-C2 signature |
| Anomalous source-IP geolocation | CloudTrail userIdentity sessionContext | distance from identity baseline | Stolen-credential signal |
| Network-flow-without-CloudTrail-event | VPC Flow Logs + CloudTrail | flows from instance with no preceding console/API auth | Backdoor identity signal |
| CloudTrail-event-without-network-flow | CloudTrail + VPC Flow Logs | API calls that should produce flow but didn’t | Phantom-API-call signal |
Athena SQL — Time-Window Fusion Join
The fusion join is the heart of the pipeline. Done naïvely, joining a 100M-row CloudTrail table against a 10B-row VPC Flow Log table produces a query that will not finish before your cluster credit runs out. The trick is to filter both sides to the events of interest first, then join.
WITH lotc_events AS (
SELECT eventtime, useridentity.arn AS identity_arn,
eventname, sourceipaddress, awsregion, requestparameters,
CAST(from_iso8601_timestamp(eventtime) AS BIGINT) AS event_epoch
FROM cloudtrail_logs
WHERE eventname IN (
'StartSession','SendCommand','AssumeRole','GetSessionToken',
'ModifySnapshotAttribute','PutBucketPolicy','CreateStateMachine',
'UpdateFunctionCode','SetQueueAttributes','CreateQueue',
'InvokeModel','RunInstances','DescribeVolumes','CreateSnapshot'
)
AND day BETWEEN '2026/05/09' AND '2026/05/15'
),
event_flows AS (
SELECT e.eventtime, e.identity_arn, e.eventname, e.sourceipaddress,
f.srcaddr AS flow_src, f.dstaddr AS flow_dst, f.dstport, f.bytes,
f.start AS flow_start
FROM lotc_events e
LEFT JOIN central_vpc_flow_logs f
ON f.srcaddr = e.sourceipaddress
AND f.start BETWEEN e.event_epoch - 60 AND e.event_epoch + 60
AND f.day BETWEEN '2026/05/09' AND '2026/05/15'
)
SELECT identity_arn,
COUNT(*) AS event_count,
COUNT(DISTINCT eventname) AS event_diversity,
COUNT(*) FILTER (WHERE eventname = 'StartSession') AS ssm_sessions,
COUNT(*) FILTER (WHERE eventname = 'ModifySnapshotAttribute') AS snapshot_shares,
COUNT(*) FILTER (WHERE flow_dst = '169.254.169.254') AS imds_pivots,
COUNT(*) FILTER (WHERE eventname = 'AssumeRole') AS assume_role_count
FROM event_flows
GROUP BY identity_arn
HAVING (ssm_sessions > 0 AND imds_pivots > 0)
OR snapshot_shares > 0
OR (event_diversity >= 5 AND assume_role_count > 3)
ORDER BY event_count DESC;
The output is your LotC kill-chain candidate queue — identities exhibiting at least one of the high-risk combinations (SSM + IMDS, snapshot sharing, or diverse-API-call escalation). Per row, the SOC sees the identity, the count of each high-risk event class, and can drill into the underlying CloudTrail events for triage.
Reconstructing the Kill Chain Narrative
An alert that says “identity X has 12 high-risk events in the last hour” is useful but not actionable. The chain reconstructor turns it into a narrative:
2026-05-15 09:14:22 — AssumeRole AWSReservedSSO_AdminAccess from 47.x.x.x (Russia)
[identity: arn:aws:sts::1234:assumed-role/.../[email protected]]
2026-05-15 09:14:48 — StartSession ssm:i-0abc... (production-jumpbox)
2026-05-15 09:15:31 — flow 10.x.x.x → 169.254.169.254 (IMDS)
2026-05-15 09:15:33 — AssumeRole EC2-DataAccess-Role [credential laundering]
2026-05-15 09:17:02 — DescribeVolumes (8 prod volumes enumerated)
2026-05-15 09:19:11 — CreateSnapshot vol-prod-customer-data
2026-05-15 09:19:45 — ModifySnapshotAttribute snap-xyz (shared to acct 999988887777)
[external account — not in org]
total: 5m23s, 7 events, kill chain confirmed
The Lambda that produces this narrative reads the alert, fetches the full event window for the identity, runs the chain-matcher, and emits both the structured alert and the human-readable timeline. Triage time drops from “30 minutes investigating which API calls came first” to “I see what happened and I have the IAM principal to act against.”
Limits and False-Positive Sources
- Legitimate automation. Terraform, CloudFormation, CDK, Pulumi, and other IaC tooling produce high-velocity AssumeRole + Run Command + create-resource sequences that look exactly like an attacker’s kill chain. Allow-list by automation role ARN.
- Incident response activity. When the SOC is responding to a real incident, the responders themselves trigger LotC-pattern alerts. Add an “investigation mode” flag for known responders.
- Cost-optimisation tooling. Spot Cleanup, Cloud Custodian, AWS Config remediations, and other governance tools share snapshots, modify bucket policies, and execute SendCommand across fleets. Treat them as automation and tag accordingly.
- Cross-account audit roles. Security Hub, Macie, GuardDuty themselves use cross-account AssumeRole patterns. Allow-list by destination role ARN.
- Disaster-recovery drills. DR exercises legitimately involve snapshot sharing, instance launches in different accounts, and broad API velocity. Coordinate with DR teams to suppress during scheduled exercises.
MITRE ATT&CK Techniques Covered (Cloud Matrix)
| ATT&CK ID | Technique / sub-technique | Coverage | Hunter notes |
|---|---|---|---|
| T1078.004 | Valid Accounts: Cloud Accounts | Full | The entry vector for every LotC chain |
| T1098 | Account Manipulation | Full | — |
| T1098.001 | Account Manipulation: Additional Cloud Credentials | Full | — |
| T1098.003 | Account Manipulation: Additional Cloud Roles | Full | AssumeRole chain |
| T1552.005 | Unsecured Credentials: Cloud Instance Metadata API | Full | IMDS pivot is direct |
| T1059 | Command and Scripting Interpreter | Partial | SSM Run Command for execution |
| T1648 | Serverless Execution | Full | Step Functions / Lambda orchestrator |
| T1578 | Modify Cloud Compute Infrastructure | Full | — |
| T1578.001 | Create Snapshot | Full | Snapshot-exfil chain |
| T1537 | Transfer Data to Cloud Account | Full | The exfil endpoint |
| T1530 | Data from Cloud Storage | Full | S3 / EBS plane |
| T1567 | Exfiltration Over Web Service | Full | SQS / SNS / Lambda channels |
| T1102 | Web Service | Full | — |
| T1556 | Modify Authentication Process | Partial | Pair with IdP-side detection |
| T1199 | Trusted Relationship | Partial | Cross-account share events |
| T1219 | Remote Access Software (SSM is the cloud-native equivalent) | Full | SSM Session Manager |
Adversary emulation. The cleanest LotC emulation framework is open-source cloud attack-emulation tooling from commercial observability vendors — it has purpose-built atomics for every primitive listed above, runs against your own account, leaves no malware behind. older cloud-attack frameworks is the older, broader cloud-attack framework. Run both in a lab account; confirm the pipeline scores each technique.
Adversary groups. LotC techniques are the hallmark of modern cloud-focused intrusion sets — G1015 — Scattered Spider, G0102 — Wizard Spider, and most of the financially-motivated cloud-ransom affiliates. State-sponsored actors increasingly favour LotC because attribution is harder than malware-based campaigns.
D3FEND mapping. D3-UBA (User Behavior Analysis) for the identity layer, D3-NTA for the network layer, fused into a single detection.
Closing the Series — Reading Order for New Hunters
If you are arriving fresh and want to deploy these detections in order of priority:
- Start with the VPC Flow Logs primer if you have not already enabled the basics.
- Build the FFT C2 beacon detection (post #1) — the broadest single hunt with the highest yield-to-effort ratio.
- Add lateral movement graph detection (post #2) and the Markov kill-chain (post #5) for the structural and sequential layers.
- Add this LotC fusion pipeline once CloudTrail is well-ingested — it is the apex correlation.
- Backfill the specialist hunts (DGA, TLS fingerprinting, cryptojacking, Tor, Kubernetes, UEBA) as your team capacity allows.
By the end of the series, every dimension of network and identity signal in your AWS environment is being analysed by a pipeline that runs on telemetry you already pay for. Total infrastructure cost for a mid-size environment: under $200/month in Athena + Lambda. Total engineering investment: roughly six engineer-weeks for the full deployment. The return is detection coverage for the entire MITRE ATT&CK cloud matrix at a depth most enterprise SOCs do not currently have.
Final Thoughts
Living-off-the-Cloud is the future of cloud intrusion. The attackers have already adopted it; the defenders are catching up. The fusion pipeline above is the minimum baseline every cloud-native SOC should be running in 2026. Build it, validate it against open-source cloud attack-emulation tooling, and tune until the queue runs at single-digit alerts per day. The identities that surface in that queue are nearly always worth the conversation.
If this series helped your team — or if you found a pattern we missed — get in touch. We are planning a deep-dive series on the IAM side of identity hunting and a separate one on container-image supply-chain attacks. Reader stories shape what we cover next.
Happy threat hunting.
#threathunting #livingoffthecloud #cloudtrail #vpcflowlogs #awssecurity #ssm #lotc #cloudkillchain #soc #blueteam #detectionengineering #mitreattack #cloudsecurity









